From 36dd51cacdd46a3c2755ef5b9b5ccc67c65dd9a1 Mon Sep 17 00:00:00 2001 From: Shayan Patel Date: Wed, 30 Oct 2024 11:19:57 -0400 Subject: [PATCH 01/74] WIP: enable pg_tracing --- src/charm.py | 1 + src/constants.py | 2 +- templates/patroni.yml.j2 | 10 +++++++++- 3 files changed, 11 insertions(+), 2 deletions(-) diff --git a/src/charm.py b/src/charm.py index b228e15ad0..fe79b6b6d7 100755 --- a/src/charm.py +++ b/src/charm.py @@ -1701,6 +1701,7 @@ def update_config(self, is_creating_backup: bool = False) -> bool: restore_to_latest=self.app_peer_data.get("restore-to-time", None) == "latest", stanza=self.app_peer_data.get("stanza", self.unit_peer_data.get("stanza")), restore_stanza=self.app_peer_data.get("restore-stanza"), + tracing_endpoint_config=self._tracing_endpoint_config, parameters=pg_parameters, ) if not self._is_workload_running: diff --git a/src/constants.py b/src/constants.py index f55769ec0b..53e6e18180 100644 --- a/src/constants.py +++ b/src/constants.py @@ -34,7 +34,7 @@ SNAP_PACKAGES = [ ( POSTGRESQL_SNAP_NAME, - {"revision": {"aarch64": "132", "x86_64": "133"}}, + {"revision": {"aarch64": "132", "x86_64": "134"}}, ) ] diff --git a/templates/patroni.yml.j2 b/templates/patroni.yml.j2 index bd3f87154a..d3e9d19f3b 100644 --- a/templates/patroni.yml.j2 +++ b/templates/patroni.yml.j2 @@ -102,7 +102,15 @@ bootstrap: log_truncate_on_rotation: 'on' logging_collector: 'on' wal_level: logical - shared_preload_libraries: 'timescaledb,pgaudit' + shared_preload_libraries: 'pg_tracing,pgaudit' + {%- if tracing_endpoint_config %} + compute_query_id = on + pg_tracing.max_span = 10000 + pg_tracing.track = all + pg_tracing.otel_endpoint = {{tracing_endpoint_config}}/v1/traces + pg_tracing.otel_naptime = 2000 + pg_tracing.sample_rate = 1.0 + {% endif %} {%- if pg_parameters %} {%- for key, value in pg_parameters.items() %} {{key}}: {{value}} From a7ebd834a2893ef509970f2f946374aa5d18c31c Mon Sep 17 00:00:00 2001 From: Lucas Gameiro Borges Date: Wed, 30 Oct 2024 15:55:52 +0000 Subject: [PATCH 02/74] adapt render patroni function --- src/cluster.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/cluster.py b/src/cluster.py index c77d801375..ecd062d9a8 100644 --- a/src/cluster.py +++ b/src/cluster.py @@ -579,6 +579,7 @@ def render_patroni_yml_file( pitr_target: str | None = None, restore_timeline: str | None = None, restore_to_latest: bool = False, + tracing_endpoint_config: str | None = None, parameters: dict[str, str] | None = None, ) -> None: """Render the Patroni configuration file. @@ -631,6 +632,7 @@ def render_patroni_yml_file( version=self.get_postgresql_version().split(".")[0], minority_count=self.planned_units // 2, pg_parameters=parameters, + tracing_endpoint_config=tracing_endpoint_config, primary_cluster_endpoint=self.charm.async_replication.get_primary_cluster_endpoint(), extra_replication_endpoints=self.charm.async_replication.get_standby_endpoints(), raft_password=self.raft_password, From a6ab1333890ac5d86eadff12aaf22109b81801ad Mon Sep 17 00:00:00 2001 From: Shayan Patel Date: Thu, 31 Oct 2024 09:44:21 -0400 Subject: [PATCH 03/74] Move pg_tracing conf to the correct location --- templates/patroni.yml.j2 | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/templates/patroni.yml.j2 b/templates/patroni.yml.j2 index d3e9d19f3b..aaf39d30e5 100644 --- a/templates/patroni.yml.j2 +++ b/templates/patroni.yml.j2 @@ -103,14 +103,6 @@ bootstrap: logging_collector: 'on' wal_level: logical shared_preload_libraries: 'pg_tracing,pgaudit' - {%- if tracing_endpoint_config %} - compute_query_id = on - pg_tracing.max_span = 10000 - pg_tracing.track = all - pg_tracing.otel_endpoint = {{tracing_endpoint_config}}/v1/traces - pg_tracing.otel_naptime = 2000 - pg_tracing.sample_rate = 1.0 - {% endif %} {%- if pg_parameters %} {%- for key, value in pg_parameters.items() %} {{key}}: {{value}} @@ -159,6 +151,14 @@ postgresql: ssl_cert_file: {{ conf_path }}/cert.pem ssl_key_file: {{ conf_path }}/key.pem {%- endif %} + {%- if tracing_endpoint_config %} + compute_query_id: on + pg_tracing.max_span: 10000 + pg_tracing.track: all + pg_tracing.otel_endpoint: {{tracing_endpoint_config}}/v1/traces + pg_tracing.otel_naptime: 2000 + pg_tracing.sample_rate: 1.0 + {%- endif %} unix_socket_directories: /tmp {%- if pg_parameters %} {%- for key, value in pg_parameters.items() %} From 14157ac3761a24e6f8254f7dca7934ba78b0ac7d Mon Sep 17 00:00:00 2001 From: Lucas Gameiro Borges Date: Wed, 6 Nov 2024 20:17:36 +0000 Subject: [PATCH 04/74] fix lint and unit tests --- src/cluster.py | 1 + tests/unit/test_charm.py | 2 ++ 2 files changed, 3 insertions(+) diff --git a/src/cluster.py b/src/cluster.py index ecd062d9a8..ea39b205c9 100644 --- a/src/cluster.py +++ b/src/cluster.py @@ -595,6 +595,7 @@ def render_patroni_yml_file( pitr_target: point-in-time-recovery target for the restore. restore_timeline: timeline to restore from. restore_to_latest: restore all the WAL transaction logs from the stanza. + tracing_endpoint_config: endpoint for tracing data. parameters: PostgreSQL parameters to be added to the postgresql.conf file. """ # Open the template patroni.yml file. diff --git a/tests/unit/test_charm.py b/tests/unit/test_charm.py index 650dbe689d..be8a721504 100644 --- a/tests/unit/test_charm.py +++ b/tests/unit/test_charm.py @@ -1295,6 +1295,7 @@ def test_update_config(harness): restore_timeline=None, pitr_target=None, restore_to_latest=False, + tracing_endpoint_config=None, parameters={"test": "test"}, ) _handle_postgresql_restart_need.assert_called_once_with(False) @@ -1318,6 +1319,7 @@ def test_update_config(harness): restore_timeline=None, pitr_target=None, restore_to_latest=False, + tracing_endpoint_config=None, parameters={"test": "test"}, ) _handle_postgresql_restart_need.assert_called_once() From 66330507a283011decc108748182a11cda0ed816 Mon Sep 17 00:00:00 2001 From: Lucas Gameiro Borges Date: Wed, 6 Nov 2024 21:56:42 +0000 Subject: [PATCH 05/74] use ubuntu 24.04 as base --- charmcraft.yaml | 4 ++-- terraform/variables.tf | 2 +- tests/integration/helpers.py | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/charmcraft.yaml b/charmcraft.yaml index e5e0208749..5efdadb089 100644 --- a/charmcraft.yaml +++ b/charmcraft.yaml @@ -4,10 +4,10 @@ type: charm bases: - name: ubuntu - channel: "22.04" + channel: "24.04" architectures: [amd64] - name: ubuntu - channel: "22.04" + channel: "24.04" architectures: [arm64] parts: charm: diff --git a/terraform/variables.tf b/terraform/variables.tf index ede475f37a..aa24ebbc71 100644 --- a/terraform/variables.tf +++ b/terraform/variables.tf @@ -24,7 +24,7 @@ variable "revision" { variable "base" { description = "Application base" type = string - default = "ubuntu@22.04" + default = "ubuntu@24.04" } variable "units" { diff --git a/tests/integration/helpers.py b/tests/integration/helpers.py index 2d587b38d5..d0a32e58c6 100644 --- a/tests/integration/helpers.py +++ b/tests/integration/helpers.py @@ -30,7 +30,7 @@ wait_fixed, ) -CHARM_BASE = "ubuntu@22.04" +CHARM_BASE = "ubuntu@24.04" METADATA = yaml.safe_load(Path("./metadata.yaml").read_text()) DATABASE_APP_NAME = METADATA["name"] STORAGE_PATH = METADATA["storage"]["pgdata"]["location"] From fd5fa46cee7504b493a4cc2c0f50677e35dfed66 Mon Sep 17 00:00:00 2001 From: Lucas Gameiro Borges Date: Thu, 7 Nov 2024 14:00:32 +0000 Subject: [PATCH 06/74] fix issues + revert base to jammy --- charmcraft.yaml | 4 ++-- lib/charms/postgresql_k8s/v0/postgresql.py | 15 +++++++++------ src/constants.py | 2 +- src/locales.py | 10 +++++++++- templates/patroni.yml.j2 | 2 +- terraform/variables.tf | 2 +- tests/integration/helpers.py | 2 +- 7 files changed, 24 insertions(+), 13 deletions(-) diff --git a/charmcraft.yaml b/charmcraft.yaml index 5efdadb089..e5e0208749 100644 --- a/charmcraft.yaml +++ b/charmcraft.yaml @@ -4,10 +4,10 @@ type: charm bases: - name: ubuntu - channel: "24.04" + channel: "22.04" architectures: [amd64] - name: ubuntu - channel: "24.04" + channel: "22.04" architectures: [arm64] parts: charm: diff --git a/lib/charms/postgresql_k8s/v0/postgresql.py b/lib/charms/postgresql_k8s/v0/postgresql.py index 2f2b2f9990..3490605b38 100644 --- a/lib/charms/postgresql_k8s/v0/postgresql.py +++ b/lib/charms/postgresql_k8s/v0/postgresql.py @@ -339,10 +339,12 @@ def enable_disable_extensions(self, extensions: Dict[str, bool], database: str = # Enable/disabled the extension in each database. for database in databases: - with self._connect_to_database( - database=database - ) as connection, connection.cursor() as cursor: + connection = self._connect_to_database(database=database) + connection.autocommit = True + with connection.cursor() as cursor: for extension, enable in ordered_extensions.items(): + if extension == "postgis": + cursor.execute("SET pgaudit.log = 'none';") cursor.execute( f"CREATE EXTENSION IF NOT EXISTS {extension};" if enable @@ -364,6 +366,7 @@ def _generate_database_privileges_statements( ) -> List[Composed]: """Generates a list of databases privileges statements.""" statements = [] + statements.append(sql.SQL("GRANT USAGE, CREATE ON SCHEMA public TO admin;")) if relations_accessing_this_database == 1: statements.append( sql.SQL( @@ -412,11 +415,11 @@ def _generate_database_privileges_statements( sql.SQL("GRANT ALL PRIVILEGES ON ALL FUNCTIONS IN SCHEMA {} TO {};").format( schema, sql.Identifier(user) ), - sql.SQL("GRANT USAGE ON SCHEMA {} TO {};").format( + sql.SQL("GRANT USAGE, CREATE ON SCHEMA {} TO {};").format( schema, sql.Identifier(user) ), - sql.SQL("GRANT CREATE ON SCHEMA {} TO {};").format( - schema, sql.Identifier(user) + sql.SQL("GRANT USAGE, CREATE ON SCHEMA {} TO admin;").format( + schema ), ]) return statements diff --git a/src/constants.py b/src/constants.py index 53e6e18180..17d18bbf5c 100644 --- a/src/constants.py +++ b/src/constants.py @@ -34,7 +34,7 @@ SNAP_PACKAGES = [ ( POSTGRESQL_SNAP_NAME, - {"revision": {"aarch64": "132", "x86_64": "134"}}, + {"revision": {"aarch64": "132", "x86_64": "135"}}, ) ] diff --git a/src/locales.py b/src/locales.py index 7fd67a0ee8..d9422a958b 100644 --- a/src/locales.py +++ b/src/locales.py @@ -8,7 +8,6 @@ "aa_DJ", "aa_DJ.utf8", "aa_ER", - "aa_ER@saaho", "aa_ET", "af_ZA", "af_ZA.utf8", @@ -97,6 +96,7 @@ "chr_US", "ckb_IQ", "cmn_TW", + "crh_RU", "crh_UA", "cs_CZ", "cs_CZ.utf8", @@ -248,6 +248,7 @@ "ga_IE", "ga_IE.utf8", "ga_IE@euro", + "gbm_IN", "gd_GB", "gd_GB.utf8", "gez_ER", @@ -308,6 +309,7 @@ "ks_IN@devanagari", "ku_TR", "ku_TR.utf8", + "kv_RU", "kw_GB", "kw_GB.utf8", "ky_KG", @@ -389,6 +391,7 @@ "pt_PT@euro", "quz_PE", "raj_IN", + "rif_MA", "ro_RO", "ro_RO.utf8", "ru_RU", @@ -430,8 +433,10 @@ "sr_RS", "sr_RS@latin", "ss_ZA", + "ssy_ER", "st_ZA", "st_ZA.utf8", + "su_ID", "sv_FI", "sv_FI.utf8", "sv_FI@euro", @@ -440,6 +445,7 @@ "sv_SE.utf8", "sw_KE", "sw_TZ", + "syr", "szl_PL", "ta_IN", "ta_LK", @@ -458,6 +464,7 @@ "tl_PH.utf8", "tn_ZA", "to_TO", + "tok", "tpi_PG", "tr_CY", "tr_CY.utf8", @@ -491,6 +498,7 @@ "yo_NG", "yue_HK", "yuw_PG", + "zgh_MA", "zh_CN", "zh_CN.gb18030", "zh_CN.gbk", diff --git a/templates/patroni.yml.j2 b/templates/patroni.yml.j2 index aaf39d30e5..9885271226 100644 --- a/templates/patroni.yml.j2 +++ b/templates/patroni.yml.j2 @@ -102,7 +102,7 @@ bootstrap: log_truncate_on_rotation: 'on' logging_collector: 'on' wal_level: logical - shared_preload_libraries: 'pg_tracing,pgaudit' + shared_preload_libraries: 'timescaledb,pg_tracing,pgaudit' {%- if pg_parameters %} {%- for key, value in pg_parameters.items() %} {{key}}: {{value}} diff --git a/terraform/variables.tf b/terraform/variables.tf index aa24ebbc71..ede475f37a 100644 --- a/terraform/variables.tf +++ b/terraform/variables.tf @@ -24,7 +24,7 @@ variable "revision" { variable "base" { description = "Application base" type = string - default = "ubuntu@24.04" + default = "ubuntu@22.04" } variable "units" { diff --git a/tests/integration/helpers.py b/tests/integration/helpers.py index d0a32e58c6..2d587b38d5 100644 --- a/tests/integration/helpers.py +++ b/tests/integration/helpers.py @@ -30,7 +30,7 @@ wait_fixed, ) -CHARM_BASE = "ubuntu@24.04" +CHARM_BASE = "ubuntu@22.04" METADATA = yaml.safe_load(Path("./metadata.yaml").read_text()) DATABASE_APP_NAME = METADATA["name"] STORAGE_PATH = METADATA["storage"]["pgdata"]["location"] From 8be9d0fb60c961ec7629fa86721139f8ae7153d9 Mon Sep 17 00:00:00 2001 From: Lucas Gameiro Borges Date: Thu, 7 Nov 2024 19:20:02 +0000 Subject: [PATCH 07/74] fix queries for plugin testing --- tests/integration/test_plugins.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/tests/integration/test_plugins.py b/tests/integration/test_plugins.py index 7b3d5d3ce1..f5f7a3d5df 100644 --- a/tests/integration/test_plugins.py +++ b/tests/integration/test_plugins.py @@ -62,8 +62,10 @@ HYPOPG_EXTENSION_STATEMENT = "CREATE TABLE hypopg_test (id integer, val text); SELECT hypopg_create_index('CREATE INDEX ON hypopg_test (id)');" IP4R_EXTENSION_STATEMENT = "CREATE TABLE ip4r_test (ip ip4);" JSONB_PLPERL_EXTENSION_STATEMENT = "CREATE OR REPLACE FUNCTION jsonb_plperl_test(val jsonb) RETURNS jsonb TRANSFORM FOR TYPE jsonb LANGUAGE plperl as $$ return $_[0]; $$;" -ORAFCE_EXTENSION_STATEMENT = "SELECT add_months(date '2005-05-31',1);" -PG_SIMILARITY_EXTENSION_STATEMENT = "SHOW pg_similarity.levenshtein_threshold;" +ORAFCE_EXTENSION_STATEMENT = "SELECT oracle.add_months(date '2005-05-31',1);" +PG_SIMILARITY_EXTENSION_STATEMENT = ( + "SET pg_similarity.levenshtein_threshold = 0.7; SELECT 'aaa', 'aab', lev('aaa','aab');" +) PLPERL_EXTENSION_STATEMENT = "CREATE OR REPLACE FUNCTION plperl_test(name text) RETURNS text AS $$ return $_SHARED{$_[0]}; $$ LANGUAGE plperl;" PREFIX_EXTENSION_STATEMENT = "SELECT '123'::prefix_range @> '123456';" RDKIT_EXTENSION_STATEMENT = "SELECT is_valid_smiles('CCC');" From a9ca5d5a22476d86b0904f912979db510a45d1f5 Mon Sep 17 00:00:00 2001 From: Lucas Gameiro Borges Date: Tue, 19 Nov 2024 20:22:41 +0000 Subject: [PATCH 08/74] downgrade psycopg2 version --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index ea24e76a47..1b71f31d3f 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -12,7 +12,7 @@ boto3 = "^1.35.49" pgconnstr = "^1.0.1" requests = "^2.32.3" tenacity = "^9.0.0" -psycopg2 = "^2.9.10" +psycopg2 = "2.9.4" cosl = "^0.0.42" pydantic = "^1.10.18" poetry-core = "^1.9.1" From 2090059d5ddbbb879a064acf762420333da6d124 Mon Sep 17 00:00:00 2001 From: Lucas Gameiro Borges Date: Tue, 19 Nov 2024 20:36:39 +0000 Subject: [PATCH 09/74] update lock file --- poetry.lock | 766 +++++++++++++++++++++++++--------------------------- 1 file changed, 368 insertions(+), 398 deletions(-) diff --git a/poetry.lock b/poetry.lock index 7b4d2ac24f..dcac0f8e31 100644 --- a/poetry.lock +++ b/poetry.lock @@ -122,38 +122,36 @@ files = [ [[package]] name = "bcrypt" -version = "4.2.0" +version = "4.2.1" description = "Modern password hashing for your software and your servers" optional = false python-versions = ">=3.7" files = [ - {file = "bcrypt-4.2.0-cp37-abi3-macosx_10_12_universal2.whl", hash = "sha256:096a15d26ed6ce37a14c1ac1e48119660f21b24cba457f160a4b830f3fe6b5cb"}, - {file = "bcrypt-4.2.0-cp37-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c02d944ca89d9b1922ceb8a46460dd17df1ba37ab66feac4870f6862a1533c00"}, - {file = "bcrypt-4.2.0-cp37-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1d84cf6d877918620b687b8fd1bf7781d11e8a0998f576c7aa939776b512b98d"}, - {file = "bcrypt-4.2.0-cp37-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:1bb429fedbe0249465cdd85a58e8376f31bb315e484f16e68ca4c786dcc04291"}, - {file = "bcrypt-4.2.0-cp37-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:655ea221910bcac76ea08aaa76df427ef8625f92e55a8ee44fbf7753dbabb328"}, - {file = "bcrypt-4.2.0-cp37-abi3-musllinux_1_1_aarch64.whl", hash = "sha256:1ee38e858bf5d0287c39b7a1fc59eec64bbf880c7d504d3a06a96c16e14058e7"}, - {file = "bcrypt-4.2.0-cp37-abi3-musllinux_1_1_x86_64.whl", hash = "sha256:0da52759f7f30e83f1e30a888d9163a81353ef224d82dc58eb5bb52efcabc399"}, - {file = "bcrypt-4.2.0-cp37-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:3698393a1b1f1fd5714524193849d0c6d524d33523acca37cd28f02899285060"}, - {file = "bcrypt-4.2.0-cp37-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:762a2c5fb35f89606a9fde5e51392dad0cd1ab7ae64149a8b935fe8d79dd5ed7"}, - {file = "bcrypt-4.2.0-cp37-abi3-win32.whl", hash = "sha256:5a1e8aa9b28ae28020a3ac4b053117fb51c57a010b9f969603ed885f23841458"}, - {file = "bcrypt-4.2.0-cp37-abi3-win_amd64.whl", hash = "sha256:8f6ede91359e5df88d1f5c1ef47428a4420136f3ce97763e31b86dd8280fbdf5"}, - {file = "bcrypt-4.2.0-cp39-abi3-macosx_10_12_universal2.whl", hash = "sha256:c52aac18ea1f4a4f65963ea4f9530c306b56ccd0c6f8c8da0c06976e34a6e841"}, - {file = "bcrypt-4.2.0-cp39-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3bbbfb2734f0e4f37c5136130405332640a1e46e6b23e000eeff2ba8d005da68"}, - {file = "bcrypt-4.2.0-cp39-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3413bd60460f76097ee2e0a493ccebe4a7601918219c02f503984f0a7ee0aebe"}, - {file = "bcrypt-4.2.0-cp39-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:8d7bb9c42801035e61c109c345a28ed7e84426ae4865511eb82e913df18f58c2"}, - {file = "bcrypt-4.2.0-cp39-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:3d3a6d28cb2305b43feac298774b997e372e56c7c7afd90a12b3dc49b189151c"}, - {file = "bcrypt-4.2.0-cp39-abi3-musllinux_1_1_aarch64.whl", hash = "sha256:9c1c4ad86351339c5f320ca372dfba6cb6beb25e8efc659bedd918d921956bae"}, - {file = "bcrypt-4.2.0-cp39-abi3-musllinux_1_1_x86_64.whl", hash = "sha256:27fe0f57bb5573104b5a6de5e4153c60814c711b29364c10a75a54bb6d7ff48d"}, - {file = "bcrypt-4.2.0-cp39-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:8ac68872c82f1add6a20bd489870c71b00ebacd2e9134a8aa3f98a0052ab4b0e"}, - {file = "bcrypt-4.2.0-cp39-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:cb2a8ec2bc07d3553ccebf0746bbf3d19426d1c6d1adbd4fa48925f66af7b9e8"}, - {file = "bcrypt-4.2.0-cp39-abi3-win32.whl", hash = "sha256:77800b7147c9dc905db1cba26abe31e504d8247ac73580b4aa179f98e6608f34"}, - {file = "bcrypt-4.2.0-cp39-abi3-win_amd64.whl", hash = "sha256:61ed14326ee023917ecd093ee6ef422a72f3aec6f07e21ea5f10622b735538a9"}, - {file = "bcrypt-4.2.0-pp310-pypy310_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:39e1d30c7233cfc54f5c3f2c825156fe044efdd3e0b9d309512cc514a263ec2a"}, - {file = "bcrypt-4.2.0-pp310-pypy310_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:f4f4acf526fcd1c34e7ce851147deedd4e26e6402369304220250598b26448db"}, - {file = "bcrypt-4.2.0-pp39-pypy39_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:1ff39b78a52cf03fdf902635e4c81e544714861ba3f0efc56558979dd4f09170"}, - {file = "bcrypt-4.2.0-pp39-pypy39_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:373db9abe198e8e2c70d12b479464e0d5092cc122b20ec504097b5f2297ed184"}, - {file = "bcrypt-4.2.0.tar.gz", hash = "sha256:cf69eaf5185fd58f268f805b505ce31f9b9fc2d64b376642164e9244540c1221"}, + {file = "bcrypt-4.2.1-cp37-abi3-macosx_10_12_universal2.whl", hash = "sha256:1340411a0894b7d3ef562fb233e4b6ed58add185228650942bdc885362f32c17"}, + {file = "bcrypt-4.2.1-cp37-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b1ee315739bc8387aa36ff127afc99120ee452924e0df517a8f3e4c0187a0f5f"}, + {file = "bcrypt-4.2.1-cp37-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8dbd0747208912b1e4ce730c6725cb56c07ac734b3629b60d4398f082ea718ad"}, + {file = "bcrypt-4.2.1-cp37-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:aaa2e285be097050dba798d537b6efd9b698aa88eef52ec98d23dcd6d7cf6fea"}, + {file = "bcrypt-4.2.1-cp37-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:76d3e352b32f4eeb34703370e370997065d28a561e4a18afe4fef07249cb4396"}, + {file = "bcrypt-4.2.1-cp37-abi3-musllinux_1_1_aarch64.whl", hash = "sha256:b7703ede632dc945ed1172d6f24e9f30f27b1b1a067f32f68bf169c5f08d0425"}, + {file = "bcrypt-4.2.1-cp37-abi3-musllinux_1_1_x86_64.whl", hash = "sha256:89df2aea2c43be1e1fa066df5f86c8ce822ab70a30e4c210968669565c0f4685"}, + {file = "bcrypt-4.2.1-cp37-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:04e56e3fe8308a88b77e0afd20bec516f74aecf391cdd6e374f15cbed32783d6"}, + {file = "bcrypt-4.2.1-cp37-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:cfdf3d7530c790432046c40cda41dfee8c83e29482e6a604f8930b9930e94139"}, + {file = "bcrypt-4.2.1-cp37-abi3-win32.whl", hash = "sha256:adadd36274510a01f33e6dc08f5824b97c9580583bd4487c564fc4617b328005"}, + {file = "bcrypt-4.2.1-cp37-abi3-win_amd64.whl", hash = "sha256:8c458cd103e6c5d1d85cf600e546a639f234964d0228909d8f8dbeebff82d526"}, + {file = "bcrypt-4.2.1-cp39-abi3-macosx_10_12_universal2.whl", hash = "sha256:8ad2f4528cbf0febe80e5a3a57d7a74e6635e41af1ea5675282a33d769fba413"}, + {file = "bcrypt-4.2.1-cp39-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:909faa1027900f2252a9ca5dfebd25fc0ef1417943824783d1c8418dd7d6df4a"}, + {file = "bcrypt-4.2.1-cp39-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:cde78d385d5e93ece5479a0a87f73cd6fa26b171c786a884f955e165032b262c"}, + {file = "bcrypt-4.2.1-cp39-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:533e7f3bcf2f07caee7ad98124fab7499cb3333ba2274f7a36cf1daee7409d99"}, + {file = "bcrypt-4.2.1-cp39-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:687cf30e6681eeda39548a93ce9bfbb300e48b4d445a43db4298d2474d2a1e54"}, + {file = "bcrypt-4.2.1-cp39-abi3-musllinux_1_1_aarch64.whl", hash = "sha256:041fa0155c9004eb98a232d54da05c0b41d4b8e66b6fc3cb71b4b3f6144ba837"}, + {file = "bcrypt-4.2.1-cp39-abi3-musllinux_1_1_x86_64.whl", hash = "sha256:f85b1ffa09240c89aa2e1ae9f3b1c687104f7b2b9d2098da4e923f1b7082d331"}, + {file = "bcrypt-4.2.1-cp39-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:c6f5fa3775966cca251848d4d5393ab016b3afed251163c1436fefdec3b02c84"}, + {file = "bcrypt-4.2.1-cp39-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:807261df60a8b1ccd13e6599c779014a362ae4e795f5c59747f60208daddd96d"}, + {file = "bcrypt-4.2.1-cp39-abi3-win32.whl", hash = "sha256:b588af02b89d9fad33e5f98f7838bf590d6d692df7153647724a7f20c186f6bf"}, + {file = "bcrypt-4.2.1-cp39-abi3-win_amd64.whl", hash = "sha256:e84e0e6f8e40a242b11bce56c313edc2be121cec3e0ec2d76fce01f6af33c07c"}, + {file = "bcrypt-4.2.1-pp310-pypy310_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:76132c176a6d9953cdc83c296aeaed65e1a708485fd55abf163e0d9f8f16ce0e"}, + {file = "bcrypt-4.2.1-pp310-pypy310_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:e158009a54c4c8bc91d5e0da80920d048f918c61a581f0a63e4e93bb556d362f"}, + {file = "bcrypt-4.2.1.tar.gz", hash = "sha256:6765386e3ab87f569b276988742039baab087b2cdb01e809d74e74503c2faafe"}, ] [package.extras] @@ -162,17 +160,17 @@ typecheck = ["mypy"] [[package]] name = "boto3" -version = "1.35.49" +version = "1.35.64" description = "The AWS SDK for Python" optional = false python-versions = ">=3.8" files = [ - {file = "boto3-1.35.49-py3-none-any.whl", hash = "sha256:b660c649a27a6b47a34f6f858f5bd7c3b0a798a16dec8dda7cbebeee80fd1f60"}, - {file = "boto3-1.35.49.tar.gz", hash = "sha256:ddecb27f5699ca9f97711c52b6c0652c2e63bf6c2bfbc13b819b4f523b4d30ff"}, + {file = "boto3-1.35.64-py3-none-any.whl", hash = "sha256:cdacf03fc750caa3aa0dbf6158166def9922c9d67b4160999ff8fc350662facc"}, + {file = "boto3-1.35.64.tar.gz", hash = "sha256:bc3fc12b41fa2c91e51ab140f74fb1544408a2b1e00f88a4c2369a66d18ddf20"}, ] [package.dependencies] -botocore = ">=1.35.49,<1.36.0" +botocore = ">=1.35.64,<1.36.0" jmespath = ">=0.7.1,<2.0.0" s3transfer = ">=0.10.0,<0.11.0" @@ -181,13 +179,13 @@ crt = ["botocore[crt] (>=1.21.0,<2.0a0)"] [[package]] name = "botocore" -version = "1.35.49" +version = "1.35.64" description = "Low-level, data-driven core of boto 3." optional = false python-versions = ">=3.8" files = [ - {file = "botocore-1.35.49-py3-none-any.whl", hash = "sha256:aed4d3643afd702920792b68fbe712a8c3847993820d1048cd238a6469354da1"}, - {file = "botocore-1.35.49.tar.gz", hash = "sha256:07d0c1325fdbfa49a4a054413dbdeab0a6030449b2aa66099241af2dac48afd8"}, + {file = "botocore-1.35.64-py3-none-any.whl", hash = "sha256:bbd96bf7f442b1d5e35b36f501076e4a588c83d8d84a1952e9ee1d767e5efb3e"}, + {file = "botocore-1.35.64.tar.gz", hash = "sha256:2f95c83f31c9e38a66995c88810fc638c829790e125032ba00ab081a2cf48cb9"}, ] [package.dependencies] @@ -462,73 +460,73 @@ typing-extensions = "*" [[package]] name = "coverage" -version = "7.6.4" +version = "7.6.7" description = "Code coverage measurement for Python" optional = false python-versions = ">=3.9" files = [ - {file = "coverage-7.6.4-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:5f8ae553cba74085db385d489c7a792ad66f7f9ba2ee85bfa508aeb84cf0ba07"}, - {file = "coverage-7.6.4-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:8165b796df0bd42e10527a3f493c592ba494f16ef3c8b531288e3d0d72c1f6f0"}, - {file = "coverage-7.6.4-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c7c8b95bf47db6d19096a5e052ffca0a05f335bc63cef281a6e8fe864d450a72"}, - {file = "coverage-7.6.4-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:8ed9281d1b52628e81393f5eaee24a45cbd64965f41857559c2b7ff19385df51"}, - {file = "coverage-7.6.4-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0809082ee480bb8f7416507538243c8863ac74fd8a5d2485c46f0f7499f2b491"}, - {file = "coverage-7.6.4-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:d541423cdd416b78626b55f123412fcf979d22a2c39fce251b350de38c15c15b"}, - {file = "coverage-7.6.4-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:58809e238a8a12a625c70450b48e8767cff9eb67c62e6154a642b21ddf79baea"}, - {file = "coverage-7.6.4-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:c9b8e184898ed014884ca84c70562b4a82cbc63b044d366fedc68bc2b2f3394a"}, - {file = "coverage-7.6.4-cp310-cp310-win32.whl", hash = "sha256:6bd818b7ea14bc6e1f06e241e8234508b21edf1b242d49831831a9450e2f35fa"}, - {file = "coverage-7.6.4-cp310-cp310-win_amd64.whl", hash = "sha256:06babbb8f4e74b063dbaeb74ad68dfce9186c595a15f11f5d5683f748fa1d172"}, - {file = "coverage-7.6.4-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:73d2b73584446e66ee633eaad1a56aad577c077f46c35ca3283cd687b7715b0b"}, - {file = "coverage-7.6.4-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:51b44306032045b383a7a8a2c13878de375117946d68dcb54308111f39775a25"}, - {file = "coverage-7.6.4-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0b3fb02fe73bed561fa12d279a417b432e5b50fe03e8d663d61b3d5990f29546"}, - {file = "coverage-7.6.4-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ed8fe9189d2beb6edc14d3ad19800626e1d9f2d975e436f84e19efb7fa19469b"}, - {file = "coverage-7.6.4-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b369ead6527d025a0fe7bd3864e46dbee3aa8f652d48df6174f8d0bac9e26e0e"}, - {file = "coverage-7.6.4-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:ade3ca1e5f0ff46b678b66201f7ff477e8fa11fb537f3b55c3f0568fbfe6e718"}, - {file = "coverage-7.6.4-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:27fb4a050aaf18772db513091c9c13f6cb94ed40eacdef8dad8411d92d9992db"}, - {file = "coverage-7.6.4-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:4f704f0998911abf728a7783799444fcbbe8261c4a6c166f667937ae6a8aa522"}, - {file = "coverage-7.6.4-cp311-cp311-win32.whl", hash = "sha256:29155cd511ee058e260db648b6182c419422a0d2e9a4fa44501898cf918866cf"}, - {file = "coverage-7.6.4-cp311-cp311-win_amd64.whl", hash = "sha256:8902dd6a30173d4ef09954bfcb24b5d7b5190cf14a43170e386979651e09ba19"}, - {file = "coverage-7.6.4-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:12394842a3a8affa3ba62b0d4ab7e9e210c5e366fbac3e8b2a68636fb19892c2"}, - {file = "coverage-7.6.4-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:2b6b4c83d8e8ea79f27ab80778c19bc037759aea298da4b56621f4474ffeb117"}, - {file = "coverage-7.6.4-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1d5b8007f81b88696d06f7df0cb9af0d3b835fe0c8dbf489bad70b45f0e45613"}, - {file = "coverage-7.6.4-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b57b768feb866f44eeed9f46975f3d6406380275c5ddfe22f531a2bf187eda27"}, - {file = "coverage-7.6.4-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5915fcdec0e54ee229926868e9b08586376cae1f5faa9bbaf8faf3561b393d52"}, - {file = "coverage-7.6.4-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:0b58c672d14f16ed92a48db984612f5ce3836ae7d72cdd161001cc54512571f2"}, - {file = "coverage-7.6.4-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:2fdef0d83a2d08d69b1f2210a93c416d54e14d9eb398f6ab2f0a209433db19e1"}, - {file = "coverage-7.6.4-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:8cf717ee42012be8c0cb205dbbf18ffa9003c4cbf4ad078db47b95e10748eec5"}, - {file = "coverage-7.6.4-cp312-cp312-win32.whl", hash = "sha256:7bb92c539a624cf86296dd0c68cd5cc286c9eef2d0c3b8b192b604ce9de20a17"}, - {file = "coverage-7.6.4-cp312-cp312-win_amd64.whl", hash = "sha256:1032e178b76a4e2b5b32e19d0fd0abbce4b58e77a1ca695820d10e491fa32b08"}, - {file = "coverage-7.6.4-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:023bf8ee3ec6d35af9c1c6ccc1d18fa69afa1cb29eaac57cb064dbb262a517f9"}, - {file = "coverage-7.6.4-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:b0ac3d42cb51c4b12df9c5f0dd2f13a4f24f01943627120ec4d293c9181219ba"}, - {file = "coverage-7.6.4-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f8fe4984b431f8621ca53d9380901f62bfb54ff759a1348cd140490ada7b693c"}, - {file = "coverage-7.6.4-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:5fbd612f8a091954a0c8dd4c0b571b973487277d26476f8480bfa4b2a65b5d06"}, - {file = "coverage-7.6.4-cp313-cp313-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dacbc52de979f2823a819571f2e3a350a7e36b8cb7484cdb1e289bceaf35305f"}, - {file = "coverage-7.6.4-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:dab4d16dfef34b185032580e2f2f89253d302facba093d5fa9dbe04f569c4f4b"}, - {file = "coverage-7.6.4-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:862264b12ebb65ad8d863d51f17758b1684560b66ab02770d4f0baf2ff75da21"}, - {file = "coverage-7.6.4-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:5beb1ee382ad32afe424097de57134175fea3faf847b9af002cc7895be4e2a5a"}, - {file = "coverage-7.6.4-cp313-cp313-win32.whl", hash = "sha256:bf20494da9653f6410213424f5f8ad0ed885e01f7e8e59811f572bdb20b8972e"}, - {file = "coverage-7.6.4-cp313-cp313-win_amd64.whl", hash = "sha256:182e6cd5c040cec0a1c8d415a87b67ed01193ed9ad458ee427741c7d8513d963"}, - {file = "coverage-7.6.4-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:a181e99301a0ae128493a24cfe5cfb5b488c4e0bf2f8702091473d033494d04f"}, - {file = "coverage-7.6.4-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:df57bdbeffe694e7842092c5e2e0bc80fff7f43379d465f932ef36f027179806"}, - {file = "coverage-7.6.4-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0bcd1069e710600e8e4cf27f65c90c7843fa8edfb4520fb0ccb88894cad08b11"}, - {file = "coverage-7.6.4-cp313-cp313t-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:99b41d18e6b2a48ba949418db48159d7a2e81c5cc290fc934b7d2380515bd0e3"}, - {file = "coverage-7.6.4-cp313-cp313t-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a6b1e54712ba3474f34b7ef7a41e65bd9037ad47916ccb1cc78769bae324c01a"}, - {file = "coverage-7.6.4-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:53d202fd109416ce011578f321460795abfe10bb901b883cafd9b3ef851bacfc"}, - {file = "coverage-7.6.4-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:c48167910a8f644671de9f2083a23630fbf7a1cb70ce939440cd3328e0919f70"}, - {file = "coverage-7.6.4-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:cc8ff50b50ce532de2fa7a7daae9dd12f0a699bfcd47f20945364e5c31799fef"}, - {file = "coverage-7.6.4-cp313-cp313t-win32.whl", hash = "sha256:b8d3a03d9bfcaf5b0141d07a88456bb6a4c3ce55c080712fec8418ef3610230e"}, - {file = "coverage-7.6.4-cp313-cp313t-win_amd64.whl", hash = "sha256:f3ddf056d3ebcf6ce47bdaf56142af51bb7fad09e4af310241e9db7a3a8022e1"}, - {file = "coverage-7.6.4-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:9cb7fa111d21a6b55cbf633039f7bc2749e74932e3aa7cb7333f675a58a58bf3"}, - {file = "coverage-7.6.4-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:11a223a14e91a4693d2d0755c7a043db43d96a7450b4f356d506c2562c48642c"}, - {file = "coverage-7.6.4-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a413a096c4cbac202433c850ee43fa326d2e871b24554da8327b01632673a076"}, - {file = "coverage-7.6.4-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:00a1d69c112ff5149cabe60d2e2ee948752c975d95f1e1096742e6077affd376"}, - {file = "coverage-7.6.4-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1f76846299ba5c54d12c91d776d9605ae33f8ae2b9d1d3c3703cf2db1a67f2c0"}, - {file = "coverage-7.6.4-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:fe439416eb6380de434886b00c859304338f8b19f6f54811984f3420a2e03858"}, - {file = "coverage-7.6.4-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:0294ca37f1ba500667b1aef631e48d875ced93ad5e06fa665a3295bdd1d95111"}, - {file = "coverage-7.6.4-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:6f01ba56b1c0e9d149f9ac85a2f999724895229eb36bd997b61e62999e9b0901"}, - {file = "coverage-7.6.4-cp39-cp39-win32.whl", hash = "sha256:bc66f0bf1d7730a17430a50163bb264ba9ded56739112368ba985ddaa9c3bd09"}, - {file = "coverage-7.6.4-cp39-cp39-win_amd64.whl", hash = "sha256:c481b47f6b5845064c65a7bc78bc0860e635a9b055af0df46fdf1c58cebf8e8f"}, - {file = "coverage-7.6.4-pp39.pp310-none-any.whl", hash = "sha256:3c65d37f3a9ebb703e710befdc489a38683a5b152242664b973a7b7b22348a4e"}, - {file = "coverage-7.6.4.tar.gz", hash = "sha256:29fc0f17b1d3fea332f8001d4558f8214af7f1d87a345f3a133c901d60347c73"}, + {file = "coverage-7.6.7-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:108bb458827765d538abcbf8288599fee07d2743357bdd9b9dad456c287e121e"}, + {file = "coverage-7.6.7-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:c973b2fe4dc445cb865ab369df7521df9c27bf40715c837a113edaa2aa9faf45"}, + {file = "coverage-7.6.7-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3c6b24007c4bcd0b19fac25763a7cac5035c735ae017e9a349b927cfc88f31c1"}, + {file = "coverage-7.6.7-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:acbb8af78f8f91b3b51f58f288c0994ba63c646bc1a8a22ad072e4e7e0a49f1c"}, + {file = "coverage-7.6.7-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ad32a981bcdedb8d2ace03b05e4fd8dace8901eec64a532b00b15217d3677dd2"}, + {file = "coverage-7.6.7-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:34d23e28ccb26236718a3a78ba72744212aa383141961dd6825f6595005c8b06"}, + {file = "coverage-7.6.7-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:e25bacb53a8c7325e34d45dddd2f2fbae0dbc230d0e2642e264a64e17322a777"}, + {file = "coverage-7.6.7-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:af05bbba896c4472a29408455fe31b3797b4d8648ed0a2ccac03e074a77e2314"}, + {file = "coverage-7.6.7-cp310-cp310-win32.whl", hash = "sha256:796c9b107d11d2d69e1849b2dfe41730134b526a49d3acb98ca02f4985eeff7a"}, + {file = "coverage-7.6.7-cp310-cp310-win_amd64.whl", hash = "sha256:987a8e3da7da4eed10a20491cf790589a8e5e07656b6dc22d3814c4d88faf163"}, + {file = "coverage-7.6.7-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:7e61b0e77ff4dddebb35a0e8bb5a68bf0f8b872407d8d9f0c726b65dfabe2469"}, + {file = "coverage-7.6.7-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:1a5407a75ca4abc20d6252efeb238377a71ce7bda849c26c7a9bece8680a5d99"}, + {file = "coverage-7.6.7-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:df002e59f2d29e889c37abd0b9ee0d0e6e38c24f5f55d71ff0e09e3412a340ec"}, + {file = "coverage-7.6.7-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:673184b3156cba06154825f25af33baa2671ddae6343f23175764e65a8c4c30b"}, + {file = "coverage-7.6.7-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e69ad502f1a2243f739f5bd60565d14a278be58be4c137d90799f2c263e7049a"}, + {file = "coverage-7.6.7-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:60dcf7605c50ea72a14490d0756daffef77a5be15ed1b9fea468b1c7bda1bc3b"}, + {file = "coverage-7.6.7-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:9c2eb378bebb2c8f65befcb5147877fc1c9fbc640fc0aad3add759b5df79d55d"}, + {file = "coverage-7.6.7-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:3c0317288f032221d35fa4cbc35d9f4923ff0dfd176c79c9b356e8ef8ef2dff4"}, + {file = "coverage-7.6.7-cp311-cp311-win32.whl", hash = "sha256:951aade8297358f3618a6e0660dc74f6b52233c42089d28525749fc8267dccd2"}, + {file = "coverage-7.6.7-cp311-cp311-win_amd64.whl", hash = "sha256:5e444b8e88339a2a67ce07d41faabb1d60d1004820cee5a2c2b54e2d8e429a0f"}, + {file = "coverage-7.6.7-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:f07ff574986bc3edb80e2c36391678a271d555f91fd1d332a1e0f4b5ea4b6ea9"}, + {file = "coverage-7.6.7-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:49ed5ee4109258973630c1f9d099c7e72c5c36605029f3a91fe9982c6076c82b"}, + {file = "coverage-7.6.7-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f3e8796434a8106b3ac025fd15417315d7a58ee3e600ad4dbcfddc3f4b14342c"}, + {file = "coverage-7.6.7-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a3b925300484a3294d1c70f6b2b810d6526f2929de954e5b6be2bf8caa1f12c1"}, + {file = "coverage-7.6.7-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3c42ec2c522e3ddd683dec5cdce8e62817afb648caedad9da725001fa530d354"}, + {file = "coverage-7.6.7-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:0266b62cbea568bd5e93a4da364d05de422110cbed5056d69339bd5af5685433"}, + {file = "coverage-7.6.7-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:e5f2a0f161d126ccc7038f1f3029184dbdf8f018230af17ef6fd6a707a5b881f"}, + {file = "coverage-7.6.7-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:c132b5a22821f9b143f87446805e13580b67c670a548b96da945a8f6b4f2efbb"}, + {file = "coverage-7.6.7-cp312-cp312-win32.whl", hash = "sha256:7c07de0d2a110f02af30883cd7dddbe704887617d5c27cf373362667445a4c76"}, + {file = "coverage-7.6.7-cp312-cp312-win_amd64.whl", hash = "sha256:fd49c01e5057a451c30c9b892948976f5d38f2cbd04dc556a82743ba8e27ed8c"}, + {file = "coverage-7.6.7-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:46f21663e358beae6b368429ffadf14ed0a329996248a847a4322fb2e35d64d3"}, + {file = "coverage-7.6.7-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:40cca284c7c310d622a1677f105e8507441d1bb7c226f41978ba7c86979609ab"}, + {file = "coverage-7.6.7-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:77256ad2345c29fe59ae861aa11cfc74579c88d4e8dbf121cbe46b8e32aec808"}, + {file = "coverage-7.6.7-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:87ea64b9fa52bf395272e54020537990a28078478167ade6c61da7ac04dc14bc"}, + {file = "coverage-7.6.7-cp313-cp313-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2d608a7808793e3615e54e9267519351c3ae204a6d85764d8337bd95993581a8"}, + {file = "coverage-7.6.7-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:cdd94501d65adc5c24f8a1a0eda110452ba62b3f4aeaba01e021c1ed9cb8f34a"}, + {file = "coverage-7.6.7-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:82c809a62e953867cf57e0548c2b8464207f5f3a6ff0e1e961683e79b89f2c55"}, + {file = "coverage-7.6.7-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:bb684694e99d0b791a43e9fc0fa58efc15ec357ac48d25b619f207c41f2fd384"}, + {file = "coverage-7.6.7-cp313-cp313-win32.whl", hash = "sha256:963e4a08cbb0af6623e61492c0ec4c0ec5c5cf74db5f6564f98248d27ee57d30"}, + {file = "coverage-7.6.7-cp313-cp313-win_amd64.whl", hash = "sha256:14045b8bfd5909196a90da145a37f9d335a5d988a83db34e80f41e965fb7cb42"}, + {file = "coverage-7.6.7-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:f2c7a045eef561e9544359a0bf5784b44e55cefc7261a20e730baa9220c83413"}, + {file = "coverage-7.6.7-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:5dd4e4a49d9c72a38d18d641135d2fb0bdf7b726ca60a103836b3d00a1182acd"}, + {file = "coverage-7.6.7-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5c95e0fa3d1547cb6f021ab72f5c23402da2358beec0a8e6d19a368bd7b0fb37"}, + {file = "coverage-7.6.7-cp313-cp313t-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f63e21ed474edd23f7501f89b53280014436e383a14b9bd77a648366c81dce7b"}, + {file = "coverage-7.6.7-cp313-cp313t-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ead9b9605c54d15be228687552916c89c9683c215370c4a44f1f217d2adcc34d"}, + {file = "coverage-7.6.7-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:0573f5cbf39114270842d01872952d301027d2d6e2d84013f30966313cadb529"}, + {file = "coverage-7.6.7-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:e2c8e3384c12dfa19fa9a52f23eb091a8fad93b5b81a41b14c17c78e23dd1d8b"}, + {file = "coverage-7.6.7-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:70a56a2ec1869e6e9fa69ef6b76b1a8a7ef709972b9cc473f9ce9d26b5997ce3"}, + {file = "coverage-7.6.7-cp313-cp313t-win32.whl", hash = "sha256:dbba8210f5067398b2c4d96b4e64d8fb943644d5eb70be0d989067c8ca40c0f8"}, + {file = "coverage-7.6.7-cp313-cp313t-win_amd64.whl", hash = "sha256:dfd14bcae0c94004baba5184d1c935ae0d1231b8409eb6c103a5fd75e8ecdc56"}, + {file = "coverage-7.6.7-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:37a15573f988b67f7348916077c6d8ad43adb75e478d0910957394df397d2874"}, + {file = "coverage-7.6.7-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:b6cce5c76985f81da3769c52203ee94722cd5d5889731cd70d31fee939b74bf0"}, + {file = "coverage-7.6.7-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a1ab9763d291a17b527ac6fd11d1a9a9c358280adb320e9c2672a97af346ac2c"}, + {file = "coverage-7.6.7-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6cf96ceaa275f071f1bea3067f8fd43bec184a25a962c754024c973af871e1b7"}, + {file = "coverage-7.6.7-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:aee9cf6b0134d6f932d219ce253ef0e624f4fa588ee64830fcba193269e4daa3"}, + {file = "coverage-7.6.7-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:2bc3e45c16564cc72de09e37413262b9f99167803e5e48c6156bccdfb22c8327"}, + {file = "coverage-7.6.7-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:623e6965dcf4e28a3debaa6fcf4b99ee06d27218f46d43befe4db1c70841551c"}, + {file = "coverage-7.6.7-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:850cfd2d6fc26f8346f422920ac204e1d28814e32e3a58c19c91980fa74d8289"}, + {file = "coverage-7.6.7-cp39-cp39-win32.whl", hash = "sha256:c296263093f099da4f51b3dff1eff5d4959b527d4f2f419e16508c5da9e15e8c"}, + {file = "coverage-7.6.7-cp39-cp39-win_amd64.whl", hash = "sha256:90746521206c88bdb305a4bf3342b1b7316ab80f804d40c536fc7d329301ee13"}, + {file = "coverage-7.6.7-pp39.pp310-none-any.whl", hash = "sha256:0ddcb70b3a3a57581b450571b31cb774f23eb9519c2aaa6176d3a84c9fc57671"}, + {file = "coverage-7.6.7.tar.gz", hash = "sha256:d79d4826e41441c9a118ff045e4bccb9fdbdcb1d02413e7ea6eb5c87b5439d24"}, ] [package.dependencies] @@ -599,20 +597,20 @@ files = [ [[package]] name = "deprecated" -version = "1.2.14" +version = "1.2.15" description = "Python @deprecated decorator to deprecate old python classes, functions or methods." optional = false -python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" +python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,>=2.7" files = [ - {file = "Deprecated-1.2.14-py2.py3-none-any.whl", hash = "sha256:6fac8b097794a90302bdbb17b9b815e732d3c4720583ff1b198499d78470466c"}, - {file = "Deprecated-1.2.14.tar.gz", hash = "sha256:e5323eb936458dccc2582dc6f9c322c852a775a27065ff2b0c4970b9d53d01b3"}, + {file = "Deprecated-1.2.15-py2.py3-none-any.whl", hash = "sha256:353bc4a8ac4bfc96800ddab349d89c25dec1079f65fd53acdcc1e0b975b21320"}, + {file = "deprecated-1.2.15.tar.gz", hash = "sha256:683e561a90de76239796e6b6feac66b99030d2dd3fcf61ef996330f14bbb9b0d"}, ] [package.dependencies] wrapt = ">=1.10,<2" [package.extras] -dev = ["PyTest", "PyTest-Cov", "bump2version (<1)", "sphinx (<2)", "tox"] +dev = ["PyTest", "PyTest-Cov", "bump2version (<1)", "jinja2 (>=3.0.3,<3.1.0)", "setuptools", "sphinx (<2)", "tox"] [[package]] name = "durationpy" @@ -655,13 +653,13 @@ tests = ["asttokens (>=2.1.0)", "coverage", "coverage-enable-subprocess", "ipyth [[package]] name = "google-auth" -version = "2.35.0" +version = "2.36.0" description = "Google Authentication Library" optional = false python-versions = ">=3.7" files = [ - {file = "google_auth-2.35.0-py2.py3-none-any.whl", hash = "sha256:25df55f327ef021de8be50bad0dfd4a916ad0de96da86cd05661c9297723ad3f"}, - {file = "google_auth-2.35.0.tar.gz", hash = "sha256:f4c64ed4e01e8e8b646ef34c018f8bf3338df0c8e37d8b3bba40e7f574a3278a"}, + {file = "google_auth-2.36.0-py2.py3-none-any.whl", hash = "sha256:51a15d47028b66fd36e5c64a82d2d57480075bccc7da37cde257fc94177a61fb"}, + {file = "google_auth-2.36.0.tar.gz", hash = "sha256:545e9618f2df0bcbb7dcbc45a546485b1212624716975a1ea5ae8149ce769ab1"}, ] [package.dependencies] @@ -678,13 +676,13 @@ requests = ["requests (>=2.20.0,<3.0.0.dev0)"] [[package]] name = "googleapis-common-protos" -version = "1.65.0" +version = "1.66.0" description = "Common protobufs used in Google APIs" optional = false python-versions = ">=3.7" files = [ - {file = "googleapis_common_protos-1.65.0-py2.py3-none-any.whl", hash = "sha256:2972e6c496f435b92590fd54045060867f3fe9be2c82ab148fc8885035479a63"}, - {file = "googleapis_common_protos-1.65.0.tar.gz", hash = "sha256:334a29d07cddc3aa01dee4988f9afd9b2916ee2ff49d6b757155dc0d197852c0"}, + {file = "googleapis_common_protos-1.66.0-py2.py3-none-any.whl", hash = "sha256:d7abcd75fabb2e0ec9f74466401f6c119a0b498e27370e9be4c94cb7e382b8ed"}, + {file = "googleapis_common_protos-1.66.0.tar.gz", hash = "sha256:c3e7b33d15fdca5374cc0a7346dd92ffa847425cc4ea941d970f13680052ec8c"}, ] [package.dependencies] @@ -706,13 +704,13 @@ files = [ [[package]] name = "httpcore" -version = "1.0.6" +version = "1.0.7" description = "A minimal low-level HTTP client." optional = false python-versions = ">=3.8" files = [ - {file = "httpcore-1.0.6-py3-none-any.whl", hash = "sha256:27b59625743b85577a8c0e10e55b50b5368a4f2cfe8cc7bcfa9cf00829c2682f"}, - {file = "httpcore-1.0.6.tar.gz", hash = "sha256:73f6dbd6eb8c21bbf7ef8efad555481853f5f6acdeaff1edb0694289269ee17f"}, + {file = "httpcore-1.0.7-py3-none-any.whl", hash = "sha256:a3fff8f43dc260d5bd363d9f9cf1830fa3a458b332856f34282de498ed420edd"}, + {file = "httpcore-1.0.7.tar.gz", hash = "sha256:8551cb62a169ec7162ac7be8d4817d561f60e08eaa485234898414bb5a8a0b4c"}, ] [package.dependencies] @@ -867,22 +865,22 @@ test-extra = ["curio", "ipython[test]", "matplotlib (!=3.2.0)", "nbformat", "num [[package]] name = "jedi" -version = "0.19.1" +version = "0.19.2" description = "An autocompletion tool for Python that can be used for text editors." optional = false python-versions = ">=3.6" files = [ - {file = "jedi-0.19.1-py2.py3-none-any.whl", hash = "sha256:e983c654fe5c02867aef4cdfce5a2fbb4a50adc0af145f70504238f18ef5e7e0"}, - {file = "jedi-0.19.1.tar.gz", hash = "sha256:cf0496f3651bc65d7174ac1b7d043eff454892c708a87d1b683e57b569927ffd"}, + {file = "jedi-0.19.2-py2.py3-none-any.whl", hash = "sha256:a8ef22bde8490f57fe5c7681a3c83cb58874daf72b4784de3cce5b6ef6edb5b9"}, + {file = "jedi-0.19.2.tar.gz", hash = "sha256:4770dc3de41bde3966b02eb84fbcf557fb33cce26ad23da12c742fb50ecb11f0"}, ] [package.dependencies] -parso = ">=0.8.3,<0.9.0" +parso = ">=0.8.4,<0.9.0" [package.extras] docs = ["Jinja2 (==2.11.3)", "MarkupSafe (==1.1.1)", "Pygments (==2.8.1)", "alabaster (==0.7.12)", "babel (==2.9.1)", "chardet (==4.0.0)", "commonmark (==0.8.1)", "docutils (==0.17.1)", "future (==0.18.2)", "idna (==2.10)", "imagesize (==1.2.0)", "mock (==1.0.1)", "packaging (==20.9)", "pyparsing (==2.4.7)", "pytz (==2021.1)", "readthedocs-sphinx-ext (==2.1.4)", "recommonmark (==0.5.0)", "requests (==2.25.1)", "six (==1.15.0)", "snowballstemmer (==2.1.0)", "sphinx (==1.8.5)", "sphinx-rtd-theme (==0.4.3)", "sphinxcontrib-serializinghtml (==1.1.4)", "sphinxcontrib-websupport (==1.2.4)", "urllib3 (==1.26.4)"] qa = ["flake8 (==5.0.4)", "mypy (==0.971)", "types-setuptools (==67.2.0.1)"] -testing = ["Django", "attrs", "colorama", "docopt", "pytest (<7.0.0)"] +testing = ["Django", "attrs", "colorama", "docopt", "pytest (<9.0.0)"] [[package]] name = "jinja2" @@ -1013,13 +1011,13 @@ requests = "*" [[package]] name = "lightkube" -version = "0.15.4" +version = "0.15.5" description = "Lightweight kubernetes client library" optional = false python-versions = "*" files = [ - {file = "lightkube-0.15.4-py3-none-any.whl", hash = "sha256:7dde49694f2933b757ee6dfee0e028a56d7a13f47476bf54a21ec7cee343b09b"}, - {file = "lightkube-0.15.4.tar.gz", hash = "sha256:fe7939f8da5b68d80809243c9abf601fca2e5425228bb02c2ad23ed3e56401a8"}, + {file = "lightkube-0.15.5-py3-none-any.whl", hash = "sha256:0d93be743cbeae022d18a1d3fbb45d1df58f9a603ea0061237842658f68d93fd"}, + {file = "lightkube-0.15.5.tar.gz", hash = "sha256:5edbfd1aee83398374179f41f4897519e8f89dc9754c866d40bbdc68c49c033f"}, ] [package.dependencies] @@ -1305,13 +1303,13 @@ testing = ["ops-scenario (>=7.0.5,<8)"] [[package]] name = "packaging" -version = "24.1" +version = "24.2" description = "Core utilities for Python packages" optional = false python-versions = ">=3.8" files = [ - {file = "packaging-24.1-py3-none-any.whl", hash = "sha256:5b8f2217dbdbd2f7f384c41c628544e6d52f2d0f53c6d0c3ea61aa5d1d7ff124"}, - {file = "packaging-24.1.tar.gz", hash = "sha256:026ed72c8ed3fcce5bf8950572258698927fd1dbda10a5e981cdf0ac37f4f002"}, + {file = "packaging-24.2-py3-none-any.whl", hash = "sha256:09abb1bccd265c01f4a3aa3f7a7db064b36514d2cba19a2f694fe6150451a759"}, + {file = "packaging-24.2.tar.gz", hash = "sha256:c228a6dc5e932d346bc5739379109d49e8853dd8223571c7c5b55260edc0b97f"}, ] [[package]] @@ -1451,20 +1449,22 @@ files = [ [[package]] name = "psycopg2" -version = "2.9.10" +version = "2.9.4" description = "psycopg2 - Python-PostgreSQL Database Adapter" optional = false -python-versions = ">=3.8" +python-versions = ">=3.6" files = [ - {file = "psycopg2-2.9.10-cp310-cp310-win32.whl", hash = "sha256:5df2b672140f95adb453af93a7d669d7a7bf0a56bcd26f1502329166f4a61716"}, - {file = "psycopg2-2.9.10-cp310-cp310-win_amd64.whl", hash = "sha256:c6f7b8561225f9e711a9c47087388a97fdc948211c10a4bccbf0ba68ab7b3b5a"}, - {file = "psycopg2-2.9.10-cp311-cp311-win32.whl", hash = "sha256:47c4f9875125344f4c2b870e41b6aad585901318068acd01de93f3677a6522c2"}, - {file = "psycopg2-2.9.10-cp311-cp311-win_amd64.whl", hash = "sha256:0435034157049f6846e95103bd8f5a668788dd913a7c30162ca9503fdf542cb4"}, - {file = "psycopg2-2.9.10-cp312-cp312-win32.whl", hash = "sha256:65a63d7ab0e067e2cdb3cf266de39663203d38d6a8ed97f5ca0cb315c73fe067"}, - {file = "psycopg2-2.9.10-cp312-cp312-win_amd64.whl", hash = "sha256:4a579d6243da40a7b3182e0430493dbd55950c493d8c68f4eec0b302f6bbf20e"}, - {file = "psycopg2-2.9.10-cp39-cp39-win32.whl", hash = "sha256:9d5b3b94b79a844a986d029eee38998232451119ad653aea42bb9220a8c5066b"}, - {file = "psycopg2-2.9.10-cp39-cp39-win_amd64.whl", hash = "sha256:88138c8dedcbfa96408023ea2b0c369eda40fe5d75002c0964c78f46f11fa442"}, - {file = "psycopg2-2.9.10.tar.gz", hash = "sha256:12ec0b40b0273f95296233e8750441339298e6a572f7039da5b260e3c8b60e11"}, + {file = "psycopg2-2.9.4-cp310-cp310-win32.whl", hash = "sha256:8de6a9fc5f42fa52f559e65120dcd7502394692490c98fed1221acf0819d7797"}, + {file = "psycopg2-2.9.4-cp310-cp310-win_amd64.whl", hash = "sha256:1da77c061bdaab450581458932ae5e469cc6e36e0d62f988376e9f513f11cb5c"}, + {file = "psycopg2-2.9.4-cp36-cp36m-win32.whl", hash = "sha256:a11946bad3557ca254f17357d5a4ed63bdca45163e7a7d2bfb8e695df069cc3a"}, + {file = "psycopg2-2.9.4-cp36-cp36m-win_amd64.whl", hash = "sha256:46361c054df612c3cc813fdb343733d56543fb93565cff0f8ace422e4da06acb"}, + {file = "psycopg2-2.9.4-cp37-cp37m-win32.whl", hash = "sha256:aafa96f2da0071d6dd0cbb7633406d99f414b40ab0f918c9d9af7df928a1accb"}, + {file = "psycopg2-2.9.4-cp37-cp37m-win_amd64.whl", hash = "sha256:aa184d551a767ad25df3b8d22a0a62ef2962e0e374c04f6cbd1204947f540d61"}, + {file = "psycopg2-2.9.4-cp38-cp38-win32.whl", hash = "sha256:839f9ea8f6098e39966d97fcb8d08548fbc57c523a1e27a1f0609addf40f777c"}, + {file = "psycopg2-2.9.4-cp38-cp38-win_amd64.whl", hash = "sha256:c7fa041b4acb913f6968fce10169105af5200f296028251d817ab37847c30184"}, + {file = "psycopg2-2.9.4-cp39-cp39-win32.whl", hash = "sha256:07b90a24d5056687781ddaef0ea172fd951f2f7293f6ffdd03d4f5077801f426"}, + {file = "psycopg2-2.9.4-cp39-cp39-win_amd64.whl", hash = "sha256:849bd868ae3369932127f0771c08d1109b254f08d48dc42493c3d1b87cb2d308"}, + {file = "psycopg2-2.9.4.tar.gz", hash = "sha256:d529926254e093a1b669f692a3aa50069bc71faf5b0ecd91686a78f62767d52f"}, ] [[package]] @@ -1606,54 +1606,54 @@ files = [ [[package]] name = "pydantic" -version = "1.10.18" +version = "1.10.19" description = "Data validation and settings management using python type hints" optional = false python-versions = ">=3.7" files = [ - {file = "pydantic-1.10.18-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:e405ffcc1254d76bb0e760db101ee8916b620893e6edfbfee563b3c6f7a67c02"}, - {file = "pydantic-1.10.18-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:e306e280ebebc65040034bff1a0a81fd86b2f4f05daac0131f29541cafd80b80"}, - {file = "pydantic-1.10.18-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:11d9d9b87b50338b1b7de4ebf34fd29fdb0d219dc07ade29effc74d3d2609c62"}, - {file = "pydantic-1.10.18-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b661ce52c7b5e5f600c0c3c5839e71918346af2ef20062705ae76b5c16914cab"}, - {file = "pydantic-1.10.18-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:c20f682defc9ef81cd7eaa485879ab29a86a0ba58acf669a78ed868e72bb89e0"}, - {file = "pydantic-1.10.18-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:c5ae6b7c8483b1e0bf59e5f1843e4fd8fd405e11df7de217ee65b98eb5462861"}, - {file = "pydantic-1.10.18-cp310-cp310-win_amd64.whl", hash = "sha256:74fe19dda960b193b0eb82c1f4d2c8e5e26918d9cda858cbf3f41dd28549cb70"}, - {file = "pydantic-1.10.18-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:72fa46abace0a7743cc697dbb830a41ee84c9db8456e8d77a46d79b537efd7ec"}, - {file = "pydantic-1.10.18-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:ef0fe7ad7cbdb5f372463d42e6ed4ca9c443a52ce544472d8842a0576d830da5"}, - {file = "pydantic-1.10.18-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a00e63104346145389b8e8f500bc6a241e729feaf0559b88b8aa513dd2065481"}, - {file = "pydantic-1.10.18-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ae6fa2008e1443c46b7b3a5eb03800121868d5ab6bc7cda20b5df3e133cde8b3"}, - {file = "pydantic-1.10.18-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:9f463abafdc92635da4b38807f5b9972276be7c8c5121989768549fceb8d2588"}, - {file = "pydantic-1.10.18-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:3445426da503c7e40baccefb2b2989a0c5ce6b163679dd75f55493b460f05a8f"}, - {file = "pydantic-1.10.18-cp311-cp311-win_amd64.whl", hash = "sha256:467a14ee2183bc9c902579bb2f04c3d3dac00eff52e252850509a562255b2a33"}, - {file = "pydantic-1.10.18-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:efbc8a7f9cb5fe26122acba1852d8dcd1e125e723727c59dcd244da7bdaa54f2"}, - {file = "pydantic-1.10.18-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:24a4a159d0f7a8e26bf6463b0d3d60871d6a52eac5bb6a07a7df85c806f4c048"}, - {file = "pydantic-1.10.18-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b74be007703547dc52e3c37344d130a7bfacca7df112a9e5ceeb840a9ce195c7"}, - {file = "pydantic-1.10.18-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:fcb20d4cb355195c75000a49bb4a31d75e4295200df620f454bbc6bdf60ca890"}, - {file = "pydantic-1.10.18-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:46f379b8cb8a3585e3f61bf9ae7d606c70d133943f339d38b76e041ec234953f"}, - {file = "pydantic-1.10.18-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:cbfbca662ed3729204090c4d09ee4beeecc1a7ecba5a159a94b5a4eb24e3759a"}, - {file = "pydantic-1.10.18-cp312-cp312-win_amd64.whl", hash = "sha256:c6d0a9f9eccaf7f438671a64acf654ef0d045466e63f9f68a579e2383b63f357"}, - {file = "pydantic-1.10.18-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:3d5492dbf953d7d849751917e3b2433fb26010d977aa7a0765c37425a4026ff1"}, - {file = "pydantic-1.10.18-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fe734914977eed33033b70bfc097e1baaffb589517863955430bf2e0846ac30f"}, - {file = "pydantic-1.10.18-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:15fdbe568beaca9aacfccd5ceadfb5f1a235087a127e8af5e48df9d8a45ae85c"}, - {file = "pydantic-1.10.18-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:c3e742f62198c9eb9201781fbebe64533a3bbf6a76a91b8d438d62b813079dbc"}, - {file = "pydantic-1.10.18-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:19a3bd00b9dafc2cd7250d94d5b578edf7a0bd7daf102617153ff9a8fa37871c"}, - {file = "pydantic-1.10.18-cp37-cp37m-win_amd64.whl", hash = "sha256:2ce3fcf75b2bae99aa31bd4968de0474ebe8c8258a0110903478bd83dfee4e3b"}, - {file = "pydantic-1.10.18-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:335a32d72c51a313b33fa3a9b0fe283503272ef6467910338e123f90925f0f03"}, - {file = "pydantic-1.10.18-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:34a3613c7edb8c6fa578e58e9abe3c0f5e7430e0fc34a65a415a1683b9c32d9a"}, - {file = "pydantic-1.10.18-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e9ee4e6ca1d9616797fa2e9c0bfb8815912c7d67aca96f77428e316741082a1b"}, - {file = "pydantic-1.10.18-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:23e8ec1ce4e57b4f441fc91e3c12adba023fedd06868445a5b5f1d48f0ab3682"}, - {file = "pydantic-1.10.18-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:44ae8a3e35a54d2e8fa88ed65e1b08967a9ef8c320819a969bfa09ce5528fafe"}, - {file = "pydantic-1.10.18-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:d5389eb3b48a72da28c6e061a247ab224381435256eb541e175798483368fdd3"}, - {file = "pydantic-1.10.18-cp38-cp38-win_amd64.whl", hash = "sha256:069b9c9fc645474d5ea3653788b544a9e0ccd3dca3ad8c900c4c6eac844b4620"}, - {file = "pydantic-1.10.18-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:80b982d42515632eb51f60fa1d217dfe0729f008e81a82d1544cc392e0a50ddf"}, - {file = "pydantic-1.10.18-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:aad8771ec8dbf9139b01b56f66386537c6fe4e76c8f7a47c10261b69ad25c2c9"}, - {file = "pydantic-1.10.18-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:941a2eb0a1509bd7f31e355912eb33b698eb0051730b2eaf9e70e2e1589cae1d"}, - {file = "pydantic-1.10.18-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:65f7361a09b07915a98efd17fdec23103307a54db2000bb92095457ca758d485"}, - {file = "pydantic-1.10.18-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:6951f3f47cb5ca4da536ab161ac0163cab31417d20c54c6de5ddcab8bc813c3f"}, - {file = "pydantic-1.10.18-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:7a4c5eec138a9b52c67f664c7d51d4c7234c5ad65dd8aacd919fb47445a62c86"}, - {file = "pydantic-1.10.18-cp39-cp39-win_amd64.whl", hash = "sha256:49e26c51ca854286bffc22b69787a8d4063a62bf7d83dc21d44d2ff426108518"}, - {file = "pydantic-1.10.18-py3-none-any.whl", hash = "sha256:06a189b81ffc52746ec9c8c007f16e5167c8b0a696e1a726369327e3db7b2a82"}, - {file = "pydantic-1.10.18.tar.gz", hash = "sha256:baebdff1907d1d96a139c25136a9bb7d17e118f133a76a2ef3b845e831e3403a"}, + {file = "pydantic-1.10.19-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:a415b9e95fa602b10808113967f72b2da8722061265d6af69268c111c254832d"}, + {file = "pydantic-1.10.19-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:11965f421f7eb026439d4eb7464e9182fe6d69c3d4d416e464a4485d1ba61ab6"}, + {file = "pydantic-1.10.19-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f5bb81fcfc6d5bff62cd786cbd87480a11d23f16d5376ad2e057c02b3b44df96"}, + {file = "pydantic-1.10.19-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:83ee8c9916689f8e6e7d90161e6663ac876be2efd32f61fdcfa3a15e87d4e413"}, + {file = "pydantic-1.10.19-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:0399094464ae7f28482de22383e667625e38e1516d6b213176df1acdd0c477ea"}, + {file = "pydantic-1.10.19-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:8b2cf5e26da84f2d2dee3f60a3f1782adedcee785567a19b68d0af7e1534bd1f"}, + {file = "pydantic-1.10.19-cp310-cp310-win_amd64.whl", hash = "sha256:1fc8cc264afaf47ae6a9bcbd36c018d0c6b89293835d7fb0e5e1a95898062d59"}, + {file = "pydantic-1.10.19-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:d7a8a1dd68bac29f08f0a3147de1885f4dccec35d4ea926e6e637fac03cdb4b3"}, + {file = "pydantic-1.10.19-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:07d00ca5ef0de65dd274005433ce2bb623730271d495a7d190a91c19c5679d34"}, + {file = "pydantic-1.10.19-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ad57004e5d73aee36f1e25e4e73a4bc853b473a1c30f652dc8d86b0a987ffce3"}, + {file = "pydantic-1.10.19-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:dce355fe7ae53e3090f7f5fa242423c3a7b53260747aa398b4b3aaf8b25f41c3"}, + {file = "pydantic-1.10.19-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:0d32227ea9a3bf537a2273fd2fdb6d64ab4d9b83acd9e4e09310a777baaabb98"}, + {file = "pydantic-1.10.19-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:e351df83d1c9cffa53d4e779009a093be70f1d5c6bb7068584086f6a19042526"}, + {file = "pydantic-1.10.19-cp311-cp311-win_amd64.whl", hash = "sha256:d8d72553d2f3f57ce547de4fa7dc8e3859927784ab2c88343f1fc1360ff17a08"}, + {file = "pydantic-1.10.19-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:d5b5b7c6bafaef90cbb7dafcb225b763edd71d9e22489647ee7df49d6d341890"}, + {file = "pydantic-1.10.19-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:570ad0aeaf98b5e33ff41af75aba2ef6604ee25ce0431ecd734a28e74a208555"}, + {file = "pydantic-1.10.19-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0890fbd7fec9e151c7512941243d830b2d6076d5df159a2030952d480ab80a4e"}, + {file = "pydantic-1.10.19-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ec5c44e6e9eac5128a9bfd21610df3b8c6b17343285cc185105686888dc81206"}, + {file = "pydantic-1.10.19-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:6eb56074b11a696e0b66c7181da682e88c00e5cebe6570af8013fcae5e63e186"}, + {file = "pydantic-1.10.19-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:9d7d48fbc5289efd23982a0d68e973a1f37d49064ccd36d86de4543aff21e086"}, + {file = "pydantic-1.10.19-cp312-cp312-win_amd64.whl", hash = "sha256:fd34012691fbd4e67bdf4accb1f0682342101015b78327eaae3543583fcd451e"}, + {file = "pydantic-1.10.19-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:4a5d5b877c7d3d9e17399571a8ab042081d22fe6904416a8b20f8af5909e6c8f"}, + {file = "pydantic-1.10.19-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9c46f58ef2df958ed2ea7437a8be0897d5efe9ee480818405338c7da88186fb3"}, + {file = "pydantic-1.10.19-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6d8a38a44bb6a15810084316ed69c854a7c06e0c99c5429f1d664ad52cec353c"}, + {file = "pydantic-1.10.19-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:a82746c6d6e91ca17e75f7f333ed41d70fce93af520a8437821dec3ee52dfb10"}, + {file = "pydantic-1.10.19-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:566bebdbe6bc0ac593fa0f67d62febbad9f8be5433f686dc56401ba4aab034e3"}, + {file = "pydantic-1.10.19-cp37-cp37m-win_amd64.whl", hash = "sha256:22a1794e01591884741be56c6fba157c4e99dcc9244beb5a87bd4aa54b84ea8b"}, + {file = "pydantic-1.10.19-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:076c49e24b73d346c45f9282d00dbfc16eef7ae27c970583d499f11110d9e5b0"}, + {file = "pydantic-1.10.19-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:5d4320510682d5a6c88766b2a286d03b87bd3562bf8d78c73d63bab04b21e7b4"}, + {file = "pydantic-1.10.19-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7e66aa0fa7f8aa9d0a620361834f6eb60d01d3e9cea23ca1a92cda99e6f61dac"}, + {file = "pydantic-1.10.19-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d216f8d0484d88ab72ab45d699ac669fe031275e3fa6553e3804e69485449fa0"}, + {file = "pydantic-1.10.19-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:9f28a81978e936136c44e6a70c65bde7548d87f3807260f73aeffbf76fb94c2f"}, + {file = "pydantic-1.10.19-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:d3449633c207ec3d2d672eedb3edbe753e29bd4e22d2e42a37a2c1406564c20f"}, + {file = "pydantic-1.10.19-cp38-cp38-win_amd64.whl", hash = "sha256:7ea24e8614f541d69ea72759ff635df0e612b7dc9d264d43f51364df310081a3"}, + {file = "pydantic-1.10.19-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:573254d844f3e64093f72fcd922561d9c5696821ff0900a0db989d8c06ab0c25"}, + {file = "pydantic-1.10.19-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:ff09600cebe957ecbb4a27496fe34c1d449e7957ed20a202d5029a71a8af2e35"}, + {file = "pydantic-1.10.19-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4739c206bfb6bb2bdc78dcd40bfcebb2361add4ceac6d170e741bb914e9eff0f"}, + {file = "pydantic-1.10.19-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0bfb5b378b78229119d66ced6adac2e933c67a0aa1d0a7adffbe432f3ec14ce4"}, + {file = "pydantic-1.10.19-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:7f31742c95e3f9443b8c6fa07c119623e61d76603be9c0d390bcf7e888acabcb"}, + {file = "pydantic-1.10.19-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:c6444368b651a14c2ce2fb22145e1496f7ab23cbdb978590d47c8d34a7bc0289"}, + {file = "pydantic-1.10.19-cp39-cp39-win_amd64.whl", hash = "sha256:945407f4d08cd12485757a281fca0e5b41408606228612f421aa4ea1b63a095d"}, + {file = "pydantic-1.10.19-py3-none-any.whl", hash = "sha256:2206a1752d9fac011e95ca83926a269fb0ef5536f7e053966d058316e24d929f"}, + {file = "pydantic-1.10.19.tar.gz", hash = "sha256:fea36c2065b7a1d28c6819cc2e93387b43dd5d3cf5a1e82d8132ee23f36d1f10"}, ] [package.dependencies] @@ -2006,114 +2006,101 @@ rsa = ["oauthlib[signedtoken] (>=3.0.0)"] [[package]] name = "rpds-py" -version = "0.20.0" +version = "0.21.0" description = "Python bindings to Rust's persistent data structures (rpds)" optional = false -python-versions = ">=3.8" +python-versions = ">=3.9" files = [ - {file = "rpds_py-0.20.0-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:3ad0fda1635f8439cde85c700f964b23ed5fc2d28016b32b9ee5fe30da5c84e2"}, - {file = "rpds_py-0.20.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:9bb4a0d90fdb03437c109a17eade42dfbf6190408f29b2744114d11586611d6f"}, - {file = "rpds_py-0.20.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c6377e647bbfd0a0b159fe557f2c6c602c159fc752fa316572f012fc0bf67150"}, - {file = "rpds_py-0.20.0-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:eb851b7df9dda52dc1415ebee12362047ce771fc36914586b2e9fcbd7d293b3e"}, - {file = "rpds_py-0.20.0-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1e0f80b739e5a8f54837be5d5c924483996b603d5502bfff79bf33da06164ee2"}, - {file = "rpds_py-0.20.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5a8c94dad2e45324fc74dce25e1645d4d14df9a4e54a30fa0ae8bad9a63928e3"}, - {file = "rpds_py-0.20.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f8e604fe73ba048c06085beaf51147eaec7df856824bfe7b98657cf436623daf"}, - {file = "rpds_py-0.20.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:df3de6b7726b52966edf29663e57306b23ef775faf0ac01a3e9f4012a24a4140"}, - {file = "rpds_py-0.20.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:cf258ede5bc22a45c8e726b29835b9303c285ab46fc7c3a4cc770736b5304c9f"}, - {file = "rpds_py-0.20.0-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:55fea87029cded5df854ca7e192ec7bdb7ecd1d9a3f63d5c4eb09148acf4a7ce"}, - {file = "rpds_py-0.20.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:ae94bd0b2f02c28e199e9bc51485d0c5601f58780636185660f86bf80c89af94"}, - {file = "rpds_py-0.20.0-cp310-none-win32.whl", hash = "sha256:28527c685f237c05445efec62426d285e47a58fb05ba0090a4340b73ecda6dee"}, - {file = "rpds_py-0.20.0-cp310-none-win_amd64.whl", hash = "sha256:238a2d5b1cad28cdc6ed15faf93a998336eb041c4e440dd7f902528b8891b399"}, - {file = "rpds_py-0.20.0-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:ac2f4f7a98934c2ed6505aead07b979e6f999389f16b714448fb39bbaa86a489"}, - {file = "rpds_py-0.20.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:220002c1b846db9afd83371d08d239fdc865e8f8c5795bbaec20916a76db3318"}, - {file = "rpds_py-0.20.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8d7919548df3f25374a1f5d01fbcd38dacab338ef5f33e044744b5c36729c8db"}, - {file = "rpds_py-0.20.0-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:758406267907b3781beee0f0edfe4a179fbd97c0be2e9b1154d7f0a1279cf8e5"}, - {file = "rpds_py-0.20.0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3d61339e9f84a3f0767b1995adfb171a0d00a1185192718a17af6e124728e0f5"}, - {file = "rpds_py-0.20.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:1259c7b3705ac0a0bd38197565a5d603218591d3f6cee6e614e380b6ba61c6f6"}, - {file = "rpds_py-0.20.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5c1dc0f53856b9cc9a0ccca0a7cc61d3d20a7088201c0937f3f4048c1718a209"}, - {file = "rpds_py-0.20.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:7e60cb630f674a31f0368ed32b2a6b4331b8350d67de53c0359992444b116dd3"}, - {file = "rpds_py-0.20.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:dbe982f38565bb50cb7fb061ebf762c2f254ca3d8c20d4006878766e84266272"}, - {file = "rpds_py-0.20.0-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:514b3293b64187172bc77c8fb0cdae26981618021053b30d8371c3a902d4d5ad"}, - {file = "rpds_py-0.20.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:d0a26ffe9d4dd35e4dfdd1e71f46401cff0181c75ac174711ccff0459135fa58"}, - {file = "rpds_py-0.20.0-cp311-none-win32.whl", hash = "sha256:89c19a494bf3ad08c1da49445cc5d13d8fefc265f48ee7e7556839acdacf69d0"}, - {file = "rpds_py-0.20.0-cp311-none-win_amd64.whl", hash = "sha256:c638144ce971df84650d3ed0096e2ae7af8e62ecbbb7b201c8935c370df00a2c"}, - {file = "rpds_py-0.20.0-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:a84ab91cbe7aab97f7446652d0ed37d35b68a465aeef8fc41932a9d7eee2c1a6"}, - {file = "rpds_py-0.20.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:56e27147a5a4c2c21633ff8475d185734c0e4befd1c989b5b95a5d0db699b21b"}, - {file = "rpds_py-0.20.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2580b0c34583b85efec8c5c5ec9edf2dfe817330cc882ee972ae650e7b5ef739"}, - {file = "rpds_py-0.20.0-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:b80d4a7900cf6b66bb9cee5c352b2d708e29e5a37fe9bf784fa97fc11504bf6c"}, - {file = "rpds_py-0.20.0-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:50eccbf054e62a7b2209b28dc7a22d6254860209d6753e6b78cfaeb0075d7bee"}, - {file = "rpds_py-0.20.0-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:49a8063ea4296b3a7e81a5dfb8f7b2d73f0b1c20c2af401fb0cdf22e14711a96"}, - {file = "rpds_py-0.20.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ea438162a9fcbee3ecf36c23e6c68237479f89f962f82dae83dc15feeceb37e4"}, - {file = "rpds_py-0.20.0-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:18d7585c463087bddcfa74c2ba267339f14f2515158ac4db30b1f9cbdb62c8ef"}, - {file = "rpds_py-0.20.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:d4c7d1a051eeb39f5c9547e82ea27cbcc28338482242e3e0b7768033cb083821"}, - {file = "rpds_py-0.20.0-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:e4df1e3b3bec320790f699890d41c59d250f6beda159ea3c44c3f5bac1976940"}, - {file = "rpds_py-0.20.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:2cf126d33a91ee6eedc7f3197b53e87a2acdac63602c0f03a02dd69e4b138174"}, - {file = "rpds_py-0.20.0-cp312-none-win32.whl", hash = "sha256:8bc7690f7caee50b04a79bf017a8d020c1f48c2a1077ffe172abec59870f1139"}, - {file = "rpds_py-0.20.0-cp312-none-win_amd64.whl", hash = "sha256:0e13e6952ef264c40587d510ad676a988df19adea20444c2b295e536457bc585"}, - {file = "rpds_py-0.20.0-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:aa9a0521aeca7d4941499a73ad7d4f8ffa3d1affc50b9ea11d992cd7eff18a29"}, - {file = "rpds_py-0.20.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:4a1f1d51eccb7e6c32ae89243cb352389228ea62f89cd80823ea7dd1b98e0b91"}, - {file = "rpds_py-0.20.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8a86a9b96070674fc88b6f9f71a97d2c1d3e5165574615d1f9168ecba4cecb24"}, - {file = "rpds_py-0.20.0-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:6c8ef2ebf76df43f5750b46851ed1cdf8f109d7787ca40035fe19fbdc1acc5a7"}, - {file = "rpds_py-0.20.0-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b74b25f024b421d5859d156750ea9a65651793d51b76a2e9238c05c9d5f203a9"}, - {file = "rpds_py-0.20.0-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:57eb94a8c16ab08fef6404301c38318e2c5a32216bf5de453e2714c964c125c8"}, - {file = "rpds_py-0.20.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e1940dae14e715e2e02dfd5b0f64a52e8374a517a1e531ad9412319dc3ac7879"}, - {file = "rpds_py-0.20.0-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:d20277fd62e1b992a50c43f13fbe13277a31f8c9f70d59759c88f644d66c619f"}, - {file = "rpds_py-0.20.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:06db23d43f26478303e954c34c75182356ca9aa7797d22c5345b16871ab9c45c"}, - {file = "rpds_py-0.20.0-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:b2a5db5397d82fa847e4c624b0c98fe59d2d9b7cf0ce6de09e4d2e80f8f5b3f2"}, - {file = "rpds_py-0.20.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:5a35df9f5548fd79cb2f52d27182108c3e6641a4feb0f39067911bf2adaa3e57"}, - {file = "rpds_py-0.20.0-cp313-none-win32.whl", hash = "sha256:fd2d84f40633bc475ef2d5490b9c19543fbf18596dcb1b291e3a12ea5d722f7a"}, - {file = "rpds_py-0.20.0-cp313-none-win_amd64.whl", hash = "sha256:9bc2d153989e3216b0559251b0c260cfd168ec78b1fac33dd485750a228db5a2"}, - {file = "rpds_py-0.20.0-cp38-cp38-macosx_10_12_x86_64.whl", hash = "sha256:f2fbf7db2012d4876fb0d66b5b9ba6591197b0f165db8d99371d976546472a24"}, - {file = "rpds_py-0.20.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:1e5f3cd7397c8f86c8cc72d5a791071431c108edd79872cdd96e00abd8497d29"}, - {file = "rpds_py-0.20.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ce9845054c13696f7af7f2b353e6b4f676dab1b4b215d7fe5e05c6f8bb06f965"}, - {file = "rpds_py-0.20.0-cp38-cp38-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:c3e130fd0ec56cb76eb49ef52faead8ff09d13f4527e9b0c400307ff72b408e1"}, - {file = "rpds_py-0.20.0-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:4b16aa0107ecb512b568244ef461f27697164d9a68d8b35090e9b0c1c8b27752"}, - {file = "rpds_py-0.20.0-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:aa7f429242aae2947246587d2964fad750b79e8c233a2367f71b554e9447949c"}, - {file = "rpds_py-0.20.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:af0fc424a5842a11e28956e69395fbbeab2c97c42253169d87e90aac2886d751"}, - {file = "rpds_py-0.20.0-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:b8c00a3b1e70c1d3891f0db1b05292747f0dbcfb49c43f9244d04c70fbc40eb8"}, - {file = "rpds_py-0.20.0-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:40ce74fc86ee4645d0a225498d091d8bc61f39b709ebef8204cb8b5a464d3c0e"}, - {file = "rpds_py-0.20.0-cp38-cp38-musllinux_1_2_i686.whl", hash = "sha256:4fe84294c7019456e56d93e8ababdad5a329cd25975be749c3f5f558abb48253"}, - {file = "rpds_py-0.20.0-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:338ca4539aad4ce70a656e5187a3a31c5204f261aef9f6ab50e50bcdffaf050a"}, - {file = "rpds_py-0.20.0-cp38-none-win32.whl", hash = "sha256:54b43a2b07db18314669092bb2de584524d1ef414588780261e31e85846c26a5"}, - {file = "rpds_py-0.20.0-cp38-none-win_amd64.whl", hash = "sha256:a1862d2d7ce1674cffa6d186d53ca95c6e17ed2b06b3f4c476173565c862d232"}, - {file = "rpds_py-0.20.0-cp39-cp39-macosx_10_12_x86_64.whl", hash = "sha256:3fde368e9140312b6e8b6c09fb9f8c8c2f00999d1823403ae90cc00480221b22"}, - {file = "rpds_py-0.20.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:9824fb430c9cf9af743cf7aaf6707bf14323fb51ee74425c380f4c846ea70789"}, - {file = "rpds_py-0.20.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:11ef6ce74616342888b69878d45e9f779b95d4bd48b382a229fe624a409b72c5"}, - {file = "rpds_py-0.20.0-cp39-cp39-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:c52d3f2f82b763a24ef52f5d24358553e8403ce05f893b5347098014f2d9eff2"}, - {file = "rpds_py-0.20.0-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:9d35cef91e59ebbeaa45214861874bc6f19eb35de96db73e467a8358d701a96c"}, - {file = "rpds_py-0.20.0-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d72278a30111e5b5525c1dd96120d9e958464316f55adb030433ea905866f4de"}, - {file = "rpds_py-0.20.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b4c29cbbba378759ac5786730d1c3cb4ec6f8ababf5c42a9ce303dc4b3d08cda"}, - {file = "rpds_py-0.20.0-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:6632f2d04f15d1bd6fe0eedd3b86d9061b836ddca4c03d5cf5c7e9e6b7c14580"}, - {file = "rpds_py-0.20.0-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:d0b67d87bb45ed1cd020e8fbf2307d449b68abc45402fe1a4ac9e46c3c8b192b"}, - {file = "rpds_py-0.20.0-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:ec31a99ca63bf3cd7f1a5ac9fe95c5e2d060d3c768a09bc1d16e235840861420"}, - {file = "rpds_py-0.20.0-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:22e6c9976e38f4d8c4a63bd8a8edac5307dffd3ee7e6026d97f3cc3a2dc02a0b"}, - {file = "rpds_py-0.20.0-cp39-none-win32.whl", hash = "sha256:569b3ea770c2717b730b61998b6c54996adee3cef69fc28d444f3e7920313cf7"}, - {file = "rpds_py-0.20.0-cp39-none-win_amd64.whl", hash = "sha256:e6900ecdd50ce0facf703f7a00df12374b74bbc8ad9fe0f6559947fb20f82364"}, - {file = "rpds_py-0.20.0-pp310-pypy310_pp73-macosx_10_12_x86_64.whl", hash = "sha256:617c7357272c67696fd052811e352ac54ed1d9b49ab370261a80d3b6ce385045"}, - {file = "rpds_py-0.20.0-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:9426133526f69fcaba6e42146b4e12d6bc6c839b8b555097020e2b78ce908dcc"}, - {file = "rpds_py-0.20.0-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:deb62214c42a261cb3eb04d474f7155279c1a8a8c30ac89b7dcb1721d92c3c02"}, - {file = "rpds_py-0.20.0-pp310-pypy310_pp73-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:fcaeb7b57f1a1e071ebd748984359fef83ecb026325b9d4ca847c95bc7311c92"}, - {file = "rpds_py-0.20.0-pp310-pypy310_pp73-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d454b8749b4bd70dd0a79f428731ee263fa6995f83ccb8bada706e8d1d3ff89d"}, - {file = "rpds_py-0.20.0-pp310-pypy310_pp73-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d807dc2051abe041b6649681dce568f8e10668e3c1c6543ebae58f2d7e617855"}, - {file = "rpds_py-0.20.0-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c3c20f0ddeb6e29126d45f89206b8291352b8c5b44384e78a6499d68b52ae511"}, - {file = "rpds_py-0.20.0-pp310-pypy310_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:b7f19250ceef892adf27f0399b9e5afad019288e9be756d6919cb58892129f51"}, - {file = "rpds_py-0.20.0-pp310-pypy310_pp73-musllinux_1_2_aarch64.whl", hash = "sha256:4f1ed4749a08379555cebf4650453f14452eaa9c43d0a95c49db50c18b7da075"}, - {file = "rpds_py-0.20.0-pp310-pypy310_pp73-musllinux_1_2_i686.whl", hash = "sha256:dcedf0b42bcb4cfff4101d7771a10532415a6106062f005ab97d1d0ab5681c60"}, - {file = "rpds_py-0.20.0-pp310-pypy310_pp73-musllinux_1_2_x86_64.whl", hash = "sha256:39ed0d010457a78f54090fafb5d108501b5aa5604cc22408fc1c0c77eac14344"}, - {file = "rpds_py-0.20.0-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:bb273176be34a746bdac0b0d7e4e2c467323d13640b736c4c477881a3220a989"}, - {file = "rpds_py-0.20.0-pp39-pypy39_pp73-macosx_10_12_x86_64.whl", hash = "sha256:f918a1a130a6dfe1d7fe0f105064141342e7dd1611f2e6a21cd2f5c8cb1cfb3e"}, - {file = "rpds_py-0.20.0-pp39-pypy39_pp73-macosx_11_0_arm64.whl", hash = "sha256:f60012a73aa396be721558caa3a6fd49b3dd0033d1675c6d59c4502e870fcf0c"}, - {file = "rpds_py-0.20.0-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3d2b1ad682a3dfda2a4e8ad8572f3100f95fad98cb99faf37ff0ddfe9cbf9d03"}, - {file = "rpds_py-0.20.0-pp39-pypy39_pp73-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:614fdafe9f5f19c63ea02817fa4861c606a59a604a77c8cdef5aa01d28b97921"}, - {file = "rpds_py-0.20.0-pp39-pypy39_pp73-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:fa518bcd7600c584bf42e6617ee8132869e877db2f76bcdc281ec6a4113a53ab"}, - {file = "rpds_py-0.20.0-pp39-pypy39_pp73-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f0475242f447cc6cb8a9dd486d68b2ef7fbee84427124c232bff5f63b1fe11e5"}, - {file = "rpds_py-0.20.0-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f90a4cd061914a60bd51c68bcb4357086991bd0bb93d8aa66a6da7701370708f"}, - {file = "rpds_py-0.20.0-pp39-pypy39_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:def7400461c3a3f26e49078302e1c1b38f6752342c77e3cf72ce91ca69fb1bc1"}, - {file = "rpds_py-0.20.0-pp39-pypy39_pp73-musllinux_1_2_aarch64.whl", hash = "sha256:65794e4048ee837494aea3c21a28ad5fc080994dfba5b036cf84de37f7ad5074"}, - {file = "rpds_py-0.20.0-pp39-pypy39_pp73-musllinux_1_2_i686.whl", hash = "sha256:faefcc78f53a88f3076b7f8be0a8f8d35133a3ecf7f3770895c25f8813460f08"}, - {file = "rpds_py-0.20.0-pp39-pypy39_pp73-musllinux_1_2_x86_64.whl", hash = "sha256:5b4f105deeffa28bbcdff6c49b34e74903139afa690e35d2d9e3c2c2fba18cec"}, - {file = "rpds_py-0.20.0-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:fdfc3a892927458d98f3d55428ae46b921d1f7543b89382fdb483f5640daaec8"}, - {file = "rpds_py-0.20.0.tar.gz", hash = "sha256:d72a210824facfdaf8768cf2d7ca25a042c30320b3020de2fa04640920d4e121"}, + {file = "rpds_py-0.21.0-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:a017f813f24b9df929674d0332a374d40d7f0162b326562daae8066b502d0590"}, + {file = "rpds_py-0.21.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:20cc1ed0bcc86d8e1a7e968cce15be45178fd16e2ff656a243145e0b439bd250"}, + {file = "rpds_py-0.21.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ad116dda078d0bc4886cb7840e19811562acdc7a8e296ea6ec37e70326c1b41c"}, + {file = "rpds_py-0.21.0-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:808f1ac7cf3b44f81c9475475ceb221f982ef548e44e024ad5f9e7060649540e"}, + {file = "rpds_py-0.21.0-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:de552f4a1916e520f2703ec474d2b4d3f86d41f353e7680b597512ffe7eac5d0"}, + {file = "rpds_py-0.21.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:efec946f331349dfc4ae9d0e034c263ddde19414fe5128580f512619abed05f1"}, + {file = "rpds_py-0.21.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b80b4690bbff51a034bfde9c9f6bf9357f0a8c61f548942b80f7b66356508bf5"}, + {file = "rpds_py-0.21.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:085ed25baac88953d4283e5b5bd094b155075bb40d07c29c4f073e10623f9f2e"}, + {file = "rpds_py-0.21.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:daa8efac2a1273eed2354397a51216ae1e198ecbce9036fba4e7610b308b6153"}, + {file = "rpds_py-0.21.0-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:95a5bad1ac8a5c77b4e658671642e4af3707f095d2b78a1fdd08af0dfb647624"}, + {file = "rpds_py-0.21.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:3e53861b29a13d5b70116ea4230b5f0f3547b2c222c5daa090eb7c9c82d7f664"}, + {file = "rpds_py-0.21.0-cp310-none-win32.whl", hash = "sha256:ea3a6ac4d74820c98fcc9da4a57847ad2cc36475a8bd9683f32ab6d47a2bd682"}, + {file = "rpds_py-0.21.0-cp310-none-win_amd64.whl", hash = "sha256:b8f107395f2f1d151181880b69a2869c69e87ec079c49c0016ab96860b6acbe5"}, + {file = "rpds_py-0.21.0-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:5555db3e618a77034954b9dc547eae94166391a98eb867905ec8fcbce1308d95"}, + {file = "rpds_py-0.21.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:97ef67d9bbc3e15584c2f3c74bcf064af36336c10d2e21a2131e123ce0f924c9"}, + {file = "rpds_py-0.21.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4ab2c2a26d2f69cdf833174f4d9d86118edc781ad9a8fa13970b527bf8236027"}, + {file = "rpds_py-0.21.0-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:4e8921a259f54bfbc755c5bbd60c82bb2339ae0324163f32868f63f0ebb873d9"}, + {file = "rpds_py-0.21.0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:8a7ff941004d74d55a47f916afc38494bd1cfd4b53c482b77c03147c91ac0ac3"}, + {file = "rpds_py-0.21.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5145282a7cd2ac16ea0dc46b82167754d5e103a05614b724457cffe614f25bd8"}, + {file = "rpds_py-0.21.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:de609a6f1b682f70bb7163da745ee815d8f230d97276db049ab447767466a09d"}, + {file = "rpds_py-0.21.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:40c91c6e34cf016fa8e6b59d75e3dbe354830777fcfd74c58b279dceb7975b75"}, + {file = "rpds_py-0.21.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:d2132377f9deef0c4db89e65e8bb28644ff75a18df5293e132a8d67748397b9f"}, + {file = "rpds_py-0.21.0-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:0a9e0759e7be10109645a9fddaaad0619d58c9bf30a3f248a2ea57a7c417173a"}, + {file = "rpds_py-0.21.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:9e20da3957bdf7824afdd4b6eeb29510e83e026473e04952dca565170cd1ecc8"}, + {file = "rpds_py-0.21.0-cp311-none-win32.whl", hash = "sha256:f71009b0d5e94c0e86533c0b27ed7cacc1239cb51c178fd239c3cfefefb0400a"}, + {file = "rpds_py-0.21.0-cp311-none-win_amd64.whl", hash = "sha256:e168afe6bf6ab7ab46c8c375606298784ecbe3ba31c0980b7dcbb9631dcba97e"}, + {file = "rpds_py-0.21.0-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:30b912c965b2aa76ba5168fd610087bad7fcde47f0a8367ee8f1876086ee6d1d"}, + {file = "rpds_py-0.21.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:ca9989d5d9b1b300bc18e1801c67b9f6d2c66b8fd9621b36072ed1df2c977f72"}, + {file = "rpds_py-0.21.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6f54e7106f0001244a5f4cf810ba8d3f9c542e2730821b16e969d6887b664266"}, + {file = "rpds_py-0.21.0-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:fed5dfefdf384d6fe975cc026886aece4f292feaf69d0eeb716cfd3c5a4dd8be"}, + {file = "rpds_py-0.21.0-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:590ef88db231c9c1eece44dcfefd7515d8bf0d986d64d0caf06a81998a9e8cab"}, + {file = "rpds_py-0.21.0-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f983e4c2f603c95dde63df633eec42955508eefd8d0f0e6d236d31a044c882d7"}, + {file = "rpds_py-0.21.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b229ce052ddf1a01c67d68166c19cb004fb3612424921b81c46e7ea7ccf7c3bf"}, + {file = "rpds_py-0.21.0-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:ebf64e281a06c904a7636781d2e973d1f0926a5b8b480ac658dc0f556e7779f4"}, + {file = "rpds_py-0.21.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:998a8080c4495e4f72132f3d66ff91f5997d799e86cec6ee05342f8f3cda7dca"}, + {file = "rpds_py-0.21.0-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:98486337f7b4f3c324ab402e83453e25bb844f44418c066623db88e4c56b7c7b"}, + {file = "rpds_py-0.21.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:a78d8b634c9df7f8d175451cfeac3810a702ccb85f98ec95797fa98b942cea11"}, + {file = "rpds_py-0.21.0-cp312-none-win32.whl", hash = "sha256:a58ce66847711c4aa2ecfcfaff04cb0327f907fead8945ffc47d9407f41ff952"}, + {file = "rpds_py-0.21.0-cp312-none-win_amd64.whl", hash = "sha256:e860f065cc4ea6f256d6f411aba4b1251255366e48e972f8a347cf88077b24fd"}, + {file = "rpds_py-0.21.0-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:ee4eafd77cc98d355a0d02f263efc0d3ae3ce4a7c24740010a8b4012bbb24937"}, + {file = "rpds_py-0.21.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:688c93b77e468d72579351a84b95f976bd7b3e84aa6686be6497045ba84be560"}, + {file = "rpds_py-0.21.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c38dbf31c57032667dd5a2f0568ccde66e868e8f78d5a0d27dcc56d70f3fcd3b"}, + {file = "rpds_py-0.21.0-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:2d6129137f43f7fa02d41542ffff4871d4aefa724a5fe38e2c31a4e0fd343fb0"}, + {file = "rpds_py-0.21.0-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:520ed8b99b0bf86a176271f6fe23024323862ac674b1ce5b02a72bfeff3fff44"}, + {file = "rpds_py-0.21.0-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:aaeb25ccfb9b9014a10eaf70904ebf3f79faaa8e60e99e19eef9f478651b9b74"}, + {file = "rpds_py-0.21.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:af04ac89c738e0f0f1b913918024c3eab6e3ace989518ea838807177d38a2e94"}, + {file = "rpds_py-0.21.0-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:b9b76e2afd585803c53c5b29e992ecd183f68285b62fe2668383a18e74abe7a3"}, + {file = "rpds_py-0.21.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:5afb5efde74c54724e1a01118c6e5c15e54e642c42a1ba588ab1f03544ac8c7a"}, + {file = "rpds_py-0.21.0-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:52c041802a6efa625ea18027a0723676a778869481d16803481ef6cc02ea8cb3"}, + {file = "rpds_py-0.21.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:ee1e4fc267b437bb89990b2f2abf6c25765b89b72dd4a11e21934df449e0c976"}, + {file = "rpds_py-0.21.0-cp313-none-win32.whl", hash = "sha256:0c025820b78817db6a76413fff6866790786c38f95ea3f3d3c93dbb73b632202"}, + {file = "rpds_py-0.21.0-cp313-none-win_amd64.whl", hash = "sha256:320c808df533695326610a1b6a0a6e98f033e49de55d7dc36a13c8a30cfa756e"}, + {file = "rpds_py-0.21.0-cp39-cp39-macosx_10_12_x86_64.whl", hash = "sha256:2c51d99c30091f72a3c5d126fad26236c3f75716b8b5e5cf8effb18889ced928"}, + {file = "rpds_py-0.21.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:cbd7504a10b0955ea287114f003b7ad62330c9e65ba012c6223dba646f6ffd05"}, + {file = "rpds_py-0.21.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6dcc4949be728ede49e6244eabd04064336012b37f5c2200e8ec8eb2988b209c"}, + {file = "rpds_py-0.21.0-cp39-cp39-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:f414da5c51bf350e4b7960644617c130140423882305f7574b6cf65a3081cecb"}, + {file = "rpds_py-0.21.0-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:9afe42102b40007f588666bc7de82451e10c6788f6f70984629db193849dced1"}, + {file = "rpds_py-0.21.0-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3b929c2bb6e29ab31f12a1117c39f7e6d6450419ab7464a4ea9b0b417174f044"}, + {file = "rpds_py-0.21.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8404b3717da03cbf773a1d275d01fec84ea007754ed380f63dfc24fb76ce4592"}, + {file = "rpds_py-0.21.0-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:e12bb09678f38b7597b8346983d2323a6482dcd59e423d9448108c1be37cac9d"}, + {file = "rpds_py-0.21.0-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:58a0e345be4b18e6b8501d3b0aa540dad90caeed814c515e5206bb2ec26736fd"}, + {file = "rpds_py-0.21.0-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:c3761f62fcfccf0864cc4665b6e7c3f0c626f0380b41b8bd1ce322103fa3ef87"}, + {file = "rpds_py-0.21.0-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:c2b2f71c6ad6c2e4fc9ed9401080badd1469fa9889657ec3abea42a3d6b2e1ed"}, + {file = "rpds_py-0.21.0-cp39-none-win32.whl", hash = "sha256:b21747f79f360e790525e6f6438c7569ddbfb1b3197b9e65043f25c3c9b489d8"}, + {file = "rpds_py-0.21.0-cp39-none-win_amd64.whl", hash = "sha256:0626238a43152918f9e72ede9a3b6ccc9e299adc8ade0d67c5e142d564c9a83d"}, + {file = "rpds_py-0.21.0-pp310-pypy310_pp73-macosx_10_12_x86_64.whl", hash = "sha256:6b4ef7725386dc0762857097f6b7266a6cdd62bfd209664da6712cb26acef035"}, + {file = "rpds_py-0.21.0-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:6bc0e697d4d79ab1aacbf20ee5f0df80359ecf55db33ff41481cf3e24f206919"}, + {file = "rpds_py-0.21.0-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:da52d62a96e61c1c444f3998c434e8b263c384f6d68aca8274d2e08d1906325c"}, + {file = "rpds_py-0.21.0-pp310-pypy310_pp73-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:98e4fe5db40db87ce1c65031463a760ec7906ab230ad2249b4572c2fc3ef1f9f"}, + {file = "rpds_py-0.21.0-pp310-pypy310_pp73-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:30bdc973f10d28e0337f71d202ff29345320f8bc49a31c90e6c257e1ccef4333"}, + {file = "rpds_py-0.21.0-pp310-pypy310_pp73-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:faa5e8496c530f9c71f2b4e1c49758b06e5f4055e17144906245c99fa6d45356"}, + {file = "rpds_py-0.21.0-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:32eb88c30b6a4f0605508023b7141d043a79b14acb3b969aa0b4f99b25bc7d4a"}, + {file = "rpds_py-0.21.0-pp310-pypy310_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:a89a8ce9e4e75aeb7fa5d8ad0f3fecdee813802592f4f46a15754dcb2fd6b061"}, + {file = "rpds_py-0.21.0-pp310-pypy310_pp73-musllinux_1_2_aarch64.whl", hash = "sha256:241e6c125568493f553c3d0fdbb38c74babf54b45cef86439d4cd97ff8feb34d"}, + {file = "rpds_py-0.21.0-pp310-pypy310_pp73-musllinux_1_2_i686.whl", hash = "sha256:3b766a9f57663396e4f34f5140b3595b233a7b146e94777b97a8413a1da1be18"}, + {file = "rpds_py-0.21.0-pp310-pypy310_pp73-musllinux_1_2_x86_64.whl", hash = "sha256:af4a644bf890f56e41e74be7d34e9511e4954894d544ec6b8efe1e21a1a8da6c"}, + {file = "rpds_py-0.21.0-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:3e30a69a706e8ea20444b98a49f386c17b26f860aa9245329bab0851ed100677"}, + {file = "rpds_py-0.21.0-pp39-pypy39_pp73-macosx_10_12_x86_64.whl", hash = "sha256:031819f906bb146561af051c7cef4ba2003d28cff07efacef59da973ff7969ba"}, + {file = "rpds_py-0.21.0-pp39-pypy39_pp73-macosx_11_0_arm64.whl", hash = "sha256:b876f2bc27ab5954e2fd88890c071bd0ed18b9c50f6ec3de3c50a5ece612f7a6"}, + {file = "rpds_py-0.21.0-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:dc5695c321e518d9f03b7ea6abb5ea3af4567766f9852ad1560f501b17588c7b"}, + {file = "rpds_py-0.21.0-pp39-pypy39_pp73-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:b4de1da871b5c0fd5537b26a6fc6814c3cc05cabe0c941db6e9044ffbb12f04a"}, + {file = "rpds_py-0.21.0-pp39-pypy39_pp73-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:878f6fea96621fda5303a2867887686d7a198d9e0f8a40be100a63f5d60c88c9"}, + {file = "rpds_py-0.21.0-pp39-pypy39_pp73-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a8eeec67590e94189f434c6d11c426892e396ae59e4801d17a93ac96b8c02a6c"}, + {file = "rpds_py-0.21.0-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1ff2eba7f6c0cb523d7e9cff0903f2fe1feff8f0b2ceb6bd71c0e20a4dcee271"}, + {file = "rpds_py-0.21.0-pp39-pypy39_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:a429b99337062877d7875e4ff1a51fe788424d522bd64a8c0a20ef3021fdb6ed"}, + {file = "rpds_py-0.21.0-pp39-pypy39_pp73-musllinux_1_2_aarch64.whl", hash = "sha256:d167e4dbbdac48bd58893c7e446684ad5d425b407f9336e04ab52e8b9194e2ed"}, + {file = "rpds_py-0.21.0-pp39-pypy39_pp73-musllinux_1_2_i686.whl", hash = "sha256:4eb2de8a147ffe0626bfdc275fc6563aa7bf4b6db59cf0d44f0ccd6ca625a24e"}, + {file = "rpds_py-0.21.0-pp39-pypy39_pp73-musllinux_1_2_x86_64.whl", hash = "sha256:e78868e98f34f34a88e23ee9ccaeeec460e4eaf6db16d51d7a9b883e5e785a5e"}, + {file = "rpds_py-0.21.0-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:4991ca61656e3160cdaca4851151fd3f4a92e9eba5c7a530ab030d6aee96ec89"}, + {file = "rpds_py-0.21.0.tar.gz", hash = "sha256:ed6378c9d66d0de903763e7706383d60c33829581f0adff47b6535f1802fa6db"}, ] [[package]] @@ -2132,29 +2119,29 @@ pyasn1 = ">=0.1.3" [[package]] name = "ruff" -version = "0.7.1" +version = "0.7.4" description = "An extremely fast Python linter and code formatter, written in Rust." optional = false python-versions = ">=3.7" files = [ - {file = "ruff-0.7.1-py3-none-linux_armv6l.whl", hash = "sha256:cb1bc5ed9403daa7da05475d615739cc0212e861b7306f314379d958592aaa89"}, - {file = "ruff-0.7.1-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:27c1c52a8d199a257ff1e5582d078eab7145129aa02721815ca8fa4f9612dc35"}, - {file = "ruff-0.7.1-py3-none-macosx_11_0_arm64.whl", hash = "sha256:588a34e1ef2ea55b4ddfec26bbe76bc866e92523d8c6cdec5e8aceefeff02d99"}, - {file = "ruff-0.7.1-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:94fc32f9cdf72dc75c451e5f072758b118ab8100727168a3df58502b43a599ca"}, - {file = "ruff-0.7.1-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:985818742b833bffa543a84d1cc11b5e6871de1b4e0ac3060a59a2bae3969250"}, - {file = "ruff-0.7.1-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:32f1e8a192e261366c702c5fb2ece9f68d26625f198a25c408861c16dc2dea9c"}, - {file = "ruff-0.7.1-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:699085bf05819588551b11751eff33e9ca58b1b86a6843e1b082a7de40da1565"}, - {file = "ruff-0.7.1-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:344cc2b0814047dc8c3a8ff2cd1f3d808bb23c6658db830d25147339d9bf9ea7"}, - {file = "ruff-0.7.1-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:4316bbf69d5a859cc937890c7ac7a6551252b6a01b1d2c97e8fc96e45a7c8b4a"}, - {file = "ruff-0.7.1-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:79d3af9dca4c56043e738a4d6dd1e9444b6d6c10598ac52d146e331eb155a8ad"}, - {file = "ruff-0.7.1-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:c5c121b46abde94a505175524e51891f829414e093cd8326d6e741ecfc0a9112"}, - {file = "ruff-0.7.1-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:8422104078324ea250886954e48f1373a8fe7de59283d747c3a7eca050b4e378"}, - {file = "ruff-0.7.1-py3-none-musllinux_1_2_i686.whl", hash = "sha256:56aad830af8a9db644e80098fe4984a948e2b6fc2e73891538f43bbe478461b8"}, - {file = "ruff-0.7.1-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:658304f02f68d3a83c998ad8bf91f9b4f53e93e5412b8f2388359d55869727fd"}, - {file = "ruff-0.7.1-py3-none-win32.whl", hash = "sha256:b517a2011333eb7ce2d402652ecaa0ac1a30c114fbbd55c6b8ee466a7f600ee9"}, - {file = "ruff-0.7.1-py3-none-win_amd64.whl", hash = "sha256:f38c41fcde1728736b4eb2b18850f6d1e3eedd9678c914dede554a70d5241307"}, - {file = "ruff-0.7.1-py3-none-win_arm64.whl", hash = "sha256:19aa200ec824c0f36d0c9114c8ec0087082021732979a359d6f3c390a6ff2a37"}, - {file = "ruff-0.7.1.tar.gz", hash = "sha256:9d8a41d4aa2dad1575adb98a82870cf5db5f76b2938cf2206c22c940034a36f4"}, + {file = "ruff-0.7.4-py3-none-linux_armv6l.whl", hash = "sha256:a4919925e7684a3f18e18243cd6bea7cfb8e968a6eaa8437971f681b7ec51478"}, + {file = "ruff-0.7.4-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:cfb365c135b830778dda8c04fb7d4280ed0b984e1aec27f574445231e20d6c63"}, + {file = "ruff-0.7.4-py3-none-macosx_11_0_arm64.whl", hash = "sha256:63a569b36bc66fbadec5beaa539dd81e0527cb258b94e29e0531ce41bacc1f20"}, + {file = "ruff-0.7.4-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0d06218747d361d06fd2fdac734e7fa92df36df93035db3dc2ad7aa9852cb109"}, + {file = "ruff-0.7.4-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:e0cea28d0944f74ebc33e9f934238f15c758841f9f5edd180b5315c203293452"}, + {file = "ruff-0.7.4-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:80094ecd4793c68b2571b128f91754d60f692d64bc0d7272ec9197fdd09bf9ea"}, + {file = "ruff-0.7.4-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:997512325c6620d1c4c2b15db49ef59543ef9cd0f4aa8065ec2ae5103cedc7e7"}, + {file = "ruff-0.7.4-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:00b4cf3a6b5fad6d1a66e7574d78956bbd09abfd6c8a997798f01f5da3d46a05"}, + {file = "ruff-0.7.4-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:7dbdc7d8274e1422722933d1edddfdc65b4336abf0b16dfcb9dedd6e6a517d06"}, + {file = "ruff-0.7.4-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0e92dfb5f00eaedb1501b2f906ccabfd67b2355bdf117fea9719fc99ac2145bc"}, + {file = "ruff-0.7.4-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:3bd726099f277d735dc38900b6a8d6cf070f80828877941983a57bca1cd92172"}, + {file = "ruff-0.7.4-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:2e32829c429dd081ee5ba39aef436603e5b22335c3d3fff013cd585806a6486a"}, + {file = "ruff-0.7.4-py3-none-musllinux_1_2_i686.whl", hash = "sha256:662a63b4971807623f6f90c1fb664613f67cc182dc4d991471c23c541fee62dd"}, + {file = "ruff-0.7.4-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:876f5e09eaae3eb76814c1d3b68879891d6fde4824c015d48e7a7da4cf066a3a"}, + {file = "ruff-0.7.4-py3-none-win32.whl", hash = "sha256:75c53f54904be42dd52a548728a5b572344b50d9b2873d13a3f8c5e3b91f5cac"}, + {file = "ruff-0.7.4-py3-none-win_amd64.whl", hash = "sha256:745775c7b39f914238ed1f1b0bebed0b9155a17cd8bc0b08d3c87e4703b990d6"}, + {file = "ruff-0.7.4-py3-none-win_arm64.whl", hash = "sha256:11bff065102c3ae9d3ea4dc9ecdfe5a5171349cdd0787c1fc64761212fc9cf1f"}, + {file = "ruff-0.7.4.tar.gz", hash = "sha256:cd12e35031f5af6b9b93715d8c4f40360070b2041f81273d0527683d5708fce2"}, ] [[package]] @@ -2232,13 +2219,13 @@ test = ["pytest", "tornado (>=4.5)", "typeguard"] [[package]] name = "tomli" -version = "2.0.2" +version = "2.1.0" description = "A lil' TOML parser" optional = false python-versions = ">=3.8" files = [ - {file = "tomli-2.0.2-py3-none-any.whl", hash = "sha256:2ebe24485c53d303f690b0ec092806a085f07af5a5aa1464f3931eec36caaa38"}, - {file = "tomli-2.0.2.tar.gz", hash = "sha256:d46d457a85337051c36524bc5349dd91b1877838e2979ac5ced3e710ed8a60ed"}, + {file = "tomli-2.1.0-py3-none-any.whl", hash = "sha256:a5c57c3d1c56f5ccdf89f6523458f60ef716e210fc47c4cfb188c5ba473e0391"}, + {file = "tomli-2.1.0.tar.gz", hash = "sha256:3f646cae2aec94e17d04973e4249548320197cfabdf130015d023de4b74d8ab8"}, ] [[package]] @@ -2339,97 +2326,80 @@ test = ["websockets"] [[package]] name = "websockets" -version = "13.1" +version = "14.1" description = "An implementation of the WebSocket Protocol (RFC 6455 & 7692)" optional = false -python-versions = ">=3.8" +python-versions = ">=3.9" files = [ - {file = "websockets-13.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:f48c749857f8fb598fb890a75f540e3221d0976ed0bf879cf3c7eef34151acee"}, - {file = "websockets-13.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:c7e72ce6bda6fb9409cc1e8164dd41d7c91466fb599eb047cfda72fe758a34a7"}, - {file = "websockets-13.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:f779498eeec470295a2b1a5d97aa1bc9814ecd25e1eb637bd9d1c73a327387f6"}, - {file = "websockets-13.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4676df3fe46956fbb0437d8800cd5f2b6d41143b6e7e842e60554398432cf29b"}, - {file = "websockets-13.1-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a7affedeb43a70351bb811dadf49493c9cfd1ed94c9c70095fd177e9cc1541fa"}, - {file = "websockets-13.1-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1971e62d2caa443e57588e1d82d15f663b29ff9dfe7446d9964a4b6f12c1e700"}, - {file = "websockets-13.1-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:5f2e75431f8dc4a47f31565a6e1355fb4f2ecaa99d6b89737527ea917066e26c"}, - {file = "websockets-13.1-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:58cf7e75dbf7e566088b07e36ea2e3e2bd5676e22216e4cad108d4df4a7402a0"}, - {file = "websockets-13.1-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:c90d6dec6be2c7d03378a574de87af9b1efea77d0c52a8301dd831ece938452f"}, - {file = "websockets-13.1-cp310-cp310-win32.whl", hash = "sha256:730f42125ccb14602f455155084f978bd9e8e57e89b569b4d7f0f0c17a448ffe"}, - {file = "websockets-13.1-cp310-cp310-win_amd64.whl", hash = "sha256:5993260f483d05a9737073be197371940c01b257cc45ae3f1d5d7adb371b266a"}, - {file = "websockets-13.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:61fc0dfcda609cda0fc9fe7977694c0c59cf9d749fbb17f4e9483929e3c48a19"}, - {file = "websockets-13.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:ceec59f59d092c5007e815def4ebb80c2de330e9588e101cf8bd94c143ec78a5"}, - {file = "websockets-13.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:c1dca61c6db1166c48b95198c0b7d9c990b30c756fc2923cc66f68d17dc558fd"}, - {file = "websockets-13.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:308e20f22c2c77f3f39caca508e765f8725020b84aa963474e18c59accbf4c02"}, - {file = "websockets-13.1-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:62d516c325e6540e8a57b94abefc3459d7dab8ce52ac75c96cad5549e187e3a7"}, - {file = "websockets-13.1-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:87c6e35319b46b99e168eb98472d6c7d8634ee37750d7693656dc766395df096"}, - {file = "websockets-13.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:5f9fee94ebafbc3117c30be1844ed01a3b177bb6e39088bc6b2fa1dc15572084"}, - {file = "websockets-13.1-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:7c1e90228c2f5cdde263253fa5db63e6653f1c00e7ec64108065a0b9713fa1b3"}, - {file = "websockets-13.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:6548f29b0e401eea2b967b2fdc1c7c7b5ebb3eeb470ed23a54cd45ef078a0db9"}, - {file = "websockets-13.1-cp311-cp311-win32.whl", hash = "sha256:c11d4d16e133f6df8916cc5b7e3e96ee4c44c936717d684a94f48f82edb7c92f"}, - {file = "websockets-13.1-cp311-cp311-win_amd64.whl", hash = "sha256:d04f13a1d75cb2b8382bdc16ae6fa58c97337253826dfe136195b7f89f661557"}, - {file = "websockets-13.1-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:9d75baf00138f80b48f1eac72ad1535aac0b6461265a0bcad391fc5aba875cfc"}, - {file = "websockets-13.1-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:9b6f347deb3dcfbfde1c20baa21c2ac0751afaa73e64e5b693bb2b848efeaa49"}, - {file = "websockets-13.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:de58647e3f9c42f13f90ac7e5f58900c80a39019848c5547bc691693098ae1bd"}, - {file = "websockets-13.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a1b54689e38d1279a51d11e3467dd2f3a50f5f2e879012ce8f2d6943f00e83f0"}, - {file = "websockets-13.1-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:cf1781ef73c073e6b0f90af841aaf98501f975d306bbf6221683dd594ccc52b6"}, - {file = "websockets-13.1-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8d23b88b9388ed85c6faf0e74d8dec4f4d3baf3ecf20a65a47b836d56260d4b9"}, - {file = "websockets-13.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:3c78383585f47ccb0fcf186dcb8a43f5438bd7d8f47d69e0b56f71bf431a0a68"}, - {file = "websockets-13.1-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:d6d300f8ec35c24025ceb9b9019ae9040c1ab2f01cddc2bcc0b518af31c75c14"}, - {file = "websockets-13.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:a9dcaf8b0cc72a392760bb8755922c03e17a5a54e08cca58e8b74f6902b433cf"}, - {file = "websockets-13.1-cp312-cp312-win32.whl", hash = "sha256:2f85cf4f2a1ba8f602298a853cec8526c2ca42a9a4b947ec236eaedb8f2dc80c"}, - {file = "websockets-13.1-cp312-cp312-win_amd64.whl", hash = "sha256:38377f8b0cdeee97c552d20cf1865695fcd56aba155ad1b4ca8779a5b6ef4ac3"}, - {file = "websockets-13.1-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:a9ab1e71d3d2e54a0aa646ab6d4eebfaa5f416fe78dfe4da2839525dc5d765c6"}, - {file = "websockets-13.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:b9d7439d7fab4dce00570bb906875734df13d9faa4b48e261c440a5fec6d9708"}, - {file = "websockets-13.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:327b74e915cf13c5931334c61e1a41040e365d380f812513a255aa804b183418"}, - {file = "websockets-13.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:325b1ccdbf5e5725fdcb1b0e9ad4d2545056479d0eee392c291c1bf76206435a"}, - {file = "websockets-13.1-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:346bee67a65f189e0e33f520f253d5147ab76ae42493804319b5716e46dddf0f"}, - {file = "websockets-13.1-cp313-cp313-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:91a0fa841646320ec0d3accdff5b757b06e2e5c86ba32af2e0815c96c7a603c5"}, - {file = "websockets-13.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:18503d2c5f3943e93819238bf20df71982d193f73dcecd26c94514f417f6b135"}, - {file = "websockets-13.1-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:a9cd1af7e18e5221d2878378fbc287a14cd527fdd5939ed56a18df8a31136bb2"}, - {file = "websockets-13.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:70c5be9f416aa72aab7a2a76c90ae0a4fe2755c1816c153c1a2bcc3333ce4ce6"}, - {file = "websockets-13.1-cp313-cp313-win32.whl", hash = "sha256:624459daabeb310d3815b276c1adef475b3e6804abaf2d9d2c061c319f7f187d"}, - {file = "websockets-13.1-cp313-cp313-win_amd64.whl", hash = "sha256:c518e84bb59c2baae725accd355c8dc517b4a3ed8db88b4bc93c78dae2974bf2"}, - {file = "websockets-13.1-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:c7934fd0e920e70468e676fe7f1b7261c1efa0d6c037c6722278ca0228ad9d0d"}, - {file = "websockets-13.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:149e622dc48c10ccc3d2760e5f36753db9cacf3ad7bc7bbbfd7d9c819e286f23"}, - {file = "websockets-13.1-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:a569eb1b05d72f9bce2ebd28a1ce2054311b66677fcd46cf36204ad23acead8c"}, - {file = "websockets-13.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:95df24ca1e1bd93bbca51d94dd049a984609687cb2fb08a7f2c56ac84e9816ea"}, - {file = "websockets-13.1-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d8dbb1bf0c0a4ae8b40bdc9be7f644e2f3fb4e8a9aca7145bfa510d4a374eeb7"}, - {file = "websockets-13.1-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:035233b7531fb92a76beefcbf479504db8c72eb3bff41da55aecce3a0f729e54"}, - {file = "websockets-13.1-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:e4450fc83a3df53dec45922b576e91e94f5578d06436871dce3a6be38e40f5db"}, - {file = "websockets-13.1-cp38-cp38-musllinux_1_2_i686.whl", hash = "sha256:463e1c6ec853202dd3657f156123d6b4dad0c546ea2e2e38be2b3f7c5b8e7295"}, - {file = "websockets-13.1-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:6d6855bbe70119872c05107e38fbc7f96b1d8cb047d95c2c50869a46c65a8e96"}, - {file = "websockets-13.1-cp38-cp38-win32.whl", hash = "sha256:204e5107f43095012b00f1451374693267adbb832d29966a01ecc4ce1db26faf"}, - {file = "websockets-13.1-cp38-cp38-win_amd64.whl", hash = "sha256:485307243237328c022bc908b90e4457d0daa8b5cf4b3723fd3c4a8012fce4c6"}, - {file = "websockets-13.1-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:9b37c184f8b976f0c0a231a5f3d6efe10807d41ccbe4488df8c74174805eea7d"}, - {file = "websockets-13.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:163e7277e1a0bd9fb3c8842a71661ad19c6aa7bb3d6678dc7f89b17fbcc4aeb7"}, - {file = "websockets-13.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:4b889dbd1342820cc210ba44307cf75ae5f2f96226c0038094455a96e64fb07a"}, - {file = "websockets-13.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:586a356928692c1fed0eca68b4d1c2cbbd1ca2acf2ac7e7ebd3b9052582deefa"}, - {file = "websockets-13.1-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7bd6abf1e070a6b72bfeb71049d6ad286852e285f146682bf30d0296f5fbadfa"}, - {file = "websockets-13.1-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6d2aad13a200e5934f5a6767492fb07151e1de1d6079c003ab31e1823733ae79"}, - {file = "websockets-13.1-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:df01aea34b6e9e33572c35cd16bae5a47785e7d5c8cb2b54b2acdb9678315a17"}, - {file = "websockets-13.1-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:e54affdeb21026329fb0744ad187cf812f7d3c2aa702a5edb562b325191fcab6"}, - {file = "websockets-13.1-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:9ef8aa8bdbac47f4968a5d66462a2a0935d044bf35c0e5a8af152d58516dbeb5"}, - {file = "websockets-13.1-cp39-cp39-win32.whl", hash = "sha256:deeb929efe52bed518f6eb2ddc00cc496366a14c726005726ad62c2dd9017a3c"}, - {file = "websockets-13.1-cp39-cp39-win_amd64.whl", hash = "sha256:7c65ffa900e7cc958cd088b9a9157a8141c991f8c53d11087e6fb7277a03f81d"}, - {file = "websockets-13.1-pp310-pypy310_pp73-macosx_10_15_x86_64.whl", hash = "sha256:5dd6da9bec02735931fccec99d97c29f47cc61f644264eb995ad6c0c27667238"}, - {file = "websockets-13.1-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:2510c09d8e8df777177ee3d40cd35450dc169a81e747455cc4197e63f7e7bfe5"}, - {file = "websockets-13.1-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f1c3cf67185543730888b20682fb186fc8d0fa6f07ccc3ef4390831ab4b388d9"}, - {file = "websockets-13.1-pp310-pypy310_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:bcc03c8b72267e97b49149e4863d57c2d77f13fae12066622dc78fe322490fe6"}, - {file = "websockets-13.1-pp310-pypy310_pp73-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:004280a140f220c812e65f36944a9ca92d766b6cc4560be652a0a3883a79ed8a"}, - {file = "websockets-13.1-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:e2620453c075abeb0daa949a292e19f56de518988e079c36478bacf9546ced23"}, - {file = "websockets-13.1-pp38-pypy38_pp73-macosx_10_9_x86_64.whl", hash = "sha256:9156c45750b37337f7b0b00e6248991a047be4aa44554c9886fe6bdd605aab3b"}, - {file = "websockets-13.1-pp38-pypy38_pp73-macosx_11_0_arm64.whl", hash = "sha256:80c421e07973a89fbdd93e6f2003c17d20b69010458d3a8e37fb47874bd67d51"}, - {file = "websockets-13.1-pp38-pypy38_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:82d0ba76371769d6a4e56f7e83bb8e81846d17a6190971e38b5de108bde9b0d7"}, - {file = "websockets-13.1-pp38-pypy38_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e9875a0143f07d74dc5e1ded1c4581f0d9f7ab86c78994e2ed9e95050073c94d"}, - {file = "websockets-13.1-pp38-pypy38_pp73-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a11e38ad8922c7961447f35c7b17bffa15de4d17c70abd07bfbe12d6faa3e027"}, - {file = "websockets-13.1-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:4059f790b6ae8768471cddb65d3c4fe4792b0ab48e154c9f0a04cefaabcd5978"}, - {file = "websockets-13.1-pp39-pypy39_pp73-macosx_10_15_x86_64.whl", hash = "sha256:25c35bf84bf7c7369d247f0b8cfa157f989862c49104c5cf85cb5436a641d93e"}, - {file = "websockets-13.1-pp39-pypy39_pp73-macosx_11_0_arm64.whl", hash = "sha256:83f91d8a9bb404b8c2c41a707ac7f7f75b9442a0a876df295de27251a856ad09"}, - {file = "websockets-13.1-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7a43cfdcddd07f4ca2b1afb459824dd3c6d53a51410636a2c7fc97b9a8cf4842"}, - {file = "websockets-13.1-pp39-pypy39_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:48a2ef1381632a2f0cb4efeff34efa97901c9fbc118e01951ad7cfc10601a9bb"}, - {file = "websockets-13.1-pp39-pypy39_pp73-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:459bf774c754c35dbb487360b12c5727adab887f1622b8aed5755880a21c4a20"}, - {file = "websockets-13.1-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:95858ca14a9f6fa8413d29e0a585b31b278388aa775b8a81fa24830123874678"}, - {file = "websockets-13.1-py3-none-any.whl", hash = "sha256:a9a396a6ad26130cdae92ae10c36af09d9bfe6cafe69670fd3b6da9b07b4044f"}, - {file = "websockets-13.1.tar.gz", hash = "sha256:a3b3366087c1bc0a2795111edcadddb8b3b59509d5db5d7ea3fdd69f954a8878"}, + {file = "websockets-14.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:a0adf84bc2e7c86e8a202537b4fd50e6f7f0e4a6b6bf64d7ccb96c4cd3330b29"}, + {file = "websockets-14.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:90b5d9dfbb6d07a84ed3e696012610b6da074d97453bd01e0e30744b472c8179"}, + {file = "websockets-14.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:2177ee3901075167f01c5e335a6685e71b162a54a89a56001f1c3e9e3d2ad250"}, + {file = "websockets-14.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3f14a96a0034a27f9d47fd9788913924c89612225878f8078bb9d55f859272b0"}, + {file = "websockets-14.1-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1f874ba705deea77bcf64a9da42c1f5fc2466d8f14daf410bc7d4ceae0a9fcb0"}, + {file = "websockets-14.1-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9607b9a442392e690a57909c362811184ea429585a71061cd5d3c2b98065c199"}, + {file = "websockets-14.1-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:bea45f19b7ca000380fbd4e02552be86343080120d074b87f25593ce1700ad58"}, + {file = "websockets-14.1-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:219c8187b3ceeadbf2afcf0f25a4918d02da7b944d703b97d12fb01510869078"}, + {file = "websockets-14.1-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:ad2ab2547761d79926effe63de21479dfaf29834c50f98c4bf5b5480b5838434"}, + {file = "websockets-14.1-cp310-cp310-win32.whl", hash = "sha256:1288369a6a84e81b90da5dbed48610cd7e5d60af62df9851ed1d1d23a9069f10"}, + {file = "websockets-14.1-cp310-cp310-win_amd64.whl", hash = "sha256:e0744623852f1497d825a49a99bfbec9bea4f3f946df6eb9d8a2f0c37a2fec2e"}, + {file = "websockets-14.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:449d77d636f8d9c17952628cc7e3b8faf6e92a17ec581ec0c0256300717e1512"}, + {file = "websockets-14.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:a35f704be14768cea9790d921c2c1cc4fc52700410b1c10948511039be824aac"}, + {file = "websockets-14.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:b1f3628a0510bd58968c0f60447e7a692933589b791a6b572fcef374053ca280"}, + {file = "websockets-14.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3c3deac3748ec73ef24fc7be0b68220d14d47d6647d2f85b2771cb35ea847aa1"}, + {file = "websockets-14.1-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7048eb4415d46368ef29d32133134c513f507fff7d953c18c91104738a68c3b3"}, + {file = "websockets-14.1-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f6cf0ad281c979306a6a34242b371e90e891bce504509fb6bb5246bbbf31e7b6"}, + {file = "websockets-14.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:cc1fc87428c1d18b643479caa7b15db7d544652e5bf610513d4a3478dbe823d0"}, + {file = "websockets-14.1-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:f95ba34d71e2fa0c5d225bde3b3bdb152e957150100e75c86bc7f3964c450d89"}, + {file = "websockets-14.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:9481a6de29105d73cf4515f2bef8eb71e17ac184c19d0b9918a3701c6c9c4f23"}, + {file = "websockets-14.1-cp311-cp311-win32.whl", hash = "sha256:368a05465f49c5949e27afd6fbe0a77ce53082185bbb2ac096a3a8afaf4de52e"}, + {file = "websockets-14.1-cp311-cp311-win_amd64.whl", hash = "sha256:6d24fc337fc055c9e83414c94e1ee0dee902a486d19d2a7f0929e49d7d604b09"}, + {file = "websockets-14.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:ed907449fe5e021933e46a3e65d651f641975a768d0649fee59f10c2985529ed"}, + {file = "websockets-14.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:87e31011b5c14a33b29f17eb48932e63e1dcd3fa31d72209848652310d3d1f0d"}, + {file = "websockets-14.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:bc6ccf7d54c02ae47a48ddf9414c54d48af9c01076a2e1023e3b486b6e72c707"}, + {file = "websockets-14.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9777564c0a72a1d457f0848977a1cbe15cfa75fa2f67ce267441e465717dcf1a"}, + {file = "websockets-14.1-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a655bde548ca98f55b43711b0ceefd2a88a71af6350b0c168aa77562104f3f45"}, + {file = "websockets-14.1-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a3dfff83ca578cada2d19e665e9c8368e1598d4e787422a460ec70e531dbdd58"}, + {file = "websockets-14.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:6a6c9bcf7cdc0fd41cc7b7944447982e8acfd9f0d560ea6d6845428ed0562058"}, + {file = "websockets-14.1-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:4b6caec8576e760f2c7dd878ba817653144d5f369200b6ddf9771d64385b84d4"}, + {file = "websockets-14.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:eb6d38971c800ff02e4a6afd791bbe3b923a9a57ca9aeab7314c21c84bf9ff05"}, + {file = "websockets-14.1-cp312-cp312-win32.whl", hash = "sha256:1d045cbe1358d76b24d5e20e7b1878efe578d9897a25c24e6006eef788c0fdf0"}, + {file = "websockets-14.1-cp312-cp312-win_amd64.whl", hash = "sha256:90f4c7a069c733d95c308380aae314f2cb45bd8a904fb03eb36d1a4983a4993f"}, + {file = "websockets-14.1-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:3630b670d5057cd9e08b9c4dab6493670e8e762a24c2c94ef312783870736ab9"}, + {file = "websockets-14.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:36ebd71db3b89e1f7b1a5deaa341a654852c3518ea7a8ddfdf69cc66acc2db1b"}, + {file = "websockets-14.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:5b918d288958dc3fa1c5a0b9aa3256cb2b2b84c54407f4813c45d52267600cd3"}, + {file = "websockets-14.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:00fe5da3f037041da1ee0cf8e308374e236883f9842c7c465aa65098b1c9af59"}, + {file = "websockets-14.1-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:8149a0f5a72ca36720981418eeffeb5c2729ea55fa179091c81a0910a114a5d2"}, + {file = "websockets-14.1-cp313-cp313-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:77569d19a13015e840b81550922056acabc25e3f52782625bc6843cfa034e1da"}, + {file = "websockets-14.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:cf5201a04550136ef870aa60ad3d29d2a59e452a7f96b94193bee6d73b8ad9a9"}, + {file = "websockets-14.1-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:88cf9163ef674b5be5736a584c999e98daf3aabac6e536e43286eb74c126b9c7"}, + {file = "websockets-14.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:836bef7ae338a072e9d1863502026f01b14027250a4545672673057997d5c05a"}, + {file = "websockets-14.1-cp313-cp313-win32.whl", hash = "sha256:0d4290d559d68288da9f444089fd82490c8d2744309113fc26e2da6e48b65da6"}, + {file = "websockets-14.1-cp313-cp313-win_amd64.whl", hash = "sha256:8621a07991add373c3c5c2cf89e1d277e49dc82ed72c75e3afc74bd0acc446f0"}, + {file = "websockets-14.1-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:01bb2d4f0a6d04538d3c5dfd27c0643269656c28045a53439cbf1c004f90897a"}, + {file = "websockets-14.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:414ffe86f4d6f434a8c3b7913655a1a5383b617f9bf38720e7c0799fac3ab1c6"}, + {file = "websockets-14.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:8fda642151d5affdee8a430bd85496f2e2517be3a2b9d2484d633d5712b15c56"}, + {file = "websockets-14.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:cd7c11968bc3860d5c78577f0dbc535257ccec41750675d58d8dc66aa47fe52c"}, + {file = "websockets-14.1-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a032855dc7db987dff813583d04f4950d14326665d7e714d584560b140ae6b8b"}, + {file = "websockets-14.1-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b7e7ea2f782408c32d86b87a0d2c1fd8871b0399dd762364c731d86c86069a78"}, + {file = "websockets-14.1-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:39450e6215f7d9f6f7bc2a6da21d79374729f5d052333da4d5825af8a97e6735"}, + {file = "websockets-14.1-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:ceada5be22fa5a5a4cdeec74e761c2ee7db287208f54c718f2df4b7e200b8d4a"}, + {file = "websockets-14.1-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:3fc753451d471cff90b8f467a1fc0ae64031cf2d81b7b34e1811b7e2691bc4bc"}, + {file = "websockets-14.1-cp39-cp39-win32.whl", hash = "sha256:14839f54786987ccd9d03ed7f334baec0f02272e7ec4f6e9d427ff584aeea8b4"}, + {file = "websockets-14.1-cp39-cp39-win_amd64.whl", hash = "sha256:d9fd19ecc3a4d5ae82ddbfb30962cf6d874ff943e56e0c81f5169be2fda62979"}, + {file = "websockets-14.1-pp310-pypy310_pp73-macosx_10_15_x86_64.whl", hash = "sha256:e5dc25a9dbd1a7f61eca4b7cb04e74ae4b963d658f9e4f9aad9cd00b688692c8"}, + {file = "websockets-14.1-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:04a97aca96ca2acedf0d1f332c861c5a4486fdcba7bcef35873820f940c4231e"}, + {file = "websockets-14.1-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:df174ece723b228d3e8734a6f2a6febbd413ddec39b3dc592f5a4aa0aff28098"}, + {file = "websockets-14.1-pp310-pypy310_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:034feb9f4286476f273b9a245fb15f02c34d9586a5bc936aff108c3ba1b21beb"}, + {file = "websockets-14.1-pp310-pypy310_pp73-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:660c308dabd2b380807ab64b62985eaccf923a78ebc572bd485375b9ca2b7dc7"}, + {file = "websockets-14.1-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:5a42d3ecbb2db5080fc578314439b1d79eef71d323dc661aa616fb492436af5d"}, + {file = "websockets-14.1-pp39-pypy39_pp73-macosx_10_15_x86_64.whl", hash = "sha256:ddaa4a390af911da6f680be8be4ff5aaf31c4c834c1a9147bc21cbcbca2d4370"}, + {file = "websockets-14.1-pp39-pypy39_pp73-macosx_11_0_arm64.whl", hash = "sha256:a4c805c6034206143fbabd2d259ec5e757f8b29d0a2f0bf3d2fe5d1f60147a4a"}, + {file = "websockets-14.1-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:205f672a6c2c671a86d33f6d47c9b35781a998728d2c7c2a3e1cf3333fcb62b7"}, + {file = "websockets-14.1-pp39-pypy39_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:5ef440054124728cc49b01c33469de06755e5a7a4e83ef61934ad95fc327fbb0"}, + {file = "websockets-14.1-pp39-pypy39_pp73-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e7591d6f440af7f73c4bd9404f3772bfee064e639d2b6cc8c94076e71b2471c1"}, + {file = "websockets-14.1-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:25225cc79cfebc95ba1d24cd3ab86aaa35bcd315d12fa4358939bd55e9bd74a5"}, + {file = "websockets-14.1-py3-none-any.whl", hash = "sha256:4d4fc827a20abe6d544a119896f6b78ee13fe81cbfef416f3f2ddf09a03f0e2e"}, + {file = "websockets-14.1.tar.gz", hash = "sha256:398b10c77d471c0aab20a845e7a60076b6390bfdaac7a6d2edb0d2c59d75e8d8"}, ] [[package]] @@ -2513,13 +2483,13 @@ files = [ [[package]] name = "zipp" -version = "3.20.2" +version = "3.21.0" description = "Backport of pathlib-compatible object wrapper for zip files" optional = false -python-versions = ">=3.8" +python-versions = ">=3.9" files = [ - {file = "zipp-3.20.2-py3-none-any.whl", hash = "sha256:a817ac80d6cf4b23bf7f2828b7cabf326f15a001bea8b1f9b49631780ba28350"}, - {file = "zipp-3.20.2.tar.gz", hash = "sha256:bc9eb26f4506fda01b81bcde0ca78103b6e62f991b381fec825435c836edbc29"}, + {file = "zipp-3.21.0-py3-none-any.whl", hash = "sha256:ac1bbe05fd2991f160ebce24ffbac5f6d11d83dc90891255885223d42b3cd931"}, + {file = "zipp-3.21.0.tar.gz", hash = "sha256:2c9958f6430a2040341a52eb608ed6dd93ef4392e02ffe219417c1b28b5dd1f4"}, ] [package.extras] @@ -2533,4 +2503,4 @@ type = ["pytest-mypy"] [metadata] lock-version = "2.0" python-versions = "^3.10" -content-hash = "a24006bb8af98b161cd722b73b93b3ce7fbc5f44e46ee2d4faa24e438c09e0de" +content-hash = "c8e3d81234f4ddceffabc50a95d54b4f29700a25ea70a86baa7ee81ae91569ca" From a0f30062399e6b8324d479ca195e0720c8743135 Mon Sep 17 00:00:00 2001 From: Lucas Gameiro Borges Date: Tue, 19 Nov 2024 20:53:58 +0000 Subject: [PATCH 10/74] update psycopg2-binary too --- poetry.lock | 132 +++++++++++++++++++++++-------------------------- pyproject.toml | 2 +- 2 files changed, 63 insertions(+), 71 deletions(-) diff --git a/poetry.lock b/poetry.lock index dcac0f8e31..adc555b028 100644 --- a/poetry.lock +++ b/poetry.lock @@ -1469,78 +1469,70 @@ files = [ [[package]] name = "psycopg2-binary" -version = "2.9.10" +version = "2.9.4" description = "psycopg2 - Python-PostgreSQL Database Adapter" optional = false -python-versions = ">=3.8" +python-versions = ">=3.6" files = [ - {file = "psycopg2-binary-2.9.10.tar.gz", hash = "sha256:4b3df0e6990aa98acda57d983942eff13d824135fe2250e6522edaa782a06de2"}, - {file = "psycopg2_binary-2.9.10-cp310-cp310-macosx_12_0_x86_64.whl", hash = "sha256:0ea8e3d0ae83564f2fc554955d327fa081d065c8ca5cc6d2abb643e2c9c1200f"}, - {file = "psycopg2_binary-2.9.10-cp310-cp310-macosx_14_0_arm64.whl", hash = "sha256:3e9c76f0ac6f92ecfc79516a8034a544926430f7b080ec5a0537bca389ee0906"}, - {file = "psycopg2_binary-2.9.10-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2ad26b467a405c798aaa1458ba09d7e2b6e5f96b1ce0ac15d82fd9f95dc38a92"}, - {file = "psycopg2_binary-2.9.10-cp310-cp310-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:270934a475a0e4b6925b5f804e3809dd5f90f8613621d062848dd82f9cd62007"}, - {file = "psycopg2_binary-2.9.10-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:48b338f08d93e7be4ab2b5f1dbe69dc5e9ef07170fe1f86514422076d9c010d0"}, - {file = "psycopg2_binary-2.9.10-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7f4152f8f76d2023aac16285576a9ecd2b11a9895373a1f10fd9db54b3ff06b4"}, - {file = "psycopg2_binary-2.9.10-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:32581b3020c72d7a421009ee1c6bf4a131ef5f0a968fab2e2de0c9d2bb4577f1"}, - {file = "psycopg2_binary-2.9.10-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:2ce3e21dc3437b1d960521eca599d57408a695a0d3c26797ea0f72e834c7ffe5"}, - {file = "psycopg2_binary-2.9.10-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:e984839e75e0b60cfe75e351db53d6db750b00de45644c5d1f7ee5d1f34a1ce5"}, - {file = "psycopg2_binary-2.9.10-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:3c4745a90b78e51d9ba06e2088a2fe0c693ae19cc8cb051ccda44e8df8a6eb53"}, - {file = "psycopg2_binary-2.9.10-cp310-cp310-win32.whl", hash = "sha256:e5720a5d25e3b99cd0dc5c8a440570469ff82659bb09431c1439b92caf184d3b"}, - {file = "psycopg2_binary-2.9.10-cp310-cp310-win_amd64.whl", hash = "sha256:3c18f74eb4386bf35e92ab2354a12c17e5eb4d9798e4c0ad3a00783eae7cd9f1"}, - {file = "psycopg2_binary-2.9.10-cp311-cp311-macosx_12_0_x86_64.whl", hash = "sha256:04392983d0bb89a8717772a193cfaac58871321e3ec69514e1c4e0d4957b5aff"}, - {file = "psycopg2_binary-2.9.10-cp311-cp311-macosx_14_0_arm64.whl", hash = "sha256:1a6784f0ce3fec4edc64e985865c17778514325074adf5ad8f80636cd029ef7c"}, - {file = "psycopg2_binary-2.9.10-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b5f86c56eeb91dc3135b3fd8a95dc7ae14c538a2f3ad77a19645cf55bab1799c"}, - {file = "psycopg2_binary-2.9.10-cp311-cp311-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:2b3d2491d4d78b6b14f76881905c7a8a8abcf974aad4a8a0b065273a0ed7a2cb"}, - {file = "psycopg2_binary-2.9.10-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:2286791ececda3a723d1910441c793be44625d86d1a4e79942751197f4d30341"}, - {file = "psycopg2_binary-2.9.10-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:512d29bb12608891e349af6a0cccedce51677725a921c07dba6342beaf576f9a"}, - {file = "psycopg2_binary-2.9.10-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:5a507320c58903967ef7384355a4da7ff3f28132d679aeb23572753cbf2ec10b"}, - {file = "psycopg2_binary-2.9.10-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:6d4fa1079cab9018f4d0bd2db307beaa612b0d13ba73b5c6304b9fe2fb441ff7"}, - {file = "psycopg2_binary-2.9.10-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:851485a42dbb0bdc1edcdabdb8557c09c9655dfa2ca0460ff210522e073e319e"}, - {file = "psycopg2_binary-2.9.10-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:35958ec9e46432d9076286dda67942ed6d968b9c3a6a2fd62b48939d1d78bf68"}, - {file = "psycopg2_binary-2.9.10-cp311-cp311-win32.whl", hash = "sha256:ecced182e935529727401b24d76634a357c71c9275b356efafd8a2a91ec07392"}, - {file = "psycopg2_binary-2.9.10-cp311-cp311-win_amd64.whl", hash = "sha256:ee0e8c683a7ff25d23b55b11161c2663d4b099770f6085ff0a20d4505778d6b4"}, - {file = "psycopg2_binary-2.9.10-cp312-cp312-macosx_12_0_x86_64.whl", hash = "sha256:880845dfe1f85d9d5f7c412efea7a08946a46894537e4e5d091732eb1d34d9a0"}, - {file = "psycopg2_binary-2.9.10-cp312-cp312-macosx_14_0_arm64.whl", hash = "sha256:9440fa522a79356aaa482aa4ba500b65f28e5d0e63b801abf6aa152a29bd842a"}, - {file = "psycopg2_binary-2.9.10-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e3923c1d9870c49a2d44f795df0c889a22380d36ef92440ff618ec315757e539"}, - {file = "psycopg2_binary-2.9.10-cp312-cp312-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7b2c956c028ea5de47ff3a8d6b3cc3330ab45cf0b7c3da35a2d6ff8420896526"}, - {file = "psycopg2_binary-2.9.10-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f758ed67cab30b9a8d2833609513ce4d3bd027641673d4ebc9c067e4d208eec1"}, - {file = "psycopg2_binary-2.9.10-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8cd9b4f2cfab88ed4a9106192de509464b75a906462fb846b936eabe45c2063e"}, - {file = "psycopg2_binary-2.9.10-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:6dc08420625b5a20b53551c50deae6e231e6371194fa0651dbe0fb206452ae1f"}, - {file = "psycopg2_binary-2.9.10-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:d7cd730dfa7c36dbe8724426bf5612798734bff2d3c3857f36f2733f5bfc7c00"}, - {file = "psycopg2_binary-2.9.10-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:155e69561d54d02b3c3209545fb08938e27889ff5a10c19de8d23eb5a41be8a5"}, - {file = "psycopg2_binary-2.9.10-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:c3cc28a6fd5a4a26224007712e79b81dbaee2ffb90ff406256158ec4d7b52b47"}, - {file = "psycopg2_binary-2.9.10-cp312-cp312-win32.whl", hash = "sha256:ec8a77f521a17506a24a5f626cb2aee7850f9b69a0afe704586f63a464f3cd64"}, - {file = "psycopg2_binary-2.9.10-cp312-cp312-win_amd64.whl", hash = "sha256:18c5ee682b9c6dd3696dad6e54cc7ff3a1a9020df6a5c0f861ef8bfd338c3ca0"}, - {file = "psycopg2_binary-2.9.10-cp313-cp313-macosx_12_0_x86_64.whl", hash = "sha256:26540d4a9a4e2b096f1ff9cce51253d0504dca5a85872c7f7be23be5a53eb18d"}, - {file = "psycopg2_binary-2.9.10-cp313-cp313-macosx_14_0_arm64.whl", hash = "sha256:e217ce4d37667df0bc1c397fdcd8de5e81018ef305aed9415c3b093faaeb10fb"}, - {file = "psycopg2_binary-2.9.10-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:245159e7ab20a71d989da00f280ca57da7641fa2cdcf71749c193cea540a74f7"}, - {file = "psycopg2_binary-2.9.10-cp313-cp313-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3c4ded1a24b20021ebe677b7b08ad10bf09aac197d6943bfe6fec70ac4e4690d"}, - {file = "psycopg2_binary-2.9.10-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3abb691ff9e57d4a93355f60d4f4c1dd2d68326c968e7db17ea96df3c023ef73"}, - {file = "psycopg2_binary-2.9.10-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8608c078134f0b3cbd9f89b34bd60a943b23fd33cc5f065e8d5f840061bd0673"}, - {file = "psycopg2_binary-2.9.10-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:230eeae2d71594103cd5b93fd29d1ace6420d0b86f4778739cb1a5a32f607d1f"}, - {file = "psycopg2_binary-2.9.10-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:bb89f0a835bcfc1d42ccd5f41f04870c1b936d8507c6df12b7737febc40f0909"}, - {file = "psycopg2_binary-2.9.10-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:f0c2d907a1e102526dd2986df638343388b94c33860ff3bbe1384130828714b1"}, - {file = "psycopg2_binary-2.9.10-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:f8157bed2f51db683f31306aa497311b560f2265998122abe1dce6428bd86567"}, - {file = "psycopg2_binary-2.9.10-cp38-cp38-macosx_12_0_x86_64.whl", hash = "sha256:eb09aa7f9cecb45027683bb55aebaaf45a0df8bf6de68801a6afdc7947bb09d4"}, - {file = "psycopg2_binary-2.9.10-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b73d6d7f0ccdad7bc43e6d34273f70d587ef62f824d7261c4ae9b8b1b6af90e8"}, - {file = "psycopg2_binary-2.9.10-cp38-cp38-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ce5ab4bf46a211a8e924d307c1b1fcda82368586a19d0a24f8ae166f5c784864"}, - {file = "psycopg2_binary-2.9.10-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:056470c3dc57904bbf63d6f534988bafc4e970ffd50f6271fc4ee7daad9498a5"}, - {file = "psycopg2_binary-2.9.10-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:73aa0e31fa4bb82578f3a6c74a73c273367727de397a7a0f07bd83cbea696baa"}, - {file = "psycopg2_binary-2.9.10-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:8de718c0e1c4b982a54b41779667242bc630b2197948405b7bd8ce16bcecac92"}, - {file = "psycopg2_binary-2.9.10-cp38-cp38-musllinux_1_2_i686.whl", hash = "sha256:5c370b1e4975df846b0277b4deba86419ca77dbc25047f535b0bb03d1a544d44"}, - {file = "psycopg2_binary-2.9.10-cp38-cp38-musllinux_1_2_ppc64le.whl", hash = "sha256:ffe8ed017e4ed70f68b7b371d84b7d4a790368db9203dfc2d222febd3a9c8863"}, - {file = "psycopg2_binary-2.9.10-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:8aecc5e80c63f7459a1a2ab2c64df952051df196294d9f739933a9f6687e86b3"}, - {file = "psycopg2_binary-2.9.10-cp39-cp39-macosx_12_0_x86_64.whl", hash = "sha256:7a813c8bdbaaaab1f078014b9b0b13f5de757e2b5d9be6403639b298a04d218b"}, - {file = "psycopg2_binary-2.9.10-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d00924255d7fc916ef66e4bf22f354a940c67179ad3fd7067d7a0a9c84d2fbfc"}, - {file = "psycopg2_binary-2.9.10-cp39-cp39-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7559bce4b505762d737172556a4e6ea8a9998ecac1e39b5233465093e8cee697"}, - {file = "psycopg2_binary-2.9.10-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e8b58f0a96e7a1e341fc894f62c1177a7c83febebb5ff9123b579418fdc8a481"}, - {file = "psycopg2_binary-2.9.10-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6b269105e59ac96aba877c1707c600ae55711d9dcd3fc4b5012e4af68e30c648"}, - {file = "psycopg2_binary-2.9.10-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:79625966e176dc97ddabc142351e0409e28acf4660b88d1cf6adb876d20c490d"}, - {file = "psycopg2_binary-2.9.10-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:8aabf1c1a04584c168984ac678a668094d831f152859d06e055288fa515e4d30"}, - {file = "psycopg2_binary-2.9.10-cp39-cp39-musllinux_1_2_ppc64le.whl", hash = "sha256:19721ac03892001ee8fdd11507e6a2e01f4e37014def96379411ca99d78aeb2c"}, - {file = "psycopg2_binary-2.9.10-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:7f5d859928e635fa3ce3477704acee0f667b3a3d3e4bb109f2b18d4005f38287"}, - {file = "psycopg2_binary-2.9.10-cp39-cp39-win32.whl", hash = "sha256:3216ccf953b3f267691c90c6fe742e45d890d8272326b4a8b20850a03d05b7b8"}, - {file = "psycopg2_binary-2.9.10-cp39-cp39-win_amd64.whl", hash = "sha256:30e34c4e97964805f715206c7b789d54a78b70f3ff19fbe590104b71c45600e5"}, + {file = "psycopg2-binary-2.9.4.tar.gz", hash = "sha256:a6a2d3d75d8698dee492f4af7ad07606d0734e581edf9e2ce2f74b6fce90f42e"}, + {file = "psycopg2_binary-2.9.4-cp310-cp310-macosx_10_15_x86_64.macosx_10_9_intel.macosx_10_9_x86_64.macosx_10_10_intel.macosx_10_10_x86_64.whl", hash = "sha256:e72491d72870c3cb2f0d6f4174485533caec0e9ed7e717e2859b7cc7ff2ae1c4"}, + {file = "psycopg2_binary-2.9.4-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:2903bf90b1e6bfc9bbfc94a1db0b50ffa9830a0ca4c042fbc38d93890c02ce08"}, + {file = "psycopg2_binary-2.9.4-cp310-cp310-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:15e0ac0ed8a85f6049e836e95ddee627766561c85be8d23f4b3edb6ddbaa7310"}, + {file = "psycopg2_binary-2.9.4-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:edf0a66ce9517365c7dcfed597894d8dd1f27b59e550b77a089054101435213b"}, + {file = "psycopg2_binary-2.9.4-cp310-cp310-manylinux_2_24_aarch64.whl", hash = "sha256:61c6a258469c66412ae8358a0501df6ccb3bb48aa9c43b56624571ff9767f91d"}, + {file = "psycopg2_binary-2.9.4-cp310-cp310-manylinux_2_24_ppc64le.whl", hash = "sha256:704f1fcdc5b606b70563ea696c69bda90caee3a2f45ffc9cee60a901b394a79f"}, + {file = "psycopg2_binary-2.9.4-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:30200b07779446760813eef06098ec6d084131e4365b4e023eb43100de758b11"}, + {file = "psycopg2_binary-2.9.4-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:f5fbb3b325c65010e04af206a9243e2df8606736c510c7f268aca6a93e5294a9"}, + {file = "psycopg2_binary-2.9.4-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:52383e932e6de5595963f9178cf2af7b9e1f3daacf5135b9c0e21aabbc5bf7c4"}, + {file = "psycopg2_binary-2.9.4-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:0d8e0c9eec79fe1ae66691e06e3cc714da6fbd77981209bf32fa823c03dbaff8"}, + {file = "psycopg2_binary-2.9.4-cp310-cp310-win32.whl", hash = "sha256:161dc52a617f0bb610a87d391cb2e77fe65b89ebfbd752f4f3217dde701ea196"}, + {file = "psycopg2_binary-2.9.4-cp310-cp310-win_amd64.whl", hash = "sha256:33ac8b4754e6b6b21f3ee180da169d8526d91aee9408ec1fc573c16ab32b0207"}, + {file = "psycopg2_binary-2.9.4-cp36-cp36m-macosx_10_14_x86_64.macosx_10_9_intel.macosx_10_9_x86_64.macosx_10_10_intel.macosx_10_10_x86_64.whl", hash = "sha256:7751b11cd7f6b952b4b5ec5b93b5be9ce20faba786c18c25c354f5d8717a173c"}, + {file = "psycopg2_binary-2.9.4-cp36-cp36m-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b216a15e13f6e763db40ac3beb74b588650bc030d10a78fde182b88d273b82b5"}, + {file = "psycopg2_binary-2.9.4-cp36-cp36m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0eae72190be519bf2629062eab7ac8d4ceec5bd132953cefa1596584d86964fe"}, + {file = "psycopg2_binary-2.9.4-cp36-cp36m-manylinux_2_24_aarch64.whl", hash = "sha256:fb639a0e65dce4a9cccbcbdd8ddd0c8c6ab10bca317b827a5c52ac3c3a4ad60a"}, + {file = "psycopg2_binary-2.9.4-cp36-cp36m-manylinux_2_24_ppc64le.whl", hash = "sha256:80ed219ce6cb21a5b53ead0edf5b56b6d23de4cb95389ac606f47670474f4816"}, + {file = "psycopg2_binary-2.9.4-cp36-cp36m-musllinux_1_1_aarch64.whl", hash = "sha256:f78cafa25731e0b5aa16fe20bea1abf643d4e853f6bfb8a64421b06b878e2b88"}, + {file = "psycopg2_binary-2.9.4-cp36-cp36m-musllinux_1_1_i686.whl", hash = "sha256:34fd249275faa782c3a2016e86ac2330636ac58d731a1580e7d686e3976b9536"}, + {file = "psycopg2_binary-2.9.4-cp36-cp36m-musllinux_1_1_ppc64le.whl", hash = "sha256:24d627ed69e754c48dd142a914124858c600b4108c92546eb0ba822e63c0c6e2"}, + {file = "psycopg2_binary-2.9.4-cp36-cp36m-musllinux_1_1_x86_64.whl", hash = "sha256:65d5f4e70a2d3fbaa1349236968792611088f3f2dccead36c1626e1d183cc327"}, + {file = "psycopg2_binary-2.9.4-cp36-cp36m-win32.whl", hash = "sha256:ae5b41dbf7731b838021923edfbe3b5ccdec84d92d5795f5229c0d08d32509d9"}, + {file = "psycopg2_binary-2.9.4-cp36-cp36m-win_amd64.whl", hash = "sha256:97e4f3d9b17d12e7c00cb1c29c0040044135cd5146838da4274615dbe0baae78"}, + {file = "psycopg2_binary-2.9.4-cp37-cp37m-macosx_10_15_x86_64.macosx_10_9_intel.macosx_10_9_x86_64.macosx_10_10_intel.macosx_10_10_x86_64.whl", hash = "sha256:8660112e9127a019969a23c878e1b4a419e8a6427f9a9050c19830f152628c8a"}, + {file = "psycopg2_binary-2.9.4-cp37-cp37m-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ea8d5cd689fa7225d81ae0a049ba03e0165f4ed9ca083b19a405be9ad0b36845"}, + {file = "psycopg2_binary-2.9.4-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:63edc507f8cbfbb5903adb75bad8a99f9798981c854df9119dbebab2ec3ee0e1"}, + {file = "psycopg2_binary-2.9.4-cp37-cp37m-manylinux_2_24_aarch64.whl", hash = "sha256:25e0517ad7ee3c5c3c69dbe3c1d95504c811e42f452b39a3505d0763b1f6caa0"}, + {file = "psycopg2_binary-2.9.4-cp37-cp37m-manylinux_2_24_ppc64le.whl", hash = "sha256:0a9465f0aa36480c8e7614991cbe8ca8aa16b0517c5398a49648ce345e446c19"}, + {file = "psycopg2_binary-2.9.4-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:aff258af03dda9a990960a53759d10c3a9b936837c71fe2f3b581acd356b9121"}, + {file = "psycopg2_binary-2.9.4-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:576b9dfbcd154a0e8b5d9dae6316d037450e64a3b31df87dec71d88e2a2d5e5f"}, + {file = "psycopg2_binary-2.9.4-cp37-cp37m-musllinux_1_1_ppc64le.whl", hash = "sha256:044b6ab68613de7ea1e63856627deea091bfea09dea5ab4f050b13250fd18cab"}, + {file = "psycopg2_binary-2.9.4-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:7b47643c45e7619788c081d42e1d9d98c7c8a4933010a9967d097cc3c4c29f41"}, + {file = "psycopg2_binary-2.9.4-cp37-cp37m-win32.whl", hash = "sha256:82df4a8600999c4c0cb7d6614df1bbdb3c74732f63e79f78487893ffbed3d083"}, + {file = "psycopg2_binary-2.9.4-cp37-cp37m-win_amd64.whl", hash = "sha256:8d7bc25729bb6d96b44f49ad78fde0e27a1a867cb205322b7e5f5b49e04d6f1f"}, + {file = "psycopg2_binary-2.9.4-cp38-cp38-macosx_10_15_x86_64.macosx_10_9_intel.macosx_10_9_x86_64.macosx_10_10_intel.macosx_10_10_x86_64.whl", hash = "sha256:2f1ded23d17af0d738e7e78087f0b88a53228887845b1989b03af4dfd3fef703"}, + {file = "psycopg2_binary-2.9.4-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:f225784812b2b57d340f2eb0d2cebef989dcc82c288f5553e28ee9767c7c8344"}, + {file = "psycopg2_binary-2.9.4-cp38-cp38-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:89a86c2b35460700d04b4d6461153ab39ee85af5a5385acac9563a8310e6320a"}, + {file = "psycopg2_binary-2.9.4-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:59a3010d566a48b919490a982f6807f68842686941dc12d568e129d9cd7703d6"}, + {file = "psycopg2_binary-2.9.4-cp38-cp38-manylinux_2_24_aarch64.whl", hash = "sha256:02cde837df012fa5d579b9cf4bc8e1feb460f38d61f7a4ab4a919d55a9f6eeef"}, + {file = "psycopg2_binary-2.9.4-cp38-cp38-manylinux_2_24_ppc64le.whl", hash = "sha256:226f11be577b70a57f4910c0ee28591d4d9fcb3d455e966267179156ae2e0c41"}, + {file = "psycopg2_binary-2.9.4-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:181ac372a5a5308b4076933601a9b5f0cd139b389b0aa5e164786a2abbcdb978"}, + {file = "psycopg2_binary-2.9.4-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:d5f27b1d1b56470385faa2b2636fcb823e7ac5b5b734e0aa76b14637c66eb3b7"}, + {file = "psycopg2_binary-2.9.4-cp38-cp38-musllinux_1_1_ppc64le.whl", hash = "sha256:55137faec669c4277c5687c6ce7c1fbc4dece0e2f14256ee808f4a652f0a2170"}, + {file = "psycopg2_binary-2.9.4-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:ffb2f288f577a748cc23c65a818290755a4c2da1f87a40d7055b61a096d31e20"}, + {file = "psycopg2_binary-2.9.4-cp38-cp38-win32.whl", hash = "sha256:451550e0bb5889bbabbf92575a6d6eafced941cc28c86be6ae4667f81bf32d67"}, + {file = "psycopg2_binary-2.9.4-cp38-cp38-win_amd64.whl", hash = "sha256:b23b25b1243576b952689966205ef7d4285688068b966a1ca0e620bcb390d483"}, + {file = "psycopg2_binary-2.9.4-cp39-cp39-macosx_10_15_x86_64.macosx_10_9_intel.macosx_10_9_x86_64.macosx_10_10_intel.macosx_10_10_x86_64.whl", hash = "sha256:7ad9d032dc1a31a86ca7b059f43554a049a2bfda8fe32d1492ad25f6686aff03"}, + {file = "psycopg2_binary-2.9.4-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:7b01d07006a0ac2216921b69a220b9f0974345d0b1b36efaeabdc7550b1cc4f8"}, + {file = "psycopg2_binary-2.9.4-cp39-cp39-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:eb5341fc7c53fdd95ac2415be77b1de854ab266488cff71174ebb007baf0e675"}, + {file = "psycopg2_binary-2.9.4-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a431deb6ffdfa551f7400b3a94fa4b964837e67f49e3c37aa26d90dc75970816"}, + {file = "psycopg2_binary-2.9.4-cp39-cp39-manylinux_2_24_aarch64.whl", hash = "sha256:d6ba33f39436191ece7ea2b3d0b4dff00af71acd5c6e6f1d6b7563aa7286e9f2"}, + {file = "psycopg2_binary-2.9.4-cp39-cp39-manylinux_2_24_ppc64le.whl", hash = "sha256:d6c5e1df6f427d7a82606cf8f07cf3ba9fb3f366804b01e65f1f00f8df6b54f1"}, + {file = "psycopg2_binary-2.9.4-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:1c22c59ab7d9dc110d409445f111f58556bf699b0548f3fc5176684a29c629c4"}, + {file = "psycopg2_binary-2.9.4-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:b896637091cde69d170a89253dde9aee814b25ca204b7e213fd0a6462e666638"}, + {file = "psycopg2_binary-2.9.4-cp39-cp39-musllinux_1_1_ppc64le.whl", hash = "sha256:2535f44b00f26f6af0e949c825e6aecb9adcb56c965c17af5b97137fb69f00c0"}, + {file = "psycopg2_binary-2.9.4-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:6a1618260a112a9c93504511f0b6254b4402a8c41b7130dc6d4c9e39aff3aa0c"}, + {file = "psycopg2_binary-2.9.4-cp39-cp39-win32.whl", hash = "sha256:e02f77b620ad6b36564fe41980865436912e21a3b1138cdde175cf24afde1bc5"}, + {file = "psycopg2_binary-2.9.4-cp39-cp39-win_amd64.whl", hash = "sha256:44f5dc9b4384bafca8429759ce76c8960ffc2b583fcad9e5dfb3e5f4894269e4"}, ] [[package]] @@ -2503,4 +2495,4 @@ type = ["pytest-mypy"] [metadata] lock-version = "2.0" python-versions = "^3.10" -content-hash = "c8e3d81234f4ddceffabc50a95d54b4f29700a25ea70a86baa7ee81ae91569ca" +content-hash = "5d064178693ed5c0550e063bf8ce9bab23ccef0ccf23143e18f05f388528a5ce" diff --git a/pyproject.toml b/pyproject.toml index 1b71f31d3f..ee688dbe3a 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -71,7 +71,7 @@ boto3 = "*" tenacity = "*" landscape-api-py3 = "^0.9.0" mailmanclient = "^3.3.5" -psycopg2-binary = "^2.9.10" +psycopg2-binary = "2.9.4" allure-pytest = "^2.13.5" allure-pytest-collection-report = {git = "https://github.com/canonical/data-platform-workflows", tag = "v23.0.5", subdirectory = "python/pytest_plugins/allure_pytest_collection_report"} From 16fc2b6c79ac579c11ebdd5931a8c0f6c99bcfa0 Mon Sep 17 00:00:00 2001 From: Lucas Gameiro Borges Date: Tue, 19 Nov 2024 21:28:42 +0000 Subject: [PATCH 11/74] revery psycopg2 versions and add ssl params --- poetry.lock | 156 ++++++++++++++++++++------------------- pyproject.toml | 4 +- templates/patroni.yml.j2 | 15 ++++ 3 files changed, 98 insertions(+), 77 deletions(-) diff --git a/poetry.lock b/poetry.lock index adc555b028..4b25296de0 100644 --- a/poetry.lock +++ b/poetry.lock @@ -1449,90 +1449,96 @@ files = [ [[package]] name = "psycopg2" -version = "2.9.4" +version = "2.9.10" description = "psycopg2 - Python-PostgreSQL Database Adapter" optional = false -python-versions = ">=3.6" +python-versions = ">=3.8" files = [ - {file = "psycopg2-2.9.4-cp310-cp310-win32.whl", hash = "sha256:8de6a9fc5f42fa52f559e65120dcd7502394692490c98fed1221acf0819d7797"}, - {file = "psycopg2-2.9.4-cp310-cp310-win_amd64.whl", hash = "sha256:1da77c061bdaab450581458932ae5e469cc6e36e0d62f988376e9f513f11cb5c"}, - {file = "psycopg2-2.9.4-cp36-cp36m-win32.whl", hash = "sha256:a11946bad3557ca254f17357d5a4ed63bdca45163e7a7d2bfb8e695df069cc3a"}, - {file = "psycopg2-2.9.4-cp36-cp36m-win_amd64.whl", hash = "sha256:46361c054df612c3cc813fdb343733d56543fb93565cff0f8ace422e4da06acb"}, - {file = "psycopg2-2.9.4-cp37-cp37m-win32.whl", hash = "sha256:aafa96f2da0071d6dd0cbb7633406d99f414b40ab0f918c9d9af7df928a1accb"}, - {file = "psycopg2-2.9.4-cp37-cp37m-win_amd64.whl", hash = "sha256:aa184d551a767ad25df3b8d22a0a62ef2962e0e374c04f6cbd1204947f540d61"}, - {file = "psycopg2-2.9.4-cp38-cp38-win32.whl", hash = "sha256:839f9ea8f6098e39966d97fcb8d08548fbc57c523a1e27a1f0609addf40f777c"}, - {file = "psycopg2-2.9.4-cp38-cp38-win_amd64.whl", hash = "sha256:c7fa041b4acb913f6968fce10169105af5200f296028251d817ab37847c30184"}, - {file = "psycopg2-2.9.4-cp39-cp39-win32.whl", hash = "sha256:07b90a24d5056687781ddaef0ea172fd951f2f7293f6ffdd03d4f5077801f426"}, - {file = "psycopg2-2.9.4-cp39-cp39-win_amd64.whl", hash = "sha256:849bd868ae3369932127f0771c08d1109b254f08d48dc42493c3d1b87cb2d308"}, - {file = "psycopg2-2.9.4.tar.gz", hash = "sha256:d529926254e093a1b669f692a3aa50069bc71faf5b0ecd91686a78f62767d52f"}, + {file = "psycopg2-2.9.10-cp310-cp310-win32.whl", hash = "sha256:5df2b672140f95adb453af93a7d669d7a7bf0a56bcd26f1502329166f4a61716"}, + {file = "psycopg2-2.9.10-cp310-cp310-win_amd64.whl", hash = "sha256:c6f7b8561225f9e711a9c47087388a97fdc948211c10a4bccbf0ba68ab7b3b5a"}, + {file = "psycopg2-2.9.10-cp311-cp311-win32.whl", hash = "sha256:47c4f9875125344f4c2b870e41b6aad585901318068acd01de93f3677a6522c2"}, + {file = "psycopg2-2.9.10-cp311-cp311-win_amd64.whl", hash = "sha256:0435034157049f6846e95103bd8f5a668788dd913a7c30162ca9503fdf542cb4"}, + {file = "psycopg2-2.9.10-cp312-cp312-win32.whl", hash = "sha256:65a63d7ab0e067e2cdb3cf266de39663203d38d6a8ed97f5ca0cb315c73fe067"}, + {file = "psycopg2-2.9.10-cp312-cp312-win_amd64.whl", hash = "sha256:4a579d6243da40a7b3182e0430493dbd55950c493d8c68f4eec0b302f6bbf20e"}, + {file = "psycopg2-2.9.10-cp39-cp39-win32.whl", hash = "sha256:9d5b3b94b79a844a986d029eee38998232451119ad653aea42bb9220a8c5066b"}, + {file = "psycopg2-2.9.10-cp39-cp39-win_amd64.whl", hash = "sha256:88138c8dedcbfa96408023ea2b0c369eda40fe5d75002c0964c78f46f11fa442"}, + {file = "psycopg2-2.9.10.tar.gz", hash = "sha256:12ec0b40b0273f95296233e8750441339298e6a572f7039da5b260e3c8b60e11"}, ] [[package]] name = "psycopg2-binary" -version = "2.9.4" +version = "2.9.10" description = "psycopg2 - Python-PostgreSQL Database Adapter" optional = false -python-versions = ">=3.6" +python-versions = ">=3.8" files = [ - {file = "psycopg2-binary-2.9.4.tar.gz", hash = "sha256:a6a2d3d75d8698dee492f4af7ad07606d0734e581edf9e2ce2f74b6fce90f42e"}, - {file = "psycopg2_binary-2.9.4-cp310-cp310-macosx_10_15_x86_64.macosx_10_9_intel.macosx_10_9_x86_64.macosx_10_10_intel.macosx_10_10_x86_64.whl", hash = "sha256:e72491d72870c3cb2f0d6f4174485533caec0e9ed7e717e2859b7cc7ff2ae1c4"}, - {file = "psycopg2_binary-2.9.4-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:2903bf90b1e6bfc9bbfc94a1db0b50ffa9830a0ca4c042fbc38d93890c02ce08"}, - {file = "psycopg2_binary-2.9.4-cp310-cp310-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:15e0ac0ed8a85f6049e836e95ddee627766561c85be8d23f4b3edb6ddbaa7310"}, - {file = "psycopg2_binary-2.9.4-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:edf0a66ce9517365c7dcfed597894d8dd1f27b59e550b77a089054101435213b"}, - {file = "psycopg2_binary-2.9.4-cp310-cp310-manylinux_2_24_aarch64.whl", hash = "sha256:61c6a258469c66412ae8358a0501df6ccb3bb48aa9c43b56624571ff9767f91d"}, - {file = "psycopg2_binary-2.9.4-cp310-cp310-manylinux_2_24_ppc64le.whl", hash = "sha256:704f1fcdc5b606b70563ea696c69bda90caee3a2f45ffc9cee60a901b394a79f"}, - {file = "psycopg2_binary-2.9.4-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:30200b07779446760813eef06098ec6d084131e4365b4e023eb43100de758b11"}, - {file = "psycopg2_binary-2.9.4-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:f5fbb3b325c65010e04af206a9243e2df8606736c510c7f268aca6a93e5294a9"}, - {file = "psycopg2_binary-2.9.4-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:52383e932e6de5595963f9178cf2af7b9e1f3daacf5135b9c0e21aabbc5bf7c4"}, - {file = "psycopg2_binary-2.9.4-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:0d8e0c9eec79fe1ae66691e06e3cc714da6fbd77981209bf32fa823c03dbaff8"}, - {file = "psycopg2_binary-2.9.4-cp310-cp310-win32.whl", hash = "sha256:161dc52a617f0bb610a87d391cb2e77fe65b89ebfbd752f4f3217dde701ea196"}, - {file = "psycopg2_binary-2.9.4-cp310-cp310-win_amd64.whl", hash = "sha256:33ac8b4754e6b6b21f3ee180da169d8526d91aee9408ec1fc573c16ab32b0207"}, - {file = "psycopg2_binary-2.9.4-cp36-cp36m-macosx_10_14_x86_64.macosx_10_9_intel.macosx_10_9_x86_64.macosx_10_10_intel.macosx_10_10_x86_64.whl", hash = "sha256:7751b11cd7f6b952b4b5ec5b93b5be9ce20faba786c18c25c354f5d8717a173c"}, - {file = "psycopg2_binary-2.9.4-cp36-cp36m-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b216a15e13f6e763db40ac3beb74b588650bc030d10a78fde182b88d273b82b5"}, - {file = "psycopg2_binary-2.9.4-cp36-cp36m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0eae72190be519bf2629062eab7ac8d4ceec5bd132953cefa1596584d86964fe"}, - {file = "psycopg2_binary-2.9.4-cp36-cp36m-manylinux_2_24_aarch64.whl", hash = "sha256:fb639a0e65dce4a9cccbcbdd8ddd0c8c6ab10bca317b827a5c52ac3c3a4ad60a"}, - {file = "psycopg2_binary-2.9.4-cp36-cp36m-manylinux_2_24_ppc64le.whl", hash = "sha256:80ed219ce6cb21a5b53ead0edf5b56b6d23de4cb95389ac606f47670474f4816"}, - {file = "psycopg2_binary-2.9.4-cp36-cp36m-musllinux_1_1_aarch64.whl", hash = "sha256:f78cafa25731e0b5aa16fe20bea1abf643d4e853f6bfb8a64421b06b878e2b88"}, - {file = "psycopg2_binary-2.9.4-cp36-cp36m-musllinux_1_1_i686.whl", hash = "sha256:34fd249275faa782c3a2016e86ac2330636ac58d731a1580e7d686e3976b9536"}, - {file = "psycopg2_binary-2.9.4-cp36-cp36m-musllinux_1_1_ppc64le.whl", hash = "sha256:24d627ed69e754c48dd142a914124858c600b4108c92546eb0ba822e63c0c6e2"}, - {file = "psycopg2_binary-2.9.4-cp36-cp36m-musllinux_1_1_x86_64.whl", hash = "sha256:65d5f4e70a2d3fbaa1349236968792611088f3f2dccead36c1626e1d183cc327"}, - {file = "psycopg2_binary-2.9.4-cp36-cp36m-win32.whl", hash = "sha256:ae5b41dbf7731b838021923edfbe3b5ccdec84d92d5795f5229c0d08d32509d9"}, - {file = "psycopg2_binary-2.9.4-cp36-cp36m-win_amd64.whl", hash = "sha256:97e4f3d9b17d12e7c00cb1c29c0040044135cd5146838da4274615dbe0baae78"}, - {file = "psycopg2_binary-2.9.4-cp37-cp37m-macosx_10_15_x86_64.macosx_10_9_intel.macosx_10_9_x86_64.macosx_10_10_intel.macosx_10_10_x86_64.whl", hash = "sha256:8660112e9127a019969a23c878e1b4a419e8a6427f9a9050c19830f152628c8a"}, - {file = "psycopg2_binary-2.9.4-cp37-cp37m-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ea8d5cd689fa7225d81ae0a049ba03e0165f4ed9ca083b19a405be9ad0b36845"}, - {file = "psycopg2_binary-2.9.4-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:63edc507f8cbfbb5903adb75bad8a99f9798981c854df9119dbebab2ec3ee0e1"}, - {file = "psycopg2_binary-2.9.4-cp37-cp37m-manylinux_2_24_aarch64.whl", hash = "sha256:25e0517ad7ee3c5c3c69dbe3c1d95504c811e42f452b39a3505d0763b1f6caa0"}, - {file = "psycopg2_binary-2.9.4-cp37-cp37m-manylinux_2_24_ppc64le.whl", hash = "sha256:0a9465f0aa36480c8e7614991cbe8ca8aa16b0517c5398a49648ce345e446c19"}, - {file = "psycopg2_binary-2.9.4-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:aff258af03dda9a990960a53759d10c3a9b936837c71fe2f3b581acd356b9121"}, - {file = "psycopg2_binary-2.9.4-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:576b9dfbcd154a0e8b5d9dae6316d037450e64a3b31df87dec71d88e2a2d5e5f"}, - {file = "psycopg2_binary-2.9.4-cp37-cp37m-musllinux_1_1_ppc64le.whl", hash = "sha256:044b6ab68613de7ea1e63856627deea091bfea09dea5ab4f050b13250fd18cab"}, - {file = "psycopg2_binary-2.9.4-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:7b47643c45e7619788c081d42e1d9d98c7c8a4933010a9967d097cc3c4c29f41"}, - {file = "psycopg2_binary-2.9.4-cp37-cp37m-win32.whl", hash = "sha256:82df4a8600999c4c0cb7d6614df1bbdb3c74732f63e79f78487893ffbed3d083"}, - {file = "psycopg2_binary-2.9.4-cp37-cp37m-win_amd64.whl", hash = "sha256:8d7bc25729bb6d96b44f49ad78fde0e27a1a867cb205322b7e5f5b49e04d6f1f"}, - {file = "psycopg2_binary-2.9.4-cp38-cp38-macosx_10_15_x86_64.macosx_10_9_intel.macosx_10_9_x86_64.macosx_10_10_intel.macosx_10_10_x86_64.whl", hash = "sha256:2f1ded23d17af0d738e7e78087f0b88a53228887845b1989b03af4dfd3fef703"}, - {file = "psycopg2_binary-2.9.4-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:f225784812b2b57d340f2eb0d2cebef989dcc82c288f5553e28ee9767c7c8344"}, - {file = "psycopg2_binary-2.9.4-cp38-cp38-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:89a86c2b35460700d04b4d6461153ab39ee85af5a5385acac9563a8310e6320a"}, - {file = "psycopg2_binary-2.9.4-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:59a3010d566a48b919490a982f6807f68842686941dc12d568e129d9cd7703d6"}, - {file = "psycopg2_binary-2.9.4-cp38-cp38-manylinux_2_24_aarch64.whl", hash = "sha256:02cde837df012fa5d579b9cf4bc8e1feb460f38d61f7a4ab4a919d55a9f6eeef"}, - {file = "psycopg2_binary-2.9.4-cp38-cp38-manylinux_2_24_ppc64le.whl", hash = "sha256:226f11be577b70a57f4910c0ee28591d4d9fcb3d455e966267179156ae2e0c41"}, - {file = "psycopg2_binary-2.9.4-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:181ac372a5a5308b4076933601a9b5f0cd139b389b0aa5e164786a2abbcdb978"}, - {file = "psycopg2_binary-2.9.4-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:d5f27b1d1b56470385faa2b2636fcb823e7ac5b5b734e0aa76b14637c66eb3b7"}, - {file = "psycopg2_binary-2.9.4-cp38-cp38-musllinux_1_1_ppc64le.whl", hash = "sha256:55137faec669c4277c5687c6ce7c1fbc4dece0e2f14256ee808f4a652f0a2170"}, - {file = "psycopg2_binary-2.9.4-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:ffb2f288f577a748cc23c65a818290755a4c2da1f87a40d7055b61a096d31e20"}, - {file = "psycopg2_binary-2.9.4-cp38-cp38-win32.whl", hash = "sha256:451550e0bb5889bbabbf92575a6d6eafced941cc28c86be6ae4667f81bf32d67"}, - {file = "psycopg2_binary-2.9.4-cp38-cp38-win_amd64.whl", hash = "sha256:b23b25b1243576b952689966205ef7d4285688068b966a1ca0e620bcb390d483"}, - {file = "psycopg2_binary-2.9.4-cp39-cp39-macosx_10_15_x86_64.macosx_10_9_intel.macosx_10_9_x86_64.macosx_10_10_intel.macosx_10_10_x86_64.whl", hash = "sha256:7ad9d032dc1a31a86ca7b059f43554a049a2bfda8fe32d1492ad25f6686aff03"}, - {file = "psycopg2_binary-2.9.4-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:7b01d07006a0ac2216921b69a220b9f0974345d0b1b36efaeabdc7550b1cc4f8"}, - {file = "psycopg2_binary-2.9.4-cp39-cp39-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:eb5341fc7c53fdd95ac2415be77b1de854ab266488cff71174ebb007baf0e675"}, - {file = "psycopg2_binary-2.9.4-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a431deb6ffdfa551f7400b3a94fa4b964837e67f49e3c37aa26d90dc75970816"}, - {file = "psycopg2_binary-2.9.4-cp39-cp39-manylinux_2_24_aarch64.whl", hash = "sha256:d6ba33f39436191ece7ea2b3d0b4dff00af71acd5c6e6f1d6b7563aa7286e9f2"}, - {file = "psycopg2_binary-2.9.4-cp39-cp39-manylinux_2_24_ppc64le.whl", hash = "sha256:d6c5e1df6f427d7a82606cf8f07cf3ba9fb3f366804b01e65f1f00f8df6b54f1"}, - {file = "psycopg2_binary-2.9.4-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:1c22c59ab7d9dc110d409445f111f58556bf699b0548f3fc5176684a29c629c4"}, - {file = "psycopg2_binary-2.9.4-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:b896637091cde69d170a89253dde9aee814b25ca204b7e213fd0a6462e666638"}, - {file = "psycopg2_binary-2.9.4-cp39-cp39-musllinux_1_1_ppc64le.whl", hash = "sha256:2535f44b00f26f6af0e949c825e6aecb9adcb56c965c17af5b97137fb69f00c0"}, - {file = "psycopg2_binary-2.9.4-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:6a1618260a112a9c93504511f0b6254b4402a8c41b7130dc6d4c9e39aff3aa0c"}, - {file = "psycopg2_binary-2.9.4-cp39-cp39-win32.whl", hash = "sha256:e02f77b620ad6b36564fe41980865436912e21a3b1138cdde175cf24afde1bc5"}, - {file = "psycopg2_binary-2.9.4-cp39-cp39-win_amd64.whl", hash = "sha256:44f5dc9b4384bafca8429759ce76c8960ffc2b583fcad9e5dfb3e5f4894269e4"}, + {file = "psycopg2-binary-2.9.10.tar.gz", hash = "sha256:4b3df0e6990aa98acda57d983942eff13d824135fe2250e6522edaa782a06de2"}, + {file = "psycopg2_binary-2.9.10-cp310-cp310-macosx_12_0_x86_64.whl", hash = "sha256:0ea8e3d0ae83564f2fc554955d327fa081d065c8ca5cc6d2abb643e2c9c1200f"}, + {file = "psycopg2_binary-2.9.10-cp310-cp310-macosx_14_0_arm64.whl", hash = "sha256:3e9c76f0ac6f92ecfc79516a8034a544926430f7b080ec5a0537bca389ee0906"}, + {file = "psycopg2_binary-2.9.10-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2ad26b467a405c798aaa1458ba09d7e2b6e5f96b1ce0ac15d82fd9f95dc38a92"}, + {file = "psycopg2_binary-2.9.10-cp310-cp310-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:270934a475a0e4b6925b5f804e3809dd5f90f8613621d062848dd82f9cd62007"}, + {file = "psycopg2_binary-2.9.10-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:48b338f08d93e7be4ab2b5f1dbe69dc5e9ef07170fe1f86514422076d9c010d0"}, + {file = "psycopg2_binary-2.9.10-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7f4152f8f76d2023aac16285576a9ecd2b11a9895373a1f10fd9db54b3ff06b4"}, + {file = "psycopg2_binary-2.9.10-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:32581b3020c72d7a421009ee1c6bf4a131ef5f0a968fab2e2de0c9d2bb4577f1"}, + {file = "psycopg2_binary-2.9.10-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:2ce3e21dc3437b1d960521eca599d57408a695a0d3c26797ea0f72e834c7ffe5"}, + {file = "psycopg2_binary-2.9.10-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:e984839e75e0b60cfe75e351db53d6db750b00de45644c5d1f7ee5d1f34a1ce5"}, + {file = "psycopg2_binary-2.9.10-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:3c4745a90b78e51d9ba06e2088a2fe0c693ae19cc8cb051ccda44e8df8a6eb53"}, + {file = "psycopg2_binary-2.9.10-cp310-cp310-win32.whl", hash = "sha256:e5720a5d25e3b99cd0dc5c8a440570469ff82659bb09431c1439b92caf184d3b"}, + {file = "psycopg2_binary-2.9.10-cp310-cp310-win_amd64.whl", hash = "sha256:3c18f74eb4386bf35e92ab2354a12c17e5eb4d9798e4c0ad3a00783eae7cd9f1"}, + {file = "psycopg2_binary-2.9.10-cp311-cp311-macosx_12_0_x86_64.whl", hash = "sha256:04392983d0bb89a8717772a193cfaac58871321e3ec69514e1c4e0d4957b5aff"}, + {file = "psycopg2_binary-2.9.10-cp311-cp311-macosx_14_0_arm64.whl", hash = "sha256:1a6784f0ce3fec4edc64e985865c17778514325074adf5ad8f80636cd029ef7c"}, + {file = "psycopg2_binary-2.9.10-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b5f86c56eeb91dc3135b3fd8a95dc7ae14c538a2f3ad77a19645cf55bab1799c"}, + {file = "psycopg2_binary-2.9.10-cp311-cp311-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:2b3d2491d4d78b6b14f76881905c7a8a8abcf974aad4a8a0b065273a0ed7a2cb"}, + {file = "psycopg2_binary-2.9.10-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:2286791ececda3a723d1910441c793be44625d86d1a4e79942751197f4d30341"}, + {file = "psycopg2_binary-2.9.10-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:512d29bb12608891e349af6a0cccedce51677725a921c07dba6342beaf576f9a"}, + {file = "psycopg2_binary-2.9.10-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:5a507320c58903967ef7384355a4da7ff3f28132d679aeb23572753cbf2ec10b"}, + {file = "psycopg2_binary-2.9.10-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:6d4fa1079cab9018f4d0bd2db307beaa612b0d13ba73b5c6304b9fe2fb441ff7"}, + {file = "psycopg2_binary-2.9.10-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:851485a42dbb0bdc1edcdabdb8557c09c9655dfa2ca0460ff210522e073e319e"}, + {file = "psycopg2_binary-2.9.10-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:35958ec9e46432d9076286dda67942ed6d968b9c3a6a2fd62b48939d1d78bf68"}, + {file = "psycopg2_binary-2.9.10-cp311-cp311-win32.whl", hash = "sha256:ecced182e935529727401b24d76634a357c71c9275b356efafd8a2a91ec07392"}, + {file = "psycopg2_binary-2.9.10-cp311-cp311-win_amd64.whl", hash = "sha256:ee0e8c683a7ff25d23b55b11161c2663d4b099770f6085ff0a20d4505778d6b4"}, + {file = "psycopg2_binary-2.9.10-cp312-cp312-macosx_12_0_x86_64.whl", hash = "sha256:880845dfe1f85d9d5f7c412efea7a08946a46894537e4e5d091732eb1d34d9a0"}, + {file = "psycopg2_binary-2.9.10-cp312-cp312-macosx_14_0_arm64.whl", hash = "sha256:9440fa522a79356aaa482aa4ba500b65f28e5d0e63b801abf6aa152a29bd842a"}, + {file = "psycopg2_binary-2.9.10-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e3923c1d9870c49a2d44f795df0c889a22380d36ef92440ff618ec315757e539"}, + {file = "psycopg2_binary-2.9.10-cp312-cp312-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7b2c956c028ea5de47ff3a8d6b3cc3330ab45cf0b7c3da35a2d6ff8420896526"}, + {file = "psycopg2_binary-2.9.10-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f758ed67cab30b9a8d2833609513ce4d3bd027641673d4ebc9c067e4d208eec1"}, + {file = "psycopg2_binary-2.9.10-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8cd9b4f2cfab88ed4a9106192de509464b75a906462fb846b936eabe45c2063e"}, + {file = "psycopg2_binary-2.9.10-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:6dc08420625b5a20b53551c50deae6e231e6371194fa0651dbe0fb206452ae1f"}, + {file = "psycopg2_binary-2.9.10-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:d7cd730dfa7c36dbe8724426bf5612798734bff2d3c3857f36f2733f5bfc7c00"}, + {file = "psycopg2_binary-2.9.10-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:155e69561d54d02b3c3209545fb08938e27889ff5a10c19de8d23eb5a41be8a5"}, + {file = "psycopg2_binary-2.9.10-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:c3cc28a6fd5a4a26224007712e79b81dbaee2ffb90ff406256158ec4d7b52b47"}, + {file = "psycopg2_binary-2.9.10-cp312-cp312-win32.whl", hash = "sha256:ec8a77f521a17506a24a5f626cb2aee7850f9b69a0afe704586f63a464f3cd64"}, + {file = "psycopg2_binary-2.9.10-cp312-cp312-win_amd64.whl", hash = "sha256:18c5ee682b9c6dd3696dad6e54cc7ff3a1a9020df6a5c0f861ef8bfd338c3ca0"}, + {file = "psycopg2_binary-2.9.10-cp313-cp313-macosx_12_0_x86_64.whl", hash = "sha256:26540d4a9a4e2b096f1ff9cce51253d0504dca5a85872c7f7be23be5a53eb18d"}, + {file = "psycopg2_binary-2.9.10-cp313-cp313-macosx_14_0_arm64.whl", hash = "sha256:e217ce4d37667df0bc1c397fdcd8de5e81018ef305aed9415c3b093faaeb10fb"}, + {file = "psycopg2_binary-2.9.10-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:245159e7ab20a71d989da00f280ca57da7641fa2cdcf71749c193cea540a74f7"}, + {file = "psycopg2_binary-2.9.10-cp313-cp313-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3c4ded1a24b20021ebe677b7b08ad10bf09aac197d6943bfe6fec70ac4e4690d"}, + {file = "psycopg2_binary-2.9.10-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3abb691ff9e57d4a93355f60d4f4c1dd2d68326c968e7db17ea96df3c023ef73"}, + {file = "psycopg2_binary-2.9.10-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8608c078134f0b3cbd9f89b34bd60a943b23fd33cc5f065e8d5f840061bd0673"}, + {file = "psycopg2_binary-2.9.10-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:230eeae2d71594103cd5b93fd29d1ace6420d0b86f4778739cb1a5a32f607d1f"}, + {file = "psycopg2_binary-2.9.10-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:bb89f0a835bcfc1d42ccd5f41f04870c1b936d8507c6df12b7737febc40f0909"}, + {file = "psycopg2_binary-2.9.10-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:f0c2d907a1e102526dd2986df638343388b94c33860ff3bbe1384130828714b1"}, + {file = "psycopg2_binary-2.9.10-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:f8157bed2f51db683f31306aa497311b560f2265998122abe1dce6428bd86567"}, + {file = "psycopg2_binary-2.9.10-cp38-cp38-macosx_12_0_x86_64.whl", hash = "sha256:eb09aa7f9cecb45027683bb55aebaaf45a0df8bf6de68801a6afdc7947bb09d4"}, + {file = "psycopg2_binary-2.9.10-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b73d6d7f0ccdad7bc43e6d34273f70d587ef62f824d7261c4ae9b8b1b6af90e8"}, + {file = "psycopg2_binary-2.9.10-cp38-cp38-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ce5ab4bf46a211a8e924d307c1b1fcda82368586a19d0a24f8ae166f5c784864"}, + {file = "psycopg2_binary-2.9.10-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:056470c3dc57904bbf63d6f534988bafc4e970ffd50f6271fc4ee7daad9498a5"}, + {file = "psycopg2_binary-2.9.10-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:73aa0e31fa4bb82578f3a6c74a73c273367727de397a7a0f07bd83cbea696baa"}, + {file = "psycopg2_binary-2.9.10-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:8de718c0e1c4b982a54b41779667242bc630b2197948405b7bd8ce16bcecac92"}, + {file = "psycopg2_binary-2.9.10-cp38-cp38-musllinux_1_2_i686.whl", hash = "sha256:5c370b1e4975df846b0277b4deba86419ca77dbc25047f535b0bb03d1a544d44"}, + {file = "psycopg2_binary-2.9.10-cp38-cp38-musllinux_1_2_ppc64le.whl", hash = "sha256:ffe8ed017e4ed70f68b7b371d84b7d4a790368db9203dfc2d222febd3a9c8863"}, + {file = "psycopg2_binary-2.9.10-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:8aecc5e80c63f7459a1a2ab2c64df952051df196294d9f739933a9f6687e86b3"}, + {file = "psycopg2_binary-2.9.10-cp39-cp39-macosx_12_0_x86_64.whl", hash = "sha256:7a813c8bdbaaaab1f078014b9b0b13f5de757e2b5d9be6403639b298a04d218b"}, + {file = "psycopg2_binary-2.9.10-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d00924255d7fc916ef66e4bf22f354a940c67179ad3fd7067d7a0a9c84d2fbfc"}, + {file = "psycopg2_binary-2.9.10-cp39-cp39-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7559bce4b505762d737172556a4e6ea8a9998ecac1e39b5233465093e8cee697"}, + {file = "psycopg2_binary-2.9.10-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e8b58f0a96e7a1e341fc894f62c1177a7c83febebb5ff9123b579418fdc8a481"}, + {file = "psycopg2_binary-2.9.10-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6b269105e59ac96aba877c1707c600ae55711d9dcd3fc4b5012e4af68e30c648"}, + {file = "psycopg2_binary-2.9.10-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:79625966e176dc97ddabc142351e0409e28acf4660b88d1cf6adb876d20c490d"}, + {file = "psycopg2_binary-2.9.10-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:8aabf1c1a04584c168984ac678a668094d831f152859d06e055288fa515e4d30"}, + {file = "psycopg2_binary-2.9.10-cp39-cp39-musllinux_1_2_ppc64le.whl", hash = "sha256:19721ac03892001ee8fdd11507e6a2e01f4e37014def96379411ca99d78aeb2c"}, + {file = "psycopg2_binary-2.9.10-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:7f5d859928e635fa3ce3477704acee0f667b3a3d3e4bb109f2b18d4005f38287"}, + {file = "psycopg2_binary-2.9.10-cp39-cp39-win32.whl", hash = "sha256:3216ccf953b3f267691c90c6fe742e45d890d8272326b4a8b20850a03d05b7b8"}, + {file = "psycopg2_binary-2.9.10-cp39-cp39-win_amd64.whl", hash = "sha256:30e34c4e97964805f715206c7b789d54a78b70f3ff19fbe590104b71c45600e5"}, ] [[package]] @@ -2495,4 +2501,4 @@ type = ["pytest-mypy"] [metadata] lock-version = "2.0" python-versions = "^3.10" -content-hash = "5d064178693ed5c0550e063bf8ce9bab23ccef0ccf23143e18f05f388528a5ce" +content-hash = "a24006bb8af98b161cd722b73b93b3ce7fbc5f44e46ee2d4faa24e438c09e0de" diff --git a/pyproject.toml b/pyproject.toml index ee688dbe3a..ea24e76a47 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -12,7 +12,7 @@ boto3 = "^1.35.49" pgconnstr = "^1.0.1" requests = "^2.32.3" tenacity = "^9.0.0" -psycopg2 = "2.9.4" +psycopg2 = "^2.9.10" cosl = "^0.0.42" pydantic = "^1.10.18" poetry-core = "^1.9.1" @@ -71,7 +71,7 @@ boto3 = "*" tenacity = "*" landscape-api-py3 = "^0.9.0" mailmanclient = "^3.3.5" -psycopg2-binary = "2.9.4" +psycopg2-binary = "^2.9.10" allure-pytest = "^2.13.5" allure-pytest-collection-report = {git = "https://github.com/canonical/data-platform-workflows", tag = "v23.0.5", subdirectory = "python/pytest_plugins/allure_pytest_collection_report"} diff --git a/templates/patroni.yml.j2 b/templates/patroni.yml.j2 index 9885271226..68a3ae8c4d 100644 --- a/templates/patroni.yml.j2 +++ b/templates/patroni.yml.j2 @@ -190,12 +190,27 @@ postgresql: replication: username: replication password: {{ replication_password }} + {%- if enable_tls %} + sslrootcert: {{ conf_path }}/ca.pem + sslcert: {{ conf_path }}/cert.pem + sslkey: {{ conf_path }}/key.pem + {%- endif %} rewind: username: {{ rewind_user }} password: {{ rewind_password }} + {%- if enable_tls %} + sslrootcert: {{ conf_path }}/ca.pem + sslcert: {{ conf_path }}/cert.pem + sslkey: {{ conf_path }}/key.pem + {%- endif %} superuser: username: {{ superuser }} password: {{ superuser_password }} + {%- if enable_tls %} + sslrootcert: {{ conf_path }}/ca.pem + sslcert: {{ conf_path }}/cert.pem + sslkey: {{ conf_path }}/key.pem + {%- endif %} use_unix_socket: true {%- if is_creating_backup %} tags: From 35c0e5309172a3901ad622ad71842f2077e3d68e Mon Sep 17 00:00:00 2001 From: Lucas Gameiro Borges Date: Tue, 19 Nov 2024 21:30:03 +0000 Subject: [PATCH 12/74] revert lock --- poetry.lock | 740 +++++++++++++++++++++++++++------------------------- 1 file changed, 386 insertions(+), 354 deletions(-) diff --git a/poetry.lock b/poetry.lock index 4b25296de0..7b4d2ac24f 100644 --- a/poetry.lock +++ b/poetry.lock @@ -122,36 +122,38 @@ files = [ [[package]] name = "bcrypt" -version = "4.2.1" +version = "4.2.0" description = "Modern password hashing for your software and your servers" optional = false python-versions = ">=3.7" files = [ - {file = "bcrypt-4.2.1-cp37-abi3-macosx_10_12_universal2.whl", hash = "sha256:1340411a0894b7d3ef562fb233e4b6ed58add185228650942bdc885362f32c17"}, - {file = "bcrypt-4.2.1-cp37-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b1ee315739bc8387aa36ff127afc99120ee452924e0df517a8f3e4c0187a0f5f"}, - {file = "bcrypt-4.2.1-cp37-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8dbd0747208912b1e4ce730c6725cb56c07ac734b3629b60d4398f082ea718ad"}, - {file = "bcrypt-4.2.1-cp37-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:aaa2e285be097050dba798d537b6efd9b698aa88eef52ec98d23dcd6d7cf6fea"}, - {file = "bcrypt-4.2.1-cp37-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:76d3e352b32f4eeb34703370e370997065d28a561e4a18afe4fef07249cb4396"}, - {file = "bcrypt-4.2.1-cp37-abi3-musllinux_1_1_aarch64.whl", hash = "sha256:b7703ede632dc945ed1172d6f24e9f30f27b1b1a067f32f68bf169c5f08d0425"}, - {file = "bcrypt-4.2.1-cp37-abi3-musllinux_1_1_x86_64.whl", hash = "sha256:89df2aea2c43be1e1fa066df5f86c8ce822ab70a30e4c210968669565c0f4685"}, - {file = "bcrypt-4.2.1-cp37-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:04e56e3fe8308a88b77e0afd20bec516f74aecf391cdd6e374f15cbed32783d6"}, - {file = "bcrypt-4.2.1-cp37-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:cfdf3d7530c790432046c40cda41dfee8c83e29482e6a604f8930b9930e94139"}, - {file = "bcrypt-4.2.1-cp37-abi3-win32.whl", hash = "sha256:adadd36274510a01f33e6dc08f5824b97c9580583bd4487c564fc4617b328005"}, - {file = "bcrypt-4.2.1-cp37-abi3-win_amd64.whl", hash = "sha256:8c458cd103e6c5d1d85cf600e546a639f234964d0228909d8f8dbeebff82d526"}, - {file = "bcrypt-4.2.1-cp39-abi3-macosx_10_12_universal2.whl", hash = "sha256:8ad2f4528cbf0febe80e5a3a57d7a74e6635e41af1ea5675282a33d769fba413"}, - {file = "bcrypt-4.2.1-cp39-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:909faa1027900f2252a9ca5dfebd25fc0ef1417943824783d1c8418dd7d6df4a"}, - {file = "bcrypt-4.2.1-cp39-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:cde78d385d5e93ece5479a0a87f73cd6fa26b171c786a884f955e165032b262c"}, - {file = "bcrypt-4.2.1-cp39-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:533e7f3bcf2f07caee7ad98124fab7499cb3333ba2274f7a36cf1daee7409d99"}, - {file = "bcrypt-4.2.1-cp39-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:687cf30e6681eeda39548a93ce9bfbb300e48b4d445a43db4298d2474d2a1e54"}, - {file = "bcrypt-4.2.1-cp39-abi3-musllinux_1_1_aarch64.whl", hash = "sha256:041fa0155c9004eb98a232d54da05c0b41d4b8e66b6fc3cb71b4b3f6144ba837"}, - {file = "bcrypt-4.2.1-cp39-abi3-musllinux_1_1_x86_64.whl", hash = "sha256:f85b1ffa09240c89aa2e1ae9f3b1c687104f7b2b9d2098da4e923f1b7082d331"}, - {file = "bcrypt-4.2.1-cp39-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:c6f5fa3775966cca251848d4d5393ab016b3afed251163c1436fefdec3b02c84"}, - {file = "bcrypt-4.2.1-cp39-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:807261df60a8b1ccd13e6599c779014a362ae4e795f5c59747f60208daddd96d"}, - {file = "bcrypt-4.2.1-cp39-abi3-win32.whl", hash = "sha256:b588af02b89d9fad33e5f98f7838bf590d6d692df7153647724a7f20c186f6bf"}, - {file = "bcrypt-4.2.1-cp39-abi3-win_amd64.whl", hash = "sha256:e84e0e6f8e40a242b11bce56c313edc2be121cec3e0ec2d76fce01f6af33c07c"}, - {file = "bcrypt-4.2.1-pp310-pypy310_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:76132c176a6d9953cdc83c296aeaed65e1a708485fd55abf163e0d9f8f16ce0e"}, - {file = "bcrypt-4.2.1-pp310-pypy310_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:e158009a54c4c8bc91d5e0da80920d048f918c61a581f0a63e4e93bb556d362f"}, - {file = "bcrypt-4.2.1.tar.gz", hash = "sha256:6765386e3ab87f569b276988742039baab087b2cdb01e809d74e74503c2faafe"}, + {file = "bcrypt-4.2.0-cp37-abi3-macosx_10_12_universal2.whl", hash = "sha256:096a15d26ed6ce37a14c1ac1e48119660f21b24cba457f160a4b830f3fe6b5cb"}, + {file = "bcrypt-4.2.0-cp37-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c02d944ca89d9b1922ceb8a46460dd17df1ba37ab66feac4870f6862a1533c00"}, + {file = "bcrypt-4.2.0-cp37-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1d84cf6d877918620b687b8fd1bf7781d11e8a0998f576c7aa939776b512b98d"}, + {file = "bcrypt-4.2.0-cp37-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:1bb429fedbe0249465cdd85a58e8376f31bb315e484f16e68ca4c786dcc04291"}, + {file = "bcrypt-4.2.0-cp37-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:655ea221910bcac76ea08aaa76df427ef8625f92e55a8ee44fbf7753dbabb328"}, + {file = "bcrypt-4.2.0-cp37-abi3-musllinux_1_1_aarch64.whl", hash = "sha256:1ee38e858bf5d0287c39b7a1fc59eec64bbf880c7d504d3a06a96c16e14058e7"}, + {file = "bcrypt-4.2.0-cp37-abi3-musllinux_1_1_x86_64.whl", hash = "sha256:0da52759f7f30e83f1e30a888d9163a81353ef224d82dc58eb5bb52efcabc399"}, + {file = "bcrypt-4.2.0-cp37-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:3698393a1b1f1fd5714524193849d0c6d524d33523acca37cd28f02899285060"}, + {file = "bcrypt-4.2.0-cp37-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:762a2c5fb35f89606a9fde5e51392dad0cd1ab7ae64149a8b935fe8d79dd5ed7"}, + {file = "bcrypt-4.2.0-cp37-abi3-win32.whl", hash = "sha256:5a1e8aa9b28ae28020a3ac4b053117fb51c57a010b9f969603ed885f23841458"}, + {file = "bcrypt-4.2.0-cp37-abi3-win_amd64.whl", hash = "sha256:8f6ede91359e5df88d1f5c1ef47428a4420136f3ce97763e31b86dd8280fbdf5"}, + {file = "bcrypt-4.2.0-cp39-abi3-macosx_10_12_universal2.whl", hash = "sha256:c52aac18ea1f4a4f65963ea4f9530c306b56ccd0c6f8c8da0c06976e34a6e841"}, + {file = "bcrypt-4.2.0-cp39-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3bbbfb2734f0e4f37c5136130405332640a1e46e6b23e000eeff2ba8d005da68"}, + {file = "bcrypt-4.2.0-cp39-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3413bd60460f76097ee2e0a493ccebe4a7601918219c02f503984f0a7ee0aebe"}, + {file = "bcrypt-4.2.0-cp39-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:8d7bb9c42801035e61c109c345a28ed7e84426ae4865511eb82e913df18f58c2"}, + {file = "bcrypt-4.2.0-cp39-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:3d3a6d28cb2305b43feac298774b997e372e56c7c7afd90a12b3dc49b189151c"}, + {file = "bcrypt-4.2.0-cp39-abi3-musllinux_1_1_aarch64.whl", hash = "sha256:9c1c4ad86351339c5f320ca372dfba6cb6beb25e8efc659bedd918d921956bae"}, + {file = "bcrypt-4.2.0-cp39-abi3-musllinux_1_1_x86_64.whl", hash = "sha256:27fe0f57bb5573104b5a6de5e4153c60814c711b29364c10a75a54bb6d7ff48d"}, + {file = "bcrypt-4.2.0-cp39-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:8ac68872c82f1add6a20bd489870c71b00ebacd2e9134a8aa3f98a0052ab4b0e"}, + {file = "bcrypt-4.2.0-cp39-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:cb2a8ec2bc07d3553ccebf0746bbf3d19426d1c6d1adbd4fa48925f66af7b9e8"}, + {file = "bcrypt-4.2.0-cp39-abi3-win32.whl", hash = "sha256:77800b7147c9dc905db1cba26abe31e504d8247ac73580b4aa179f98e6608f34"}, + {file = "bcrypt-4.2.0-cp39-abi3-win_amd64.whl", hash = "sha256:61ed14326ee023917ecd093ee6ef422a72f3aec6f07e21ea5f10622b735538a9"}, + {file = "bcrypt-4.2.0-pp310-pypy310_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:39e1d30c7233cfc54f5c3f2c825156fe044efdd3e0b9d309512cc514a263ec2a"}, + {file = "bcrypt-4.2.0-pp310-pypy310_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:f4f4acf526fcd1c34e7ce851147deedd4e26e6402369304220250598b26448db"}, + {file = "bcrypt-4.2.0-pp39-pypy39_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:1ff39b78a52cf03fdf902635e4c81e544714861ba3f0efc56558979dd4f09170"}, + {file = "bcrypt-4.2.0-pp39-pypy39_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:373db9abe198e8e2c70d12b479464e0d5092cc122b20ec504097b5f2297ed184"}, + {file = "bcrypt-4.2.0.tar.gz", hash = "sha256:cf69eaf5185fd58f268f805b505ce31f9b9fc2d64b376642164e9244540c1221"}, ] [package.extras] @@ -160,17 +162,17 @@ typecheck = ["mypy"] [[package]] name = "boto3" -version = "1.35.64" +version = "1.35.49" description = "The AWS SDK for Python" optional = false python-versions = ">=3.8" files = [ - {file = "boto3-1.35.64-py3-none-any.whl", hash = "sha256:cdacf03fc750caa3aa0dbf6158166def9922c9d67b4160999ff8fc350662facc"}, - {file = "boto3-1.35.64.tar.gz", hash = "sha256:bc3fc12b41fa2c91e51ab140f74fb1544408a2b1e00f88a4c2369a66d18ddf20"}, + {file = "boto3-1.35.49-py3-none-any.whl", hash = "sha256:b660c649a27a6b47a34f6f858f5bd7c3b0a798a16dec8dda7cbebeee80fd1f60"}, + {file = "boto3-1.35.49.tar.gz", hash = "sha256:ddecb27f5699ca9f97711c52b6c0652c2e63bf6c2bfbc13b819b4f523b4d30ff"}, ] [package.dependencies] -botocore = ">=1.35.64,<1.36.0" +botocore = ">=1.35.49,<1.36.0" jmespath = ">=0.7.1,<2.0.0" s3transfer = ">=0.10.0,<0.11.0" @@ -179,13 +181,13 @@ crt = ["botocore[crt] (>=1.21.0,<2.0a0)"] [[package]] name = "botocore" -version = "1.35.64" +version = "1.35.49" description = "Low-level, data-driven core of boto 3." optional = false python-versions = ">=3.8" files = [ - {file = "botocore-1.35.64-py3-none-any.whl", hash = "sha256:bbd96bf7f442b1d5e35b36f501076e4a588c83d8d84a1952e9ee1d767e5efb3e"}, - {file = "botocore-1.35.64.tar.gz", hash = "sha256:2f95c83f31c9e38a66995c88810fc638c829790e125032ba00ab081a2cf48cb9"}, + {file = "botocore-1.35.49-py3-none-any.whl", hash = "sha256:aed4d3643afd702920792b68fbe712a8c3847993820d1048cd238a6469354da1"}, + {file = "botocore-1.35.49.tar.gz", hash = "sha256:07d0c1325fdbfa49a4a054413dbdeab0a6030449b2aa66099241af2dac48afd8"}, ] [package.dependencies] @@ -460,73 +462,73 @@ typing-extensions = "*" [[package]] name = "coverage" -version = "7.6.7" +version = "7.6.4" description = "Code coverage measurement for Python" optional = false python-versions = ">=3.9" files = [ - {file = "coverage-7.6.7-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:108bb458827765d538abcbf8288599fee07d2743357bdd9b9dad456c287e121e"}, - {file = "coverage-7.6.7-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:c973b2fe4dc445cb865ab369df7521df9c27bf40715c837a113edaa2aa9faf45"}, - {file = "coverage-7.6.7-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3c6b24007c4bcd0b19fac25763a7cac5035c735ae017e9a349b927cfc88f31c1"}, - {file = "coverage-7.6.7-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:acbb8af78f8f91b3b51f58f288c0994ba63c646bc1a8a22ad072e4e7e0a49f1c"}, - {file = "coverage-7.6.7-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ad32a981bcdedb8d2ace03b05e4fd8dace8901eec64a532b00b15217d3677dd2"}, - {file = "coverage-7.6.7-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:34d23e28ccb26236718a3a78ba72744212aa383141961dd6825f6595005c8b06"}, - {file = "coverage-7.6.7-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:e25bacb53a8c7325e34d45dddd2f2fbae0dbc230d0e2642e264a64e17322a777"}, - {file = "coverage-7.6.7-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:af05bbba896c4472a29408455fe31b3797b4d8648ed0a2ccac03e074a77e2314"}, - {file = "coverage-7.6.7-cp310-cp310-win32.whl", hash = "sha256:796c9b107d11d2d69e1849b2dfe41730134b526a49d3acb98ca02f4985eeff7a"}, - {file = "coverage-7.6.7-cp310-cp310-win_amd64.whl", hash = "sha256:987a8e3da7da4eed10a20491cf790589a8e5e07656b6dc22d3814c4d88faf163"}, - {file = "coverage-7.6.7-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:7e61b0e77ff4dddebb35a0e8bb5a68bf0f8b872407d8d9f0c726b65dfabe2469"}, - {file = "coverage-7.6.7-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:1a5407a75ca4abc20d6252efeb238377a71ce7bda849c26c7a9bece8680a5d99"}, - {file = "coverage-7.6.7-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:df002e59f2d29e889c37abd0b9ee0d0e6e38c24f5f55d71ff0e09e3412a340ec"}, - {file = "coverage-7.6.7-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:673184b3156cba06154825f25af33baa2671ddae6343f23175764e65a8c4c30b"}, - {file = "coverage-7.6.7-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e69ad502f1a2243f739f5bd60565d14a278be58be4c137d90799f2c263e7049a"}, - {file = "coverage-7.6.7-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:60dcf7605c50ea72a14490d0756daffef77a5be15ed1b9fea468b1c7bda1bc3b"}, - {file = "coverage-7.6.7-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:9c2eb378bebb2c8f65befcb5147877fc1c9fbc640fc0aad3add759b5df79d55d"}, - {file = "coverage-7.6.7-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:3c0317288f032221d35fa4cbc35d9f4923ff0dfd176c79c9b356e8ef8ef2dff4"}, - {file = "coverage-7.6.7-cp311-cp311-win32.whl", hash = "sha256:951aade8297358f3618a6e0660dc74f6b52233c42089d28525749fc8267dccd2"}, - {file = "coverage-7.6.7-cp311-cp311-win_amd64.whl", hash = "sha256:5e444b8e88339a2a67ce07d41faabb1d60d1004820cee5a2c2b54e2d8e429a0f"}, - {file = "coverage-7.6.7-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:f07ff574986bc3edb80e2c36391678a271d555f91fd1d332a1e0f4b5ea4b6ea9"}, - {file = "coverage-7.6.7-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:49ed5ee4109258973630c1f9d099c7e72c5c36605029f3a91fe9982c6076c82b"}, - {file = "coverage-7.6.7-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f3e8796434a8106b3ac025fd15417315d7a58ee3e600ad4dbcfddc3f4b14342c"}, - {file = "coverage-7.6.7-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a3b925300484a3294d1c70f6b2b810d6526f2929de954e5b6be2bf8caa1f12c1"}, - {file = "coverage-7.6.7-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3c42ec2c522e3ddd683dec5cdce8e62817afb648caedad9da725001fa530d354"}, - {file = "coverage-7.6.7-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:0266b62cbea568bd5e93a4da364d05de422110cbed5056d69339bd5af5685433"}, - {file = "coverage-7.6.7-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:e5f2a0f161d126ccc7038f1f3029184dbdf8f018230af17ef6fd6a707a5b881f"}, - {file = "coverage-7.6.7-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:c132b5a22821f9b143f87446805e13580b67c670a548b96da945a8f6b4f2efbb"}, - {file = "coverage-7.6.7-cp312-cp312-win32.whl", hash = "sha256:7c07de0d2a110f02af30883cd7dddbe704887617d5c27cf373362667445a4c76"}, - {file = "coverage-7.6.7-cp312-cp312-win_amd64.whl", hash = "sha256:fd49c01e5057a451c30c9b892948976f5d38f2cbd04dc556a82743ba8e27ed8c"}, - {file = "coverage-7.6.7-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:46f21663e358beae6b368429ffadf14ed0a329996248a847a4322fb2e35d64d3"}, - {file = "coverage-7.6.7-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:40cca284c7c310d622a1677f105e8507441d1bb7c226f41978ba7c86979609ab"}, - {file = "coverage-7.6.7-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:77256ad2345c29fe59ae861aa11cfc74579c88d4e8dbf121cbe46b8e32aec808"}, - {file = "coverage-7.6.7-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:87ea64b9fa52bf395272e54020537990a28078478167ade6c61da7ac04dc14bc"}, - {file = "coverage-7.6.7-cp313-cp313-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2d608a7808793e3615e54e9267519351c3ae204a6d85764d8337bd95993581a8"}, - {file = "coverage-7.6.7-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:cdd94501d65adc5c24f8a1a0eda110452ba62b3f4aeaba01e021c1ed9cb8f34a"}, - {file = "coverage-7.6.7-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:82c809a62e953867cf57e0548c2b8464207f5f3a6ff0e1e961683e79b89f2c55"}, - {file = "coverage-7.6.7-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:bb684694e99d0b791a43e9fc0fa58efc15ec357ac48d25b619f207c41f2fd384"}, - {file = "coverage-7.6.7-cp313-cp313-win32.whl", hash = "sha256:963e4a08cbb0af6623e61492c0ec4c0ec5c5cf74db5f6564f98248d27ee57d30"}, - {file = "coverage-7.6.7-cp313-cp313-win_amd64.whl", hash = "sha256:14045b8bfd5909196a90da145a37f9d335a5d988a83db34e80f41e965fb7cb42"}, - {file = "coverage-7.6.7-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:f2c7a045eef561e9544359a0bf5784b44e55cefc7261a20e730baa9220c83413"}, - {file = "coverage-7.6.7-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:5dd4e4a49d9c72a38d18d641135d2fb0bdf7b726ca60a103836b3d00a1182acd"}, - {file = "coverage-7.6.7-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5c95e0fa3d1547cb6f021ab72f5c23402da2358beec0a8e6d19a368bd7b0fb37"}, - {file = "coverage-7.6.7-cp313-cp313t-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f63e21ed474edd23f7501f89b53280014436e383a14b9bd77a648366c81dce7b"}, - {file = "coverage-7.6.7-cp313-cp313t-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ead9b9605c54d15be228687552916c89c9683c215370c4a44f1f217d2adcc34d"}, - {file = "coverage-7.6.7-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:0573f5cbf39114270842d01872952d301027d2d6e2d84013f30966313cadb529"}, - {file = "coverage-7.6.7-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:e2c8e3384c12dfa19fa9a52f23eb091a8fad93b5b81a41b14c17c78e23dd1d8b"}, - {file = "coverage-7.6.7-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:70a56a2ec1869e6e9fa69ef6b76b1a8a7ef709972b9cc473f9ce9d26b5997ce3"}, - {file = "coverage-7.6.7-cp313-cp313t-win32.whl", hash = "sha256:dbba8210f5067398b2c4d96b4e64d8fb943644d5eb70be0d989067c8ca40c0f8"}, - {file = "coverage-7.6.7-cp313-cp313t-win_amd64.whl", hash = "sha256:dfd14bcae0c94004baba5184d1c935ae0d1231b8409eb6c103a5fd75e8ecdc56"}, - {file = "coverage-7.6.7-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:37a15573f988b67f7348916077c6d8ad43adb75e478d0910957394df397d2874"}, - {file = "coverage-7.6.7-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:b6cce5c76985f81da3769c52203ee94722cd5d5889731cd70d31fee939b74bf0"}, - {file = "coverage-7.6.7-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a1ab9763d291a17b527ac6fd11d1a9a9c358280adb320e9c2672a97af346ac2c"}, - {file = "coverage-7.6.7-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6cf96ceaa275f071f1bea3067f8fd43bec184a25a962c754024c973af871e1b7"}, - {file = "coverage-7.6.7-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:aee9cf6b0134d6f932d219ce253ef0e624f4fa588ee64830fcba193269e4daa3"}, - {file = "coverage-7.6.7-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:2bc3e45c16564cc72de09e37413262b9f99167803e5e48c6156bccdfb22c8327"}, - {file = "coverage-7.6.7-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:623e6965dcf4e28a3debaa6fcf4b99ee06d27218f46d43befe4db1c70841551c"}, - {file = "coverage-7.6.7-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:850cfd2d6fc26f8346f422920ac204e1d28814e32e3a58c19c91980fa74d8289"}, - {file = "coverage-7.6.7-cp39-cp39-win32.whl", hash = "sha256:c296263093f099da4f51b3dff1eff5d4959b527d4f2f419e16508c5da9e15e8c"}, - {file = "coverage-7.6.7-cp39-cp39-win_amd64.whl", hash = "sha256:90746521206c88bdb305a4bf3342b1b7316ab80f804d40c536fc7d329301ee13"}, - {file = "coverage-7.6.7-pp39.pp310-none-any.whl", hash = "sha256:0ddcb70b3a3a57581b450571b31cb774f23eb9519c2aaa6176d3a84c9fc57671"}, - {file = "coverage-7.6.7.tar.gz", hash = "sha256:d79d4826e41441c9a118ff045e4bccb9fdbdcb1d02413e7ea6eb5c87b5439d24"}, + {file = "coverage-7.6.4-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:5f8ae553cba74085db385d489c7a792ad66f7f9ba2ee85bfa508aeb84cf0ba07"}, + {file = "coverage-7.6.4-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:8165b796df0bd42e10527a3f493c592ba494f16ef3c8b531288e3d0d72c1f6f0"}, + {file = "coverage-7.6.4-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c7c8b95bf47db6d19096a5e052ffca0a05f335bc63cef281a6e8fe864d450a72"}, + {file = "coverage-7.6.4-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:8ed9281d1b52628e81393f5eaee24a45cbd64965f41857559c2b7ff19385df51"}, + {file = "coverage-7.6.4-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0809082ee480bb8f7416507538243c8863ac74fd8a5d2485c46f0f7499f2b491"}, + {file = "coverage-7.6.4-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:d541423cdd416b78626b55f123412fcf979d22a2c39fce251b350de38c15c15b"}, + {file = "coverage-7.6.4-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:58809e238a8a12a625c70450b48e8767cff9eb67c62e6154a642b21ddf79baea"}, + {file = "coverage-7.6.4-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:c9b8e184898ed014884ca84c70562b4a82cbc63b044d366fedc68bc2b2f3394a"}, + {file = "coverage-7.6.4-cp310-cp310-win32.whl", hash = "sha256:6bd818b7ea14bc6e1f06e241e8234508b21edf1b242d49831831a9450e2f35fa"}, + {file = "coverage-7.6.4-cp310-cp310-win_amd64.whl", hash = "sha256:06babbb8f4e74b063dbaeb74ad68dfce9186c595a15f11f5d5683f748fa1d172"}, + {file = "coverage-7.6.4-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:73d2b73584446e66ee633eaad1a56aad577c077f46c35ca3283cd687b7715b0b"}, + {file = "coverage-7.6.4-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:51b44306032045b383a7a8a2c13878de375117946d68dcb54308111f39775a25"}, + {file = "coverage-7.6.4-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0b3fb02fe73bed561fa12d279a417b432e5b50fe03e8d663d61b3d5990f29546"}, + {file = "coverage-7.6.4-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ed8fe9189d2beb6edc14d3ad19800626e1d9f2d975e436f84e19efb7fa19469b"}, + {file = "coverage-7.6.4-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b369ead6527d025a0fe7bd3864e46dbee3aa8f652d48df6174f8d0bac9e26e0e"}, + {file = "coverage-7.6.4-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:ade3ca1e5f0ff46b678b66201f7ff477e8fa11fb537f3b55c3f0568fbfe6e718"}, + {file = "coverage-7.6.4-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:27fb4a050aaf18772db513091c9c13f6cb94ed40eacdef8dad8411d92d9992db"}, + {file = "coverage-7.6.4-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:4f704f0998911abf728a7783799444fcbbe8261c4a6c166f667937ae6a8aa522"}, + {file = "coverage-7.6.4-cp311-cp311-win32.whl", hash = "sha256:29155cd511ee058e260db648b6182c419422a0d2e9a4fa44501898cf918866cf"}, + {file = "coverage-7.6.4-cp311-cp311-win_amd64.whl", hash = "sha256:8902dd6a30173d4ef09954bfcb24b5d7b5190cf14a43170e386979651e09ba19"}, + {file = "coverage-7.6.4-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:12394842a3a8affa3ba62b0d4ab7e9e210c5e366fbac3e8b2a68636fb19892c2"}, + {file = "coverage-7.6.4-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:2b6b4c83d8e8ea79f27ab80778c19bc037759aea298da4b56621f4474ffeb117"}, + {file = "coverage-7.6.4-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1d5b8007f81b88696d06f7df0cb9af0d3b835fe0c8dbf489bad70b45f0e45613"}, + {file = "coverage-7.6.4-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b57b768feb866f44eeed9f46975f3d6406380275c5ddfe22f531a2bf187eda27"}, + {file = "coverage-7.6.4-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5915fcdec0e54ee229926868e9b08586376cae1f5faa9bbaf8faf3561b393d52"}, + {file = "coverage-7.6.4-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:0b58c672d14f16ed92a48db984612f5ce3836ae7d72cdd161001cc54512571f2"}, + {file = "coverage-7.6.4-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:2fdef0d83a2d08d69b1f2210a93c416d54e14d9eb398f6ab2f0a209433db19e1"}, + {file = "coverage-7.6.4-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:8cf717ee42012be8c0cb205dbbf18ffa9003c4cbf4ad078db47b95e10748eec5"}, + {file = "coverage-7.6.4-cp312-cp312-win32.whl", hash = "sha256:7bb92c539a624cf86296dd0c68cd5cc286c9eef2d0c3b8b192b604ce9de20a17"}, + {file = "coverage-7.6.4-cp312-cp312-win_amd64.whl", hash = "sha256:1032e178b76a4e2b5b32e19d0fd0abbce4b58e77a1ca695820d10e491fa32b08"}, + {file = "coverage-7.6.4-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:023bf8ee3ec6d35af9c1c6ccc1d18fa69afa1cb29eaac57cb064dbb262a517f9"}, + {file = "coverage-7.6.4-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:b0ac3d42cb51c4b12df9c5f0dd2f13a4f24f01943627120ec4d293c9181219ba"}, + {file = "coverage-7.6.4-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f8fe4984b431f8621ca53d9380901f62bfb54ff759a1348cd140490ada7b693c"}, + {file = "coverage-7.6.4-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:5fbd612f8a091954a0c8dd4c0b571b973487277d26476f8480bfa4b2a65b5d06"}, + {file = "coverage-7.6.4-cp313-cp313-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dacbc52de979f2823a819571f2e3a350a7e36b8cb7484cdb1e289bceaf35305f"}, + {file = "coverage-7.6.4-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:dab4d16dfef34b185032580e2f2f89253d302facba093d5fa9dbe04f569c4f4b"}, + {file = "coverage-7.6.4-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:862264b12ebb65ad8d863d51f17758b1684560b66ab02770d4f0baf2ff75da21"}, + {file = "coverage-7.6.4-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:5beb1ee382ad32afe424097de57134175fea3faf847b9af002cc7895be4e2a5a"}, + {file = "coverage-7.6.4-cp313-cp313-win32.whl", hash = "sha256:bf20494da9653f6410213424f5f8ad0ed885e01f7e8e59811f572bdb20b8972e"}, + {file = "coverage-7.6.4-cp313-cp313-win_amd64.whl", hash = "sha256:182e6cd5c040cec0a1c8d415a87b67ed01193ed9ad458ee427741c7d8513d963"}, + {file = "coverage-7.6.4-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:a181e99301a0ae128493a24cfe5cfb5b488c4e0bf2f8702091473d033494d04f"}, + {file = "coverage-7.6.4-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:df57bdbeffe694e7842092c5e2e0bc80fff7f43379d465f932ef36f027179806"}, + {file = "coverage-7.6.4-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0bcd1069e710600e8e4cf27f65c90c7843fa8edfb4520fb0ccb88894cad08b11"}, + {file = "coverage-7.6.4-cp313-cp313t-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:99b41d18e6b2a48ba949418db48159d7a2e81c5cc290fc934b7d2380515bd0e3"}, + {file = "coverage-7.6.4-cp313-cp313t-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a6b1e54712ba3474f34b7ef7a41e65bd9037ad47916ccb1cc78769bae324c01a"}, + {file = "coverage-7.6.4-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:53d202fd109416ce011578f321460795abfe10bb901b883cafd9b3ef851bacfc"}, + {file = "coverage-7.6.4-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:c48167910a8f644671de9f2083a23630fbf7a1cb70ce939440cd3328e0919f70"}, + {file = "coverage-7.6.4-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:cc8ff50b50ce532de2fa7a7daae9dd12f0a699bfcd47f20945364e5c31799fef"}, + {file = "coverage-7.6.4-cp313-cp313t-win32.whl", hash = "sha256:b8d3a03d9bfcaf5b0141d07a88456bb6a4c3ce55c080712fec8418ef3610230e"}, + {file = "coverage-7.6.4-cp313-cp313t-win_amd64.whl", hash = "sha256:f3ddf056d3ebcf6ce47bdaf56142af51bb7fad09e4af310241e9db7a3a8022e1"}, + {file = "coverage-7.6.4-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:9cb7fa111d21a6b55cbf633039f7bc2749e74932e3aa7cb7333f675a58a58bf3"}, + {file = "coverage-7.6.4-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:11a223a14e91a4693d2d0755c7a043db43d96a7450b4f356d506c2562c48642c"}, + {file = "coverage-7.6.4-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a413a096c4cbac202433c850ee43fa326d2e871b24554da8327b01632673a076"}, + {file = "coverage-7.6.4-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:00a1d69c112ff5149cabe60d2e2ee948752c975d95f1e1096742e6077affd376"}, + {file = "coverage-7.6.4-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1f76846299ba5c54d12c91d776d9605ae33f8ae2b9d1d3c3703cf2db1a67f2c0"}, + {file = "coverage-7.6.4-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:fe439416eb6380de434886b00c859304338f8b19f6f54811984f3420a2e03858"}, + {file = "coverage-7.6.4-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:0294ca37f1ba500667b1aef631e48d875ced93ad5e06fa665a3295bdd1d95111"}, + {file = "coverage-7.6.4-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:6f01ba56b1c0e9d149f9ac85a2f999724895229eb36bd997b61e62999e9b0901"}, + {file = "coverage-7.6.4-cp39-cp39-win32.whl", hash = "sha256:bc66f0bf1d7730a17430a50163bb264ba9ded56739112368ba985ddaa9c3bd09"}, + {file = "coverage-7.6.4-cp39-cp39-win_amd64.whl", hash = "sha256:c481b47f6b5845064c65a7bc78bc0860e635a9b055af0df46fdf1c58cebf8e8f"}, + {file = "coverage-7.6.4-pp39.pp310-none-any.whl", hash = "sha256:3c65d37f3a9ebb703e710befdc489a38683a5b152242664b973a7b7b22348a4e"}, + {file = "coverage-7.6.4.tar.gz", hash = "sha256:29fc0f17b1d3fea332f8001d4558f8214af7f1d87a345f3a133c901d60347c73"}, ] [package.dependencies] @@ -597,20 +599,20 @@ files = [ [[package]] name = "deprecated" -version = "1.2.15" +version = "1.2.14" description = "Python @deprecated decorator to deprecate old python classes, functions or methods." optional = false -python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,>=2.7" +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" files = [ - {file = "Deprecated-1.2.15-py2.py3-none-any.whl", hash = "sha256:353bc4a8ac4bfc96800ddab349d89c25dec1079f65fd53acdcc1e0b975b21320"}, - {file = "deprecated-1.2.15.tar.gz", hash = "sha256:683e561a90de76239796e6b6feac66b99030d2dd3fcf61ef996330f14bbb9b0d"}, + {file = "Deprecated-1.2.14-py2.py3-none-any.whl", hash = "sha256:6fac8b097794a90302bdbb17b9b815e732d3c4720583ff1b198499d78470466c"}, + {file = "Deprecated-1.2.14.tar.gz", hash = "sha256:e5323eb936458dccc2582dc6f9c322c852a775a27065ff2b0c4970b9d53d01b3"}, ] [package.dependencies] wrapt = ">=1.10,<2" [package.extras] -dev = ["PyTest", "PyTest-Cov", "bump2version (<1)", "jinja2 (>=3.0.3,<3.1.0)", "setuptools", "sphinx (<2)", "tox"] +dev = ["PyTest", "PyTest-Cov", "bump2version (<1)", "sphinx (<2)", "tox"] [[package]] name = "durationpy" @@ -653,13 +655,13 @@ tests = ["asttokens (>=2.1.0)", "coverage", "coverage-enable-subprocess", "ipyth [[package]] name = "google-auth" -version = "2.36.0" +version = "2.35.0" description = "Google Authentication Library" optional = false python-versions = ">=3.7" files = [ - {file = "google_auth-2.36.0-py2.py3-none-any.whl", hash = "sha256:51a15d47028b66fd36e5c64a82d2d57480075bccc7da37cde257fc94177a61fb"}, - {file = "google_auth-2.36.0.tar.gz", hash = "sha256:545e9618f2df0bcbb7dcbc45a546485b1212624716975a1ea5ae8149ce769ab1"}, + {file = "google_auth-2.35.0-py2.py3-none-any.whl", hash = "sha256:25df55f327ef021de8be50bad0dfd4a916ad0de96da86cd05661c9297723ad3f"}, + {file = "google_auth-2.35.0.tar.gz", hash = "sha256:f4c64ed4e01e8e8b646ef34c018f8bf3338df0c8e37d8b3bba40e7f574a3278a"}, ] [package.dependencies] @@ -676,13 +678,13 @@ requests = ["requests (>=2.20.0,<3.0.0.dev0)"] [[package]] name = "googleapis-common-protos" -version = "1.66.0" +version = "1.65.0" description = "Common protobufs used in Google APIs" optional = false python-versions = ">=3.7" files = [ - {file = "googleapis_common_protos-1.66.0-py2.py3-none-any.whl", hash = "sha256:d7abcd75fabb2e0ec9f74466401f6c119a0b498e27370e9be4c94cb7e382b8ed"}, - {file = "googleapis_common_protos-1.66.0.tar.gz", hash = "sha256:c3e7b33d15fdca5374cc0a7346dd92ffa847425cc4ea941d970f13680052ec8c"}, + {file = "googleapis_common_protos-1.65.0-py2.py3-none-any.whl", hash = "sha256:2972e6c496f435b92590fd54045060867f3fe9be2c82ab148fc8885035479a63"}, + {file = "googleapis_common_protos-1.65.0.tar.gz", hash = "sha256:334a29d07cddc3aa01dee4988f9afd9b2916ee2ff49d6b757155dc0d197852c0"}, ] [package.dependencies] @@ -704,13 +706,13 @@ files = [ [[package]] name = "httpcore" -version = "1.0.7" +version = "1.0.6" description = "A minimal low-level HTTP client." optional = false python-versions = ">=3.8" files = [ - {file = "httpcore-1.0.7-py3-none-any.whl", hash = "sha256:a3fff8f43dc260d5bd363d9f9cf1830fa3a458b332856f34282de498ed420edd"}, - {file = "httpcore-1.0.7.tar.gz", hash = "sha256:8551cb62a169ec7162ac7be8d4817d561f60e08eaa485234898414bb5a8a0b4c"}, + {file = "httpcore-1.0.6-py3-none-any.whl", hash = "sha256:27b59625743b85577a8c0e10e55b50b5368a4f2cfe8cc7bcfa9cf00829c2682f"}, + {file = "httpcore-1.0.6.tar.gz", hash = "sha256:73f6dbd6eb8c21bbf7ef8efad555481853f5f6acdeaff1edb0694289269ee17f"}, ] [package.dependencies] @@ -865,22 +867,22 @@ test-extra = ["curio", "ipython[test]", "matplotlib (!=3.2.0)", "nbformat", "num [[package]] name = "jedi" -version = "0.19.2" +version = "0.19.1" description = "An autocompletion tool for Python that can be used for text editors." optional = false python-versions = ">=3.6" files = [ - {file = "jedi-0.19.2-py2.py3-none-any.whl", hash = "sha256:a8ef22bde8490f57fe5c7681a3c83cb58874daf72b4784de3cce5b6ef6edb5b9"}, - {file = "jedi-0.19.2.tar.gz", hash = "sha256:4770dc3de41bde3966b02eb84fbcf557fb33cce26ad23da12c742fb50ecb11f0"}, + {file = "jedi-0.19.1-py2.py3-none-any.whl", hash = "sha256:e983c654fe5c02867aef4cdfce5a2fbb4a50adc0af145f70504238f18ef5e7e0"}, + {file = "jedi-0.19.1.tar.gz", hash = "sha256:cf0496f3651bc65d7174ac1b7d043eff454892c708a87d1b683e57b569927ffd"}, ] [package.dependencies] -parso = ">=0.8.4,<0.9.0" +parso = ">=0.8.3,<0.9.0" [package.extras] docs = ["Jinja2 (==2.11.3)", "MarkupSafe (==1.1.1)", "Pygments (==2.8.1)", "alabaster (==0.7.12)", "babel (==2.9.1)", "chardet (==4.0.0)", "commonmark (==0.8.1)", "docutils (==0.17.1)", "future (==0.18.2)", "idna (==2.10)", "imagesize (==1.2.0)", "mock (==1.0.1)", "packaging (==20.9)", "pyparsing (==2.4.7)", "pytz (==2021.1)", "readthedocs-sphinx-ext (==2.1.4)", "recommonmark (==0.5.0)", "requests (==2.25.1)", "six (==1.15.0)", "snowballstemmer (==2.1.0)", "sphinx (==1.8.5)", "sphinx-rtd-theme (==0.4.3)", "sphinxcontrib-serializinghtml (==1.1.4)", "sphinxcontrib-websupport (==1.2.4)", "urllib3 (==1.26.4)"] qa = ["flake8 (==5.0.4)", "mypy (==0.971)", "types-setuptools (==67.2.0.1)"] -testing = ["Django", "attrs", "colorama", "docopt", "pytest (<9.0.0)"] +testing = ["Django", "attrs", "colorama", "docopt", "pytest (<7.0.0)"] [[package]] name = "jinja2" @@ -1011,13 +1013,13 @@ requests = "*" [[package]] name = "lightkube" -version = "0.15.5" +version = "0.15.4" description = "Lightweight kubernetes client library" optional = false python-versions = "*" files = [ - {file = "lightkube-0.15.5-py3-none-any.whl", hash = "sha256:0d93be743cbeae022d18a1d3fbb45d1df58f9a603ea0061237842658f68d93fd"}, - {file = "lightkube-0.15.5.tar.gz", hash = "sha256:5edbfd1aee83398374179f41f4897519e8f89dc9754c866d40bbdc68c49c033f"}, + {file = "lightkube-0.15.4-py3-none-any.whl", hash = "sha256:7dde49694f2933b757ee6dfee0e028a56d7a13f47476bf54a21ec7cee343b09b"}, + {file = "lightkube-0.15.4.tar.gz", hash = "sha256:fe7939f8da5b68d80809243c9abf601fca2e5425228bb02c2ad23ed3e56401a8"}, ] [package.dependencies] @@ -1303,13 +1305,13 @@ testing = ["ops-scenario (>=7.0.5,<8)"] [[package]] name = "packaging" -version = "24.2" +version = "24.1" description = "Core utilities for Python packages" optional = false python-versions = ">=3.8" files = [ - {file = "packaging-24.2-py3-none-any.whl", hash = "sha256:09abb1bccd265c01f4a3aa3f7a7db064b36514d2cba19a2f694fe6150451a759"}, - {file = "packaging-24.2.tar.gz", hash = "sha256:c228a6dc5e932d346bc5739379109d49e8853dd8223571c7c5b55260edc0b97f"}, + {file = "packaging-24.1-py3-none-any.whl", hash = "sha256:5b8f2217dbdbd2f7f384c41c628544e6d52f2d0f53c6d0c3ea61aa5d1d7ff124"}, + {file = "packaging-24.1.tar.gz", hash = "sha256:026ed72c8ed3fcce5bf8950572258698927fd1dbda10a5e981cdf0ac37f4f002"}, ] [[package]] @@ -1604,54 +1606,54 @@ files = [ [[package]] name = "pydantic" -version = "1.10.19" +version = "1.10.18" description = "Data validation and settings management using python type hints" optional = false python-versions = ">=3.7" files = [ - {file = "pydantic-1.10.19-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:a415b9e95fa602b10808113967f72b2da8722061265d6af69268c111c254832d"}, - {file = "pydantic-1.10.19-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:11965f421f7eb026439d4eb7464e9182fe6d69c3d4d416e464a4485d1ba61ab6"}, - {file = "pydantic-1.10.19-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f5bb81fcfc6d5bff62cd786cbd87480a11d23f16d5376ad2e057c02b3b44df96"}, - {file = "pydantic-1.10.19-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:83ee8c9916689f8e6e7d90161e6663ac876be2efd32f61fdcfa3a15e87d4e413"}, - {file = "pydantic-1.10.19-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:0399094464ae7f28482de22383e667625e38e1516d6b213176df1acdd0c477ea"}, - {file = "pydantic-1.10.19-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:8b2cf5e26da84f2d2dee3f60a3f1782adedcee785567a19b68d0af7e1534bd1f"}, - {file = "pydantic-1.10.19-cp310-cp310-win_amd64.whl", hash = "sha256:1fc8cc264afaf47ae6a9bcbd36c018d0c6b89293835d7fb0e5e1a95898062d59"}, - {file = "pydantic-1.10.19-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:d7a8a1dd68bac29f08f0a3147de1885f4dccec35d4ea926e6e637fac03cdb4b3"}, - {file = "pydantic-1.10.19-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:07d00ca5ef0de65dd274005433ce2bb623730271d495a7d190a91c19c5679d34"}, - {file = "pydantic-1.10.19-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ad57004e5d73aee36f1e25e4e73a4bc853b473a1c30f652dc8d86b0a987ffce3"}, - {file = "pydantic-1.10.19-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:dce355fe7ae53e3090f7f5fa242423c3a7b53260747aa398b4b3aaf8b25f41c3"}, - {file = "pydantic-1.10.19-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:0d32227ea9a3bf537a2273fd2fdb6d64ab4d9b83acd9e4e09310a777baaabb98"}, - {file = "pydantic-1.10.19-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:e351df83d1c9cffa53d4e779009a093be70f1d5c6bb7068584086f6a19042526"}, - {file = "pydantic-1.10.19-cp311-cp311-win_amd64.whl", hash = "sha256:d8d72553d2f3f57ce547de4fa7dc8e3859927784ab2c88343f1fc1360ff17a08"}, - {file = "pydantic-1.10.19-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:d5b5b7c6bafaef90cbb7dafcb225b763edd71d9e22489647ee7df49d6d341890"}, - {file = "pydantic-1.10.19-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:570ad0aeaf98b5e33ff41af75aba2ef6604ee25ce0431ecd734a28e74a208555"}, - {file = "pydantic-1.10.19-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0890fbd7fec9e151c7512941243d830b2d6076d5df159a2030952d480ab80a4e"}, - {file = "pydantic-1.10.19-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ec5c44e6e9eac5128a9bfd21610df3b8c6b17343285cc185105686888dc81206"}, - {file = "pydantic-1.10.19-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:6eb56074b11a696e0b66c7181da682e88c00e5cebe6570af8013fcae5e63e186"}, - {file = "pydantic-1.10.19-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:9d7d48fbc5289efd23982a0d68e973a1f37d49064ccd36d86de4543aff21e086"}, - {file = "pydantic-1.10.19-cp312-cp312-win_amd64.whl", hash = "sha256:fd34012691fbd4e67bdf4accb1f0682342101015b78327eaae3543583fcd451e"}, - {file = "pydantic-1.10.19-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:4a5d5b877c7d3d9e17399571a8ab042081d22fe6904416a8b20f8af5909e6c8f"}, - {file = "pydantic-1.10.19-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9c46f58ef2df958ed2ea7437a8be0897d5efe9ee480818405338c7da88186fb3"}, - {file = "pydantic-1.10.19-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6d8a38a44bb6a15810084316ed69c854a7c06e0c99c5429f1d664ad52cec353c"}, - {file = "pydantic-1.10.19-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:a82746c6d6e91ca17e75f7f333ed41d70fce93af520a8437821dec3ee52dfb10"}, - {file = "pydantic-1.10.19-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:566bebdbe6bc0ac593fa0f67d62febbad9f8be5433f686dc56401ba4aab034e3"}, - {file = "pydantic-1.10.19-cp37-cp37m-win_amd64.whl", hash = "sha256:22a1794e01591884741be56c6fba157c4e99dcc9244beb5a87bd4aa54b84ea8b"}, - {file = "pydantic-1.10.19-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:076c49e24b73d346c45f9282d00dbfc16eef7ae27c970583d499f11110d9e5b0"}, - {file = "pydantic-1.10.19-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:5d4320510682d5a6c88766b2a286d03b87bd3562bf8d78c73d63bab04b21e7b4"}, - {file = "pydantic-1.10.19-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7e66aa0fa7f8aa9d0a620361834f6eb60d01d3e9cea23ca1a92cda99e6f61dac"}, - {file = "pydantic-1.10.19-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d216f8d0484d88ab72ab45d699ac669fe031275e3fa6553e3804e69485449fa0"}, - {file = "pydantic-1.10.19-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:9f28a81978e936136c44e6a70c65bde7548d87f3807260f73aeffbf76fb94c2f"}, - {file = "pydantic-1.10.19-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:d3449633c207ec3d2d672eedb3edbe753e29bd4e22d2e42a37a2c1406564c20f"}, - {file = "pydantic-1.10.19-cp38-cp38-win_amd64.whl", hash = "sha256:7ea24e8614f541d69ea72759ff635df0e612b7dc9d264d43f51364df310081a3"}, - {file = "pydantic-1.10.19-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:573254d844f3e64093f72fcd922561d9c5696821ff0900a0db989d8c06ab0c25"}, - {file = "pydantic-1.10.19-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:ff09600cebe957ecbb4a27496fe34c1d449e7957ed20a202d5029a71a8af2e35"}, - {file = "pydantic-1.10.19-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4739c206bfb6bb2bdc78dcd40bfcebb2361add4ceac6d170e741bb914e9eff0f"}, - {file = "pydantic-1.10.19-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0bfb5b378b78229119d66ced6adac2e933c67a0aa1d0a7adffbe432f3ec14ce4"}, - {file = "pydantic-1.10.19-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:7f31742c95e3f9443b8c6fa07c119623e61d76603be9c0d390bcf7e888acabcb"}, - {file = "pydantic-1.10.19-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:c6444368b651a14c2ce2fb22145e1496f7ab23cbdb978590d47c8d34a7bc0289"}, - {file = "pydantic-1.10.19-cp39-cp39-win_amd64.whl", hash = "sha256:945407f4d08cd12485757a281fca0e5b41408606228612f421aa4ea1b63a095d"}, - {file = "pydantic-1.10.19-py3-none-any.whl", hash = "sha256:2206a1752d9fac011e95ca83926a269fb0ef5536f7e053966d058316e24d929f"}, - {file = "pydantic-1.10.19.tar.gz", hash = "sha256:fea36c2065b7a1d28c6819cc2e93387b43dd5d3cf5a1e82d8132ee23f36d1f10"}, + {file = "pydantic-1.10.18-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:e405ffcc1254d76bb0e760db101ee8916b620893e6edfbfee563b3c6f7a67c02"}, + {file = "pydantic-1.10.18-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:e306e280ebebc65040034bff1a0a81fd86b2f4f05daac0131f29541cafd80b80"}, + {file = "pydantic-1.10.18-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:11d9d9b87b50338b1b7de4ebf34fd29fdb0d219dc07ade29effc74d3d2609c62"}, + {file = "pydantic-1.10.18-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b661ce52c7b5e5f600c0c3c5839e71918346af2ef20062705ae76b5c16914cab"}, + {file = "pydantic-1.10.18-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:c20f682defc9ef81cd7eaa485879ab29a86a0ba58acf669a78ed868e72bb89e0"}, + {file = "pydantic-1.10.18-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:c5ae6b7c8483b1e0bf59e5f1843e4fd8fd405e11df7de217ee65b98eb5462861"}, + {file = "pydantic-1.10.18-cp310-cp310-win_amd64.whl", hash = "sha256:74fe19dda960b193b0eb82c1f4d2c8e5e26918d9cda858cbf3f41dd28549cb70"}, + {file = "pydantic-1.10.18-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:72fa46abace0a7743cc697dbb830a41ee84c9db8456e8d77a46d79b537efd7ec"}, + {file = "pydantic-1.10.18-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:ef0fe7ad7cbdb5f372463d42e6ed4ca9c443a52ce544472d8842a0576d830da5"}, + {file = "pydantic-1.10.18-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a00e63104346145389b8e8f500bc6a241e729feaf0559b88b8aa513dd2065481"}, + {file = "pydantic-1.10.18-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ae6fa2008e1443c46b7b3a5eb03800121868d5ab6bc7cda20b5df3e133cde8b3"}, + {file = "pydantic-1.10.18-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:9f463abafdc92635da4b38807f5b9972276be7c8c5121989768549fceb8d2588"}, + {file = "pydantic-1.10.18-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:3445426da503c7e40baccefb2b2989a0c5ce6b163679dd75f55493b460f05a8f"}, + {file = "pydantic-1.10.18-cp311-cp311-win_amd64.whl", hash = "sha256:467a14ee2183bc9c902579bb2f04c3d3dac00eff52e252850509a562255b2a33"}, + {file = "pydantic-1.10.18-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:efbc8a7f9cb5fe26122acba1852d8dcd1e125e723727c59dcd244da7bdaa54f2"}, + {file = "pydantic-1.10.18-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:24a4a159d0f7a8e26bf6463b0d3d60871d6a52eac5bb6a07a7df85c806f4c048"}, + {file = "pydantic-1.10.18-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b74be007703547dc52e3c37344d130a7bfacca7df112a9e5ceeb840a9ce195c7"}, + {file = "pydantic-1.10.18-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:fcb20d4cb355195c75000a49bb4a31d75e4295200df620f454bbc6bdf60ca890"}, + {file = "pydantic-1.10.18-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:46f379b8cb8a3585e3f61bf9ae7d606c70d133943f339d38b76e041ec234953f"}, + {file = "pydantic-1.10.18-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:cbfbca662ed3729204090c4d09ee4beeecc1a7ecba5a159a94b5a4eb24e3759a"}, + {file = "pydantic-1.10.18-cp312-cp312-win_amd64.whl", hash = "sha256:c6d0a9f9eccaf7f438671a64acf654ef0d045466e63f9f68a579e2383b63f357"}, + {file = "pydantic-1.10.18-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:3d5492dbf953d7d849751917e3b2433fb26010d977aa7a0765c37425a4026ff1"}, + {file = "pydantic-1.10.18-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fe734914977eed33033b70bfc097e1baaffb589517863955430bf2e0846ac30f"}, + {file = "pydantic-1.10.18-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:15fdbe568beaca9aacfccd5ceadfb5f1a235087a127e8af5e48df9d8a45ae85c"}, + {file = "pydantic-1.10.18-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:c3e742f62198c9eb9201781fbebe64533a3bbf6a76a91b8d438d62b813079dbc"}, + {file = "pydantic-1.10.18-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:19a3bd00b9dafc2cd7250d94d5b578edf7a0bd7daf102617153ff9a8fa37871c"}, + {file = "pydantic-1.10.18-cp37-cp37m-win_amd64.whl", hash = "sha256:2ce3fcf75b2bae99aa31bd4968de0474ebe8c8258a0110903478bd83dfee4e3b"}, + {file = "pydantic-1.10.18-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:335a32d72c51a313b33fa3a9b0fe283503272ef6467910338e123f90925f0f03"}, + {file = "pydantic-1.10.18-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:34a3613c7edb8c6fa578e58e9abe3c0f5e7430e0fc34a65a415a1683b9c32d9a"}, + {file = "pydantic-1.10.18-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e9ee4e6ca1d9616797fa2e9c0bfb8815912c7d67aca96f77428e316741082a1b"}, + {file = "pydantic-1.10.18-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:23e8ec1ce4e57b4f441fc91e3c12adba023fedd06868445a5b5f1d48f0ab3682"}, + {file = "pydantic-1.10.18-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:44ae8a3e35a54d2e8fa88ed65e1b08967a9ef8c320819a969bfa09ce5528fafe"}, + {file = "pydantic-1.10.18-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:d5389eb3b48a72da28c6e061a247ab224381435256eb541e175798483368fdd3"}, + {file = "pydantic-1.10.18-cp38-cp38-win_amd64.whl", hash = "sha256:069b9c9fc645474d5ea3653788b544a9e0ccd3dca3ad8c900c4c6eac844b4620"}, + {file = "pydantic-1.10.18-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:80b982d42515632eb51f60fa1d217dfe0729f008e81a82d1544cc392e0a50ddf"}, + {file = "pydantic-1.10.18-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:aad8771ec8dbf9139b01b56f66386537c6fe4e76c8f7a47c10261b69ad25c2c9"}, + {file = "pydantic-1.10.18-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:941a2eb0a1509bd7f31e355912eb33b698eb0051730b2eaf9e70e2e1589cae1d"}, + {file = "pydantic-1.10.18-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:65f7361a09b07915a98efd17fdec23103307a54db2000bb92095457ca758d485"}, + {file = "pydantic-1.10.18-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:6951f3f47cb5ca4da536ab161ac0163cab31417d20c54c6de5ddcab8bc813c3f"}, + {file = "pydantic-1.10.18-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:7a4c5eec138a9b52c67f664c7d51d4c7234c5ad65dd8aacd919fb47445a62c86"}, + {file = "pydantic-1.10.18-cp39-cp39-win_amd64.whl", hash = "sha256:49e26c51ca854286bffc22b69787a8d4063a62bf7d83dc21d44d2ff426108518"}, + {file = "pydantic-1.10.18-py3-none-any.whl", hash = "sha256:06a189b81ffc52746ec9c8c007f16e5167c8b0a696e1a726369327e3db7b2a82"}, + {file = "pydantic-1.10.18.tar.gz", hash = "sha256:baebdff1907d1d96a139c25136a9bb7d17e118f133a76a2ef3b845e831e3403a"}, ] [package.dependencies] @@ -2004,101 +2006,114 @@ rsa = ["oauthlib[signedtoken] (>=3.0.0)"] [[package]] name = "rpds-py" -version = "0.21.0" +version = "0.20.0" description = "Python bindings to Rust's persistent data structures (rpds)" optional = false -python-versions = ">=3.9" +python-versions = ">=3.8" files = [ - {file = "rpds_py-0.21.0-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:a017f813f24b9df929674d0332a374d40d7f0162b326562daae8066b502d0590"}, - {file = "rpds_py-0.21.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:20cc1ed0bcc86d8e1a7e968cce15be45178fd16e2ff656a243145e0b439bd250"}, - {file = "rpds_py-0.21.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ad116dda078d0bc4886cb7840e19811562acdc7a8e296ea6ec37e70326c1b41c"}, - {file = "rpds_py-0.21.0-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:808f1ac7cf3b44f81c9475475ceb221f982ef548e44e024ad5f9e7060649540e"}, - {file = "rpds_py-0.21.0-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:de552f4a1916e520f2703ec474d2b4d3f86d41f353e7680b597512ffe7eac5d0"}, - {file = "rpds_py-0.21.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:efec946f331349dfc4ae9d0e034c263ddde19414fe5128580f512619abed05f1"}, - {file = "rpds_py-0.21.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b80b4690bbff51a034bfde9c9f6bf9357f0a8c61f548942b80f7b66356508bf5"}, - {file = "rpds_py-0.21.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:085ed25baac88953d4283e5b5bd094b155075bb40d07c29c4f073e10623f9f2e"}, - {file = "rpds_py-0.21.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:daa8efac2a1273eed2354397a51216ae1e198ecbce9036fba4e7610b308b6153"}, - {file = "rpds_py-0.21.0-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:95a5bad1ac8a5c77b4e658671642e4af3707f095d2b78a1fdd08af0dfb647624"}, - {file = "rpds_py-0.21.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:3e53861b29a13d5b70116ea4230b5f0f3547b2c222c5daa090eb7c9c82d7f664"}, - {file = "rpds_py-0.21.0-cp310-none-win32.whl", hash = "sha256:ea3a6ac4d74820c98fcc9da4a57847ad2cc36475a8bd9683f32ab6d47a2bd682"}, - {file = "rpds_py-0.21.0-cp310-none-win_amd64.whl", hash = "sha256:b8f107395f2f1d151181880b69a2869c69e87ec079c49c0016ab96860b6acbe5"}, - {file = "rpds_py-0.21.0-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:5555db3e618a77034954b9dc547eae94166391a98eb867905ec8fcbce1308d95"}, - {file = "rpds_py-0.21.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:97ef67d9bbc3e15584c2f3c74bcf064af36336c10d2e21a2131e123ce0f924c9"}, - {file = "rpds_py-0.21.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4ab2c2a26d2f69cdf833174f4d9d86118edc781ad9a8fa13970b527bf8236027"}, - {file = "rpds_py-0.21.0-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:4e8921a259f54bfbc755c5bbd60c82bb2339ae0324163f32868f63f0ebb873d9"}, - {file = "rpds_py-0.21.0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:8a7ff941004d74d55a47f916afc38494bd1cfd4b53c482b77c03147c91ac0ac3"}, - {file = "rpds_py-0.21.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5145282a7cd2ac16ea0dc46b82167754d5e103a05614b724457cffe614f25bd8"}, - {file = "rpds_py-0.21.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:de609a6f1b682f70bb7163da745ee815d8f230d97276db049ab447767466a09d"}, - {file = "rpds_py-0.21.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:40c91c6e34cf016fa8e6b59d75e3dbe354830777fcfd74c58b279dceb7975b75"}, - {file = "rpds_py-0.21.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:d2132377f9deef0c4db89e65e8bb28644ff75a18df5293e132a8d67748397b9f"}, - {file = "rpds_py-0.21.0-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:0a9e0759e7be10109645a9fddaaad0619d58c9bf30a3f248a2ea57a7c417173a"}, - {file = "rpds_py-0.21.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:9e20da3957bdf7824afdd4b6eeb29510e83e026473e04952dca565170cd1ecc8"}, - {file = "rpds_py-0.21.0-cp311-none-win32.whl", hash = "sha256:f71009b0d5e94c0e86533c0b27ed7cacc1239cb51c178fd239c3cfefefb0400a"}, - {file = "rpds_py-0.21.0-cp311-none-win_amd64.whl", hash = "sha256:e168afe6bf6ab7ab46c8c375606298784ecbe3ba31c0980b7dcbb9631dcba97e"}, - {file = "rpds_py-0.21.0-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:30b912c965b2aa76ba5168fd610087bad7fcde47f0a8367ee8f1876086ee6d1d"}, - {file = "rpds_py-0.21.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:ca9989d5d9b1b300bc18e1801c67b9f6d2c66b8fd9621b36072ed1df2c977f72"}, - {file = "rpds_py-0.21.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6f54e7106f0001244a5f4cf810ba8d3f9c542e2730821b16e969d6887b664266"}, - {file = "rpds_py-0.21.0-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:fed5dfefdf384d6fe975cc026886aece4f292feaf69d0eeb716cfd3c5a4dd8be"}, - {file = "rpds_py-0.21.0-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:590ef88db231c9c1eece44dcfefd7515d8bf0d986d64d0caf06a81998a9e8cab"}, - {file = "rpds_py-0.21.0-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f983e4c2f603c95dde63df633eec42955508eefd8d0f0e6d236d31a044c882d7"}, - {file = "rpds_py-0.21.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b229ce052ddf1a01c67d68166c19cb004fb3612424921b81c46e7ea7ccf7c3bf"}, - {file = "rpds_py-0.21.0-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:ebf64e281a06c904a7636781d2e973d1f0926a5b8b480ac658dc0f556e7779f4"}, - {file = "rpds_py-0.21.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:998a8080c4495e4f72132f3d66ff91f5997d799e86cec6ee05342f8f3cda7dca"}, - {file = "rpds_py-0.21.0-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:98486337f7b4f3c324ab402e83453e25bb844f44418c066623db88e4c56b7c7b"}, - {file = "rpds_py-0.21.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:a78d8b634c9df7f8d175451cfeac3810a702ccb85f98ec95797fa98b942cea11"}, - {file = "rpds_py-0.21.0-cp312-none-win32.whl", hash = "sha256:a58ce66847711c4aa2ecfcfaff04cb0327f907fead8945ffc47d9407f41ff952"}, - {file = "rpds_py-0.21.0-cp312-none-win_amd64.whl", hash = "sha256:e860f065cc4ea6f256d6f411aba4b1251255366e48e972f8a347cf88077b24fd"}, - {file = "rpds_py-0.21.0-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:ee4eafd77cc98d355a0d02f263efc0d3ae3ce4a7c24740010a8b4012bbb24937"}, - {file = "rpds_py-0.21.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:688c93b77e468d72579351a84b95f976bd7b3e84aa6686be6497045ba84be560"}, - {file = "rpds_py-0.21.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c38dbf31c57032667dd5a2f0568ccde66e868e8f78d5a0d27dcc56d70f3fcd3b"}, - {file = "rpds_py-0.21.0-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:2d6129137f43f7fa02d41542ffff4871d4aefa724a5fe38e2c31a4e0fd343fb0"}, - {file = "rpds_py-0.21.0-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:520ed8b99b0bf86a176271f6fe23024323862ac674b1ce5b02a72bfeff3fff44"}, - {file = "rpds_py-0.21.0-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:aaeb25ccfb9b9014a10eaf70904ebf3f79faaa8e60e99e19eef9f478651b9b74"}, - {file = "rpds_py-0.21.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:af04ac89c738e0f0f1b913918024c3eab6e3ace989518ea838807177d38a2e94"}, - {file = "rpds_py-0.21.0-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:b9b76e2afd585803c53c5b29e992ecd183f68285b62fe2668383a18e74abe7a3"}, - {file = "rpds_py-0.21.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:5afb5efde74c54724e1a01118c6e5c15e54e642c42a1ba588ab1f03544ac8c7a"}, - {file = "rpds_py-0.21.0-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:52c041802a6efa625ea18027a0723676a778869481d16803481ef6cc02ea8cb3"}, - {file = "rpds_py-0.21.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:ee1e4fc267b437bb89990b2f2abf6c25765b89b72dd4a11e21934df449e0c976"}, - {file = "rpds_py-0.21.0-cp313-none-win32.whl", hash = "sha256:0c025820b78817db6a76413fff6866790786c38f95ea3f3d3c93dbb73b632202"}, - {file = "rpds_py-0.21.0-cp313-none-win_amd64.whl", hash = "sha256:320c808df533695326610a1b6a0a6e98f033e49de55d7dc36a13c8a30cfa756e"}, - {file = "rpds_py-0.21.0-cp39-cp39-macosx_10_12_x86_64.whl", hash = "sha256:2c51d99c30091f72a3c5d126fad26236c3f75716b8b5e5cf8effb18889ced928"}, - {file = "rpds_py-0.21.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:cbd7504a10b0955ea287114f003b7ad62330c9e65ba012c6223dba646f6ffd05"}, - {file = "rpds_py-0.21.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6dcc4949be728ede49e6244eabd04064336012b37f5c2200e8ec8eb2988b209c"}, - {file = "rpds_py-0.21.0-cp39-cp39-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:f414da5c51bf350e4b7960644617c130140423882305f7574b6cf65a3081cecb"}, - {file = "rpds_py-0.21.0-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:9afe42102b40007f588666bc7de82451e10c6788f6f70984629db193849dced1"}, - {file = "rpds_py-0.21.0-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3b929c2bb6e29ab31f12a1117c39f7e6d6450419ab7464a4ea9b0b417174f044"}, - {file = "rpds_py-0.21.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8404b3717da03cbf773a1d275d01fec84ea007754ed380f63dfc24fb76ce4592"}, - {file = "rpds_py-0.21.0-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:e12bb09678f38b7597b8346983d2323a6482dcd59e423d9448108c1be37cac9d"}, - {file = "rpds_py-0.21.0-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:58a0e345be4b18e6b8501d3b0aa540dad90caeed814c515e5206bb2ec26736fd"}, - {file = "rpds_py-0.21.0-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:c3761f62fcfccf0864cc4665b6e7c3f0c626f0380b41b8bd1ce322103fa3ef87"}, - {file = "rpds_py-0.21.0-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:c2b2f71c6ad6c2e4fc9ed9401080badd1469fa9889657ec3abea42a3d6b2e1ed"}, - {file = "rpds_py-0.21.0-cp39-none-win32.whl", hash = "sha256:b21747f79f360e790525e6f6438c7569ddbfb1b3197b9e65043f25c3c9b489d8"}, - {file = "rpds_py-0.21.0-cp39-none-win_amd64.whl", hash = "sha256:0626238a43152918f9e72ede9a3b6ccc9e299adc8ade0d67c5e142d564c9a83d"}, - {file = "rpds_py-0.21.0-pp310-pypy310_pp73-macosx_10_12_x86_64.whl", hash = "sha256:6b4ef7725386dc0762857097f6b7266a6cdd62bfd209664da6712cb26acef035"}, - {file = "rpds_py-0.21.0-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:6bc0e697d4d79ab1aacbf20ee5f0df80359ecf55db33ff41481cf3e24f206919"}, - {file = "rpds_py-0.21.0-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:da52d62a96e61c1c444f3998c434e8b263c384f6d68aca8274d2e08d1906325c"}, - {file = "rpds_py-0.21.0-pp310-pypy310_pp73-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:98e4fe5db40db87ce1c65031463a760ec7906ab230ad2249b4572c2fc3ef1f9f"}, - {file = "rpds_py-0.21.0-pp310-pypy310_pp73-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:30bdc973f10d28e0337f71d202ff29345320f8bc49a31c90e6c257e1ccef4333"}, - {file = "rpds_py-0.21.0-pp310-pypy310_pp73-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:faa5e8496c530f9c71f2b4e1c49758b06e5f4055e17144906245c99fa6d45356"}, - {file = "rpds_py-0.21.0-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:32eb88c30b6a4f0605508023b7141d043a79b14acb3b969aa0b4f99b25bc7d4a"}, - {file = "rpds_py-0.21.0-pp310-pypy310_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:a89a8ce9e4e75aeb7fa5d8ad0f3fecdee813802592f4f46a15754dcb2fd6b061"}, - {file = "rpds_py-0.21.0-pp310-pypy310_pp73-musllinux_1_2_aarch64.whl", hash = "sha256:241e6c125568493f553c3d0fdbb38c74babf54b45cef86439d4cd97ff8feb34d"}, - {file = "rpds_py-0.21.0-pp310-pypy310_pp73-musllinux_1_2_i686.whl", hash = "sha256:3b766a9f57663396e4f34f5140b3595b233a7b146e94777b97a8413a1da1be18"}, - {file = "rpds_py-0.21.0-pp310-pypy310_pp73-musllinux_1_2_x86_64.whl", hash = "sha256:af4a644bf890f56e41e74be7d34e9511e4954894d544ec6b8efe1e21a1a8da6c"}, - {file = "rpds_py-0.21.0-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:3e30a69a706e8ea20444b98a49f386c17b26f860aa9245329bab0851ed100677"}, - {file = "rpds_py-0.21.0-pp39-pypy39_pp73-macosx_10_12_x86_64.whl", hash = "sha256:031819f906bb146561af051c7cef4ba2003d28cff07efacef59da973ff7969ba"}, - {file = "rpds_py-0.21.0-pp39-pypy39_pp73-macosx_11_0_arm64.whl", hash = "sha256:b876f2bc27ab5954e2fd88890c071bd0ed18b9c50f6ec3de3c50a5ece612f7a6"}, - {file = "rpds_py-0.21.0-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:dc5695c321e518d9f03b7ea6abb5ea3af4567766f9852ad1560f501b17588c7b"}, - {file = "rpds_py-0.21.0-pp39-pypy39_pp73-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:b4de1da871b5c0fd5537b26a6fc6814c3cc05cabe0c941db6e9044ffbb12f04a"}, - {file = "rpds_py-0.21.0-pp39-pypy39_pp73-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:878f6fea96621fda5303a2867887686d7a198d9e0f8a40be100a63f5d60c88c9"}, - {file = "rpds_py-0.21.0-pp39-pypy39_pp73-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a8eeec67590e94189f434c6d11c426892e396ae59e4801d17a93ac96b8c02a6c"}, - {file = "rpds_py-0.21.0-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1ff2eba7f6c0cb523d7e9cff0903f2fe1feff8f0b2ceb6bd71c0e20a4dcee271"}, - {file = "rpds_py-0.21.0-pp39-pypy39_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:a429b99337062877d7875e4ff1a51fe788424d522bd64a8c0a20ef3021fdb6ed"}, - {file = "rpds_py-0.21.0-pp39-pypy39_pp73-musllinux_1_2_aarch64.whl", hash = "sha256:d167e4dbbdac48bd58893c7e446684ad5d425b407f9336e04ab52e8b9194e2ed"}, - {file = "rpds_py-0.21.0-pp39-pypy39_pp73-musllinux_1_2_i686.whl", hash = "sha256:4eb2de8a147ffe0626bfdc275fc6563aa7bf4b6db59cf0d44f0ccd6ca625a24e"}, - {file = "rpds_py-0.21.0-pp39-pypy39_pp73-musllinux_1_2_x86_64.whl", hash = "sha256:e78868e98f34f34a88e23ee9ccaeeec460e4eaf6db16d51d7a9b883e5e785a5e"}, - {file = "rpds_py-0.21.0-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:4991ca61656e3160cdaca4851151fd3f4a92e9eba5c7a530ab030d6aee96ec89"}, - {file = "rpds_py-0.21.0.tar.gz", hash = "sha256:ed6378c9d66d0de903763e7706383d60c33829581f0adff47b6535f1802fa6db"}, + {file = "rpds_py-0.20.0-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:3ad0fda1635f8439cde85c700f964b23ed5fc2d28016b32b9ee5fe30da5c84e2"}, + {file = "rpds_py-0.20.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:9bb4a0d90fdb03437c109a17eade42dfbf6190408f29b2744114d11586611d6f"}, + {file = "rpds_py-0.20.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c6377e647bbfd0a0b159fe557f2c6c602c159fc752fa316572f012fc0bf67150"}, + {file = "rpds_py-0.20.0-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:eb851b7df9dda52dc1415ebee12362047ce771fc36914586b2e9fcbd7d293b3e"}, + {file = "rpds_py-0.20.0-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1e0f80b739e5a8f54837be5d5c924483996b603d5502bfff79bf33da06164ee2"}, + {file = "rpds_py-0.20.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5a8c94dad2e45324fc74dce25e1645d4d14df9a4e54a30fa0ae8bad9a63928e3"}, + {file = "rpds_py-0.20.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f8e604fe73ba048c06085beaf51147eaec7df856824bfe7b98657cf436623daf"}, + {file = "rpds_py-0.20.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:df3de6b7726b52966edf29663e57306b23ef775faf0ac01a3e9f4012a24a4140"}, + {file = "rpds_py-0.20.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:cf258ede5bc22a45c8e726b29835b9303c285ab46fc7c3a4cc770736b5304c9f"}, + {file = "rpds_py-0.20.0-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:55fea87029cded5df854ca7e192ec7bdb7ecd1d9a3f63d5c4eb09148acf4a7ce"}, + {file = "rpds_py-0.20.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:ae94bd0b2f02c28e199e9bc51485d0c5601f58780636185660f86bf80c89af94"}, + {file = "rpds_py-0.20.0-cp310-none-win32.whl", hash = "sha256:28527c685f237c05445efec62426d285e47a58fb05ba0090a4340b73ecda6dee"}, + {file = "rpds_py-0.20.0-cp310-none-win_amd64.whl", hash = "sha256:238a2d5b1cad28cdc6ed15faf93a998336eb041c4e440dd7f902528b8891b399"}, + {file = "rpds_py-0.20.0-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:ac2f4f7a98934c2ed6505aead07b979e6f999389f16b714448fb39bbaa86a489"}, + {file = "rpds_py-0.20.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:220002c1b846db9afd83371d08d239fdc865e8f8c5795bbaec20916a76db3318"}, + {file = "rpds_py-0.20.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8d7919548df3f25374a1f5d01fbcd38dacab338ef5f33e044744b5c36729c8db"}, + {file = "rpds_py-0.20.0-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:758406267907b3781beee0f0edfe4a179fbd97c0be2e9b1154d7f0a1279cf8e5"}, + {file = "rpds_py-0.20.0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3d61339e9f84a3f0767b1995adfb171a0d00a1185192718a17af6e124728e0f5"}, + {file = "rpds_py-0.20.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:1259c7b3705ac0a0bd38197565a5d603218591d3f6cee6e614e380b6ba61c6f6"}, + {file = "rpds_py-0.20.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5c1dc0f53856b9cc9a0ccca0a7cc61d3d20a7088201c0937f3f4048c1718a209"}, + {file = "rpds_py-0.20.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:7e60cb630f674a31f0368ed32b2a6b4331b8350d67de53c0359992444b116dd3"}, + {file = "rpds_py-0.20.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:dbe982f38565bb50cb7fb061ebf762c2f254ca3d8c20d4006878766e84266272"}, + {file = "rpds_py-0.20.0-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:514b3293b64187172bc77c8fb0cdae26981618021053b30d8371c3a902d4d5ad"}, + {file = "rpds_py-0.20.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:d0a26ffe9d4dd35e4dfdd1e71f46401cff0181c75ac174711ccff0459135fa58"}, + {file = "rpds_py-0.20.0-cp311-none-win32.whl", hash = "sha256:89c19a494bf3ad08c1da49445cc5d13d8fefc265f48ee7e7556839acdacf69d0"}, + {file = "rpds_py-0.20.0-cp311-none-win_amd64.whl", hash = "sha256:c638144ce971df84650d3ed0096e2ae7af8e62ecbbb7b201c8935c370df00a2c"}, + {file = "rpds_py-0.20.0-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:a84ab91cbe7aab97f7446652d0ed37d35b68a465aeef8fc41932a9d7eee2c1a6"}, + {file = "rpds_py-0.20.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:56e27147a5a4c2c21633ff8475d185734c0e4befd1c989b5b95a5d0db699b21b"}, + {file = "rpds_py-0.20.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2580b0c34583b85efec8c5c5ec9edf2dfe817330cc882ee972ae650e7b5ef739"}, + {file = "rpds_py-0.20.0-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:b80d4a7900cf6b66bb9cee5c352b2d708e29e5a37fe9bf784fa97fc11504bf6c"}, + {file = "rpds_py-0.20.0-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:50eccbf054e62a7b2209b28dc7a22d6254860209d6753e6b78cfaeb0075d7bee"}, + {file = "rpds_py-0.20.0-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:49a8063ea4296b3a7e81a5dfb8f7b2d73f0b1c20c2af401fb0cdf22e14711a96"}, + {file = "rpds_py-0.20.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ea438162a9fcbee3ecf36c23e6c68237479f89f962f82dae83dc15feeceb37e4"}, + {file = "rpds_py-0.20.0-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:18d7585c463087bddcfa74c2ba267339f14f2515158ac4db30b1f9cbdb62c8ef"}, + {file = "rpds_py-0.20.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:d4c7d1a051eeb39f5c9547e82ea27cbcc28338482242e3e0b7768033cb083821"}, + {file = "rpds_py-0.20.0-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:e4df1e3b3bec320790f699890d41c59d250f6beda159ea3c44c3f5bac1976940"}, + {file = "rpds_py-0.20.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:2cf126d33a91ee6eedc7f3197b53e87a2acdac63602c0f03a02dd69e4b138174"}, + {file = "rpds_py-0.20.0-cp312-none-win32.whl", hash = "sha256:8bc7690f7caee50b04a79bf017a8d020c1f48c2a1077ffe172abec59870f1139"}, + {file = "rpds_py-0.20.0-cp312-none-win_amd64.whl", hash = "sha256:0e13e6952ef264c40587d510ad676a988df19adea20444c2b295e536457bc585"}, + {file = "rpds_py-0.20.0-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:aa9a0521aeca7d4941499a73ad7d4f8ffa3d1affc50b9ea11d992cd7eff18a29"}, + {file = "rpds_py-0.20.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:4a1f1d51eccb7e6c32ae89243cb352389228ea62f89cd80823ea7dd1b98e0b91"}, + {file = "rpds_py-0.20.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8a86a9b96070674fc88b6f9f71a97d2c1d3e5165574615d1f9168ecba4cecb24"}, + {file = "rpds_py-0.20.0-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:6c8ef2ebf76df43f5750b46851ed1cdf8f109d7787ca40035fe19fbdc1acc5a7"}, + {file = "rpds_py-0.20.0-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b74b25f024b421d5859d156750ea9a65651793d51b76a2e9238c05c9d5f203a9"}, + {file = "rpds_py-0.20.0-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:57eb94a8c16ab08fef6404301c38318e2c5a32216bf5de453e2714c964c125c8"}, + {file = "rpds_py-0.20.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e1940dae14e715e2e02dfd5b0f64a52e8374a517a1e531ad9412319dc3ac7879"}, + {file = "rpds_py-0.20.0-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:d20277fd62e1b992a50c43f13fbe13277a31f8c9f70d59759c88f644d66c619f"}, + {file = "rpds_py-0.20.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:06db23d43f26478303e954c34c75182356ca9aa7797d22c5345b16871ab9c45c"}, + {file = "rpds_py-0.20.0-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:b2a5db5397d82fa847e4c624b0c98fe59d2d9b7cf0ce6de09e4d2e80f8f5b3f2"}, + {file = "rpds_py-0.20.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:5a35df9f5548fd79cb2f52d27182108c3e6641a4feb0f39067911bf2adaa3e57"}, + {file = "rpds_py-0.20.0-cp313-none-win32.whl", hash = "sha256:fd2d84f40633bc475ef2d5490b9c19543fbf18596dcb1b291e3a12ea5d722f7a"}, + {file = "rpds_py-0.20.0-cp313-none-win_amd64.whl", hash = "sha256:9bc2d153989e3216b0559251b0c260cfd168ec78b1fac33dd485750a228db5a2"}, + {file = "rpds_py-0.20.0-cp38-cp38-macosx_10_12_x86_64.whl", hash = "sha256:f2fbf7db2012d4876fb0d66b5b9ba6591197b0f165db8d99371d976546472a24"}, + {file = "rpds_py-0.20.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:1e5f3cd7397c8f86c8cc72d5a791071431c108edd79872cdd96e00abd8497d29"}, + {file = "rpds_py-0.20.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ce9845054c13696f7af7f2b353e6b4f676dab1b4b215d7fe5e05c6f8bb06f965"}, + {file = "rpds_py-0.20.0-cp38-cp38-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:c3e130fd0ec56cb76eb49ef52faead8ff09d13f4527e9b0c400307ff72b408e1"}, + {file = "rpds_py-0.20.0-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:4b16aa0107ecb512b568244ef461f27697164d9a68d8b35090e9b0c1c8b27752"}, + {file = "rpds_py-0.20.0-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:aa7f429242aae2947246587d2964fad750b79e8c233a2367f71b554e9447949c"}, + {file = "rpds_py-0.20.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:af0fc424a5842a11e28956e69395fbbeab2c97c42253169d87e90aac2886d751"}, + {file = "rpds_py-0.20.0-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:b8c00a3b1e70c1d3891f0db1b05292747f0dbcfb49c43f9244d04c70fbc40eb8"}, + {file = "rpds_py-0.20.0-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:40ce74fc86ee4645d0a225498d091d8bc61f39b709ebef8204cb8b5a464d3c0e"}, + {file = "rpds_py-0.20.0-cp38-cp38-musllinux_1_2_i686.whl", hash = "sha256:4fe84294c7019456e56d93e8ababdad5a329cd25975be749c3f5f558abb48253"}, + {file = "rpds_py-0.20.0-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:338ca4539aad4ce70a656e5187a3a31c5204f261aef9f6ab50e50bcdffaf050a"}, + {file = "rpds_py-0.20.0-cp38-none-win32.whl", hash = "sha256:54b43a2b07db18314669092bb2de584524d1ef414588780261e31e85846c26a5"}, + {file = "rpds_py-0.20.0-cp38-none-win_amd64.whl", hash = "sha256:a1862d2d7ce1674cffa6d186d53ca95c6e17ed2b06b3f4c476173565c862d232"}, + {file = "rpds_py-0.20.0-cp39-cp39-macosx_10_12_x86_64.whl", hash = "sha256:3fde368e9140312b6e8b6c09fb9f8c8c2f00999d1823403ae90cc00480221b22"}, + {file = "rpds_py-0.20.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:9824fb430c9cf9af743cf7aaf6707bf14323fb51ee74425c380f4c846ea70789"}, + {file = "rpds_py-0.20.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:11ef6ce74616342888b69878d45e9f779b95d4bd48b382a229fe624a409b72c5"}, + {file = "rpds_py-0.20.0-cp39-cp39-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:c52d3f2f82b763a24ef52f5d24358553e8403ce05f893b5347098014f2d9eff2"}, + {file = "rpds_py-0.20.0-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:9d35cef91e59ebbeaa45214861874bc6f19eb35de96db73e467a8358d701a96c"}, + {file = "rpds_py-0.20.0-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d72278a30111e5b5525c1dd96120d9e958464316f55adb030433ea905866f4de"}, + {file = "rpds_py-0.20.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b4c29cbbba378759ac5786730d1c3cb4ec6f8ababf5c42a9ce303dc4b3d08cda"}, + {file = "rpds_py-0.20.0-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:6632f2d04f15d1bd6fe0eedd3b86d9061b836ddca4c03d5cf5c7e9e6b7c14580"}, + {file = "rpds_py-0.20.0-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:d0b67d87bb45ed1cd020e8fbf2307d449b68abc45402fe1a4ac9e46c3c8b192b"}, + {file = "rpds_py-0.20.0-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:ec31a99ca63bf3cd7f1a5ac9fe95c5e2d060d3c768a09bc1d16e235840861420"}, + {file = "rpds_py-0.20.0-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:22e6c9976e38f4d8c4a63bd8a8edac5307dffd3ee7e6026d97f3cc3a2dc02a0b"}, + {file = "rpds_py-0.20.0-cp39-none-win32.whl", hash = "sha256:569b3ea770c2717b730b61998b6c54996adee3cef69fc28d444f3e7920313cf7"}, + {file = "rpds_py-0.20.0-cp39-none-win_amd64.whl", hash = "sha256:e6900ecdd50ce0facf703f7a00df12374b74bbc8ad9fe0f6559947fb20f82364"}, + {file = "rpds_py-0.20.0-pp310-pypy310_pp73-macosx_10_12_x86_64.whl", hash = "sha256:617c7357272c67696fd052811e352ac54ed1d9b49ab370261a80d3b6ce385045"}, + {file = "rpds_py-0.20.0-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:9426133526f69fcaba6e42146b4e12d6bc6c839b8b555097020e2b78ce908dcc"}, + {file = "rpds_py-0.20.0-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:deb62214c42a261cb3eb04d474f7155279c1a8a8c30ac89b7dcb1721d92c3c02"}, + {file = "rpds_py-0.20.0-pp310-pypy310_pp73-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:fcaeb7b57f1a1e071ebd748984359fef83ecb026325b9d4ca847c95bc7311c92"}, + {file = "rpds_py-0.20.0-pp310-pypy310_pp73-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d454b8749b4bd70dd0a79f428731ee263fa6995f83ccb8bada706e8d1d3ff89d"}, + {file = "rpds_py-0.20.0-pp310-pypy310_pp73-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d807dc2051abe041b6649681dce568f8e10668e3c1c6543ebae58f2d7e617855"}, + {file = "rpds_py-0.20.0-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c3c20f0ddeb6e29126d45f89206b8291352b8c5b44384e78a6499d68b52ae511"}, + {file = "rpds_py-0.20.0-pp310-pypy310_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:b7f19250ceef892adf27f0399b9e5afad019288e9be756d6919cb58892129f51"}, + {file = "rpds_py-0.20.0-pp310-pypy310_pp73-musllinux_1_2_aarch64.whl", hash = "sha256:4f1ed4749a08379555cebf4650453f14452eaa9c43d0a95c49db50c18b7da075"}, + {file = "rpds_py-0.20.0-pp310-pypy310_pp73-musllinux_1_2_i686.whl", hash = "sha256:dcedf0b42bcb4cfff4101d7771a10532415a6106062f005ab97d1d0ab5681c60"}, + {file = "rpds_py-0.20.0-pp310-pypy310_pp73-musllinux_1_2_x86_64.whl", hash = "sha256:39ed0d010457a78f54090fafb5d108501b5aa5604cc22408fc1c0c77eac14344"}, + {file = "rpds_py-0.20.0-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:bb273176be34a746bdac0b0d7e4e2c467323d13640b736c4c477881a3220a989"}, + {file = "rpds_py-0.20.0-pp39-pypy39_pp73-macosx_10_12_x86_64.whl", hash = "sha256:f918a1a130a6dfe1d7fe0f105064141342e7dd1611f2e6a21cd2f5c8cb1cfb3e"}, + {file = "rpds_py-0.20.0-pp39-pypy39_pp73-macosx_11_0_arm64.whl", hash = "sha256:f60012a73aa396be721558caa3a6fd49b3dd0033d1675c6d59c4502e870fcf0c"}, + {file = "rpds_py-0.20.0-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3d2b1ad682a3dfda2a4e8ad8572f3100f95fad98cb99faf37ff0ddfe9cbf9d03"}, + {file = "rpds_py-0.20.0-pp39-pypy39_pp73-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:614fdafe9f5f19c63ea02817fa4861c606a59a604a77c8cdef5aa01d28b97921"}, + {file = "rpds_py-0.20.0-pp39-pypy39_pp73-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:fa518bcd7600c584bf42e6617ee8132869e877db2f76bcdc281ec6a4113a53ab"}, + {file = "rpds_py-0.20.0-pp39-pypy39_pp73-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f0475242f447cc6cb8a9dd486d68b2ef7fbee84427124c232bff5f63b1fe11e5"}, + {file = "rpds_py-0.20.0-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f90a4cd061914a60bd51c68bcb4357086991bd0bb93d8aa66a6da7701370708f"}, + {file = "rpds_py-0.20.0-pp39-pypy39_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:def7400461c3a3f26e49078302e1c1b38f6752342c77e3cf72ce91ca69fb1bc1"}, + {file = "rpds_py-0.20.0-pp39-pypy39_pp73-musllinux_1_2_aarch64.whl", hash = "sha256:65794e4048ee837494aea3c21a28ad5fc080994dfba5b036cf84de37f7ad5074"}, + {file = "rpds_py-0.20.0-pp39-pypy39_pp73-musllinux_1_2_i686.whl", hash = "sha256:faefcc78f53a88f3076b7f8be0a8f8d35133a3ecf7f3770895c25f8813460f08"}, + {file = "rpds_py-0.20.0-pp39-pypy39_pp73-musllinux_1_2_x86_64.whl", hash = "sha256:5b4f105deeffa28bbcdff6c49b34e74903139afa690e35d2d9e3c2c2fba18cec"}, + {file = "rpds_py-0.20.0-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:fdfc3a892927458d98f3d55428ae46b921d1f7543b89382fdb483f5640daaec8"}, + {file = "rpds_py-0.20.0.tar.gz", hash = "sha256:d72a210824facfdaf8768cf2d7ca25a042c30320b3020de2fa04640920d4e121"}, ] [[package]] @@ -2117,29 +2132,29 @@ pyasn1 = ">=0.1.3" [[package]] name = "ruff" -version = "0.7.4" +version = "0.7.1" description = "An extremely fast Python linter and code formatter, written in Rust." optional = false python-versions = ">=3.7" files = [ - {file = "ruff-0.7.4-py3-none-linux_armv6l.whl", hash = "sha256:a4919925e7684a3f18e18243cd6bea7cfb8e968a6eaa8437971f681b7ec51478"}, - {file = "ruff-0.7.4-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:cfb365c135b830778dda8c04fb7d4280ed0b984e1aec27f574445231e20d6c63"}, - {file = "ruff-0.7.4-py3-none-macosx_11_0_arm64.whl", hash = "sha256:63a569b36bc66fbadec5beaa539dd81e0527cb258b94e29e0531ce41bacc1f20"}, - {file = "ruff-0.7.4-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0d06218747d361d06fd2fdac734e7fa92df36df93035db3dc2ad7aa9852cb109"}, - {file = "ruff-0.7.4-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:e0cea28d0944f74ebc33e9f934238f15c758841f9f5edd180b5315c203293452"}, - {file = "ruff-0.7.4-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:80094ecd4793c68b2571b128f91754d60f692d64bc0d7272ec9197fdd09bf9ea"}, - {file = "ruff-0.7.4-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:997512325c6620d1c4c2b15db49ef59543ef9cd0f4aa8065ec2ae5103cedc7e7"}, - {file = "ruff-0.7.4-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:00b4cf3a6b5fad6d1a66e7574d78956bbd09abfd6c8a997798f01f5da3d46a05"}, - {file = "ruff-0.7.4-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:7dbdc7d8274e1422722933d1edddfdc65b4336abf0b16dfcb9dedd6e6a517d06"}, - {file = "ruff-0.7.4-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0e92dfb5f00eaedb1501b2f906ccabfd67b2355bdf117fea9719fc99ac2145bc"}, - {file = "ruff-0.7.4-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:3bd726099f277d735dc38900b6a8d6cf070f80828877941983a57bca1cd92172"}, - {file = "ruff-0.7.4-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:2e32829c429dd081ee5ba39aef436603e5b22335c3d3fff013cd585806a6486a"}, - {file = "ruff-0.7.4-py3-none-musllinux_1_2_i686.whl", hash = "sha256:662a63b4971807623f6f90c1fb664613f67cc182dc4d991471c23c541fee62dd"}, - {file = "ruff-0.7.4-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:876f5e09eaae3eb76814c1d3b68879891d6fde4824c015d48e7a7da4cf066a3a"}, - {file = "ruff-0.7.4-py3-none-win32.whl", hash = "sha256:75c53f54904be42dd52a548728a5b572344b50d9b2873d13a3f8c5e3b91f5cac"}, - {file = "ruff-0.7.4-py3-none-win_amd64.whl", hash = "sha256:745775c7b39f914238ed1f1b0bebed0b9155a17cd8bc0b08d3c87e4703b990d6"}, - {file = "ruff-0.7.4-py3-none-win_arm64.whl", hash = "sha256:11bff065102c3ae9d3ea4dc9ecdfe5a5171349cdd0787c1fc64761212fc9cf1f"}, - {file = "ruff-0.7.4.tar.gz", hash = "sha256:cd12e35031f5af6b9b93715d8c4f40360070b2041f81273d0527683d5708fce2"}, + {file = "ruff-0.7.1-py3-none-linux_armv6l.whl", hash = "sha256:cb1bc5ed9403daa7da05475d615739cc0212e861b7306f314379d958592aaa89"}, + {file = "ruff-0.7.1-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:27c1c52a8d199a257ff1e5582d078eab7145129aa02721815ca8fa4f9612dc35"}, + {file = "ruff-0.7.1-py3-none-macosx_11_0_arm64.whl", hash = "sha256:588a34e1ef2ea55b4ddfec26bbe76bc866e92523d8c6cdec5e8aceefeff02d99"}, + {file = "ruff-0.7.1-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:94fc32f9cdf72dc75c451e5f072758b118ab8100727168a3df58502b43a599ca"}, + {file = "ruff-0.7.1-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:985818742b833bffa543a84d1cc11b5e6871de1b4e0ac3060a59a2bae3969250"}, + {file = "ruff-0.7.1-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:32f1e8a192e261366c702c5fb2ece9f68d26625f198a25c408861c16dc2dea9c"}, + {file = "ruff-0.7.1-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:699085bf05819588551b11751eff33e9ca58b1b86a6843e1b082a7de40da1565"}, + {file = "ruff-0.7.1-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:344cc2b0814047dc8c3a8ff2cd1f3d808bb23c6658db830d25147339d9bf9ea7"}, + {file = "ruff-0.7.1-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:4316bbf69d5a859cc937890c7ac7a6551252b6a01b1d2c97e8fc96e45a7c8b4a"}, + {file = "ruff-0.7.1-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:79d3af9dca4c56043e738a4d6dd1e9444b6d6c10598ac52d146e331eb155a8ad"}, + {file = "ruff-0.7.1-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:c5c121b46abde94a505175524e51891f829414e093cd8326d6e741ecfc0a9112"}, + {file = "ruff-0.7.1-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:8422104078324ea250886954e48f1373a8fe7de59283d747c3a7eca050b4e378"}, + {file = "ruff-0.7.1-py3-none-musllinux_1_2_i686.whl", hash = "sha256:56aad830af8a9db644e80098fe4984a948e2b6fc2e73891538f43bbe478461b8"}, + {file = "ruff-0.7.1-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:658304f02f68d3a83c998ad8bf91f9b4f53e93e5412b8f2388359d55869727fd"}, + {file = "ruff-0.7.1-py3-none-win32.whl", hash = "sha256:b517a2011333eb7ce2d402652ecaa0ac1a30c114fbbd55c6b8ee466a7f600ee9"}, + {file = "ruff-0.7.1-py3-none-win_amd64.whl", hash = "sha256:f38c41fcde1728736b4eb2b18850f6d1e3eedd9678c914dede554a70d5241307"}, + {file = "ruff-0.7.1-py3-none-win_arm64.whl", hash = "sha256:19aa200ec824c0f36d0c9114c8ec0087082021732979a359d6f3c390a6ff2a37"}, + {file = "ruff-0.7.1.tar.gz", hash = "sha256:9d8a41d4aa2dad1575adb98a82870cf5db5f76b2938cf2206c22c940034a36f4"}, ] [[package]] @@ -2217,13 +2232,13 @@ test = ["pytest", "tornado (>=4.5)", "typeguard"] [[package]] name = "tomli" -version = "2.1.0" +version = "2.0.2" description = "A lil' TOML parser" optional = false python-versions = ">=3.8" files = [ - {file = "tomli-2.1.0-py3-none-any.whl", hash = "sha256:a5c57c3d1c56f5ccdf89f6523458f60ef716e210fc47c4cfb188c5ba473e0391"}, - {file = "tomli-2.1.0.tar.gz", hash = "sha256:3f646cae2aec94e17d04973e4249548320197cfabdf130015d023de4b74d8ab8"}, + {file = "tomli-2.0.2-py3-none-any.whl", hash = "sha256:2ebe24485c53d303f690b0ec092806a085f07af5a5aa1464f3931eec36caaa38"}, + {file = "tomli-2.0.2.tar.gz", hash = "sha256:d46d457a85337051c36524bc5349dd91b1877838e2979ac5ced3e710ed8a60ed"}, ] [[package]] @@ -2324,80 +2339,97 @@ test = ["websockets"] [[package]] name = "websockets" -version = "14.1" +version = "13.1" description = "An implementation of the WebSocket Protocol (RFC 6455 & 7692)" optional = false -python-versions = ">=3.9" +python-versions = ">=3.8" files = [ - {file = "websockets-14.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:a0adf84bc2e7c86e8a202537b4fd50e6f7f0e4a6b6bf64d7ccb96c4cd3330b29"}, - {file = "websockets-14.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:90b5d9dfbb6d07a84ed3e696012610b6da074d97453bd01e0e30744b472c8179"}, - {file = "websockets-14.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:2177ee3901075167f01c5e335a6685e71b162a54a89a56001f1c3e9e3d2ad250"}, - {file = "websockets-14.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3f14a96a0034a27f9d47fd9788913924c89612225878f8078bb9d55f859272b0"}, - {file = "websockets-14.1-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1f874ba705deea77bcf64a9da42c1f5fc2466d8f14daf410bc7d4ceae0a9fcb0"}, - {file = "websockets-14.1-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9607b9a442392e690a57909c362811184ea429585a71061cd5d3c2b98065c199"}, - {file = "websockets-14.1-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:bea45f19b7ca000380fbd4e02552be86343080120d074b87f25593ce1700ad58"}, - {file = "websockets-14.1-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:219c8187b3ceeadbf2afcf0f25a4918d02da7b944d703b97d12fb01510869078"}, - {file = "websockets-14.1-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:ad2ab2547761d79926effe63de21479dfaf29834c50f98c4bf5b5480b5838434"}, - {file = "websockets-14.1-cp310-cp310-win32.whl", hash = "sha256:1288369a6a84e81b90da5dbed48610cd7e5d60af62df9851ed1d1d23a9069f10"}, - {file = "websockets-14.1-cp310-cp310-win_amd64.whl", hash = "sha256:e0744623852f1497d825a49a99bfbec9bea4f3f946df6eb9d8a2f0c37a2fec2e"}, - {file = "websockets-14.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:449d77d636f8d9c17952628cc7e3b8faf6e92a17ec581ec0c0256300717e1512"}, - {file = "websockets-14.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:a35f704be14768cea9790d921c2c1cc4fc52700410b1c10948511039be824aac"}, - {file = "websockets-14.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:b1f3628a0510bd58968c0f60447e7a692933589b791a6b572fcef374053ca280"}, - {file = "websockets-14.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3c3deac3748ec73ef24fc7be0b68220d14d47d6647d2f85b2771cb35ea847aa1"}, - {file = "websockets-14.1-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7048eb4415d46368ef29d32133134c513f507fff7d953c18c91104738a68c3b3"}, - {file = "websockets-14.1-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f6cf0ad281c979306a6a34242b371e90e891bce504509fb6bb5246bbbf31e7b6"}, - {file = "websockets-14.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:cc1fc87428c1d18b643479caa7b15db7d544652e5bf610513d4a3478dbe823d0"}, - {file = "websockets-14.1-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:f95ba34d71e2fa0c5d225bde3b3bdb152e957150100e75c86bc7f3964c450d89"}, - {file = "websockets-14.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:9481a6de29105d73cf4515f2bef8eb71e17ac184c19d0b9918a3701c6c9c4f23"}, - {file = "websockets-14.1-cp311-cp311-win32.whl", hash = "sha256:368a05465f49c5949e27afd6fbe0a77ce53082185bbb2ac096a3a8afaf4de52e"}, - {file = "websockets-14.1-cp311-cp311-win_amd64.whl", hash = "sha256:6d24fc337fc055c9e83414c94e1ee0dee902a486d19d2a7f0929e49d7d604b09"}, - {file = "websockets-14.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:ed907449fe5e021933e46a3e65d651f641975a768d0649fee59f10c2985529ed"}, - {file = "websockets-14.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:87e31011b5c14a33b29f17eb48932e63e1dcd3fa31d72209848652310d3d1f0d"}, - {file = "websockets-14.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:bc6ccf7d54c02ae47a48ddf9414c54d48af9c01076a2e1023e3b486b6e72c707"}, - {file = "websockets-14.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9777564c0a72a1d457f0848977a1cbe15cfa75fa2f67ce267441e465717dcf1a"}, - {file = "websockets-14.1-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a655bde548ca98f55b43711b0ceefd2a88a71af6350b0c168aa77562104f3f45"}, - {file = "websockets-14.1-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a3dfff83ca578cada2d19e665e9c8368e1598d4e787422a460ec70e531dbdd58"}, - {file = "websockets-14.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:6a6c9bcf7cdc0fd41cc7b7944447982e8acfd9f0d560ea6d6845428ed0562058"}, - {file = "websockets-14.1-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:4b6caec8576e760f2c7dd878ba817653144d5f369200b6ddf9771d64385b84d4"}, - {file = "websockets-14.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:eb6d38971c800ff02e4a6afd791bbe3b923a9a57ca9aeab7314c21c84bf9ff05"}, - {file = "websockets-14.1-cp312-cp312-win32.whl", hash = "sha256:1d045cbe1358d76b24d5e20e7b1878efe578d9897a25c24e6006eef788c0fdf0"}, - {file = "websockets-14.1-cp312-cp312-win_amd64.whl", hash = "sha256:90f4c7a069c733d95c308380aae314f2cb45bd8a904fb03eb36d1a4983a4993f"}, - {file = "websockets-14.1-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:3630b670d5057cd9e08b9c4dab6493670e8e762a24c2c94ef312783870736ab9"}, - {file = "websockets-14.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:36ebd71db3b89e1f7b1a5deaa341a654852c3518ea7a8ddfdf69cc66acc2db1b"}, - {file = "websockets-14.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:5b918d288958dc3fa1c5a0b9aa3256cb2b2b84c54407f4813c45d52267600cd3"}, - {file = "websockets-14.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:00fe5da3f037041da1ee0cf8e308374e236883f9842c7c465aa65098b1c9af59"}, - {file = "websockets-14.1-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:8149a0f5a72ca36720981418eeffeb5c2729ea55fa179091c81a0910a114a5d2"}, - {file = "websockets-14.1-cp313-cp313-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:77569d19a13015e840b81550922056acabc25e3f52782625bc6843cfa034e1da"}, - {file = "websockets-14.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:cf5201a04550136ef870aa60ad3d29d2a59e452a7f96b94193bee6d73b8ad9a9"}, - {file = "websockets-14.1-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:88cf9163ef674b5be5736a584c999e98daf3aabac6e536e43286eb74c126b9c7"}, - {file = "websockets-14.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:836bef7ae338a072e9d1863502026f01b14027250a4545672673057997d5c05a"}, - {file = "websockets-14.1-cp313-cp313-win32.whl", hash = "sha256:0d4290d559d68288da9f444089fd82490c8d2744309113fc26e2da6e48b65da6"}, - {file = "websockets-14.1-cp313-cp313-win_amd64.whl", hash = "sha256:8621a07991add373c3c5c2cf89e1d277e49dc82ed72c75e3afc74bd0acc446f0"}, - {file = "websockets-14.1-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:01bb2d4f0a6d04538d3c5dfd27c0643269656c28045a53439cbf1c004f90897a"}, - {file = "websockets-14.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:414ffe86f4d6f434a8c3b7913655a1a5383b617f9bf38720e7c0799fac3ab1c6"}, - {file = "websockets-14.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:8fda642151d5affdee8a430bd85496f2e2517be3a2b9d2484d633d5712b15c56"}, - {file = "websockets-14.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:cd7c11968bc3860d5c78577f0dbc535257ccec41750675d58d8dc66aa47fe52c"}, - {file = "websockets-14.1-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a032855dc7db987dff813583d04f4950d14326665d7e714d584560b140ae6b8b"}, - {file = "websockets-14.1-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b7e7ea2f782408c32d86b87a0d2c1fd8871b0399dd762364c731d86c86069a78"}, - {file = "websockets-14.1-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:39450e6215f7d9f6f7bc2a6da21d79374729f5d052333da4d5825af8a97e6735"}, - {file = "websockets-14.1-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:ceada5be22fa5a5a4cdeec74e761c2ee7db287208f54c718f2df4b7e200b8d4a"}, - {file = "websockets-14.1-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:3fc753451d471cff90b8f467a1fc0ae64031cf2d81b7b34e1811b7e2691bc4bc"}, - {file = "websockets-14.1-cp39-cp39-win32.whl", hash = "sha256:14839f54786987ccd9d03ed7f334baec0f02272e7ec4f6e9d427ff584aeea8b4"}, - {file = "websockets-14.1-cp39-cp39-win_amd64.whl", hash = "sha256:d9fd19ecc3a4d5ae82ddbfb30962cf6d874ff943e56e0c81f5169be2fda62979"}, - {file = "websockets-14.1-pp310-pypy310_pp73-macosx_10_15_x86_64.whl", hash = "sha256:e5dc25a9dbd1a7f61eca4b7cb04e74ae4b963d658f9e4f9aad9cd00b688692c8"}, - {file = "websockets-14.1-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:04a97aca96ca2acedf0d1f332c861c5a4486fdcba7bcef35873820f940c4231e"}, - {file = "websockets-14.1-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:df174ece723b228d3e8734a6f2a6febbd413ddec39b3dc592f5a4aa0aff28098"}, - {file = "websockets-14.1-pp310-pypy310_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:034feb9f4286476f273b9a245fb15f02c34d9586a5bc936aff108c3ba1b21beb"}, - {file = "websockets-14.1-pp310-pypy310_pp73-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:660c308dabd2b380807ab64b62985eaccf923a78ebc572bd485375b9ca2b7dc7"}, - {file = "websockets-14.1-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:5a42d3ecbb2db5080fc578314439b1d79eef71d323dc661aa616fb492436af5d"}, - {file = "websockets-14.1-pp39-pypy39_pp73-macosx_10_15_x86_64.whl", hash = "sha256:ddaa4a390af911da6f680be8be4ff5aaf31c4c834c1a9147bc21cbcbca2d4370"}, - {file = "websockets-14.1-pp39-pypy39_pp73-macosx_11_0_arm64.whl", hash = "sha256:a4c805c6034206143fbabd2d259ec5e757f8b29d0a2f0bf3d2fe5d1f60147a4a"}, - {file = "websockets-14.1-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:205f672a6c2c671a86d33f6d47c9b35781a998728d2c7c2a3e1cf3333fcb62b7"}, - {file = "websockets-14.1-pp39-pypy39_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:5ef440054124728cc49b01c33469de06755e5a7a4e83ef61934ad95fc327fbb0"}, - {file = "websockets-14.1-pp39-pypy39_pp73-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e7591d6f440af7f73c4bd9404f3772bfee064e639d2b6cc8c94076e71b2471c1"}, - {file = "websockets-14.1-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:25225cc79cfebc95ba1d24cd3ab86aaa35bcd315d12fa4358939bd55e9bd74a5"}, - {file = "websockets-14.1-py3-none-any.whl", hash = "sha256:4d4fc827a20abe6d544a119896f6b78ee13fe81cbfef416f3f2ddf09a03f0e2e"}, - {file = "websockets-14.1.tar.gz", hash = "sha256:398b10c77d471c0aab20a845e7a60076b6390bfdaac7a6d2edb0d2c59d75e8d8"}, + {file = "websockets-13.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:f48c749857f8fb598fb890a75f540e3221d0976ed0bf879cf3c7eef34151acee"}, + {file = "websockets-13.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:c7e72ce6bda6fb9409cc1e8164dd41d7c91466fb599eb047cfda72fe758a34a7"}, + {file = "websockets-13.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:f779498eeec470295a2b1a5d97aa1bc9814ecd25e1eb637bd9d1c73a327387f6"}, + {file = "websockets-13.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4676df3fe46956fbb0437d8800cd5f2b6d41143b6e7e842e60554398432cf29b"}, + {file = "websockets-13.1-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a7affedeb43a70351bb811dadf49493c9cfd1ed94c9c70095fd177e9cc1541fa"}, + {file = "websockets-13.1-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1971e62d2caa443e57588e1d82d15f663b29ff9dfe7446d9964a4b6f12c1e700"}, + {file = "websockets-13.1-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:5f2e75431f8dc4a47f31565a6e1355fb4f2ecaa99d6b89737527ea917066e26c"}, + {file = "websockets-13.1-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:58cf7e75dbf7e566088b07e36ea2e3e2bd5676e22216e4cad108d4df4a7402a0"}, + {file = "websockets-13.1-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:c90d6dec6be2c7d03378a574de87af9b1efea77d0c52a8301dd831ece938452f"}, + {file = "websockets-13.1-cp310-cp310-win32.whl", hash = "sha256:730f42125ccb14602f455155084f978bd9e8e57e89b569b4d7f0f0c17a448ffe"}, + {file = "websockets-13.1-cp310-cp310-win_amd64.whl", hash = "sha256:5993260f483d05a9737073be197371940c01b257cc45ae3f1d5d7adb371b266a"}, + {file = "websockets-13.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:61fc0dfcda609cda0fc9fe7977694c0c59cf9d749fbb17f4e9483929e3c48a19"}, + {file = "websockets-13.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:ceec59f59d092c5007e815def4ebb80c2de330e9588e101cf8bd94c143ec78a5"}, + {file = "websockets-13.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:c1dca61c6db1166c48b95198c0b7d9c990b30c756fc2923cc66f68d17dc558fd"}, + {file = "websockets-13.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:308e20f22c2c77f3f39caca508e765f8725020b84aa963474e18c59accbf4c02"}, + {file = "websockets-13.1-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:62d516c325e6540e8a57b94abefc3459d7dab8ce52ac75c96cad5549e187e3a7"}, + {file = "websockets-13.1-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:87c6e35319b46b99e168eb98472d6c7d8634ee37750d7693656dc766395df096"}, + {file = "websockets-13.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:5f9fee94ebafbc3117c30be1844ed01a3b177bb6e39088bc6b2fa1dc15572084"}, + {file = "websockets-13.1-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:7c1e90228c2f5cdde263253fa5db63e6653f1c00e7ec64108065a0b9713fa1b3"}, + {file = "websockets-13.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:6548f29b0e401eea2b967b2fdc1c7c7b5ebb3eeb470ed23a54cd45ef078a0db9"}, + {file = "websockets-13.1-cp311-cp311-win32.whl", hash = "sha256:c11d4d16e133f6df8916cc5b7e3e96ee4c44c936717d684a94f48f82edb7c92f"}, + {file = "websockets-13.1-cp311-cp311-win_amd64.whl", hash = "sha256:d04f13a1d75cb2b8382bdc16ae6fa58c97337253826dfe136195b7f89f661557"}, + {file = "websockets-13.1-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:9d75baf00138f80b48f1eac72ad1535aac0b6461265a0bcad391fc5aba875cfc"}, + {file = "websockets-13.1-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:9b6f347deb3dcfbfde1c20baa21c2ac0751afaa73e64e5b693bb2b848efeaa49"}, + {file = "websockets-13.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:de58647e3f9c42f13f90ac7e5f58900c80a39019848c5547bc691693098ae1bd"}, + {file = "websockets-13.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a1b54689e38d1279a51d11e3467dd2f3a50f5f2e879012ce8f2d6943f00e83f0"}, + {file = "websockets-13.1-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:cf1781ef73c073e6b0f90af841aaf98501f975d306bbf6221683dd594ccc52b6"}, + {file = "websockets-13.1-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8d23b88b9388ed85c6faf0e74d8dec4f4d3baf3ecf20a65a47b836d56260d4b9"}, + {file = "websockets-13.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:3c78383585f47ccb0fcf186dcb8a43f5438bd7d8f47d69e0b56f71bf431a0a68"}, + {file = "websockets-13.1-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:d6d300f8ec35c24025ceb9b9019ae9040c1ab2f01cddc2bcc0b518af31c75c14"}, + {file = "websockets-13.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:a9dcaf8b0cc72a392760bb8755922c03e17a5a54e08cca58e8b74f6902b433cf"}, + {file = "websockets-13.1-cp312-cp312-win32.whl", hash = "sha256:2f85cf4f2a1ba8f602298a853cec8526c2ca42a9a4b947ec236eaedb8f2dc80c"}, + {file = "websockets-13.1-cp312-cp312-win_amd64.whl", hash = "sha256:38377f8b0cdeee97c552d20cf1865695fcd56aba155ad1b4ca8779a5b6ef4ac3"}, + {file = "websockets-13.1-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:a9ab1e71d3d2e54a0aa646ab6d4eebfaa5f416fe78dfe4da2839525dc5d765c6"}, + {file = "websockets-13.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:b9d7439d7fab4dce00570bb906875734df13d9faa4b48e261c440a5fec6d9708"}, + {file = "websockets-13.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:327b74e915cf13c5931334c61e1a41040e365d380f812513a255aa804b183418"}, + {file = "websockets-13.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:325b1ccdbf5e5725fdcb1b0e9ad4d2545056479d0eee392c291c1bf76206435a"}, + {file = "websockets-13.1-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:346bee67a65f189e0e33f520f253d5147ab76ae42493804319b5716e46dddf0f"}, + {file = "websockets-13.1-cp313-cp313-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:91a0fa841646320ec0d3accdff5b757b06e2e5c86ba32af2e0815c96c7a603c5"}, + {file = "websockets-13.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:18503d2c5f3943e93819238bf20df71982d193f73dcecd26c94514f417f6b135"}, + {file = "websockets-13.1-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:a9cd1af7e18e5221d2878378fbc287a14cd527fdd5939ed56a18df8a31136bb2"}, + {file = "websockets-13.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:70c5be9f416aa72aab7a2a76c90ae0a4fe2755c1816c153c1a2bcc3333ce4ce6"}, + {file = "websockets-13.1-cp313-cp313-win32.whl", hash = "sha256:624459daabeb310d3815b276c1adef475b3e6804abaf2d9d2c061c319f7f187d"}, + {file = "websockets-13.1-cp313-cp313-win_amd64.whl", hash = "sha256:c518e84bb59c2baae725accd355c8dc517b4a3ed8db88b4bc93c78dae2974bf2"}, + {file = "websockets-13.1-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:c7934fd0e920e70468e676fe7f1b7261c1efa0d6c037c6722278ca0228ad9d0d"}, + {file = "websockets-13.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:149e622dc48c10ccc3d2760e5f36753db9cacf3ad7bc7bbbfd7d9c819e286f23"}, + {file = "websockets-13.1-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:a569eb1b05d72f9bce2ebd28a1ce2054311b66677fcd46cf36204ad23acead8c"}, + {file = "websockets-13.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:95df24ca1e1bd93bbca51d94dd049a984609687cb2fb08a7f2c56ac84e9816ea"}, + {file = "websockets-13.1-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d8dbb1bf0c0a4ae8b40bdc9be7f644e2f3fb4e8a9aca7145bfa510d4a374eeb7"}, + {file = "websockets-13.1-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:035233b7531fb92a76beefcbf479504db8c72eb3bff41da55aecce3a0f729e54"}, + {file = "websockets-13.1-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:e4450fc83a3df53dec45922b576e91e94f5578d06436871dce3a6be38e40f5db"}, + {file = "websockets-13.1-cp38-cp38-musllinux_1_2_i686.whl", hash = "sha256:463e1c6ec853202dd3657f156123d6b4dad0c546ea2e2e38be2b3f7c5b8e7295"}, + {file = "websockets-13.1-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:6d6855bbe70119872c05107e38fbc7f96b1d8cb047d95c2c50869a46c65a8e96"}, + {file = "websockets-13.1-cp38-cp38-win32.whl", hash = "sha256:204e5107f43095012b00f1451374693267adbb832d29966a01ecc4ce1db26faf"}, + {file = "websockets-13.1-cp38-cp38-win_amd64.whl", hash = "sha256:485307243237328c022bc908b90e4457d0daa8b5cf4b3723fd3c4a8012fce4c6"}, + {file = "websockets-13.1-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:9b37c184f8b976f0c0a231a5f3d6efe10807d41ccbe4488df8c74174805eea7d"}, + {file = "websockets-13.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:163e7277e1a0bd9fb3c8842a71661ad19c6aa7bb3d6678dc7f89b17fbcc4aeb7"}, + {file = "websockets-13.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:4b889dbd1342820cc210ba44307cf75ae5f2f96226c0038094455a96e64fb07a"}, + {file = "websockets-13.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:586a356928692c1fed0eca68b4d1c2cbbd1ca2acf2ac7e7ebd3b9052582deefa"}, + {file = "websockets-13.1-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7bd6abf1e070a6b72bfeb71049d6ad286852e285f146682bf30d0296f5fbadfa"}, + {file = "websockets-13.1-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6d2aad13a200e5934f5a6767492fb07151e1de1d6079c003ab31e1823733ae79"}, + {file = "websockets-13.1-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:df01aea34b6e9e33572c35cd16bae5a47785e7d5c8cb2b54b2acdb9678315a17"}, + {file = "websockets-13.1-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:e54affdeb21026329fb0744ad187cf812f7d3c2aa702a5edb562b325191fcab6"}, + {file = "websockets-13.1-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:9ef8aa8bdbac47f4968a5d66462a2a0935d044bf35c0e5a8af152d58516dbeb5"}, + {file = "websockets-13.1-cp39-cp39-win32.whl", hash = "sha256:deeb929efe52bed518f6eb2ddc00cc496366a14c726005726ad62c2dd9017a3c"}, + {file = "websockets-13.1-cp39-cp39-win_amd64.whl", hash = "sha256:7c65ffa900e7cc958cd088b9a9157a8141c991f8c53d11087e6fb7277a03f81d"}, + {file = "websockets-13.1-pp310-pypy310_pp73-macosx_10_15_x86_64.whl", hash = "sha256:5dd6da9bec02735931fccec99d97c29f47cc61f644264eb995ad6c0c27667238"}, + {file = "websockets-13.1-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:2510c09d8e8df777177ee3d40cd35450dc169a81e747455cc4197e63f7e7bfe5"}, + {file = "websockets-13.1-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f1c3cf67185543730888b20682fb186fc8d0fa6f07ccc3ef4390831ab4b388d9"}, + {file = "websockets-13.1-pp310-pypy310_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:bcc03c8b72267e97b49149e4863d57c2d77f13fae12066622dc78fe322490fe6"}, + {file = "websockets-13.1-pp310-pypy310_pp73-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:004280a140f220c812e65f36944a9ca92d766b6cc4560be652a0a3883a79ed8a"}, + {file = "websockets-13.1-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:e2620453c075abeb0daa949a292e19f56de518988e079c36478bacf9546ced23"}, + {file = "websockets-13.1-pp38-pypy38_pp73-macosx_10_9_x86_64.whl", hash = "sha256:9156c45750b37337f7b0b00e6248991a047be4aa44554c9886fe6bdd605aab3b"}, + {file = "websockets-13.1-pp38-pypy38_pp73-macosx_11_0_arm64.whl", hash = "sha256:80c421e07973a89fbdd93e6f2003c17d20b69010458d3a8e37fb47874bd67d51"}, + {file = "websockets-13.1-pp38-pypy38_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:82d0ba76371769d6a4e56f7e83bb8e81846d17a6190971e38b5de108bde9b0d7"}, + {file = "websockets-13.1-pp38-pypy38_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e9875a0143f07d74dc5e1ded1c4581f0d9f7ab86c78994e2ed9e95050073c94d"}, + {file = "websockets-13.1-pp38-pypy38_pp73-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a11e38ad8922c7961447f35c7b17bffa15de4d17c70abd07bfbe12d6faa3e027"}, + {file = "websockets-13.1-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:4059f790b6ae8768471cddb65d3c4fe4792b0ab48e154c9f0a04cefaabcd5978"}, + {file = "websockets-13.1-pp39-pypy39_pp73-macosx_10_15_x86_64.whl", hash = "sha256:25c35bf84bf7c7369d247f0b8cfa157f989862c49104c5cf85cb5436a641d93e"}, + {file = "websockets-13.1-pp39-pypy39_pp73-macosx_11_0_arm64.whl", hash = "sha256:83f91d8a9bb404b8c2c41a707ac7f7f75b9442a0a876df295de27251a856ad09"}, + {file = "websockets-13.1-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7a43cfdcddd07f4ca2b1afb459824dd3c6d53a51410636a2c7fc97b9a8cf4842"}, + {file = "websockets-13.1-pp39-pypy39_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:48a2ef1381632a2f0cb4efeff34efa97901c9fbc118e01951ad7cfc10601a9bb"}, + {file = "websockets-13.1-pp39-pypy39_pp73-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:459bf774c754c35dbb487360b12c5727adab887f1622b8aed5755880a21c4a20"}, + {file = "websockets-13.1-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:95858ca14a9f6fa8413d29e0a585b31b278388aa775b8a81fa24830123874678"}, + {file = "websockets-13.1-py3-none-any.whl", hash = "sha256:a9a396a6ad26130cdae92ae10c36af09d9bfe6cafe69670fd3b6da9b07b4044f"}, + {file = "websockets-13.1.tar.gz", hash = "sha256:a3b3366087c1bc0a2795111edcadddb8b3b59509d5db5d7ea3fdd69f954a8878"}, ] [[package]] @@ -2481,13 +2513,13 @@ files = [ [[package]] name = "zipp" -version = "3.21.0" +version = "3.20.2" description = "Backport of pathlib-compatible object wrapper for zip files" optional = false -python-versions = ">=3.9" +python-versions = ">=3.8" files = [ - {file = "zipp-3.21.0-py3-none-any.whl", hash = "sha256:ac1bbe05fd2991f160ebce24ffbac5f6d11d83dc90891255885223d42b3cd931"}, - {file = "zipp-3.21.0.tar.gz", hash = "sha256:2c9958f6430a2040341a52eb608ed6dd93ef4392e02ffe219417c1b28b5dd1f4"}, + {file = "zipp-3.20.2-py3-none-any.whl", hash = "sha256:a817ac80d6cf4b23bf7f2828b7cabf326f15a001bea8b1f9b49631780ba28350"}, + {file = "zipp-3.20.2.tar.gz", hash = "sha256:bc9eb26f4506fda01b81bcde0ca78103b6e62f991b381fec825435c836edbc29"}, ] [package.extras] From 59af024c4c96c6a964576d70909c6bab50fc6fd6 Mon Sep 17 00:00:00 2001 From: Lucas Gameiro Borges Date: Tue, 19 Nov 2024 22:39:32 +0000 Subject: [PATCH 13/74] use noble as base --- .github/workflows/ci.yaml | 1 + charmcraft.yaml | 16 +++++++++------- terraform/variables.tf | 2 +- tests/integration/helpers.py | 2 +- tests/integration/test_tls.py | 2 +- 5 files changed, 13 insertions(+), 10 deletions(-) diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index b76a889f3c..7dc3404482 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -48,6 +48,7 @@ jobs: uses: canonical/data-platform-workflows/.github/workflows/build_charm.yaml@v23.0.5 with: cache: true + charmcraft-snap-channel: 3.x/stable integration-test: strategy: diff --git a/charmcraft.yaml b/charmcraft.yaml index e5e0208749..15efefa9d5 100644 --- a/charmcraft.yaml +++ b/charmcraft.yaml @@ -2,13 +2,15 @@ # See LICENSE file for licensing details. type: charm -bases: - - name: ubuntu - channel: "22.04" - architectures: [amd64] - - name: ubuntu - channel: "22.04" - architectures: [arm64] +base: ubuntu@24.04 + +platforms: + amd64: + build-on: amd64 + build-for: amd64 + arm64: + build-on: arm64 + build-for: arm64 parts: charm: build-snaps: diff --git a/terraform/variables.tf b/terraform/variables.tf index ede475f37a..aa24ebbc71 100644 --- a/terraform/variables.tf +++ b/terraform/variables.tf @@ -24,7 +24,7 @@ variable "revision" { variable "base" { description = "Application base" type = string - default = "ubuntu@22.04" + default = "ubuntu@24.04" } variable "units" { diff --git a/tests/integration/helpers.py b/tests/integration/helpers.py index 2d587b38d5..d0a32e58c6 100644 --- a/tests/integration/helpers.py +++ b/tests/integration/helpers.py @@ -30,7 +30,7 @@ wait_fixed, ) -CHARM_BASE = "ubuntu@22.04" +CHARM_BASE = "ubuntu@24.04" METADATA = yaml.safe_load(Path("./metadata.yaml").read_text()) DATABASE_APP_NAME = METADATA["name"] STORAGE_PATH = METADATA["storage"]["pgdata"]["location"] diff --git a/tests/integration/test_tls.py b/tests/integration/test_tls.py index 7408a8352f..245faaf559 100644 --- a/tests/integration/test_tls.py +++ b/tests/integration/test_tls.py @@ -148,7 +148,7 @@ async def test_tls_enabled(ops_test: OpsTest) -> None: await run_command_on_unit( ops_test, primary, - "pkill --signal SIGKILL -f /snap/charmed-postgresql/current/usr/lib/postgresql/14/bin/postgres", + "pkill --signal SIGKILL -f /snap/charmed-postgresql/current/usr/lib/postgresql/16/bin/postgres", ) await run_command_on_unit( ops_test, From 77e203e17701da227f02ee0b0a24c49c6d98cbba Mon Sep 17 00:00:00 2001 From: Lucas Gameiro Borges Date: Tue, 19 Nov 2024 23:13:35 +0000 Subject: [PATCH 14/74] use different workflow version --- .github/workflows/ci.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index 7dc3404482..9acde8c415 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -45,7 +45,7 @@ jobs: build: name: Build charm - uses: canonical/data-platform-workflows/.github/workflows/build_charm.yaml@v23.0.5 + uses: canonical/data-platform-workflows/.github/workflows/build_charm.yaml@lucas/test-cc3 with: cache: true charmcraft-snap-channel: 3.x/stable From ce650d9c1600441a3be1b543ad128902a39e6fc9 Mon Sep 17 00:00:00 2001 From: Lucas Gameiro Borges Date: Tue, 19 Nov 2024 23:17:13 +0000 Subject: [PATCH 15/74] remove cache --- .github/workflows/ci.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index 9acde8c415..44bfb49ade 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -47,7 +47,7 @@ jobs: name: Build charm uses: canonical/data-platform-workflows/.github/workflows/build_charm.yaml@lucas/test-cc3 with: - cache: true + cache: false charmcraft-snap-channel: 3.x/stable integration-test: From 8229e7f57f6f47ec9a9b6e0ac342e97d3c566724 Mon Sep 17 00:00:00 2001 From: Lucas Gameiro Borges Date: Wed, 20 Nov 2024 02:55:32 +0000 Subject: [PATCH 16/74] edit plugin --- poetry.lock | 6 +++--- pyproject.toml | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/poetry.lock b/poetry.lock index 7b4d2ac24f..5ba763369d 100644 --- a/poetry.lock +++ b/poetry.lock @@ -1840,8 +1840,8 @@ pyyaml = "*" [package.source] type = "git" url = "https://github.com/canonical/data-platform-workflows" -reference = "v23.0.5" -resolved_reference = "e3f522c648375decee87fc0982c012e46ffb0b98" +reference = "lucas/test-cc3" +resolved_reference = "01d3e1fbbf1aa8a016acb64f10dfdb4b09b53cb8" subdirectory = "python/pytest_plugins/pytest_operator_cache" [[package]] @@ -2533,4 +2533,4 @@ type = ["pytest-mypy"] [metadata] lock-version = "2.0" python-versions = "^3.10" -content-hash = "a24006bb8af98b161cd722b73b93b3ce7fbc5f44e46ee2d4faa24e438c09e0de" +content-hash = "697438e08e7ffb92c13ad76ff09e4ccf7a8d6eec676b5ba001a9463ab788cbe9" diff --git a/pyproject.toml b/pyproject.toml index ea24e76a47..0f6b526cf0 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -63,7 +63,7 @@ optional = true pytest = "^8.3.3" pytest-github-secrets = {git = "https://github.com/canonical/data-platform-workflows", tag = "v23.0.5", subdirectory = "python/pytest_plugins/github_secrets"} pytest-operator = "^0.38.0" -pytest-operator-cache = {git = "https://github.com/canonical/data-platform-workflows", tag = "v23.0.5", subdirectory = "python/pytest_plugins/pytest_operator_cache"} +pytest-operator-cache = {git = "https://github.com/canonical/data-platform-workflows", branch = "lucas/test-cc3", subdirectory = "python/pytest_plugins/pytest_operator_cache"} pytest-operator-groups = {git = "https://github.com/canonical/data-platform-workflows", tag = "v23.0.5", subdirectory = "python/pytest_plugins/pytest_operator_groups"} # renovate caret doesn't work: https://github.com/renovatebot/renovate/issues/26940 juju = "<=3.5.0.0" From c30f4807f79d2673023da4491f73eb77d9253b90 Mon Sep 17 00:00:00 2001 From: Lucas Gameiro Borges Date: Wed, 20 Nov 2024 04:16:39 +0000 Subject: [PATCH 17/74] fix integration tests --- tests/integration/ha_tests/test_replication.py | 1 - tests/integration/ha_tests/test_self_healing.py | 1 - tests/integration/new_relations/test_new_relations.py | 6 +----- tests/integration/new_relations/test_relations_coherence.py | 2 +- tests/integration/relations/test_relations.py | 2 -- tests/integration/test_db.py | 2 -- tests/integration/test_tls.py | 2 +- 7 files changed, 3 insertions(+), 13 deletions(-) diff --git a/tests/integration/ha_tests/test_replication.py b/tests/integration/ha_tests/test_replication.py index dd924948da..64539a0561 100644 --- a/tests/integration/ha_tests/test_replication.py +++ b/tests/integration/ha_tests/test_replication.py @@ -42,7 +42,6 @@ async def test_build_and_deploy(ops_test: OpsTest) -> None: await ops_test.model.deploy( APPLICATION_NAME, application_name=APPLICATION_NAME, - base=CHARM_BASE, channel="edge", ) diff --git a/tests/integration/ha_tests/test_self_healing.py b/tests/integration/ha_tests/test_self_healing.py index 8f197f1a94..5e6a26da58 100644 --- a/tests/integration/ha_tests/test_self_healing.py +++ b/tests/integration/ha_tests/test_self_healing.py @@ -87,7 +87,6 @@ async def test_build_and_deploy(ops_test: OpsTest) -> None: await ops_test.model.deploy( APPLICATION_NAME, application_name=APPLICATION_NAME, - base=CHARM_BASE, channel="edge", ) diff --git a/tests/integration/new_relations/test_new_relations.py b/tests/integration/new_relations/test_new_relations.py index 24307164eb..15812ec0ec 100644 --- a/tests/integration/new_relations/test_new_relations.py +++ b/tests/integration/new_relations/test_new_relations.py @@ -49,7 +49,6 @@ async def test_deploy_charms(ops_test: OpsTest, charm): APPLICATION_APP_NAME, application_name=APPLICATION_APP_NAME, num_units=2, - base=CHARM_BASE, channel="edge", ), ops_test.model.deploy( @@ -218,7 +217,6 @@ async def test_two_applications_doesnt_share_the_same_relation_data(ops_test: Op APPLICATION_APP_NAME, application_name=another_application_app_name, channel="edge", - base=CHARM_BASE, ) await ops_test.model.wait_for_idle(apps=all_app_names, status="active") @@ -449,7 +447,7 @@ async def test_admin_role(ops_test: OpsTest): all_app_names = [DATA_INTEGRATOR_APP_NAME] all_app_names.extend(APP_NAMES) async with ops_test.fast_forward(): - await ops_test.model.deploy(DATA_INTEGRATOR_APP_NAME, base=CHARM_BASE) + await ops_test.model.deploy(DATA_INTEGRATOR_APP_NAME) await ops_test.model.wait_for_idle(apps=[DATA_INTEGRATOR_APP_NAME], status="blocked") await ops_test.model.applications[DATA_INTEGRATOR_APP_NAME].set_config({ "database-name": DATA_INTEGRATOR_APP_NAME.replace("-", "_"), @@ -540,7 +538,6 @@ async def test_invalid_extra_user_roles(ops_test: OpsTest): await ops_test.model.deploy( DATA_INTEGRATOR_APP_NAME, application_name=another_data_integrator_app_name, - base=CHARM_BASE, ) await ops_test.model.wait_for_idle( apps=[another_data_integrator_app_name], status="blocked" @@ -605,7 +602,6 @@ async def test_nextcloud_db_blocked(ops_test: OpsTest, charm: str) -> None: channel="edge", application_name="nextcloud", num_units=1, - base=CHARM_BASE, ), ) await asyncio.gather( diff --git a/tests/integration/new_relations/test_relations_coherence.py b/tests/integration/new_relations/test_relations_coherence.py index 1f2a751922..67117c6bb6 100644 --- a/tests/integration/new_relations/test_relations_coherence.py +++ b/tests/integration/new_relations/test_relations_coherence.py @@ -35,7 +35,7 @@ async def test_relations(ops_test: OpsTest, charm): await ops_test.model.wait_for_idle(apps=[DATABASE_APP_NAME], status="active", timeout=3000) # Creating first time relation with user role - await ops_test.model.deploy(DATA_INTEGRATOR_APP_NAME, base=CHARM_BASE) + await ops_test.model.deploy(DATA_INTEGRATOR_APP_NAME) await ops_test.model.applications[DATA_INTEGRATOR_APP_NAME].set_config({ "database-name": DATA_INTEGRATOR_APP_NAME.replace("-", "_"), }) diff --git a/tests/integration/relations/test_relations.py b/tests/integration/relations/test_relations.py index b3a542decc..277c1e9cd9 100644 --- a/tests/integration/relations/test_relations.py +++ b/tests/integration/relations/test_relations.py @@ -37,7 +37,6 @@ async def test_deploy_charms(ops_test: OpsTest, charm): APPLICATION_APP_NAME, application_name=DATABASE_APP_NAME, num_units=1, - base=CHARM_BASE, channel="edge", ), ops_test.model.deploy( @@ -55,7 +54,6 @@ async def test_deploy_charms(ops_test: OpsTest, charm): APPLICATION_APP_NAME, application_name=DB_APP_NAME, num_units=1, - base=CHARM_BASE, channel="edge", ), ) diff --git a/tests/integration/test_db.py b/tests/integration/test_db.py index 88f87c1536..8da8b707ea 100644 --- a/tests/integration/test_db.py +++ b/tests/integration/test_db.py @@ -198,14 +198,12 @@ async def test_roles_blocking(ops_test: OpsTest, charm: str) -> None: APPLICATION_NAME, application_name=APPLICATION_NAME, config={"legacy_roles": True}, - base=CHARM_BASE, channel="edge", ) await ops_test.model.deploy( APPLICATION_NAME, application_name=f"{APPLICATION_NAME}2", config={"legacy_roles": True}, - base=CHARM_BASE, channel="edge", ) diff --git a/tests/integration/test_tls.py b/tests/integration/test_tls.py index 245faaf559..4116746dd3 100644 --- a/tests/integration/test_tls.py +++ b/tests/integration/test_tls.py @@ -66,7 +66,7 @@ async def test_tls_enabled(ops_test: OpsTest) -> None: async with ops_test.fast_forward(): # Deploy TLS Certificates operator. await ops_test.model.deploy( - tls_certificates_app_name, config=tls_config, channel=tls_channel, base=CHARM_BASE + tls_certificates_app_name, config=tls_config, channel=tls_channel ) # Relate it to the PostgreSQL to enable TLS. From ede9c4d03177462101171349163bb94b0abdfb58 Mon Sep 17 00:00:00 2001 From: Lucas Gameiro Borges Date: Thu, 21 Nov 2024 17:14:17 +0000 Subject: [PATCH 18/74] revert pgtracing config + add new snaps --- src/charm.py | 1 - src/cluster.py | 3 --- src/constants.py | 2 +- templates/patroni.yml.j2 | 10 +--------- tests/unit/test_charm.py | 2 -- 5 files changed, 2 insertions(+), 16 deletions(-) diff --git a/src/charm.py b/src/charm.py index 2258c312fa..37fbfb1552 100755 --- a/src/charm.py +++ b/src/charm.py @@ -1703,7 +1703,6 @@ def update_config(self, is_creating_backup: bool = False) -> bool: restore_to_latest=self.app_peer_data.get("restore-to-time", None) == "latest", stanza=self.app_peer_data.get("stanza", self.unit_peer_data.get("stanza")), restore_stanza=self.app_peer_data.get("restore-stanza"), - tracing_endpoint_config=self._tracing_endpoint_config, parameters=pg_parameters, ) if not self._is_workload_running: diff --git a/src/cluster.py b/src/cluster.py index ea39b205c9..c77d801375 100644 --- a/src/cluster.py +++ b/src/cluster.py @@ -579,7 +579,6 @@ def render_patroni_yml_file( pitr_target: str | None = None, restore_timeline: str | None = None, restore_to_latest: bool = False, - tracing_endpoint_config: str | None = None, parameters: dict[str, str] | None = None, ) -> None: """Render the Patroni configuration file. @@ -595,7 +594,6 @@ def render_patroni_yml_file( pitr_target: point-in-time-recovery target for the restore. restore_timeline: timeline to restore from. restore_to_latest: restore all the WAL transaction logs from the stanza. - tracing_endpoint_config: endpoint for tracing data. parameters: PostgreSQL parameters to be added to the postgresql.conf file. """ # Open the template patroni.yml file. @@ -633,7 +631,6 @@ def render_patroni_yml_file( version=self.get_postgresql_version().split(".")[0], minority_count=self.planned_units // 2, pg_parameters=parameters, - tracing_endpoint_config=tracing_endpoint_config, primary_cluster_endpoint=self.charm.async_replication.get_primary_cluster_endpoint(), extra_replication_endpoints=self.charm.async_replication.get_standby_endpoints(), raft_password=self.raft_password, diff --git a/src/constants.py b/src/constants.py index 17d18bbf5c..5e256d35e2 100644 --- a/src/constants.py +++ b/src/constants.py @@ -34,7 +34,7 @@ SNAP_PACKAGES = [ ( POSTGRESQL_SNAP_NAME, - {"revision": {"aarch64": "132", "x86_64": "135"}}, + {"revision": {"aarch64": "137", "x86_64": "136"}}, ) ] diff --git a/templates/patroni.yml.j2 b/templates/patroni.yml.j2 index 68a3ae8c4d..accb64e639 100644 --- a/templates/patroni.yml.j2 +++ b/templates/patroni.yml.j2 @@ -102,7 +102,7 @@ bootstrap: log_truncate_on_rotation: 'on' logging_collector: 'on' wal_level: logical - shared_preload_libraries: 'timescaledb,pg_tracing,pgaudit' + shared_preload_libraries: 'timescaledb,pgaudit' {%- if pg_parameters %} {%- for key, value in pg_parameters.items() %} {{key}}: {{value}} @@ -151,14 +151,6 @@ postgresql: ssl_cert_file: {{ conf_path }}/cert.pem ssl_key_file: {{ conf_path }}/key.pem {%- endif %} - {%- if tracing_endpoint_config %} - compute_query_id: on - pg_tracing.max_span: 10000 - pg_tracing.track: all - pg_tracing.otel_endpoint: {{tracing_endpoint_config}}/v1/traces - pg_tracing.otel_naptime: 2000 - pg_tracing.sample_rate: 1.0 - {%- endif %} unix_socket_directories: /tmp {%- if pg_parameters %} {%- for key, value in pg_parameters.items() %} diff --git a/tests/unit/test_charm.py b/tests/unit/test_charm.py index be8a721504..650dbe689d 100644 --- a/tests/unit/test_charm.py +++ b/tests/unit/test_charm.py @@ -1295,7 +1295,6 @@ def test_update_config(harness): restore_timeline=None, pitr_target=None, restore_to_latest=False, - tracing_endpoint_config=None, parameters={"test": "test"}, ) _handle_postgresql_restart_need.assert_called_once_with(False) @@ -1319,7 +1318,6 @@ def test_update_config(harness): restore_timeline=None, pitr_target=None, restore_to_latest=False, - tracing_endpoint_config=None, parameters={"test": "test"}, ) _handle_postgresql_restart_need.assert_called_once() From 644ea771deffe54d5908e5b3bfac7d7efdff90ea Mon Sep 17 00:00:00 2001 From: Lucas Gameiro Borges Date: Thu, 21 Nov 2024 19:17:31 +0000 Subject: [PATCH 19/74] try fixes --- psycopg2-2.9.10-cp310-cp310-linux_x86_64.whl | Bin 0 -> 499331 bytes src/charm.py | 8 ++++++++ tests/integration/test_subordinates.py | 2 -- tests/unit/test_charm.py | 1 + 4 files changed, 9 insertions(+), 2 deletions(-) create mode 100644 psycopg2-2.9.10-cp310-cp310-linux_x86_64.whl diff --git a/psycopg2-2.9.10-cp310-cp310-linux_x86_64.whl b/psycopg2-2.9.10-cp310-cp310-linux_x86_64.whl new file mode 100644 index 0000000000000000000000000000000000000000..604572cba38a8e271a0b9a58891cea8da6a2ee71 GIT binary patch literal 499331 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 z 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̊bl#YCj|VMq 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≷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 z%0A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

x+Mp#&E^bJ@(E(#9 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 z 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�s#k`tU+R6}zxw&n?Ve!; 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⋘{;2^!V!$H?=_(fgLY`ZZ|)sm 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 fC;yX${|WK(Qs5B(LLh(K>psSr_Oy?51LR0 literal 0 HcmV?d00001 diff --git a/src/charm.py b/src/charm.py index 37fbfb1552..22f1b4dc4f 100755 --- a/src/charm.py +++ b/src/charm.py @@ -1716,6 +1716,14 @@ def update_config(self, is_creating_backup: bool = False) -> bool: return True if not self._patroni.member_started: + if enable_tls: + logger.debug( + "Early exit update_config: patroni not responding but TLS is enabled." + ) + self.unit_peer_data.update({"tls": "enabled"}) + self.postgresql_client_relation.update_endpoints() + # self._handle_postgresql_restart_need(True) + return True logger.debug("Early exit update_config: Patroni not started yet") return False diff --git a/tests/integration/test_subordinates.py b/tests/integration/test_subordinates.py index be9be926cc..2e2772236b 100644 --- a/tests/integration/test_subordinates.py +++ b/tests/integration/test_subordinates.py @@ -35,7 +35,6 @@ async def test_deploy(ops_test: OpsTest, charm: str, github_secrets): config={"token": github_secrets["UBUNTU_PRO_TOKEN"]}, channel="latest/edge", num_units=0, - base=CHARM_BASE, ), ops_test.model.deploy( LS_CLIENT, @@ -46,7 +45,6 @@ async def test_deploy(ops_test: OpsTest, charm: str, github_secrets): }, channel="latest/edge", num_units=0, - base=CHARM_BASE, ), ) diff --git a/tests/unit/test_charm.py b/tests/unit/test_charm.py index 650dbe689d..9b38d7ec3e 100644 --- a/tests/unit/test_charm.py +++ b/tests/unit/test_charm.py @@ -1338,6 +1338,7 @@ def test_update_config(harness): harness.update_relation_data( rel_id, harness.charm.unit.name, {"tls": ""} ) # Mock some data in the relation to test that it doesn't change. + _is_tls_enabled.return_value = False harness.charm.update_config() _handle_postgresql_restart_need.assert_not_called() assert "tls" not in harness.get_relation_data(rel_id, harness.charm.unit.name) From 1de4ef41d6e702560b7589ca8a9e04e5d8c63304 Mon Sep 17 00:00:00 2001 From: Lucas Gameiro Borges Date: Thu, 21 Nov 2024 20:27:02 +0000 Subject: [PATCH 20/74] try new fixes --- src/charm.py | 4 +--- tests/integration/ha_tests/test_upgrade.py | 4 ++++ tests/integration/ha_tests/test_upgrade_from_stable.py | 3 +++ tests/integration/test_subordinates.py | 2 +- 4 files changed, 9 insertions(+), 4 deletions(-) diff --git a/src/charm.py b/src/charm.py index 22f1b4dc4f..2c743a416c 100755 --- a/src/charm.py +++ b/src/charm.py @@ -1720,9 +1720,7 @@ def update_config(self, is_creating_backup: bool = False) -> bool: logger.debug( "Early exit update_config: patroni not responding but TLS is enabled." ) - self.unit_peer_data.update({"tls": "enabled"}) - self.postgresql_client_relation.update_endpoints() - # self._handle_postgresql_restart_need(True) + self._handle_postgresql_restart_need(True) return True logger.debug("Early exit update_config: Patroni not started yet") return False diff --git a/tests/integration/ha_tests/test_upgrade.py b/tests/integration/ha_tests/test_upgrade.py index 497c7ace9a..520451f831 100644 --- a/tests/integration/ha_tests/test_upgrade.py +++ b/tests/integration/ha_tests/test_upgrade.py @@ -30,6 +30,7 @@ @pytest.mark.group(1) +@pytest.mark.unstable @pytest.mark.abort_on_fail async def test_deploy_latest(ops_test: OpsTest) -> None: """Simple test to ensure that the PostgreSQL and application charms get deployed.""" @@ -53,6 +54,7 @@ async def test_deploy_latest(ops_test: OpsTest) -> None: @pytest.mark.group(1) +@pytest.mark.unstable @pytest.mark.abort_on_fail async def test_pre_upgrade_check(ops_test: OpsTest) -> None: """Test that the pre-upgrade-check action runs successfully.""" @@ -66,6 +68,7 @@ async def test_pre_upgrade_check(ops_test: OpsTest) -> None: @pytest.mark.group(1) +@pytest.mark.unstable @pytest.mark.abort_on_fail async def test_upgrade_from_edge(ops_test: OpsTest, continuous_writes) -> None: # Start an application that continuously writes data to the database. @@ -116,6 +119,7 @@ async def test_upgrade_from_edge(ops_test: OpsTest, continuous_writes) -> None: @pytest.mark.group(1) +@pytest.mark.unstable @pytest.mark.abort_on_fail async def test_fail_and_rollback(ops_test, continuous_writes) -> None: # Start an application that continuously writes data to the database. diff --git a/tests/integration/ha_tests/test_upgrade_from_stable.py b/tests/integration/ha_tests/test_upgrade_from_stable.py index fd75c93f2b..36a2f0c24c 100644 --- a/tests/integration/ha_tests/test_upgrade_from_stable.py +++ b/tests/integration/ha_tests/test_upgrade_from_stable.py @@ -26,6 +26,7 @@ @pytest.mark.group(1) +@pytest.mark.unstable @pytest.mark.abort_on_fail async def test_deploy_stable(ops_test: OpsTest) -> None: """Simple test to ensure that the PostgreSQL and application charms get deployed.""" @@ -77,6 +78,7 @@ async def test_deploy_stable(ops_test: OpsTest) -> None: @pytest.mark.group(1) +@pytest.mark.unstable @pytest.mark.abort_on_fail async def test_pre_upgrade_check(ops_test: OpsTest) -> None: """Test that the pre-upgrade-check action runs successfully.""" @@ -95,6 +97,7 @@ async def test_pre_upgrade_check(ops_test: OpsTest) -> None: @pytest.mark.group(1) +@pytest.mark.unstable @pytest.mark.abort_on_fail async def test_upgrade_from_stable(ops_test: OpsTest): """Test updating from stable channel.""" diff --git a/tests/integration/test_subordinates.py b/tests/integration/test_subordinates.py index 2e2772236b..11a5f74028 100644 --- a/tests/integration/test_subordinates.py +++ b/tests/integration/test_subordinates.py @@ -33,7 +33,7 @@ async def test_deploy(ops_test: OpsTest, charm: str, github_secrets): ops_test.model.deploy( UBUNTU_PRO_APP_NAME, config={"token": github_secrets["UBUNTU_PRO_TOKEN"]}, - channel="latest/edge", + channel="noble/edge", num_units=0, ), ops_test.model.deploy( From 6aa0ae434577ddbe03170f9b3ab197565d74ba9e Mon Sep 17 00:00:00 2001 From: Lucas Gameiro Borges Date: Thu, 21 Nov 2024 22:20:52 +0000 Subject: [PATCH 21/74] try fixing restart --- psycopg2-2.9.10-cp310-cp310-linux_x86_64.whl | Bin 499331 -> 0 bytes src/charm.py | 5 ++++- tests/integration/test_subordinates.py | 2 +- 3 files changed, 5 insertions(+), 2 deletions(-) delete mode 100644 psycopg2-2.9.10-cp310-cp310-linux_x86_64.whl diff --git a/psycopg2-2.9.10-cp310-cp310-linux_x86_64.whl b/psycopg2-2.9.10-cp310-cp310-linux_x86_64.whl deleted file mode 100644 index 604572cba38a8e271a0b9a58891cea8da6a2ee71..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 499331 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 z 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̊bl#YCj|VMq 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≷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 z%0AlfIG%aa(zG`}eE=Enr+fiyC2#vu4twRM}D?Ud9~-2FG9|rS`B;>I2jX)3&pRvc)X)R;t3vTWO!i@5`(W* z5F0mXzF4`*OCJydCBC>k7ARiW(^5Urpy?0oVPKEC1JK%RyB>oAFnHW2?zf9qSVhG^&$xF5Felqlnrc59xdD zns4Z8zHJ!FA8Hi83iz#6z{QI?`jCJI6GiD2f7C!#QFm)cNgK9aAwi}@Eu^eFJbwR` zsfo9eb&Cu{E=A z1to@bzk;_N&rOo!e4$)t%j_JU`ibx1@SJ6K(}1iz%D{Vt7sHAD{Cj2pRC}j3wLsXk z7m1R2aUK{$YE|CoP)(V#IM0kppR6v$O)Y%}c`k44t5uEk0e#Gj4B0<9@;XWKUXICn zQDq_&J8a79a&qcyxCCjPRjTfB-jnf~Y&d--9RaN9;xmkob>U8lxM0HKvky{F^N4C^ z)v&)_s`XC^2L(tsxLq9A1|?l89PrWXbHj`msm))~j)ib`s)YMp6zc=mVk;Ppuh(id#`g1?mKN0}y~e{Uj? z=>NKe?q)1-3)j_@7u*{bX;eDLb*iUCUAZ6;S{AvH2{`lW@=qzBOWIs(_U`Nj4q|?J zmX6T=Yg0kYeV);ddBP@5Yx^OvX6^pNRZ{=bHCDitUVsE|fX5Hg-I^o9WD_;U@_1n6 z)K)dW)CqZ9D8B{0KWvCO5v2~#56L+73uRi4T=7d68ge-86U5^fs-#p6$9z>CyiXLoklwYX0}K8IcuAhjMsJ8!~L(9jJ++Ehxv?V3E<3lP5U67C%!eQWU$Va)*WHnf|gV zPin-ws>*5)Mf+v-jF-jUIK<%fz5>E%`E{x>3p+5_;aPt3!gBGkbN!m?S_hSix09d7 z+BtWH-VZhxS&VW)!Sd92Ilz_HDpB1Nj4gDA#%4(rGRQ{|R!TXYF7FPx*P0@iOv6s* z^p(1_o`MUwgu9Lr$rt!}XBF@&N!=dtQ|l|4SHMrbwv(6**s-GIrnSxu3MFuG|)*>cJfK9#T1ljHbo~SBtp$F*=2yDjXh}XE@uSZR9)6t3a=b zb++O&k~Sp4CE8Lr!@Lx-aam^MrwvB^Pv?rOUs@>FzFc8Hw{TO+S=sJGL{I?gPgYcK zn81v?^Sm_JijL=>tTEPSF?=2Yik#1EvlR`AJ;@vSx)jT}Jr=PFoFCAbex;X)Yu11H zmU$TP-h>Y}V%N3p&R$+w7>5TLSNKq#cvmXDG|@}Ge%Dw$2C`Ol^b@ki9Hm-Yadx_{ zjvR=zEYR2yJR4LCcN>?9YwHF&(;J8e69!$~?4L5{)dv$3@FPI>4J9RzMLgUZHa0r# z0-829h)*^)?8(*+S0FUY;XeVVRvEWn(sog{o@+wxAlZSkRz4NNGT3?{_3LIo32S_9j&=@z?mUejG#F!Y^-Zhm3^u)6u?&CzQW-H&y<& zv1{2FXARa4`Q@StrA7a3B>cz6RqhT%yQoK?2gS|xW!nm=h5rr=w1q8Y7N?uaqI<(K zC5cXhXGq>wx+IUDV-0J-?a@(xKsZ5^Yxq>k4@vV7QtZ*$3-m4iQbwLEPcn*c^?C(r z);a1Z4Ezb)p?+uky=;Eu4<9$C>zQN7VA{QDc`fn$B#ip?jeE1;-q9!A0ZQ^M_RHV;Yx1PafaR=7 zn`ztmA&B*m|4pcUEWomMcYVy#l)`}9cB|afLyL{5UnVIBpn(;<#yz*@Z-uO40k}u5 z=d4}4_O>QATg^9CmhT?`=aq@eqRZx~Pxd=p9RRRX4oU+GkMji}kK48Q`-1QwJG+BG zpg8S6(wPR569k36xl=a>t=-AR**&$VT@f6|vrtSP>djws77lCvxL3qR=Nh|9nfPe?aaqU0HCvIgu*au(RJz$ULuhGii za~Fhfxo&B5D(pp4vNn?I44y}u%sO&}Hg9Cue}qR=6yBl}jt+3`3#A*hHkkzN^`&{b znWv9Gg{O=M>d*EByY|VAw5#pZ4vt8vCId0XDF$5WMqG+gbG5K4 zFXH87e2#79`%dg;jz22=Nt((8E;OszxtrI24eXkc6TrX}D4ME_2F6jZ-d=;O8FI3n z15Ne$^288K5v7UXW`34-QT~HabqZvLq!99PJ);xxFvzvf72mLe1`Qcs4(JqVVQuIF zb`Lcmcj)HL9c(8}fC6Ts11QWf_k8N%B%=zIs6_SpaJ2Dn1=IyDNY#|oy!3P(kL2ji^ZU92)N5P{6`#mA?#fqIa( zuzJXxM)(f#l4h>?VuEqn+Ft5gZEAUdLFvP{LFqX4{Wf4odU(4m)9%+%4)Ei-=6%g` z4LaTb$lYA!8pNoNG6l$Fb96ia&dy+H`R!5#I!9VrJwN$H1Asy~AH`C`9e>sU>dH$h zJ>zP?D_LiJj7HEc`<-}BF{?dgYQNg>=26B5-lkxD24mAydpER(C_5*}{4U7!qo#oy zN%1Y4isT9=FcAF^8kJ`8H7kQj+1>nZ_W`f>YabBA{l8H6|52dp;uK+x`@i!~MnUm_ zI&jc)4!Djy!#}>|wFf5*>`;Ye%who8x%-g5|5OI()B!3e{2vV3e^iWw z9Z)xG)COFQo#~Qry;Zp=u618;3W592^z!X4T#G^f=eecMp^*AUqjt^45!SB97BM(Ux0}z=ulp+O zeoW~Yj+y{sISY%Mk+aqGCK^3=vpZYVfUA+SGW2Uoepb&zR*qg;z@*HN6yWObw`2k4 z7^q~*MzcuX_q@hM)^69rOE(T%^ZOIq3O-g(JVm-UFn^;e^B_B*--Mhkx7s}DRBtay zijS0a?}hFkq$v_ehLBu1$n+OH>^MT(D9jz9P8r|}j4H=v;Gw<|;W z2_V~MPB#T#Ga||*_sro2_Y>1;aMy)tgv4DJ-#ygJUP(r4mog>~B*aDUw*4X{*exx< zXBlF#eU4Dk9Tm;jNRQ>1jx$?fP%rl2q&Ptd047j_`Z9pe5ky8BWl-KfYsp^`o{Kg< z5YV~k!@Zt~WC}!vKB~YeH^Qsir+-!%liguRA59NdE|=S>dSI4c-@vf#jeeoWHdleG zKnrvBzUSCd>ZG1*u3p%pw#&cJ_))o#h@p<#?n9fJT7#jY(<6AU$lxtYtc)T?wj?-t z#Zu0TX+Iu;$tmm>g4V{7SQ(#IxDDosh7u$D=`Z~&t6h#6yya>;zKQEOlp;0J7=5Zg zvRiM1o}M5$jP9;3jQdJK0x@Jlr2LFjcIj2x!|6N_mvaY6=-4A;rxhL=9dZIas1joa zsCMEU((dt7#Me|ZzrVIo;%N%wp{2LW9QZQ zl)oeIZ*~27apbJps@f|5G%J6MFD!z{7&oaA8J(G`Y)AOi z^7pH{j^A`@$_-~fJwckRh3C&QZdV*jN`hlnPajWjeQw;_GWLoxK_~p2!NpCdAcdF{ zPU(84VlHu-qkbk3o=fAGfsA=fHH&PEEPKoYm*Os!Cb+DW<7|67Vbt+P;FN#kmTyqu zfW}krZ`XTYnsohthct8usvDivKHmfNg1pq$3^1FN+P(gG2nif=_g8d%+EETf9>o=Lm-thgA;r4_WK95@fJwjikZiLo7s4x?%)`U3bC8tN}nY6Si! zcMFo}d)O+Kc;MPBOHdB1GyA|sv4yIvZdkVX+1o89Q(XNyMb|#D9p9dT5=T+Ui#)-y zwRjFIHY=wD1LoyNdbZR!;Yd`5V@_4)RV71E(pm!*e}BWOtkEfF%hQzD7?~E#3DYMp zohpluq@j$dy9MClbW$JkNNCH4d(3!NDjbDWB`|)D&C)dc0a0z(3e8Y~i-ltzg{3xg zv530zafDn=vuG}-k8aVfNZ50wn^>VDM2{u6qKofFpW1**Y+q*HVmn&~ix)@x{j&(H zyerKog>2eu_phtb__Ulxr#CcC?hVxq!wOuAuIr*~zNn9S@z_dD#~Sw`3m5s}wpC3y zRkpEwC*s}Y#8wj0Fu$#P%%ty1>Vm2SgNZ~x@VaEX8G8wqDT|NMzYL_n5J-q4C9ap$ zjRhM1<}&mDKiv^8baz;h83IT zoi>rHvo#Z}^XkuWneJ8Q+2-CVYAdW}6~f~!N_CFoSBYog)SeOvBipl0?#4}b<8za( zm0D35(NIaooHsOAa%%?}54->LI;GY^9g=&?irV+_&zpA^aUqGC#o7;w-A+={^&`rw z4rGIIaZ~h;T-n-^EfhsfrnXA;Bi6I@m)g!U!ai&sbe0*G1)JbQ!a&>5)`{E+WQm!h6=eNes;llzT*&9{)6cqKJl-#Ti0oyLh92X> zf_yqPDuN>roTFe=HvUNE`fv9aH(Nm`pGOolgYVA@sk7|LR6&aR5ro#k+^3XWqh6YBp;Y_dj`jp zaG)`hBV}9*bpFixXlt(=$iS6++8Menv?sp&Hsn9|MS7{S|C*|l|0aeXN{-o#E2P(0 zw(h%=GVwH=e{7#8R2^>a(5f(IMLw<4u($IWZ{oJd{u1JKZXQoJ`ivi@qA0zqO3WH1 zj%eV=;-<7Pop66RC-O+6_LIM-LS#|FL|U$#j$~mj?*|R|X7s;#G?%z^diSh!b2?AXTF$|F$F^IT9O_dW$Lxb_X_H6m>?MO|3`QLnoEkV% z?JtpeDL5NVu^lGrKvQWgz*EFCK3`lFpTn(cb*6~#nc=|+i}^-LzaAVCC>~<@XFrdX zDz4GjLVfo5!Pkf+L56=NAB-SsTy`q*N=8ZQ_?6v=zdrJX<=LZsn7P&MX+F(#qT_wy znmfzO;V8vjtIP#nQwkn;HjKO`h=n{APQ~aJExP|n>BSw(NK2gi)~x1W)EA?I(_l4s zp+Zz#z||Js+#U(5s`ko6gQA=}*~8&)%AE)ZGAJSWp-@Yi(#0;9vfl-M<%AbwJU6h@ zoy~V|(j-$t#hsAAVqxBy7Wf89dle2*FwdXOdqrWZqvyh&jbrXc7yexXOlUL!F=UQe5Y``iJ`Z^*IFsJ6F52zzdX5Oj1wssf8}AG*FPs&G(zaPLdM*# z=V60ui7%yokWv2QYN1mXp8wxJfd%6g`585p8B%W7*-3F5rFr4pADb`-NL3W;uX4}r zbDysk+RCi|#NY|!Ikfo|TTTIs(Fi6jafR<4s9RNOI?z~-<rPgUe)OUjVZbAo*hR$)&| zhL;W5N5l!%2*D4@k|^j)9Vo`BiD8aN&cf?m8D#ra#%4d8Ohr;dGIAOstxLD0@OO6d z2nXuk!_hDiI#G;aVzlWSXFbhC>*N##^@OtYy-xc#GvAAtxlfz^@2ACZc-e1)ei`&gr@>EeRuyap+DNP;ReFlTT#W?p8y+!^`o{2` zpsw5zu?S+1Uq0DKwC&knE7Aw`Kj+o^mY-+{5N_Pcrh^~QbwN)FoASq!hHl5pJ$7E? ziDtqM=6v#9=1yb$%fVXW!Vha&$R$ zG6!4qsbr-b7YTRetYu_-29k5WrW>YdWcM&k=mP8?&kGUe2_0 zR~P#dqb!v3Dl5(wQwul*@ZB`iMQ$3F%HEQa_fgIW&uL&i*1^G@yJ*PuHMR4>vDTe1sYjim{9<_u?pN2(A0r)o#RhuOmLi(nNzqvLWQK z@@c~Y4NTdOhhi3OFiG1VMHV5L5Wq-w&DZy8lab&uBbkPelCezU+MbEg%-hhl=M26- z=`V_i4q~bk@zMKn+QZM2&>7$LZ6d$l6;`%*Au|KM8NQ-^;D@dBc}=@7nl`JjQ|^HKs22Z|29Xx+DVT^dMG?G_a*KmRk& zCxtucR{yNkB8OZ^F^oz$zI3Gje(JuiX-iSlqsY<#~+m#LbDIX8+f_?C?6ZZ!9}qUzKq-5z0$hc=3- zfEaPO=o|e*QSI2p^ufIUbSnG!myWpli-tu?oDT_(W0rkicT!z8@r|h*dA)r< zOz(CUFyD{TUH07De}m(n%Q9J@4ELe~FU0#4htIgBuB|1jQ&(7T$fZ1PUx%E;VTob% z|2ZrT*^J<-r4r}*9yb<-THb<5+7jm$7v9PXN16^%Gj6^Rh7rHh0B1JEsNSH9a|zo1 zSzLxC#WxV;H4LETGV&R17kHOEl9_K4cUvni*6ST7%^Om*kgx1Nr(e^}WS|0?{3m;i z_^KIeDYFwXi;l3q|hp3V|m&7mqZZG!g=PL#%M<* zuq;Eb|IN*&5D0Ysr?u5>0<6bpVaG^VTaS&rv>g8bQ{N?5cY8s*c-lZF1wQcppN!7u z>;a9yB=4-bLH`?Ya6IEV5RDoW`yS!wE?Wn`_kRZ^EgKeYYvrfa^UW<6>FkJfbs0|a zMSS!_byLr6RxHM?s^^6DkHuN2e5WynV3Yi6+&imPC8~K?vdM_z>zjcB0`314xQYJ! zfwyWUkk`U)oT0it$q)pRKV>^wtZL;iwDU@B{PV|_>sg~R0!CnY=;kK<#jt_Z#*R_b zESq`(tj7Y>7pJy931Mkj2KxAz9LPErXpTXkIZQ;T7uwv7H5{#+HtiiaXh5L#S~h?l z;Z@f6AAh<`V}B0<71vqhM1;}E+2Y`%HQ|`Q}wU8wyVAmO> zVE#bCRkpA#EwF~_NX^j*sxb>F&D0j+l{z>k%EaI~q;PbzT5KS`JUzMq~ ze)hqA8l+tYI5}cA0)3RLr~Po-CqIN7IH4?rK^iClE5Bn_-UTUg5T*XkE*)GwyBu6y z?h={?tr=*+G&W;n6}@QiD0%1*KYbbDvHGQw22O(-C9U1nDJz<(gYchWk?vXLCx}Vs zP;Z}HPpo{w=4Fq<>Iz(Wrt9J)#Sir1MA46F78JVM<0%ZuXWf9FF1S4lER~eUt3Ujn zM>YuC2tr{CwjTXZ#sSB$eUh8)o<65zfk_M7_~=QcTbl6wQF_QCGrHL>cxqJ(!mS@V zt(q!)64co`zuxg2Lsw0eB6o~9bF9o>Oe2me8+R@%0;V}cJ`IYu-TifQIwyCQTj05p|n%0E2D@iu-#;ecEv zpx>?A&zh-tmC(dsomC5++=v$_1o4!fuxDuJ70aHw=Q<_{ZO}-h-saIR1VRmlg zs+fCBqt9DA)VmsaK&_<#PG*&V23EmRZ%J^*eG)slkv9WEwrYB&sRBv-o5z?33Z%DO zMor%o?hdT)8kf_?i2+GQmlQ1kMf0Z3=<_3S1r^r3p+R9`G%jlo6jA?A#ppLt0Nk-{ zt~ayZH!~Rx#ckVDn>RDwVC!PQr4c^|pzd#J)W^T2F=qZ2&Gb#+O8gc;x980hyXFs2 zwIs>cN(jFl_q@;>BZFK8+#KptVnbJ%u3~pWVObgLy0a3%o&7zJaHr?n*-d%k7)GLy$ zY35db^{(L<@E`3?zf)tufWb>>D+)bVne_~O!a$I@y`*YOUX+$9<&{k=dWU+ZPP{Rj zjC#Wo3O7)_;|ChRIk=Vra1+PE0Fs#2RRnJFRZKnPXtv{3m2%)$zfHX9#xMcw^k2U8 z15ce*q8BtVkpfiNS{R*|RZ|X>B$fUX@tQWAqkQpi(L6bY2?%tVSLc0Y7#V9MgG7t3O(@bn+hNrPV@O zZItshq5NDq5o%d7plm9t^oWkE)3YMXP6^NwD1ArAh`v(;m$;C zP+3hP>=Cw?o*v%6 zYKY=>iu~Xv+(n^YHTRsvM{?LI6z&rd-Z#2&twy<}C4G*KXT+Y1in=;MMisURRk|;? z!)1f&N*axJD~$DR)$n|tTi%Vlw7&fbHTHseai3vZZOpg4cbt#k4?2JEd%k%5eNonW zOq5_QFD>X}fW-K7=0lD7m-|2V?Vb}2`T`a!j6`fFNyxaj!=HaY?nKEVGtL5DLR;Sd zuZg#Zm8v-2?J&{aZ|XM{>m5gF#N&?t+i5Gmbe(bD{YFHWsj# zs@nWnLc9VY$J52*&qh|8Zbm_{utXJN`_so0Hq&m#)8(`7!Alrv9EemdxpGoWXh@K` z$j^gbU-gWEHbsFite^I%iq+87jEkE7s{Uh;&0VdV`B$ z)b7Ft(TiUYTYf_6}mJ_vHyQrn9Er{#Z05N8~$5%rSTnBB+=J=plObhgN zW{uuk?bLp)JB*gFnAfSK5w}d=ZaQ2zTjdfAq1EvCOg`=X4XY}0Dl}OBTg~r4#5SaP zd%t{!z}XvWG5Nji?d&nCK&f_@>r(qN`S}H16|mhcyf$uS%Pol~5z?T)8BCVyzC}cL z-?vr`Fzu(E79maW@t!IL~=FD=Ij&Hxs zSlh#Y;X@0)+x)%z?yfamrFY3VhD_7Os{-f8_rX4yh!<(%6H*27K*OU!CjI;r5u-}X zySA~~sqK1e#5A6(OoYQslutAz-|1gDzDV>%TO~2%lV}BTzJs#agXJWfW z_!L3>Z0OWWNWAgW0be+&J^^cdl{~D70C)4T#OqzeVxR>n4j3lTZ+3}o@pFBjcEkt0n4xN3$6u&v0eP6^Q6_oxcEYnTw@nVLwi)Pg!z2HoMXG}WmwkJq#NV22YqU>)y6Q(w7|~ymRD)09UbcMbU-UWz^e#9* zK#k&dK9{&7cF1PdS*|*G<2^^&<|d~|;K^kxEBq;ef}fV#o`iLR4xdli+4eyd_*utT zCri2NJsylg$Q$xBr?~$64rYTHEu?OO$)k-h)(I_rtToS}Ufw)F{_|R&)L$pU+>YRw zF>f)*mxM}%8`8CZrd~^<6)=hL9I7|Qmm zL8J~eaMlyg^sIF>S#tEp@tkcQMB#rcKLI$5CJH2M{*(GazHtwztK^Tm(`>mD9F$L+ z29t3@vabdnO}FY3OU1I7ZQ`1FUp}o4$*l!rkC(M@ORcT2q}|>jTXrO+YtJe8grEdge^jfs&X;y|OduRC zvCFkjIN7Cn4(z#A@@CRMO=#|!32Ths$Yght={xga8cNH(gZ`>}%-n*MSUS>c7;t$; zQ`iv;m)=5%hOf7RB@v!Gd)$ctAlvGlX%l=pPOa(6q-WS2g*ql;agM5Cux87 zDtRX>J}!JH5lj^;oicyLlsT*wF?pmbne%AiWQ3OIajGzHcx5R8ekO8M?RStHQoyiePzIa_V;@?7 zZ*yaF=GCq+MlD4fqWdZAptP>-zH2ho7TtX&_AtV4R6=goNLp-iCGEBhs{P0$LgCRC zH`9~d`x6^@1vN@nZfz9GN%q39m0$|pcKF`w*QOW^mxLHvAJ@w%l6VcozJS`bzdfPT za(0B;FWpq_>KCe$k+8HyUk~Y0pFcd#4XY|q(}E4Dl>|F}O&3e$d4(mJI>zR~kjrCt ziYo;Qszx81p`7R!m*yHPVbKZ@1yzu$n-O?J?st)WlCR^;BwMKx&&v&j=QFm3UqY<*%yU$Opq)6640MY>2&Z$thLtECUL`zO ztZS|mW?O4p;NWJWat4Jxf74+Y%iAN+WC~r*CrQ4PHLsPCkuyUt(_@`*Tq|by%T9Xt z!C9|*cjI$h_Tk90r!q0_5x0tu3pg)Zxgt6C&j`obS&0YnA^$gF^R}+qpsyu2C7nXJ z#F8lSiH0x^Eg zs8^~{ncpS69;x3EBLm#$HW8@E*?+SC-KW%ZN zMUHWM9g2;~?A)ccAK$VG^jlg3$&I`1gJvV3{A#-XVLG1%ABBh}cwx68J!U}7gUNp{ z#oiedzd$3wUdr6&*6~b*RryEjpQ4){+W~(p>@4*tOeqxy#HBiX8EK4;Z4Ps zuCGbJJIiF)*4Z}Zq19u$A@lq8TL(>oHM*N#0_&2*UuGxl{+DdB6q$s2^l#k9V@HxO zB8=Skphw2SB~2C$F)U!{+ap!Ezbb^vz)hpSO5&17@e*45 zIac*+H2j!#*tY*J=mqP$pO{lE)!W zel%Ddx+9b&2-psO`c|cT+}_q0%v?+K9Pe+xL6XG)NOUZaQ+)%=zZDU~Umy-S zMPEASMcC79HAY!o=<#u(OnVN3x_NK`cc4E7kcqAS1GsID#np2Gwlc1BpcD+)VQZUr2$#t%C!=li5<6Jj zzJ3N=l{itE$2l8>4{Vt75}zf1j+Yo^D%$6^6d zmw*chI!FQVh!fN|qCe37vm_`_Xje!(n%xx)QuhTSX4MMHt#OBlR5>N$9IDMQ0I#ozy)Of0>=%`!%C1lfSqrowJ!K{lRic zIm$6B#&$6Iq1@qvtMosWV{QX}cj{dK@~BTzjh>KrK=9{qw5 zq6Za+7e4Sls{xT9I8oEYNWmeSRAc^1T%Sjd-VSw{AWD})=b1(PJ za9EN^6(pL37llRiSv7_|f@{EvAKZN>Ys%Z(*?qN(JYdDp%hpgL^FvD#ZTtstU^{A| zCjIt>1jaGeQCR6>^in5MFol{f-#o}4#14W&DME&EP8bRa_UmEhXe1iT>V|jAvr7q# zKf1XP+50opa#CX|6O|$>b3NSId`@GuRKDH{x;e2Ujd#52!(v`F7 zI|nk4N?cEem*i-8q$?%NGBLw25><&g35@fKfFAfomQwEvT&_lVARylxR79{WG=Rj% z4ixZ&I4D8X-f*)YsP(@PLbWZojxPG;k{w6m*gT(@Wh(9(BHx2r@C7{7*PpOBtYjj~ZoSmOu6;*m#d# z0ieE2Mp{QD3WmS6^q`>tun4pe0!KA@fC*TXwo7maehH<%)he039l)&i8xQmw58K`x z(0p`o%!wpP5#X3fA`Ji<{}V#~PiXv2=)h6ojU!zg01L&xF{S^f^T13WmDmyUW?(aW zqmF*_d^ExPx-{Zfa!jVX9Pj$m`-{0S*exEoxhl_8{!%?gUC3RizF*SZ|NboFEop;R z2IrON+vLi1D0$1hyg9_x@vAcpUXraL7{LBpYtAUeUl#RB?D!Ddq&FRXqMq};2!FYX zkGl|mnI}}VPU+w@o31p(z-*Ih00wYXkAerl&2V?v0N_vWsj3)T!{R|atc#@-%H;GO zw3Mh(V6Y4-^Rb$xg9kq?pz+mS2v9puXolKYW zrl@L>Rd^(+$XT^-P3VU9&|vu?d(f0jH86=b6F|{xkH}{f;nTV_oEXzlXb)0);-g>1}RrZk{AKt z&~)h12VUA^yoi6FSmw(mO0wyg@UYQ0T1BsAwObVPrhLH2=SCMn=yztr0s~CPj&Z55 z`a`GsK^GxKfA&5?!JH|_TDG`u*ml=rxE&U9RFbWKqx{2ttq_E;;-~C@YzXg#NsnfY z+XOd$9Z2T@E>%Ix@T_B0Zo}Rtb^vGl&~o#ZB;!F6RD5X}6d0d@HLW7R=cvj-qsHY* z8JNCYlu++E=0E5oD2kWbqJ_YsbZ+68{kg_e+?&KIvi&y^7T8FXEIqOeZ@vxczO%UB zAWixd?Nd@m-h3mM9y)_BMw}CrWTChVpNmjtXqqA{OUN7=zR^Lij1t!K7kY4Y#%67t zc$HAf`=k_FRBHv^>0j~^u|fB3I(pzD8O_-OdbXzc$_`G7zrq8Hgsjv%^=;vYhun9c zB7WAe?OFcrV@QQ{^+V0xc0-Sh1~Q1CM3AeWG5NtBXgRLLj7bWLK=!ZNHq{r zuk(N3YT4I<*q})aQmm6oElg7()VaH5bCO`}-AI zc!Q`r9(D6-_sXYg^xCfK>a(|2dYK^YTCHL_9p~qfU{l%IqIp_y-h|soJ8HTm0bf{w zfLq@RqmoNI%TM7Z*{A@~%n#wXaK(LJkXxfmisimOFm%m1Mn2#)e?jn*&^MZ3myS5_ zd_rUQTM<`4N`#sbTs*XPoXv2I>Q_~_Q1imD&rTQB zH@|l5kTm$Gb^cW@PHF1+u4(yr7PiK~2*2e$PU@k#)5o5gOk>{|Tos zbE@j@?-c&;YOvzds$LiWz%SA>dq+*yGPd;pUP#7;B}k^AFL(Z0JgVl7%|LfxmPNK7 z9}6#7vPd)){*NyU zR62v}4>F^29-HSNq775Q39QdIOpBbEFL6}&j`Tz2TbN2cVpu^efq%CIn!V9mGK zk`5$Lw~8qm%C|N&WvG|-4(0+8+BIu5l$b|8WhmVdA$q7DM0kq(0w_3gP{39&45p7V zR1gM=$b~30(*Il8jU}pV1T1Tg!u;`tR(b25f*x3s8q|%Gf`O8Q0|(EF90W7_f*}%U zh#LqKucFKY4PQwB3TT-`YL}nVFGCaw9Nr?}r2j_1iU6OnjxoRutqiqUXQPJ$1^;cm zCqryE7+QI&Cs#C(__D>;Q@o{awmn z64??sP`rbNv2@Y)2EYa*JP%NTjga<6fTph1#RmYha59xb!yLFNDnP?c5HG(6U>jq= zzYCay+!-=}01n6jPqZ*dIq{85z5(6KX|1ep4zpu*fcryERSh5ceTG->Q79Zc zD>BZ3CdEZr0`Q0CB2uWJ5*Q>R0ty+%03)t0YC2qz3r1KtS$IsJZYWVOG~8-5RM2D; zR8SaIz*ZZu9RXXb7A6G&DJl$Hv8iZ)kt;J2W=tGBbkKA?g~-J;D^yTCfTu;Fkg*Ne zQi2XJ^2VgNPliD<(iMS)Bj``{FU|if#6?`J1q}Ah(DkLQ0wy(@7tI0X z`ya5ExFX+hxATv4#(qwi0je^_H^av)_Y;!911c08Zx|_I5%*p64;4pfB*%B3J}B7c z;jw982S5#dqXyn0lG+Kz64@f}vw)QOLT10hAQ=R{U${Mv2XZfA-|BXK()!uLpx}Pq zNnc1Z+n2m2C0qH0j3g2eHxJdrEd;6_EKtyc83=D4K`+n&6k(pHG&K0kaFn0(jFgZ? zreL#tn|_mZzK<^^Bcka3a;Oc3q)6HH9s$XVLu9H1Drf_+k_wGO1asX#L%}JMb4wCZIR0xWfP$ez!sHbV=q5ZYMP+Pz@^OG zDGqP|lX^LEMYhI_-%NUVARwEZ4GkcL!s!FL`9hO5paNYO|MN0^gB&pfX6^1E;BpVm z$pTUekuByOOhCZWndB|~PX_tTVd6g-K0Q^yeBc}bV6U~5fx9c^tvLt(9X8<`j@9gq zSn`dyB_os+xa`Z%WEler^4jPspkU?&|1+on&5*qJBl(!Sr8ln*+&xiq;yKvBon>=m z1)NLn@_}??mH2e5=Pe;8SNh2`+2dFT#^G9ZvyIeZ~F7}Bg z&_J5{mM)~ZvI1nS7b0kwynn7OO@^NJpy0G+dw}5#r7y$f{pnir0(pk>8O!M@&{E$AYE+>0{shgzRKK`_Wbs51 zB?u-(p15G)hP`Y%5s;G5mbryQ626SB{Q*oAK+d8`fTdU_3_3{d<8w6v5|}pg4S)gz zSCPZ)4TS`T!8G%|EGq2$QL z`EH4d6dheO7c>)IBwKo?PKKu&V&FyQi!1EI+UutukTtz8UTJfryU1FJ%ql-j|4KGM zh|q|7?1k&{%FQk~V2na-_wyEwLbEUY;Iyz5N3ZmAS&4k$zftMF(-6>_AaCk*F@JA3 z~?+u=%w@XwP zzkH-_RPbEvREQRR7`5OlQ6${_gk%zP(Sf+87^!B_7~`{de0?GCu2A5&P5AbPdG?9m zV!T>d-RoYy@g`Ff+t5~i6qVJUAHBe_IQ1Vb=&55ZZrkgyXNm(-`%*e=@7fhTNT6mDk?_lwFg(GzTIYDRVlXLk;vx`Nh zUbs))j^9l}M%iZJTVCUr9-`fIPsh6|KB5Uc-lT^-4mcgWHw4`o@o&n&sdv+fEc*f6JInWoJo30y#%Y7Kr8riv9V zrs0im4kj_2O>G5#pT2~q@3p}p5)E89f~m1pFUT&=b_*1d=K;ot$Y$aB1z>848AySB#IxZ(2z-JuFI;;lX!lI{pf57I>t|a{m>c~ z(;JzYI1?2uGu_Tfv>7g-sd9MZ+Tud%TGcBxbpq=OcRKaypS!g2P)hbQYgF5`!>w$Q z6?3%6pR^pVNAZ%&0{h$aUzY+|zAL|)vwZCemQlBUO$ik}t?EIOI7!F7iOb)4tLX98$YIG{(Ea9?v7KRwzuDDKA}7eY~JLtFAohmf^DV+4L2=KQp{f+|`p{ zpGji6JsyN1BXPyPF8jih(96xsYZS}+&Xwe#n=!dn&BUA&ev<$1u2j@!Of@>23MoHj zyj~V9mg=X!;RSIr9xr0=}9|jW9N; z*T>YDOV@2z0(F^l%G{0gfukE7QUSD1Kz_Jhv}Lc6>tK8_Y(*SHG%+`-P@R zeWe-2$%xcKxqA!KvQ1dtA!2{bVD%ltbi6{DeZev7N}q5LWt56jz0$Gk`;`-^JG`me zDs9aC^XAt)2+`S?|77D+S+3;~<&?U@rDs}>&?Fd-cM`mtZ&`6U*|_)DGp(W9GY#D? z&+-V0qVj3eBM)-$=5r@7>c8iSw`q3vdghsCgGr(Kia*h)_WEXD@=RON&#|m{KKD%X zzw=Bp=(%R@h@Nbm&q6*StZ?vmAaBcFTj@ZaU^cHUY+v7jnMIq<@nFK=OJn zAb-5+mPdQzz(1MvPhRF?ScLg_xs7}rW_@`kKB zE48%hVr6b1$yEEa4ujeEKS@KKZVsWMkzjuxzWMe|h*glBIW${pc`#e_b0ae>SG#M= zIXC77xY_o0(K?fKqz(E4eDwAMG30X^mU#c<)4Ny9S@*cH+#Q%T`2@?o94*7Q*a*Ta zqarlqfgfvyqU>cuR zjaWdVdIyhKqT;Vj(vt?a6QQht*1?ZT@^-u=4@P})#SdfIe?5sD=#`5(8mu4DxffRU z9H0tFKEG%t+|nxCH)A0a4?7X-lQoAR%DuZj2ko{W!jMQG$-Hz?he~H(T&!o89KB79 zmvPbf`?a4yoZp}3lqF*ORC)$mKZ%@b%7n`X@8LC)YeP!*Tly=H^HXZ=-_#;f1_Wzt zyFw|w0=HI?KRUQpnL+i+1?LR==T9(#rKo0-g>J#D%GJ~v0^kA^=akvh4dAC>;Y^KU zgCv(>gdlAq7rLjs9KQlzmi+XeMC`TZbS^Zty?FlEE~~8pe>1lY7X_X8?*=))o8q13 zvMOZazg>)?hahaaY3>t|N=BpM_XSd#SCLuQzxI$cr zj+~v_s-^~Fh@2X`YN@Tl2l+K5&Y4mz43(S~-R8z$+*P*@za`gCbHNPrVLHAzUPMx+G$v7vX`jseq5yN z0SmRwj2)z2aP;8WCyx+H9ndV?s(i_IC|VZBCV5sM>}2x*zUA53!UhTO2I+eaOTO+* ze`N{mW6PaW^l`Xc2sd0o#OV6GmCVtOBitkW)S&A~=f@F6#`^>&0yfJSNqHaE*=sIx zd~w(zV=NCgo2DMufpa=zK0el>s$+NadyP8dHMnh0>@(`2)A9QZIAor)_eTX!;}+6# zRh%w&oxLQ&K_GMpQ-|q~UXn#4rGGp-4}StI@m(i)GR4jObf4|9K2Orx)157iSvN&*B=-*EKOYLmA{-GfGR+`uzK9+A#hR6E*5 z59w{V;B{L`k&Mdv3ekpIf(f=EgwnAMp|b`t$Mopcqh0Y}^R$IwF}L=TZ?)I8j_LkK z_fuWa?hLPNXIJErZHLt2O#?sW4R%>^q1PY_L;5zpYqWJv=}}N;;LZ1EV2oE&O_tY=s%#~kLkn5kJKrE318vEdICu0A0clE8Pi|JsV4#k;@Kz`9RjfH8al!*#A{!gUq|!C$~@<9FlB z0DCaBPoNr3VDQ z5&8oDr+kSZ9()R!CX%oe+~w%@+!m^1`Y!{WYsa^agnO7Z+7GHsxrQY)SumYY?vZ*p z$#&sxp^ngo0c{dwG068FiDJ$#+(C19W{fUR4fs>}+4>ISD5RA_9eS}Y|z63tn*ieQDmv-9S z>7w;e_b@$(h7|W(sBq*yJs8>XS-S{DZ@6P}D|F0W7_f60Lm-_#zf-pKombU3HB;DeRyVJScffRpU zFb>90?e2bSlqet(=cI}ec59ShQ5L9}hUpX_1yq%W{Lp!M=uXMQgt6B*BL>OseU@SL#leSLRac#t$I0cEH~n?dz7gklOm#HeC<2 zs{`T5Yl zlgp7;134hW6$?}KLuaqF>Y7g}W$omH8BuYzFU9-WF&<)~Z|A>dZIJyVP@p1)E6Y8( zI28}o=qXCinB~4&sDB#N`uMD4hr0*Oq_7}IS$swV&NAeeD&#JjyL0<~0Lv#YRhunjG zK=_6d0|iGmXBdBHBW1jFw0Z8kUA@)}*Umyhr&?+!`1XL9F9c9^tFlkr`mk;18+h2s zpTbuBxaAt0?iOS|!#(bK3~J?y%XO8hks5yhJ^FVEqK>dgx%PpWK zHY!~bSnor7L_Au4@+WB$%8r-pfJde!sFt@AhbMyM+3`JkzTJa{Nd^24T$1>L^RzjDfOX2LRb;4%aSDDKnHF5&!m!`?43k@d8seUXTI7BR zqc2B<{|9DWB2Jxt3Sx(6b*cz&GHW3yL0J63%~^zfseYovAKCz{{pOK2pn z6wg`d*~CLl2qw3`h9g>X=zevmt=+hCn<73%y%Q6De`(iR`68m;5rm+{8IVy`^yIOg zIU=*TWyghRFjaGpvSCseHP}TniGWfQtg$JmcIdpZzh|*o2t4yhVbsR$1f#B<_%hg^ zPqZ#KCPiEtfOHi2tZ%nVIKDS>J(@XxK8!J~@%}!4at@y*Xta7F&(OC8g_-=}4hi`G z%k<^(v>RnYO=OIrM0Z@PC)1M)*B_xzAepW?kyz54FJ-gF-2tut1QLHvq7&&sr)(X6 z#(e#E3a`HpdP7Gr;yPLZ{c*Mit~EUA4B8cvIE$Oq6?s|#ELv)GhFa_?#4uLM#AiR*YqfwW zvRr|UdRH>A|BpcWit6%(p+Fb64|7-^Im{WEwK!yQo_48RKA|wdI8wmPKXPLJY6vbAp7}N}+MQQ-%0-(k|YJ9jCKEdkz0< z{9eSTyP#a7_!3H|g%db$v&)L_*4Up)NdMKj?7+)?;YE-j*_80kM!9%z-&@fth+31G z)of|xiqahRK(RzLDc-X%!sPQMwqq5x8b!ZXNo^YoSHM&D4Be93ez{Qo>n7T;RJWEz zuOb$Sdv%<%hM@$96kH;2N*RAZzdB;VPdU?)_;G>df^V70$46f$m&V>@wxr9AGn&f1 zNZ*-!d0+7@v!vGoL0dL-oCfz}vf9#TurX&akb8O!c(cs@HT?2h7(1JgYUy(=K{mte z(q~Vo?BR!{&l5uyBU~R<$+|6CnkyPAvh?RnvM?g$zZ*|{J7P)Wc^PYjPD!K%zu$e5}M znrxsHeyBBI8ZFcqr7_0RFBaavjnKLA47D@YsBB>npAZJ7i)r$(a&dOu zfp-4t6KnA4YZYtwwY{8M^+4xhvdbM!X^2f-S?`PiX{@6X22Uva2yxGnX#^u~Ksne& z?lN|uB&a-T05cV*XAzYk*O`1^BG`**9R!b1)Xl(yq}UQ)mY4xkwl-u-N$)t~OZD6e z>0!8&)ll6^c~tu?FCVOWHk&yZf>5?*=}emk><;wW^O+n+*7efvxOn$FQ^Kun;1yeR zM?QHFV65@UA1z8-MT*F#3ya*T@@`=~aD_px1po}*y7IrY^}na@XHy zXy4{kAFfQ8#v!&Jcj1+2U(!VlYZO$x8l%4^m?#H) z`tD}`kji0~t@AWXDuI&g88dy(fz0*B2~&Mlws0j?@R7S(?vbLBUJ7=|h#-ex(D}GX z`5NJyh+yPf130ynky5=sprRByTVC3MDb>!t)G^m$F}kC_nSfbS0Bhdby8z{uYd#5} zmLcam&`Q<+yolZ)N|vI48HidF+h+wPO^NLz0M1z9Zvx4?Z)xXZB*Xvwu$vB6N7IOQVo4wXV^4d=cM&B{h~ztY5SOE%5%tQy^Bc_NjzB z5ZpriChT429rOoq)8DqO_s{=&zFo`-aC*yUv%?cYoX=Dml#(w-fcYc9kYIXsemZ$_ z4l^M0U4 zD&u#?imA$r^pe~v+gEOQ_u#f==2ZuIjXdPh+xQVqN{HcCBlJ3rIWZvg%pCF4@w2}U zPD}&e@$+j90(L1cP<|#jv2OL?$Tkv^lTBAJ-xhHZ5k!`D{Gq_@lm>)${XL@nytWb? zQmk!Px*Ry7|Mgu}u8B~%!l*SfGdmsS#9vShD$7R>?9sSuymTXLf!4SXLZ9sRxp&mF z$-EON*G?rLXFrv}$(u@tj9D}_S%8qotaCJjAeSG?$IZ8)U9dg~hR%P?L>E)E+-A5A z2$s?6?zO;?P8pfl@-g7?rfiZVxyow^vmY|IbpEArtrfz&oiI86DPg50Gh6N(7ps_W zniCRB?16ZzNY|9!RBA~yj?ISF&#rX+UT}r=x|n-kgZ%tGPYtQetMkD!Ca`!j9HAbA z5{%Sv#MSMyT#4@Jrx5iY263~7u@!#p;CI3)A@A|7EYcZxt9!+A`Qouz9gGW?^`wu| z+${>jJz+ywncuPZ?k4ChvWF?XNP#T!ZQ9+E4r>!Rj@Ona{FnODd^CzLdk+ ze(nM<+aVF+=Brqr)!8tbGer~6#Tjek%bj4>fIc!3(XUI(-T!+eq#hL>#av6SQpaI(OFFKe zY&6oH!gxF^!i~GjlQQd)S*gz+9kr83=11|yq#LG)H_jiqr!aO52wmX0Md#ft_}bA~ zQB+YXyk!c%sN-$8Jj0Zm2#fP+Ha!lsf9R{gs939--bESb=diH>Un+ z0Jo=9bqTKy>q{&15_WFPx^N*llX7;?&1N!p%Np#^qhE(ohieEH%b%Tx`3&JS>9>+a znx2RG=E06}Pz&|fdS1W9Qg_L9-(%#Q*Z}3$CRSmnLfmWTbcdLXeVsn_eXHKyt%9_N z!h0t}56U)&e|>+KwC#$szO6Dc!SD}Pw~SC*J?)b{Fgp6hGLq` zpL>a%*JL#RQMclg&~c2(f_BFbA-sHdd=HySDDLi(vpz~QD8QUOA~ZkRf4n9oR_MZ# zD@qhnL{c|r3$%P(2sO9#!$wz|Vno?0NoO=Z5^=)C4|$lU4Gl#uL5jK;T=<7*`UN~X zxQDriY**ldNrwH&Czxjid@HYaI_^g>zS2aWTm!-1%RjlM5_$hnFKGINi)01fk&-2X z<-GJ0VLvg9;0h~oxNZw6!#Smy94e<6%;TO@{Vn~w^m4?dP1s7PCPnmG1-G@66qjdF zL1eKjg~}Bs()6gZj}ERD?hMAmOBZxpmi+3|y3!?5b6<8zWEaaeGKp-*F9QniDAe4_s3im4-1$TPLGD#%Pg zE;E6(Nhzs?F=uex`^YF^{Qrv@fg)0jChQ( zJM>_7B-Y)wq|B=6;H?j5pNpM=bwag~+MFgA`Xg(&XD%)0MSlcUW*<@VBsO|o>{3Q{ zcopo%XWHeA^Thbdu4UJvVA4%YRCxbVJKM5L3a%h+4Q7klFrX%F7naOjgRQyg%?pJ; z8_&|mro+zcfWfU52u1mnP3{cQue+`K?)|A^=1c9&bjmD9AF`|mF{jCkz1mfAu3dvI zTj)YZwW#XYw79WiRlT3r zU!JBJ&?I=RY#OwIorHJz?t5P%fu)39rP}j;$?RS6|Hb2x9f4QRemE4`AvQDFuexj~ z+qM1R`IY= zIh+k~M7K^?T_K=}KMdndZ5lIi<#VOF^gZj}61g1Ck@LB(^6PHciX_VR4JqrE(*HI_ zs*x4glHY3*f-dW5I>a^cGcwTGHh-2^rjQhSO}9#Drhh$o>@H$uxp~S^pHF|uSEMYP zQRfZ{+27L=TkP~s+ES=yGqbl2KU>X1ZWF}a^O*4rGZBykCYECxP!BQ5II!YrIkzv4 zCJeX)*Z!(e+Y&Fj|4Yf1=T8@V+F^EColqOOR*FB_u(35bQ{+A(6sn@nP7n>YPf8JA zDSQoUBX(nGQ^D5vl4n3nHZ^i!*w=E7IjW%>Y2>f5)ODLI??*f}FZxVz>WkIaDVlPT zbfxcY5aUTX?=}F=JnG(u1X^~{ZFpE8Z;EOLQx*QOvIA!I-To*F{ZTty%{_b)D49!E z^F>n!n$3pJ^jYjvz?x0sHZ}v^PZ;B1g}Ms zPnX#1u?|~kuI}d((+{P-3Dd7Cj8j02nt@RyQNWMvTaPwEcG6PGiXDGvOI?zk8ery!f-;ZT;b4Bl<*vM`AK}F$9vvJNeH*ci| zS8_h^=-G(l?T+~otC$p54Pp?b07ob2sGLX##~|sQ;I0q=R>LU(xqW!Q6L~0TefKhh z_1o<*FhkP4vVsc_qhvJLK%(yNiV77LD<_X~h?m4xzTbXP0FRaF47%(q%>0qZ!hNWc zxsDY*9eQF*|Le^||xRy8s8v!uocTkQXmLyZBvBXjnAQpv^=}u~_u4HFkPirytW5p~DYnX}(U-FYw`5bC9i*4{c zp<#FL{?4Wn(p>H``Pm@_(+HLQm6T9Ub&n#X;ixozVd^VO)QZ>P2{Vr$SpjiRV~J?cUj`PTC?*ngyWe0$^}3o6lspkg4Wr9{PJCzaO3;`Q3X4!F#uTe z+q?|GZBy;Dq77MOQ?ZF1_*$mgfvHmoPbk7_2QJ}-!emiqD>x$CBoKp22p`o4Q^hS_ zXz$63+2hx8EFuPdjSC(ZeXwt$DM3jNn76)aM99%WBikg3BJ?zeS_+ib?^9(KGeFi~ zRN!QF=xMCj6)1ZsWbyihr2C$VwBrzp<=!E zg$bYILG(0wD1`~%h>=4K#(m@TI%ispD3Oe>;Jkq|mt4!h?wV$b=fEUCdF(VYj!bZcB4_HgP>%mLC(#T$!&6D zE?O^D{Svv0HcnKJ@U1|LdO_rXM7&X|S~N3%CyUzz&EgF}$xOcxvEL|_01MO@8xwF? zEJSekRv{o(bkO<={hAr?XFW@l>Bk1FA3E^oj3?SHY!WL?Vzr80=tQ>7;b_Wef1^+FVAwa9ER!VQS=0!rM= zbW~XYWHiVmMoSbh`<^97Sp1i5`VhOX%OO<_#CQR9K#Z4hj22HQeqd9=4|^$jPSgHY z$tZZhqY8u?gJo-nY){@xP`?m5N&?{iT!#nJxq&>uTC{k)lD0$=I;tvV{I-46aqLpn zt1S;8z*~F;8}Oq>1zNa%r0DDeP-)(O&T&!Dz4JLn0suc!gdt-wSbsX797io?M_V83Fffbtt9AMnb}0e%SH(2JcGDGelBCIiW8?I<1$Ue=XMoeQ zThg^?%`cm3fe?d5m)edaIU2yR^K&>H`isklIJ(JmqZnQxQ#06dd=KPukGd#(B=uB} z2ss|Q;$raCSW3*WF4P1s-R_2Nn9y#{_Y)?x*BF->RLewNeMYZ_xa=9HdWD>Zj^qb= zFF}6NYQGyBoLY$HXbR2-4{dv@1oEagHO2C^P9!6IK2Eo~Q}tR@ilcf?$lZ6LvWe&$ zL$IWHtf_iT(U*49`{f!KJ=}AxeZLsSv)o_y(jC~h3qS3Ot2FnM>SUNQw>!zUM=sQP z`jQgW%RkaNM#NEwB7&6<<>TPvf?7j&h7KDOM-KIe>g6mv_Y8*4h@U`H`o6J#(yH~G zrwZskQ)mmL^nBS#lX>ds+qjxVG7|wy>*yqZu6GJvpawhC@Yc%36bA!XQxf?$LzMpD z8@k2RBHyh%aQD3+d!{;}@S#hw`Tc@P`uhIempw7-ZNoX>J& zx>7c_5msiFWEJdcWi7%6N~|HNWpEnEOve>h{iWZvI9pv(ZZ+;U&2vJ%8+_iYkE)e` z$34}!tt6Bx&Z+IsO_^B1<{AWMt;%UKkohb)&5X*n2h_$H9X>0C%^`wOex!;&+g}-m zgPN6rxA+ylutqvihVI7m&HwB`dFT%#9(13I(%2REqTevOI+oSkcf*|{dmJ^WxTH6~ zG(l(20Dt4~3D5#== z4_MFI07OljGGEJAo&Z~xH(f!TYRzce$L5MUR)}{mKd+XBF%8XIsW4#hS@Sz8@?Dat zUtrDR>!<$X>x}C@e_K(s~Au2S4kQV-DQ-KF2+#( za%I{tNju$)BVIgljgOZTh)hhuE@#uyA}WN?F2z;`pU0*B3f;^6Lst{D(|Z`s+2e?k z6j}6Dp#Jy{PZJ-#D*hz~78;Mwh$7W~;cul!MZnzr*phg${I5EbADLlC*SeS?uu#T{ z23d8O176G!dxBz!L1rWI7ouh*dX7RD4)v%ebJ5)BWFdI`LUpm$0Q~>3bPdpzG);Rq z*CDu8xR{(8?mJPK zSLHVP1l<^3O+FTwsgi~D_1pAAdPyTcn2@$$xmd%0q6Q>o>B9uRmqEdV+BWQ z6~`RC55Bgo55DG$$O0zBX?WM@D|;KAB#8>|Kn%d&vK+6I>6oA~;5z3HJ|hkwLk z7$Nf<%MbG$k6cK$W6qznzaW0@${c;Q{k z;;BWPrKPw+5iqHtIZCMtkB3GYQFqH>M}?+S`P)4w+mlFA1vKrzs^&fUWEp@pdwd%F z@s@1F0x+YTKUUHy0Il()!jLU8)FIq;pVvQvyp=6WIjhFVWk|N>>4BGX$e!}|ZGUED z2WDN%4}6?NVhDG{9i)mX+V^nDHixc3l=r%M_GNg$ zl~X`jf5j~>aWxULo#N^kM@rU?H;S#@C&vegcbwvYgbWv__xpZ8L1`kTheq#*2|8WL z-H+@j!-t&D`}x@6Tv1}GJ^g&i!K2x7x}?hCw7Krwc4AW)x}1B>hlOa%c{wn`FFWG> zOWULLZ_(hQ9F&v#orlqpwGjj#Jo4Iik8s}J{dSesx*SF*`SjQK9bIAfw(|2=?zHXf zVsTlQ1i>!h=mPQuqW;tLZ8er~PqWvZcC)pj5I0GtIbPlHXI24RP_56M>-dvjW9-Fz z!HpFfc9fGN9#OXp`d*Nk@o~PxfXR69KWG=}UU2l9fk~}&EI4Ykc%v!2>~nbFhN45i z|BlvdCUs#l))x!#<+Fovb#ip}_j9Uorjc(=6?ud5lyktk`>MU^A*MioKTf2MXB9hS z-1B9xM8udM`ew6V#;$l0(3QcVfjjOJL_O)T$XNGS%P`MuVvhCD73FQNAsxV6CG~Sc zuINBQZ-{Gykp#0twv$gTWK=XQK9YxSOKfuJT$>wu{hw}qEG^om0_2bnM-bDL?&0U$ z@5imdo1c77P|!iPnvU%A`_LZ>&FVi9HAf05&zpnltAp7tK!MpLgJ@pZ-^gjZ-3=< z3uW)~W`B=M2C8KXL8V6%HpmR%F*JbM1fLK(S%(~BRuWl<7$fep@q~>dMj7@mDCag0 z2O9JA|j_E+&9`P)kIbeR+tWkygfQv^hD_{n`6YSGK zK0afcN1$sBSe7@CHGdmPH+%m>ao&jCGmwD?2l6L?12M`QF$WTqPdv^~Fg`$ZU_cJBm|(AL z{H5K;bHYAX<~G^rdm?w4(s+Um^hm<5?Iw|ZKuaC>XfHrad>?Q;0QS5T|LA1eoLu}5 zHaz}YtWBVBq7wpgNNBtdX^SILRZ!$!-IXDG=ycG}BttNcSWO9=5+8B)bxWDSUr6!_ zvh?@!QVGbZfr`R_R-^%Sr1(qMsi=?$@)LtV-C#STk6?Z;5{b2lt5+w9>6b7&?I7XA zJFL{`6JZ~5JtTHSl3uiMLykW6{e?JtL=u+1AVO(o)2M_I;`~B?Jx*;V>D#oYS~6fy zL{FnSt@&AsrMs0J*sbVo%kaV4M1)6{pA&-g{GmV1>BB0V7)WGyluDFz*Ob%e0gfP# zgvCmnPppM;EZi4QGJ$yw2||-ktfVF`3^*1hKqL`(+uznOezX^|(kOQ$ln5vjfKc6X zNS=ZwQ@V4SN(qg}P!FP{UyBO-7QRquYMd zcJ!z|-1k|K@r{a{o#FjpaX>eyFlkeONTyPzYEuxSQKbx4VH;1{P@Ll<%SUh^QQema zilu6+S~yn;-9v3@!pf&sby%m*+n2DpvNU3QVyRyzaPP`Hkgyq!oc3;u_oPIYsNQ!m z5FAa?qEU7DwT=6uv%NNw`A1v4=AOQ?L;ef&b2zcK>G|%VefM`RAWh5NlHMghU(^dp zEf&E@(#9{2*1=88EaJtEIa7_3@uUT(1ks+7=0 zc&aw&G&u(nSH!Qtw1*DbC-3MqnZ2ZSfuPc}YOu!7pD^ z2H;RGEdC?w|58|DN&^XUf%q^1kTAR79vFuwi`fo0G1fU0HCja zO;Ir&#;(q@p%Rd9YQYinrvu5O?%WJ;>Q#-m5axir|8j&KOWLq>$c8m<1AeZl+LnvZ zpMj7^unO=9E_!Aeh_C8k+h{6U7uwFowhUCFjiLb=tKw;ZGs~l{CTS)Tq&3<#`2lT! z1=Mieyl&r=`Hyg$gqe^2M8*+M*Be!rJKS=v0M4Fg+soIu$ee!yPb=HkkhT(BD94io zfk{2CHH1-$yI%S#0@n&eJ;5&p2&0SN&q>s(Py*HayheXRDhcEveoqL1vXfW;ek!6? zl_(cKtvrwb+%wVbWI^mp0ALw8UmS`Gw^K#{NfXJk^W=JJ)hMG(`}7E4vu@_i&&AzvrbSct-`)r1~_ zf=yO{Ju%+u6OIElPza!I!6Kqes*GB|o5`eTd?WPhcgHu#@O%J=5CKj=E;-gUYwY@J zCS}9rec4a%5W^(XA@XO$=PQbkTykgdj`>78^1!v2{q@qO8!ayt1ggWnC@HFpc+wzA zs+PqI1eP?FIIJB47s!qKv=KJdJ;g+Dsl>Drg?Mw=v{Gi-fGR{}nu#NRCC1q;9H@K- zWR}C2Z)SbMx=aDF#ZoW~g0}djZQhX*DFH29UeS}NFL66G_^xxp5;fe=TZkQ`l38f< zN7m^L6IUN#Q<&D~2czv#98vg3mg%>lJ3K$94g%Ga?y($`jltPJ7cwJYFR|?&h^Elg zeHo7~BEON*IvPwhz`z5SkGA-rMHZNCAB;nGHAe{cuZw31C`6Oj%K5ZgI}bm{ro&7l zH-zsbJc3E{6qYQGOFuYlmoh1WTR=0H6;4^y*_cct@Q)q6v_~d%n9HQ{GEMd}GO?Hf zO5qh6Jx?fB=lnE$@-uzm{J|Zg%ynh5r{$05?}UBOGV8qHO-W5YJef741GApM?uUKQassGW|BgpE3G4$Eq&;8AxdVXXrkME5BwL&SM zCl^+vtTHDxb_^9*$$|RJMJedRUtg+W7Ma*Iab3i6d>{v=zSs)on@f@9|0%noIiLgEAsnSj0>H-{J z60pe4*1#%q*@C213T>0ZYKbCdjRK~iJ@|vZ6Y*k<^p^ zK)hmFzCxOWJ?WVe>#1Fe0v7-bAe;anwRkT>D)&GQl0BaxgxJJ#?}t+9kwMZ*{T13e@5Bs?tJr!H8} z8{!N1Jgwj^$gBL1u>T+dzuGh6a=yDmF#IF>=>FU3q$`#37!P540fM$YA&m<3cZ5fwNm=7hbA z@r@zk9*9?wH%yL*aE|E?e3amcOpi9Zup=4!pPZMFZz2wkjAvB}0xKf~DQip^u&+F^ z@X-w^;1RQ{)LE33v{g6)tBfaE2mt;8xa2&->8OJ7=*X3Y*$PXBnP5AG1#bmJPU4G@L%Zq{s={>_*%StrrtRCD$ zN|%pMMpJZ4(o7l{ls@$1Gs6lXr=9Tm>q`n~!|orm>#NPG5n3x<&|-!rbQ-b$ffZ~U z?iDap@-<&4;dNm9p!%L2@>SCSNVb?foNUcVDW3@(Rp}E_dSzPB-H( zOPNoIPIv3#`0FKUr0GFfx?u}qgo~JPToyxXuY@p}tQ4Ifi!R>toZiQG85E4m*sfxF zeC`^&Vq!5@Aa2-|EBx+NG;dRe?mO(#h=9oyf?Zdw6!|{VYeR90mD8mjhhpfxAibJi`|B1&S)l`* zt5CUBpog|JWK#(FJNoXOx%<{%la-HDmX#$^n#Jm!8&*B$jcQ;;YyifC$v)nZKK@P} z2j36PIH`Spj~>TIh{b!^1I}A@!cq&+Ez9&)=XQ;ncU*2AP(Mh94==QZDZwz^28$=E zoI_Mu47c+%sS{4+pV&k2ulRJp)u&hfw|N^;^m2sIAK9(&o=(QhF86M#c&vW6kBhA; zcF**Hiew<2>-NZ9Q%6^zhHh>(xhDmwcUdsA?)R=vV|9^loHdAE1FDPSC_eD3077sT z6Dbn?XMyl&lO~osdiW#D36`YMrf8099mQXxosmaHTt8avfm>=2E$|dZk+fOWHy-Gjg@YuHS{C@@see(vumxy-V@m1Q=^tfU|R6iZPw($S(e&{UK<2^`1fUlEkp~Gt8HKx_}Nt+oB;>`17DWxr9F*UY2@dGq`Mavu@AyIzP%8PivP9L60Bshq>!JujC+ zuY_91vLJ`)fxL6Cs_hQ6OpoXI(fRBnqDon->eLj3bc#xW2pE%0|qo1nB*y7a=E zR3f24%4nM4l0kVEsbOK{8lrq*-OZiDSg(dbTTZwg9gqA(P6=d28Lr1iW4b!GC$DH` z%-!R4huIr#)0OIG!F3G6BNJ9n$k63A{8J6m(YXB^0hX^g&0E72Ebm^A>J*vSCp?0&0AB65~L5W{gK5RH1h}8r=PCvw+oQlYgdc6tfs5!Zs9#P zf(yzh->9LUL%geXB-1nYkAGO74;oKrJ~K}&1Dhgym-A1rAK!cU>>r}``0}su%7S`6 z?p^};B3nZ&-iBE|_CEdive}0jebnn}w_DG3@9(9Uq^uid;P*bV3CuMZ9L&7*f+yor z$@&h-C*Vc*$$H%(Yq%)RcFHev07Hi}UJT%SS``$7HNX2UojHZ!4WS5DvZPnjdHGGM z7?eXBf=7}-jh3+H5p~d!+mf;sm!c$6@%v8${JJ&5V33QVw523f2!%jsyh-YPeo7cQ zoPnHnqoe$TG|$AHfsWRV8TgWmV!bCUQ1U~yIx7~}_S0)K04;x!?*@!Zu(MoBfc zr)Ehtiut$cNHyFVOvA#G(MPh3!otuHPLfm2zQ@^qHI2WT^5$Pn8fu1Fko+zZQVsqE zHG_OGVO>e7Mn4RLFN}zWVYWW_ja0)R{{V;31Sr7Tbj-dGNg(3Df=E@B8-@n7k0jdF z0*XvoGi)H80~lh)7f!`s)iNMFZUc=bHy{i>Fw_B52ArSQ1(0e~{?8BJHsmYBbU;{g zX{wofO2E{yd2LuS@J?1Fb%7m3w-y0M0#)mzTxt+Unkc<|5(gG7Ak?;sG$fSHFdo_& zWDgnxbmRy;HoNk%osp&B6rCe6w-XaB_W^hQQ@Uc_ChKhUZQqQGh9+-{z(r%5mAYyVu&cZDE9TFsnVU&jXrb#JR9rOY73Yn)Pvvty^+j`7nDTY=0dEcvkq^BKYKc** zK!h%9=P?r^ zz&6D%uVIvIyT2M?PJT)138|s}rH#4RCp-z+IK6rSHieJW4NWaT1@5RB%KsEvLnDyj z$WBCEM~-@3PN85lD3^nc5kweLGf{UsQ@GzUXwZP5>P^TWlm8+tcJ9=!8VIn>c{0Hw zFGxVQ$8Bk1){1v;0oh$6XIV9Yejf=O2pv{m(0baDD7^$QU{?wc7ON)pv8YErwmH!I zH8#S#EWG2OJP$)?fEIs^aEKd;zQSI%t===F>Yjm}n@(|Ju|cbM>R9{R-q_8;@3xNAGX`ksJ?h6#OePH{U48!#Afh^m8JD z0t0xqMV*1=1Fas9cK5bN%w7OuL)U@-74jm;Y(v2m05#1?w@Ey8iYHu7sshLjjm$9yV>DN)& z1~{^sgWMPq;2iW+JA4NNyuuzF(rjmNNaM%zkx-WD!9j{xu#f@cwm8WBjAKZ5^q(Gm zH)97X>{Vz*`yoSaO>3Ixst5lmRN}jnQi+pNfsMuixtG-+(o4JD`ngED=uk^ApyIR1 z#nFK2S{9ME8jy>7R4pO^&-_V~KH=YhfWl?k`rsBvx!AikCi%a8V@J;7DxO?GD_o{n zDQIY@_^mRj#ulKtvqAn)z^;D@Dlw5b0dO7EM0i8+dWHlPs_qH=rUL1ry7-12@0?W^ zkwNBwRm=)x*qoz-ck4xir#iRJLWvi`3#koGtLQYYqsffdN5=n%JZ-lthOQo#h8m zL4o{_2IxJWT3QFplScK-@5>A<1m>$-Wpo_(WvwhF)d=in4Gu_s4a$NPO#?{1L|-=t zoP(C+*`=>Y!UA)B2=`0&BQ9Sy95|*WK&DGAQkO4~mA-DcPiC1bCamKt9ClmbD@`Qt zr57BB6)?N?fdRxXClgYgW?&p^{Qxv86n!e>;V)Zs#_P5(CPLEgmRiGGT9aL2RuuhC z>vHc2D>HGA6gy0_-r(>;*E9{dF108fm-d9=;zL^ zFTf+Xx(=w={A1=FMGkbswkAJV7=N_shFJ}WFLaGCf|iKi0`C2h13WzIYqMv{fjL~V zVjGNn8FSrS`gtsFKJ_6o4LX|tOaN~F8I7vfOmt9TvVG?V%&m{3nyCSEdnjA`in=)| z-2N3ayYXyAOA-Z0-1X2CR-SUP`4^ENX!dL5R}g`j0AS9O-q(?l6-FKy6n=GOR4kSp zz7Y}&KVVlBL07vwPA4u9HUo@g^zJ2X1sw}Gs(EZ9<}v>2T;jAXdZ8|0WX7)e)tji1 zAF{gLadBzURoR2Z%JqyWFjI((^+cytAmxmh9E|rn%mhR!LN^hsQxd<1DVPXb++J>? zCib1qF)*2!MD)>w0E|p5Bs|#j6p6%4^9tc%;-;xmvaEM<@e>OY@o@>IAf50F6%!7u zh2ujMRMa&ft#(A?_Y!V|LqXW;fZ1CPHhHbqxPw(wxy-4RS!1@(@J!UKQ$uQ{H>I#+ z)X%|c3Z>ZwD6lHGpB98CsBC+>>LL(t6=i17Q}?6uVe?Yaa#mCv73qW|vZW#56{=}_ zD8}O54o=-)&q^jn^CsHv1P|jV`zF@Zkn3`^u`(hDHJf;#(fx~i!1Q>Z7T*Ytv=**0 zr*J14?U=1q*!>~oB!=0=YHYeE<3Ed>Oq(a4@t2|uD;Q%cDA{Z|QrG^r5KI#HnMI0N zJ!2SQwrV?unt9(YVC*uDhZa#1NzpOi79m6PL064_A>9OsNGmyl);DHTm9EEPlVLUH++nuZWgQcLY_W--?y- zbOenH-ilSwblh_wWyQWKqZOa=A(~3)uw&oSUlfnz3|VniQCsA`VP>{$s{9zBhFdiR zR@j+1Y$@%i(i!R1GFi9P4vlr6a9KGG7oL{wCA|oB##8qAAiW5p)7<;h9JGhiQ2*WT z`!I)15TXMCa*ie}7qUGA@?XuI0`B!W9E&?-LlbG18#-e)>Ha;rz;Q*Fe1KUwESvwX zunQOAuuN*xKJ%t zfSkQD2V^cABEGO{URt?ztD_HjziPhpynTvaQj}EFVJEhV?|zpvfvs01wmePMGcM64 zC%Tn&1Y!_rT*kLmsMvqjtwhIBh)#Ww66gX6BBTW^g5&H=6eXw|GmKM_Zb|f!bIh7! z%UoRgHwW3e;b&%0#RvVS8+zsa$wf~}y0AxmaIL@D+#!PNDb3iQxo=z(u=Qt1mw(@pJZ~{>^w)-jCuVkbA#pB*Q00=NW=TisvJd>q zshZ~mbp<1Sq!iMiFcz;cW|_lMiE2bk+&kxZ!H!Don|-+TskHAm+a{yiRfA6^ROy!( zXona+7`;>`3^8O6sjd1Y^U>omvU3j`*S`;x8&LaKdoOZ`kyB*FeL>9vx$>nX=v#?B zp&PJdbFxHgvEh9IN;|kg8QUE=-SkALL6~KIo=~oRpeBV57X-QWeV`|odrwb@<&AwI z?lBf>L5Vs$L;#Dm82NrQ-tx z?&g1@?>j06yMc}Lq7MFp<`dV2P^65I*Tw_%(UitR@yV!=l>q)c3zKMddd+DST+JIf zJEVsMKU1q`WhR4eJFaTyfbC*WxkD3iq8vOeiM}8z$x=j`pf-)|GU*o;`~ud|QPI+< zhi78h7?#8$5daG#yNQ<&>q^_xc#1FYv@lD%Yb_v~^z=eoEkJHkP-MruWC%jC1V2JX zyNIAQ@9nQyDd+PGWi`*>Xz*4WCw%$pixWLQ61q-|29o*&Vd%p4sUM?1GpDjoeyMGX4cuv>rQ zvw-oQ#L=*F1s*iVScf>rNNaMj4gjziek<&2Ddo$IokXkl`p${hv66QZuLnQ)%d`c#oW?u+aLsg^R6g6vB7z^4& z+Mr3XPHY5L5YT`cUetkAQ?abv;I>Fqe~P-RHg`;IFc_$N$fY3 z(El75frA1jr%54+X2IOdDOXU(qEG--aA_t}E7@9QQLBG-LWFo*fbp6IlBt0tSvj+{ zkTq~XV7(me2NO1s5dFA;7l>$2Al#k$J^o!`Ev}!m0xGOkUxofNNFmu<>{*!z2Ih!r zk)n@Pq?Rh7sS4WIh^wq%oX$SE{;5XMST8D-sNULGSQ#EB0bM*ZR-R_2-;1K5j!e3SEuSh#WoaQB za8JSNi-w&UD7rmL&l-q5P^T;=w)gCX2lwt8f`Y7X?b>7Yz zqcbNHZVDa7nsqK9_qW@xrEFVdQ?~1kDxMttsqRtiSSC-?G%r^MQe<8FE37x=IGyY^ z{3mmZ!cM^kG4*k&r;I@l)p!T6NTo(;{W2V{iim(&!K;FMSl8WNH`VuYs|DQ_x3>po z*rcq!Qqw+k!&&jRXb&L~mIB%bc}%JU(r~$1W_SVgMJUG73(9oInJCT?adnZRDziQ{ z2(Q0m$ZB_38D(^R(7#v3)g{BwS`I6{RB$$mFetns3k1Y`dL1N@D8+*M?bPj2OJ*T= zM9Ej6BNHVleCjBTT?21pIJim=1;dKBLX);yHX6o)MwcMbWm-ZCi51XDy0+^E4*xFI1dyh3glH~3P=x4 z-zC%gpd}}qaa|)&sRKn{A{ZzDkcn$SCY=FYqwEQaFOV?6huIzVweo4occhr$ zJ!gB~Y9p}`No0-rT9izy>v=7@qtez|mDbiqQX>Lb*`NjP=Q1~BBO^=v=lUlG7#W)v zYBo@_Ge$_DWN_Z8Ix`Dz>r%u?(;=)k;DtZZX*(iYg#glZ#n{W%RUn~rnj6yET3;|wD*7CApOOL8LP1DldzFxN+ z*HxvpwLDbu@G(=BtXa1cIuc{ht72fPru6TpNErF&h`(YcP8W|${+WltV?r%7(a}J| zi4hg$a=Nawpx;nAY*BKJjAIykt-ajjmGE0NMd=pjMQrpn0`qDxV>f}XenR*ayWKPl zmIT((>Z34M%ExS(6~+6zMRxHzy*F9E!6i-Ppq;B|n{Yf}on5BmM(>w7?Rz4U?Tmka zFlitr1#PeD#je*B75q|CoIS-ucfo{}oaR^&3{o>19qeU#TE{3}%&8Ef*0AC#rss z6O$q4qGuN<+H~CFy;EB2Q5nZoPvWZwoAS_F6aGsuH=09JTheLiyGW5XdTfzkt)?XywHr?= z?%sm?5LW?cFRo*k@cF)&p^h+Pzfom4O`G+l;-yl9nCH%GZ_yfuno)eZhlMy$_FHl~5V&oGcG zuurDk9kfSzLw@(uEAD~mx_5x?et~pCNw<0bGM9XsgIn2J8BykT(E!hu81+4~{MbLy z!_^~QHjORmZ7n~elEmZZo61yxuB}!11?fxiyj;cpV?g=hW$4GZq^2~J7<63TRBP@~ z(sO}wf#`AB<0flEgPLiG-UWN3NGg}Kt#VojD!?Sp-k`pk>l+@aO?Z7{UTlIOA z7o6nCVSEp4c|U-!OlzE;Nb$4!3m?|_c*@cBDqYokw(I#%PCNgEbRND z-qyYwW6Y&REzH8OiKN#q`o?g{SvCjpI5C`RBk=yu<-%gPh5U2*je)1Q&Z%h~H#@F7 z_pO^vynXSX43$&GO|O#fU80w8)-HdMQ+QXqB8)v_GdQQn^O9T1vEoUKWNX;d9;%*( zv7Qw|0HRF~%hr(PUR|b*CMfPs__qW{ZTx{QXG;Syenl3a&^7+ymPR3}WZqqNT~$Fc zzf=#oop#uzs16PGDBH4DS56seL-URKP~$&(<{P8?7d(4+vLGtR=4ov*$c+=~i7N&k3Ke5J%V40-r1>>ik^bD)VGO!a z`!%6H{@E{O3tVXIZ;a4E%aoDl6`7VKdF6Q6I}M>ACx|3s_KE^cjsGkbo2?}OMNRq- zy<0eT+MlToioMb8#;~q~sVpx37e271^XzDzCY_$>3!q>9z5ugjV(V)Ef6P}+0az(se- zGe30i*FDxp^1M?7wVlE8Jaoe(8J=4gzGVx^5+whwS^Q&mjTaW2z^v$-m;|qQamjSP z)6>gpPEdOpBJtgmML?%apATW>h|vB3AjV@CIeRGQWYL*cJpY zYsvKBzg3}l@$l@3e1Ec(xqcM8ux^Ui-XnYcE#rdn{TB>JU2^<+L*OGh4{q!^UHkc( zJo)CI=|1>@7hH+s1qrCXRMuR46foMBD%ndx!c? z<0~5f*#2=66oXb=N@jze_=2OTe@Kyv8=y^uhIIb+E)`eXZ;wgI7Mlav4xerQ?{}7h z+Y`$cMSiqp`nimXX=iX}#lzRS`V~)t89f9Se;K#=JcMu@g+UG-gbZci2D0!%I|#OY zeg1ro!d?UR4pkVxY$~zk*Wk4VC=C^$u;(UVq{kIr2BdIS>x>Az_S{qZxM2wkdp@ zhAFbKq=KN;Rs6+^=f93R{rnn<%u76rpU~P=g}2>J`w^@6gl&e?bd}F1As9uHxDqQz zT#`lo;;SIo6B8@K_bycq@Yo15U2ZzYyO#WuT2)vV*%i1-hmuyr5CNILE!Ar!8cq7= zytSHkVj3EmzQlY2G_Z|*pSz5K3qhDGiUU}E(7&xyWMc@qCW{kMYz&aOZjzjT!dWq= zJb(cL2OM@q#)qn@m6n0^ewT|SOwS1Rtr^VMc$^#}L%!v-x=o*%r5bMUwk8w~? zCO^TQOq0&9nlZ}Vltzuk&s3jQ+%&q=H?VVdm%PEhbil0ktCYOp z8f~l(BTMt2cpNK_Nyeg)H*ef&dKf8<*vVFEPwKPGwTR}c3xU;!8>sUJ_4>J7j$~S=U$cH zYVac0y^CB3h#iu_JaQdN5X8U{m(B$FFZIOHqXvH-zNEbpVRT)G|4H27ozcv^paOaC zd=U=P-btoG8NTf1Zm*_|7sUQfq4OvM>Fa6n2Bs+ZRWxjzA34?^P+Yzv=z~Q>SeK|-T??6R{R(`g`p!H1~Fn+G7S#dCsDGs)&6Od*q(;Vp=el55ZW+A%zQ~a znE~hw`8R@^?uvb_UFg1v+B9Djy`ftlJ=IgA`35tDiCS4A8z6k7w+I7|AbL-8N7xV- zvOKO>7?I)OmbZ6%wPvPj=(Y)X2JQo)O-oxidGp_S-Wg4mGe=I2O=C#XB^lo7sEbzB z(FB|oX5wxZ6;_2+9H^C@`0O9Eps9k%R8i<(aa?WaOd zXgeGHnwI|KC1st8;rim3{g9{9t-1_4>{#@&f3y)VjChpFmW7{)3Y@l16qv*{u z^VU`V+K#Lse0k93e7HisGH6EP*rY<%=f*e02WOa)OVW6+=Of}i<-I6^xpv&IK7;b` zrp}>s`H#)d!0Aw*pll`b9oAaQ+-OEUoPS!wW&Q7>l(SjFHh3X)Zs0Rq(S=4n{FauE zTnA^o3#FUn+Z?-YiN7)~8!>G$_z)36Ro8OAw?ag{F!O){KgSyU5=8>xo`}mJP-?^s|zVXK1tU=#mPeRH%)KG;oX=n6oGx8qY5!=5bt_~Jll@z16IOsnJmp+g42 za$o{^npFM7&FR>8`$=y{@kXVxKdIv}NqAl{B+2$yEV|>^{;0azv71%~g_Y@M5sO3W zlIv0<4y_C98UOpgJ zTAJuLo6ZN%KclpyM({M^Vu1fRIR?Yxa0kiTb+3}yf75Vx0hinpyIPye-v>uJ~$wmACyiFHZ*VA8I-f7}u+@Svm zz3N@OMm=ZEN?8T2>6S%*!A2cye)Ue)TDa^6*4puQvOl6Mk^Y%IqEHYt4;EZS^1kQU zmj5s}R)IF&N1BE(q`GJlx zwo$_snf{tXv|aN3WB60`5-O!COqQbHbufncwKB}Laq#^O^zCY&<(k(u>QdKqTClW@o^4xuDh{d1xO3e?_w}EsE!sncf$Gp@xy_0E1H~#e%)6oXjC94Iw1J2@#h$k^9&eNNZ^E9X(qCC;8FHa;WR-oRO-iPp4P!34bE!+hH<{$A?#TA0 z-1=zCtHfvTk5ord+gC44aulNOB+RieO^gQ5@>B)-Y<=lVNS2t0xV5RMJIILh_0QOd z1zx0RE2gb6>87uUIdxb=M7)MgN1|t0C&cUNojcVbv5o(H@vmR*STq=YM+(rSM?@Y4UJ1BVAsh%t;+4L#KUQ=L zq*+b3Qe$H2O<7m$j%Ry{hvs6%IPQ({Gd}+OUe-Gay2o=Hmt>D|WKQt3cyC1Sp@!s# zu(QYSL>CKfT82=nL-Z4ZS~)=9rtqb8g3`u09j81~?B0&{G{4QRR0tJ^zp}qmro-4Xyz|jWqCyp zL`>|J$Dvc)#ucK%k@8O>^dpW4C!5P$rJ5#%6!>_e+((MIN7AvKDYpG5gM8s>sJ6F@E*a;nSE+F@94?O3#qT1_V!#BNT|?G4v)s{h7imAO^{1;8h0e z;Ni->8+QhX&65q4@e1d%2Z;c>S}o!?`WJ$-fWcJW@o;@{F*3v#V)2X=3`^>JdG5I9 zgu=Kfv>%2vU8I!_neM;eiwsO{?8=V$8P;a@Y%;V|x&BQ^Tb0-;>i^bxGXDL(%t@=} z-J^F)3V~JY0r2im9RgdY`51o-$jehWOZlA#nSbe%n4**5#!ts$Qp#a~G;4S~YNZk$ zv#++%9eu34t=a0tK%4ga@1pP}|KNFo5&o?;192)~1SbYSzL50dK(6b0o{i!QiG2^= z@+P7bf~1*&VQz7VW5e>tD|xWt+^zz3y1s-5Kt~IjhPo#S2G=;ItOpHp+UrmE?9WpP z^#d)xvP(%l>QuF~xjeNlD6`c1-Vz#1kvmVNtnBq$(nB_2j-|YEiQhBl_%pZpip2nV zk?lk5E(~C?Yh8{+nQ4Wwnqa*9&q|xd-2PIXGSFYvO;c?ud)_#-r3kW$Y#afPKbki| z-_Y7zTiaB>is08p7xR|$hj+V8lKxK%mtZDub3N=@Maab0mU&QYSex3{&uJ?rL zQ*`W^0R0<1jlx!ix0c?XsYH#p@%)v#g#7MEq+;2 z6dale78XIpQiB~=$o6!!oTp{rW~c|_>(07m1&ohns1CcxLW*Q%HUzQan!=E(kjOIk z>WyDVirZ!3u)8Qmo{(wcxddxuv}q_gTfYI%)9-RN7z-s$f& zVirbyA@^OJN)4?e2Gvsq39y~xiEHq;CDzEfscml&2v!cgAuG=-Tv!;{61*yd8rxY2tR=h{#E)To3g{q@S??Jz>aPQ>| z@>T;e+~<4kQ-oVVQP^lbsjOS6pSGW3^iJ>W;CdHW3uq94l^dKGH7c)s3J`jff!g3_$ z5dGHh9^X!XNn5&ydrlZ-HM`I{?euGLM6r0URVg+l!H^=Bt_Hb{R9^75dPs_7VtrD4 zC@r8Z8-HpkH6?BjNzX@#%p^Vr)24;J`%e=OlYYgkt!^bF`5$J|xDys%TwhCjFGd%K z_(zSSX)^0}3lD-P0{BC`!gy=V5ca_$VHXXn(QZniXFiPrK zQ;&I>jQ+@?tAQpf9;L~IFfeYTBMon0GH$!Z?jd>fQK1b$c-^FK=i275i?pFapQU4g zXd%9a31bd^3)7F34*t%46wdGw5g?D?CBGuX(Pj+ZjaR`0Td-t=dC`jBz3yowwHwg~ z`>~DJjnBK)k4_URi~hHq9Pzcz3bwNS@9p>E$~tEz4qtG+ELvz#M~M`oh1Q_Mfwr;# zW9%=Z>S&@hKo}>uL-6444ncxTaEIV-2X_zd1PJc#?gV#&26uONhiTq>zi(#Nnzd$r zR99`SXIE7>=Nzhcoh!bVZ>37r($KSOPF_&|z9nMc>cPE^GMw&(tr++|@)@rUvnjb4 z+#TleHW{u%Q#mU6s?Go4^u|~q1mWAL;lr!kjoC$8A=gW4kL@V^$SnQh);+krXzDRa z)13&MrBJRqGU_%ItlmjX%s)m6xvEf^L{d-oBNhYQ#yO z=ClLXC!9ECW5*6MZdO>e@7j+kJfmHVrT;$TPn#XM!}B!PN5_#2l_IF`#F&Gg0S9j@Scc?MR_?D1RCFv;4TVuH?|y8xvL~Yvy6vrNbmhb zcYm6@457%(%g=<@6!Q-q0_C1y$)^1&Ju#tzDr;QwaV4%TnSWPbpfAmz+Kq+ey$Y;T zA7UjLE*gg=sDhHO2;$EhXnHc_3XRmRDY&N3i*T2`=2rM+x}&a(fHkG&L@SiJ%ip6` zdd6|%ByzL=`S8;(`N?UhI3SIus)*aPqSCQ4G5fJp~IzwZzCo$e)F2qVaD$~kgs7}=c(=e*E^1!WB zM-#yX!=K)le|1v+b<>ljF54S%TbGMQIJ8e1dwX`<@5seoQcG@(k+%`tj#w0&diwpdd=L zCP^tlo;1WM0cYRPp!?g@Nnefs3)g`R0iSbJd0w)f7(qJBcdGY$nmN89Hq%v%od*j> zP!efjJ%vW-o?`Eq$R9P04%=zXxoB`Dgih`Ve>eO~mGRzxV1(49TJd?%p;Vyx3VAEvL@Byvrx{`WsgWDufXp*@l>0+E`jlFFp2t7;Cgu7*H&DiGa6f5k`bJ!5 zjA=ZjZd+Mmkiy^RB$JCu0E=7>a-e+ua-?B-Ca&uNf0E;7&4D&@@`Q1qpU9IsM19JU z7Syn`irBV89QIVkh^JHMug@IP#j5{g&J8ds6q! zCl-Qqrhr2%2Z9U~EPonj#>|%PevI(mp5VVert<1cnH=J732xhQ5spRA*KKw7(%G51 zspwUvMw5t3;FaA_j`!bpUkmgInI;krt&x2D?;v;2Aw8bxHCq=n9N|mz)*+6NZ*IDS zHmq6=8e1}kR&Njo=o>2XWmHbt)4p?ZUQv8GS6HA!U|z-Duao#EEFGCid4pXa=ADW* z8=I--W_H4~y*`8dI__qFl#Y8f!X26n%Q!>m8=GkbLvlK^Zz?%REK5L!Ej>8@eLP!# zun2b{SuCc_QH@Z>i!K{w{$MF5W}Q*$lvi1aH|@(DeS&wyo};D2h4Sk6@_n+|k_IJJ zShJL^2z;IKbX>httXv z_)qivXq>Vy^V=-NHJW^du0OaWv`056ST|vwcTB<^)Y)^Oopk}mfo*_8BC=g{r8ZXE z7lO(89Q881{!R2wN>@|}7>TkAuQNzT3PNQ*C*tH|`eX|4Yz{Gt7Rh`6pdRyv@Gbne zyt;f7%h0@O(nHA9VGFDiNV6|C9xGO+bF+A6qmlDZ>UGU?+gMncENr^RQgx89(wGZ| zbx?1L6IFV>$#q1wPa==s2l|wzlUzwe=oEb&S!PCa2sQo739u_q)LW2jFj*0Bl;b7b zp^}bARdTHNNyUb=v}n0e`i5jJ{8VJ-{A)=DXv!g&ci2BWHebEWxDBjHMu@ihnu1z z_4p|_Vl}uiTQO0%+3wl;8fh!pJ_y5ZQPnxQ%B ziXislQ$xR^DPyNVj^OI;FcT21dfnn5)TZM%Nmjri8o4aCy~QpV#kU`Z+6kS*u1R|J z?7wr+A?j;Z&eGknf99)G9tod6+8($kuBFqmQiZ1#~~=u}>)M;!rw=YD?tI>r-m z-#I2MU&Ybpy2x5%sRli*mj19AqU!)f$}cp8;|r;$C^}|*5DUPmqF0hvpR-Qz{Lb;b#sRTdW(;ziuwQM}CNvu6{5B5Sh!keIG@wl~r34`&rPWI1ykA9Eir6dgmH99k;ADLDHxtUgCWw;G-wU$$hGdzWt zcz8s?AE1etcu1=zv}*?xB~INt#u z*PzDYo=D7oO0l;}$ySTD~K7RTDFZ8NP&--=Aw4LwOB0$>q>01 zz&IR*->7_l7Gc20UTC{esXAG$QG&tSMm{F<1(5w@^=daDzybtT z9|AbMMo&ufg)ZLg(Qx66p3^#sDfm=honm+tr`KhHrXUSqS2|ZZ$ua#Q%EnHG2D}pDH_}NmTdMoQkp0P-~d0pYf^S^&~o1is#U2r0e}2)+L8i{rR0z)w%8~k?exb}>CvCgQ^{#Ea(Znl ztC`D}HADVU`dL`Uk=JtiOKs?{j(dEyLEvVZOK+l_{bdhyk$H0lx$iUfXsASr0jSHn z4|d&Tnm%=#ZQ=ju{pjNK6^@dQ^!vfD@GurEDnLczpFz{Ojj*83q$4@v+-dp#KlhxY zh-x-cAI=Z|0%j0B`ZdvX76n;7kceQtpt+CtvOb_Nc`UonPHQfup?=H0&*dYyRWSGn z^6lfbtYG1kTczb@%|oQc{n3-n%or2FUPY-}RKKZsHvPlow<9Arm&JIQx7?XTFMtrw zY}b$-(N%aTt6@pnGjpL-176@~#j+~Rv5WWRV`js6?Y)9qNh7E!AzT@)9;8k(cj}f{ zhI$OF_PJ9n=!#`oE{M5Pp@+HC8IVHxG70W_E8!=gglf&5{>VqgK6XJ34C-%Pr5J7| zpwZ;*>M;JFv`Fd(z7%t($N{ewPls3H5Og*iHOx0dk6?&dlrp!$O6>o=YcWSgwJ1SQ{)OocTZ` zY1{$*7Hgiz{%g{HULJYtj#JY1%Q4pjK{eJ7`nz44;-$&PD9R}avFZJ zJa)OKGMfLGjOx`bQbiodF)_$y>dJ_Rf~D5gDL-dEYh-T#PyjHA5dlDm6?L1sV4rPP zD}QL`{v8@oU~IOvW-?1(DXO0qbM0 zCto{Aqe;2#3F_DdB=Xb#(sY9`lVt}Y{_p`h)g`VW5BKb^*7=UawV6DqNJ z$MK$Or=-(G90dZVh{EG^=tyO)m^IZAhY+gQ%5DdVPYZRJah|5NY27vpMd8JTDN)pN zEN<7VFY^kvCUm)Me{~`6Si^rO^`BL1RBpd&$Oey=_p+ilea2#M+S|X}`$CW1B{$|* zSyl_y$O_ryZ9K&8D<&=sYXWudc!UYfcD1?9FQ=-)9J5MhfK(n-3&S~rw5hj=e$6Ia zycB(1Tv~Z!rPeOisW!`TW2LUML|}HXiTb<9?zcDUA}X0y&Uedc;^91+Nb{inP&MR7 z&Y;)w>V*l*Pbk#uNtSGCNoE6M8keTxX*EhAw1xxWD5|u5k-vFW(Adw3^gkIDi9A!0p2S!fRuLG%erTg9Zi!4~q>*#+Fti%m&2uAFlXoAh3@J|D!JP zqAGrUC`k@-88!4GCD#at+Wrrw1_l&QC>EmzkyA8akpC=3;L_;|je4;dLDMde;sBwd zCO;Yu99gAzrT}$#Q(xic={Y2u8uV8Q8xXiFPh0;VtTtau2v;hXh!VR0U)f#9yx*hA zWI}7QA!RT7!}&NmFVqvvaJ= zqmQDJlayWEiKHbdm`Wx}nvz#5`z_W2Rhb#!}4@RTyV&?6vH`}F7z4xmeYDb ze5tlH;bicWmR>JxhUWNt^M=y$z#*@eX(vDtQ8Gn6FhIhkm)I#xhQP(Uj!Fw5(I?=g z0A~^_PVK7lWZ}sTQ6>5}O=J?fTUPxl7XgnN{ll zIGIJ2J9YNn`@OSau$SZUXeSG7Ia3_Wf2X9lLqGj49@ECG=zdL!LU5|T(k$9&Baffo z1VR7PM~Y0CpqZzLzm7eux#-}L56F+q(bZ-R3s576Y3LTqscBPcdPtGP6?urNGHhUE0fWSf_`rll`|{?Sg*mWF|k6BEq$Y;<39KGLI)9eH(L%pF8F z#%itGNP;LB{EYL9CN_T1#gXW4HoVa0&ClbTgt7sRY&EaC!~7dPNjy@d=@Qhe6jb_7 zes$DT;!u~C2Kl!m)wY1oJi-d@;08F2>5|2O&pg8q5G~wncE$4m?+bdQ7jsL~R+*<7=dy!V$$|rE&)AMOz)sgB&it zv`wAo+8q!n_*Ja;B3~WXQ5_8gcQZ8UMI|2!AY@+Fkc|sT>3ZBav2L$FN-4pGu z@f_Ur0_&js8K_mwYR}!)n?(3hr4U#bxG2)ZHJx=SA?9$7pUh#VMdnD@u36X1yx=$y zC@9s}Pdi*k3ilK!H@yUP2u^%DOc*cPF=D=87YV&+rEAR6G#3iGpr{+Whn*QtM|HkZ zTS-K%87ACO{1gVGvPf5nF*^T7;Sr~Qt#HRZP6nc?)oWs3Frw92LBwneNV81KVoVC8 zE05B+Y%L&9*2{abv8)d+v9$Ec6)hCUXYAz{Kd&ORHZ)JoqxB#&sYtQxVv@eG(1K^g zidjtw4t^^Wg^cMR_Pwr9<25#3lH_-ZnhuH4fCeK0J-WSD-o01aJz~sba>EW4 zzP;xLL!UAufo#Lzq$xgP{#jzcRJkJgw+Z_7H!QXc!vc~$+Xf?XM!85H%|S28e)@ck z$IuiPd&5ysEQP7RW9JroNTe<4*DF=(*MJvZ8^NdxZGtuM2%99!PBZUv&p8G;Xb&}t z0@awzvrYuML`${zm6e5eFo{FUeBk`YHr%GYT+#loj`NaBtYUV|waa0fCoS`=E9TNZ zVSOUcezAmoiGLKEHHHww5?A*}B6mj+90vl1mz zjisDA_DTCIT5=gC)Eh^i#8$W>|7$cuZAB7Ij{ptbY0W2Kb0Vos@A0UT$Lo^j)n(Pp zL&TQ^S5hYP7s1fo{DBFLZ@=Z`o#ZnfViFF22CgLx^yR-R232KYJt@8Dk-f){WVM

x+Mp#&E^bJ@(E(#9 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 z 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�s#k`tU+R6}zxw&n?Ve!; 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⋘{;2^!V!$H?=_(fgLY`ZZ|)sm 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 fC;yX${|WK(Qs5B(LLh(K>psSr_Oy?51LR0 diff --git a/src/charm.py b/src/charm.py index 2c743a416c..06a9be9b85 100755 --- a/src/charm.py +++ b/src/charm.py @@ -1778,7 +1778,10 @@ def _validate_config_options(self) -> None: def _handle_postgresql_restart_need(self, enable_tls: bool) -> None: """Handle PostgreSQL restart need based on the TLS configuration and configuration changes.""" - restart_postgresql = self.is_tls_enabled != self.postgresql.is_tls_enabled() + if self._can_connect_to_postgresql: + restart_postgresql = self.is_tls_enabled != self.postgresql.is_tls_enabled() + else: + restart_postgresql = False self._patroni.reload_patroni_configuration() # Wait for some more time than the Patroni's loop_wait default value (10 seconds), # which tells how much time Patroni will wait before checking the configuration diff --git a/tests/integration/test_subordinates.py b/tests/integration/test_subordinates.py index 11a5f74028..2e2772236b 100644 --- a/tests/integration/test_subordinates.py +++ b/tests/integration/test_subordinates.py @@ -33,7 +33,7 @@ async def test_deploy(ops_test: OpsTest, charm: str, github_secrets): ops_test.model.deploy( UBUNTU_PRO_APP_NAME, config={"token": github_secrets["UBUNTU_PRO_TOKEN"]}, - channel="noble/edge", + channel="latest/edge", num_units=0, ), ops_test.model.deploy( From e27901ad05b884c605706415ec99263294be291f Mon Sep 17 00:00:00 2001 From: Lucas Gameiro Borges Date: Fri, 22 Nov 2024 00:26:30 +0000 Subject: [PATCH 22/74] try catching reload errors --- src/charm.py | 5 ++++- src/cluster.py | 2 +- tests/integration/test_subordinates.py | 1 + 3 files changed, 6 insertions(+), 2 deletions(-) diff --git a/src/charm.py b/src/charm.py index 06a9be9b85..2178032c6b 100755 --- a/src/charm.py +++ b/src/charm.py @@ -1782,7 +1782,10 @@ def _handle_postgresql_restart_need(self, enable_tls: bool) -> None: restart_postgresql = self.is_tls_enabled != self.postgresql.is_tls_enabled() else: restart_postgresql = False - self._patroni.reload_patroni_configuration() + try: + self._patroni.reload_patroni_configuration() + except Exception as e: + logger.error(f"Reload patroni call failed! error: {e!s}") # Wait for some more time than the Patroni's loop_wait default value (10 seconds), # which tells how much time Patroni will wait before checking the configuration # file again to reload it. diff --git a/src/cluster.py b/src/cluster.py index c77d801375..c858a25b82 100644 --- a/src/cluster.py +++ b/src/cluster.py @@ -777,7 +777,7 @@ def remove_raft_member(self, member_ip: str) -> None: if "SUCCESS" not in result: raise RemoveRaftMemberFailedError() - @retry(stop=stop_after_attempt(10), wait=wait_exponential(multiplier=1, min=2, max=10)) + @retry(stop=stop_after_attempt(20), wait=wait_exponential(multiplier=1, min=2, max=10)) def reload_patroni_configuration(self): """Reload Patroni configuration after it was changed.""" requests.post( diff --git a/tests/integration/test_subordinates.py b/tests/integration/test_subordinates.py index 2e2772236b..3c4aaa070d 100644 --- a/tests/integration/test_subordinates.py +++ b/tests/integration/test_subordinates.py @@ -35,6 +35,7 @@ async def test_deploy(ops_test: OpsTest, charm: str, github_secrets): config={"token": github_secrets["UBUNTU_PRO_TOKEN"]}, channel="latest/edge", num_units=0, + base=CHARM_BASE, ), ops_test.model.deploy( LS_CLIENT, From e4b8953b910c167e5b5a95bb9336d2e775e85484 Mon Sep 17 00:00:00 2001 From: Lucas Gameiro Borges Date: Fri, 22 Nov 2024 13:35:36 +0000 Subject: [PATCH 23/74] adapt test_subordinates to remove ubuntu pro charm --- tests/integration/test_subordinates.py | 19 +++---------------- 1 file changed, 3 insertions(+), 16 deletions(-) diff --git a/tests/integration/test_subordinates.py b/tests/integration/test_subordinates.py index 3c4aaa070d..b2efdaf979 100644 --- a/tests/integration/test_subordinates.py +++ b/tests/integration/test_subordinates.py @@ -15,7 +15,6 @@ DATABASE_APP_NAME = "pg" LS_CLIENT = "landscape-client" -UBUNTU_PRO_APP_NAME = "ubuntu-advantage" logger = logging.getLogger(__name__) @@ -30,13 +29,6 @@ async def test_deploy(ops_test: OpsTest, charm: str, github_secrets): num_units=3, base=CHARM_BASE, ), - ops_test.model.deploy( - UBUNTU_PRO_APP_NAME, - config={"token": github_secrets["UBUNTU_PRO_TOKEN"]}, - channel="latest/edge", - num_units=0, - base=CHARM_BASE, - ), ops_test.model.deploy( LS_CLIENT, config={ @@ -51,12 +43,7 @@ async def test_deploy(ops_test: OpsTest, charm: str, github_secrets): await ops_test.model.wait_for_idle(apps=[DATABASE_APP_NAME], status="active", timeout=2000) await ops_test.model.relate(f"{DATABASE_APP_NAME}:juju-info", f"{LS_CLIENT}:container") - await ops_test.model.relate( - f"{DATABASE_APP_NAME}:juju-info", f"{UBUNTU_PRO_APP_NAME}:juju-info" - ) - await ops_test.model.wait_for_idle( - apps=[LS_CLIENT, UBUNTU_PRO_APP_NAME, DATABASE_APP_NAME], status="active" - ) + await ops_test.model.wait_for_idle(apps=[LS_CLIENT, DATABASE_APP_NAME], status="active") @pytest.mark.group(1) @@ -64,7 +51,7 @@ async def test_scale_up(ops_test: OpsTest, github_secrets): await scale_application(ops_test, DATABASE_APP_NAME, 4) await ops_test.model.wait_for_idle( - apps=[LS_CLIENT, UBUNTU_PRO_APP_NAME, DATABASE_APP_NAME], status="active", timeout=1500 + apps=[LS_CLIENT, DATABASE_APP_NAME], status="active", timeout=1500 ) @@ -73,5 +60,5 @@ async def test_scale_down(ops_test: OpsTest, github_secrets): await scale_application(ops_test, DATABASE_APP_NAME, 3) await ops_test.model.wait_for_idle( - apps=[LS_CLIENT, UBUNTU_PRO_APP_NAME, DATABASE_APP_NAME], status="active", timeout=1500 + apps=[LS_CLIENT, DATABASE_APP_NAME], status="active", timeout=1500 ) From 381845cf54b1f607bf59b259ab4a54d7a1c994a4 Mon Sep 17 00:00:00 2001 From: Lucas Gameiro Borges Date: Tue, 26 Nov 2024 01:52:44 +0000 Subject: [PATCH 24/74] try use newer branch for workflow --- .github/workflows/ci.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index 44bfb49ade..f6f0c5a8c4 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -45,7 +45,7 @@ jobs: build: name: Build charm - uses: canonical/data-platform-workflows/.github/workflows/build_charm.yaml@lucas/test-cc3 + uses: canonical/data-platform-workflows/.github/workflows/build_charm.yaml@lucas/fix-collect-bases with: cache: false charmcraft-snap-channel: 3.x/stable From 6d18038959e8338ca8806cda47f872ad538f8aae Mon Sep 17 00:00:00 2001 From: Lucas Gameiro Borges Date: Tue, 26 Nov 2024 01:58:08 +0000 Subject: [PATCH 25/74] fix lock hash --- poetry.lock | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/poetry.lock b/poetry.lock index 04ad509491..53361d0acf 100644 --- a/poetry.lock +++ b/poetry.lock @@ -2573,4 +2573,4 @@ type = ["pytest-mypy"] [metadata] lock-version = "2.0" python-versions = "^3.10" -content-hash = "697438e08e7ffb92c13ad76ff09e4ccf7a8d6eec676b5ba001a9463ab788cbe9" +content-hash = "c013930b1168d8753268f5dba9fb8534bb1bf04fc0ea5b73bd2c80db29ed20d9" From 7c780fe40c773964dedc44f4cf36925361249891 Mon Sep 17 00:00:00 2001 From: Lucas Gameiro Borges Date: Tue, 26 Nov 2024 02:04:03 +0000 Subject: [PATCH 26/74] use new branch for plugin too --- poetry.lock | 6 +++--- pyproject.toml | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/poetry.lock b/poetry.lock index 53361d0acf..fbd0f9017b 100644 --- a/poetry.lock +++ b/poetry.lock @@ -1880,8 +1880,8 @@ pyyaml = "*" [package.source] type = "git" url = "https://github.com/canonical/data-platform-workflows" -reference = "lucas/test-cc3" -resolved_reference = "01d3e1fbbf1aa8a016acb64f10dfdb4b09b53cb8" +reference = "lucas/fix-collect-bases" +resolved_reference = "c5e624e285315bac740d8c7cd9404ebc981a280d" subdirectory = "python/pytest_plugins/pytest_operator_cache" [[package]] @@ -2573,4 +2573,4 @@ type = ["pytest-mypy"] [metadata] lock-version = "2.0" python-versions = "^3.10" -content-hash = "c013930b1168d8753268f5dba9fb8534bb1bf04fc0ea5b73bd2c80db29ed20d9" +content-hash = "899fa21aef98df5592a8b6a1e0b3554a1c6263029b2febc5b1db0dfa2f90c3a8" diff --git a/pyproject.toml b/pyproject.toml index 580a81fcd1..dc92dcf796 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -65,7 +65,7 @@ optional = true pytest = "^8.3.3" pytest-github-secrets = {git = "https://github.com/canonical/data-platform-workflows", tag = "v23.0.5", subdirectory = "python/pytest_plugins/github_secrets"} pytest-operator = "^0.38.0" -pytest-operator-cache = {git = "https://github.com/canonical/data-platform-workflows", branch = "lucas/test-cc3", subdirectory = "python/pytest_plugins/pytest_operator_cache"} +pytest-operator-cache = {git = "https://github.com/canonical/data-platform-workflows", branch = "lucas/fix-collect-bases", subdirectory = "python/pytest_plugins/pytest_operator_cache"} pytest-operator-groups = {git = "https://github.com/canonical/data-platform-workflows", tag = "v23.0.5", subdirectory = "python/pytest_plugins/pytest_operator_groups"} # renovate caret doesn't work: https://github.com/renovatebot/renovate/issues/26940 juju = "<=3.5.0.0" From d5ebec76456fa78b98e5d203f864bbf92b00056a Mon Sep 17 00:00:00 2001 From: Lucas Gameiro Borges Date: Tue, 26 Nov 2024 13:22:02 +0000 Subject: [PATCH 27/74] remove old refs to pg 14 --- .github/workflows/release.yaml | 10 ++++++---- README.md | 4 ++-- src/dependency.json | 4 ++-- src/upgrade.py | 4 ++-- terraform/variables.tf | 2 +- tests/integration/ha_tests/test_upgrade.py | 2 +- .../ha_tests/test_upgrade_from_stable.py | 16 ++++++++-------- tests/unit/test_backups.py | 2 +- tests/unit/test_charm.py | 18 +++++++++--------- tests/unit/test_cluster.py | 8 ++++---- 10 files changed, 36 insertions(+), 34 deletions(-) diff --git a/.github/workflows/release.yaml b/.github/workflows/release.yaml index f709bf43b1..9f2f95d947 100644 --- a/.github/workflows/release.yaml +++ b/.github/workflows/release.yaml @@ -5,7 +5,7 @@ name: Release to Charmhub on: push: branches: - - main + - 16/edge paths-ignore: - 'tests/**' - 'docs/**' @@ -14,6 +14,8 @@ on: - '.github/workflows/ci.yaml' - '.github/workflows/lib-check.yaml' - '.github/workflows/sync_docs.yaml' + # for testing purposes: + workflow_dispatch: jobs: ci-tests: @@ -25,16 +27,16 @@ jobs: build: name: Build charm - uses: canonical/data-platform-workflows/.github/workflows/build_charm.yaml@v23.0.5 + uses: canonical/data-platform-workflows/.github/workflows/build_charm.yaml@lucas/fix-collect-bases release: name: Release charm needs: - ci-tests - build - uses: canonical/data-platform-workflows/.github/workflows/release_charm.yaml@v23.0.5 + uses: canonical/data-platform-workflows/.github/workflows/release_charm.yaml@lucas/fix-collect-bases with: - channel: 14/edge + channel: 16/edge artifact-prefix: ${{ needs.build.outputs.artifact-prefix }} secrets: charmhub-token: ${{ secrets.CHARMHUB_TOKEN }} diff --git a/README.md b/README.md index 7c3e83c644..db0c8bb6b1 100644 --- a/README.md +++ b/README.md @@ -29,7 +29,7 @@ juju add-model sample-model To deploy a single unit of PostgreSQL using its [default configuration](config.yaml), run the following command: ```shell -juju deploy postgresql --channel 14/stable +juju deploy postgresql --channel 16/stable ``` It is customary to use PostgreSQL with replication to ensure high availability. A replica is equivalent to a juju unit. @@ -37,7 +37,7 @@ It is customary to use PostgreSQL with replication to ensure high availability. To deploy PostgreSQL with multiple replicas, specify the number of desired units with the `-n` option: ```shell -juju deploy postgresql --channel 14/stable -n +juju deploy postgresql --channel 16/stable -n ``` To add replicas to an existing deployment, see the [Add replicas](#add-replicas) section. diff --git a/src/dependency.json b/src/dependency.json index 8071abbc37..5a0a238746 100644 --- a/src/dependency.json +++ b/src/dependency.json @@ -8,7 +8,7 @@ "snap": { "dependencies": {}, "name": "charmed-postgresql", - "upgrade_supported": "^14", - "version": "14.12" + "upgrade_supported": "^16", + "version": "16.4" } } diff --git a/src/upgrade.py b/src/upgrade.py index 629ba06fa8..c14b612548 100644 --- a/src/upgrade.py +++ b/src/upgrade.py @@ -114,8 +114,8 @@ def _on_upgrade_charm_check_legacy(self) -> None: fixed_dependencies["snap"] = { "dependencies": {}, "name": "charmed-postgresql", - "upgrade_supported": "^14", - "version": "14.9", + "upgrade_supported": "^16", + "version": "16.4", } self.peer_relation.data[self.charm.app].update({ "dependencies": json.dumps(fixed_dependencies) diff --git a/terraform/variables.tf b/terraform/variables.tf index aa24ebbc71..d3ceba0c1e 100644 --- a/terraform/variables.tf +++ b/terraform/variables.tf @@ -12,7 +12,7 @@ variable "app_name" { variable "channel" { description = "Charm channel to use when deploying" type = string - default = "14/stable" + default = "16/stable" } variable "revision" { diff --git a/tests/integration/ha_tests/test_upgrade.py b/tests/integration/ha_tests/test_upgrade.py index 520451f831..ff34eae500 100644 --- a/tests/integration/ha_tests/test_upgrade.py +++ b/tests/integration/ha_tests/test_upgrade.py @@ -37,7 +37,7 @@ async def test_deploy_latest(ops_test: OpsTest) -> None: await ops_test.model.deploy( DATABASE_APP_NAME, num_units=3, - channel="14/edge", + channel="16/edge", config={"profile": "testing"}, ) await ops_test.model.deploy( diff --git a/tests/integration/ha_tests/test_upgrade_from_stable.py b/tests/integration/ha_tests/test_upgrade_from_stable.py index 36a2f0c24c..1339fd8e48 100644 --- a/tests/integration/ha_tests/test_upgrade_from_stable.py +++ b/tests/integration/ha_tests/test_upgrade_from_stable.py @@ -36,33 +36,33 @@ async def test_deploy_stable(ops_test: OpsTest) -> None: # Revisions lower than 315 have a currently broken workaround for chown. parsed_charm_info = json.loads(charm_info) revision = ( - parsed_charm_info["channels"]["14"]["stable"][0]["revision"] + parsed_charm_info["channels"]["16"]["stable"][0]["revision"] if "channels" in parsed_charm_info - else parsed_charm_info["channel-map"]["14/stable"]["revision"] + else parsed_charm_info["channel-map"]["16/stable"]["revision"] ) - logger.info(f"14/stable revision: {revision}") + logger.info(f"16/stable revision: {revision}") if int(revision) < 315: original_charm_name = "./postgresql.charm" return_code, _, stderr = await ops_test.juju( "download", "postgresql", - "--channel=14/stable", + "--channel=16/stable", f"--filepath={original_charm_name}", ) if return_code != 0: raise Exception( - f"failed to download charm from 14/stable channel with error: {stderr}" + f"failed to download charm from 16/stable channel with error: {stderr}" ) patched_charm_name = "./modified_postgresql.charm" remove_chown_workaround(original_charm_name, patched_charm_name) return_code, _, stderr = await ops_test.juju("deploy", patched_charm_name, "-n", "3") if return_code != 0: - raise Exception(f"failed to deploy charm from 14/stable channel with error: {stderr}") + raise Exception(f"failed to deploy charm from 16/stable channel with error: {stderr}") else: await ops_test.model.deploy( DATABASE_APP_NAME, num_units=3, - channel="14/stable", + channel="16/stable", ) await ops_test.model.deploy( APPLICATION_NAME, @@ -84,7 +84,7 @@ async def test_pre_upgrade_check(ops_test: OpsTest) -> None: """Test that the pre-upgrade-check action runs successfully.""" application = ops_test.model.applications[DATABASE_APP_NAME] if "pre-upgrade-check" not in await application.get_actions(): - logger.info("skipping the test because the charm from 14/stable doesn't support upgrade") + logger.info("skipping the test because the charm from 16/stable doesn't support upgrade") return logger.info("Get leader unit") diff --git a/tests/unit/test_backups.py b/tests/unit/test_backups.py index da24fe6e3c..15bd2e8112 100644 --- a/tests/unit/test_backups.py +++ b/tests/unit/test_backups.py @@ -202,7 +202,7 @@ def test_can_use_s3_repository(harness): patch("charm.Patroni.member_started", new_callable=PropertyMock) as _member_started, patch("charm.PostgresqlOperatorCharm.update_config") as _update_config, patch( - "charm.Patroni.get_postgresql_version", return_value="14.10" + "charm.Patroni.get_postgresql_version", return_value="16.4" ) as _get_postgresql_version, patch("charm.PostgresqlOperatorCharm.postgresql") as _postgresql, patch( diff --git a/tests/unit/test_charm.py b/tests/unit/test_charm.py index 9b38d7ec3e..2b47be3ca9 100644 --- a/tests/unit/test_charm.py +++ b/tests/unit/test_charm.py @@ -623,7 +623,7 @@ def test_on_start(harness): side_effect=[False, True, True, True, True, True], ) as _is_storage_attached, ): - _get_postgresql_version.return_value = "14.0" + _get_postgresql_version.return_value = "16.4" # Test without storage. harness.charm.on.start.emit() @@ -709,7 +709,7 @@ def test_on_start_replica(harness): return_value=True, ) as _is_storage_attached, ): - _get_postgresql_version.return_value = "14.0" + _get_postgresql_version.return_value = "16.4" # Set the current unit to be a replica (non leader unit). harness.set_leader(False) @@ -768,7 +768,7 @@ def test_on_start_no_patroni_member(harness): bootstrap_cluster = patroni.return_value.bootstrap_cluster bootstrap_cluster.return_value = True - patroni.return_value.get_postgresql_version.return_value = "14.0" + patroni.return_value.get_postgresql_version.return_value = "16.4" harness.set_leader() harness.charm.on.start.emit() @@ -1119,29 +1119,29 @@ def test_install_snap_packages(harness): # Test for problem with snap update. with pytest.raises(snap.SnapError): - harness.charm._install_snap_packages([("postgresql", {"channel": "14/edge"})]) + harness.charm._install_snap_packages([("postgresql", {"channel": "16/edge"})]) _snap_cache.return_value.__getitem__.assert_called_once_with("postgresql") _snap_cache.assert_called_once_with() - _snap_package.ensure.assert_called_once_with(snap.SnapState.Latest, channel="14/edge") + _snap_package.ensure.assert_called_once_with(snap.SnapState.Latest, channel="16/edge") # Test with a not found package. _snap_cache.reset_mock() _snap_package.reset_mock() _snap_package.ensure.side_effect = snap.SnapNotFoundError with pytest.raises(snap.SnapNotFoundError): - harness.charm._install_snap_packages([("postgresql", {"channel": "14/edge"})]) + harness.charm._install_snap_packages([("postgresql", {"channel": "16/edge"})]) _snap_cache.return_value.__getitem__.assert_called_once_with("postgresql") _snap_cache.assert_called_once_with() - _snap_package.ensure.assert_called_once_with(snap.SnapState.Latest, channel="14/edge") + _snap_package.ensure.assert_called_once_with(snap.SnapState.Latest, channel="16/edge") # Then test a valid one. _snap_cache.reset_mock() _snap_package.reset_mock() _snap_package.ensure.side_effect = None - harness.charm._install_snap_packages([("postgresql", {"channel": "14/edge"})]) + harness.charm._install_snap_packages([("postgresql", {"channel": "16/edge"})]) _snap_cache.assert_called_once_with() _snap_cache.return_value.__getitem__.assert_called_once_with("postgresql") - _snap_package.ensure.assert_called_once_with(snap.SnapState.Latest, channel="14/edge") + _snap_package.ensure.assert_called_once_with(snap.SnapState.Latest, channel="16/edge") _snap_package.hold.assert_not_called() # Test revision diff --git a/tests/unit/test_cluster.py b/tests/unit/test_cluster.py index 9cb737f0bf..55fe81f797 100644 --- a/tests/unit/test_cluster.py +++ b/tests/unit/test_cluster.py @@ -165,11 +165,11 @@ def test_get_postgresql_version(peers_ips, patroni): _get_installed_snaps = _snap_client.return_value.get_installed_snaps _get_installed_snaps.return_value = [ {"name": "something"}, - {"name": "charmed-postgresql", "version": "14.0"}, + {"name": "charmed-postgresql", "version": "16.4"}, ] version = patroni.get_postgresql_version() - assert version == "14.0" + assert version == "16.4" _snap_client.assert_called_once_with() _get_installed_snaps.assert_called_once_with() @@ -314,7 +314,7 @@ def test_render_patroni_yml_file(peers_ips, patroni): patch("charm.Patroni.render_file") as _render_file, patch("charm.Patroni._create_directory"), ): - _get_postgresql_version.return_value = "14.7" + _get_postgresql_version.return_value = "16.4" # Define variables to render in the template. member_name = "postgresql-0" @@ -324,7 +324,7 @@ def test_render_patroni_yml_file(peers_ips, patroni): rewind_password = "fake-rewind-password" raft_password = "fake-raft-password" patroni_password = "fake-patroni-password" - postgresql_version = "14" + postgresql_version = "16" # Get the expected content from a file. with open("templates/patroni.yml.j2") as file: From 167ac59289fd3e4d40a1c5f884ad357aa31b4701 Mon Sep 17 00:00:00 2001 From: Lucas Gameiro Borges Date: Wed, 27 Nov 2024 15:46:14 +0000 Subject: [PATCH 28/74] specify cc version 3 on release workflow --- .github/workflows/release.yaml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.github/workflows/release.yaml b/.github/workflows/release.yaml index 9f2f95d947..ca5e7cc9d1 100644 --- a/.github/workflows/release.yaml +++ b/.github/workflows/release.yaml @@ -28,6 +28,9 @@ jobs: build: name: Build charm uses: canonical/data-platform-workflows/.github/workflows/build_charm.yaml@lucas/fix-collect-bases + with: + cache: false + charmcraft-snap-channel: 3.x/stable release: name: Release charm From 3fa1e3bc3d9f524874377332f48178ac8c485f70 Mon Sep 17 00:00:00 2001 From: Lucas Gameiro Borges Date: Wed, 27 Nov 2024 16:32:41 +0000 Subject: [PATCH 29/74] remove juju 2.9 + refactor release workflow --- .github/workflows/ci.yaml | 3 -- .github/workflows/release.yaml | 55 +++++++++++++++++++++++++++++----- 2 files changed, 47 insertions(+), 11 deletions(-) diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index f6f0c5a8c4..a17341e546 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -55,9 +55,6 @@ jobs: fail-fast: false matrix: juju: - - agent: 2.9.51 # renovate: juju-agent-pin-minor - libjuju: ==2.9.49.0 # renovate: latest libjuju 2 - allure_on_amd64: false - agent: 3.4.6 # renovate: juju-agent-pin-minor allure_on_amd64: true - snap_channel: 3.6/candidate diff --git a/.github/workflows/release.yaml b/.github/workflows/release.yaml index ca5e7cc9d1..ff3c1a4535 100644 --- a/.github/workflows/release.yaml +++ b/.github/workflows/release.yaml @@ -18,13 +18,6 @@ on: workflow_dispatch: jobs: - ci-tests: - name: Tests - uses: ./.github/workflows/ci.yaml - secrets: inherit - permissions: - contents: write # Needed for Allure Report beta - build: name: Build charm uses: canonical/data-platform-workflows/.github/workflows/build_charm.yaml@lucas/fix-collect-bases @@ -32,10 +25,56 @@ jobs: cache: false charmcraft-snap-channel: 3.x/stable + integration-test: + strategy: + fail-fast: false + matrix: + juju: + - agent: 3.4.6 # renovate: juju-agent-pin-minor + allure_on_amd64: true + - snap_channel: 3.6/candidate + allure_on_amd64: false + architecture: + - amd64 + include: + - juju: + agent: 3.4.6 # renovate: juju-agent-pin-minor + allure_on_amd64: true + architecture: arm64 + - juju: + snap_channel: 3.6/candidate + allure_on_amd64: false + architecture: arm64 + name: Integration | ${{ matrix.juju.agent || matrix.juju.snap_channel }} | ${{ matrix.architecture }} + needs: + - build + uses: canonical/data-platform-workflows/.github/workflows/integration_test_charm.yaml@v23.0.5 + with: + artifact-prefix: ${{ needs.build.outputs.artifact-prefix }} + architecture: ${{ matrix.architecture }} + cloud: lxd + juju-agent-version: ${{ matrix.juju.agent }} + juju-snap-channel: ${{ matrix.juju.snap_channel }} + libjuju-version-constraint: ${{ matrix.juju.libjuju }} + _beta_allure_report: ${{ matrix.juju.allure_on_amd64 && matrix.architecture == 'amd64' }} + secrets: + integration-test: | + { + "AWS_ACCESS_KEY": "${{ secrets.AWS_ACCESS_KEY }}", + "AWS_SECRET_KEY": "${{ secrets.AWS_SECRET_KEY }}", + "GCP_ACCESS_KEY": "${{ secrets.GCP_ACCESS_KEY }}", + "GCP_SECRET_KEY": "${{ secrets.GCP_SECRET_KEY }}", + "UBUNTU_PRO_TOKEN" : "${{ secrets.UBUNTU_PRO_TOKEN }}", + "LANDSCAPE_ACCOUNT_NAME": "${{ secrets.LANDSCAPE_ACCOUNT_NAME }}", + "LANDSCAPE_REGISTRATION_KEY": "${{ secrets.LANDSCAPE_REGISTRATION_KEY }}", + } + permissions: + contents: write # Needed for Allure Report beta + release: name: Release charm needs: - - ci-tests + - integration-test - build uses: canonical/data-platform-workflows/.github/workflows/release_charm.yaml@lucas/fix-collect-bases with: From 788acd7443b57f22c9a429fb0e66e5b412a4147b Mon Sep 17 00:00:00 2001 From: Lucas Gameiro Borges Date: Wed, 27 Nov 2024 16:35:13 +0000 Subject: [PATCH 30/74] remove libjuju constraint --- .github/workflows/ci.yaml | 1 - 1 file changed, 1 deletion(-) diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index a17341e546..4fcc1a375f 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -82,7 +82,6 @@ jobs: cloud: lxd juju-agent-version: ${{ matrix.juju.agent }} juju-snap-channel: ${{ matrix.juju.snap_channel }} - libjuju-version-constraint: ${{ matrix.juju.libjuju }} _beta_allure_report: ${{ matrix.juju.allure_on_amd64 && matrix.architecture == 'amd64' }} secrets: integration-test: | From 88dd50ba8f514be02e6e5284a11c2053f153a12e Mon Sep 17 00:00:00 2001 From: Lucas Gameiro Borges Date: Wed, 27 Nov 2024 16:36:10 +0000 Subject: [PATCH 31/74] fix release too --- .github/workflows/release.yaml | 1 - 1 file changed, 1 deletion(-) diff --git a/.github/workflows/release.yaml b/.github/workflows/release.yaml index ff3c1a4535..c5eaa04465 100644 --- a/.github/workflows/release.yaml +++ b/.github/workflows/release.yaml @@ -55,7 +55,6 @@ jobs: cloud: lxd juju-agent-version: ${{ matrix.juju.agent }} juju-snap-channel: ${{ matrix.juju.snap_channel }} - libjuju-version-constraint: ${{ matrix.juju.libjuju }} _beta_allure_report: ${{ matrix.juju.allure_on_amd64 && matrix.architecture == 'amd64' }} secrets: integration-test: | From 0835d0773cb40a3da7f0fdf7929ab512470cc3cd Mon Sep 17 00:00:00 2001 From: Lucas Gameiro Borges Date: Wed, 4 Dec 2024 14:28:40 +0000 Subject: [PATCH 32/74] use new charm + small adjustments --- lib/charms/postgresql_k8s/v0/postgresql.py | 2 +- src/constants.py | 2 +- src/dependency.json | 2 +- src/upgrade.py | 2 +- tests/integration/ha_tests/test_upgrade.py | 4 ---- tests/unit/test_backups.py | 2 +- tests/unit/test_charm.py | 6 +++--- tests/unit/test_cluster.py | 6 +++--- 8 files changed, 11 insertions(+), 15 deletions(-) diff --git a/lib/charms/postgresql_k8s/v0/postgresql.py b/lib/charms/postgresql_k8s/v0/postgresql.py index ca0826ffa2..e55cf00ff8 100644 --- a/lib/charms/postgresql_k8s/v0/postgresql.py +++ b/lib/charms/postgresql_k8s/v0/postgresql.py @@ -36,7 +36,7 @@ # Increment this PATCH version before using `charmcraft publish-lib` or reset # to 0 if you are raising the major API version -LIBPATCH = 39 +LIBPATCH = 40 INVALID_EXTRA_USER_ROLE_BLOCKING_MESSAGE = "invalid role(s) for extra user roles" diff --git a/src/constants.py b/src/constants.py index 5e256d35e2..6de25f4cd6 100644 --- a/src/constants.py +++ b/src/constants.py @@ -34,7 +34,7 @@ SNAP_PACKAGES = [ ( POSTGRESQL_SNAP_NAME, - {"revision": {"aarch64": "137", "x86_64": "136"}}, + {"revision": {"aarch64": "138", "x86_64": "139"}}, ) ] diff --git a/src/dependency.json b/src/dependency.json index 5a0a238746..cc4e9f524d 100644 --- a/src/dependency.json +++ b/src/dependency.json @@ -9,6 +9,6 @@ "dependencies": {}, "name": "charmed-postgresql", "upgrade_supported": "^16", - "version": "16.4" + "version": "16.6" } } diff --git a/src/upgrade.py b/src/upgrade.py index c14b612548..3f7c183e16 100644 --- a/src/upgrade.py +++ b/src/upgrade.py @@ -115,7 +115,7 @@ def _on_upgrade_charm_check_legacy(self) -> None: "dependencies": {}, "name": "charmed-postgresql", "upgrade_supported": "^16", - "version": "16.4", + "version": "16.6", } self.peer_relation.data[self.charm.app].update({ "dependencies": json.dumps(fixed_dependencies) diff --git a/tests/integration/ha_tests/test_upgrade.py b/tests/integration/ha_tests/test_upgrade.py index ff34eae500..749615825c 100644 --- a/tests/integration/ha_tests/test_upgrade.py +++ b/tests/integration/ha_tests/test_upgrade.py @@ -30,7 +30,6 @@ @pytest.mark.group(1) -@pytest.mark.unstable @pytest.mark.abort_on_fail async def test_deploy_latest(ops_test: OpsTest) -> None: """Simple test to ensure that the PostgreSQL and application charms get deployed.""" @@ -54,7 +53,6 @@ async def test_deploy_latest(ops_test: OpsTest) -> None: @pytest.mark.group(1) -@pytest.mark.unstable @pytest.mark.abort_on_fail async def test_pre_upgrade_check(ops_test: OpsTest) -> None: """Test that the pre-upgrade-check action runs successfully.""" @@ -68,7 +66,6 @@ async def test_pre_upgrade_check(ops_test: OpsTest) -> None: @pytest.mark.group(1) -@pytest.mark.unstable @pytest.mark.abort_on_fail async def test_upgrade_from_edge(ops_test: OpsTest, continuous_writes) -> None: # Start an application that continuously writes data to the database. @@ -119,7 +116,6 @@ async def test_upgrade_from_edge(ops_test: OpsTest, continuous_writes) -> None: @pytest.mark.group(1) -@pytest.mark.unstable @pytest.mark.abort_on_fail async def test_fail_and_rollback(ops_test, continuous_writes) -> None: # Start an application that continuously writes data to the database. diff --git a/tests/unit/test_backups.py b/tests/unit/test_backups.py index 15bd2e8112..4e6153ee8a 100644 --- a/tests/unit/test_backups.py +++ b/tests/unit/test_backups.py @@ -202,7 +202,7 @@ def test_can_use_s3_repository(harness): patch("charm.Patroni.member_started", new_callable=PropertyMock) as _member_started, patch("charm.PostgresqlOperatorCharm.update_config") as _update_config, patch( - "charm.Patroni.get_postgresql_version", return_value="16.4" + "charm.Patroni.get_postgresql_version", return_value="16.6" ) as _get_postgresql_version, patch("charm.PostgresqlOperatorCharm.postgresql") as _postgresql, patch( diff --git a/tests/unit/test_charm.py b/tests/unit/test_charm.py index 2b47be3ca9..d6a158d24e 100644 --- a/tests/unit/test_charm.py +++ b/tests/unit/test_charm.py @@ -623,7 +623,7 @@ def test_on_start(harness): side_effect=[False, True, True, True, True, True], ) as _is_storage_attached, ): - _get_postgresql_version.return_value = "16.4" + _get_postgresql_version.return_value = "16.6" # Test without storage. harness.charm.on.start.emit() @@ -709,7 +709,7 @@ def test_on_start_replica(harness): return_value=True, ) as _is_storage_attached, ): - _get_postgresql_version.return_value = "16.4" + _get_postgresql_version.return_value = "16.6" # Set the current unit to be a replica (non leader unit). harness.set_leader(False) @@ -768,7 +768,7 @@ def test_on_start_no_patroni_member(harness): bootstrap_cluster = patroni.return_value.bootstrap_cluster bootstrap_cluster.return_value = True - patroni.return_value.get_postgresql_version.return_value = "16.4" + patroni.return_value.get_postgresql_version.return_value = "16.6" harness.set_leader() harness.charm.on.start.emit() diff --git a/tests/unit/test_cluster.py b/tests/unit/test_cluster.py index 55fe81f797..180db17274 100644 --- a/tests/unit/test_cluster.py +++ b/tests/unit/test_cluster.py @@ -165,11 +165,11 @@ def test_get_postgresql_version(peers_ips, patroni): _get_installed_snaps = _snap_client.return_value.get_installed_snaps _get_installed_snaps.return_value = [ {"name": "something"}, - {"name": "charmed-postgresql", "version": "16.4"}, + {"name": "charmed-postgresql", "version": "16.6"}, ] version = patroni.get_postgresql_version() - assert version == "16.4" + assert version == "16.6" _snap_client.assert_called_once_with() _get_installed_snaps.assert_called_once_with() @@ -314,7 +314,7 @@ def test_render_patroni_yml_file(peers_ips, patroni): patch("charm.Patroni.render_file") as _render_file, patch("charm.Patroni._create_directory"), ): - _get_postgresql_version.return_value = "16.4" + _get_postgresql_version.return_value = "16.6" # Define variables to render in the template. member_name = "postgresql-0" From 26561aa2f5c5ec9cbab517a23011112adf003acf Mon Sep 17 00:00:00 2001 From: Lucas Gameiro Borges Date: Thu, 5 Dec 2024 12:30:20 +0000 Subject: [PATCH 33/74] fix lock file --- poetry.lock | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/poetry.lock b/poetry.lock index b2bcd7fd51..2369a7d35e 100644 --- a/poetry.lock +++ b/poetry.lock @@ -1901,8 +1901,8 @@ pytest = "*" [package.source] type = "git" url = "https://github.com/canonical/data-platform-workflows" -reference = "v23.1.1" -resolved_reference = "7dc172891bf274e74eef2a4d822450ca00f55188" +reference = "v23.0.5" +resolved_reference = "e3f522c648375decee87fc0982c012e46ffb0b98" subdirectory = "python/pytest_plugins/pytest_operator_groups" [[package]] @@ -2575,4 +2575,4 @@ type = ["pytest-mypy"] [metadata] lock-version = "2.0" python-versions = "^3.10" -content-hash = "899fa21aef98df5592a8b6a1e0b3554a1c6263029b2febc5b1db0dfa2f90c3a8" +content-hash = "e8d69ec1f3f25200e55166ef04f4ea31444a8ae1bf9eedb9405cc7e21f94745e" From bf0e25a77639398b2b2435085536ce5a6707be6e Mon Sep 17 00:00:00 2001 From: Lucas Gameiro Borges Date: Fri, 7 Feb 2025 16:48:03 -0300 Subject: [PATCH 34/74] fix build_charm issue --- tests/integration/conftest.py | 4 ++- .../ha_tests/test_async_replication.py | 5 +-- .../integration/ha_tests/test_replication.py | 6 ++-- .../ha_tests/test_restore_cluster.py | 3 +- tests/integration/ha_tests/test_scaling.py | 3 +- .../ha_tests/test_scaling_three_units.py | 3 +- .../integration/ha_tests/test_self_healing.py | 3 +- tests/integration/ha_tests/test_upgrade.py | 5 +-- .../ha_tests/test_upgrade_from_stable.py | 3 +- tests/integration/helpers.py | 31 +++++++++++++++++++ tests/integration/test_config.py | 3 +- tests/integration/test_password_rotation.py | 3 +- tests/integration/test_plugins.py | 3 +- tests/integration/test_tls.py | 3 +- 14 files changed, 61 insertions(+), 17 deletions(-) diff --git a/tests/integration/conftest.py b/tests/integration/conftest.py index 87bd24fb9b..99ceda68b0 100644 --- a/tests/integration/conftest.py +++ b/tests/integration/conftest.py @@ -4,9 +4,11 @@ import pytest from pytest_operator.plugin import OpsTest +from .helpers import build_charm + @pytest.fixture(scope="module") async def charm(ops_test: OpsTest): """Build the charm-under-test.""" # Build charm from local source folder. - yield await ops_test.build_charm(".") + yield await build_charm(".") diff --git a/tests/integration/ha_tests/test_async_replication.py b/tests/integration/ha_tests/test_async_replication.py index 7b76660bb3..89e8f87a67 100644 --- a/tests/integration/ha_tests/test_async_replication.py +++ b/tests/integration/ha_tests/test_async_replication.py @@ -16,6 +16,7 @@ from ..helpers import ( APPLICATION_NAME, DATABASE_APP_NAME, + build_charm, get_leader_unit, get_password, get_primary, @@ -107,7 +108,7 @@ async def test_deploy_async_replication_setup( ) -> None: """Build and deploy two PostgreSQL cluster in two separate models to test async replication.""" if not await app_name(ops_test): - charm = await ops_test.build_charm(".") + charm = await build_charm(".") await ops_test.model.deploy( charm, num_units=CLUSTER_SIZE, @@ -122,7 +123,7 @@ async def test_deploy_async_replication_setup( ) await ops_test.model.relate(DATABASE_APP_NAME, DATA_INTEGRATOR_APP_NAME) if not await app_name(ops_test, model=second_model): - charm = await ops_test.build_charm(".") + charm = await build_charm(".") await second_model.deploy( charm, num_units=CLUSTER_SIZE, diff --git a/tests/integration/ha_tests/test_replication.py b/tests/integration/ha_tests/test_replication.py index 64539a0561..f021e016c0 100644 --- a/tests/integration/ha_tests/test_replication.py +++ b/tests/integration/ha_tests/test_replication.py @@ -6,7 +6,7 @@ from pytest_operator.plugin import OpsTest from tenacity import Retrying, stop_after_delay, wait_fixed -from ..helpers import APPLICATION_NAME, CHARM_BASE, db_connect, scale_application +from ..helpers import APPLICATION_NAME, CHARM_BASE, build_charm, db_connect, scale_application from .helpers import ( app_name, are_writes_increasing, @@ -27,7 +27,7 @@ async def test_build_and_deploy(ops_test: OpsTest) -> None: # is a pre-existing cluster. if not await app_name(ops_test): wait_for_apps = True - charm = await ops_test.build_charm(".") + charm = await build_charm(".") async with ops_test.fast_forward(): await ops_test.model.deploy( charm, @@ -115,7 +115,7 @@ async def test_no_data_replicated_between_clusters(ops_test: OpsTest, continuous # Deploy another cluster. new_cluster_app = f"second-{app}" if not await app_name(ops_test, new_cluster_app): - charm = await ops_test.build_charm(".") + charm = await build_charm(".") async with ops_test.fast_forward(): await ops_test.model.deploy( charm, diff --git a/tests/integration/ha_tests/test_restore_cluster.py b/tests/integration/ha_tests/test_restore_cluster.py index 9542dbb850..917eb4491f 100644 --- a/tests/integration/ha_tests/test_restore_cluster.py +++ b/tests/integration/ha_tests/test_restore_cluster.py @@ -8,6 +8,7 @@ from ..helpers import ( CHARM_BASE, + build_charm, db_connect, get_password, get_patroni_cluster, @@ -34,7 +35,7 @@ async def test_build_and_deploy(ops_test: OpsTest) -> None: """Build and deploy two PostgreSQL clusters.""" # This is a potentially destructive test, so it shouldn't be run against existing clusters - charm = await ops_test.build_charm(".") + charm = await build_charm(".") async with ops_test.fast_forward(): # Deploy the first cluster with reusable storage await ops_test.model.deploy( diff --git a/tests/integration/ha_tests/test_scaling.py b/tests/integration/ha_tests/test_scaling.py index f3105d38cd..c589c8cffe 100644 --- a/tests/integration/ha_tests/test_scaling.py +++ b/tests/integration/ha_tests/test_scaling.py @@ -11,6 +11,7 @@ from ..helpers import ( CHARM_BASE, DATABASE_APP_NAME, + build_charm, ) from .conftest import APPLICATION_NAME from .helpers import ( @@ -33,7 +34,7 @@ async def test_build_and_deploy(ops_test: OpsTest) -> None: """Build and deploy two PostgreSQL clusters.""" # This is a potentially destructive test, so it shouldn't be run against existing clusters - charm = await ops_test.build_charm(".") + charm = await build_charm(".") async with ops_test.fast_forward(): # Deploy the first cluster with reusable storage await gather( diff --git a/tests/integration/ha_tests/test_scaling_three_units.py b/tests/integration/ha_tests/test_scaling_three_units.py index 6817cd238a..69b65215b3 100644 --- a/tests/integration/ha_tests/test_scaling_three_units.py +++ b/tests/integration/ha_tests/test_scaling_three_units.py @@ -11,6 +11,7 @@ from ..helpers import ( CHARM_BASE, DATABASE_APP_NAME, + build_charm, get_machine_from_unit, stop_machine, ) @@ -34,7 +35,7 @@ async def test_build_and_deploy(ops_test: OpsTest) -> None: """Build and deploy two PostgreSQL clusters.""" # This is a potentially destructive test, so it shouldn't be run against existing clusters - charm = await ops_test.build_charm(".") + charm = await build_charm(".") async with ops_test.fast_forward(): # Deploy the first cluster with reusable storage await gather( diff --git a/tests/integration/ha_tests/test_self_healing.py b/tests/integration/ha_tests/test_self_healing.py index a6f1ff0525..2383ef97e5 100644 --- a/tests/integration/ha_tests/test_self_healing.py +++ b/tests/integration/ha_tests/test_self_healing.py @@ -11,6 +11,7 @@ from ..helpers import ( CHARM_BASE, + build_charm, db_connect, get_machine_from_unit, get_password, @@ -71,7 +72,7 @@ async def test_build_and_deploy(ops_test: OpsTest) -> None: # is a pre-existing cluster. if not await app_name(ops_test): wait_for_apps = True - charm = await ops_test.build_charm(".") + charm = await build_charm(".") async with ops_test.fast_forward(): await ops_test.model.deploy( charm, diff --git a/tests/integration/ha_tests/test_upgrade.py b/tests/integration/ha_tests/test_upgrade.py index 749615825c..ce6498e46c 100644 --- a/tests/integration/ha_tests/test_upgrade.py +++ b/tests/integration/ha_tests/test_upgrade.py @@ -13,6 +13,7 @@ from ..helpers import ( APPLICATION_NAME, DATABASE_APP_NAME, + build_charm, count_switchovers, get_leader_unit, get_primary, @@ -82,7 +83,7 @@ async def test_upgrade_from_edge(ops_test: OpsTest, continuous_writes) -> None: application = ops_test.model.applications[DATABASE_APP_NAME] logger.info("Build charm locally") - charm = await ops_test.build_charm(".") + charm = await build_charm(".") logger.info("Refresh the charm") await application.refresh(path=charm) @@ -134,7 +135,7 @@ async def test_fail_and_rollback(ops_test, continuous_writes) -> None: action = await leader_unit.run_action("pre-upgrade-check") await action.wait() - local_charm = await ops_test.build_charm(".") + local_charm = await build_charm(".") filename = local_charm.split("/")[-1] if isinstance(local_charm, str) else local_charm.name fault_charm = Path("/tmp/", filename) shutil.copy(local_charm, fault_charm) diff --git a/tests/integration/ha_tests/test_upgrade_from_stable.py b/tests/integration/ha_tests/test_upgrade_from_stable.py index 3237429473..d250bdf81b 100644 --- a/tests/integration/ha_tests/test_upgrade_from_stable.py +++ b/tests/integration/ha_tests/test_upgrade_from_stable.py @@ -9,6 +9,7 @@ from ..helpers import ( APPLICATION_NAME, DATABASE_APP_NAME, + build_charm, count_switchovers, get_leader_unit, get_primary, @@ -116,7 +117,7 @@ async def test_upgrade_from_stable(ops_test: OpsTest): actions = await application.get_actions() logger.info("Build charm locally") - charm = await ops_test.build_charm(".") + charm = await build_charm(".") logger.info("Refresh the charm") await application.refresh(path=charm) diff --git a/tests/integration/helpers.py b/tests/integration/helpers.py index 68a87f9409..3eb6bb6a3c 100644 --- a/tests/integration/helpers.py +++ b/tests/integration/helpers.py @@ -39,6 +39,37 @@ logger = logging.getLogger(__name__) +async def build_charm(charm_path) -> Path: + charm_path = Path(charm_path) + architecture = subprocess.run( + ["dpkg", "--print-architecture"], + capture_output=True, + check=True, + encoding="utf-8", + ).stdout.strip() + assert architecture in ("amd64", "arm64") + # 24.04 pin is temporary solution while multi-base integration testing not supported by data-platform-workflows + packed_charms = list(charm_path.glob(f"*ubuntu@24.04-{architecture}.charm")) + if len(packed_charms) == 1: + # python-libjuju's model.deploy(), juju deploy, and juju bundle files expect local charms + # to begin with `./` or `/` to distinguish them from Charmhub charms. + # Therefore, we need to return an absolute path—a relative `pathlib.Path` does not start + # with `./` when cast to a str. + # (python-libjuju model.deploy() expects a str but will cast any input to a str as a + # workaround for pytest-operator's non-compliant `build_charm` return type of + # `pathlib.Path`.) + return packed_charms[0].resolve(strict=True) + elif len(packed_charms) > 1: + raise ValueError( + f"More than one matching .charm file found at {charm_path=} for {architecture=} and " + f"Ubuntu 24.04: {packed_charms}." + ) + else: + raise ValueError( + f"Unable to find .charm file for {architecture=} and Ubuntu 24.04 at {charm_path=}" + ) + + async def build_connection_string( ops_test: OpsTest, application_name: str, diff --git a/tests/integration/test_config.py b/tests/integration/test_config.py index 622264c6c4..b6bca2716d 100644 --- a/tests/integration/test_config.py +++ b/tests/integration/test_config.py @@ -9,6 +9,7 @@ from .helpers import ( CHARM_BASE, DATABASE_APP_NAME, + build_charm, get_leader_unit, ) @@ -21,7 +22,7 @@ async def test_config_parameters(ops_test: OpsTest) -> None: """Build and deploy one unit of PostgreSQL and then test config with wrong parameters.""" # Build and deploy the PostgreSQL charm. async with ops_test.fast_forward(): - charm = await ops_test.build_charm(".") + charm = await build_charm(".") await ops_test.model.deploy( charm, num_units=1, diff --git a/tests/integration/test_password_rotation.py b/tests/integration/test_password_rotation.py index 0cf2f6c26c..ebcaf57a4b 100644 --- a/tests/integration/test_password_rotation.py +++ b/tests/integration/test_password_rotation.py @@ -13,6 +13,7 @@ from .helpers import ( CHARM_BASE, METADATA, + build_charm, check_patroni, db_connect, get_leader_unit, @@ -32,7 +33,7 @@ @pytest.mark.skip_if_deployed async def test_deploy_active(ops_test: OpsTest): """Build the charm and deploy it.""" - charm = await ops_test.build_charm(".") + charm = await build_charm(".") async with ops_test.fast_forward(): await ops_test.model.deploy( charm, diff --git a/tests/integration/test_plugins.py b/tests/integration/test_plugins.py index f5f7a3d5df..9c7a40ed88 100644 --- a/tests/integration/test_plugins.py +++ b/tests/integration/test_plugins.py @@ -10,6 +10,7 @@ from .helpers import ( CHARM_BASE, DATABASE_APP_NAME, + build_charm, db_connect, get_password, get_primary, @@ -96,7 +97,7 @@ async def test_plugins(ops_test: OpsTest) -> None: """Build and deploy one unit of PostgreSQL and then test the available plugins.""" # Build and deploy the PostgreSQL charm. async with ops_test.fast_forward(): - charm = await ops_test.build_charm(".") + charm = await build_charm(".") await ops_test.model.deploy( charm, num_units=2, diff --git a/tests/integration/test_tls.py b/tests/integration/test_tls.py index 4116746dd3..4654816700 100644 --- a/tests/integration/test_tls.py +++ b/tests/integration/test_tls.py @@ -15,6 +15,7 @@ CHARM_BASE, DATABASE_APP_NAME, METADATA, + build_charm, change_primary_start_timeout, check_tls, check_tls_patroni_api, @@ -46,7 +47,7 @@ @pytest.mark.skip_if_deployed async def test_deploy_active(ops_test: OpsTest): """Build the charm and deploy it.""" - charm = await ops_test.build_charm(".") + charm = await build_charm(".") async with ops_test.fast_forward(): await ops_test.model.deploy( charm, From 4ae72f05d72f05a3978c071bc33a7357ed841b29 Mon Sep 17 00:00:00 2001 From: Lucas Gameiro Borges Date: Fri, 7 Feb 2025 17:08:57 -0300 Subject: [PATCH 35/74] remove base from deploy calls --- tests/integration/ha_tests/test_replication.py | 4 +--- tests/integration/ha_tests/test_restore_cluster.py | 3 --- tests/integration/ha_tests/test_scaling.py | 3 --- tests/integration/ha_tests/test_scaling_three_units.py | 3 --- tests/integration/ha_tests/test_self_healing.py | 2 -- tests/integration/ha_tests/test_smoke.py | 4 ---- tests/integration/helpers.py | 1 - tests/integration/new_relations/test_new_relations.py | 4 ---- tests/integration/new_relations/test_relations_coherence.py | 3 +-- tests/integration/relations/test_relations.py | 3 +-- tests/integration/test_backups.py | 3 --- tests/integration/test_backups_pitr.py | 2 -- tests/integration/test_charm.py | 2 -- tests/integration/test_config.py | 2 -- tests/integration/test_db.py | 1 - tests/integration/test_db_admin.py | 2 -- tests/integration/test_password_rotation.py | 2 -- tests/integration/test_plugins.py | 2 -- tests/integration/test_subordinates.py | 2 -- tests/integration/test_tls.py | 2 -- 20 files changed, 3 insertions(+), 47 deletions(-) diff --git a/tests/integration/ha_tests/test_replication.py b/tests/integration/ha_tests/test_replication.py index f021e016c0..558d5cb718 100644 --- a/tests/integration/ha_tests/test_replication.py +++ b/tests/integration/ha_tests/test_replication.py @@ -6,7 +6,7 @@ from pytest_operator.plugin import OpsTest from tenacity import Retrying, stop_after_delay, wait_fixed -from ..helpers import APPLICATION_NAME, CHARM_BASE, build_charm, db_connect, scale_application +from ..helpers import APPLICATION_NAME, build_charm, db_connect, scale_application from .helpers import ( app_name, are_writes_increasing, @@ -32,7 +32,6 @@ async def test_build_and_deploy(ops_test: OpsTest) -> None: await ops_test.model.deploy( charm, num_units=3, - base=CHARM_BASE, config={"profile": "testing"}, ) # Deploy the continuous writes application charm if it wasn't already deployed. @@ -121,7 +120,6 @@ async def test_no_data_replicated_between_clusters(ops_test: OpsTest, continuous charm, application_name=new_cluster_app, num_units=2, - base=CHARM_BASE, config={"profile": "testing"}, ) await ops_test.model.wait_for_idle( diff --git a/tests/integration/ha_tests/test_restore_cluster.py b/tests/integration/ha_tests/test_restore_cluster.py index 917eb4491f..898aa27d62 100644 --- a/tests/integration/ha_tests/test_restore_cluster.py +++ b/tests/integration/ha_tests/test_restore_cluster.py @@ -7,7 +7,6 @@ from pytest_operator.plugin import OpsTest from ..helpers import ( - CHARM_BASE, build_charm, db_connect, get_password, @@ -42,7 +41,6 @@ async def test_build_and_deploy(ops_test: OpsTest) -> None: charm, application_name=FIRST_APPLICATION, num_units=3, - base=CHARM_BASE, storage={"pgdata": {"pool": "lxd-btrfs", "size": 2048}}, config={"profile": "testing"}, ) @@ -52,7 +50,6 @@ async def test_build_and_deploy(ops_test: OpsTest) -> None: charm, application_name=SECOND_APPLICATION, num_units=1, - base=CHARM_BASE, config={"profile": "testing"}, ) diff --git a/tests/integration/ha_tests/test_scaling.py b/tests/integration/ha_tests/test_scaling.py index c589c8cffe..d6c2d7601d 100644 --- a/tests/integration/ha_tests/test_scaling.py +++ b/tests/integration/ha_tests/test_scaling.py @@ -9,7 +9,6 @@ from .. import markers from ..helpers import ( - CHARM_BASE, DATABASE_APP_NAME, build_charm, ) @@ -42,13 +41,11 @@ async def test_build_and_deploy(ops_test: OpsTest) -> None: charm, application_name=DATABASE_APP_NAME, num_units=2, - base=CHARM_BASE, config={"profile": "testing"}, ), ops_test.model.deploy( APPLICATION_NAME, application_name=APPLICATION_NAME, - base=CHARM_BASE, channel="edge", ), ) diff --git a/tests/integration/ha_tests/test_scaling_three_units.py b/tests/integration/ha_tests/test_scaling_three_units.py index 69b65215b3..41acfa74b8 100644 --- a/tests/integration/ha_tests/test_scaling_three_units.py +++ b/tests/integration/ha_tests/test_scaling_three_units.py @@ -9,7 +9,6 @@ from .. import markers from ..helpers import ( - CHARM_BASE, DATABASE_APP_NAME, build_charm, get_machine_from_unit, @@ -43,13 +42,11 @@ async def test_build_and_deploy(ops_test: OpsTest) -> None: charm, application_name=DATABASE_APP_NAME, num_units=3, - base=CHARM_BASE, config={"profile": "testing"}, ), ops_test.model.deploy( APPLICATION_NAME, application_name=APPLICATION_NAME, - base=CHARM_BASE, channel="edge", ), ) diff --git a/tests/integration/ha_tests/test_self_healing.py b/tests/integration/ha_tests/test_self_healing.py index 2383ef97e5..3b46eaa2ad 100644 --- a/tests/integration/ha_tests/test_self_healing.py +++ b/tests/integration/ha_tests/test_self_healing.py @@ -10,7 +10,6 @@ from tenacity import Retrying, stop_after_delay, wait_fixed from ..helpers import ( - CHARM_BASE, build_charm, db_connect, get_machine_from_unit, @@ -77,7 +76,6 @@ async def test_build_and_deploy(ops_test: OpsTest) -> None: await ops_test.model.deploy( charm, num_units=3, - base=CHARM_BASE, storage={"pgdata": {"pool": "lxd-btrfs", "size": 2048}}, config={"profile": "testing"}, ) diff --git a/tests/integration/ha_tests/test_smoke.py b/tests/integration/ha_tests/test_smoke.py index ea872d45d0..2db47958fe 100644 --- a/tests/integration/ha_tests/test_smoke.py +++ b/tests/integration/ha_tests/test_smoke.py @@ -12,7 +12,6 @@ from ..helpers import ( APPLICATION_NAME, - CHARM_BASE, ) from ..juju_ import juju_major_version from .helpers import ( @@ -44,7 +43,6 @@ async def test_app_force_removal(ops_test: OpsTest, charm: str): charm, application_name=APPLICATION_NAME, num_units=1, - base=CHARM_BASE, storage={"pgdata": {"pool": "lxd-btrfs", "size": 8046}}, config={"profile": "testing"}, ) @@ -150,7 +148,6 @@ async def test_app_resources_conflicts_v3(ops_test: OpsTest, charm: str): charm, application_name=DUP_APPLICATION_NAME, num_units=1, - base=CHARM_BASE, attach_storage=[tag.storage(garbage_storage)], config={"profile": "testing"}, ) @@ -191,7 +188,6 @@ async def test_app_resources_conflicts_v2(ops_test: OpsTest, charm: str): charm, application_name=DUP_APPLICATION_NAME, num_units=1, - base=CHARM_BASE, config={"profile": "testing"}, ) diff --git a/tests/integration/helpers.py b/tests/integration/helpers.py index 3eb6bb6a3c..ade0742da5 100644 --- a/tests/integration/helpers.py +++ b/tests/integration/helpers.py @@ -1157,7 +1157,6 @@ async def backup_operations( charm, application_name=database_app_name, num_units=2, - base=CHARM_BASE, config={"profile": "testing"}, ) diff --git a/tests/integration/new_relations/test_new_relations.py b/tests/integration/new_relations/test_new_relations.py index 0dc5ad89ee..be6bd96231 100644 --- a/tests/integration/new_relations/test_new_relations.py +++ b/tests/integration/new_relations/test_new_relations.py @@ -15,7 +15,6 @@ from .. import markers from ..helpers import ( - CHARM_BASE, assert_sync_standbys, get_leader_unit, get_machine_from_unit, @@ -65,14 +64,12 @@ async def test_deploy_charms(ops_test: OpsTest, charm): charm, application_name=DATABASE_APP_NAME, num_units=1, - base=CHARM_BASE, config={"profile": "testing"}, ), ops_test.model.deploy( charm, application_name=ANOTHER_DATABASE_APP_NAME, num_units=2, - base=CHARM_BASE, config={"profile": "testing"}, ), ) @@ -635,7 +632,6 @@ async def test_nextcloud_db_blocked(ops_test: OpsTest, charm: str) -> None: charm, application_name=DATABASE_APP_NAME, num_units=1, - base=CHARM_BASE, config={"profile": "testing"}, ), ops_test.model.deploy( diff --git a/tests/integration/new_relations/test_relations_coherence.py b/tests/integration/new_relations/test_relations_coherence.py index 67117c6bb6..ce280b8122 100644 --- a/tests/integration/new_relations/test_relations_coherence.py +++ b/tests/integration/new_relations/test_relations_coherence.py @@ -9,7 +9,7 @@ import pytest from pytest_operator.plugin import OpsTest -from ..helpers import CHARM_BASE, DATABASE_APP_NAME +from ..helpers import DATABASE_APP_NAME from .helpers import build_connection_string from .test_new_relations import DATA_INTEGRATOR_APP_NAME @@ -29,7 +29,6 @@ async def test_relations(ops_test: OpsTest, charm): charm, application_name=DATABASE_APP_NAME, num_units=1, - base=CHARM_BASE, config={"profile": "testing"}, ) await ops_test.model.wait_for_idle(apps=[DATABASE_APP_NAME], status="active", timeout=3000) diff --git a/tests/integration/relations/test_relations.py b/tests/integration/relations/test_relations.py index 277c1e9cd9..c3d08d0210 100644 --- a/tests/integration/relations/test_relations.py +++ b/tests/integration/relations/test_relations.py @@ -9,7 +9,7 @@ from pytest_operator.plugin import OpsTest from tenacity import Retrying, stop_after_delay, wait_fixed -from ..helpers import CHARM_BASE, METADATA +from ..helpers import METADATA from ..new_relations.test_new_relations import APPLICATION_APP_NAME, build_connection_string from ..relations.helpers import get_legacy_db_connection_str @@ -43,7 +43,6 @@ async def test_deploy_charms(ops_test: OpsTest, charm): charm, application_name=APP_NAME, num_units=1, - base=CHARM_BASE, config={ "profile": "testing", "plugin_unaccent_enable": "True", diff --git a/tests/integration/test_backups.py b/tests/integration/test_backups.py index e087abd5b3..d71497f936 100644 --- a/tests/integration/test_backups.py +++ b/tests/integration/test_backups.py @@ -11,7 +11,6 @@ from . import architecture from .helpers import ( - CHARM_BASE, DATABASE_APP_NAME, backup_operations, construct_endpoint, @@ -205,13 +204,11 @@ async def test_restore_on_new_cluster(ops_test: OpsTest, github_secrets, charm) await ops_test.model.deploy( charm, application_name=previous_database_app_name, - base=CHARM_BASE, config={"profile": "testing"}, ) await ops_test.model.deploy( charm, application_name=database_app_name, - base=CHARM_BASE, config={"profile": "testing"}, ) await ops_test.model.relate(previous_database_app_name, S3_INTEGRATOR_APP_NAME) diff --git a/tests/integration/test_backups_pitr.py b/tests/integration/test_backups_pitr.py index 9a76ad3a5a..4398a3aec6 100644 --- a/tests/integration/test_backups_pitr.py +++ b/tests/integration/test_backups_pitr.py @@ -11,7 +11,6 @@ from . import architecture from .helpers import ( - CHARM_BASE, DATABASE_APP_NAME, construct_endpoint, db_connect, @@ -112,7 +111,6 @@ async def pitr_backup_operations( charm, application_name=database_app_name, num_units=2, - base=CHARM_BASE, config={"profile": "testing"}, ) diff --git a/tests/integration/test_charm.py b/tests/integration/test_charm.py index 53ac76e4b2..935670f196 100644 --- a/tests/integration/test_charm.py +++ b/tests/integration/test_charm.py @@ -17,7 +17,6 @@ from .ha_tests.helpers import get_cluster_roles from .helpers import ( - CHARM_BASE, DATABASE_APP_NAME, STORAGE_PATH, check_cluster_members, @@ -50,7 +49,6 @@ async def test_deploy(ops_test: OpsTest, charm: str): charm, application_name=DATABASE_APP_NAME, num_units=3, - base=CHARM_BASE, config={"profile": "testing"}, ) diff --git a/tests/integration/test_config.py b/tests/integration/test_config.py index b6bca2716d..19cf0b4e61 100644 --- a/tests/integration/test_config.py +++ b/tests/integration/test_config.py @@ -7,7 +7,6 @@ from pytest_operator.plugin import OpsTest from .helpers import ( - CHARM_BASE, DATABASE_APP_NAME, build_charm, get_leader_unit, @@ -26,7 +25,6 @@ async def test_config_parameters(ops_test: OpsTest) -> None: await ops_test.model.deploy( charm, num_units=1, - base=CHARM_BASE, config={"profile": "testing"}, ) await ops_test.model.wait_for_idle(apps=[DATABASE_APP_NAME], status="active", timeout=1500) diff --git a/tests/integration/test_db.py b/tests/integration/test_db.py index 8da8b707ea..d06c5d127d 100644 --- a/tests/integration/test_db.py +++ b/tests/integration/test_db.py @@ -50,7 +50,6 @@ async def test_mailman3_core_db(ops_test: OpsTest, charm: str) -> None: charm, application_name=DATABASE_APP_NAME, num_units=DATABASE_UNITS, - base=CHARM_BASE, config={"profile": "testing"}, ) diff --git a/tests/integration/test_db_admin.py b/tests/integration/test_db_admin.py index b95d38d70d..09882403e9 100644 --- a/tests/integration/test_db_admin.py +++ b/tests/integration/test_db_admin.py @@ -11,7 +11,6 @@ from tenacity import Retrying, stop_after_delay, wait_fixed from .helpers import ( - CHARM_BASE, DATABASE_APP_NAME, build_connection_string, check_database_users_existence, @@ -44,7 +43,6 @@ async def test_landscape_scalable_bundle_db(ops_test: OpsTest, charm: str) -> No charm, application_name=DATABASE_APP_NAME, num_units=DATABASE_UNITS, - base=CHARM_BASE, config={"profile": "testing"}, ) diff --git a/tests/integration/test_password_rotation.py b/tests/integration/test_password_rotation.py index ebcaf57a4b..079a8cf92f 100644 --- a/tests/integration/test_password_rotation.py +++ b/tests/integration/test_password_rotation.py @@ -11,7 +11,6 @@ from . import markers from .helpers import ( - CHARM_BASE, METADATA, build_charm, check_patroni, @@ -39,7 +38,6 @@ async def test_deploy_active(ops_test: OpsTest): charm, application_name=APP_NAME, num_units=3, - base=CHARM_BASE, config={"profile": "testing"}, ) await ops_test.model.wait_for_idle(apps=[APP_NAME], status="active", timeout=1500) diff --git a/tests/integration/test_plugins.py b/tests/integration/test_plugins.py index 9c7a40ed88..5e8d2f2d48 100644 --- a/tests/integration/test_plugins.py +++ b/tests/integration/test_plugins.py @@ -8,7 +8,6 @@ from pytest_operator.plugin import OpsTest from .helpers import ( - CHARM_BASE, DATABASE_APP_NAME, build_charm, db_connect, @@ -101,7 +100,6 @@ async def test_plugins(ops_test: OpsTest) -> None: await ops_test.model.deploy( charm, num_units=2, - base=CHARM_BASE, config={"profile": "testing"}, ) await ops_test.model.wait_for_idle(apps=[DATABASE_APP_NAME], status="active", timeout=1500) diff --git a/tests/integration/test_subordinates.py b/tests/integration/test_subordinates.py index b2efdaf979..48463bc0b1 100644 --- a/tests/integration/test_subordinates.py +++ b/tests/integration/test_subordinates.py @@ -9,7 +9,6 @@ from pytest_operator.plugin import OpsTest from .helpers import ( - CHARM_BASE, scale_application, ) @@ -27,7 +26,6 @@ async def test_deploy(ops_test: OpsTest, charm: str, github_secrets): charm, application_name=DATABASE_APP_NAME, num_units=3, - base=CHARM_BASE, ), ops_test.model.deploy( LS_CLIENT, diff --git a/tests/integration/test_tls.py b/tests/integration/test_tls.py index 4654816700..f8cf495948 100644 --- a/tests/integration/test_tls.py +++ b/tests/integration/test_tls.py @@ -12,7 +12,6 @@ change_patroni_setting, ) from .helpers import ( - CHARM_BASE, DATABASE_APP_NAME, METADATA, build_charm, @@ -53,7 +52,6 @@ async def test_deploy_active(ops_test: OpsTest): charm, application_name=APP_NAME, num_units=3, - base=CHARM_BASE, config={"profile": "testing"}, ) # No wait between deploying charms, since we can't guarantee users will wait. Furthermore, From e41d396759116289969cbe64f6d23e603faf63bf Mon Sep 17 00:00:00 2001 From: Lucas Gameiro Borges Date: Mon, 10 Feb 2025 19:21:52 -0300 Subject: [PATCH 36/74] nits --- .github/workflows/release.yaml | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/.github/workflows/release.yaml b/.github/workflows/release.yaml index 7eb0022ef5..2a641f276c 100644 --- a/.github/workflows/release.yaml +++ b/.github/workflows/release.yaml @@ -18,20 +18,21 @@ on: workflow_dispatch: jobs: - build: - name: Build charm - uses: canonical/data-platform-workflows/.github/workflows/build_charm.yaml@v29.1.0 - with: - cache: false + ci-tests: + name: Tests + uses: ./.github/workflows/ci.yaml + secrets: inherit + permissions: + contents: write # Needed for Allure Report beta release: name: Release charm needs: - - build + - ci-tests uses: canonical/data-platform-workflows/.github/workflows/release_charm.yaml@v29.1.0 with: channel: 16/edge - artifact-prefix: ${{ needs.build.outputs.artifact-prefix }} + artifact-prefix: ${{ needs.ci-tests.outputs.artifact-prefix }} secrets: charmhub-token: ${{ secrets.CHARMHUB_TOKEN }} permissions: From 6495a1e2cca3d3fcaa59cab7b121a5daa3164a9f Mon Sep 17 00:00:00 2001 From: Carl Csaposs Date: Tue, 11 Feb 2025 09:41:12 +0000 Subject: [PATCH 37/74] Use `charmcraft test` & concierge (#762) --- .github/workflows/ci.yaml | 39 +- .github/workflows/integration_test.yaml | 316 +++++++++++++ .github/workflows/release.yaml | 2 +- CONTRIBUTING.md | 2 +- concierge.yaml | 13 + poetry.lock | 84 +--- pyproject.toml | 7 +- spread.yaml | 137 ++++++ tests/integration/conftest.py | 15 +- .../ha_tests/test_async_replication.py | 12 - .../integration/ha_tests/test_replication.py | 12 +- .../ha_tests/test_restore_cluster.py | 5 +- tests/integration/ha_tests/test_scaling.py | 9 +- .../ha_tests/test_scaling_three_units.py | 5 +- .../integration/ha_tests/test_self_healing.py | 13 +- tests/integration/ha_tests/test_smoke.py | 4 - tests/integration/ha_tests/test_upgrade.py | 18 +- .../ha_tests/test_upgrade_from_stable.py | 8 +- ...w_relations.py => test_new_relations_1.py} | 56 --- .../new_relations/test_new_relations_2.py | 67 +++ .../new_relations/test_relations_coherence.py | 3 +- tests/integration/relations/test_relations.py | 5 +- tests/integration/test_audit.py | 1 - tests/integration/test_backups_aws.py | 168 +++++++ tests/integration/test_backups_ceph.py | 1 - .../{test_backups.py => test_backups_gcp.py} | 99 +--- ...ckups_pitr.py => test_backups_pitr_aws.py} | 32 +- tests/integration/test_backups_pitr_gcp.py | 440 ++++++++++++++++++ tests/integration/test_charm.py | 9 - tests/integration/test_config.py | 4 +- tests/integration/test_db.py | 7 +- tests/integration/test_db_admin.py | 1 - tests/integration/test_password_rotation.py | 10 +- tests/integration/test_plugins.py | 5 +- tests/integration/test_subordinates.py | 16 +- tests/integration/test_tls.py | 5 +- .../test_async_replication.py/task.yaml | 9 + tests/spread/test_audit.py/task.yaml | 7 + tests/spread/test_backups_aws.py/task.yaml | 9 + tests/spread/test_backups_ceph.py/task.yaml | 9 + tests/spread/test_backups_gcp.py/task.yaml | 9 + .../spread/test_backups_pitr_aws.py/task.yaml | 9 + .../spread/test_backups_pitr_gcp.py/task.yaml | 9 + tests/spread/test_charm.py/task.yaml | 7 + tests/spread/test_config.py/task.yaml | 7 + tests/spread/test_db.py/task.yaml | 7 + tests/spread/test_db_admin.py/task.yaml | 7 + .../spread/test_new_relations_1.py/task.yaml | 7 + .../spread/test_new_relations_2.py/task.yaml | 9 + .../test_password_rotation.py/task.yaml | 7 + tests/spread/test_plugins.py/task.yaml | 7 + tests/spread/test_relations.py/task.yaml | 7 + .../test_relations_coherence.py/task.yaml | 7 + tests/spread/test_replication.py/task.yaml | 7 + .../spread/test_restore_cluster.py/task.yaml | 7 + tests/spread/test_scaling.py/task.yaml | 9 + .../test_scaling_three_units.py/task.yaml | 9 + tests/spread/test_self_healing.py/task.yaml | 7 + tests/spread/test_smoke.py/task.yaml | 7 + tests/spread/test_subordinates.py/task.yaml | 9 + tests/spread/test_tls.py/task.yaml | 7 + tests/spread/test_upgrade.py/task.yaml | 7 + .../test_upgrade_from_stable.py/task.yaml | 7 + tox.ini | 9 +- 64 files changed, 1426 insertions(+), 422 deletions(-) create mode 100644 .github/workflows/integration_test.yaml create mode 100644 concierge.yaml create mode 100644 spread.yaml rename tests/integration/new_relations/{test_new_relations.py => test_new_relations_1.py} (93%) create mode 100644 tests/integration/new_relations/test_new_relations_2.py create mode 100644 tests/integration/test_backups_aws.py rename tests/integration/{test_backups.py => test_backups_gcp.py} (72%) rename tests/integration/{test_backups_pitr.py => test_backups_pitr_aws.py} (94%) create mode 100644 tests/integration/test_backups_pitr_gcp.py create mode 100644 tests/spread/test_async_replication.py/task.yaml create mode 100644 tests/spread/test_audit.py/task.yaml create mode 100644 tests/spread/test_backups_aws.py/task.yaml create mode 100644 tests/spread/test_backups_ceph.py/task.yaml create mode 100644 tests/spread/test_backups_gcp.py/task.yaml create mode 100644 tests/spread/test_backups_pitr_aws.py/task.yaml create mode 100644 tests/spread/test_backups_pitr_gcp.py/task.yaml create mode 100644 tests/spread/test_charm.py/task.yaml create mode 100644 tests/spread/test_config.py/task.yaml create mode 100644 tests/spread/test_db.py/task.yaml create mode 100644 tests/spread/test_db_admin.py/task.yaml create mode 100644 tests/spread/test_new_relations_1.py/task.yaml create mode 100644 tests/spread/test_new_relations_2.py/task.yaml create mode 100644 tests/spread/test_password_rotation.py/task.yaml create mode 100644 tests/spread/test_plugins.py/task.yaml create mode 100644 tests/spread/test_relations.py/task.yaml create mode 100644 tests/spread/test_relations_coherence.py/task.yaml create mode 100644 tests/spread/test_replication.py/task.yaml create mode 100644 tests/spread/test_restore_cluster.py/task.yaml create mode 100644 tests/spread/test_scaling.py/task.yaml create mode 100644 tests/spread/test_scaling_three_units.py/task.yaml create mode 100644 tests/spread/test_self_healing.py/task.yaml create mode 100644 tests/spread/test_smoke.py/task.yaml create mode 100644 tests/spread/test_subordinates.py/task.yaml create mode 100644 tests/spread/test_tls.py/task.yaml create mode 100644 tests/spread/test_upgrade.py/task.yaml create mode 100644 tests/spread/test_upgrade_from_stable.py/task.yaml diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index b9dd27ad30..05c7ea52aa 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -52,45 +52,14 @@ jobs: uses: canonical/data-platform-workflows/.github/workflows/build_charm.yaml@v29.1.0 integration-test: - strategy: - fail-fast: false - matrix: - juju: - - agent: 2.9.51 # renovate: juju-agent-pin-minor - libjuju: ==2.9.49.1 # renovate: latest libjuju 2 - allure_on_amd64: false - - agent: 3.6.2 # renovate: juju-agent-pin-minor - allure_on_amd64: true - architecture: - - amd64 - include: - - juju: - agent: 3.6.2 # renovate: juju-agent-pin-minor - allure_on_amd64: true - architecture: arm64 - name: Integration | ${{ matrix.juju.agent }} | ${{ matrix.architecture }} + name: Integration test charm needs: - lint - unit-test - build - uses: canonical/data-platform-workflows/.github/workflows/integration_test_charm.yaml@v29.1.0 + uses: ./.github/workflows/integration_test.yaml with: artifact-prefix: ${{ needs.build.outputs.artifact-prefix }} - architecture: ${{ matrix.architecture }} - cloud: lxd - juju-agent-version: ${{ matrix.juju.agent }} - libjuju-version-constraint: ${{ matrix.juju.libjuju }} - _beta_allure_report: ${{ matrix.juju.allure_on_amd64 && matrix.architecture == 'amd64' }} - secrets: - integration-test: | - { - "AWS_ACCESS_KEY": "${{ secrets.AWS_ACCESS_KEY }}", - "AWS_SECRET_KEY": "${{ secrets.AWS_SECRET_KEY }}", - "GCP_ACCESS_KEY": "${{ secrets.GCP_ACCESS_KEY }}", - "GCP_SECRET_KEY": "${{ secrets.GCP_SECRET_KEY }}", - "UBUNTU_PRO_TOKEN" : "${{ secrets.UBUNTU_PRO_TOKEN }}", - "LANDSCAPE_ACCOUNT_NAME": "${{ secrets.LANDSCAPE_ACCOUNT_NAME }}", - "LANDSCAPE_REGISTRATION_KEY": "${{ secrets.LANDSCAPE_REGISTRATION_KEY }}", - } + secrets: inherit permissions: - contents: write # Needed for Allure Report beta + contents: write # Needed for Allure Report diff --git a/.github/workflows/integration_test.yaml b/.github/workflows/integration_test.yaml new file mode 100644 index 0000000000..a89194c525 --- /dev/null +++ b/.github/workflows/integration_test.yaml @@ -0,0 +1,316 @@ +on: + workflow_call: + inputs: + artifact-prefix: + description: | + Prefix for charm package GitHub artifact(s) + + Use canonical/data-platform-workflows build_charm.yaml to build the charm(s) + required: true + type: string + +jobs: + collect-integration-tests: + name: Collect integration test spread jobs + runs-on: ubuntu-latest + timeout-minutes: 5 + steps: + - name: Checkout + uses: actions/checkout@v4 + - name: Set up environment + run: | + sudo snap install charmcraft --classic + pipx install tox poetry + - name: Collect spread jobs + id: collect-jobs + shell: python + run: | + import json + import os + import subprocess + + spread_jobs = ( + subprocess.run( + ["charmcraft", "test", "--list", "github-ci"], capture_output=True, check=True, text=True + ) + .stdout.strip() + .split("\n") + ) + jobs = [] + for job in spread_jobs: + # Example `job`: "github-ci:ubuntu-24.04:tests/spread/test_charm.py:juju36" + _, runner, task, variant = job.split(":") + # Example: "test_charm.py" + task = task.removeprefix("tests/spread/") + if runner.endswith("-arm"): + architecture = "arm64" + else: + architecture = "amd64" + # Example: "test_charm.py:juju36 | amd64" + name = f"{task}:{variant} | {architecture}" + # ":" character not valid in GitHub Actions artifact + name_in_artifact = f"{task}-{variant}-{architecture}" + jobs.append({ + "spread_job": job, + "name": name, + "name_in_artifact": name_in_artifact, + "runner": runner, + }) + output = f"jobs={json.dumps(jobs)}" + print(output) + with open(os.environ["GITHUB_OUTPUT"], "a") as file: + file.write(output) + - name: Generate Allure default test results + if: ${{ github.event_name == 'schedule' && github.run_attempt == '1' }} + run: tox run -e integration -- tests/integration --allure-default-dir=allure-default-results + - name: Upload Allure default results + # Default test results in case the integration tests time out or runner set up fails + # (So that Allure report will show "unknown"/"failed" test result, instead of omitting the test) + if: ${{ github.event_name == 'schedule' && github.run_attempt == '1' }} + uses: actions/upload-artifact@v4 + with: + name: allure-default-results-integration-test + path: allure-default-results/ + if-no-files-found: error + outputs: + jobs: ${{ steps.collect-jobs.outputs.jobs }} + + integration-test: + strategy: + fail-fast: false + matrix: + job: ${{ fromJSON(needs.collect-integration-tests.outputs.jobs) }} + name: ${{ matrix.job.name }} + needs: + - collect-integration-tests + runs-on: ${{ matrix.job.runner }} + timeout-minutes: 217 # Sum of steps `timeout-minutes` + 5 + steps: + - name: Free up disk space + timeout-minutes: 1 + run: | + printf '\nDisk usage before cleanup\n' + df --human-readable + # Based on https://github.com/actions/runner-images/issues/2840#issuecomment-790492173 + rm -r /opt/hostedtoolcache/ + printf '\nDisk usage after cleanup\n' + df --human-readable + - name: Checkout + timeout-minutes: 3 + uses: actions/checkout@v4 + - name: Set up environment + timeout-minutes: 5 + run: sudo snap install charmcraft --classic + # TODO: remove when https://github.com/canonical/charmcraft/issues/2105 and + # https://github.com/canonical/charmcraft/issues/2130 fixed + - run: | + sudo snap install go --classic + go install github.com/snapcore/spread/cmd/spread@latest + - name: Download packed charm(s) + timeout-minutes: 5 + uses: actions/download-artifact@v4 + with: + pattern: ${{ inputs.artifact-prefix }}-* + merge-multiple: true + - name: Run spread job + timeout-minutes: 180 + id: spread + # TODO: replace with `charmcraft test` when + # https://github.com/canonical/charmcraft/issues/2105 and + # https://github.com/canonical/charmcraft/issues/2130 fixed + run: ~/go/bin/spread -vv -artifacts=artifacts '${{ matrix.job.spread_job }}' + env: + AWS_ACCESS_KEY: ${{ secrets.AWS_ACCESS_KEY }} + AWS_SECRET_KEY: ${{ secrets.AWS_SECRET_KEY }} + GCP_ACCESS_KEY: ${{ secrets.GCP_ACCESS_KEY }} + GCP_SECRET_KEY: ${{ secrets.GCP_SECRET_KEY }} + UBUNTU_PRO_TOKEN: ${{ secrets.UBUNTU_PRO_TOKEN }} + LANDSCAPE_ACCOUNT_NAME: ${{ secrets.LANDSCAPE_ACCOUNT_NAME }} + LANDSCAPE_REGISTRATION_KEY: ${{ secrets.LANDSCAPE_REGISTRATION_KEY }} + - name: Upload Allure results + timeout-minutes: 3 + # Only upload results from one spread system & one spread variant + # Allure can only process one result per pytest test ID. If parameterization is done via + # spread instead of pytest, there will be overlapping pytest test IDs. + if: ${{ (success() || (failure() && steps.spread.outcome == 'failure')) && startsWith(matrix.job.spread_job, 'github-ci:ubuntu-24.04:') && endsWith(matrix.job.spread_job, ':juju36') && github.event_name == 'schedule' && github.run_attempt == '1' }} + uses: actions/upload-artifact@v4 + with: + name: allure-results-integration-test-${{ matrix.job.name_in_artifact }} + path: artifacts/${{ matrix.job.spread_job }}/allure-results/ + if-no-files-found: error + - timeout-minutes: 1 + if: ${{ success() || (failure() && steps.spread.outcome == 'failure') }} + run: snap list + - name: Select model + timeout-minutes: 1 + # `!contains(matrix.job.spread_job, 'juju29')` workaround for juju 2 error: + # "ERROR cannot acquire lock file to read controller concierge-microk8s: unable to open + # /tmp/juju-store-lock-3635383939333230: permission denied" + # Unable to workaround error with `sudo rm /tmp/juju-*` + if: ${{ !contains(matrix.job.spread_job, 'juju29') && (success() || (failure() && steps.spread.outcome == 'failure')) }} + id: juju-switch + run: | + # sudo needed since spread runs scripts as root + # "testing" is default model created by concierge + sudo juju switch testing + mkdir ~/logs/ + - name: juju status + timeout-minutes: 1 + if: ${{ !contains(matrix.job.spread_job, 'juju29') && (success() || (failure() && steps.spread.outcome == 'failure')) }} + run: sudo juju status --color --relations | tee ~/logs/juju-status.txt + - name: juju debug-log + timeout-minutes: 3 + if: ${{ !contains(matrix.job.spread_job, 'juju29') && (success() || (failure() && steps.spread.outcome == 'failure')) }} + run: sudo juju debug-log --color --replay --no-tail | tee ~/logs/juju-debug-log.txt + - name: jhack tail + timeout-minutes: 3 + if: ${{ !contains(matrix.job.spread_job, 'juju29') && (success() || (failure() && steps.spread.outcome == 'failure')) }} + run: sudo jhack tail --printer raw --replay --no-watch | tee ~/logs/jhack-tail.txt + - name: Upload logs + timeout-minutes: 5 + if: ${{ !contains(matrix.job.spread_job, 'juju29') && (success() || (failure() && steps.spread.outcome == 'failure')) }} + uses: actions/upload-artifact@v4 + with: + name: logs-integration-test-${{ matrix.job.name_in_artifact }} + path: ~/logs/ + if-no-files-found: error + - name: Disk usage + timeout-minutes: 1 + if: ${{ success() || (failure() && steps.spread.outcome == 'failure') }} + run: df --human-readable + + allure-report: + # TODO future improvement: use concurrency group for job + name: Publish Allure report + if: ${{ !cancelled() && github.event_name == 'schedule' && github.run_attempt == '1' }} + needs: + - integration-test + runs-on: ubuntu-latest + timeout-minutes: 5 + steps: + - name: Download Allure + # Following instructions from https://allurereport.org/docs/install-for-linux/#install-from-a-deb-package + run: gh release download --repo allure-framework/allure2 --pattern 'allure_*.deb' + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + - name: Install Allure + run: | + sudo apt-get update + sudo apt-get install ./allure_*.deb -y + # For first run, manually create branch with no history + # (e.g. + # git checkout --orphan gh-pages-beta + # git rm -rf . + # touch .nojekyll + # git add .nojekyll + # git commit -m "Initial commit" + # git push origin gh-pages-beta + # ) + - name: Checkout GitHub pages branch + uses: actions/checkout@v4 + with: + ref: gh-pages-beta + path: repo/ + - name: Download default test results + # Default test results in case the integration tests time out or runner set up fails + # (So that Allure report will show "unknown"/"failed" test result, instead of omitting the test) + uses: actions/download-artifact@v4 + with: + path: allure-default-results/ + name: allure-default-results-integration-test + - name: Download test results + uses: actions/download-artifact@v4 + with: + path: allure-results/ + pattern: allure-results-integration-test-* + merge-multiple: true + - name: Combine Allure default results & actual results + # For every test: if actual result available, use that. Otherwise, use default result + # So that, if actual result not available, Allure report will show "unknown"/"failed" test result + # instead of omitting the test + shell: python + run: | + import dataclasses + import json + import pathlib + + + @dataclasses.dataclass(frozen=True) + class Result: + test_case_id: str + path: pathlib.Path + + def __eq__(self, other): + if not isinstance(other, type(self)): + return False + return self.test_case_id == other.test_case_id + + + actual_results = pathlib.Path("allure-results") + default_results = pathlib.Path("allure-default-results") + + results: dict[pathlib.Path, set[Result]] = { + actual_results: set(), + default_results: set(), + } + for directory, results_ in results.items(): + for path in directory.glob("*-result.json"): + with path.open("r") as file: + id_ = json.load(file)["testCaseId"] + results_.add(Result(id_, path)) + + actual_results.mkdir(exist_ok=True) + + missing_results = results[default_results] - results[actual_results] + for default_result in missing_results: + # Move to `actual_results` directory + default_result.path.rename(actual_results / default_result.path.name) + - name: Load test report history + run: | + if [[ -d repo/_latest/history/ ]] + then + echo 'Loading history' + cp -r repo/_latest/history/ allure-results/ + fi + - name: Create executor.json + shell: python + run: | + # Reverse engineered from https://github.com/simple-elf/allure-report-action/blob/eca283b643d577c69b8e4f048dd6cd8eb8457cfd/entrypoint.sh + import json + + DATA = { + "name": "GitHub Actions", + "type": "github", + "buildOrder": ${{ github.run_number }}, # TODO future improvement: use run ID + "buildName": "Run ${{ github.run_id }}", + "buildUrl": "https://github.com/${{ github.repository }}/actions/runs/${{ github.run_id }}", + "reportUrl": "../${{ github.run_number }}/", + } + with open("allure-results/executor.json", "w") as file: + json.dump(DATA, file) + - name: Generate Allure report + run: allure generate + - name: Create index.html + shell: python + run: | + DATA = f""" + + + + """ + with open("repo/index.html", "w") as file: + file.write(DATA) + - name: Update GitHub pages branch + working-directory: repo/ + # TODO future improvement: commit message + run: | + mkdir '${{ github.run_number }}' + rm -f _latest + ln -s '${{ github.run_number }}' _latest + cp -r ../allure-report/. _latest/ + git add . + git config user.name "GitHub Actions" + git config user.email "41898282+github-actions[bot]@users.noreply.github.com" + git commit -m "Allure report ${{ github.run_number }}" + # Uses token set in checkout step + git push origin gh-pages-beta diff --git a/.github/workflows/release.yaml b/.github/workflows/release.yaml index deefea45b2..b3de155ce1 100644 --- a/.github/workflows/release.yaml +++ b/.github/workflows/release.yaml @@ -21,7 +21,7 @@ jobs: uses: ./.github/workflows/ci.yaml secrets: inherit permissions: - contents: write # Needed for Allure Report beta + contents: write # Needed for Allure Report release: name: Release charm diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 2e22c8c702..388378cdff 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -33,7 +33,7 @@ source venv/bin/activate tox run -e format # update your code according to linting rules tox run -e lint # code style tox run -e unit # unit tests -tox run -e integration # integration tests +charmcraft test lxd-vm: # integration tests tox # runs 'lint' and 'unit' environments ``` diff --git a/concierge.yaml b/concierge.yaml new file mode 100644 index 0000000000..15a78cc947 --- /dev/null +++ b/concierge.yaml @@ -0,0 +1,13 @@ +juju: + model-defaults: + logging-config: =INFO; unit=DEBUG +providers: + lxd: + enable: true + bootstrap: true +host: + snaps: + jhack: + channel: latest/edge + connections: + - jhack:dot-local-share-juju snapd diff --git a/poetry.lock b/poetry.lock index c3e2e6d8ba..a951426a86 100644 --- a/poetry.lock +++ b/poetry.lock @@ -1,4 +1,4 @@ -# This file is automatically @generated by Poetry 2.0.1 and should not be changed by hand. +# This file is automatically @generated by Poetry 2.0.0 and should not be changed by hand. [[package]] name = "allure-pytest" @@ -17,26 +17,21 @@ allure-python-commons = "2.13.5" pytest = ">=4.5.0" [[package]] -name = "allure-pytest-collection-report" -version = "0.1.0" -description = "" +name = "allure-pytest-default-results" +version = "0.1.2" +description = "Generate default \"unknown\" results to show in Allure Report if test case does not run" optional = false python-versions = ">=3.8" groups = ["integration"] -files = [] -develop = false +files = [ + {file = "allure_pytest_default_results-0.1.2-py3-none-any.whl", hash = "sha256:8dc6c5a5d548661c38111a2890509e794204586fa81cefbe61315fb63996e50c"}, + {file = "allure_pytest_default_results-0.1.2.tar.gz", hash = "sha256:eb6c16aa1c2ede69e653a0ee38094791685eaacb0ac6b2cae5c6da1379dbdbfd"}, +] [package.dependencies] allure-pytest = ">=2.13.5" pytest = "*" -[package.source] -type = "git" -url = "https://github.com/canonical/data-platform-workflows" -reference = "v29.1.0" -resolved_reference = "cf3e292107a8d420c452e35cf7552c225add7fbd" -subdirectory = "python/pytest_plugins/allure_pytest_collection_report" - [[package]] name = "allure-python-commons" version = "2.13.5" @@ -566,6 +561,7 @@ files = [ {file = "cryptography-44.0.0-cp37-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:761817a3377ef15ac23cd7834715081791d4ec77f9297ee694ca1ee9c2c7e5eb"}, {file = "cryptography-44.0.0-cp37-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:3c672a53c0fb4725a29c303be906d3c1fa99c32f58abe008a82705f9ee96f40b"}, {file = "cryptography-44.0.0-cp37-abi3-manylinux_2_34_aarch64.whl", hash = "sha256:4ac4c9f37eba52cb6fbeaf5b59c152ea976726b865bd4cf87883a7e7006cc543"}, + {file = "cryptography-44.0.0-cp37-abi3-manylinux_2_34_x86_64.whl", hash = "sha256:60eb32934076fa07e4316b7b2742fa52cbb190b42c2df2863dbc4230a0a9b385"}, {file = "cryptography-44.0.0-cp37-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:ed3534eb1090483c96178fcb0f8893719d96d5274dfde98aa6add34614e97c8e"}, {file = "cryptography-44.0.0-cp37-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:f3f6fdfa89ee2d9d496e2c087cebef9d4fcbb0ad63c40e821b39f74bf48d9c5e"}, {file = "cryptography-44.0.0-cp37-abi3-win32.whl", hash = "sha256:eb33480f1bad5b78233b0ad3e1b0be21e8ef1da745d8d2aecbb20671658b9053"}, @@ -576,6 +572,7 @@ files = [ {file = "cryptography-44.0.0-cp39-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:c5eb858beed7835e5ad1faba59e865109f3e52b3783b9ac21e7e47dc5554e289"}, {file = "cryptography-44.0.0-cp39-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:f53c2c87e0fb4b0c00fa9571082a057e37690a8f12233306161c8f4b819960b7"}, {file = "cryptography-44.0.0-cp39-abi3-manylinux_2_34_aarch64.whl", hash = "sha256:9e6fc8a08e116fb7c7dd1f040074c9d7b51d74a8ea40d4df2fc7aa08b76b9e6c"}, + {file = "cryptography-44.0.0-cp39-abi3-manylinux_2_34_x86_64.whl", hash = "sha256:9abcc2e083cbe8dde89124a47e5e53ec38751f0d7dfd36801008f316a127d7ba"}, {file = "cryptography-44.0.0-cp39-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:d2436114e46b36d00f8b72ff57e598978b37399d2786fd39793c36c6d5cb1c64"}, {file = "cryptography-44.0.0-cp39-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:a01956ddfa0a6790d594f5b34fc1bfa6098aca434696a03cfdbe469b8ed79285"}, {file = "cryptography-44.0.0-cp39-abi3-win32.whl", hash = "sha256:eca27345e1214d1b9f9490d200f9db5a874479be914199194e746c893788d417"}, @@ -1555,7 +1552,6 @@ files = [ {file = "psycopg2-2.9.10-cp311-cp311-win_amd64.whl", hash = "sha256:0435034157049f6846e95103bd8f5a668788dd913a7c30162ca9503fdf542cb4"}, {file = "psycopg2-2.9.10-cp312-cp312-win32.whl", hash = "sha256:65a63d7ab0e067e2cdb3cf266de39663203d38d6a8ed97f5ca0cb315c73fe067"}, {file = "psycopg2-2.9.10-cp312-cp312-win_amd64.whl", hash = "sha256:4a579d6243da40a7b3182e0430493dbd55950c493d8c68f4eec0b302f6bbf20e"}, - {file = "psycopg2-2.9.10-cp313-cp313-win_amd64.whl", hash = "sha256:91fd603a2155da8d0cfcdbf8ab24a2d54bca72795b90d2a3ed2b6da8d979dee2"}, {file = "psycopg2-2.9.10-cp39-cp39-win32.whl", hash = "sha256:9d5b3b94b79a844a986d029eee38998232451119ad653aea42bb9220a8c5066b"}, {file = "psycopg2-2.9.10-cp39-cp39-win_amd64.whl", hash = "sha256:88138c8dedcbfa96408023ea2b0c369eda40fe5d75002c0964c78f46f11fa442"}, {file = "psycopg2-2.9.10.tar.gz", hash = "sha256:12ec0b40b0273f95296233e8750441339298e6a572f7039da5b260e3c8b60e11"}, @@ -1616,7 +1612,6 @@ files = [ {file = "psycopg2_binary-2.9.10-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:bb89f0a835bcfc1d42ccd5f41f04870c1b936d8507c6df12b7737febc40f0909"}, {file = "psycopg2_binary-2.9.10-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:f0c2d907a1e102526dd2986df638343388b94c33860ff3bbe1384130828714b1"}, {file = "psycopg2_binary-2.9.10-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:f8157bed2f51db683f31306aa497311b560f2265998122abe1dce6428bd86567"}, - {file = "psycopg2_binary-2.9.10-cp313-cp313-win_amd64.whl", hash = "sha256:27422aa5f11fbcd9b18da48373eb67081243662f9b46e6fd07c3eb46e4535142"}, {file = "psycopg2_binary-2.9.10-cp38-cp38-macosx_12_0_x86_64.whl", hash = "sha256:eb09aa7f9cecb45027683bb55aebaaf45a0df8bf6de68801a6afdc7947bb09d4"}, {file = "psycopg2_binary-2.9.10-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b73d6d7f0ccdad7bc43e6d34273f70d587ef62f824d7261c4ae9b8b1b6af90e8"}, {file = "psycopg2_binary-2.9.10-cp38-cp38-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ce5ab4bf46a211a8e924d307c1b1fcda82368586a19d0a24f8ae166f5c784864"}, @@ -1900,23 +1895,6 @@ pytest = ">=7.0.0" docs = ["sphinx (>=5.3)", "sphinx-rtd-theme (>=1.0)"] testing = ["coverage (>=6.2)", "flaky (>=3.5.0)", "hypothesis (>=5.7.1)", "mypy (>=0.931)", "pytest-trio (>=0.7.0)"] -[[package]] -name = "pytest-github-secrets" -version = "0.1.0" -description = "" -optional = false -python-versions = ">=3.8" -groups = ["integration"] -files = [] -develop = false - -[package.source] -type = "git" -url = "https://github.com/canonical/data-platform-workflows" -reference = "v29.1.0" -resolved_reference = "cf3e292107a8d420c452e35cf7552c225add7fbd" -subdirectory = "python/pytest_plugins/github_secrets" - [[package]] name = "pytest-operator" version = "0.39.0" @@ -1937,46 +1915,6 @@ pytest = "*" pytest-asyncio = "<0.23" pyyaml = "*" -[[package]] -name = "pytest-operator-cache" -version = "0.1.0" -description = "" -optional = false -python-versions = ">=3.8" -groups = ["integration"] -files = [] -develop = false - -[package.dependencies] -pyyaml = "*" - -[package.source] -type = "git" -url = "https://github.com/canonical/data-platform-workflows" -reference = "v29.1.0" -resolved_reference = "cf3e292107a8d420c452e35cf7552c225add7fbd" -subdirectory = "python/pytest_plugins/pytest_operator_cache" - -[[package]] -name = "pytest-operator-groups" -version = "0.1.0" -description = "" -optional = false -python-versions = ">=3.8" -groups = ["integration"] -files = [] -develop = false - -[package.dependencies] -pytest = "*" - -[package.source] -type = "git" -url = "https://github.com/canonical/data-platform-workflows" -reference = "v29.1.0" -resolved_reference = "cf3e292107a8d420c452e35cf7552c225add7fbd" -subdirectory = "python/pytest_plugins/pytest_operator_groups" - [[package]] name = "python-dateutil" version = "2.9.0.post0" @@ -2697,4 +2635,4 @@ type = ["pytest-mypy"] [metadata] lock-version = "2.1" python-versions = "^3.10" -content-hash = "b89b458ed0f91e834c027be0d2541d464e97d65aaf56b0454c1ed07b004828e1" +content-hash = "2bc4a893d47cdea828762f430354381eeea5e1ef3685f83302c33299117a439d" diff --git a/pyproject.toml b/pyproject.toml index 11a4e50151..fee816917b 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -61,10 +61,7 @@ optional = true [tool.poetry.group.integration.dependencies] pytest = "^8.3.4" -pytest-github-secrets = {git = "https://github.com/canonical/data-platform-workflows", tag = "v29.1.0", subdirectory = "python/pytest_plugins/github_secrets"} pytest-operator = "^0.39.0" -pytest-operator-cache = {git = "https://github.com/canonical/data-platform-workflows", tag = "v29.1.0", subdirectory = "python/pytest_plugins/pytest_operator_cache"} -pytest-operator-groups = {git = "https://github.com/canonical/data-platform-workflows", tag = "v29.1.0", subdirectory = "python/pytest_plugins/pytest_operator_groups"} # renovate caret doesn't work: https://github.com/renovatebot/renovate/issues/26940 juju = "<=3.6.1.0" boto3 = "*" @@ -73,7 +70,7 @@ landscape-api-py3 = "^0.9.0" mailmanclient = "^3.3.5" psycopg2-binary = "^2.9.10" allure-pytest = "^2.13.5" -allure-pytest-collection-report = {git = "https://github.com/canonical/data-platform-workflows", tag = "v29.1.0", subdirectory = "python/pytest_plugins/allure_pytest_collection_report"} +allure-pytest-default-results = "^0.1.2" # Testing tools configuration [tool.coverage.run] @@ -89,7 +86,7 @@ exclude_lines = [ minversion = "6.0" log_cli_level = "INFO" asyncio_mode = "auto" -markers = ["unstable", "juju2", "juju3", "juju_secrets"] +markers = ["juju2", "juju3", "juju_secrets"] # Formatting tools configuration [tool.black] diff --git a/spread.yaml b/spread.yaml new file mode 100644 index 0000000000..2b435a5885 --- /dev/null +++ b/spread.yaml @@ -0,0 +1,137 @@ +project: postgresql-operator + +backends: + # Derived from https://github.com/jnsgruk/zinc-k8s-operator/blob/a21eae8399eb3b9df4ddb934b837af25ef831976/spread.yaml#L11 + lxd-vm: + # TODO: remove after https://github.com/canonical/spread/pull/185 merged & in charmcraft + type: adhoc + allocate: | + hash=$(python3 -c "import hashlib; print(hashlib.sha256('$SPREAD_PASSWORD'.encode()).hexdigest()[:6])") + VM_NAME="${VM_NAME:-${SPREAD_SYSTEM//./-}-${hash}}" + DISK="${DISK:-20}" + CPU="${CPU:-4}" + MEM="${MEM:-8}" + + cloud_config="#cloud-config + ssh_pwauth: true + users: + - default + - name: runner + plain_text_passwd: $SPREAD_PASSWORD + lock_passwd: false + sudo: ALL=(ALL) NOPASSWD:ALL + " + + lxc launch --vm \ + "${SPREAD_SYSTEM//-/:}" \ + "${VM_NAME}" \ + -c user.user-data="${cloud_config}" \ + -c limits.cpu="${CPU}" \ + -c limits.memory="${MEM}GiB" \ + -d root,size="${DISK}GiB" + + # Wait for the runner user + while ! lxc exec "${VM_NAME}" -- id -u runner &>/dev/null; do sleep 0.5; done + + # Set the instance address for spread + ADDRESS "$(lxc ls -f csv | grep "${VM_NAME}" | cut -d"," -f3 | cut -d" " -f1)" + discard: | + hash=$(python3 -c "import hashlib; print(hashlib.sha256('$SPREAD_PASSWORD'.encode()).hexdigest()[:6])") + VM_NAME="${VM_NAME:-${SPREAD_SYSTEM//./-}-${hash}}" + lxc delete --force "${VM_NAME}" + environment: + CONCIERGE_EXTRA_SNAPS: charmcraft + CONCIERGE_EXTRA_DEBS: pipx + systems: + - ubuntu-24.04: + username: runner + prepare: | + systemctl disable --now unattended-upgrades.service + systemctl mask unattended-upgrades.service + pipx install charmcraftcache + cd "$SPREAD_PATH" + charmcraftcache pack -v + restore-each: | + cd "$SPREAD_PATH" + # Revert python-libjuju version override + git restore pyproject.toml poetry.lock + + # Use instead of `concierge restore` to save time between tests + # For example, with microk8s, using `concierge restore` takes twice as long as this (e.g. 6 + # min instead of 3 min between every spread job) + juju destroy-model --force --no-wait --destroy-storage --no-prompt testing + juju kill-controller --no-prompt concierge-lxd + restore: | + rm -rf "$SPREAD_PATH" + + github-ci: + type: adhoc + # Only run on CI + manual: true + # HACK: spread requires runners to be accessible via SSH + # Configure local sshd & instruct spread to connect to the same machine spread is running on + # (spread cannot provision GitHub Actions runners, so we provision a GitHub Actions runner for + # each spread job & select a single job when running spread) + # Derived from https://github.com/jnsgruk/zinc-k8s-operator/blob/a21eae8399eb3b9df4ddb934b837af25ef831976/spread.yaml#L47 + allocate: | + sudo tee /etc/ssh/sshd_config.d/10-spread-github-ci.conf << 'EOF' + PasswordAuthentication yes + PermitEmptyPasswords yes + EOF + + ADDRESS localhost + # HACK: spread does not pass environment variables set on runner + # Manually pass specific environment variables + environment: + CI: '$(HOST: echo $CI)' + AWS_ACCESS_KEY: '$(HOST: echo $AWS_ACCESS_KEY)' + AWS_SECRET_KEY: '$(HOST: echo $AWS_SECRET_KEY)' + GCP_ACCESS_KEY: '$(HOST: echo $GCP_ACCESS_KEY)' + GCP_SECRET_KEY: '$(HOST: echo $GCP_SECRET_KEY)' + UBUNTU_PRO_TOKEN: '$(HOST: echo $UBUNTU_PRO_TOKEN)' + LANDSCAPE_ACCOUNT_NAME: '$(HOST: echo $LANDSCAPE_ACCOUNT_NAME)' + LANDSCAPE_REGISTRATION_KEY: '$(HOST: echo $LANDSCAPE_REGISTRATION_KEY)' + systems: + - ubuntu-24.04: + username: runner + - ubuntu-24.04-arm: + username: runner + variants: + - -juju29 + +suites: + tests/spread/: + summary: Spread tests + +path: /root/spread_project + +kill-timeout: 3h +environment: + PATH: $PATH:$(pipx environment --value PIPX_BIN_DIR) + CONCIERGE_JUJU_CHANNEL/juju36: 3.6/stable + CONCIERGE_JUJU_CHANNEL/juju29: 2.9/stable +prepare: | + snap refresh --hold + chown -R root:root "$SPREAD_PATH" + cd "$SPREAD_PATH" + snap install --classic concierge + + # Install charmcraft & pipx (on lxd-vm backend) + concierge prepare --trace + + pipx install tox poetry +prepare-each: | + cd "$SPREAD_PATH" + if [[ $SPREAD_VARIANT == *"juju29"* ]] + then + # Each version of python-libjuju is only compatible with one major Juju version + # Override python-libjuju version pinned in poetry.lock + poetry add --lock --group integration juju@^2 + fi + # `concierge prepare` needs to be run for each spread job in case Juju version changed + concierge prepare --trace + + # Unable to set constraint on all models because of Juju bug: + # https://bugs.launchpad.net/juju/+bug/2065050 + juju set-model-constraints arch="$(dpkg --print-architecture)" +# Only restore on lxd backend—no need to restore on CI diff --git a/tests/integration/conftest.py b/tests/integration/conftest.py index 87bd24fb9b..bdce9d8e13 100644 --- a/tests/integration/conftest.py +++ b/tests/integration/conftest.py @@ -1,12 +1,13 @@ -#!/usr/bin/env python3 # Copyright 2022 Canonical Ltd. # See LICENSE file for licensing details. import pytest -from pytest_operator.plugin import OpsTest +from . import architecture -@pytest.fixture(scope="module") -async def charm(ops_test: OpsTest): - """Build the charm-under-test.""" - # Build charm from local source folder. - yield await ops_test.build_charm(".") + +@pytest.fixture(scope="session") +def charm(): + # Return str instead of pathlib.Path since python-libjuju's model.deploy(), juju deploy, and + # juju bundle files expect local charms to begin with `./` or `/` to distinguish them from + # Charmhub charms. + return f"./postgresql_ubuntu@22.04-{architecture.architecture}.charm" diff --git a/tests/integration/ha_tests/test_async_replication.py b/tests/integration/ha_tests/test_async_replication.py index 7b76660bb3..588486471f 100644 --- a/tests/integration/ha_tests/test_async_replication.py +++ b/tests/integration/ha_tests/test_async_replication.py @@ -99,7 +99,6 @@ async def second_model_continuous_writes(second_model) -> None: assert action.results["result"] == "True", "Unable to clear up continuous_writes table" -@pytest.mark.group(1) @markers.juju3 @pytest.mark.abort_on_fail async def test_deploy_async_replication_setup( @@ -107,7 +106,6 @@ async def test_deploy_async_replication_setup( ) -> None: """Build and deploy two PostgreSQL cluster in two separate models to test async replication.""" if not await app_name(ops_test): - charm = await ops_test.build_charm(".") await ops_test.model.deploy( charm, num_units=CLUSTER_SIZE, @@ -122,7 +120,6 @@ async def test_deploy_async_replication_setup( ) await ops_test.model.relate(DATABASE_APP_NAME, DATA_INTEGRATOR_APP_NAME) if not await app_name(ops_test, model=second_model): - charm = await ops_test.build_charm(".") await second_model.deploy( charm, num_units=CLUSTER_SIZE, @@ -146,7 +143,6 @@ async def test_deploy_async_replication_setup( ) -@pytest.mark.group(1) @markers.juju3 @pytest.mark.abort_on_fail async def test_async_replication( @@ -224,7 +220,6 @@ async def test_async_replication( await check_writes(ops_test, extra_model=second_model) -@pytest.mark.group(1) @markers.juju3 @pytest.mark.abort_on_fail async def test_get_data_integrator_credentials( @@ -237,7 +232,6 @@ async def test_get_data_integrator_credentials( data_integrator_credentials = result.results -@pytest.mark.group(1) @markers.juju3 @pytest.mark.abort_on_fail async def test_switchover( @@ -292,7 +286,6 @@ async def test_switchover( await are_writes_increasing(ops_test, extra_model=second_model) -@pytest.mark.group(1) @markers.juju3 @pytest.mark.abort_on_fail async def test_data_integrator_creds_keep_on_working( @@ -315,7 +308,6 @@ async def test_data_integrator_creds_keep_on_working( connection.close() -@pytest.mark.group(1) @markers.juju3 @pytest.mark.abort_on_fail async def test_promote_standby( @@ -393,7 +385,6 @@ async def test_promote_standby( await are_writes_increasing(ops_test) -@pytest.mark.group(1) @markers.juju3 @pytest.mark.abort_on_fail async def test_reestablish_relation( @@ -451,7 +442,6 @@ async def test_reestablish_relation( await check_writes(ops_test, extra_model=second_model) -@pytest.mark.group(1) @markers.juju3 @pytest.mark.abort_on_fail async def test_async_replication_failover_in_main_cluster( @@ -497,7 +487,6 @@ async def test_async_replication_failover_in_main_cluster( await check_writes(ops_test, extra_model=second_model) -@pytest.mark.group(1) @markers.juju3 @pytest.mark.abort_on_fail async def test_async_replication_failover_in_secondary_cluster( @@ -534,7 +523,6 @@ async def test_async_replication_failover_in_secondary_cluster( await check_writes(ops_test, extra_model=second_model) -@pytest.mark.group(1) @markers.juju3 @pytest.mark.abort_on_fail async def test_scaling( diff --git a/tests/integration/ha_tests/test_replication.py b/tests/integration/ha_tests/test_replication.py index dd924948da..b1c8da31a4 100644 --- a/tests/integration/ha_tests/test_replication.py +++ b/tests/integration/ha_tests/test_replication.py @@ -18,16 +18,14 @@ ) -@pytest.mark.group(1) @pytest.mark.abort_on_fail -async def test_build_and_deploy(ops_test: OpsTest) -> None: +async def test_build_and_deploy(ops_test: OpsTest, charm) -> None: """Build and deploy three unit of PostgreSQL.""" wait_for_apps = False # It is possible for users to provide their own cluster for HA testing. Hence, check if there # is a pre-existing cluster. if not await app_name(ops_test): wait_for_apps = True - charm = await ops_test.build_charm(".") async with ops_test.fast_forward(): await ops_test.model.deploy( charm, @@ -51,7 +49,6 @@ async def test_build_and_deploy(ops_test: OpsTest) -> None: await ops_test.model.wait_for_idle(status="active", timeout=1500) -@pytest.mark.group(1) async def test_reelection(ops_test: OpsTest, continuous_writes, primary_start_timeout) -> None: """Kill primary unit, check reelection.""" app = await app_name(ops_test) @@ -89,7 +86,6 @@ async def test_reelection(ops_test: OpsTest, continuous_writes, primary_start_ti await check_writes(ops_test) -@pytest.mark.group(1) async def test_consistency(ops_test: OpsTest, continuous_writes) -> None: """Write to primary, read data from secondaries (check consistency).""" # Locate primary unit. @@ -106,8 +102,9 @@ async def test_consistency(ops_test: OpsTest, continuous_writes) -> None: await check_writes(ops_test) -@pytest.mark.group(1) -async def test_no_data_replicated_between_clusters(ops_test: OpsTest, continuous_writes) -> None: +async def test_no_data_replicated_between_clusters( + ops_test: OpsTest, charm, continuous_writes +) -> None: """Check that writes in one cluster are not replicated to another cluster.""" # Locate primary unit. app = await app_name(ops_test) @@ -116,7 +113,6 @@ async def test_no_data_replicated_between_clusters(ops_test: OpsTest, continuous # Deploy another cluster. new_cluster_app = f"second-{app}" if not await app_name(ops_test, new_cluster_app): - charm = await ops_test.build_charm(".") async with ops_test.fast_forward(): await ops_test.model.deploy( charm, diff --git a/tests/integration/ha_tests/test_restore_cluster.py b/tests/integration/ha_tests/test_restore_cluster.py index 9542dbb850..8a26b15cb5 100644 --- a/tests/integration/ha_tests/test_restore_cluster.py +++ b/tests/integration/ha_tests/test_restore_cluster.py @@ -29,12 +29,10 @@ charm = None -@pytest.mark.group(1) @pytest.mark.abort_on_fail -async def test_build_and_deploy(ops_test: OpsTest) -> None: +async def test_build_and_deploy(ops_test: OpsTest, charm) -> None: """Build and deploy two PostgreSQL clusters.""" # This is a potentially destructive test, so it shouldn't be run against existing clusters - charm = await ops_test.build_charm(".") async with ops_test.fast_forward(): # Deploy the first cluster with reusable storage await ops_test.model.deploy( @@ -68,7 +66,6 @@ async def test_build_and_deploy(ops_test: OpsTest) -> None: await ops_test.model.destroy_unit(second_primary) -@pytest.mark.group(1) async def test_cluster_restore(ops_test): """Recreates the cluster from storage volumes.""" # Write some data. diff --git a/tests/integration/ha_tests/test_scaling.py b/tests/integration/ha_tests/test_scaling.py index f3105d38cd..6e43d1f83a 100644 --- a/tests/integration/ha_tests/test_scaling.py +++ b/tests/integration/ha_tests/test_scaling.py @@ -27,13 +27,11 @@ charm = None -@pytest.mark.group(1) @markers.juju3 @pytest.mark.abort_on_fail -async def test_build_and_deploy(ops_test: OpsTest) -> None: +async def test_build_and_deploy(ops_test: OpsTest, charm) -> None: """Build and deploy two PostgreSQL clusters.""" # This is a potentially destructive test, so it shouldn't be run against existing clusters - charm = await ops_test.build_charm(".") async with ops_test.fast_forward(): # Deploy the first cluster with reusable storage await gather( @@ -55,7 +53,6 @@ async def test_build_and_deploy(ops_test: OpsTest) -> None: await ops_test.model.wait_for_idle(status="active", timeout=1500) -@pytest.mark.group(1) @markers.juju3 @pytest.mark.abort_on_fail async def test_removing_stereo_primary(ops_test: OpsTest, continuous_writes) -> None: @@ -105,7 +102,6 @@ async def test_removing_stereo_primary(ops_test: OpsTest, continuous_writes) -> await check_writes(ops_test) -@pytest.mark.group(1) @markers.juju3 @pytest.mark.abort_on_fail async def test_removing_stereo_sync_standby(ops_test: OpsTest, continuous_writes) -> None: @@ -140,7 +136,6 @@ async def test_removing_stereo_sync_standby(ops_test: OpsTest, continuous_writes await check_writes(ops_test) -@pytest.mark.group(1) @markers.juju3 @pytest.mark.abort_on_fail async def test_scale_to_five_units(ops_test: OpsTest) -> None: @@ -148,7 +143,6 @@ async def test_scale_to_five_units(ops_test: OpsTest) -> None: await ops_test.model.wait_for_idle(status="active", timeout=1500) -@pytest.mark.group(1) @markers.juju3 @pytest.mark.abort_on_fail async def test_removing_raft_majority(ops_test: OpsTest, continuous_writes) -> None: @@ -206,7 +200,6 @@ async def test_removing_raft_majority(ops_test: OpsTest, continuous_writes) -> N assert new_roles["primaries"][0] == original_roles["sync_standbys"][1] -@pytest.mark.group(1) @markers.juju3 @pytest.mark.abort_on_fail async def test_removing_raft_majority_async(ops_test: OpsTest, continuous_writes) -> None: diff --git a/tests/integration/ha_tests/test_scaling_three_units.py b/tests/integration/ha_tests/test_scaling_three_units.py index 6817cd238a..74a1e8ba4b 100644 --- a/tests/integration/ha_tests/test_scaling_three_units.py +++ b/tests/integration/ha_tests/test_scaling_three_units.py @@ -28,13 +28,11 @@ charm = None -@pytest.mark.group(1) @markers.juju3 @pytest.mark.abort_on_fail -async def test_build_and_deploy(ops_test: OpsTest) -> None: +async def test_build_and_deploy(ops_test: OpsTest, charm) -> None: """Build and deploy two PostgreSQL clusters.""" # This is a potentially destructive test, so it shouldn't be run against existing clusters - charm = await ops_test.build_charm(".") async with ops_test.fast_forward(): # Deploy the first cluster with reusable storage await gather( @@ -56,7 +54,6 @@ async def test_build_and_deploy(ops_test: OpsTest) -> None: await ops_test.model.wait_for_idle(status="active", timeout=1500) -@pytest.mark.group(1) @markers.juju3 @pytest.mark.parametrize( "roles", diff --git a/tests/integration/ha_tests/test_self_healing.py b/tests/integration/ha_tests/test_self_healing.py index 12b61a4fd7..f3ddc6fe88 100644 --- a/tests/integration/ha_tests/test_self_healing.py +++ b/tests/integration/ha_tests/test_self_healing.py @@ -62,16 +62,14 @@ MEDIAN_ELECTION_TIME = 10 -@pytest.mark.group(1) @pytest.mark.abort_on_fail -async def test_build_and_deploy(ops_test: OpsTest) -> None: +async def test_build_and_deploy(ops_test: OpsTest, charm) -> None: """Build and deploy three unit of PostgreSQL.""" wait_for_apps = False # It is possible for users to provide their own cluster for HA testing. Hence, check if there # is a pre-existing cluster. if not await app_name(ops_test): wait_for_apps = True - charm = await ops_test.build_charm(".") async with ops_test.fast_forward(): await ops_test.model.deploy( charm, @@ -96,7 +94,6 @@ async def test_build_and_deploy(ops_test: OpsTest) -> None: await ops_test.model.wait_for_idle(status="active", timeout=1500) -@pytest.mark.group(1) @pytest.mark.abort_on_fail async def test_storage_re_use(ops_test, continuous_writes): """Verifies that database units with attached storage correctly repurpose storage. @@ -144,7 +141,6 @@ async def test_storage_re_use(ops_test, continuous_writes): ) -@pytest.mark.group(1) @pytest.mark.abort_on_fail @pytest.mark.parametrize("process", DB_PROCESSES) @pytest.mark.parametrize("signal", ["SIGTERM", "SIGKILL"]) @@ -179,7 +175,6 @@ async def test_interruption_db_process( await is_cluster_updated(ops_test, primary_name) -@pytest.mark.group(1) @pytest.mark.abort_on_fail @pytest.mark.parametrize("process", DB_PROCESSES) async def test_freeze_db_process( @@ -221,7 +216,6 @@ async def test_freeze_db_process( await is_cluster_updated(ops_test, primary_name) -@pytest.mark.group(1) @pytest.mark.abort_on_fail @pytest.mark.parametrize("process", DB_PROCESSES) @pytest.mark.parametrize("signal", ["SIGTERM", "SIGKILL"]) @@ -309,9 +303,8 @@ async def test_full_cluster_restart( await check_writes(ops_test) -@pytest.mark.group(1) @pytest.mark.abort_on_fail -@pytest.mark.unstable +@pytest.mark.skip(reason="Unstable") async def test_forceful_restart_without_data_and_transaction_logs( ops_test: OpsTest, continuous_writes, @@ -386,7 +379,6 @@ async def test_forceful_restart_without_data_and_transaction_logs( await is_cluster_updated(ops_test, primary_name) -@pytest.mark.group(1) @pytest.mark.abort_on_fail async def test_network_cut(ops_test: OpsTest, continuous_writes, primary_start_timeout): """Completely cut and restore network.""" @@ -475,7 +467,6 @@ async def test_network_cut(ops_test: OpsTest, continuous_writes, primary_start_t await is_cluster_updated(ops_test, primary_name, use_ip_from_inside=True) -@pytest.mark.group(1) @pytest.mark.abort_on_fail async def test_network_cut_without_ip_change( ops_test: OpsTest, continuous_writes, primary_start_timeout diff --git a/tests/integration/ha_tests/test_smoke.py b/tests/integration/ha_tests/test_smoke.py index ea872d45d0..3e718522ee 100644 --- a/tests/integration/ha_tests/test_smoke.py +++ b/tests/integration/ha_tests/test_smoke.py @@ -33,7 +33,6 @@ logger = logging.getLogger(__name__) -@pytest.mark.group(1) @pytest.mark.abort_on_fail async def test_app_force_removal(ops_test: OpsTest, charm: str): """Remove unit with force while storage is alive.""" @@ -93,7 +92,6 @@ async def test_app_force_removal(ops_test: OpsTest, charm: str): assert await is_storage_exists(ops_test, storage_id_str) -@pytest.mark.group(1) @pytest.mark.abort_on_fail async def test_charm_garbage_ignorance(ops_test: OpsTest, charm: str): """Test charm deploy in dirty environment with garbage storage.""" @@ -133,7 +131,6 @@ async def test_charm_garbage_ignorance(ops_test: OpsTest, charm: str): await ops_test.model.destroy_unit(primary_name) -@pytest.mark.group(1) @pytest.mark.abort_on_fail @pytest.mark.skipif(juju_major_version < 3, reason="Requires juju 3 or higher") async def test_app_resources_conflicts_v3(ops_test: OpsTest, charm: str): @@ -173,7 +170,6 @@ async def test_app_resources_conflicts_v3(ops_test: OpsTest, charm: str): ) -@pytest.mark.group(1) @pytest.mark.abort_on_fail @pytest.mark.skipif(juju_major_version != 2, reason="Requires juju 2") async def test_app_resources_conflicts_v2(ops_test: OpsTest, charm: str): diff --git a/tests/integration/ha_tests/test_upgrade.py b/tests/integration/ha_tests/test_upgrade.py index 497c7ace9a..06b98bcceb 100644 --- a/tests/integration/ha_tests/test_upgrade.py +++ b/tests/integration/ha_tests/test_upgrade.py @@ -29,7 +29,6 @@ TIMEOUT = 600 -@pytest.mark.group(1) @pytest.mark.abort_on_fail async def test_deploy_latest(ops_test: OpsTest) -> None: """Simple test to ensure that the PostgreSQL and application charms get deployed.""" @@ -52,7 +51,6 @@ async def test_deploy_latest(ops_test: OpsTest) -> None: assert len(ops_test.model.applications[DATABASE_APP_NAME].units) == 3 -@pytest.mark.group(1) @pytest.mark.abort_on_fail async def test_pre_upgrade_check(ops_test: OpsTest) -> None: """Test that the pre-upgrade-check action runs successfully.""" @@ -65,9 +63,8 @@ async def test_pre_upgrade_check(ops_test: OpsTest) -> None: await action.wait() -@pytest.mark.group(1) @pytest.mark.abort_on_fail -async def test_upgrade_from_edge(ops_test: OpsTest, continuous_writes) -> None: +async def test_upgrade_from_edge(ops_test: OpsTest, continuous_writes, charm) -> None: # Start an application that continuously writes data to the database. logger.info("starting continuous writes to the database") await start_continuous_writes(ops_test, DATABASE_APP_NAME) @@ -81,9 +78,6 @@ async def test_upgrade_from_edge(ops_test: OpsTest, continuous_writes) -> None: application = ops_test.model.applications[DATABASE_APP_NAME] - logger.info("Build charm locally") - charm = await ops_test.build_charm(".") - logger.info("Refresh the charm") await application.refresh(path=charm) @@ -115,9 +109,8 @@ async def test_upgrade_from_edge(ops_test: OpsTest, continuous_writes) -> None: ) -@pytest.mark.group(1) @pytest.mark.abort_on_fail -async def test_fail_and_rollback(ops_test, continuous_writes) -> None: +async def test_fail_and_rollback(ops_test, charm, continuous_writes) -> None: # Start an application that continuously writes data to the database. logger.info("starting continuous writes to the database") await start_continuous_writes(ops_test, DATABASE_APP_NAME) @@ -134,10 +127,9 @@ async def test_fail_and_rollback(ops_test, continuous_writes) -> None: action = await leader_unit.run_action("pre-upgrade-check") await action.wait() - local_charm = await ops_test.build_charm(".") - filename = local_charm.split("/")[-1] if isinstance(local_charm, str) else local_charm.name + filename = Path(charm).name fault_charm = Path("/tmp/", filename) - shutil.copy(local_charm, fault_charm) + shutil.copy(charm, fault_charm) logger.info("Inject dependency fault") await inject_dependency_fault(ops_test, DATABASE_APP_NAME, fault_charm) @@ -162,7 +154,7 @@ async def test_fail_and_rollback(ops_test, continuous_writes) -> None: await action.wait() logger.info("Re-refresh the charm") - await application.refresh(path=local_charm) + await application.refresh(path=charm) logger.info("Wait for upgrade to start") await ops_test.model.block_until( diff --git a/tests/integration/ha_tests/test_upgrade_from_stable.py b/tests/integration/ha_tests/test_upgrade_from_stable.py index 977ec9c067..db0586a2ab 100644 --- a/tests/integration/ha_tests/test_upgrade_from_stable.py +++ b/tests/integration/ha_tests/test_upgrade_from_stable.py @@ -25,7 +25,6 @@ TIMEOUT = 900 -@pytest.mark.group(1) @pytest.mark.abort_on_fail async def test_deploy_stable(ops_test: OpsTest) -> None: """Simple test to ensure that the PostgreSQL and application charms get deployed.""" @@ -76,7 +75,6 @@ async def test_deploy_stable(ops_test: OpsTest) -> None: assert len(ops_test.model.applications[DATABASE_APP_NAME].units) == 3 -@pytest.mark.group(1) @pytest.mark.abort_on_fail async def test_pre_upgrade_check(ops_test: OpsTest) -> None: """Test that the pre-upgrade-check action runs successfully.""" @@ -94,9 +92,8 @@ async def test_pre_upgrade_check(ops_test: OpsTest) -> None: await action.wait() -@pytest.mark.group(1) @pytest.mark.abort_on_fail -async def test_upgrade_from_stable(ops_test: OpsTest): +async def test_upgrade_from_stable(ops_test: OpsTest, charm): """Test updating from stable channel.""" # Start an application that continuously writes data to the database. logger.info("starting continuous writes to the database") @@ -112,9 +109,6 @@ async def test_upgrade_from_stable(ops_test: OpsTest): application = ops_test.model.applications[DATABASE_APP_NAME] actions = await application.get_actions() - logger.info("Build charm locally") - charm = await ops_test.build_charm(".") - logger.info("Refresh the charm") await application.refresh(path=charm) diff --git a/tests/integration/new_relations/test_new_relations.py b/tests/integration/new_relations/test_new_relations_1.py similarity index 93% rename from tests/integration/new_relations/test_new_relations.py rename to tests/integration/new_relations/test_new_relations_1.py index 70069e86e4..42571a80b9 100644 --- a/tests/integration/new_relations/test_new_relations.py +++ b/tests/integration/new_relations/test_new_relations_1.py @@ -1,4 +1,3 @@ -#!/usr/bin/env python3 # Copyright 2022 Canonical Ltd. # See LICENSE file for licensing details. import asyncio @@ -13,7 +12,6 @@ from pytest_operator.plugin import OpsTest from tenacity import Retrying, stop_after_attempt, wait_fixed -from .. import markers from ..helpers import ( CHARM_BASE, assert_sync_standbys, @@ -47,7 +45,6 @@ INVALID_EXTRA_USER_ROLE_BLOCKING_MESSAGE = "invalid role(s) for extra user roles" -@pytest.mark.group("new_relations_tests") @pytest.mark.abort_on_fail async def test_deploy_charms(ops_test: OpsTest, charm): """Deploy both charms (application and database) to use in the tests.""" @@ -81,7 +78,6 @@ async def test_deploy_charms(ops_test: OpsTest, charm): await ops_test.model.wait_for_idle(apps=APP_NAMES, status="active", timeout=3000) -@pytest.mark.group("new_relations_tests") async def test_no_read_only_endpoint_in_standalone_cluster(ops_test: OpsTest): """Test that there is no read-only endpoint in a standalone cluster.""" async with ops_test.fast_forward(): @@ -128,7 +124,6 @@ async def test_no_read_only_endpoint_in_standalone_cluster(ops_test: OpsTest): ) -@pytest.mark.group("new_relations_tests") async def test_read_only_endpoint_in_scaled_up_cluster(ops_test: OpsTest): """Test that there is read-only endpoint in a scaled up cluster.""" async with ops_test.fast_forward(): @@ -146,7 +141,6 @@ async def test_read_only_endpoint_in_scaled_up_cluster(ops_test: OpsTest): ) -@pytest.mark.group("new_relations_tests") async def test_database_relation_with_charm_libraries(ops_test: OpsTest): """Test basic functionality of database relation interface.""" # Get the connection string to connect to the database using the read/write endpoint. @@ -194,7 +188,6 @@ async def test_database_relation_with_charm_libraries(ops_test: OpsTest): cursor.execute("DROP TABLE test;") -@pytest.mark.group("new_relations_tests") @pytest.mark.abort_on_fail async def test_filter_out_degraded_replicas(ops_test: OpsTest): primary = await get_primary(ops_test, f"{DATABASE_APP_NAME}/0") @@ -225,7 +218,6 @@ async def test_filter_out_degraded_replicas(ops_test: OpsTest): ) -@pytest.mark.group("new_relations_tests") async def test_user_with_extra_roles(ops_test: OpsTest): """Test superuser actions and the request for more permissions.""" # Get the connection string to connect to the database. @@ -246,7 +238,6 @@ async def test_user_with_extra_roles(ops_test: OpsTest): connection.close() -@pytest.mark.group("new_relations_tests") async def test_two_applications_doesnt_share_the_same_relation_data(ops_test: OpsTest): """Test that two different application connect to the database with different credentials.""" # Set some variables to use in this test. @@ -300,7 +291,6 @@ async def test_two_applications_doesnt_share_the_same_relation_data(ops_test: Op psycopg2.connect(connection_string) -@pytest.mark.group("new_relations_tests") async def test_an_application_can_connect_to_multiple_database_clusters(ops_test: OpsTest): """Test that an application can connect to different clusters of the same database.""" # Relate the application with both database clusters @@ -331,7 +321,6 @@ async def test_an_application_can_connect_to_multiple_database_clusters(ops_test assert application_connection_string != another_application_connection_string -@pytest.mark.group("new_relations_tests") async def test_an_application_can_connect_to_multiple_aliased_database_clusters(ops_test: OpsTest): """Test that an application can connect to different clusters of the same database.""" # Relate the application with both database clusters @@ -365,7 +354,6 @@ async def test_an_application_can_connect_to_multiple_aliased_database_clusters( assert application_connection_string != another_application_connection_string -@pytest.mark.group("new_relations_tests") async def test_an_application_can_request_multiple_databases(ops_test: OpsTest): """Test that an application can request additional databases using the same interface.""" # Relate the charms using another relation and wait for them exchanging some connection data. @@ -386,7 +374,6 @@ async def test_an_application_can_request_multiple_databases(ops_test: OpsTest): assert first_database_connection_string != second_database_connection_string -@pytest.mark.group("new_relations_tests") @pytest.mark.abort_on_fail async def test_relation_data_is_updated_correctly_when_scaling(ops_test: OpsTest): """Test that relation data, like connection data, is updated correctly when scaling.""" @@ -467,7 +454,6 @@ async def test_relation_data_is_updated_correctly_when_scaling(ops_test: OpsTest psycopg2.connect(primary_connection_string) -@pytest.mark.group("new_relations_tests") async def test_relation_with_no_database_name(ops_test: OpsTest): """Test that a relation with no database name doesn't block the charm.""" async with ops_test.fast_forward(): @@ -484,7 +470,6 @@ async def test_relation_with_no_database_name(ops_test: OpsTest): await ops_test.model.wait_for_idle(apps=APP_NAMES, status="active", raise_on_blocked=True) -@pytest.mark.group("new_relations_tests") async def test_admin_role(ops_test: OpsTest): """Test that the admin role gives access to all the databases.""" all_app_names = [DATA_INTEGRATOR_APP_NAME] @@ -567,7 +552,6 @@ async def test_admin_role(ops_test: OpsTest): connection.close() -@pytest.mark.group("new_relations_tests") async def test_invalid_extra_user_roles(ops_test: OpsTest): async with ops_test.fast_forward(): # Remove the relation between the database and the first data integrator. @@ -627,43 +611,3 @@ async def test_invalid_extra_user_roles(ops_test: OpsTest): raise_on_blocked=False, timeout=1000, ) - - -@pytest.mark.group("nextcloud_blocked") -@markers.amd64_only # nextcloud charm not available for arm64 -async def test_nextcloud_db_blocked(ops_test: OpsTest, charm: str) -> None: - # Deploy Database Charm and Nextcloud - await asyncio.gather( - ops_test.model.deploy( - charm, - application_name=DATABASE_APP_NAME, - num_units=1, - base=CHARM_BASE, - config={"profile": "testing"}, - ), - ops_test.model.deploy( - "nextcloud", - channel="edge", - application_name="nextcloud", - num_units=1, - base=CHARM_BASE, - ), - ) - await asyncio.gather( - ops_test.model.wait_for_idle(apps=[DATABASE_APP_NAME], status="active", timeout=2000), - ops_test.model.wait_for_idle( - apps=["nextcloud"], - status="blocked", - raise_on_blocked=False, - timeout=2000, - ), - ) - - await ops_test.model.relate("nextcloud:database", f"{DATABASE_APP_NAME}:database") - - await ops_test.model.wait_for_idle( - apps=[DATABASE_APP_NAME, "nextcloud"], - status="active", - raise_on_blocked=False, - timeout=1000, - ) diff --git a/tests/integration/new_relations/test_new_relations_2.py b/tests/integration/new_relations/test_new_relations_2.py new file mode 100644 index 0000000000..08827c168c --- /dev/null +++ b/tests/integration/new_relations/test_new_relations_2.py @@ -0,0 +1,67 @@ +# Copyright 2022 Canonical Ltd. +# See LICENSE file for licensing details. +import asyncio +import logging +from pathlib import Path + +import yaml +from pytest_operator.plugin import OpsTest + +from .. import markers +from ..helpers import ( + CHARM_BASE, +) + +logger = logging.getLogger(__name__) + +APPLICATION_APP_NAME = "postgresql-test-app" +DATABASE_APP_NAME = "database" +ANOTHER_DATABASE_APP_NAME = "another-database" +DATA_INTEGRATOR_APP_NAME = "data-integrator" +APP_NAMES = [APPLICATION_APP_NAME, DATABASE_APP_NAME, ANOTHER_DATABASE_APP_NAME] +DATABASE_APP_METADATA = yaml.safe_load(Path("./metadata.yaml").read_text()) +FIRST_DATABASE_RELATION_NAME = "database" +SECOND_DATABASE_RELATION_NAME = "second-database" +MULTIPLE_DATABASE_CLUSTERS_RELATION_NAME = "multiple-database-clusters" +ALIASED_MULTIPLE_DATABASE_CLUSTERS_RELATION_NAME = "aliased-multiple-database-clusters" +NO_DATABASE_RELATION_NAME = "no-database" +INVALID_EXTRA_USER_ROLE_BLOCKING_MESSAGE = "invalid role(s) for extra user roles" + + +@markers.amd64_only # nextcloud charm not available for arm64 +async def test_nextcloud_db_blocked(ops_test: OpsTest, charm: str) -> None: + # Deploy Database Charm and Nextcloud + await asyncio.gather( + ops_test.model.deploy( + charm, + application_name=DATABASE_APP_NAME, + num_units=1, + base=CHARM_BASE, + config={"profile": "testing"}, + ), + ops_test.model.deploy( + "nextcloud", + channel="edge", + application_name="nextcloud", + num_units=1, + base=CHARM_BASE, + ), + ) + await asyncio.gather( + ops_test.model.wait_for_idle(apps=[DATABASE_APP_NAME], status="active", timeout=2000), + ops_test.model.wait_for_idle( + apps=["nextcloud"], + status="blocked", + raise_on_blocked=False, + timeout=2000, + ), + ) + + await ops_test.model.relate("nextcloud:database", f"{DATABASE_APP_NAME}:database") + + await ops_test.model.wait_for_idle( + apps=[DATABASE_APP_NAME, "nextcloud"], + status="active", + raise_on_blocked=False, + timeout=1000, + ) diff --git a/tests/integration/new_relations/test_relations_coherence.py b/tests/integration/new_relations/test_relations_coherence.py index 1f2a751922..fa44d33399 100644 --- a/tests/integration/new_relations/test_relations_coherence.py +++ b/tests/integration/new_relations/test_relations_coherence.py @@ -11,7 +11,7 @@ from ..helpers import CHARM_BASE, DATABASE_APP_NAME from .helpers import build_connection_string -from .test_new_relations import DATA_INTEGRATOR_APP_NAME +from .test_new_relations_1 import DATA_INTEGRATOR_APP_NAME logger = logging.getLogger(__name__) @@ -20,7 +20,6 @@ FIRST_DATABASE_RELATION_NAME = "database" -@pytest.mark.group(1) @pytest.mark.abort_on_fail async def test_relations(ops_test: OpsTest, charm): """Test that check relation data.""" diff --git a/tests/integration/relations/test_relations.py b/tests/integration/relations/test_relations.py index b3a542decc..0037b5c140 100644 --- a/tests/integration/relations/test_relations.py +++ b/tests/integration/relations/test_relations.py @@ -10,7 +10,7 @@ from tenacity import Retrying, stop_after_delay, wait_fixed from ..helpers import CHARM_BASE, METADATA -from ..new_relations.test_new_relations import APPLICATION_APP_NAME, build_connection_string +from ..new_relations.test_new_relations_1 import APPLICATION_APP_NAME, build_connection_string from ..relations.helpers import get_legacy_db_connection_str logger = logging.getLogger(__name__) @@ -25,7 +25,6 @@ APP_NAMES = [APP_NAME, DATABASE_APP_NAME, DB_APP_NAME] -@pytest.mark.group(1) @pytest.mark.abort_on_fail async def test_deploy_charms(ops_test: OpsTest, charm): """Deploy both charms (application and database) to use in the tests.""" @@ -63,7 +62,6 @@ async def test_deploy_charms(ops_test: OpsTest, charm): await ops_test.model.wait_for_idle(apps=APP_NAMES, status="active", timeout=3000) -@pytest.mark.group(1) async def test_legacy_endpoint_with_multiple_related_endpoints(ops_test: OpsTest): await ops_test.model.relate(f"{DB_APP_NAME}:{DB_RELATION}", f"{APP_NAME}:{DB_RELATION}") await ops_test.model.relate(APP_NAME, f"{DATABASE_APP_NAME}:{FIRST_DATABASE_RELATION}") @@ -104,7 +102,6 @@ async def test_legacy_endpoint_with_multiple_related_endpoints(ops_test: OpsTest psycopg2.connect(legacy_interface_connect) -@pytest.mark.group(1) async def test_modern_endpoint_with_multiple_related_endpoints(ops_test: OpsTest): await ops_test.model.relate(f"{DB_APP_NAME}:{DB_RELATION}", f"{APP_NAME}:{DB_RELATION}") await ops_test.model.relate(APP_NAME, f"{DATABASE_APP_NAME}:{FIRST_DATABASE_RELATION}") diff --git a/tests/integration/test_audit.py b/tests/integration/test_audit.py index 257ef9cf70..4b4c3ae5e0 100644 --- a/tests/integration/test_audit.py +++ b/tests/integration/test_audit.py @@ -21,7 +21,6 @@ RELATION_ENDPOINT = "database" -@pytest.mark.group(1) @pytest.mark.abort_on_fail async def test_audit_plugin(ops_test: OpsTest, charm) -> None: """Test the audit plugin.""" diff --git a/tests/integration/test_backups_aws.py b/tests/integration/test_backups_aws.py new file mode 100644 index 0000000000..76343af0ac --- /dev/null +++ b/tests/integration/test_backups_aws.py @@ -0,0 +1,168 @@ +#!/usr/bin/env python3 +# Copyright 2023 Canonical Ltd. +# See LICENSE file for licensing details. +import logging +import os +import uuid + +import boto3 +import pytest as pytest +from pytest_operator.plugin import OpsTest +from tenacity import Retrying, stop_after_attempt, wait_exponential + +from . import architecture +from .helpers import ( + DATABASE_APP_NAME, + backup_operations, + construct_endpoint, + db_connect, + get_password, + get_primary, + get_unit_address, + scale_application, + switchover, +) +from .juju_ import juju_major_version + +ANOTHER_CLUSTER_REPOSITORY_ERROR_MESSAGE = "the S3 repository has backups from another cluster" +FAILED_TO_ACCESS_CREATE_BUCKET_ERROR_MESSAGE = ( + "failed to access/create the bucket, check your S3 settings" +) +S3_INTEGRATOR_APP_NAME = "s3-integrator" +if juju_major_version < 3: + tls_certificates_app_name = "tls-certificates-operator" + tls_channel = "legacy/edge" if architecture.architecture == "arm64" else "legacy/stable" + tls_config = {"generate-self-signed-certificates": "true", "ca-common-name": "Test CA"} +else: + tls_certificates_app_name = "self-signed-certificates" + tls_channel = "latest/edge" if architecture.architecture == "arm64" else "latest/stable" + tls_config = {"ca-common-name": "Test CA"} + +logger = logging.getLogger(__name__) + +AWS = "AWS" +GCP = "GCP" + + +@pytest.fixture(scope="module") +async def cloud_configs() -> None: + # Define some configurations and credentials. + configs = { + AWS: { + "endpoint": "https://s3.amazonaws.com", + "bucket": "data-charms-testing", + "path": f"/postgresql-vm/{uuid.uuid1()}", + "region": "us-east-1", + }, + GCP: { + "endpoint": "https://storage.googleapis.com", + "bucket": "data-charms-testing", + "path": f"/postgresql-vm/{uuid.uuid1()}", + "region": "", + }, + } + credentials = { + AWS: { + "access-key": os.environ["AWS_ACCESS_KEY"], + "secret-key": os.environ["AWS_SECRET_KEY"], + }, + GCP: { + "access-key": os.environ["GCP_ACCESS_KEY"], + "secret-key": os.environ["GCP_SECRET_KEY"], + }, + } + yield configs, credentials + # Delete the previously created objects. + logger.info("deleting the previously created backups") + for cloud, config in configs.items(): + session = boto3.session.Session( + aws_access_key_id=credentials[cloud]["access-key"], + aws_secret_access_key=credentials[cloud]["secret-key"], + region_name=config["region"], + ) + s3 = session.resource( + "s3", endpoint_url=construct_endpoint(config["endpoint"], config["region"]) + ) + bucket = s3.Bucket(config["bucket"]) + # GCS doesn't support batch delete operation, so delete the objects one by one. + for bucket_object in bucket.objects.filter(Prefix=config["path"].lstrip("/")): + bucket_object.delete() + + +@pytest.mark.abort_on_fail +async def test_backup_aws(ops_test: OpsTest, cloud_configs: tuple[dict, dict], charm) -> None: + """Build and deploy two units of PostgreSQL in AWS, test backup and restore actions.""" + config = cloud_configs[0][AWS] + credentials = cloud_configs[1][AWS] + + await backup_operations( + ops_test, + S3_INTEGRATOR_APP_NAME, + tls_certificates_app_name, + tls_config, + tls_channel, + credentials, + AWS, + config, + charm, + ) + database_app_name = f"{DATABASE_APP_NAME}-aws" + + # Remove the relation to the TLS certificates operator. + await ops_test.model.applications[database_app_name].remove_relation( + f"{database_app_name}:certificates", f"{tls_certificates_app_name}:certificates" + ) + + new_unit_name = f"{database_app_name}/2" + + # Scale up to be able to test primary and leader being different. + async with ops_test.fast_forward(): + await scale_application(ops_test, database_app_name, 2) + + # Ensure replication is working correctly. + address = get_unit_address(ops_test, new_unit_name) + password = await get_password(ops_test, new_unit_name) + patroni_password = await get_password(ops_test, new_unit_name, "patroni") + with db_connect(host=address, password=password) as connection, connection.cursor() as cursor: + cursor.execute( + "SELECT EXISTS (SELECT FROM information_schema.tables" + " WHERE table_schema = 'public' AND table_name = 'backup_table_1');" + ) + assert cursor.fetchone()[0], ( + f"replication isn't working correctly: table 'backup_table_1' doesn't exist in {new_unit_name}" + ) + cursor.execute( + "SELECT EXISTS (SELECT FROM information_schema.tables" + " WHERE table_schema = 'public' AND table_name = 'backup_table_2');" + ) + assert not cursor.fetchone()[0], ( + f"replication isn't working correctly: table 'backup_table_2' exists in {new_unit_name}" + ) + connection.close() + + old_primary = await get_primary(ops_test, new_unit_name) + switchover(ops_test, old_primary, patroni_password, new_unit_name) + + # Get the new primary unit. + primary = await get_primary(ops_test, new_unit_name) + # Check that the primary changed. + for attempt in Retrying( + stop=stop_after_attempt(3), wait=wait_exponential(multiplier=1, min=2, max=30) + ): + with attempt: + assert primary == new_unit_name + + # Ensure stanza is working correctly. + logger.info("listing the available backups") + action = await ops_test.model.units.get(new_unit_name).run_action("list-backups") + await action.wait() + backups = action.results.get("backups") + assert backups, "backups not outputted" + + await ops_test.model.wait_for_idle(status="active", timeout=1000) + + # Remove the database app. + await ops_test.model.remove_application(database_app_name, block_until_done=True) + + # Remove the TLS operator. + await ops_test.model.remove_application(tls_certificates_app_name, block_until_done=True) diff --git a/tests/integration/test_backups_ceph.py b/tests/integration/test_backups_ceph.py index 99a57fb1e1..bee798191a 100644 --- a/tests/integration/test_backups_ceph.py +++ b/tests/integration/test_backups_ceph.py @@ -190,7 +190,6 @@ def cloud_configs(microceph: ConnectionInformation): } -@pytest.mark.group("ceph") @markers.amd64_only async def test_backup_ceph(ops_test: OpsTest, cloud_configs, cloud_credentials, charm) -> None: """Build and deploy two units of PostgreSQL in microceph, test backup and restore actions.""" diff --git a/tests/integration/test_backups.py b/tests/integration/test_backups_gcp.py similarity index 72% rename from tests/integration/test_backups.py rename to tests/integration/test_backups_gcp.py index e087abd5b3..63cb3617bd 100644 --- a/tests/integration/test_backups.py +++ b/tests/integration/test_backups_gcp.py @@ -2,6 +2,7 @@ # Copyright 2023 Canonical Ltd. # See LICENSE file for licensing details. import logging +import os import uuid import boto3 @@ -17,10 +18,7 @@ construct_endpoint, db_connect, get_password, - get_primary, get_unit_address, - scale_application, - switchover, wait_for_idle_on_blocked, ) from .juju_ import juju_major_version @@ -46,7 +44,7 @@ @pytest.fixture(scope="module") -async def cloud_configs(github_secrets) -> None: +async def cloud_configs() -> None: # Define some configurations and credentials. configs = { AWS: { @@ -64,12 +62,12 @@ async def cloud_configs(github_secrets) -> None: } credentials = { AWS: { - "access-key": github_secrets["AWS_ACCESS_KEY"], - "secret-key": github_secrets["AWS_SECRET_KEY"], + "access-key": os.environ["AWS_ACCESS_KEY"], + "secret-key": os.environ["AWS_SECRET_KEY"], }, GCP: { - "access-key": github_secrets["GCP_ACCESS_KEY"], - "secret-key": github_secrets["GCP_SECRET_KEY"], + "access-key": os.environ["GCP_ACCESS_KEY"], + "secret-key": os.environ["GCP_SECRET_KEY"], }, } yield configs, credentials @@ -90,87 +88,6 @@ async def cloud_configs(github_secrets) -> None: bucket_object.delete() -@pytest.mark.group("AWS") -@pytest.mark.abort_on_fail -async def test_backup_aws(ops_test: OpsTest, cloud_configs: tuple[dict, dict], charm) -> None: - """Build and deploy two units of PostgreSQL in AWS, test backup and restore actions.""" - config = cloud_configs[0][AWS] - credentials = cloud_configs[1][AWS] - - await backup_operations( - ops_test, - S3_INTEGRATOR_APP_NAME, - tls_certificates_app_name, - tls_config, - tls_channel, - credentials, - AWS, - config, - charm, - ) - database_app_name = f"{DATABASE_APP_NAME}-aws" - - # Remove the relation to the TLS certificates operator. - await ops_test.model.applications[database_app_name].remove_relation( - f"{database_app_name}:certificates", f"{tls_certificates_app_name}:certificates" - ) - - new_unit_name = f"{database_app_name}/2" - - # Scale up to be able to test primary and leader being different. - async with ops_test.fast_forward(): - await scale_application(ops_test, database_app_name, 2) - - # Ensure replication is working correctly. - address = get_unit_address(ops_test, new_unit_name) - password = await get_password(ops_test, new_unit_name) - patroni_password = await get_password(ops_test, new_unit_name, "patroni") - with db_connect(host=address, password=password) as connection, connection.cursor() as cursor: - cursor.execute( - "SELECT EXISTS (SELECT FROM information_schema.tables" - " WHERE table_schema = 'public' AND table_name = 'backup_table_1');" - ) - assert cursor.fetchone()[0], ( - f"replication isn't working correctly: table 'backup_table_1' doesn't exist in {new_unit_name}" - ) - cursor.execute( - "SELECT EXISTS (SELECT FROM information_schema.tables" - " WHERE table_schema = 'public' AND table_name = 'backup_table_2');" - ) - assert not cursor.fetchone()[0], ( - f"replication isn't working correctly: table 'backup_table_2' exists in {new_unit_name}" - ) - connection.close() - - old_primary = await get_primary(ops_test, new_unit_name) - switchover(ops_test, old_primary, patroni_password, new_unit_name) - - # Get the new primary unit. - primary = await get_primary(ops_test, new_unit_name) - # Check that the primary changed. - for attempt in Retrying( - stop=stop_after_attempt(3), wait=wait_exponential(multiplier=1, min=2, max=30) - ): - with attempt: - assert primary == new_unit_name - - # Ensure stanza is working correctly. - logger.info("listing the available backups") - action = await ops_test.model.units.get(new_unit_name).run_action("list-backups") - await action.wait() - backups = action.results.get("backups") - assert backups, "backups not outputted" - - await ops_test.model.wait_for_idle(status="active", timeout=1000) - - # Remove the database app. - await ops_test.model.remove_application(database_app_name, block_until_done=True) - - # Remove the TLS operator. - await ops_test.model.remove_application(tls_certificates_app_name, block_until_done=True) - - -@pytest.mark.group("GCP") @pytest.mark.abort_on_fail async def test_backup_gcp(ops_test: OpsTest, cloud_configs: tuple[dict, dict], charm) -> None: """Build and deploy two units of PostgreSQL in GCP, test backup and restore actions.""" @@ -197,8 +114,7 @@ async def test_backup_gcp(ops_test: OpsTest, cloud_configs: tuple[dict, dict], c await ops_test.model.remove_application(tls_certificates_app_name, block_until_done=True) -@pytest.mark.group("GCP") -async def test_restore_on_new_cluster(ops_test: OpsTest, github_secrets, charm) -> None: +async def test_restore_on_new_cluster(ops_test: OpsTest, charm) -> None: """Test that is possible to restore a backup to another PostgreSQL cluster.""" previous_database_app_name = f"{DATABASE_APP_NAME}-gcp" database_app_name = f"new-{DATABASE_APP_NAME}" @@ -295,7 +211,6 @@ async def test_restore_on_new_cluster(ops_test: OpsTest, github_secrets, charm) connection.close() -@pytest.mark.group("GCP") async def test_invalid_config_and_recovery_after_fixing_it( ops_test: OpsTest, cloud_configs: tuple[dict, dict] ) -> None: diff --git a/tests/integration/test_backups_pitr.py b/tests/integration/test_backups_pitr_aws.py similarity index 94% rename from tests/integration/test_backups_pitr.py rename to tests/integration/test_backups_pitr_aws.py index 9a76ad3a5a..70da90c104 100644 --- a/tests/integration/test_backups_pitr.py +++ b/tests/integration/test_backups_pitr_aws.py @@ -2,6 +2,7 @@ # Copyright 2024 Canonical Ltd. # See LICENSE file for licensing details. import logging +import os import uuid import boto3 @@ -39,7 +40,7 @@ @pytest.fixture(scope="module") -async def cloud_configs(github_secrets) -> None: +async def cloud_configs() -> None: # Define some configurations and credentials. configs = { AWS: { @@ -57,12 +58,12 @@ async def cloud_configs(github_secrets) -> None: } credentials = { AWS: { - "access-key": github_secrets["AWS_ACCESS_KEY"], - "secret-key": github_secrets["AWS_SECRET_KEY"], + "access-key": os.environ["AWS_ACCESS_KEY"], + "secret-key": os.environ["AWS_SECRET_KEY"], }, GCP: { - "access-key": github_secrets["GCP_ACCESS_KEY"], - "secret-key": github_secrets["GCP_SECRET_KEY"], + "access-key": os.environ["GCP_ACCESS_KEY"], + "secret-key": os.environ["GCP_SECRET_KEY"], }, } yield configs, credentials @@ -374,7 +375,6 @@ async def pitr_backup_operations( await ops_test.model.remove_application(tls_certificates_app_name, block_until_done=True) -@pytest.mark.group("AWS") @pytest.mark.abort_on_fail async def test_pitr_backup_aws(ops_test: OpsTest, cloud_configs: tuple[dict, dict], charm) -> None: """Build, deploy two units of PostgreSQL and do backup in AWS. Then, write new data into DB, switch WAL file and test point-in-time-recovery restore action.""" @@ -394,26 +394,6 @@ async def test_pitr_backup_aws(ops_test: OpsTest, cloud_configs: tuple[dict, dic ) -@pytest.mark.group("GCP") -@pytest.mark.abort_on_fail -async def test_pitr_backup_gcp(ops_test: OpsTest, cloud_configs: tuple[dict, dict], charm) -> None: - """Build, deploy two units of PostgreSQL and do backup in GCP. Then, write new data into DB, switch WAL file and test point-in-time-recovery restore action.""" - config = cloud_configs[0][GCP] - credentials = cloud_configs[1][GCP] - - await pitr_backup_operations( - ops_test, - S3_INTEGRATOR_APP_NAME, - TLS_CERTIFICATES_APP_NAME, - TLS_CONFIG, - TLS_CHANNEL, - credentials, - GCP, - config, - charm, - ) - - def _create_table(host: str, password: str): with db_connect(host=host, password=password) as connection: connection.autocommit = True diff --git a/tests/integration/test_backups_pitr_gcp.py b/tests/integration/test_backups_pitr_gcp.py new file mode 100644 index 0000000000..e85ac25610 --- /dev/null +++ b/tests/integration/test_backups_pitr_gcp.py @@ -0,0 +1,440 @@ +#!/usr/bin/env python3 +# Copyright 2024 Canonical Ltd. +# See LICENSE file for licensing details. +import logging +import os +import uuid + +import boto3 +import pytest as pytest +from pytest_operator.plugin import OpsTest +from tenacity import Retrying, stop_after_attempt, wait_exponential + +from . import architecture +from .helpers import ( + CHARM_BASE, + DATABASE_APP_NAME, + construct_endpoint, + db_connect, + get_password, + get_primary, + get_unit_address, +) +from .juju_ import juju_major_version + +CANNOT_RESTORE_PITR = "cannot restore PITR, juju debug-log for details" +S3_INTEGRATOR_APP_NAME = "s3-integrator" +if juju_major_version < 3: + TLS_CERTIFICATES_APP_NAME = "tls-certificates-operator" + TLS_CHANNEL = "legacy/edge" if architecture.architecture == "arm64" else "legacy/stable" + TLS_CONFIG = {"generate-self-signed-certificates": "true", "ca-common-name": "Test CA"} +else: + TLS_CERTIFICATES_APP_NAME = "self-signed-certificates" + TLS_CHANNEL = "latest/edge" if architecture.architecture == "arm64" else "latest/stable" + TLS_CONFIG = {"ca-common-name": "Test CA"} + +logger = logging.getLogger(__name__) + +AWS = "AWS" +GCP = "GCP" + + +@pytest.fixture(scope="module") +async def cloud_configs() -> None: + # Define some configurations and credentials. + configs = { + AWS: { + "endpoint": "https://s3.amazonaws.com", + "bucket": "data-charms-testing", + "path": f"/postgresql-vm/{uuid.uuid1()}", + "region": "us-east-1", + }, + GCP: { + "endpoint": "https://storage.googleapis.com", + "bucket": "data-charms-testing", + "path": f"/postgresql-vm/{uuid.uuid1()}", + "region": "", + }, + } + credentials = { + AWS: { + "access-key": os.environ["AWS_ACCESS_KEY"], + "secret-key": os.environ["AWS_SECRET_KEY"], + }, + GCP: { + "access-key": os.environ["GCP_ACCESS_KEY"], + "secret-key": os.environ["GCP_SECRET_KEY"], + }, + } + yield configs, credentials + # Delete the previously created objects. + logger.info("deleting the previously created backups") + for cloud, config in configs.items(): + session = boto3.session.Session( + aws_access_key_id=credentials[cloud]["access-key"], + aws_secret_access_key=credentials[cloud]["secret-key"], + region_name=config["region"], + ) + s3 = session.resource( + "s3", endpoint_url=construct_endpoint(config["endpoint"], config["region"]) + ) + bucket = s3.Bucket(config["bucket"]) + # GCS doesn't support batch delete operation, so delete the objects one by one. + for bucket_object in bucket.objects.filter(Prefix=config["path"].lstrip("/")): + bucket_object.delete() + + +async def pitr_backup_operations( + ops_test: OpsTest, + s3_integrator_app_name: str, + tls_certificates_app_name: str, + tls_config, + tls_channel, + credentials, + cloud, + config, + charm, +) -> None: + """Basic set of operations for PITR backup and timelines management testing. + + Below is presented algorithm in the next format: "(timeline): action_1 -> action_2". + 1: table -> backup_b1 -> test_data_td1 -> timestamp_ts1 -> test_data_td2 -> restore_ts1 => 2 + 2: check_td1 -> check_not_td2 -> test_data_td3 -> restore_b1_latest => 3 + 3: check_td1 -> check_td2 -> check_not_td3 -> test_data_td4 -> restore_t2_latest => 4 + 4: check_td1 -> check_not_td2 -> check_td3 -> check_not_td4 + """ + # Set-up environment + database_app_name = f"{DATABASE_APP_NAME}-{cloud.lower()}" + + logger.info("deploying the next charms: s3-integrator, self-signed-certificates, postgresql") + await ops_test.model.deploy(s3_integrator_app_name) + await ops_test.model.deploy(tls_certificates_app_name, config=tls_config, channel=tls_channel) + await ops_test.model.deploy( + charm, + application_name=database_app_name, + num_units=2, + base=CHARM_BASE, + config={"profile": "testing"}, + ) + + logger.info( + "integrating self-signed-certificates with postgresql and waiting them to stabilize" + ) + await ops_test.model.relate(database_app_name, tls_certificates_app_name) + async with ops_test.fast_forward(fast_interval="60s"): + await ops_test.model.wait_for_idle( + apps=[database_app_name, tls_certificates_app_name], status="active", timeout=1000 + ) + + logger.info(f"configuring s3-integrator for {cloud}") + await ops_test.model.applications[s3_integrator_app_name].set_config(config) + action = await ops_test.model.units.get(f"{s3_integrator_app_name}/0").run_action( + "sync-s3-credentials", + **credentials, + ) + await action.wait() + + logger.info("integrating s3-integrator with postgresql and waiting model to stabilize") + await ops_test.model.relate(database_app_name, s3_integrator_app_name) + async with ops_test.fast_forward(fast_interval="60s"): + await ops_test.model.wait_for_idle(status="active", timeout=1000) + + primary = await get_primary(ops_test, f"{database_app_name}/0") + for unit in ops_test.model.applications[database_app_name].units: + if unit.name != primary: + replica = unit.name + break + password = await get_password(ops_test, primary) + address = get_unit_address(ops_test, primary) + + logger.info("1: creating table") + _create_table(address, password) + + logger.info("1: creating backup b1") + action = await ops_test.model.units.get(replica).run_action("create-backup") + await action.wait() + backup_status = action.results.get("backup-status") + assert backup_status, "backup hasn't succeeded" + await ops_test.model.wait_for_idle(status="active", timeout=1000) + backup_b1 = await _get_most_recent_backup(ops_test, ops_test.model.units.get(replica)) + + logger.info("1: creating test data td1") + _insert_test_data("test_data_td1", address, password) + + logger.info("1: get timestamp ts1") + with db_connect(host=address, password=password) as connection, connection.cursor() as cursor: + cursor.execute("SELECT current_timestamp;") + timestamp_ts1 = str(cursor.fetchone()[0]) + connection.close() + # Wrong timestamp pointing to one year ahead + unreachable_timestamp_ts1 = timestamp_ts1.replace( + timestamp_ts1[:4], str(int(timestamp_ts1[:4]) + 1), 1 + ) + + logger.info("1: creating test data td2") + _insert_test_data("test_data_td2", address, password) + + logger.info("1: switching wal") + _switch_wal(address, password) + + logger.info("1: scaling down to do restore") + async with ops_test.fast_forward(): + await ops_test.model.destroy_unit(replica) + await ops_test.model.wait_for_idle(status="active", timeout=1000) + for unit in ops_test.model.applications[database_app_name].units: + remaining_unit = unit + break + + logger.info("1: restoring the backup b1 with bad restore-to-time parameter") + action = await remaining_unit.run_action( + "restore", **{"backup-id": backup_b1, "restore-to-time": "bad data"} + ) + await action.wait() + assert action.status == "failed", ( + "1: restore must fail with bad restore-to-time parameter, but that action succeeded" + ) + + logger.info("1: restoring the backup b1 with unreachable restore-to-time parameter") + action = await remaining_unit.run_action( + "restore", **{"backup-id": backup_b1, "restore-to-time": unreachable_timestamp_ts1} + ) + await action.wait() + logger.info("1: waiting for the database charm to become blocked after restore") + async with ops_test.fast_forward(): + await ops_test.model.block_until( + lambda: remaining_unit.workload_status_message == CANNOT_RESTORE_PITR, + timeout=1000, + ) + logger.info( + "1: database charm become in blocked state after restore, as supposed to be with unreachable PITR parameter" + ) + + for attempt in Retrying( + stop=stop_after_attempt(10), wait=wait_exponential(multiplier=1, min=2, max=30) + ): + with attempt: + logger.info("1: restoring to the timestamp ts1") + action = await remaining_unit.run_action( + "restore", **{"restore-to-time": timestamp_ts1} + ) + await action.wait() + restore_status = action.results.get("restore-status") + assert restore_status, "1: restore to the timestamp ts1 hasn't succeeded" + await ops_test.model.wait_for_idle(status="active", timeout=1000, idle_period=30) + + logger.info("2: successful restore") + primary = await get_primary(ops_test, remaining_unit.name) + address = get_unit_address(ops_test, primary) + timeline_t2 = await _get_most_recent_backup(ops_test, remaining_unit) + assert backup_b1 != timeline_t2, "2: timeline 2 do not exist in list-backups action or bad" + + logger.info("2: checking test data td1") + assert _check_test_data("test_data_td1", address, password), "2: test data td1 should exist" + + logger.info("2: checking not test data td2") + assert not _check_test_data("test_data_td2", address, password), ( + "2: test data td2 shouldn't exist" + ) + + logger.info("2: creating test data td3") + _insert_test_data("test_data_td3", address, password) + + logger.info("2: get timestamp ts2") + with db_connect(host=address, password=password) as connection, connection.cursor() as cursor: + cursor.execute("SELECT current_timestamp;") + timestamp_ts2 = str(cursor.fetchone()[0]) + connection.close() + + logger.info("2: creating test data td4") + _insert_test_data("test_data_td4", address, password) + + logger.info("2: switching wal") + _switch_wal(address, password) + + for attempt in Retrying( + stop=stop_after_attempt(10), wait=wait_exponential(multiplier=1, min=2, max=30) + ): + with attempt: + logger.info("2: restoring the backup b1 to the latest") + action = await remaining_unit.run_action( + "restore", **{"backup-id": backup_b1, "restore-to-time": "latest"} + ) + await action.wait() + restore_status = action.results.get("restore-status") + assert restore_status, "2: restore the backup b1 to the latest hasn't succeeded" + await ops_test.model.wait_for_idle(status="active", timeout=1000, idle_period=30) + + logger.info("3: successful restore") + primary = await get_primary(ops_test, remaining_unit.name) + address = get_unit_address(ops_test, primary) + timeline_t3 = await _get_most_recent_backup(ops_test, remaining_unit) + assert backup_b1 != timeline_t3 and timeline_t2 != timeline_t3, ( + "3: timeline 3 do not exist in list-backups action or bad" + ) + + logger.info("3: checking test data td1") + assert _check_test_data("test_data_td1", address, password), "3: test data td1 should exist" + + logger.info("3: checking test data td2") + assert _check_test_data("test_data_td2", address, password), "3: test data td2 should exist" + + logger.info("3: checking not test data td3") + assert not _check_test_data("test_data_td3", address, password), ( + "3: test data td3 shouldn't exist" + ) + + logger.info("3: checking not test data td4") + assert not _check_test_data("test_data_td4", address, password), ( + "3: test data td4 shouldn't exist" + ) + + logger.info("3: switching wal") + _switch_wal(address, password) + + for attempt in Retrying( + stop=stop_after_attempt(10), wait=wait_exponential(multiplier=1, min=2, max=30) + ): + with attempt: + logger.info("3: restoring the timeline 2 to the latest") + action = await remaining_unit.run_action( + "restore", **{"backup-id": timeline_t2, "restore-to-time": "latest"} + ) + await action.wait() + restore_status = action.results.get("restore-status") + assert restore_status, "3: restore the timeline 2 to the latest hasn't succeeded" + await ops_test.model.wait_for_idle(status="active", timeout=1000, idle_period=30) + + logger.info("4: successful restore") + primary = await get_primary(ops_test, remaining_unit.name) + address = get_unit_address(ops_test, primary) + timeline_t4 = await _get_most_recent_backup(ops_test, remaining_unit) + assert ( + backup_b1 != timeline_t4 and timeline_t2 != timeline_t4 and timeline_t3 != timeline_t4 + ), "4: timeline 4 do not exist in list-backups action or bad" + + logger.info("4: checking test data td1") + assert _check_test_data("test_data_td1", address, password), "4: test data td1 should exist" + + logger.info("4: checking not test data td2") + assert not _check_test_data("test_data_td2", address, password), ( + "4: test data td2 shouldn't exist" + ) + + logger.info("4: checking test data td3") + assert _check_test_data("test_data_td3", address, password), "4: test data td3 should exist" + + logger.info("4: checking test data td4") + assert _check_test_data("test_data_td4", address, password), "4: test data td4 should exist" + + logger.info("4: switching wal") + _switch_wal(address, password) + + for attempt in Retrying( + stop=stop_after_attempt(10), wait=wait_exponential(multiplier=1, min=2, max=30) + ): + with attempt: + logger.info("4: restoring to the timestamp ts2") + action = await remaining_unit.run_action( + "restore", **{"restore-to-time": timestamp_ts2} + ) + await action.wait() + restore_status = action.results.get("restore-status") + assert restore_status, "4: restore to the timestamp ts2 hasn't succeeded" + await ops_test.model.wait_for_idle(status="active", timeout=1000, idle_period=30) + + logger.info("5: successful restore") + primary = await get_primary(ops_test, remaining_unit.name) + address = get_unit_address(ops_test, primary) + timeline_t5 = await _get_most_recent_backup(ops_test, remaining_unit) + assert ( + backup_b1 != timeline_t5 + and timeline_t2 != timeline_t5 + and timeline_t3 != timeline_t5 + and timeline_t4 != timeline_t5 + ), "5: timeline 5 do not exist in list-backups action or bad" + + logger.info("5: checking test data td1") + assert _check_test_data("test_data_td1", address, password), "5: test data td1 should exist" + + logger.info("5: checking not test data td2") + assert not _check_test_data("test_data_td2", address, password), ( + "5: test data td2 shouldn't exist" + ) + + logger.info("5: checking test data td3") + assert _check_test_data("test_data_td3", address, password), "5: test data td3 should exist" + + logger.info("5: checking not test data td4") + assert not _check_test_data("test_data_td4", address, password), ( + "5: test data td4 shouldn't exist" + ) + + # Remove the database app. + await ops_test.model.remove_application(database_app_name, block_until_done=True) + # Remove the TLS operator. + await ops_test.model.remove_application(tls_certificates_app_name, block_until_done=True) + + +@pytest.mark.abort_on_fail +async def test_pitr_backup_gcp(ops_test: OpsTest, cloud_configs: tuple[dict, dict], charm) -> None: + """Build, deploy two units of PostgreSQL and do backup in GCP. Then, write new data into DB, switch WAL file and test point-in-time-recovery restore action.""" + config = cloud_configs[0][GCP] + credentials = cloud_configs[1][GCP] + + await pitr_backup_operations( + ops_test, + S3_INTEGRATOR_APP_NAME, + TLS_CERTIFICATES_APP_NAME, + TLS_CONFIG, + TLS_CHANNEL, + credentials, + GCP, + config, + charm, + ) + + +def _create_table(host: str, password: str): + with db_connect(host=host, password=password) as connection: + connection.autocommit = True + connection.cursor().execute("CREATE TABLE IF NOT EXISTS backup_table (test_column TEXT);") + connection.close() + + +def _insert_test_data(td: str, host: str, password: str): + with db_connect(host=host, password=password) as connection: + connection.autocommit = True + connection.cursor().execute( + "INSERT INTO backup_table (test_column) VALUES (%s);", + (td,), + ) + connection.close() + + +def _check_test_data(td: str, host: str, password: str) -> bool: + with db_connect(host=host, password=password) as connection, connection.cursor() as cursor: + cursor.execute( + "SELECT EXISTS (SELECT 1 FROM backup_table WHERE test_column = %s);", + (td,), + ) + res = cursor.fetchone()[0] + connection.close() + return res + + +def _switch_wal(host: str, password: str): + with db_connect(host=host, password=password) as connection: + connection.autocommit = True + connection.cursor().execute("SELECT pg_switch_wal();") + connection.close() + + +async def _get_most_recent_backup(ops_test: OpsTest, unit: any) -> str: + logger.info("listing the available backups") + action = await unit.run_action("list-backups") + await action.wait() + backups = action.results.get("backups") + assert backups, "backups not outputted" + await ops_test.model.wait_for_idle(status="active", timeout=1000) + most_recent_backup = backups.split("\n")[-1] + return most_recent_backup.split()[0] diff --git a/tests/integration/test_charm.py b/tests/integration/test_charm.py index 53ac76e4b2..743bbdb242 100644 --- a/tests/integration/test_charm.py +++ b/tests/integration/test_charm.py @@ -37,7 +37,6 @@ UNIT_IDS = [0, 1, 2] -@pytest.mark.group(1) @pytest.mark.abort_on_fail @pytest.mark.skip_if_deployed async def test_deploy(ops_test: OpsTest, charm: str): @@ -61,7 +60,6 @@ async def test_deploy(ops_test: OpsTest, charm: str): assert ops_test.model.applications[DATABASE_APP_NAME].units[0].workload_status == "active" -@pytest.mark.group(1) @pytest.mark.abort_on_fail @pytest.mark.parametrize("unit_id", UNIT_IDS) async def test_database_is_up(ops_test: OpsTest, unit_id: int): @@ -72,7 +70,6 @@ async def test_database_is_up(ops_test: OpsTest, unit_id: int): assert result.status_code == 200 -@pytest.mark.group(1) @pytest.mark.parametrize("unit_id", UNIT_IDS) async def test_exporter_is_up(ops_test: OpsTest, unit_id: int): # Query Patroni REST API and check the status that indicates @@ -85,7 +82,6 @@ async def test_exporter_is_up(ops_test: OpsTest, unit_id: int): ) -@pytest.mark.group(1) @pytest.mark.parametrize("unit_id", UNIT_IDS) async def test_settings_are_correct(ops_test: OpsTest, unit_id: int): # Connect to the PostgreSQL instance. @@ -171,7 +167,6 @@ async def test_settings_are_correct(ops_test: OpsTest, unit_id: int): assert unit.data["port-ranges"][0]["protocol"] == "tcp" -@pytest.mark.group(1) async def test_postgresql_locales(ops_test: OpsTest) -> None: raw_locales = await run_command_on_unit( ops_test, @@ -188,7 +183,6 @@ async def test_postgresql_locales(ops_test: OpsTest) -> None: assert locales == SNAP_LOCALES -@pytest.mark.group(1) async def test_postgresql_parameters_change(ops_test: OpsTest) -> None: """Test that's possible to change PostgreSQL parameters.""" await ops_test.model.applications[DATABASE_APP_NAME].set_config({ @@ -236,7 +230,6 @@ async def test_postgresql_parameters_change(ops_test: OpsTest) -> None: connection.close() -@pytest.mark.group(1) async def test_scale_down_and_up(ops_test: OpsTest): """Test data is replicated to new units after a scale up.""" # Ensure the initial number of units in the application. @@ -324,7 +317,6 @@ async def test_scale_down_and_up(ops_test: OpsTest): await scale_application(ops_test, DATABASE_APP_NAME, initial_scale) -@pytest.mark.group(1) async def test_switchover_sync_standby(ops_test: OpsTest): original_roles = await get_cluster_roles( ops_test, ops_test.model.applications[DATABASE_APP_NAME].units[0].name @@ -342,7 +334,6 @@ async def test_switchover_sync_standby(ops_test: OpsTest): assert new_roles["primaries"][0] == original_roles["sync_standbys"][0] -@pytest.mark.group(1) async def test_persist_data_through_primary_deletion(ops_test: OpsTest): """Test data persists through a primary deletion.""" # Set a composite application name in order to test in more than one series at the same time. diff --git a/tests/integration/test_config.py b/tests/integration/test_config.py index 622264c6c4..304c8e9efc 100644 --- a/tests/integration/test_config.py +++ b/tests/integration/test_config.py @@ -15,13 +15,11 @@ logger = logging.getLogger(__name__) -@pytest.mark.group(1) @pytest.mark.abort_on_fail -async def test_config_parameters(ops_test: OpsTest) -> None: +async def test_config_parameters(ops_test: OpsTest, charm) -> None: """Build and deploy one unit of PostgreSQL and then test config with wrong parameters.""" # Build and deploy the PostgreSQL charm. async with ops_test.fast_forward(): - charm = await ops_test.build_charm(".") await ops_test.model.deploy( charm, num_units=1, diff --git a/tests/integration/test_db.py b/tests/integration/test_db.py index 88f87c1536..5d6195a8e2 100644 --- a/tests/integration/test_db.py +++ b/tests/integration/test_db.py @@ -41,7 +41,6 @@ ) -@pytest.mark.group(1) @pytest.mark.abort_on_fail async def test_mailman3_core_db(ops_test: OpsTest, charm: str) -> None: """Deploy Mailman3 Core to test the 'db' relation.""" @@ -109,7 +108,6 @@ async def test_mailman3_core_db(ops_test: OpsTest, charm: str) -> None: assert domain_name not in [domain.mail_host for domain in client.domains] -@pytest.mark.group(1) @pytest.mark.abort_on_fail async def test_relation_data_is_updated_correctly_when_scaling(ops_test: OpsTest): """Test that relation data, like connection data, is updated correctly when scaling.""" @@ -192,7 +190,6 @@ async def test_relation_data_is_updated_correctly_when_scaling(ops_test: OpsTest psycopg2.connect(primary_connection_string) -@pytest.mark.group(1) async def test_roles_blocking(ops_test: OpsTest, charm: str) -> None: await ops_test.model.deploy( APPLICATION_NAME, @@ -250,7 +247,6 @@ async def test_roles_blocking(ops_test: OpsTest, charm: str) -> None: ) -@pytest.mark.group(1) async def test_extensions_blocking(ops_test: OpsTest, charm: str) -> None: await asyncio.gather( ops_test.model.applications[APPLICATION_NAME].set_config({"legacy_roles": "False"}), @@ -292,8 +288,7 @@ async def test_extensions_blocking(ops_test: OpsTest, charm: str) -> None: @markers.juju2 -@pytest.mark.group(1) -@pytest.mark.unstable +@pytest.mark.skip(reason="Unstable") @markers.amd64_only # canonical-livepatch-server charm (in bundle) not available for arm64 async def test_canonical_livepatch_onprem_bundle_db(ops_test: OpsTest) -> None: # Deploy and test the Livepatch onprem bundle (using this PostgreSQL charm diff --git a/tests/integration/test_db_admin.py b/tests/integration/test_db_admin.py index b95d38d70d..763b7a93cb 100644 --- a/tests/integration/test_db_admin.py +++ b/tests/integration/test_db_admin.py @@ -37,7 +37,6 @@ RELATION_NAME = "db-admin" -@pytest.mark.group(1) async def test_landscape_scalable_bundle_db(ops_test: OpsTest, charm: str) -> None: """Deploy Landscape Scalable Bundle to test the 'db-admin' relation.""" await ops_test.model.deploy( diff --git a/tests/integration/test_password_rotation.py b/tests/integration/test_password_rotation.py index 0cf2f6c26c..563626b229 100644 --- a/tests/integration/test_password_rotation.py +++ b/tests/integration/test_password_rotation.py @@ -27,12 +27,10 @@ APP_NAME = METADATA["name"] -@pytest.mark.group(1) @pytest.mark.abort_on_fail @pytest.mark.skip_if_deployed -async def test_deploy_active(ops_test: OpsTest): +async def test_deploy_active(ops_test: OpsTest, charm): """Build the charm and deploy it.""" - charm = await ops_test.build_charm(".") async with ops_test.fast_forward(): await ops_test.model.deploy( charm, @@ -44,7 +42,6 @@ async def test_deploy_active(ops_test: OpsTest): await ops_test.model.wait_for_idle(apps=[APP_NAME], status="active", timeout=1500) -@pytest.mark.group(1) async def test_password_rotation(ops_test: OpsTest): """Test password rotation action.""" # Get the initial passwords set for the system users. @@ -120,7 +117,6 @@ async def test_password_rotation(ops_test: OpsTest): assert check_patroni(ops_test, unit.name, restart_time) -@pytest.mark.group(1) @markers.juju_secrets async def test_password_from_secret_same_as_cli(ops_test: OpsTest): """Checking if password is same as returned by CLI. @@ -147,7 +143,6 @@ async def test_password_from_secret_same_as_cli(ops_test: OpsTest): assert data[secret_id]["content"]["Data"]["replication-password"] == password -@pytest.mark.group(1) async def test_empty_password(ops_test: OpsTest) -> None: """Test that the password can't be set to an empty string.""" leader_unit = await get_leader_unit(ops_test, APP_NAME) @@ -160,7 +155,6 @@ async def test_empty_password(ops_test: OpsTest) -> None: assert password == "None" -@pytest.mark.group(1) async def test_db_connection_with_empty_password(ops_test: OpsTest): """Test that user can't connect with empty password.""" primary = await get_primary(ops_test, f"{APP_NAME}/0") @@ -169,7 +163,6 @@ async def test_db_connection_with_empty_password(ops_test: OpsTest): connection.close() -@pytest.mark.group(1) async def test_no_password_change_on_invalid_password(ops_test: OpsTest) -> None: """Test that in general, there is no change when password validation fails.""" leader_unit = await get_leader_unit(ops_test, APP_NAME) @@ -182,7 +175,6 @@ async def test_no_password_change_on_invalid_password(ops_test: OpsTest) -> None assert password1 == password2 -@pytest.mark.group(1) async def test_no_password_exposed_on_logs(ops_test: OpsTest) -> None: """Test that passwords don't get exposed on postgresql logs.""" for unit in ops_test.model.applications[APP_NAME].units: diff --git a/tests/integration/test_plugins.py b/tests/integration/test_plugins.py index 7b3d5d3ce1..a24c963000 100644 --- a/tests/integration/test_plugins.py +++ b/tests/integration/test_plugins.py @@ -88,13 +88,11 @@ TIMESCALEDB_EXTENSION_STATEMENT = "CREATE TABLE test_timescaledb (time TIMESTAMPTZ NOT NULL); SELECT create_hypertable('test_timescaledb', 'time');" -@pytest.mark.group(1) @pytest.mark.abort_on_fail -async def test_plugins(ops_test: OpsTest) -> None: +async def test_plugins(ops_test: OpsTest, charm) -> None: """Build and deploy one unit of PostgreSQL and then test the available plugins.""" # Build and deploy the PostgreSQL charm. async with ops_test.fast_forward(): - charm = await ops_test.build_charm(".") await ops_test.model.deploy( charm, num_units=2, @@ -210,7 +208,6 @@ def enable_disable_config(enabled: False): connection.close() -@pytest.mark.group(1) async def test_plugin_objects(ops_test: OpsTest) -> None: """Checks if charm gets blocked when trying to disable a plugin in use.""" primary = await get_primary(ops_test, f"{DATABASE_APP_NAME}/0") diff --git a/tests/integration/test_subordinates.py b/tests/integration/test_subordinates.py index be9be926cc..c03288ae36 100644 --- a/tests/integration/test_subordinates.py +++ b/tests/integration/test_subordinates.py @@ -3,6 +3,7 @@ # See LICENSE file for licensing details. import logging +import os from asyncio import gather import pytest @@ -20,9 +21,8 @@ logger = logging.getLogger(__name__) -@pytest.mark.group(1) @pytest.mark.abort_on_fail -async def test_deploy(ops_test: OpsTest, charm: str, github_secrets): +async def test_deploy(ops_test: OpsTest, charm: str): await gather( ops_test.model.deploy( charm, @@ -32,7 +32,7 @@ async def test_deploy(ops_test: OpsTest, charm: str, github_secrets): ), ops_test.model.deploy( UBUNTU_PRO_APP_NAME, - config={"token": github_secrets["UBUNTU_PRO_TOKEN"]}, + config={"token": os.environ["UBUNTU_PRO_TOKEN"]}, channel="latest/edge", num_units=0, base=CHARM_BASE, @@ -40,8 +40,8 @@ async def test_deploy(ops_test: OpsTest, charm: str, github_secrets): ops_test.model.deploy( LS_CLIENT, config={ - "account-name": github_secrets["LANDSCAPE_ACCOUNT_NAME"], - "registration-key": github_secrets["LANDSCAPE_REGISTRATION_KEY"], + "account-name": os.environ["LANDSCAPE_ACCOUNT_NAME"], + "registration-key": os.environ["LANDSCAPE_REGISTRATION_KEY"], "ppa": "ppa:landscape/self-hosted-beta", }, channel="latest/edge", @@ -60,8 +60,7 @@ async def test_deploy(ops_test: OpsTest, charm: str, github_secrets): ) -@pytest.mark.group(1) -async def test_scale_up(ops_test: OpsTest, github_secrets): +async def test_scale_up(ops_test: OpsTest): await scale_application(ops_test, DATABASE_APP_NAME, 4) await ops_test.model.wait_for_idle( @@ -69,8 +68,7 @@ async def test_scale_up(ops_test: OpsTest, github_secrets): ) -@pytest.mark.group(1) -async def test_scale_down(ops_test: OpsTest, github_secrets): +async def test_scale_down(ops_test: OpsTest): await scale_application(ops_test, DATABASE_APP_NAME, 3) await ops_test.model.wait_for_idle( diff --git a/tests/integration/test_tls.py b/tests/integration/test_tls.py index 7408a8352f..31053c4677 100644 --- a/tests/integration/test_tls.py +++ b/tests/integration/test_tls.py @@ -41,12 +41,10 @@ tls_config = {"ca-common-name": "Test CA"} -@pytest.mark.group(1) @pytest.mark.abort_on_fail @pytest.mark.skip_if_deployed -async def test_deploy_active(ops_test: OpsTest): +async def test_deploy_active(ops_test: OpsTest, charm): """Build the charm and deploy it.""" - charm = await ops_test.build_charm(".") async with ops_test.fast_forward(): await ops_test.model.deploy( charm, @@ -59,7 +57,6 @@ async def test_deploy_active(ops_test: OpsTest): # bundles don't wait between deploying charms. -@pytest.mark.group(1) @pytest.mark.abort_on_fail async def test_tls_enabled(ops_test: OpsTest) -> None: """Test that TLS is enabled when relating to the TLS Certificates Operator.""" diff --git a/tests/spread/test_async_replication.py/task.yaml b/tests/spread/test_async_replication.py/task.yaml new file mode 100644 index 0000000000..4fbf3b6b36 --- /dev/null +++ b/tests/spread/test_async_replication.py/task.yaml @@ -0,0 +1,9 @@ +summary: test_async_replication.py +environment: + TEST_MODULE: ha_tests/test_async_replication.py +execute: | + tox run -e integration -- "tests/integration/$TEST_MODULE" --model testing --alluredir="$SPREAD_TASK/allure-results" +artifacts: + - allure-results +variants: + - -juju29 diff --git a/tests/spread/test_audit.py/task.yaml b/tests/spread/test_audit.py/task.yaml new file mode 100644 index 0000000000..9cbc84e43d --- /dev/null +++ b/tests/spread/test_audit.py/task.yaml @@ -0,0 +1,7 @@ +summary: test_audit.py +environment: + TEST_MODULE: test_audit.py +execute: | + tox run -e integration -- "tests/integration/$TEST_MODULE" --model testing --alluredir="$SPREAD_TASK/allure-results" +artifacts: + - allure-results diff --git a/tests/spread/test_backups_aws.py/task.yaml b/tests/spread/test_backups_aws.py/task.yaml new file mode 100644 index 0000000000..c7eb541232 --- /dev/null +++ b/tests/spread/test_backups_aws.py/task.yaml @@ -0,0 +1,9 @@ +summary: test_backups_aws.py +environment: + TEST_MODULE: test_backups_aws.py +execute: | + tox run -e integration -- "tests/integration/$TEST_MODULE" --model testing --alluredir="$SPREAD_TASK/allure-results" +artifacts: + - allure-results +backends: + - -lxd-vm # Requires CI secrets diff --git a/tests/spread/test_backups_ceph.py/task.yaml b/tests/spread/test_backups_ceph.py/task.yaml new file mode 100644 index 0000000000..8f6c8a387d --- /dev/null +++ b/tests/spread/test_backups_ceph.py/task.yaml @@ -0,0 +1,9 @@ +summary: test_backups_ceph.py +environment: + TEST_MODULE: test_backups_ceph.py +execute: | + tox run -e integration -- "tests/integration/$TEST_MODULE" --model testing --alluredir="$SPREAD_TASK/allure-results" +artifacts: + - allure-results +systems: + - -ubuntu-24.04-arm diff --git a/tests/spread/test_backups_gcp.py/task.yaml b/tests/spread/test_backups_gcp.py/task.yaml new file mode 100644 index 0000000000..c0dc3ac976 --- /dev/null +++ b/tests/spread/test_backups_gcp.py/task.yaml @@ -0,0 +1,9 @@ +summary: test_backups_gcp.py +environment: + TEST_MODULE: test_backups_gcp.py +execute: | + tox run -e integration -- "tests/integration/$TEST_MODULE" --model testing --alluredir="$SPREAD_TASK/allure-results" +artifacts: + - allure-results +backends: + - -lxd-vm # Requires CI secrets diff --git a/tests/spread/test_backups_pitr_aws.py/task.yaml b/tests/spread/test_backups_pitr_aws.py/task.yaml new file mode 100644 index 0000000000..4ac59fbf85 --- /dev/null +++ b/tests/spread/test_backups_pitr_aws.py/task.yaml @@ -0,0 +1,9 @@ +summary: test_backups_pitr_aws.py +environment: + TEST_MODULE: test_backups_pitr_aws.py +execute: | + tox run -e integration -- "tests/integration/$TEST_MODULE" --model testing --alluredir="$SPREAD_TASK/allure-results" +artifacts: + - allure-results +backends: + - -lxd-vm # Requires CI secrets diff --git a/tests/spread/test_backups_pitr_gcp.py/task.yaml b/tests/spread/test_backups_pitr_gcp.py/task.yaml new file mode 100644 index 0000000000..a6b31a59a6 --- /dev/null +++ b/tests/spread/test_backups_pitr_gcp.py/task.yaml @@ -0,0 +1,9 @@ +summary: test_backups_pitr_gcp.py +environment: + TEST_MODULE: test_backups_pitr_gcp.py +execute: | + tox run -e integration -- "tests/integration/$TEST_MODULE" --model testing --alluredir="$SPREAD_TASK/allure-results" +artifacts: + - allure-results +backends: + - -lxd-vm # Requires CI secrets diff --git a/tests/spread/test_charm.py/task.yaml b/tests/spread/test_charm.py/task.yaml new file mode 100644 index 0000000000..96450bdc32 --- /dev/null +++ b/tests/spread/test_charm.py/task.yaml @@ -0,0 +1,7 @@ +summary: test_charm.py +environment: + TEST_MODULE: test_charm.py +execute: | + tox run -e integration -- "tests/integration/$TEST_MODULE" --model testing --alluredir="$SPREAD_TASK/allure-results" +artifacts: + - allure-results diff --git a/tests/spread/test_config.py/task.yaml b/tests/spread/test_config.py/task.yaml new file mode 100644 index 0000000000..f330f89b38 --- /dev/null +++ b/tests/spread/test_config.py/task.yaml @@ -0,0 +1,7 @@ +summary: test_config.py +environment: + TEST_MODULE: test_config.py +execute: | + tox run -e integration -- "tests/integration/$TEST_MODULE" --model testing --alluredir="$SPREAD_TASK/allure-results" +artifacts: + - allure-results diff --git a/tests/spread/test_db.py/task.yaml b/tests/spread/test_db.py/task.yaml new file mode 100644 index 0000000000..a560e14b8a --- /dev/null +++ b/tests/spread/test_db.py/task.yaml @@ -0,0 +1,7 @@ +summary: test_db.py +environment: + TEST_MODULE: test_db.py +execute: | + tox run -e integration -- "tests/integration/$TEST_MODULE" --model testing --alluredir="$SPREAD_TASK/allure-results" +artifacts: + - allure-results diff --git a/tests/spread/test_db_admin.py/task.yaml b/tests/spread/test_db_admin.py/task.yaml new file mode 100644 index 0000000000..b5f127b98c --- /dev/null +++ b/tests/spread/test_db_admin.py/task.yaml @@ -0,0 +1,7 @@ +summary: test_db_admin.py +environment: + TEST_MODULE: test_db_admin.py +execute: | + tox run -e integration -- "tests/integration/$TEST_MODULE" --model testing --alluredir="$SPREAD_TASK/allure-results" +artifacts: + - allure-results diff --git a/tests/spread/test_new_relations_1.py/task.yaml b/tests/spread/test_new_relations_1.py/task.yaml new file mode 100644 index 0000000000..0c64fe771f --- /dev/null +++ b/tests/spread/test_new_relations_1.py/task.yaml @@ -0,0 +1,7 @@ +summary: test_new_relations_1.py +environment: + TEST_MODULE: new_relations/test_new_relations_1.py +execute: | + tox run -e integration -- "tests/integration/$TEST_MODULE" --model testing --alluredir="$SPREAD_TASK/allure-results" +artifacts: + - allure-results diff --git a/tests/spread/test_new_relations_2.py/task.yaml b/tests/spread/test_new_relations_2.py/task.yaml new file mode 100644 index 0000000000..0b7af326a4 --- /dev/null +++ b/tests/spread/test_new_relations_2.py/task.yaml @@ -0,0 +1,9 @@ +summary: test_new_relations_2.py +environment: + TEST_MODULE: new_relations/test_new_relations_2.py +execute: | + tox run -e integration -- "tests/integration/$TEST_MODULE" --model testing --alluredir="$SPREAD_TASK/allure-results" +artifacts: + - allure-results +systems: + - -ubuntu-24.04-arm diff --git a/tests/spread/test_password_rotation.py/task.yaml b/tests/spread/test_password_rotation.py/task.yaml new file mode 100644 index 0000000000..439559b4e6 --- /dev/null +++ b/tests/spread/test_password_rotation.py/task.yaml @@ -0,0 +1,7 @@ +summary: test_password_rotation.py +environment: + TEST_MODULE: test_password_rotation.py +execute: | + tox run -e integration -- "tests/integration/$TEST_MODULE" --model testing --alluredir="$SPREAD_TASK/allure-results" +artifacts: + - allure-results diff --git a/tests/spread/test_plugins.py/task.yaml b/tests/spread/test_plugins.py/task.yaml new file mode 100644 index 0000000000..e9dce8e28f --- /dev/null +++ b/tests/spread/test_plugins.py/task.yaml @@ -0,0 +1,7 @@ +summary: test_plugins.py +environment: + TEST_MODULE: test_plugins.py +execute: | + tox run -e integration -- "tests/integration/$TEST_MODULE" --model testing --alluredir="$SPREAD_TASK/allure-results" +artifacts: + - allure-results diff --git a/tests/spread/test_relations.py/task.yaml b/tests/spread/test_relations.py/task.yaml new file mode 100644 index 0000000000..a1c60423eb --- /dev/null +++ b/tests/spread/test_relations.py/task.yaml @@ -0,0 +1,7 @@ +summary: test_relations.py +environment: + TEST_MODULE: relations/test_relations.py +execute: | + tox run -e integration -- "tests/integration/$TEST_MODULE" --model testing --alluredir="$SPREAD_TASK/allure-results" +artifacts: + - allure-results diff --git a/tests/spread/test_relations_coherence.py/task.yaml b/tests/spread/test_relations_coherence.py/task.yaml new file mode 100644 index 0000000000..bff0e492b3 --- /dev/null +++ b/tests/spread/test_relations_coherence.py/task.yaml @@ -0,0 +1,7 @@ +summary: test_relations_coherence.py +environment: + TEST_MODULE: new_relations/test_relations_coherence.py +execute: | + tox run -e integration -- "tests/integration/$TEST_MODULE" --model testing --alluredir="$SPREAD_TASK/allure-results" +artifacts: + - allure-results diff --git a/tests/spread/test_replication.py/task.yaml b/tests/spread/test_replication.py/task.yaml new file mode 100644 index 0000000000..237cc3981b --- /dev/null +++ b/tests/spread/test_replication.py/task.yaml @@ -0,0 +1,7 @@ +summary: test_replication.py +environment: + TEST_MODULE: ha_tests/test_replication.py +execute: | + tox run -e integration -- "tests/integration/$TEST_MODULE" --model testing --alluredir="$SPREAD_TASK/allure-results" +artifacts: + - allure-results diff --git a/tests/spread/test_restore_cluster.py/task.yaml b/tests/spread/test_restore_cluster.py/task.yaml new file mode 100644 index 0000000000..bce2ec14d4 --- /dev/null +++ b/tests/spread/test_restore_cluster.py/task.yaml @@ -0,0 +1,7 @@ +summary: test_restore_cluster.py +environment: + TEST_MODULE: ha_tests/test_restore_cluster.py +execute: | + tox run -e integration -- "tests/integration/$TEST_MODULE" --model testing --alluredir="$SPREAD_TASK/allure-results" +artifacts: + - allure-results diff --git a/tests/spread/test_scaling.py/task.yaml b/tests/spread/test_scaling.py/task.yaml new file mode 100644 index 0000000000..32358243db --- /dev/null +++ b/tests/spread/test_scaling.py/task.yaml @@ -0,0 +1,9 @@ +summary: test_scaling.py +environment: + TEST_MODULE: ha_tests/test_scaling.py +execute: | + tox run -e integration -- "tests/integration/$TEST_MODULE" --model testing --alluredir="$SPREAD_TASK/allure-results" +artifacts: + - allure-results +variants: + - -juju29 diff --git a/tests/spread/test_scaling_three_units.py/task.yaml b/tests/spread/test_scaling_three_units.py/task.yaml new file mode 100644 index 0000000000..ae8dcc1006 --- /dev/null +++ b/tests/spread/test_scaling_three_units.py/task.yaml @@ -0,0 +1,9 @@ +summary: test_scaling_three_units.py +environment: + TEST_MODULE: ha_tests/test_scaling_three_units.py +execute: | + tox run -e integration -- "tests/integration/$TEST_MODULE" --model testing --alluredir="$SPREAD_TASK/allure-results" +artifacts: + - allure-results +variants: + - -juju29 diff --git a/tests/spread/test_self_healing.py/task.yaml b/tests/spread/test_self_healing.py/task.yaml new file mode 100644 index 0000000000..d8fca3acea --- /dev/null +++ b/tests/spread/test_self_healing.py/task.yaml @@ -0,0 +1,7 @@ +summary: test_self_healing.py +environment: + TEST_MODULE: ha_tests/test_self_healing.py +execute: | + tox run -e integration -- "tests/integration/$TEST_MODULE" --model testing --alluredir="$SPREAD_TASK/allure-results" +artifacts: + - allure-results diff --git a/tests/spread/test_smoke.py/task.yaml b/tests/spread/test_smoke.py/task.yaml new file mode 100644 index 0000000000..d2fe9793d1 --- /dev/null +++ b/tests/spread/test_smoke.py/task.yaml @@ -0,0 +1,7 @@ +summary: test_smoke.py +environment: + TEST_MODULE: ha_tests/test_smoke.py +execute: | + tox run -e integration -- "tests/integration/$TEST_MODULE" --model testing --alluredir="$SPREAD_TASK/allure-results" +artifacts: + - allure-results diff --git a/tests/spread/test_subordinates.py/task.yaml b/tests/spread/test_subordinates.py/task.yaml new file mode 100644 index 0000000000..a7477d7bab --- /dev/null +++ b/tests/spread/test_subordinates.py/task.yaml @@ -0,0 +1,9 @@ +summary: test_subordinates.py +environment: + TEST_MODULE: test_subordinates.py +execute: | + tox run -e integration -- "tests/integration/$TEST_MODULE" --model testing --alluredir="$SPREAD_TASK/allure-results" +artifacts: + - allure-results +backends: + - -lxd-vm # Requires CI secrets diff --git a/tests/spread/test_tls.py/task.yaml b/tests/spread/test_tls.py/task.yaml new file mode 100644 index 0000000000..a605744913 --- /dev/null +++ b/tests/spread/test_tls.py/task.yaml @@ -0,0 +1,7 @@ +summary: test_tls.py +environment: + TEST_MODULE: test_tls.py +execute: | + tox run -e integration -- "tests/integration/$TEST_MODULE" --model testing --alluredir="$SPREAD_TASK/allure-results" +artifacts: + - allure-results diff --git a/tests/spread/test_upgrade.py/task.yaml b/tests/spread/test_upgrade.py/task.yaml new file mode 100644 index 0000000000..b3be366921 --- /dev/null +++ b/tests/spread/test_upgrade.py/task.yaml @@ -0,0 +1,7 @@ +summary: test_upgrade.py +environment: + TEST_MODULE: ha_tests/test_upgrade.py +execute: | + tox run -e integration -- "tests/integration/$TEST_MODULE" --model testing --alluredir="$SPREAD_TASK/allure-results" +artifacts: + - allure-results diff --git a/tests/spread/test_upgrade_from_stable.py/task.yaml b/tests/spread/test_upgrade_from_stable.py/task.yaml new file mode 100644 index 0000000000..047617ab39 --- /dev/null +++ b/tests/spread/test_upgrade_from_stable.py/task.yaml @@ -0,0 +1,7 @@ +summary: test_upgrade_from_stable.py +environment: + TEST_MODULE: ha_tests/test_upgrade_from_stable.py +execute: | + tox run -e integration -- "tests/integration/$TEST_MODULE" --model testing --alluredir="$SPREAD_TASK/allure-results" +artifacts: + - allure-results diff --git a/tox.ini b/tox.ini index 508d1a645f..0f7b4d4bd4 100644 --- a/tox.ini +++ b/tox.ini @@ -57,8 +57,13 @@ commands = description = Run integration tests pass_env = CI - GITHUB_OUTPUT - SECRETS_FROM_GITHUB + AWS_ACCESS_KEY + AWS_SECRET_KEY + GCP_ACCESS_KEY + GCP_SECRET_KEY + UBUNTU_PRO_TOKEN + LANDSCAPE_ACCOUNT_NAME + LANDSCAPE_REGISTRATION_KEY commands_pre = poetry install --only integration --no-root commands = From 407e26493c7788df66c10192064f78d9b56d5a28 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Wed, 12 Feb 2025 16:56:27 -0300 Subject: [PATCH 38/74] Update charmcraft.yaml build tools (#760) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- charmcraft.yaml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/charmcraft.yaml b/charmcraft.yaml index 87a3f72d53..1de48d3e3e 100644 --- a/charmcraft.yaml +++ b/charmcraft.yaml @@ -24,10 +24,10 @@ parts: # Use environment variable instead of `--break-system-packages` to avoid failing on older # versions of pip that do not recognize `--break-system-packages` # `--user` needed (in addition to `--break-system-packages`) for Ubuntu >=24.04 - PIP_BREAK_SYSTEM_PACKAGES=true python3 -m pip install --user --upgrade pip==25.0 # renovate: charmcraft-pip-latest + PIP_BREAK_SYSTEM_PACKAGES=true python3 -m pip install --user --upgrade pip==25.0.1 # renovate: charmcraft-pip-latest # Use uv to install poetry so that a newer version of Python can be installed if needed by poetry - curl --proto '=https' --tlsv1.2 -LsSf https://github.com/astral-sh/uv/releases/download/0.5.29/uv-installer.sh | sh # renovate: charmcraft-uv-latest + curl --proto '=https' --tlsv1.2 -LsSf https://github.com/astral-sh/uv/releases/download/0.5.30/uv-installer.sh | sh # renovate: charmcraft-uv-latest # poetry 2.0.0 requires Python >=3.9 if ! "$HOME/.local/bin/uv" python find '>=3.9' then From 7e79b642bfc19f3574269c81566a5f7d94153fe3 Mon Sep 17 00:00:00 2001 From: Dragomir Penev <6687393+dragomirp@users.noreply.github.com> Date: Thu, 13 Feb 2025 12:08:29 +0200 Subject: [PATCH 39/74] [DPE-6020] Better promote-to-primary unit scope error handling (#759) * Bump libs * Flip default scope * Better action failure * Wrong attr * Revert scope * Bump libs * Handle async replica switchover * Unit tests * Bump cosl --- .../data_platform_libs/v0/data_interfaces.py | 5 +- .../data_platform_libs/v0/data_models.py | 6 +- lib/charms/grafana_agent/v0/cos_agent.py | 163 ++++-- lib/charms/operator_libs_linux/v2/snap.py | 545 +++++++++++------- lib/charms/postgresql_k8s/v0/postgresql.py | 6 +- .../tempo_coordinator_k8s/v0/charm_tracing.py | 15 +- poetry.lock | 14 +- pyproject.toml | 2 +- src/charm.py | 5 +- src/cluster.py | 11 + .../ha_tests/test_async_replication.py | 4 +- tests/unit/test_charm.py | 18 +- tests/unit/test_cluster.py | 24 +- 13 files changed, 530 insertions(+), 288 deletions(-) diff --git a/lib/charms/data_platform_libs/v0/data_interfaces.py b/lib/charms/data_platform_libs/v0/data_interfaces.py index 3bc2dd8503..9717119030 100644 --- a/lib/charms/data_platform_libs/v0/data_interfaces.py +++ b/lib/charms/data_platform_libs/v0/data_interfaces.py @@ -331,7 +331,7 @@ def _on_topic_requested(self, event: TopicRequestedEvent): # Increment this PATCH version before using `charmcraft publish-lib` or reset # to 0 if you are raising the major API version -LIBPATCH = 40 +LIBPATCH = 41 PYDEPS = ["ops>=2.0.0"] @@ -609,7 +609,7 @@ def get_group(self, group: str) -> Optional[SecretGroup]: class CachedSecret: """Locally cache a secret. - The data structure is precisely re-using/simulating as in the actual Secret Storage + The data structure is precisely reusing/simulating as in the actual Secret Storage """ KNOWN_MODEL_ERRORS = [MODEL_ERRORS["no_label_and_uri"], MODEL_ERRORS["owner_no_refresh"]] @@ -2363,7 +2363,6 @@ def _update_relation_data(self, relation: Relation, data: Dict[str, str]) -> Non def _delete_relation_data(self, relation: Relation, fields: List[str]) -> None: """Delete data available (directily or indirectly -- i.e. secrets) from the relation for owner/this_app.""" if self.secret_fields and self.deleted_label: - _, normal_fields = self._process_secret_fields( relation, self.secret_fields, diff --git a/lib/charms/data_platform_libs/v0/data_models.py b/lib/charms/data_platform_libs/v0/data_models.py index a1dbb8299a..087f6f3c58 100644 --- a/lib/charms/data_platform_libs/v0/data_models.py +++ b/lib/charms/data_platform_libs/v0/data_models.py @@ -168,7 +168,7 @@ class MergedDataBag(ProviderDataBag, RequirerDataBag): # Increment this PATCH version before using `charmcraft publish-lib` or reset # to 0 if you are raising the major API version -LIBPATCH = 4 +LIBPATCH = 5 PYDEPS = ["ops>=2.0.0", "pydantic>=1.10,<2"] @@ -209,7 +209,7 @@ def validate_params(cls: Type[T]): """ def decorator( - f: Callable[[CharmBase, ActionEvent, Union[T, ValidationError]], G] + f: Callable[[CharmBase, ActionEvent, Union[T, ValidationError]], G], ) -> Callable[[CharmBase, ActionEvent], G]: @wraps(f) def event_wrapper(self: CharmBase, event: ActionEvent): @@ -287,7 +287,7 @@ def decorator( Optional[Union[UnitModel, ValidationError]], ], G, - ] + ], ) -> Callable[[CharmBase, RelationEvent], G]: @wraps(f) def event_wrapper(self: CharmBase, event: RelationEvent): diff --git a/lib/charms/grafana_agent/v0/cos_agent.py b/lib/charms/grafana_agent/v0/cos_agent.py index 1ea79a625b..f1344c06ac 100644 --- a/lib/charms/grafana_agent/v0/cos_agent.py +++ b/lib/charms/grafana_agent/v0/cos_agent.py @@ -8,6 +8,8 @@ - `COSAgentProvider`: Use in machine charms that need to have a workload's metrics or logs scraped, or forward rule files or dashboards to Prometheus, Loki or Grafana through the Grafana Agent machine charm. + NOTE: Be sure to add `limit: 1` in your charm for the cos-agent relation. That is the only + way we currently have to prevent two different grafana agent apps deployed on the same VM. - `COSAgentConsumer`: Used in the Grafana Agent machine charm to manage the requirer side of the `cos_agent` interface. @@ -232,8 +234,8 @@ def __init__(self, *args): ) import pydantic -from cosl import GrafanaDashboard, JujuTopology -from cosl.rules import AlertRules +from cosl import DashboardPath40UID, JujuTopology, LZMABase64 +from cosl.rules import AlertRules, generic_alert_groups from ops.charm import RelationChangedEvent from ops.framework import EventBase, EventSource, Object, ObjectEvents from ops.model import ModelError, Relation @@ -252,9 +254,9 @@ class _MetricsEndpointDict(TypedDict): LIBID = "dc15fa84cef84ce58155fb84f6c6213a" LIBAPI = 0 -LIBPATCH = 12 +LIBPATCH = 19 -PYDEPS = ["cosl", "pydantic"] +PYDEPS = ["cosl >= 0.0.50", "pydantic"] DEFAULT_RELATION_NAME = "cos-agent" DEFAULT_PEER_RELATION_NAME = "peers" @@ -266,7 +268,6 @@ class _MetricsEndpointDict(TypedDict): logger = logging.getLogger(__name__) SnapEndpoint = namedtuple("SnapEndpoint", "owner, name") - # Note: MutableMapping is imported from the typing module and not collections.abc # because subscripting collections.abc.MutableMapping was added in python 3.9, but # most of our charms are based on 20.04, which has python 3.8. @@ -316,7 +317,11 @@ class NotReadyError(TracingError): """Raised by the provider wrapper if a requirer hasn't published the required data (yet).""" -class ProtocolNotRequestedError(TracingError): +class ProtocolNotFoundError(TracingError): + """Raised if the user doesn't receive an endpoint for a protocol it requested.""" + + +class ProtocolNotRequestedError(ProtocolNotFoundError): """Raised if the user attempts to obtain an endpoint for a protocol it did not request.""" @@ -475,7 +480,7 @@ class CosAgentProviderUnitData(DatabagModel): # this needs to make its way to the gagent leader metrics_alert_rules: dict log_alert_rules: dict - dashboards: List[GrafanaDashboard] + dashboards: List[str] # subordinate is no longer used but we should keep it until we bump the library to ensure # we don't break compatibility. subordinate: Optional[bool] = None @@ -508,7 +513,7 @@ class CosAgentPeersUnitData(DatabagModel): # of the outgoing o11y relations. metrics_alert_rules: Optional[dict] log_alert_rules: Optional[dict] - dashboards: Optional[List[GrafanaDashboard]] + dashboards: Optional[List[str]] # when this whole datastructure is dumped into a databag, it will be nested under this key. # while not strictly necessary (we could have it 'flattened out' into the databag), @@ -578,7 +583,7 @@ class Receiver(pydantic.BaseModel): """Specification of an active receiver.""" protocol: ProtocolType = pydantic.Field(..., description="Receiver protocol name and type.") - url: str = pydantic.Field( + url: Optional[str] = pydantic.Field( ..., description="""URL at which the receiver is reachable. If there's an ingress, it would be the external URL. Otherwise, it would be the service's fqdn or internal IP. @@ -726,6 +731,10 @@ def _metrics_alert_rules(self) -> Dict: query_type="promql", topology=JujuTopology.from_charm(self._charm) ) alert_rules.add_path(self._metrics_rules, recursive=self._recursive) + alert_rules.add( + generic_alert_groups.application_rules, + group_name_prefix=JujuTopology.from_charm(self._charm).identifier, + ) return alert_rules.as_dict() @property @@ -736,12 +745,20 @@ def _log_alert_rules(self) -> Dict: return alert_rules.as_dict() @property - def _dashboards(self) -> List[GrafanaDashboard]: - dashboards: List[GrafanaDashboard] = [] + def _dashboards(self) -> List[str]: + dashboards: List[str] = [] for d in self._dashboard_dirs: for path in Path(d).glob("*"): - dashboard = GrafanaDashboard._serialize(path.read_bytes()) - dashboards.append(dashboard) + with open(path, "rt") as fp: + dashboard = json.load(fp) + rel_path = str( + path.relative_to(self._charm.charm_dir) if path.is_absolute() else path + ) + # COSAgentProvider is somewhat analogous to GrafanaDashboardProvider. We need to overwrite the uid here + # because there is currently no other way to communicate the dashboard path separately. + # https://github.com/canonical/grafana-k8s-operator/pull/363 + dashboard["uid"] = DashboardPath40UID.generate(self._charm.meta.name, rel_path) + dashboards.append(LZMABase64.compress(json.dumps(dashboard))) return dashboards @property @@ -767,7 +784,7 @@ def is_ready(self, relation: Optional[Relation] = None): """Is this endpoint ready?""" relation = relation or self._relation if not relation: - logger.debug(f"no relation on {self._relation_name !r}: tracing not ready") + logger.debug(f"no relation on {self._relation_name!r}: tracing not ready") return False if relation.data is None: logger.error(f"relation data is None for {relation}") @@ -801,29 +818,48 @@ def get_all_endpoints( def _get_tracing_endpoint( self, relation: Optional[Relation], protocol: ReceiverProtocol - ) -> Optional[str]: + ) -> str: + """Return a tracing endpoint URL if it is available or raise a ProtocolNotFoundError.""" unit_data = self.get_all_endpoints(relation) if not unit_data: - return None + # we didn't find the protocol because the remote end didn't publish any data yet + # it might also mean that grafana-agent doesn't have a relation to the tracing backend + raise ProtocolNotFoundError(protocol) receivers: List[Receiver] = [i for i in unit_data.receivers if i.protocol.name == protocol] if not receivers: - logger.error(f"no receiver found with protocol={protocol!r}") - return None + # we didn't find the protocol because grafana-agent didn't return us the protocol that we requested + # the caller might want to verify that we did indeed request this protocol + raise ProtocolNotFoundError(protocol) if len(receivers) > 1: - logger.error( + logger.warning( f"too many receivers with protocol={protocol!r}; using first one. Found: {receivers}" ) - return None receiver = receivers[0] + if not receiver.url: + # grafana-agent isn't connected to the tracing backend yet + raise ProtocolNotFoundError(protocol) return receiver.url def get_tracing_endpoint( self, protocol: ReceiverProtocol, relation: Optional[Relation] = None - ) -> Optional[str]: - """Receiver endpoint for the given protocol.""" - endpoint = self._get_tracing_endpoint(relation or self._relation, protocol=protocol) - if not endpoint: + ) -> str: + """Receiver endpoint for the given protocol. + + It could happen that this function gets called before the provider publishes the endpoints. + In such a scenario, if a non-leader unit calls this function, a permission denied exception will be raised due to + restricted access. To prevent this, this function needs to be guarded by the `is_ready` check. + + Raises: + ProtocolNotRequestedError: + If the charm unit is the leader unit and attempts to obtain an endpoint for a protocol it did not request. + ProtocolNotFoundError: + If the charm attempts to obtain an endpoint when grafana-agent isn't related to a tracing backend. + """ + try: + return self._get_tracing_endpoint(relation or self._relation, protocol=protocol) + except ProtocolNotFoundError: + # let's see if we didn't find it because we didn't request the endpoint requested_protocols = set() relations = [relation] if relation else self.relations for relation in relations: @@ -838,8 +874,7 @@ def get_tracing_endpoint( if protocol not in requested_protocols: raise ProtocolNotRequestedError(protocol, relation) - return None - return endpoint + raise class COSAgentDataChanged(EventBase): @@ -901,6 +936,8 @@ def __init__( events.relation_joined, self._on_relation_data_changed ) # TODO: do we need this? self.framework.observe(events.relation_changed, self._on_relation_data_changed) + self.framework.observe(events.relation_departed, self._on_relation_departed) + for event in self._refresh_events: self.framework.observe(event, self.trigger_refresh) # pyright: ignore @@ -928,6 +965,26 @@ def _on_peer_relation_changed(self, _): if self._charm.unit.is_leader(): self.on.data_changed.emit() # pyright: ignore + def _on_relation_departed(self, event): + """Remove provider's (principal's) alert rules and dashboards from peer data when the cos-agent relation to the principal is removed.""" + if not self.peer_relation: + event.defer() + return + # empty the departing unit's alert rules and dashboards from peer data + data = CosAgentPeersUnitData( + unit_name=event.unit.name, + relation_id=str(event.relation.id), + relation_name=event.relation.name, + metrics_alert_rules={}, + log_alert_rules={}, + dashboards=[], + ) + self.peer_relation.data[self._charm.unit][ + f"{CosAgentPeersUnitData.KEY}-{event.unit.name}" + ] = data.json() + + self.on.data_changed.emit() # pyright: ignore + def _on_relation_data_changed(self, event: RelationChangedEvent): # Peer data is the only means of communication between subordinate units. if not self.peer_relation: @@ -987,7 +1044,16 @@ def update_tracing_receivers(self): CosAgentRequirerUnitData( receivers=[ Receiver( - url=f"{self._get_tracing_receiver_url(protocol)}", + # if tracing isn't ready, we don't want the wrong receiver URLs present in the databag. + # however, because of the backwards compatibility requirements, we need to still provide + # the protocols list so that the charm with older cos_agent version doesn't error its hooks. + # before this change was added, the charm with old cos_agent version threw exceptions with + # connections to grafana-agent timing out. After the change, the charm will fail validating + # databag contents (as it expects a string in URL) but that won't cause any errors as + # tracing endpoints are the only content in the grafana-agent's side of the databag. + url=f"{self._get_tracing_receiver_url(protocol)}" + if self._charm.tracing.is_ready() # type: ignore + else None, protocol=ProtocolType( name=protocol, type=receiver_protocol_to_transport_protocol[protocol], @@ -1029,8 +1095,7 @@ def _get_requested_protocols(self, relation: Relation): if len(units) > 1: # should never happen raise ValueError( - f"unexpected error: subordinate relation {relation} " - f"should have exactly one unit" + f"unexpected error: subordinate relation {relation} should have exactly one unit" ) unit = next(iter(units), None) @@ -1286,7 +1351,7 @@ def dashboards(self) -> List[Dict[str, str]]: seen_apps.append(app_name) for encoded_dashboard in data.dashboards or (): - content = GrafanaDashboard(encoded_dashboard)._deserialize() + content = json.loads(LZMABase64.decompress(encoded_dashboard)) title = content.get("title", "no_title") @@ -1313,44 +1378,32 @@ def charm_tracing_config( If https endpoint is provided but cert_path is not found on disk: disable charm tracing. If https endpoint is provided and cert_path is None: - ERROR + raise TracingError Else: proceed with charm tracing (with or without tls, as appropriate) Usage: - If you are using charm_tracing >= v1.9: - >>> from lib.charms.tempo_k8s.v1.charm_tracing import trace_charm - >>> from lib.charms.tempo_k8s.v0.cos_agent import charm_tracing_config + >>> from lib.charms.tempo_coordinator_k8s.v0.charm_tracing import trace_charm + >>> from lib.charms.tempo_coordinator_k8s.v0.tracing import charm_tracing_config >>> @trace_charm(tracing_endpoint="my_endpoint", cert_path="cert_path") >>> class MyCharm(...): >>> _cert_path = "/path/to/cert/on/charm/container.crt" >>> def __init__(self, ...): - >>> self.cos_agent = COSAgentProvider(...) + >>> self.tracing = TracingEndpointRequirer(...) >>> self.my_endpoint, self.cert_path = charm_tracing_config( - ... self.cos_agent, self._cert_path) - - If you are using charm_tracing < v1.9: - >>> from lib.charms.tempo_k8s.v1.charm_tracing import trace_charm - >>> from lib.charms.tempo_k8s.v2.tracing import charm_tracing_config - >>> @trace_charm(tracing_endpoint="my_endpoint", cert_path="cert_path") - >>> class MyCharm(...): - >>> _cert_path = "/path/to/cert/on/charm/container.crt" - >>> def __init__(self, ...): - >>> self.cos_agent = COSAgentProvider(...) - >>> self.my_endpoint, self.cert_path = charm_tracing_config( - ... self.cos_agent, self._cert_path) - >>> @property - >>> def my_endpoint(self): - >>> return self._my_endpoint - >>> @property - >>> def cert_path(self): - >>> return self._cert_path - + ... self.tracing, self._cert_path) """ if not endpoint_requirer.is_ready(): return None, None - endpoint = endpoint_requirer.get_tracing_endpoint("otlp_http") + try: + endpoint = endpoint_requirer.get_tracing_endpoint("otlp_http") + except ProtocolNotFoundError: + logger.warn( + "Endpoint for tracing wasn't provided as tracing backend isn't ready yet. If grafana-agent isn't connected to a tracing backend, integrate it. Otherwise this issue should resolve itself in a few events." + ) + return None, None + if not endpoint: return None, None diff --git a/lib/charms/operator_libs_linux/v2/snap.py b/lib/charms/operator_libs_linux/v2/snap.py index d14f864fd9..5cd0ffd4b2 100644 --- a/lib/charms/operator_libs_linux/v2/snap.py +++ b/lib/charms/operator_libs_linux/v2/snap.py @@ -56,6 +56,8 @@ ``` """ +from __future__ import annotations + import http.client import json import logging @@ -65,14 +67,30 @@ import subprocess import sys import time +import typing import urllib.error import urllib.parse import urllib.request -from collections.abc import Mapping from datetime import datetime, timedelta, timezone from enum import Enum from subprocess import CalledProcessError, CompletedProcess -from typing import Any, Dict, Iterable, List, Optional, Union +from typing import ( + Callable, + Iterable, + Literal, + Mapping, + NoReturn, + Sequence, + TypedDict, + TypeVar, +) + +if typing.TYPE_CHECKING: + # avoid typing_extensions import at runtime + from typing_extensions import NotRequired, ParamSpec, Required, TypeAlias, Unpack + + _P = ParamSpec("_P") + _T = TypeVar("_T") logger = logging.getLogger(__name__) @@ -84,15 +102,15 @@ # Increment this PATCH version before using `charmcraft publish-lib` or reset # to 0 if you are raising the major API version -LIBPATCH = 9 +LIBPATCH = 10 # Regex to locate 7-bit C1 ANSI sequences ansi_filter = re.compile(r"\x1B(?:[@-Z\\-_]|\[[0-?]*[ -/]*[@-~])") -def _cache_init(func): - def inner(*args, **kwargs): +def _cache_init(func: Callable[_P, _T]) -> Callable[_P, _T]: + def inner(*args: _P.args, **kwargs: _P.kwargs) -> _T: if _Cache.cache is None: _Cache.cache = SnapCache() return func(*args, **kwargs) @@ -100,8 +118,59 @@ def inner(*args, **kwargs): return inner -# recursive hints seems to error out pytest -JSONType = Union[Dict[str, Any], List[Any], str, int, float] +# this is used for return types, so it (a) uses concrete types and (b) does not contain None +# because setting snap config values to null removes the key so a null value can't be returned +_JSONLeaf: TypeAlias = 'str | int | float | bool' +JSONType: TypeAlias = "dict[str, JSONType] | list[JSONType] | _JSONLeaf" +# we also need a jsonable type for arguments, +# which (a) uses abstract types and (b) may contain None +JSONAble: TypeAlias = "Mapping[str, JSONAble] | Sequence[JSONAble] | _JSONLeaf | None" + + +class _AsyncChangeDict(TypedDict, total=True): + """The subset of the json returned by GET changes that we care about internally.""" + + status: str + data: JSONType + + +class _SnapDict(TypedDict, total=True): + """The subset of the json returned by GET snap/find that we care about internally.""" + + name: str + channel: str + revision: str + confinement: str + apps: NotRequired[list[dict[str, JSONType]] | None] + + +class SnapServiceDict(TypedDict, total=True): + """Dictionary representation returned by SnapService.as_dict.""" + + daemon: str | None + daemon_scope: str | None + enabled: bool + active: bool + activators: list[str] + + +# TypedDicts with hyphenated keys +_SnapServiceKwargsDict = TypedDict("_SnapServiceKwargsDict", {"daemon-scope": str}, total=False) +# the kwargs accepted by SnapService +_SnapServiceAppDict = TypedDict( + # the data we expect a Snap._apps entry to contain for a daemon + "_SnapServiceAppDict", + { + "name": "Required[str]", + "daemon": str, + "daemon_scope": str, + "daemon-scope": str, + "enabled": bool, + "active": bool, + "activators": "list[str]", + }, + total=False, +) class SnapService: @@ -109,20 +178,20 @@ class SnapService: def __init__( self, - daemon: Optional[str] = None, - daemon_scope: Optional[str] = None, + daemon: str | None = None, + daemon_scope: str | None = None, enabled: bool = False, active: bool = False, - activators: List[str] = [], - **kwargs, + activators: list[str] | None = None, + **kwargs: Unpack[_SnapServiceKwargsDict], ): self.daemon = daemon - self.daemon_scope = kwargs.get("daemon-scope", None) or daemon_scope + self.daemon_scope = kwargs.get("daemon-scope") or daemon_scope self.enabled = enabled self.active = active - self.activators = activators + self.activators = activators if activators is not None else [] - def as_dict(self) -> Dict: + def as_dict(self) -> SnapServiceDict: """Return instance representation as dict.""" return { "daemon": self.daemon, @@ -137,57 +206,54 @@ class MetaCache(type): """MetaCache class used for initialising the snap cache.""" @property - def cache(cls) -> "SnapCache": + def cache(cls) -> SnapCache: """Property for returning the snap cache.""" return cls._cache @cache.setter - def cache(cls, cache: "SnapCache") -> None: + def cache(cls, cache: SnapCache) -> None: """Setter for the snap cache.""" cls._cache = cache - def __getitem__(cls, name) -> "Snap": + def __getitem__(cls, name: str) -> Snap: """Snap cache getter.""" return cls._cache[name] -class _Cache(object, metaclass=MetaCache): +class _Cache(metaclass=MetaCache): _cache = None class Error(Exception): """Base class of most errors raised by this library.""" - def __repr__(self): + def __init__(self, message: str = "", *args: object): + super().__init__(message, *args) + self.message = message + + def __repr__(self) -> str: """Represent the Error class.""" - return "<{}.{} {}>".format(type(self).__module__, type(self).__name__, self.args) + return f"<{type(self).__module__}.{type(self).__name__} {self.args}>" @property - def name(self): + def name(self) -> str: """Return a string representation of the model plus class.""" - return "<{}.{}>".format(type(self).__module__, type(self).__name__) - - @property - def message(self): - """Return the message passed as an argument.""" - return self.args[0] + return f"<{type(self).__module__}.{type(self).__name__}>" class SnapAPIError(Error): """Raised when an HTTP API error occurs talking to the Snapd server.""" - def __init__(self, body: Dict, code: int, status: str, message: str): + def __init__(self, body: Mapping[str, JSONAble], code: int, status: str, message: str): super().__init__(message) # Makes str(e) return message self.body = body self.code = code self.status = status self._message = message - def __repr__(self): + def __repr__(self) -> str: """Represent the SnapAPIError class.""" - return "APIError({!r}, {!r}, {!r}, {!r})".format( - self.body, self.code, self.status, self._message - ) + return f"APIError({self.body!r}, {self.code!r}, {self.status!r}, {self._message!r})" class SnapState(Enum): @@ -207,7 +273,7 @@ class SnapNotFoundError(Error): """Raised when a requested snap is not known to the system.""" -class Snap(object): +class Snap: """Represents a snap package and its properties. `Snap` exposes the following properties about a snap: @@ -220,49 +286,47 @@ class Snap(object): def __init__( self, - name, + name: str, state: SnapState, channel: str, revision: str, confinement: str, - apps: Optional[List[Dict[str, str]]] = None, - cohort: Optional[str] = "", + apps: list[dict[str, JSONType]] | None = None, + cohort: str | None = None, ) -> None: self._name = name self._state = state self._channel = channel self._revision = revision self._confinement = confinement - self._cohort = cohort + self._cohort = cohort or "" self._apps = apps or [] self._snap_client = SnapClient() - def __eq__(self, other) -> bool: + def __eq__(self, other: object) -> bool: """Equality for comparison.""" return isinstance(other, self.__class__) and ( self._name, self._revision, ) == (other._name, other._revision) - def __hash__(self): + def __hash__(self) -> int: """Calculate a hash for this snap.""" return hash((self._name, self._revision)) - def __repr__(self): + def __repr__(self) -> str: """Represent the object such that it can be reconstructed.""" - return "<{}.{}: {}>".format(self.__module__, self.__class__.__name__, self.__dict__) + return f"<{self.__module__}.{type(self).__name__}: {self.__dict__}>" - def __str__(self): + def __str__(self) -> str: """Represent the snap object as a string.""" - return "<{}: {}-{}.{} -- {}>".format( - self.__class__.__name__, - self._name, - self._revision, - self._channel, - str(self._state), + return ( + f"<{type(self).__name__}: " + f"{self._name}-{self._revision}.{self._channel}" + f" -- {self._state}>" ) - def _snap(self, command: str, optargs: Optional[Iterable[str]] = None) -> str: + def _snap(self, command: str, optargs: Iterable[str] | None = None) -> str: """Perform a snap operation. Args: @@ -276,19 +340,17 @@ def _snap(self, command: str, optargs: Optional[Iterable[str]] = None) -> str: optargs = optargs or [] args = ["snap", command, self._name, *optargs] try: - return subprocess.check_output(args, universal_newlines=True) + return subprocess.check_output(args, text=True) except CalledProcessError as e: raise SnapError( - "Snap: {!r}; command {!r} failed with output = {!r}".format( - self._name, args, e.output - ) - ) + f"Snap: {self._name!r}; command {args!r} failed with output = {e.output!r}" + ) from e def _snap_daemons( self, - command: List[str], - services: Optional[List[str]] = None, - ) -> CompletedProcess: + command: list[str], + services: list[str] | None = None, + ) -> CompletedProcess[str]: """Perform snap app commands. Args: @@ -300,18 +362,26 @@ def _snap_daemons( """ if services: # an attempt to keep the command constrained to the snap instance's services - services = ["{}.{}".format(self._name, service) for service in services] + services = [f"{self._name}.{service}" for service in services] else: services = [self._name] args = ["snap", *command, *services] try: - return subprocess.run(args, universal_newlines=True, check=True, capture_output=True) + return subprocess.run(args, text=True, check=True, capture_output=True) except CalledProcessError as e: - raise SnapError("Could not {} for snap [{}]: {}".format(args, self._name, e.stderr)) - - def get(self, key: Optional[str], *, typed: bool = False) -> Any: + raise SnapError(f"Could not {args} for snap [{self._name}]: {e.stderr}") from e + + @typing.overload + def get(self, key: None | Literal[""], *, typed: Literal[False] = False) -> NoReturn: ... + @typing.overload + def get(self, key: str, *, typed: Literal[False] = False) -> str: ... + @typing.overload + def get(self, key: None | Literal[""], *, typed: Literal[True]) -> dict[str, JSONType]: ... + @typing.overload + def get(self, key: str, *, typed: Literal[True]) -> JSONType: ... + def get(self, key: str | None, *, typed: bool = False) -> JSONType | str: """Fetch snap configuration values. Args: @@ -323,7 +393,7 @@ def get(self, key: Optional[str], *, typed: bool = False) -> Any: args = ["-d"] if key: args.append(key) - config = json.loads(self._snap("get", args)) + config = json.loads(self._snap("get", args)) # json.loads -> Any if key: return config.get(key) return config @@ -331,9 +401,10 @@ def get(self, key: Optional[str], *, typed: bool = False) -> Any: if not key: raise TypeError("Key must be provided when typed=False") + # return a string return self._snap("get", [key]).strip() - def set(self, config: Dict[str, Any], *, typed: bool = False) -> None: + def set(self, config: dict[str, JSONAble], *, typed: bool = False) -> None: """Set a snap configuration value. Args: @@ -345,7 +416,7 @@ def set(self, config: Dict[str, Any], *, typed: bool = False) -> None: config = {k: str(v) for k, v in config.items()} self._snap_client._put_snap_conf(self._name, config) - def unset(self, key) -> str: + def unset(self, key: str) -> str: """Unset a snap configuration value. Args: @@ -353,7 +424,7 @@ def unset(self, key) -> str: """ return self._snap("unset", [key]) - def start(self, services: Optional[List[str]] = None, enable: Optional[bool] = False) -> None: + def start(self, services: list[str] | None = None, enable: bool = False) -> None: """Start a snap's services. Args: @@ -363,7 +434,7 @@ def start(self, services: Optional[List[str]] = None, enable: Optional[bool] = F args = ["start", "--enable"] if enable else ["start"] self._snap_daemons(args, services) - def stop(self, services: Optional[List[str]] = None, disable: Optional[bool] = False) -> None: + def stop(self, services: list[str] | None = None, disable: bool = False) -> None: """Stop a snap's services. Args: @@ -373,7 +444,7 @@ def stop(self, services: Optional[List[str]] = None, disable: Optional[bool] = F args = ["stop", "--disable"] if disable else ["stop"] self._snap_daemons(args, services) - def logs(self, services: Optional[List[str]] = None, num_lines: Optional[int] = 10) -> str: + def logs(self, services: list[str] | None = None, num_lines: int = 10) -> str: """Fetch a snap services' logs. Args: @@ -381,12 +452,10 @@ def logs(self, services: Optional[List[str]] = None, num_lines: Optional[int] = (otherwise all) num_lines (int): (optional) integer number of log lines to return. Default `10` """ - args = ["logs", "-n={}".format(num_lines)] if num_lines else ["logs"] + args = ["logs", f"-n={num_lines}"] if num_lines else ["logs"] return self._snap_daemons(args, services).stdout - def connect( - self, plug: str, service: Optional[str] = None, slot: Optional[str] = None - ) -> None: + def connect(self, plug: str, service: str | None = None, slot: str | None = None) -> None: """Connect a plug to a slot. Args: @@ -397,20 +466,20 @@ def connect( Raises: SnapError if there is a problem encountered """ - command = ["connect", "{}:{}".format(self._name, plug)] + command = ["connect", f"{self._name}:{plug}"] if service and slot: - command = command + ["{}:{}".format(service, slot)] + command.append(f"{service}:{slot}") elif slot: - command = command + [slot] + command.append(slot) args = ["snap", *command] try: - subprocess.run(args, universal_newlines=True, check=True, capture_output=True) + subprocess.run(args, text=True, check=True, capture_output=True) except CalledProcessError as e: - raise SnapError("Could not {} for snap [{}]: {}".format(args, self._name, e.stderr)) + raise SnapError(f"Could not {args} for snap [{self._name}]: {e.stderr}") from e - def hold(self, duration: Optional[timedelta] = None) -> None: + def hold(self, duration: timedelta | None = None) -> None: """Add a refresh hold to a snap. Args: @@ -426,7 +495,7 @@ def unhold(self) -> None: """Remove the refresh hold of a snap.""" self._snap("refresh", ["--unhold"]) - def alias(self, application: str, alias: Optional[str] = None) -> None: + def alias(self, application: str, alias: str | None = None) -> None: """Create an alias for a given application. Args: @@ -437,17 +506,13 @@ def alias(self, application: str, alias: Optional[str] = None) -> None: alias = application args = ["snap", "alias", f"{self.name}.{application}", alias] try: - subprocess.check_output(args, universal_newlines=True) + subprocess.check_output(args, text=True) except CalledProcessError as e: raise SnapError( - "Snap: {!r}; command {!r} failed with output = {!r}".format( - self._name, args, e.output - ) - ) + f"Snap: {self._name!r}; command {args!r} failed with output = {e.output!r}" + ) from e - def restart( - self, services: Optional[List[str]] = None, reload: Optional[bool] = False - ) -> None: + def restart(self, services: list[str] | None = None, reload: bool = False) -> None: """Restarts a snap's services. Args: @@ -461,9 +526,9 @@ def restart( def _install( self, - channel: Optional[str] = "", - cohort: Optional[str] = "", - revision: Optional[str] = None, + channel: str = "", + cohort: str = "", + revision: str = "", ) -> None: """Add a snap to the system. @@ -474,27 +539,27 @@ def _install( """ cohort = cohort or self._cohort - args = [] + args: list[str] = [] if self.confinement == "classic": args.append("--classic") if self.confinement == "devmode": args.append("--devmode") if channel: - args.append('--channel="{}"'.format(channel)) + args.append(f'--channel="{channel}"') if revision: - args.append('--revision="{}"'.format(revision)) + args.append(f'--revision="{revision}"') if cohort: - args.append('--cohort="{}"'.format(cohort)) + args.append(f'--cohort="{cohort}"') self._snap("install", args) def _refresh( self, - channel: Optional[str] = "", - cohort: Optional[str] = "", - revision: Optional[str] = None, + channel: str = "", + cohort: str = "", + revision: str = "", devmode: bool = False, - leave_cohort: Optional[bool] = False, + leave_cohort: bool = False, ) -> None: """Refresh a snap. @@ -505,12 +570,12 @@ def _refresh( devmode: optionally, specify devmode confinement leave_cohort: leave the current cohort. """ - args = [] + args: list[str] = [] if channel: - args.append('--channel="{}"'.format(channel)) + args.append(f'--channel="{channel}"') if revision: - args.append('--revision="{}"'.format(revision)) + args.append(f'--revision="{revision}"') if devmode: args.append("--devmode") @@ -522,7 +587,7 @@ def _refresh( self._cohort = "" args.append("--leave-cohort") elif cohort: - args.append('--cohort="{}"'.format(cohort)) + args.append(f'--cohort="{cohort}"') self._snap("refresh", args) @@ -538,11 +603,11 @@ def name(self) -> str: def ensure( self, state: SnapState, - classic: Optional[bool] = False, + classic: bool = False, devmode: bool = False, - channel: Optional[str] = "", - cohort: Optional[str] = "", - revision: Optional[str] = None, + channel: str | None = None, + cohort: str | None = None, + revision: str | None = None, ): """Ensure that a snap is in a given state. @@ -560,6 +625,10 @@ def ensure( Raises: SnapError if an error is encountered """ + channel = channel or "" + cohort = cohort or "" + revision = revision or "" + if classic and devmode: raise ValueError("Cannot set both classic and devmode confinement") @@ -605,7 +674,7 @@ def _update_snap_apps(self) -> None: try: self._apps = self._snap_client.get_installed_snap_apps(self._name) except SnapAPIError: - logger.debug("Unable to retrieve snap apps for {}".format(self._name)) + logger.debug("Unable to retrieve snap apps for %s", self._name) self._apps = [] @property @@ -653,18 +722,19 @@ def confinement(self) -> str: return self._confinement @property - def apps(self) -> List: + def apps(self) -> list[dict[str, JSONType]]: """Returns (if any) the installed apps of the snap.""" self._update_snap_apps() return self._apps @property - def services(self) -> Dict: + def services(self) -> dict[str, SnapServiceDict]: """Returns (if any) the installed services of the snap.""" self._update_snap_apps() - services = {} + services: dict[str, SnapServiceDict] = {} for app in self._apps: if "daemon" in app: + app = typing.cast("_SnapServiceAppDict", app) services[app["name"]] = SnapService(**app).as_dict() return services @@ -679,7 +749,7 @@ def held(self) -> bool: class _UnixSocketConnection(http.client.HTTPConnection): """Implementation of HTTPConnection that connects to a named Unix socket.""" - def __init__(self, host, timeout=None, socket_path=None): + def __init__(self, host: str, timeout: float | None = None, socket_path: str | None = None): if timeout is None: super().__init__(host) else: @@ -689,7 +759,8 @@ def __init__(self, host, timeout=None, socket_path=None): def connect(self): """Override connect to use Unix socket (instead of TCP socket).""" if not hasattr(socket, "AF_UNIX"): - raise NotImplementedError("Unix sockets not supported on {}".format(sys.platform)) + raise NotImplementedError(f"Unix sockets not supported on {sys.platform}") + assert self.socket_path is not None # else TypeError on self.socket.connect self.sock = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM) self.sock.connect(self.socket_path) if self.timeout is not None: @@ -703,9 +774,13 @@ def __init__(self, socket_path: str): super().__init__() self.socket_path = socket_path - def http_open(self, req) -> http.client.HTTPResponse: + def http_open(self, req: urllib.request.Request) -> http.client.HTTPResponse: """Override http_open to use a Unix socket connection (instead of TCP).""" - return self.do_open(_UnixSocketConnection, req, socket_path=self.socket_path) + return self.do_open( + typing.cast("urllib.request._HTTPConnectionProtocol", _UnixSocketConnection), + req, + socket_path=self.socket_path, + ) class SnapClient: @@ -719,7 +794,7 @@ class SnapClient: def __init__( self, socket_path: str = "/run/snapd.socket", - opener: Optional[urllib.request.OpenerDirector] = None, + opener: urllib.request.OpenerDirector | None = None, base_url: str = "http://localhost/v2/", timeout: float = 30.0, ): @@ -728,18 +803,21 @@ def __init__( Args: socket_path: a path to the socket on the filesystem. Defaults to /run/snap/snapd.socket opener: specifies an opener for unix socket, if unspecified a default is used - base_url: base url for making requests to the snap client. Defaults to - http://localhost/v2/ + base_url: base URL for making requests to the snap client. Must be an HTTP(S) URL. + Defaults to http://localhost/v2/ timeout: timeout in seconds to use when making requests to the API. Default is 30.0s. """ if opener is None: opener = self._get_default_opener(socket_path) self.opener = opener + # Address ruff's suspicious-url-open-usage (S310) + if not base_url.startswith(("http:", "https:")): + raise ValueError("base_url must start with 'http:' or 'https:'") self.base_url = base_url self.timeout = timeout @classmethod - def _get_default_opener(cls, socket_path): + def _get_default_opener(cls, socket_path: str) -> urllib.request.OpenerDirector: """Build the default opener to use for requests (HTTP over Unix socket).""" opener = urllib.request.OpenerDirector() opener.add_handler(_UnixSocketHandler(socket_path)) @@ -752,9 +830,9 @@ def _request( self, method: str, path: str, - query: Dict = None, - body: Dict = None, - ) -> JSONType: + query: dict[str, str] | None = None, + body: dict[str, JSONAble] | None = None, + ) -> JSONType | None: """Make a JSON request to the Snapd server with the given HTTP method and path. If query dict is provided, it is encoded and appended as a query string @@ -769,12 +847,12 @@ def _request( headers["Content-Type"] = "application/json" response = self._request_raw(method, path, query, headers, data) - response = json.loads(response.read().decode()) + response = json.loads(response.read().decode()) # json.loads -> Any if response["type"] == "async": - return self._wait(response["change"]) + return self._wait(response["change"]) # may be `None` due to `get` return response["result"] - def _wait(self, change_id: str, timeout=300) -> JSONType: + def _wait(self, change_id: str, timeout: float = 300) -> JSONType | None: """Wait for an async change to complete. The poll time is 100 milliseconds, the same as in snap clients. @@ -784,6 +862,7 @@ def _wait(self, change_id: str, timeout=300) -> JSONType: if time.time() > deadline: raise TimeoutError(f"timeout waiting for snap change {change_id}") response = self._request("GET", f"changes/{change_id}") + response = typing.cast("_AsyncChangeDict", response) status = response["status"] if status == "Done": return response.get("data") @@ -801,9 +880,9 @@ def _request_raw( self, method: str, path: str, - query: Dict = None, - headers: Dict = None, - data: bytes = None, + query: dict[str, str] | None = None, + headers: dict[str, str] | None = None, + data: bytes | None = None, ) -> http.client.HTTPResponse: """Make a request to the Snapd server; return the raw HTTPResponse object.""" url = self.base_url + path @@ -812,7 +891,7 @@ def _request_raw( if headers is None: headers = {} - request = urllib.request.Request(url, method=method, data=data, headers=headers) + request = urllib.request.Request(url, method=method, data=data, headers=headers) # noqa: S310 try: response = self.opener.open(request, timeout=self.timeout) @@ -820,35 +899,36 @@ def _request_raw( code = e.code status = e.reason message = "" + body: dict[str, JSONType] try: - body = json.loads(e.read().decode())["result"] - except (IOError, ValueError, KeyError) as e2: + body = json.loads(e.read().decode())["result"] # json.loads -> Any + except (OSError, ValueError, KeyError) as e2: # Will only happen on read error or if Pebble sends invalid JSON. body = {} - message = "{} - {}".format(type(e2).__name__, e2) - raise SnapAPIError(body, code, status, message) + message = f"{type(e2).__name__} - {e2}" + raise SnapAPIError(body, code, status, message) from e except urllib.error.URLError as e: - raise SnapAPIError({}, 500, "Not found", e.reason) + raise SnapAPIError({}, 500, "Not found", str(e.reason)) from e return response - def get_installed_snaps(self) -> Dict: + def get_installed_snaps(self) -> list[dict[str, JSONType]]: """Get information about currently installed snaps.""" - return self._request("GET", "snaps") + return self._request("GET", "snaps") # type: ignore - def get_snap_information(self, name: str) -> Dict: + def get_snap_information(self, name: str) -> dict[str, JSONType]: """Query the snap server for information about single snap.""" - return self._request("GET", "find", {"name": name})[0] + return self._request("GET", "find", {"name": name})[0] # type: ignore - def get_installed_snap_apps(self, name: str) -> List: + def get_installed_snap_apps(self, name: str) -> list[dict[str, JSONType]]: """Query the snap server for apps belonging to a named, currently installed snap.""" - return self._request("GET", "apps", {"names": name, "select": "service"}) + return self._request("GET", "apps", {"names": name, "select": "service"}) # type: ignore - def _put_snap_conf(self, name: str, conf: Dict[str, Any]): + def _put_snap_conf(self, name: str, conf: dict[str, JSONAble]) -> None: """Set the configuration details for an installed snap.""" - return self._request("PUT", f"snaps/{name}/conf", body=conf) + self._request("PUT", f"snaps/{name}/conf", body=conf) -class SnapCache(Mapping): +class SnapCache(Mapping[str, Snap]): """An abstraction to represent installed/available packages. When instantiated, `SnapCache` iterates through the list of installed @@ -861,12 +941,12 @@ def __init__(self): if not self.snapd_installed: raise SnapError("snapd is not installed or not in /usr/bin") from None self._snap_client = SnapClient() - self._snap_map = {} + self._snap_map: dict[str, Snap | None] = {} if self.snapd_installed: self._load_available_snaps() self._load_installed_snaps() - def __contains__(self, key: str) -> bool: + def __contains__(self, key: object) -> bool: """Check if a given snap is in the cache.""" return key in self._snap_map @@ -874,26 +954,26 @@ def __len__(self) -> int: """Report number of items in the snap cache.""" return len(self._snap_map) - def __iter__(self) -> Iterable["Snap"]: + def __iter__(self) -> Iterable[Snap | None]: # pyright: ignore[reportIncompatibleMethodOverride] """Provide iterator for the snap cache.""" return iter(self._snap_map.values()) def __getitem__(self, snap_name: str) -> Snap: """Return either the installed version or latest version for a given snap.""" - snap = self._snap_map.get(snap_name, None) - if snap is None: - # The snapd cache file may not have existed when _snap_map was - # populated. This is normal. - try: - self._snap_map[snap_name] = self._load_info(snap_name) - except SnapAPIError: - raise SnapNotFoundError("Snap '{}' not found!".format(snap_name)) - - return self._snap_map[snap_name] + snap = self._snap_map.get(snap_name) + if snap is not None: + return snap + # The snapd cache file may not have existed when _snap_map was + # populated. This is normal. + try: + snap = self._snap_map[snap_name] = self._load_info(snap_name) + except SnapAPIError as e: + raise SnapNotFoundError(f"Snap '{snap_name}' not found!") from e + return snap @property def snapd_installed(self) -> bool: - """Check whether snapd has been installled on the system.""" + """Check whether snapd has been installed on the system.""" return os.path.isfile("/usr/bin/snap") def _load_available_snaps(self) -> None: @@ -907,7 +987,7 @@ def _load_available_snaps(self) -> None: # currently exist. return - with open("/var/cache/snapd/names", "r") as f: + with open("/var/cache/snapd/names") as f: for line in f: if line.strip(): self._snap_map[line.strip()] = None @@ -917,23 +997,25 @@ def _load_installed_snaps(self) -> None: installed = self._snap_client.get_installed_snaps() for i in installed: + i = typing.cast("_SnapDict", i) snap = Snap( name=i["name"], state=SnapState.Latest, channel=i["channel"], revision=i["revision"], confinement=i["confinement"], - apps=i.get("apps", None), + apps=i.get("apps"), ) self._snap_map[snap.name] = snap - def _load_info(self, name) -> Snap: + def _load_info(self, name: str) -> Snap: """Load info for snaps which are not installed if requested. Args: name: a string representing the name of the snap """ info = self._snap_client.get_snap_information(name) + info = typing.cast("_SnapDict", info) return Snap( name=info["name"], @@ -945,16 +1027,36 @@ def _load_info(self, name) -> Snap: ) +@typing.overload +def add( # return a single Snap if snap name is given as a string + snap_names: str, + state: str | SnapState = SnapState.Latest, + channel: str | None = None, + classic: bool = False, + devmode: bool = False, + cohort: str | None = None, + revision: str | None = None, +) -> Snap: ... +@typing.overload +def add( # may return a single Snap or a list depending if one or more snap names were given + snap_names: list[str], + state: str | SnapState = SnapState.Latest, + channel: str | None = None, + classic: bool = False, + devmode: bool = False, + cohort: str | None = None, + revision: str | None = None, +) -> Snap | list[Snap]: ... @_cache_init def add( - snap_names: Union[str, List[str]], - state: Union[str, SnapState] = SnapState.Latest, - channel: Optional[str] = "", - classic: Optional[bool] = False, + snap_names: str | list[str], + state: str | SnapState = SnapState.Latest, + channel: str | None = None, + classic: bool = False, devmode: bool = False, - cohort: Optional[str] = "", - revision: Optional[str] = None, -) -> Union[Snap, List[Snap]]: + cohort: str | None = None, + revision: str | None = None, +) -> Snap | list[Snap]: """Add a snap to the system. Args: @@ -982,11 +1084,25 @@ def add( if isinstance(state, str): state = SnapState(state) - return _wrap_snap_operations(snap_names, state, channel, classic, devmode, cohort, revision) + return _wrap_snap_operations( + snap_names=snap_names, + state=state, + channel=channel or "", + classic=classic, + devmode=devmode, + cohort=cohort or "", + revision=revision or "", + ) +@typing.overload +def remove(snap_names: str) -> Snap: ... +# return a single Snap if snap name is given as a string +@typing.overload +def remove(snap_names: list[str]) -> Snap | list[Snap]: ... +# may return a single Snap or a list depending if one or more snap names were given @_cache_init -def remove(snap_names: Union[str, List[str]]) -> Union[Snap, List[Snap]]: +def remove(snap_names: str | list[str]) -> Snap | list[Snap]: """Remove specified snap(s) from the system. Args: @@ -1007,16 +1123,36 @@ def remove(snap_names: Union[str, List[str]]) -> Union[Snap, List[Snap]]: ) +@typing.overload +def ensure( # return a single Snap if snap name is given as a string + snap_names: str, + state: str, + channel: str | None = None, + classic: bool = False, + devmode: bool = False, + cohort: str | None = None, + revision: int | None = None, +) -> Snap: ... +@typing.overload +def ensure( # may return a single Snap or a list depending if one or more snap names were given + snap_names: list[str], + state: str, + channel: str | None = None, + classic: bool = False, + devmode: bool = False, + cohort: str | None = None, + revision: int | None = None, +) -> Snap | list[Snap]: ... @_cache_init def ensure( - snap_names: Union[str, List[str]], + snap_names: str | list[str], state: str, - channel: Optional[str] = "", - classic: Optional[bool] = False, + channel: str | None = None, + classic: bool = False, devmode: bool = False, - cohort: Optional[str] = "", - revision: Optional[int] = None, -) -> Union[Snap, List[Snap]]: + cohort: str | None = None, + revision: int | None = None, +) -> Snap | list[Snap]: """Ensure specified snaps are in a given state on the system. Args: @@ -1047,23 +1183,24 @@ def ensure( classic=classic, devmode=devmode, cohort=cohort, - revision=revision, + revision=str(revision) if revision is not None else None, ) else: return remove(snap_names) def _wrap_snap_operations( - snap_names: List[str], + snap_names: list[str], state: SnapState, channel: str, classic: bool, devmode: bool, - cohort: Optional[str] = "", - revision: Optional[str] = None, -) -> Union[Snap, List[Snap]]: + cohort: str = "", + revision: str = "", +) -> Snap | list[Snap]: """Wrap common operations for bare commands.""" - snaps = {"success": [], "failed": []} + snaps: list[Snap] = [] + errors: list[str] = [] op = "remove" if state is SnapState.Absent else "install or refresh" @@ -1081,27 +1218,25 @@ def _wrap_snap_operations( cohort=cohort, revision=revision, ) - snaps["success"].append(snap) - except SnapError as e: - logger.warning("Failed to {} snap {}: {}!".format(op, s, e.message)) - snaps["failed"].append(s) + snaps.append(snap) + except SnapError as e: # noqa: PERF203 + logger.warning("Failed to %s snap %s: %s!", op, s, e.message) + errors.append(s) except SnapNotFoundError: - logger.warning("Snap '{}' not found in cache!".format(s)) - snaps["failed"].append(s) + logger.warning("Snap '%s' not found in cache!", s) + errors.append(s) - if len(snaps["failed"]): - raise SnapError( - "Failed to install or refresh snap(s): {}".format(", ".join(list(snaps["failed"]))) - ) + if errors: + raise SnapError(f"Failed to install or refresh snap(s): {', '.join(errors)}") - return snaps["success"] if len(snaps["success"]) > 1 else snaps["success"][0] + return snaps if len(snaps) > 1 else snaps[0] def install_local( filename: str, - classic: Optional[bool] = False, - devmode: Optional[bool] = False, - dangerous: Optional[bool] = False, + classic: bool = False, + devmode: bool = False, + dangerous: bool = False, ) -> Snap: """Perform a snap operation. @@ -1126,7 +1261,7 @@ def install_local( if dangerous: args.append("--dangerous") try: - result = subprocess.check_output(args, universal_newlines=True).splitlines()[-1] + result = subprocess.check_output(args, text=True).splitlines()[-1] snap_name, _ = result.split(" ", 1) snap_name = ansi_filter.sub("", snap_name) @@ -1136,11 +1271,13 @@ def install_local( return c[snap_name] except SnapAPIError as e: logger.error( - "Could not find snap {} when querying Snapd socket: {}".format(snap_name, e.body) + "Could not find snap %s when querying Snapd socket: %s", + snap_name, + e.body, ) - raise SnapError("Failed to find snap {} in Snap cache".format(snap_name)) + raise SnapError(f"Failed to find snap {snap_name} in Snap cache") from e except CalledProcessError as e: - raise SnapError("Could not install snap {}: {}".format(filename, e.output)) + raise SnapError(f"Could not install snap {filename}: {e.output}") from e def _system_set(config_item: str, value: str) -> None: @@ -1150,14 +1287,14 @@ def _system_set(config_item: str, value: str) -> None: config_item: name of snap system setting. E.g. 'refresh.hold' value: value to assign """ - args = ["snap", "set", "system", "{}={}".format(config_item, value)] + args = ["snap", "set", "system", f"{config_item}={value}"] try: - subprocess.check_call(args, universal_newlines=True) - except CalledProcessError: - raise SnapError("Failed setting system config '{}' to '{}'".format(config_item, value)) + subprocess.check_call(args, text=True) + except CalledProcessError as e: + raise SnapError(f"Failed setting system config '{config_item}' to '{value}'") from e -def hold_refresh(days: int = 90, forever: bool = False) -> bool: +def hold_refresh(days: int = 90, forever: bool = False) -> None: """Set the system-wide snap refresh hold. Args: @@ -1183,7 +1320,7 @@ def hold_refresh(days: int = 90, forever: bool = False) -> bool: # Format for the correct datetime format hold_date = target_date.strftime("%Y-%m-%dT%H:%M:%S%z") # Python dumps the offset in format '+0100', we need '+01:00' - hold_date = "{0}:{1}".format(hold_date[:-2], hold_date[-2:]) + hold_date = f"{hold_date[:-2]}:{hold_date[-2:]}" # Actually set the hold date _system_set("refresh.hold", hold_date) logger.info("Set system-wide snap refresh hold to: %s", hold_date) diff --git a/lib/charms/postgresql_k8s/v0/postgresql.py b/lib/charms/postgresql_k8s/v0/postgresql.py index 986ae71f0d..bdfef9afbb 100644 --- a/lib/charms/postgresql_k8s/v0/postgresql.py +++ b/lib/charms/postgresql_k8s/v0/postgresql.py @@ -21,7 +21,7 @@ import logging from collections import OrderedDict -from typing import List, Optional, Set, Tuple +from typing import Dict, List, Optional, Set, Tuple import psycopg2 from ops.model import Relation @@ -35,7 +35,7 @@ # Increment this PATCH version before using `charmcraft publish-lib` or reset # to 0 if you are raising the major API version -LIBPATCH = 41 +LIBPATCH = 42 INVALID_EXTRA_USER_ROLE_BLOCKING_MESSAGE = "invalid role(s) for extra user roles" @@ -318,7 +318,7 @@ def delete_user(self, user: str) -> None: raise PostgreSQLDeleteUserError() from e def enable_disable_extensions( - self, extensions: dict[str, bool], database: Optional[str] = None + self, extensions: Dict[str, bool], database: Optional[str] = None ) -> None: """Enables or disables a PostgreSQL extension. diff --git a/lib/charms/tempo_coordinator_k8s/v0/charm_tracing.py b/lib/charms/tempo_coordinator_k8s/v0/charm_tracing.py index cf8def11ac..a9b6deeb64 100644 --- a/lib/charms/tempo_coordinator_k8s/v0/charm_tracing.py +++ b/lib/charms/tempo_coordinator_k8s/v0/charm_tracing.py @@ -10,10 +10,10 @@ in real time from the Grafana dashboard the execution flow of your charm. # Quickstart -Fetch the following charm libs (and ensure the minimum version/revision numbers are satisfied): +Fetch the following charm libs: - charmcraft fetch-lib charms.tempo_coordinator_k8s.v0.tracing # >= 1.10 - charmcraft fetch-lib charms.tempo_coordinator_k8s.v0.charm_tracing # >= 2.7 + charmcraft fetch-lib charms.tempo_coordinator_k8s.v0.tracing + charmcraft fetch-lib charms.tempo_coordinator_k8s.v0.charm_tracing Then edit your charm code to include: @@ -168,9 +168,10 @@ class MyCharm(CharmBase): ... ``` -## Upgrading from `v0` +## Upgrading from `tempo_k8s.v0` -If you are upgrading from `charm_tracing` v0, you need to take the following steps (assuming you already +If you are upgrading from `tempo_k8s.v0.charm_tracing` (note that since then, the charm library moved to +`tempo_coordinator_k8s.v0.charm_tracing`), you need to take the following steps (assuming you already have the newest version of the library in your charm): 1) If you need the dependency for your tests, add the following dependency to your charm project (or, if your project had a dependency on `opentelemetry-exporter-otlp-proto-grpc` only because @@ -183,7 +184,7 @@ class MyCharm(CharmBase): For example: ``` - from charms.tempo_coordinator_k8s.v0.charm_tracing import trace_charm + from charms.tempo_k8s.v0.charm_tracing import trace_charm @trace_charm( tracing_endpoint="my_tracing_endpoint", @@ -337,7 +338,7 @@ def _remove_stale_otel_sdk_packages(): # Increment this PATCH version before using `charmcraft publish-lib` or reset # to 0 if you are raising the major API version -LIBPATCH = 4 +LIBPATCH = 5 PYDEPS = ["opentelemetry-exporter-otlp-proto-http==1.21.0"] diff --git a/poetry.lock b/poetry.lock index a951426a86..25c30fc155 100644 --- a/poetry.lock +++ b/poetry.lock @@ -1,4 +1,4 @@ -# This file is automatically @generated by Poetry 2.0.0 and should not be changed by hand. +# This file is automatically @generated by Poetry 2.0.1 and should not be changed by hand. [[package]] name = "allure-pytest" @@ -451,14 +451,14 @@ files = [ [[package]] name = "cosl" -version = "0.0.51" +version = "0.0.54" description = "Utils for COS Lite charms" optional = false python-versions = ">=3.8" groups = ["charm-libs"] files = [ - {file = "cosl-0.0.51-py3-none-any.whl", hash = "sha256:2ef43a94f0ca130fb4f2af924b75329f3c5e74b5c40ad4036af16713ad7d47d4"}, - {file = "cosl-0.0.51.tar.gz", hash = "sha256:32af380475bba32df7334d53ff16fb93466a169c7433e79a9fef8dbbecfdd43c"}, + {file = "cosl-0.0.54-py3-none-any.whl", hash = "sha256:b16520d73c72ac83cb42f0abe997d36510732d4f8499f70e9068cfa05f0d02fa"}, + {file = "cosl-0.0.54.tar.gz", hash = "sha256:6baa889cc4468b0c0f746cc6319892a30ea8fbe38cbf5c49c6885f6fdf89d6a9"}, ] [package.dependencies] @@ -561,7 +561,6 @@ files = [ {file = "cryptography-44.0.0-cp37-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:761817a3377ef15ac23cd7834715081791d4ec77f9297ee694ca1ee9c2c7e5eb"}, {file = "cryptography-44.0.0-cp37-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:3c672a53c0fb4725a29c303be906d3c1fa99c32f58abe008a82705f9ee96f40b"}, {file = "cryptography-44.0.0-cp37-abi3-manylinux_2_34_aarch64.whl", hash = "sha256:4ac4c9f37eba52cb6fbeaf5b59c152ea976726b865bd4cf87883a7e7006cc543"}, - {file = "cryptography-44.0.0-cp37-abi3-manylinux_2_34_x86_64.whl", hash = "sha256:60eb32934076fa07e4316b7b2742fa52cbb190b42c2df2863dbc4230a0a9b385"}, {file = "cryptography-44.0.0-cp37-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:ed3534eb1090483c96178fcb0f8893719d96d5274dfde98aa6add34614e97c8e"}, {file = "cryptography-44.0.0-cp37-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:f3f6fdfa89ee2d9d496e2c087cebef9d4fcbb0ad63c40e821b39f74bf48d9c5e"}, {file = "cryptography-44.0.0-cp37-abi3-win32.whl", hash = "sha256:eb33480f1bad5b78233b0ad3e1b0be21e8ef1da745d8d2aecbb20671658b9053"}, @@ -572,7 +571,6 @@ files = [ {file = "cryptography-44.0.0-cp39-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:c5eb858beed7835e5ad1faba59e865109f3e52b3783b9ac21e7e47dc5554e289"}, {file = "cryptography-44.0.0-cp39-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:f53c2c87e0fb4b0c00fa9571082a057e37690a8f12233306161c8f4b819960b7"}, {file = "cryptography-44.0.0-cp39-abi3-manylinux_2_34_aarch64.whl", hash = "sha256:9e6fc8a08e116fb7c7dd1f040074c9d7b51d74a8ea40d4df2fc7aa08b76b9e6c"}, - {file = "cryptography-44.0.0-cp39-abi3-manylinux_2_34_x86_64.whl", hash = "sha256:9abcc2e083cbe8dde89124a47e5e53ec38751f0d7dfd36801008f316a127d7ba"}, {file = "cryptography-44.0.0-cp39-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:d2436114e46b36d00f8b72ff57e598978b37399d2786fd39793c36c6d5cb1c64"}, {file = "cryptography-44.0.0-cp39-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:a01956ddfa0a6790d594f5b34fc1bfa6098aca434696a03cfdbe469b8ed79285"}, {file = "cryptography-44.0.0-cp39-abi3-win32.whl", hash = "sha256:eca27345e1214d1b9f9490d200f9db5a874479be914199194e746c893788d417"}, @@ -1552,6 +1550,7 @@ files = [ {file = "psycopg2-2.9.10-cp311-cp311-win_amd64.whl", hash = "sha256:0435034157049f6846e95103bd8f5a668788dd913a7c30162ca9503fdf542cb4"}, {file = "psycopg2-2.9.10-cp312-cp312-win32.whl", hash = "sha256:65a63d7ab0e067e2cdb3cf266de39663203d38d6a8ed97f5ca0cb315c73fe067"}, {file = "psycopg2-2.9.10-cp312-cp312-win_amd64.whl", hash = "sha256:4a579d6243da40a7b3182e0430493dbd55950c493d8c68f4eec0b302f6bbf20e"}, + {file = "psycopg2-2.9.10-cp313-cp313-win_amd64.whl", hash = "sha256:91fd603a2155da8d0cfcdbf8ab24a2d54bca72795b90d2a3ed2b6da8d979dee2"}, {file = "psycopg2-2.9.10-cp39-cp39-win32.whl", hash = "sha256:9d5b3b94b79a844a986d029eee38998232451119ad653aea42bb9220a8c5066b"}, {file = "psycopg2-2.9.10-cp39-cp39-win_amd64.whl", hash = "sha256:88138c8dedcbfa96408023ea2b0c369eda40fe5d75002c0964c78f46f11fa442"}, {file = "psycopg2-2.9.10.tar.gz", hash = "sha256:12ec0b40b0273f95296233e8750441339298e6a572f7039da5b260e3c8b60e11"}, @@ -1612,6 +1611,7 @@ files = [ {file = "psycopg2_binary-2.9.10-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:bb89f0a835bcfc1d42ccd5f41f04870c1b936d8507c6df12b7737febc40f0909"}, {file = "psycopg2_binary-2.9.10-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:f0c2d907a1e102526dd2986df638343388b94c33860ff3bbe1384130828714b1"}, {file = "psycopg2_binary-2.9.10-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:f8157bed2f51db683f31306aa497311b560f2265998122abe1dce6428bd86567"}, + {file = "psycopg2_binary-2.9.10-cp313-cp313-win_amd64.whl", hash = "sha256:27422aa5f11fbcd9b18da48373eb67081243662f9b46e6fd07c3eb46e4535142"}, {file = "psycopg2_binary-2.9.10-cp38-cp38-macosx_12_0_x86_64.whl", hash = "sha256:eb09aa7f9cecb45027683bb55aebaaf45a0df8bf6de68801a6afdc7947bb09d4"}, {file = "psycopg2_binary-2.9.10-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b73d6d7f0ccdad7bc43e6d34273f70d587ef62f824d7261c4ae9b8b1b6af90e8"}, {file = "psycopg2_binary-2.9.10-cp38-cp38-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ce5ab4bf46a211a8e924d307c1b1fcda82368586a19d0a24f8ae166f5c784864"}, @@ -2635,4 +2635,4 @@ type = ["pytest-mypy"] [metadata] lock-version = "2.1" python-versions = "^3.10" -content-hash = "2bc4a893d47cdea828762f430354381eeea5e1ef3685f83302c33299117a439d" +content-hash = "352dde8ecb150d3e5ef5ec0f8d75dbbca893a3a08ce79216f2428399bbab08d0" diff --git a/pyproject.toml b/pyproject.toml index fee816917b..3d4e150979 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -27,7 +27,7 @@ poetry-core = "*" # data_platform_libs/v0/data_models.py requires pydantic ^1.10 pydantic = "^1.10" # grafana_agent/v0/cos_agent.py -cosl = "*" +cosl = ">=0.0.50" # tls_certificates_interface/v2/tls_certificates.py cryptography = "*" jsonschema = "*" diff --git a/src/charm.py b/src/charm.py index bcb5cbcac6..973caf3120 100755 --- a/src/charm.py +++ b/src/charm.py @@ -61,6 +61,7 @@ Patroni, RemoveRaftMemberFailedError, SwitchoverFailedError, + SwitchoverNotSyncError, ) from cluster_topology_observer import ( ClusterTopologyChangeCharmEvents, @@ -1552,8 +1553,10 @@ def promote_primary_unit(self, event: ActionEvent) -> None: return try: self._patroni.switchover(self._member_name) - except SwitchoverFailedError: + except SwitchoverNotSyncError: event.fail("Unit is not sync standby") + except SwitchoverFailedError: + event.fail("Switchover failed or timed out, check the logs for details") def _on_update_status(self, _) -> None: """Update the unit status message and users list in the database.""" diff --git a/src/cluster.py b/src/cluster.py index cf7801b95d..a5f3115077 100644 --- a/src/cluster.py +++ b/src/cluster.py @@ -94,6 +94,10 @@ class SwitchoverFailedError(Exception): """Raised when a switchover failed for some reason.""" +class SwitchoverNotSyncError(SwitchoverFailedError): + """Raised when a switchover failed because node is not sync.""" + + class UpdateSyncNodeCountError(Exception): """Raised when updating synchronous_node_count failed for some reason.""" @@ -766,6 +770,13 @@ def switchover(self, candidate: str | None = None) -> None: # Check whether the switchover was unsuccessful. if r.status_code != 200: + if ( + r.status_code == 412 + and r.text == "candidate name does not match with sync_standby" + ): + logger.debug("Unit is not sync standby") + raise SwitchoverNotSyncError() + logger.warning(f"Switchover call failed with code {r.status_code} {r.text}") raise SwitchoverFailedError(f"received {r.status_code}") @retry( diff --git a/tests/integration/ha_tests/test_async_replication.py b/tests/integration/ha_tests/test_async_replication.py index 588486471f..7ac7ec4dbe 100644 --- a/tests/integration/ha_tests/test_async_replication.py +++ b/tests/integration/ha_tests/test_async_replication.py @@ -263,7 +263,7 @@ async def test_switchover( leader_unit = await get_leader_unit(ops_test, DATABASE_APP_NAME, model=second_model) assert leader_unit is not None, "No leader unit found" logger.info("promoting the second cluster") - run_action = await leader_unit.run_action("promote-to-primary", **{"force": True}) + run_action = await leader_unit.run_action("promote-to-primary", scope="cluster", force=True) await run_action.wait() assert (run_action.results.get("return-code", None) == 0) or ( run_action.results.get("Code", None) == "0" @@ -342,7 +342,7 @@ async def test_promote_standby( leader_unit = await get_leader_unit(ops_test, DATABASE_APP_NAME) assert leader_unit is not None, "No leader unit found" logger.info("promoting the first cluster") - run_action = await leader_unit.run_action("promote-to-primary") + run_action = await leader_unit.run_action("promote-to-primary", scope="cluster") await run_action.wait() assert (run_action.results.get("return-code", None) == 0) or ( run_action.results.get("Code", None) == "0" diff --git a/tests/unit/test_charm.py b/tests/unit/test_charm.py index 77b62a08dc..4c411d2e67 100644 --- a/tests/unit/test_charm.py +++ b/tests/unit/test_charm.py @@ -35,7 +35,12 @@ PRIMARY_NOT_REACHABLE_MESSAGE, PostgresqlOperatorCharm, ) -from cluster import NotReadyError, RemoveRaftMemberFailedError, SwitchoverFailedError +from cluster import ( + NotReadyError, + RemoveRaftMemberFailedError, + SwitchoverFailedError, + SwitchoverNotSyncError, +) from constants import PEER, POSTGRESQL_SNAP_NAME, SECRET_INTERNAL_LABEL, SNAP_PACKAGES CREATE_CLUSTER_CONF_PATH = "/etc/postgresql-common/createcluster.d/pgcharm.conf" @@ -2787,6 +2792,17 @@ def test_on_promote_to_primary(harness): harness.charm._on_promote_to_primary(event) + event.fail.assert_called_once_with( + "Switchover failed or timed out, check the logs for details" + ) + event.fail.reset_mock() + + # Unit, no force, not sync + event.params = {"scope": "unit"} + _switchover.side_effect = SwitchoverNotSyncError + + harness.charm._on_promote_to_primary(event) + event.fail.assert_called_once_with("Unit is not sync standby") event.fail.reset_mock() diff --git a/tests/unit/test_cluster.py b/tests/unit/test_cluster.py index 07f01eed47..ed316e9a20 100644 --- a/tests/unit/test_cluster.py +++ b/tests/unit/test_cluster.py @@ -20,7 +20,13 @@ ) from charm import PostgresqlOperatorCharm -from cluster import PATRONI_TIMEOUT, Patroni, RemoveRaftMemberFailedError +from cluster import ( + PATRONI_TIMEOUT, + Patroni, + RemoveRaftMemberFailedError, + SwitchoverFailedError, + SwitchoverNotSyncError, +) from constants import ( PATRONI_CONF_PATH, PATRONI_LOGS_PATH, @@ -473,6 +479,22 @@ def test_switchover(peers_ips, patroni): timeout=PATRONI_TIMEOUT, ) + # Test candidate, not sync + response = _post.return_value + response.status_code = 412 + response.text = "candidate name does not match with sync_standby" + with pytest.raises(SwitchoverNotSyncError): + patroni.switchover("candidate") + assert False + + # Test general error + response = _post.return_value + response.status_code = 412 + response.text = "something else " + with pytest.raises(SwitchoverFailedError): + patroni.switchover() + assert False + def test_update_synchronous_node_count(peers_ips, patroni): with ( From d1cdf10e8fdb6f94775625faa0e075987d0af750 Mon Sep 17 00:00:00 2001 From: Dragomir Penev <6687393+dragomirp@users.noreply.github.com> Date: Thu, 13 Feb 2025 12:31:10 +0200 Subject: [PATCH 40/74] Disable Nextcloud test (#767) --- tests/integration/new_relations/test_new_relations_2.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/tests/integration/new_relations/test_new_relations_2.py b/tests/integration/new_relations/test_new_relations_2.py index 08827c168c..4a9134a2d1 100644 --- a/tests/integration/new_relations/test_new_relations_2.py +++ b/tests/integration/new_relations/test_new_relations_2.py @@ -4,6 +4,7 @@ import logging from pathlib import Path +import pytest import yaml from pytest_operator.plugin import OpsTest @@ -29,6 +30,7 @@ @markers.amd64_only # nextcloud charm not available for arm64 +@pytest.mark.skip(reason="Unstable") async def test_nextcloud_db_blocked(ops_test: OpsTest, charm: str) -> None: # Deploy Database Charm and Nextcloud await asyncio.gather( From 754bb1ca31598b916ce983a3f1fb91bce21c6947 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Thu, 13 Feb 2025 12:06:02 +0000 Subject: [PATCH 41/74] Update canonical/data-platform-workflows action to v30 (#770) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- .github/workflows/ci.yaml | 4 ++-- .github/workflows/release.yaml | 2 +- .github/workflows/sync_docs.yaml | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index 05c7ea52aa..7a286d1ba2 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -27,7 +27,7 @@ on: jobs: lint: name: Lint - uses: canonical/data-platform-workflows/.github/workflows/lint.yaml@v29.1.0 + uses: canonical/data-platform-workflows/.github/workflows/lint.yaml@v30.0.0 unit-test: name: Unit test charm @@ -49,7 +49,7 @@ jobs: build: name: Build charm - uses: canonical/data-platform-workflows/.github/workflows/build_charm.yaml@v29.1.0 + uses: canonical/data-platform-workflows/.github/workflows/build_charm.yaml@v30.0.0 integration-test: name: Integration test charm diff --git a/.github/workflows/release.yaml b/.github/workflows/release.yaml index b3de155ce1..59bf9ca91b 100644 --- a/.github/workflows/release.yaml +++ b/.github/workflows/release.yaml @@ -27,7 +27,7 @@ jobs: name: Release charm needs: - ci-tests - uses: canonical/data-platform-workflows/.github/workflows/release_charm.yaml@v29.1.0 + uses: canonical/data-platform-workflows/.github/workflows/release_charm.yaml@v30.0.0 with: channel: 14/edge artifact-prefix: ${{ needs.ci-tests.outputs.artifact-prefix }} diff --git a/.github/workflows/sync_docs.yaml b/.github/workflows/sync_docs.yaml index 179e10d527..ec2b7b82ef 100644 --- a/.github/workflows/sync_docs.yaml +++ b/.github/workflows/sync_docs.yaml @@ -10,7 +10,7 @@ on: jobs: sync-docs: name: Sync docs from Discourse - uses: canonical/data-platform-workflows/.github/workflows/sync_docs.yaml@v29.1.0 + uses: canonical/data-platform-workflows/.github/workflows/sync_docs.yaml@v30.0.0 with: reviewers: a-velasco,izmalk permissions: From 9831317ad08d110df265d61920a31572a509274b Mon Sep 17 00:00:00 2001 From: Carl Csaposs Date: Thu, 13 Feb 2025 13:20:57 +0000 Subject: [PATCH 42/74] Use _promote_charm.yaml (#771) Use `charmcraft promote` and auto-generate release notes --- .github/release.yaml | 8 ++++++++ .github/workflows/check_pr.yaml | 18 +++++++++++++++++ .github/workflows/promote.yaml | 36 +++++++++++++++++++++++++++++++++ 3 files changed, 62 insertions(+) create mode 100644 .github/release.yaml create mode 100644 .github/workflows/check_pr.yaml create mode 100644 .github/workflows/promote.yaml diff --git a/.github/release.yaml b/.github/release.yaml new file mode 100644 index 0000000000..9ef36aca6d --- /dev/null +++ b/.github/release.yaml @@ -0,0 +1,8 @@ +changelog: + categories: + - title: Features + labels: + - enhancement + - title: Bug fixes + labels: + - bug diff --git a/.github/workflows/check_pr.yaml b/.github/workflows/check_pr.yaml new file mode 100644 index 0000000000..e989be903d --- /dev/null +++ b/.github/workflows/check_pr.yaml @@ -0,0 +1,18 @@ +# Copyright 2025 Canonical Ltd. +# See LICENSE file for licensing details. +name: Check pull request + +on: + pull_request: + types: + - opened + - labeled + - unlabeled + - edited + branches: + - main + +jobs: + check-pr: + name: Check pull request + uses: canonical/data-platform-workflows/.github/workflows/check_charm_pr.yaml@v30.0.0 diff --git a/.github/workflows/promote.yaml b/.github/workflows/promote.yaml new file mode 100644 index 0000000000..59226a484a --- /dev/null +++ b/.github/workflows/promote.yaml @@ -0,0 +1,36 @@ +# Copyright 2025 Canonical Ltd. +# See LICENSE file for licensing details. +name: Promote charm + +on: + workflow_dispatch: + inputs: + from-risk: + description: Promote from this Charmhub risk + required: true + type: choice + options: + - edge + - beta + - candidate + to-risk: + description: Promote to this Charmhub risk + required: true + type: choice + options: + - beta + - candidate + - stable + +jobs: + promote: + name: Promote charm + uses: canonical/data-platform-workflows/.github/workflows/_promote_charm.yaml@v30.0.0 + with: + track: '14' + from-risk: ${{ inputs.from-risk }} + to-risk: ${{ inputs.to-risk }} + secrets: + charmhub-token: ${{ secrets.CHARMHUB_TOKEN }} + permissions: + contents: write # Needed to update git tags From 87f075529720d10fe5c7fd3299e6e8caf08a0a56 Mon Sep 17 00:00:00 2001 From: Dragomir Penev <6687393+dragomirp@users.noreply.github.com> Date: Thu, 13 Feb 2025 16:41:54 +0200 Subject: [PATCH 43/74] [DPE-5827] Set all nodes to synchronous replicas (#672) * Set all nodes to synchronous replicas * Fix template var * Also change config patching * Update sync nodes during upgrade * Revert are_writes_increasing changes * Add back logging * Try without logs * Tactical sleep * Log removal error * Remove logs * Tweak replication test * Pass down unit * Wait for test app to idle * Add comment * Port config changes * Copy policy test * Fix import * Missed param removal * Unit test * Missing attr * Add logs * Add timeout to connection * Log conn str * Fix num of standbys * Charm fixture * Remove stepdown hook * Config description * Revert conn str * Add async scaling test * Typo * Don't remove standby and primary --- config.yaml | 6 + src/charm.py | 62 ++------ src/cluster.py | 23 ++- src/config.py | 3 +- src/upgrade.py | 1 + templates/patroni.yml.j2 | 2 +- tests/integration/ha_tests/helpers.py | 2 +- .../integration/ha_tests/test_replication.py | 4 +- tests/integration/ha_tests/test_scaling.py | 72 +--------- .../ha_tests/test_scaling_three_units.py | 15 +- .../test_scaling_three_units_async.py | 133 ++++++++++++++++++ .../ha_tests/test_synchronous_policy.py | 78 ++++++++++ tests/integration/helpers.py | 3 +- tests/integration/test_config.py | 4 + tests/integration/test_db.py | 30 ++-- .../task.yaml | 9 ++ .../test_synchronous_policy.py/task.yaml | 7 + tests/unit/test_charm.py | 46 ++---- tests/unit/test_cluster.py | 2 +- tests/unit/test_upgrade.py | 5 + 20 files changed, 323 insertions(+), 184 deletions(-) create mode 100644 tests/integration/ha_tests/test_scaling_three_units_async.py create mode 100644 tests/integration/ha_tests/test_synchronous_policy.py create mode 100644 tests/spread/test_scaling_three_units_async.py/task.yaml create mode 100644 tests/spread/test_synchronous_policy.py/task.yaml diff --git a/config.yaml b/config.yaml index 8b474ec962..46c3c9f3a7 100644 --- a/config.yaml +++ b/config.yaml @@ -2,6 +2,12 @@ # See LICENSE file for licensing details. options: + synchronous_node_count: + description: | + Sets the number of synchronous nodes to be maintained in the cluster. Should be + either "all", "majority" or a positive integer value. + type: string + default: "all" durability_synchronous_commit: description: | Sets the current transactions synchronization level. This charm allows only the diff --git a/src/charm.py b/src/charm.py index 973caf3120..349c24a609 100755 --- a/src/charm.py +++ b/src/charm.py @@ -182,7 +182,6 @@ def __init__(self, *args): self.framework.observe(self.on[PEER].relation_changed, self._on_peer_relation_changed) self.framework.observe(self.on.secret_changed, self._on_peer_relation_changed) self.framework.observe(self.on[PEER].relation_departed, self._on_peer_relation_departed) - self.framework.observe(self.on.pgdata_storage_detaching, self._on_pgdata_storage_detaching) self.framework.observe(self.on.start, self._on_start) self.framework.observe(self.on.get_password_action, self._on_get_password) self.framework.observe(self.on.set_password_action, self._on_set_password) @@ -432,10 +431,10 @@ def _on_get_primary(self, event: ActionEvent) -> None: except RetryError as e: logger.error(f"failed to get primary with error {e}") - def _updated_synchronous_node_count(self, num_units: int | None = None) -> bool: + def updated_synchronous_node_count(self) -> bool: """Tries to update synchronous_node_count configuration and reports the result.""" try: - self._patroni.update_synchronous_node_count(num_units) + self._patroni.update_synchronous_node_count() return True except RetryError: logger.debug("Unable to set synchronous_node_count") @@ -473,9 +472,7 @@ def _on_peer_relation_departed(self, event: RelationDepartedEvent) -> None: if not self.unit.is_leader(): return - if not self.is_cluster_initialised or not self._updated_synchronous_node_count( - len(self._units_ips) - ): + if not self.is_cluster_initialised or not self.updated_synchronous_node_count(): logger.debug("Deferring on_peer_relation_departed: cluster not initialized") event.defer() return @@ -501,52 +498,6 @@ def _on_peer_relation_departed(self, event: RelationDepartedEvent) -> None: # Update the sync-standby endpoint in the async replication data. self.async_replication.update_async_replication_data() - def _on_pgdata_storage_detaching(self, _) -> None: - # Change the primary if it's the unit that is being removed. - try: - primary = self._patroni.get_primary(unit_name_pattern=True) - except RetryError: - # Ignore the event if the primary couldn't be retrieved. - # If a switchover is needed, an automatic failover will be triggered - # when the unit is removed. - logger.debug("Early exit on_pgdata_storage_detaching: primary cannot be retrieved") - return - - if self.unit.name != primary: - return - - if not self._patroni.are_all_members_ready(): - logger.warning( - "could not switchover because not all members are ready" - " - an automatic failover will be triggered" - ) - return - - # Try to switchover to another member and raise an exception if it doesn't succeed. - # If it doesn't happen on time, Patroni will automatically run a fail-over. - try: - # Get the current primary to check if it has changed later. - current_primary = self._patroni.get_primary() - - # Trigger the switchover. - self._patroni.switchover() - - # Wait for the switchover to complete. - self._patroni.primary_changed(current_primary) - - logger.info("successful switchover") - except (RetryError, SwitchoverFailedError) as e: - logger.warning( - f"switchover failed with reason: {e} - an automatic failover will be triggered" - ) - return - - # Only update the connection endpoints if there is a primary. - # A cluster can have all members as replicas for some time after - # a failed switchover, so wait until the primary is elected. - if self.primary_endpoint: - self._update_relation_endpoints() - def _stuck_raft_cluster_check(self) -> None: """Check for stuck raft cluster and reinitialise if safe.""" raft_stuck = False @@ -1184,6 +1135,11 @@ def _on_config_changed(self, event) -> None: logger.error("Invalid configuration: %s", str(e)) return + if not self.updated_synchronous_node_count(): + logger.debug("Defer on_config_changed: unable to set synchronous node count") + event.defer() + return + if self.is_blocked and "Configuration Error" in self.unit.status.message: self.unit.status = ActiveStatus() @@ -1195,7 +1151,9 @@ def _on_config_changed(self, event) -> None: # Enable and/or disable the extensions. self.enable_disable_extensions() + self._unblock_extensions() + def _unblock_extensions(self) -> None: # Unblock the charm after extensions are enabled (only if it's blocked due to application # charms requesting extensions). if self.unit.status.message != EXTENSIONS_BLOCKING_MESSAGE: diff --git a/src/cluster.py b/src/cluster.py index a5f3115077..b321a4cac4 100644 --- a/src/cluster.py +++ b/src/cluster.py @@ -672,7 +672,7 @@ def render_patroni_yml_file( stanza=stanza, restore_stanza=restore_stanza, version=self.get_postgresql_version().split(".")[0], - minority_count=self.planned_units // 2, + synchronous_node_count=self._synchronous_node_count, pg_parameters=parameters, primary_cluster_endpoint=self.charm.async_replication.get_primary_cluster_endpoint(), extra_replication_endpoints=self.charm.async_replication.get_standby_endpoints(), @@ -926,6 +926,7 @@ def remove_raft_member(self, member_ip: str) -> None: raise RemoveRaftMemberFailedError() from None if not result.startswith("SUCCESS"): + logger.debug("Remove raft member: Remove call not successful") raise RemoveRaftMemberFailedError() @retry(stop=stop_after_attempt(10), wait=wait_exponential(multiplier=1, min=2, max=10)) @@ -988,16 +989,28 @@ def bulk_update_parameters_controller_by_patroni(self, parameters: dict[str, Any timeout=PATRONI_TIMEOUT, ) - def update_synchronous_node_count(self, units: int | None = None) -> None: + @property + def _synchronous_node_count(self) -> int: + planned_units = self.charm.app.planned_units() + if self.charm.config.synchronous_node_count == "all": + return planned_units - 1 + elif self.charm.config.synchronous_node_count == "majority": + return planned_units // 2 + # -1 for leader + return ( + self.charm.config.synchronous_node_count + if self.charm.config.synchronous_node_count < planned_units - 1 + else planned_units - 1 + ) + + def update_synchronous_node_count(self) -> None: """Update synchronous_node_count to the minority of the planned cluster.""" - if units is None: - units = self.planned_units # Try to update synchronous_node_count. for attempt in Retrying(stop=stop_after_delay(60), wait=wait_fixed(3)): with attempt: r = requests.patch( f"{self._patroni_url}/config", - json={"synchronous_node_count": units // 2}, + json={"synchronous_node_count": self._synchronous_node_count}, verify=self.verify, auth=self._patroni_auth, timeout=PATRONI_TIMEOUT, diff --git a/src/config.py b/src/config.py index 45b7b04566..6306243df2 100644 --- a/src/config.py +++ b/src/config.py @@ -8,7 +8,7 @@ from typing import Literal from charms.data_platform_libs.v0.data_models import BaseConfigModel -from pydantic import validator +from pydantic import PositiveInt, validator from locales import SNAP_LOCALES @@ -18,6 +18,7 @@ class CharmConfig(BaseConfigModel): """Manager for the structured configuration.""" + synchronous_node_count: Literal["all", "majority"] | PositiveInt durability_synchronous_commit: str | None instance_default_text_search_config: str | None instance_max_locks_per_transaction: int | None diff --git a/src/upgrade.py b/src/upgrade.py index 629ba06fa8..c24d2952af 100644 --- a/src/upgrade.py +++ b/src/upgrade.py @@ -146,6 +146,7 @@ def _on_upgrade_granted(self, event: UpgradeGrantedEvent) -> None: # Update the configuration. self.charm.unit.status = MaintenanceStatus("updating configuration") self.charm.update_config() + self.charm.updated_synchronous_node_count() self.charm.unit.status = MaintenanceStatus("refreshing the snap") self.charm._install_snap_packages(packages=SNAP_PACKAGES, refresh=True) diff --git a/templates/patroni.yml.j2 b/templates/patroni.yml.j2 index b072254c3a..36d4f3ff5f 100644 --- a/templates/patroni.yml.j2 +++ b/templates/patroni.yml.j2 @@ -59,7 +59,7 @@ bootstrap: retry_timeout: 10 maximum_lag_on_failover: 1048576 synchronous_mode: true - synchronous_node_count: {{ minority_count }} + synchronous_node_count: {{ synchronous_node_count }} postgresql: use_pg_rewind: true remove_data_directory_on_rewind_failure: true diff --git a/tests/integration/ha_tests/helpers.py b/tests/integration/ha_tests/helpers.py index 57ddac6dd9..951659ee45 100644 --- a/tests/integration/ha_tests/helpers.py +++ b/tests/integration/ha_tests/helpers.py @@ -106,7 +106,7 @@ async def are_writes_increasing( with attempt: more_writes, _ = await count_writes( ops_test, - down_unit=down_unit, + down_unit=down_units[0], use_ip_from_inside=use_ip_from_inside, extra_model=extra_model, ) diff --git a/tests/integration/ha_tests/test_replication.py b/tests/integration/ha_tests/test_replication.py index b1c8da31a4..fed67a8019 100644 --- a/tests/integration/ha_tests/test_replication.py +++ b/tests/integration/ha_tests/test_replication.py @@ -60,9 +60,7 @@ async def test_reelection(ops_test: OpsTest, continuous_writes, primary_start_ti # Remove the primary unit. primary_name = await get_primary(ops_test, app) - await ops_test.model.destroy_units( - primary_name, - ) + await ops_test.model.destroy_units(primary_name) # Wait and get the primary again (which can be any unit, including the previous primary). async with ops_test.fast_forward(): diff --git a/tests/integration/ha_tests/test_scaling.py b/tests/integration/ha_tests/test_scaling.py index 6e43d1f83a..8c85dd931e 100644 --- a/tests/integration/ha_tests/test_scaling.py +++ b/tests/integration/ha_tests/test_scaling.py @@ -158,72 +158,15 @@ async def test_removing_raft_majority(ops_test: OpsTest, continuous_writes) -> N ops_test.model.destroy_unit( original_roles["primaries"][0], force=True, destroy_storage=False, max_wait=1500 ), - ops_test.model.destroy_unit( - original_roles["replicas"][0], force=True, destroy_storage=False, max_wait=1500 - ), ops_test.model.destroy_unit( original_roles["sync_standbys"][0], force=True, destroy_storage=False, max_wait=1500 ), - ) - - left_unit = ops_test.model.units[original_roles["sync_standbys"][1]] - await ops_test.model.block_until( - lambda: left_unit.workload_status == "blocked" - and left_unit.workload_status_message == "Raft majority loss, run: promote-to-primary", - timeout=600, - ) - - run_action = await left_unit.run_action("promote-to-primary", scope="unit", force=True) - await run_action.wait() - - await ops_test.model.wait_for_idle(status="active", timeout=900, idle_period=45) - - await are_writes_increasing( - ops_test, - [ - original_roles["primaries"][0], - original_roles["replicas"][0], - original_roles["sync_standbys"][0], - ], - ) - - logger.info("Scaling back up") - await ops_test.model.applications[DATABASE_APP_NAME].add_unit(count=3) - await ops_test.model.wait_for_idle(status="active", timeout=1500) - - await check_writes(ops_test) - new_roles = await get_cluster_roles( - ops_test, ops_test.model.applications[DATABASE_APP_NAME].units[0].name - ) - assert len(new_roles["primaries"]) == 1 - assert len(new_roles["sync_standbys"]) == 2 - assert new_roles["primaries"][0] == original_roles["sync_standbys"][1] - - -@markers.juju3 -@pytest.mark.abort_on_fail -async def test_removing_raft_majority_async(ops_test: OpsTest, continuous_writes) -> None: - # Start an application that continuously writes data to the database. - app = await app_name(ops_test) - original_roles = await get_cluster_roles( - ops_test, ops_test.model.applications[DATABASE_APP_NAME].units[0].name - ) - - await start_continuous_writes(ops_test, app) - logger.info("Deleting primary") - await gather( ops_test.model.destroy_unit( - original_roles["primaries"][0], force=True, destroy_storage=False, max_wait=1500 - ), - ops_test.model.destroy_unit( - original_roles["replicas"][0], force=True, destroy_storage=False, max_wait=1500 - ), - ops_test.model.destroy_unit( - original_roles["replicas"][1], force=True, destroy_storage=False, max_wait=1500 + original_roles["sync_standbys"][1], force=True, destroy_storage=False, max_wait=1500 ), ) - left_unit = ops_test.model.units[original_roles["sync_standbys"][0]] + left_unit = ops_test.model.units[original_roles["sync_standbys"][2]] await ops_test.model.block_until( lambda: left_unit.workload_status == "blocked" and left_unit.workload_status_message == "Raft majority loss, run: promote-to-primary", @@ -239,8 +182,8 @@ async def test_removing_raft_majority_async(ops_test: OpsTest, continuous_writes ops_test, [ original_roles["primaries"][0], - original_roles["replicas"][0], - original_roles["replicas"][1], + original_roles["sync_standbys"][0], + original_roles["sync_standbys"][1], ], ) @@ -253,8 +196,5 @@ async def test_removing_raft_majority_async(ops_test: OpsTest, continuous_writes ops_test, ops_test.model.applications[DATABASE_APP_NAME].units[0].name ) assert len(new_roles["primaries"]) == 1 - assert len(new_roles["sync_standbys"]) == 2 - assert ( - new_roles["primaries"][0] == original_roles["sync_standbys"][0] - or new_roles["primaries"][0] == original_roles["sync_standbys"][1] - ) + assert len(new_roles["sync_standbys"]) == 4 + assert new_roles["primaries"][0] == original_roles["sync_standbys"][2] diff --git a/tests/integration/ha_tests/test_scaling_three_units.py b/tests/integration/ha_tests/test_scaling_three_units.py index 74a1e8ba4b..7a44d0f5c2 100644 --- a/tests/integration/ha_tests/test_scaling_three_units.py +++ b/tests/integration/ha_tests/test_scaling_three_units.py @@ -3,6 +3,7 @@ # See LICENSE file for licensing details. import logging from asyncio import exceptions, gather, sleep +from copy import deepcopy import pytest from pytest_operator.plugin import OpsTest @@ -60,9 +61,8 @@ async def test_build_and_deploy(ops_test: OpsTest, charm) -> None: [ ["primaries"], ["sync_standbys"], - ["replicas"], - ["primaries", "replicas"], - ["sync_standbys", "replicas"], + ["primaries", "sync_standbys"], + ["sync_standbys", "sync_standbys"], ], ) @pytest.mark.abort_on_fail @@ -73,8 +73,9 @@ async def test_removing_unit(ops_test: OpsTest, roles: list[str], continuous_wri original_roles = await get_cluster_roles( ops_test, ops_test.model.applications[DATABASE_APP_NAME].units[0].name ) + copied_roles = deepcopy(original_roles) await start_continuous_writes(ops_test, app) - units = [original_roles[role][0] for role in roles] + units = [copied_roles[role].pop(0) for role in roles] for unit in units: logger.info(f"Stopping unit {unit}") await stop_machine(ops_test, await get_machine_from_unit(ops_test, unit)) @@ -121,10 +122,10 @@ async def test_removing_unit(ops_test: OpsTest, roles: list[str], continuous_wri ops_test, ops_test.model.applications[DATABASE_APP_NAME].units[0].name ) assert len(new_roles["primaries"]) == 1 - assert len(new_roles["sync_standbys"]) == 1 - assert len(new_roles["replicas"]) == 1 + assert len(new_roles["sync_standbys"]) == 2 + assert len(new_roles["replicas"]) == 0 if "primaries" in roles: - assert new_roles["primaries"][0] == original_roles["sync_standbys"][0] + assert new_roles["primaries"][0] in original_roles["sync_standbys"] else: assert new_roles["primaries"][0] == original_roles["primaries"][0] diff --git a/tests/integration/ha_tests/test_scaling_three_units_async.py b/tests/integration/ha_tests/test_scaling_three_units_async.py new file mode 100644 index 0000000000..41fc03451d --- /dev/null +++ b/tests/integration/ha_tests/test_scaling_three_units_async.py @@ -0,0 +1,133 @@ +#!/usr/bin/env python3 +# Copyright 2024 Canonical Ltd. +# See LICENSE file for licensing details. +import logging +from asyncio import exceptions, gather, sleep +from copy import deepcopy + +import pytest +from pytest_operator.plugin import OpsTest + +from .. import markers +from ..helpers import ( + CHARM_BASE, + DATABASE_APP_NAME, + get_machine_from_unit, + stop_machine, +) +from .conftest import APPLICATION_NAME +from .helpers import ( + app_name, + are_writes_increasing, + check_writes, + get_cluster_roles, + start_continuous_writes, +) + +logger = logging.getLogger(__name__) + +charm = None + + +@markers.juju3 +@pytest.mark.abort_on_fail +async def test_build_and_deploy(ops_test: OpsTest, charm) -> None: + """Build and deploy two PostgreSQL clusters.""" + # This is a potentially destructive test, so it shouldn't be run against existing clusters + async with ops_test.fast_forward(): + # Deploy the first cluster with reusable storage + await gather( + ops_test.model.deploy( + charm, + application_name=DATABASE_APP_NAME, + num_units=3, + base=CHARM_BASE, + config={"profile": "testing", "synchronous_node_count": "majority"}, + ), + ops_test.model.deploy( + APPLICATION_NAME, + application_name=APPLICATION_NAME, + base=CHARM_BASE, + channel="edge", + ), + ) + + await ops_test.model.wait_for_idle(status="active", timeout=1500) + + +@markers.juju3 +@pytest.mark.parametrize( + "roles", + [ + ["primaries"], + ["sync_standbys"], + ["replicas"], + ["primaries", "replicas"], + ["sync_standbys", "replicas"], + ], +) +@pytest.mark.abort_on_fail +async def test_removing_unit(ops_test: OpsTest, roles: list[str], continuous_writes) -> None: + logger.info(f"removing {', '.join(roles)}") + # Start an application that continuously writes data to the database. + app = await app_name(ops_test) + original_roles = await get_cluster_roles( + ops_test, ops_test.model.applications[DATABASE_APP_NAME].units[0].name + ) + copied_roles = deepcopy(original_roles) + await start_continuous_writes(ops_test, app) + units = [copied_roles[role].pop(0) for role in roles] + for unit in units: + logger.info(f"Stopping unit {unit}") + await stop_machine(ops_test, await get_machine_from_unit(ops_test, unit)) + await sleep(15) + for unit in units: + logger.info(f"Deleting unit {unit}") + await ops_test.model.destroy_unit(unit, force=True, destroy_storage=False, max_wait=1500) + + if len(roles) > 1: + for left_unit in ops_test.model.applications[DATABASE_APP_NAME].units: + if left_unit.name not in units: + break + try: + await ops_test.model.block_until( + lambda: left_unit.workload_status == "blocked" + and left_unit.workload_status_message + == "Raft majority loss, run: promote-to-primary", + timeout=600, + ) + + run_action = ( + await ops_test.model.applications[DATABASE_APP_NAME] + .units[0] + .run_action("promote-to-primary", scope="unit", force=True) + ) + await run_action.wait() + except exceptions.TimeoutError: + # Check if Patroni self healed + assert ( + left_unit.workload_status == "active" + and left_unit.workload_status_message == "Primary" + ) + logger.warning(f"Patroni self-healed without raft reinitialisation for roles {roles}") + + await ops_test.model.wait_for_idle(status="active", timeout=600, idle_period=45) + + await are_writes_increasing(ops_test, units) + + logger.info("Scaling back up") + await ops_test.model.applications[DATABASE_APP_NAME].add_unit(count=len(roles)) + await ops_test.model.wait_for_idle(status="active", timeout=1500) + + new_roles = await get_cluster_roles( + ops_test, ops_test.model.applications[DATABASE_APP_NAME].units[0].name + ) + assert len(new_roles["primaries"]) == 1 + assert len(new_roles["sync_standbys"]) == 1 + assert len(new_roles["replicas"]) == 1 + if "primaries" in roles: + assert new_roles["primaries"][0] in original_roles["sync_standbys"] + else: + assert new_roles["primaries"][0] == original_roles["primaries"][0] + + await check_writes(ops_test) diff --git a/tests/integration/ha_tests/test_synchronous_policy.py b/tests/integration/ha_tests/test_synchronous_policy.py new file mode 100644 index 0000000000..920642d81e --- /dev/null +++ b/tests/integration/ha_tests/test_synchronous_policy.py @@ -0,0 +1,78 @@ +#!/usr/bin/env python3 +# Copyright 2024 Canonical Ltd. +# See LICENSE file for licensing details. +import pytest +from pytest_operator.plugin import OpsTest +from tenacity import Retrying, stop_after_attempt, wait_fixed + +from ..helpers import CHARM_BASE +from .helpers import app_name, get_cluster_roles + + +@pytest.mark.abort_on_fail +async def test_build_and_deploy(ops_test: OpsTest, charm: str) -> None: + """Build and deploy two PostgreSQL clusters.""" + async with ops_test.fast_forward(): + # Deploy the first cluster with reusable storage + await ops_test.model.deploy( + charm, + num_units=3, + base=CHARM_BASE, + config={"profile": "testing"}, + ) + await ops_test.model.wait_for_idle(status="active", timeout=1500) + + +async def test_default_all(ops_test: OpsTest) -> None: + app = await app_name(ops_test) + + async with ops_test.fast_forward(): + await ops_test.model.wait_for_idle(apps=[app], status="active", timeout=300) + + for attempt in Retrying(stop=stop_after_attempt(3), wait=wait_fixed(5), reraise=True): + with attempt: + roles = await get_cluster_roles( + ops_test, ops_test.model.applications[app].units[0].name + ) + + assert len(roles["primaries"]) == 1 + assert len(roles["sync_standbys"]) == 2 + assert len(roles["replicas"]) == 0 + + +async def test_majority(ops_test: OpsTest) -> None: + app = await app_name(ops_test) + + await ops_test.model.applications[app].set_config({"synchronous_node_count": "majority"}) + + async with ops_test.fast_forward(): + await ops_test.model.wait_for_idle(apps=[app], status="active") + + for attempt in Retrying(stop=stop_after_attempt(3), wait=wait_fixed(5), reraise=True): + with attempt: + roles = await get_cluster_roles( + ops_test, ops_test.model.applications[app].units[0].name + ) + + assert len(roles["primaries"]) == 1 + assert len(roles["sync_standbys"]) == 1 + assert len(roles["replicas"]) == 1 + + +async def test_constant(ops_test: OpsTest) -> None: + app = await app_name(ops_test) + + await ops_test.model.applications[app].set_config({"synchronous_node_count": "2"}) + + async with ops_test.fast_forward(): + await ops_test.model.wait_for_idle(apps=[app], status="active", timeout=300) + + for attempt in Retrying(stop=stop_after_attempt(3), wait=wait_fixed(5), reraise=True): + with attempt: + roles = await get_cluster_roles( + ops_test, ops_test.model.applications[app].units[0].name + ) + + assert len(roles["primaries"]) == 1 + assert len(roles["sync_standbys"]) == 2 + assert len(roles["replicas"]) == 0 diff --git a/tests/integration/helpers.py b/tests/integration/helpers.py index dfb0254bfc..acd4efcf01 100644 --- a/tests/integration/helpers.py +++ b/tests/integration/helpers.py @@ -1053,7 +1053,6 @@ def switchover( ) assert response.status_code == 200 app_name = current_primary.split("/")[0] - minority_count = len(ops_test.model.applications[app_name].units) // 2 for attempt in Retrying(stop=stop_after_attempt(30), wait=wait_fixed(2), reraise=True): with attempt: response = requests.get(f"http://{primary_ip}:8008/cluster") @@ -1061,7 +1060,7 @@ def switchover( standbys = len([ member for member in response.json()["members"] if member["role"] == "sync_standby" ]) - assert standbys >= minority_count + assert standbys == len(ops_test.model.applications[app_name].units) - 1 async def wait_for_idle_on_blocked( diff --git a/tests/integration/test_config.py b/tests/integration/test_config.py index 304c8e9efc..f9dba0acbb 100644 --- a/tests/integration/test_config.py +++ b/tests/integration/test_config.py @@ -32,6 +32,10 @@ async def test_config_parameters(ops_test: OpsTest, charm) -> None: test_string = "abcXYZ123" configs = [ + {"synchronous_node_count": ["0", "1"]}, # config option is greater than 0 + { + "synchronous_node_count": [test_string, "all"] + }, # config option is one of `all`, `minority` or `majority` { "durability_synchronous_commit": [test_string, "on"] }, # config option is one of `on`, `remote_apply` or `remote_write` diff --git a/tests/integration/test_db.py b/tests/integration/test_db.py index 5d6195a8e2..a24ea28475 100644 --- a/tests/integration/test_db.py +++ b/tests/integration/test_db.py @@ -53,7 +53,7 @@ async def test_mailman3_core_db(ops_test: OpsTest, charm: str) -> None: config={"profile": "testing"}, ) - # Wait until the PostgreSQL charm is successfully deployed. + logger.info("Wait until the PostgreSQL charm is successfully deployed.") await ops_test.model.wait_for_idle( apps=[DATABASE_APP_NAME], status="active", @@ -78,12 +78,12 @@ async def test_mailman3_core_db(ops_test: OpsTest, charm: str) -> None: await check_database_users_existence(ops_test, mailman3_core_users, []) - # Assert Mailman3 Core is configured to use PostgreSQL instead of SQLite. + logger.info("Assert Mailman3 Core is configured to use PostgreSQL instead of SQLite.") mailman_unit = ops_test.model.applications[MAILMAN3_CORE_APP_NAME].units[0] result = await run_command_on_unit(ops_test, mailman_unit.name, "mailman info") assert "db url: postgres://" in result - # Do some CRUD operations using Mailman3 Core client. + logger.info("Do some CRUD operations using Mailman3 Core client.") domain_name = "canonical.com" list_name = "postgresql-list" credentials = ( @@ -93,17 +93,17 @@ async def test_mailman3_core_db(ops_test: OpsTest, charm: str) -> None: f"http://{mailman_unit.public_address}:8001/3.1", credentials[0], credentials[1] ) - # Create a domain and list the domains to check that the new one is there. + logger.info("Create a domain and list the domains to check that the new one is there.") domain = client.create_domain(domain_name) assert domain_name in [domain.mail_host for domain in client.domains] - # Update the domain by creating a mailing list into it. + logger.info("Update the domain by creating a mailing list into it.") mailing_list = domain.create_list(list_name) assert mailing_list.fqdn_listname in [ mailing_list.fqdn_listname for mailing_list in domain.lists ] - # Delete the domain and check that the change was persisted. + logger.info("Delete the domain and check that the change was persisted.") domain.delete() assert domain_name not in [domain.mail_host for domain in client.domains] @@ -115,7 +115,7 @@ async def test_relation_data_is_updated_correctly_when_scaling(ops_test: OpsTest units_to_remove = [unit.name for unit in ops_test.model.applications[DATABASE_APP_NAME].units] async with ops_test.fast_forward(): - # Add two more units. + logger.info("Add two more units.") await ops_test.model.applications[DATABASE_APP_NAME].add_units(2) await ops_test.model.wait_for_idle( apps=[DATABASE_APP_NAME], status="active", timeout=1500, wait_for_exact_units=4 @@ -125,7 +125,7 @@ async def test_relation_data_is_updated_correctly_when_scaling(ops_test: OpsTest ops_test.model.applications[DATABASE_APP_NAME].units[0].public_address, 2 ) - # Remove the original units. + logger.info("Remove the original units.") leader_unit = await get_leader_unit(ops_test, DATABASE_APP_NAME) await ops_test.model.applications[DATABASE_APP_NAME].destroy_units(*[ unit for unit in units_to_remove if unit != leader_unit.name @@ -138,8 +138,9 @@ async def test_relation_data_is_updated_correctly_when_scaling(ops_test: OpsTest apps=[DATABASE_APP_NAME], status="active", timeout=600, wait_for_exact_units=2 ) - # Get the updated connection data and assert it can be used - # to write and read some data properly. + logger.info( + "Get the updated connection data and assert it can be used to write and read some data properly." + ) database_unit_name = ops_test.model.applications[DATABASE_APP_NAME].units[0].name primary_connection_string = await build_connection_string( ops_test, MAILMAN3_CORE_APP_NAME, RELATION_NAME, remote_unit_name=database_unit_name @@ -152,7 +153,7 @@ async def test_relation_data_is_updated_correctly_when_scaling(ops_test: OpsTest remote_unit_name=database_unit_name, ) - # Connect to the database using the primary connection string. + logger.info("Connect to the database using the primary connection string.") with psycopg2.connect(primary_connection_string) as connection: connection.autocommit = True with connection.cursor() as cursor: @@ -166,7 +167,7 @@ async def test_relation_data_is_updated_correctly_when_scaling(ops_test: OpsTest assert data[0] == "some data" connection.close() - # Connect to the database using the replica endpoint. + logger.info("Connect to the database using the replica endpoint.") with psycopg2.connect(replica_connection_string) as connection, connection.cursor() as cursor: # Read some data. cursor.execute("SELECT data FROM test;") @@ -178,8 +179,9 @@ async def test_relation_data_is_updated_correctly_when_scaling(ops_test: OpsTest cursor.execute("DROP TABLE test;") connection.close() - # Remove the relation and test that its user was deleted - # (by checking that the connection string doesn't work anymore). + logger.info( + "Remove the relation and test that its user was deleted (by checking that the connection string doesn't work anymore)." + ) async with ops_test.fast_forward(): await ops_test.model.applications[DATABASE_APP_NAME].remove_relation( f"{DATABASE_APP_NAME}:{RELATION_NAME}", f"{MAILMAN3_CORE_APP_NAME}:{RELATION_NAME}" diff --git a/tests/spread/test_scaling_three_units_async.py/task.yaml b/tests/spread/test_scaling_three_units_async.py/task.yaml new file mode 100644 index 0000000000..cd8a7ba5aa --- /dev/null +++ b/tests/spread/test_scaling_three_units_async.py/task.yaml @@ -0,0 +1,9 @@ +summary: test_scaling_three_units.py +environment: + TEST_MODULE: ha_tests/test_scaling_three_units_async.py +execute: | + tox run -e integration -- "tests/integration/$TEST_MODULE" --model testing --alluredir="$SPREAD_TASK/allure-results" +artifacts: + - allure-results +variants: + - -juju29 diff --git a/tests/spread/test_synchronous_policy.py/task.yaml b/tests/spread/test_synchronous_policy.py/task.yaml new file mode 100644 index 0000000000..e4ce19458f --- /dev/null +++ b/tests/spread/test_synchronous_policy.py/task.yaml @@ -0,0 +1,7 @@ +summary: test_scaling.py +environment: + TEST_MODULE: ha_tests/test_synchronous_policy.py +execute: | + tox run -e integration -- "tests/integration/$TEST_MODULE" --model testing --alluredir="$SPREAD_TASK/allure-results" +artifacts: + - allure-results diff --git a/tests/unit/test_charm.py b/tests/unit/test_charm.py index 4c411d2e67..ffa465bf88 100644 --- a/tests/unit/test_charm.py +++ b/tests/unit/test_charm.py @@ -276,6 +276,9 @@ def test_on_config_changed(harness): "charm.PostgresqlOperatorCharm._validate_config_options" ) as _validate_config_options, patch("charm.PostgresqlOperatorCharm.update_config") as _update_config, + patch( + "charm.PostgresqlOperatorCharm.updated_synchronous_node_count", return_value=True + ) as _updated_synchronous_node_count, patch("relations.db.DbProvides.set_up_relation") as _set_up_relation, patch( "charm.PostgresqlOperatorCharm.enable_disable_extensions" @@ -303,6 +306,7 @@ def test_on_config_changed(harness): harness.charm.on.config_changed.emit() assert not _update_config.called _validate_config_options.side_effect = None + _updated_synchronous_node_count.assert_called_once_with() # Test after the cluster was initialised. with harness.hooks_disabled(): @@ -338,7 +342,7 @@ def test_on_config_changed(harness): _enable_disable_extensions.assert_called_once() _set_up_relation.assert_called_once() - # Test when there are established legacy relations, + # Test when there are established legacy relations, # but the charm fails to set up one of them. _enable_disable_extensions.reset_mock() _set_up_relation.reset_mock() @@ -414,6 +418,9 @@ def test_enable_disable_extensions(harness, caplog): postgresql_mock.enable_disable_extensions.side_effect = None with caplog.at_level(logging.ERROR): config = """options: + synchronous_node_count: + type: string + default: "all" plugin_citext_enable: default: false type: boolean @@ -1826,29 +1833,6 @@ def test_client_relations(harness): assert harness.charm.client_relations == [database_relation, db_relation, db_admin_relation] -def test_on_pgdata_storage_detaching(harness): - with ( - patch( - "charm.PostgresqlOperatorCharm._update_relation_endpoints" - ) as _update_relation_endpoints, - patch("charm.PostgresqlOperatorCharm.primary_endpoint", new_callable=PropertyMock), - patch("charm.Patroni.are_all_members_ready") as _are_all_members_ready, - patch("charm.Patroni.get_primary", return_value="primary") as _get_primary, - patch("charm.Patroni.switchover") as _switchover, - patch("charm.Patroni.primary_changed") as _primary_changed, - ): - # Early exit if not primary - event = Mock() - harness.charm._on_pgdata_storage_detaching(event) - assert not _are_all_members_ready.called - - _get_primary.side_effect = [harness.charm.unit.name, "primary"] - harness.charm._on_pgdata_storage_detaching(event) - _switchover.assert_called_once_with() - _primary_changed.assert_called_once_with("primary") - _update_relation_endpoints.assert_called_once_with() - - def test_add_cluster_member(harness): with ( patch("charm.PostgresqlOperatorCharm.update_config") as _update_config, @@ -2371,7 +2355,7 @@ def test_on_peer_relation_departed(harness): patch("charm.Patroni.are_all_members_ready") as _are_all_members_ready, patch("charm.PostgresqlOperatorCharm._get_ips_to_remove") as _get_ips_to_remove, patch( - "charm.PostgresqlOperatorCharm._updated_synchronous_node_count" + "charm.PostgresqlOperatorCharm.updated_synchronous_node_count" ) as _updated_synchronous_node_count, patch("charm.Patroni.remove_raft_member") as _remove_raft_member, patch("charm.PostgresqlOperatorCharm._unit_ip") as _unit_ip, @@ -2450,7 +2434,7 @@ def test_on_peer_relation_departed(harness): harness.charm._on_peer_relation_departed(event) _remove_raft_member.assert_called_once_with(mock_ip_address) event.defer.assert_called_once() - _updated_synchronous_node_count.assert_called_once_with(1) + _updated_synchronous_node_count.assert_called_once_with() _get_ips_to_remove.assert_not_called() _remove_from_members_ips.assert_not_called() _update_config.assert_not_called() @@ -2465,7 +2449,7 @@ def test_on_peer_relation_departed(harness): harness.charm._on_peer_relation_departed(event) _remove_raft_member.assert_called_once_with(mock_ip_address) event.defer.assert_called_once() - _updated_synchronous_node_count.assert_called_once_with(2) + _updated_synchronous_node_count.assert_called_once_with() _get_ips_to_remove.assert_not_called() _remove_from_members_ips.assert_not_called() _update_config.assert_not_called() @@ -2481,7 +2465,7 @@ def test_on_peer_relation_departed(harness): harness.charm._on_peer_relation_departed(event) _remove_raft_member.assert_called_once_with(mock_ip_address) event.defer.assert_not_called() - _updated_synchronous_node_count.assert_called_once_with(2) + _updated_synchronous_node_count.assert_called_once_with() _get_ips_to_remove.assert_called_once() _remove_from_members_ips.assert_not_called() _update_config.assert_not_called() @@ -2499,7 +2483,7 @@ def test_on_peer_relation_departed(harness): harness.charm._on_peer_relation_departed(event) _remove_raft_member.assert_called_once_with(mock_ip_address) event.defer.assert_called_once() - _updated_synchronous_node_count.assert_called_once_with(2) + _updated_synchronous_node_count.assert_called_once_with() _get_ips_to_remove.assert_called_once() _remove_from_members_ips.assert_not_called() _update_config.assert_not_called() @@ -2515,7 +2499,7 @@ def test_on_peer_relation_departed(harness): harness.charm._on_peer_relation_departed(event) _remove_raft_member.assert_called_once_with(mock_ip_address) event.defer.assert_not_called() - _updated_synchronous_node_count.assert_called_once_with(2) + _updated_synchronous_node_count.assert_called_once_with() _get_ips_to_remove.assert_called_once() _remove_from_members_ips.assert_has_calls([call(ips_to_remove[0]), call(ips_to_remove[1])]) assert _update_config.call_count == 2 @@ -2534,7 +2518,7 @@ def test_on_peer_relation_departed(harness): harness.charm._on_peer_relation_departed(event) _remove_raft_member.assert_called_once_with(mock_ip_address) event.defer.assert_not_called() - _updated_synchronous_node_count.assert_called_once_with(2) + _updated_synchronous_node_count.assert_called_once_with() _get_ips_to_remove.assert_called_once() _remove_from_members_ips.assert_called_once() _update_config.assert_called_once() diff --git a/tests/unit/test_cluster.py b/tests/unit/test_cluster.py index ed316e9a20..7dd1e1ddf7 100644 --- a/tests/unit/test_cluster.py +++ b/tests/unit/test_cluster.py @@ -352,7 +352,7 @@ def test_render_patroni_yml_file(peers_ips, patroni): rewind_user=REWIND_USER, rewind_password=rewind_password, version=postgresql_version, - minority_count=patroni.planned_units // 2, + synchronous_node_count=0, raft_password=raft_password, patroni_password=patroni_password, ) diff --git a/tests/unit/test_upgrade.py b/tests/unit/test_upgrade.py index 7dfbfc521b..1baac2da9b 100644 --- a/tests/unit/test_upgrade.py +++ b/tests/unit/test_upgrade.py @@ -106,6 +106,9 @@ def test_on_upgrade_granted(harness): patch("charm.Patroni.start_patroni") as _start_patroni, patch("charm.PostgresqlOperatorCharm._install_snap_packages") as _install_snap_packages, patch("charm.PostgresqlOperatorCharm.update_config") as _update_config, + patch( + "charm.PostgresqlOperatorCharm.updated_synchronous_node_count" + ) as _updated_synchronous_node_count, ): # Test when the charm fails to start Patroni. mock_event = MagicMock() @@ -174,6 +177,7 @@ def test_on_upgrade_granted(harness): _member_started.reset_mock() _cluster_members.reset_mock() mock_event.defer.reset_mock() + _updated_synchronous_node_count.reset_mock() _is_replication_healthy.return_value = True with harness.hooks_disabled(): harness.set_leader(True) @@ -184,6 +188,7 @@ def test_on_upgrade_granted(harness): _set_unit_completed.assert_called_once() _set_unit_failed.assert_not_called() _on_upgrade_changed.assert_called_once() + _updated_synchronous_node_count.assert_called_once_with() def test_pre_upgrade_check(harness): From 0ac29ed6c4714416f3159927bd12f08aa3442e6b Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Fri, 14 Feb 2025 22:11:50 +0200 Subject: [PATCH 44/74] Update dependency psutil to v7 (#772) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- poetry.lock | 37 +++++++++++++++---------------------- pyproject.toml | 2 +- 2 files changed, 16 insertions(+), 23 deletions(-) diff --git a/poetry.lock b/poetry.lock index 25c30fc155..0027e26768 100644 --- a/poetry.lock +++ b/poetry.lock @@ -1507,33 +1507,26 @@ files = [ [[package]] name = "psutil" -version = "6.1.1" -description = "Cross-platform lib for process and system monitoring in Python." +version = "7.0.0" +description = "Cross-platform lib for process and system monitoring in Python. NOTE: the syntax of this script MUST be kept compatible with Python 2.7." optional = false -python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,>=2.7" +python-versions = ">=3.6" groups = ["main"] files = [ - {file = "psutil-6.1.1-cp27-cp27m-macosx_10_9_x86_64.whl", hash = "sha256:9ccc4316f24409159897799b83004cb1e24f9819b0dcf9c0b68bdcb6cefee6a8"}, - {file = "psutil-6.1.1-cp27-cp27m-manylinux2010_i686.whl", hash = "sha256:ca9609c77ea3b8481ab005da74ed894035936223422dc591d6772b147421f777"}, - {file = "psutil-6.1.1-cp27-cp27m-manylinux2010_x86_64.whl", hash = "sha256:8df0178ba8a9e5bc84fed9cfa61d54601b371fbec5c8eebad27575f1e105c0d4"}, - {file = "psutil-6.1.1-cp27-cp27mu-manylinux2010_i686.whl", hash = "sha256:1924e659d6c19c647e763e78670a05dbb7feaf44a0e9c94bf9e14dfc6ba50468"}, - {file = "psutil-6.1.1-cp27-cp27mu-manylinux2010_x86_64.whl", hash = "sha256:018aeae2af92d943fdf1da6b58665124897cfc94faa2ca92098838f83e1b1bca"}, - {file = "psutil-6.1.1-cp27-none-win32.whl", hash = "sha256:6d4281f5bbca041e2292be3380ec56a9413b790579b8e593b1784499d0005dac"}, - {file = "psutil-6.1.1-cp27-none-win_amd64.whl", hash = "sha256:c777eb75bb33c47377c9af68f30e9f11bc78e0f07fbf907be4a5d70b2fe5f030"}, - {file = "psutil-6.1.1-cp36-abi3-macosx_10_9_x86_64.whl", hash = "sha256:fc0ed7fe2231a444fc219b9c42d0376e0a9a1a72f16c5cfa0f68d19f1a0663e8"}, - {file = "psutil-6.1.1-cp36-abi3-macosx_11_0_arm64.whl", hash = "sha256:0bdd4eab935276290ad3cb718e9809412895ca6b5b334f5a9111ee6d9aff9377"}, - {file = "psutil-6.1.1-cp36-abi3-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b6e06c20c05fe95a3d7302d74e7097756d4ba1247975ad6905441ae1b5b66003"}, - {file = "psutil-6.1.1-cp36-abi3-manylinux_2_12_x86_64.manylinux2010_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:97f7cb9921fbec4904f522d972f0c0e1f4fabbdd4e0287813b21215074a0f160"}, - {file = "psutil-6.1.1-cp36-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:33431e84fee02bc84ea36d9e2c4a6d395d479c9dd9bba2376c1f6ee8f3a4e0b3"}, - {file = "psutil-6.1.1-cp36-cp36m-win32.whl", hash = "sha256:384636b1a64b47814437d1173be1427a7c83681b17a450bfc309a1953e329603"}, - {file = "psutil-6.1.1-cp36-cp36m-win_amd64.whl", hash = "sha256:8be07491f6ebe1a693f17d4f11e69d0dc1811fa082736500f649f79df7735303"}, - {file = "psutil-6.1.1-cp37-abi3-win32.whl", hash = "sha256:eaa912e0b11848c4d9279a93d7e2783df352b082f40111e078388701fd479e53"}, - {file = "psutil-6.1.1-cp37-abi3-win_amd64.whl", hash = "sha256:f35cfccb065fff93529d2afb4a2e89e363fe63ca1e4a5da22b603a85833c2649"}, - {file = "psutil-6.1.1.tar.gz", hash = "sha256:cf8496728c18f2d0b45198f06895be52f36611711746b7f30c464b422b50e2f5"}, + {file = "psutil-7.0.0-cp36-abi3-macosx_10_9_x86_64.whl", hash = "sha256:101d71dc322e3cffd7cea0650b09b3d08b8e7c4109dd6809fe452dfd00e58b25"}, + {file = "psutil-7.0.0-cp36-abi3-macosx_11_0_arm64.whl", hash = "sha256:39db632f6bb862eeccf56660871433e111b6ea58f2caea825571951d4b6aa3da"}, + {file = "psutil-7.0.0-cp36-abi3-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1fcee592b4c6f146991ca55919ea3d1f8926497a713ed7faaf8225e174581e91"}, + {file = "psutil-7.0.0-cp36-abi3-manylinux_2_12_x86_64.manylinux2010_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4b1388a4f6875d7e2aff5c4ca1cc16c545ed41dd8bb596cefea80111db353a34"}, + {file = "psutil-7.0.0-cp36-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a5f098451abc2828f7dc6b58d44b532b22f2088f4999a937557b603ce72b1993"}, + {file = "psutil-7.0.0-cp36-cp36m-win32.whl", hash = "sha256:84df4eb63e16849689f76b1ffcb36db7b8de703d1bc1fe41773db487621b6c17"}, + {file = "psutil-7.0.0-cp36-cp36m-win_amd64.whl", hash = "sha256:1e744154a6580bc968a0195fd25e80432d3afec619daf145b9e5ba16cc1d688e"}, + {file = "psutil-7.0.0-cp37-abi3-win32.whl", hash = "sha256:ba3fcef7523064a6c9da440fc4d6bd07da93ac726b5733c29027d7dc95b39d99"}, + {file = "psutil-7.0.0-cp37-abi3-win_amd64.whl", hash = "sha256:4cf3d4eb1aa9b348dec30105c55cd9b7d4629285735a102beb4441e38db90553"}, + {file = "psutil-7.0.0.tar.gz", hash = "sha256:7be9c3eba38beccb6495ea33afd982a44074b78f28c434a1f51cc07fd315c456"}, ] [package.extras] -dev = ["abi3audit", "black", "check-manifest", "coverage", "packaging", "pylint", "pyperf", "pypinfo", "pytest-cov", "requests", "rstcheck", "ruff", "sphinx", "sphinx_rtd_theme", "toml-sort", "twine", "virtualenv", "vulture", "wheel"] +dev = ["abi3audit", "black (==24.10.0)", "check-manifest", "coverage", "packaging", "pylint", "pyperf", "pypinfo", "pytest", "pytest-cov", "pytest-xdist", "requests", "rstcheck", "ruff", "setuptools", "sphinx", "sphinx_rtd_theme", "toml-sort", "twine", "virtualenv", "vulture", "wheel"] test = ["pytest", "pytest-xdist", "setuptools"] [[package]] @@ -2635,4 +2628,4 @@ type = ["pytest-mypy"] [metadata] lock-version = "2.1" python-versions = "^3.10" -content-hash = "352dde8ecb150d3e5ef5ec0f8d75dbbca893a3a08ce79216f2428399bbab08d0" +content-hash = "69624ae0414471d65adc54e447287151f49a62de192bc317c279a062cd483268" diff --git a/pyproject.toml b/pyproject.toml index 3d4e150979..01067270d8 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -16,7 +16,7 @@ psycopg2 = "^2.9.10" pydantic = "^1.10.21" jinja2 = "^3.1.5" pysyncobj = "^0.3.13" -psutil = "^6.1.1" +psutil = "^7.0.0" [tool.poetry.group.charm-libs.dependencies] # data_platform_libs/v0/data_interfaces.py From 678f92664a151ad37b457ba8efc64f9b7e7dc33e Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Sat, 15 Feb 2025 11:27:33 -0300 Subject: [PATCH 45/74] Update dependency cryptography to v44.0.1 [SECURITY] (#764) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- poetry.lock | 62 ++++++++++++++++++++++++++++------------------------- 1 file changed, 33 insertions(+), 29 deletions(-) diff --git a/poetry.lock b/poetry.lock index 0027e26768..497afa866c 100644 --- a/poetry.lock +++ b/poetry.lock @@ -549,39 +549,43 @@ toml = ["tomli"] [[package]] name = "cryptography" -version = "44.0.0" +version = "44.0.1" description = "cryptography is a package which provides cryptographic recipes and primitives to Python developers." optional = false python-versions = "!=3.9.0,!=3.9.1,>=3.7" groups = ["charm-libs", "integration"] files = [ - {file = "cryptography-44.0.0-cp37-abi3-macosx_10_9_universal2.whl", hash = "sha256:84111ad4ff3f6253820e6d3e58be2cc2a00adb29335d4cacb5ab4d4d34f2a123"}, - {file = "cryptography-44.0.0-cp37-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b15492a11f9e1b62ba9d73c210e2416724633167de94607ec6069ef724fad092"}, - {file = "cryptography-44.0.0-cp37-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:831c3c4d0774e488fdc83a1923b49b9957d33287de923d58ebd3cec47a0ae43f"}, - {file = "cryptography-44.0.0-cp37-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:761817a3377ef15ac23cd7834715081791d4ec77f9297ee694ca1ee9c2c7e5eb"}, - {file = "cryptography-44.0.0-cp37-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:3c672a53c0fb4725a29c303be906d3c1fa99c32f58abe008a82705f9ee96f40b"}, - {file = "cryptography-44.0.0-cp37-abi3-manylinux_2_34_aarch64.whl", hash = "sha256:4ac4c9f37eba52cb6fbeaf5b59c152ea976726b865bd4cf87883a7e7006cc543"}, - {file = "cryptography-44.0.0-cp37-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:ed3534eb1090483c96178fcb0f8893719d96d5274dfde98aa6add34614e97c8e"}, - {file = "cryptography-44.0.0-cp37-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:f3f6fdfa89ee2d9d496e2c087cebef9d4fcbb0ad63c40e821b39f74bf48d9c5e"}, - {file = "cryptography-44.0.0-cp37-abi3-win32.whl", hash = "sha256:eb33480f1bad5b78233b0ad3e1b0be21e8ef1da745d8d2aecbb20671658b9053"}, - {file = "cryptography-44.0.0-cp37-abi3-win_amd64.whl", hash = "sha256:abc998e0c0eee3c8a1904221d3f67dcfa76422b23620173e28c11d3e626c21bd"}, - {file = "cryptography-44.0.0-cp39-abi3-macosx_10_9_universal2.whl", hash = "sha256:660cb7312a08bc38be15b696462fa7cc7cd85c3ed9c576e81f4dc4d8b2b31591"}, - {file = "cryptography-44.0.0-cp39-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1923cb251c04be85eec9fda837661c67c1049063305d6be5721643c22dd4e2b7"}, - {file = "cryptography-44.0.0-cp39-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:404fdc66ee5f83a1388be54300ae978b2efd538018de18556dde92575e05defc"}, - {file = "cryptography-44.0.0-cp39-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:c5eb858beed7835e5ad1faba59e865109f3e52b3783b9ac21e7e47dc5554e289"}, - {file = "cryptography-44.0.0-cp39-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:f53c2c87e0fb4b0c00fa9571082a057e37690a8f12233306161c8f4b819960b7"}, - {file = "cryptography-44.0.0-cp39-abi3-manylinux_2_34_aarch64.whl", hash = "sha256:9e6fc8a08e116fb7c7dd1f040074c9d7b51d74a8ea40d4df2fc7aa08b76b9e6c"}, - {file = "cryptography-44.0.0-cp39-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:d2436114e46b36d00f8b72ff57e598978b37399d2786fd39793c36c6d5cb1c64"}, - {file = "cryptography-44.0.0-cp39-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:a01956ddfa0a6790d594f5b34fc1bfa6098aca434696a03cfdbe469b8ed79285"}, - {file = "cryptography-44.0.0-cp39-abi3-win32.whl", hash = "sha256:eca27345e1214d1b9f9490d200f9db5a874479be914199194e746c893788d417"}, - {file = "cryptography-44.0.0-cp39-abi3-win_amd64.whl", hash = "sha256:708ee5f1bafe76d041b53a4f95eb28cdeb8d18da17e597d46d7833ee59b97ede"}, - {file = "cryptography-44.0.0-pp310-pypy310_pp73-macosx_10_9_x86_64.whl", hash = "sha256:37d76e6863da3774cd9db5b409a9ecfd2c71c981c38788d3fcfaf177f447b731"}, - {file = "cryptography-44.0.0-pp310-pypy310_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:f677e1268c4e23420c3acade68fac427fffcb8d19d7df95ed7ad17cdef8404f4"}, - {file = "cryptography-44.0.0-pp310-pypy310_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:f5e7cb1e5e56ca0933b4873c0220a78b773b24d40d186b6738080b73d3d0a756"}, - {file = "cryptography-44.0.0-pp310-pypy310_pp73-manylinux_2_34_aarch64.whl", hash = "sha256:8b3e6eae66cf54701ee7d9c83c30ac0a1e3fa17be486033000f2a73a12ab507c"}, - {file = "cryptography-44.0.0-pp310-pypy310_pp73-manylinux_2_34_x86_64.whl", hash = "sha256:be4ce505894d15d5c5037167ffb7f0ae90b7be6f2a98f9a5c3442395501c32fa"}, - {file = "cryptography-44.0.0-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:62901fb618f74d7d81bf408c8719e9ec14d863086efe4185afd07c352aee1d2c"}, - {file = "cryptography-44.0.0.tar.gz", hash = "sha256:cd4e834f340b4293430701e772ec543b0fbe6c2dea510a5286fe0acabe153a02"}, + {file = "cryptography-44.0.1-cp37-abi3-macosx_10_9_universal2.whl", hash = "sha256:bf688f615c29bfe9dfc44312ca470989279f0e94bb9f631f85e3459af8efc009"}, + {file = "cryptography-44.0.1-cp37-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:dd7c7e2d71d908dc0f8d2027e1604102140d84b155e658c20e8ad1304317691f"}, + {file = "cryptography-44.0.1-cp37-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:887143b9ff6bad2b7570da75a7fe8bbf5f65276365ac259a5d2d5147a73775f2"}, + {file = "cryptography-44.0.1-cp37-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:322eb03ecc62784536bc173f1483e76747aafeb69c8728df48537eb431cd1911"}, + {file = "cryptography-44.0.1-cp37-abi3-manylinux_2_28_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:21377472ca4ada2906bc313168c9dc7b1d7ca417b63c1c3011d0c74b7de9ae69"}, + {file = "cryptography-44.0.1-cp37-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:df978682c1504fc93b3209de21aeabf2375cb1571d4e61907b3e7a2540e83026"}, + {file = "cryptography-44.0.1-cp37-abi3-manylinux_2_34_aarch64.whl", hash = "sha256:eb3889330f2a4a148abead555399ec9a32b13b7c8ba969b72d8e500eb7ef84cd"}, + {file = "cryptography-44.0.1-cp37-abi3-manylinux_2_34_x86_64.whl", hash = "sha256:8e6a85a93d0642bd774460a86513c5d9d80b5c002ca9693e63f6e540f1815ed0"}, + {file = "cryptography-44.0.1-cp37-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:6f76fdd6fd048576a04c5210d53aa04ca34d2ed63336d4abd306d0cbe298fddf"}, + {file = "cryptography-44.0.1-cp37-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:6c8acf6f3d1f47acb2248ec3ea261171a671f3d9428e34ad0357148d492c7864"}, + {file = "cryptography-44.0.1-cp37-abi3-win32.whl", hash = "sha256:24979e9f2040c953a94bf3c6782e67795a4c260734e5264dceea65c8f4bae64a"}, + {file = "cryptography-44.0.1-cp37-abi3-win_amd64.whl", hash = "sha256:fd0ee90072861e276b0ff08bd627abec29e32a53b2be44e41dbcdf87cbee2b00"}, + {file = "cryptography-44.0.1-cp39-abi3-macosx_10_9_universal2.whl", hash = "sha256:a2d8a7045e1ab9b9f803f0d9531ead85f90c5f2859e653b61497228b18452008"}, + {file = "cryptography-44.0.1-cp39-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b8272f257cf1cbd3f2e120f14c68bff2b6bdfcc157fafdee84a1b795efd72862"}, + {file = "cryptography-44.0.1-cp39-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1e8d181e90a777b63f3f0caa836844a1182f1f265687fac2115fcf245f5fbec3"}, + {file = "cryptography-44.0.1-cp39-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:436df4f203482f41aad60ed1813811ac4ab102765ecae7a2bbb1dbb66dcff5a7"}, + {file = "cryptography-44.0.1-cp39-abi3-manylinux_2_28_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:4f422e8c6a28cf8b7f883eb790695d6d45b0c385a2583073f3cec434cc705e1a"}, + {file = "cryptography-44.0.1-cp39-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:72198e2b5925155497a5a3e8c216c7fb3e64c16ccee11f0e7da272fa93b35c4c"}, + {file = "cryptography-44.0.1-cp39-abi3-manylinux_2_34_aarch64.whl", hash = "sha256:2a46a89ad3e6176223b632056f321bc7de36b9f9b93b2cc1cccf935a3849dc62"}, + {file = "cryptography-44.0.1-cp39-abi3-manylinux_2_34_x86_64.whl", hash = "sha256:53f23339864b617a3dfc2b0ac8d5c432625c80014c25caac9082314e9de56f41"}, + {file = "cryptography-44.0.1-cp39-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:888fcc3fce0c888785a4876ca55f9f43787f4c5c1cc1e2e0da71ad481ff82c5b"}, + {file = "cryptography-44.0.1-cp39-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:00918d859aa4e57db8299607086f793fa7813ae2ff5a4637e318a25ef82730f7"}, + {file = "cryptography-44.0.1-cp39-abi3-win32.whl", hash = "sha256:9b336599e2cb77b1008cb2ac264b290803ec5e8e89d618a5e978ff5eb6f715d9"}, + {file = "cryptography-44.0.1-cp39-abi3-win_amd64.whl", hash = "sha256:e403f7f766ded778ecdb790da786b418a9f2394f36e8cc8b796cc056ab05f44f"}, + {file = "cryptography-44.0.1-pp310-pypy310_pp73-macosx_10_9_x86_64.whl", hash = "sha256:1f9a92144fa0c877117e9748c74501bea842f93d21ee00b0cf922846d9d0b183"}, + {file = "cryptography-44.0.1-pp310-pypy310_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:610a83540765a8d8ce0f351ce42e26e53e1f774a6efb71eb1b41eb01d01c3d12"}, + {file = "cryptography-44.0.1-pp310-pypy310_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:5fed5cd6102bb4eb843e3315d2bf25fede494509bddadb81e03a859c1bc17b83"}, + {file = "cryptography-44.0.1-pp310-pypy310_pp73-manylinux_2_34_aarch64.whl", hash = "sha256:f4daefc971c2d1f82f03097dc6f216744a6cd2ac0f04c68fb935ea2ba2a0d420"}, + {file = "cryptography-44.0.1-pp310-pypy310_pp73-manylinux_2_34_x86_64.whl", hash = "sha256:94f99f2b943b354a5b6307d7e8d19f5c423a794462bde2bf310c770ba052b1c4"}, + {file = "cryptography-44.0.1-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:d9c5b9f698a83c8bd71e0f4d3f9f839ef244798e5ffe96febfa9714717db7af7"}, + {file = "cryptography-44.0.1.tar.gz", hash = "sha256:f51f5705ab27898afda1aaa430f34ad90dc117421057782022edf0600bec5f14"}, ] [package.dependencies] @@ -594,7 +598,7 @@ nox = ["nox (>=2024.4.15)", "nox[uv] (>=2024.3.2)"] pep8test = ["check-sdist", "click (>=8.0.1)", "mypy (>=1.4)", "ruff (>=0.3.6)"] sdist = ["build (>=1.0.0)"] ssh = ["bcrypt (>=3.1.5)"] -test = ["certifi (>=2024)", "cryptography-vectors (==44.0.0)", "pretend (>=0.7)", "pytest (>=7.4.0)", "pytest-benchmark (>=4.0)", "pytest-cov (>=2.10.1)", "pytest-xdist (>=3.5.0)"] +test = ["certifi (>=2024)", "cryptography-vectors (==44.0.1)", "pretend (>=0.7)", "pytest (>=7.4.0)", "pytest-benchmark (>=4.0)", "pytest-cov (>=2.10.1)", "pytest-xdist (>=3.5.0)"] test-randomorder = ["pytest-randomly"] [[package]] From 50e5dd2a04e125a7dccab5b34a933bcdb6b2fd87 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Tue, 18 Feb 2025 11:47:29 +0000 Subject: [PATCH 46/74] Update canonical/data-platform-workflows action to v30.0.2 (#765) * Update canonical/data-platform-workflows action to v30.0.2 * Update promote.yaml --------- Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> Co-authored-by: Carl Csaposs --- .github/workflows/check_pr.yaml | 2 +- .github/workflows/ci.yaml | 4 ++-- .github/workflows/promote.yaml | 4 ++-- .github/workflows/release.yaml | 2 +- .github/workflows/sync_docs.yaml | 2 +- 5 files changed, 7 insertions(+), 7 deletions(-) diff --git a/.github/workflows/check_pr.yaml b/.github/workflows/check_pr.yaml index e989be903d..5389215315 100644 --- a/.github/workflows/check_pr.yaml +++ b/.github/workflows/check_pr.yaml @@ -15,4 +15,4 @@ on: jobs: check-pr: name: Check pull request - uses: canonical/data-platform-workflows/.github/workflows/check_charm_pr.yaml@v30.0.0 + uses: canonical/data-platform-workflows/.github/workflows/check_charm_pr.yaml@v30.0.2 diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index 7a286d1ba2..70c38dcb6d 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -27,7 +27,7 @@ on: jobs: lint: name: Lint - uses: canonical/data-platform-workflows/.github/workflows/lint.yaml@v30.0.0 + uses: canonical/data-platform-workflows/.github/workflows/lint.yaml@v30.0.2 unit-test: name: Unit test charm @@ -49,7 +49,7 @@ jobs: build: name: Build charm - uses: canonical/data-platform-workflows/.github/workflows/build_charm.yaml@v30.0.0 + uses: canonical/data-platform-workflows/.github/workflows/build_charm.yaml@v30.0.2 integration-test: name: Integration test charm diff --git a/.github/workflows/promote.yaml b/.github/workflows/promote.yaml index 59226a484a..bbbda02abd 100644 --- a/.github/workflows/promote.yaml +++ b/.github/workflows/promote.yaml @@ -25,7 +25,7 @@ on: jobs: promote: name: Promote charm - uses: canonical/data-platform-workflows/.github/workflows/_promote_charm.yaml@v30.0.0 + uses: canonical/data-platform-workflows/.github/workflows/_promote_charm.yaml@v30.0.2 with: track: '14' from-risk: ${{ inputs.from-risk }} @@ -33,4 +33,4 @@ jobs: secrets: charmhub-token: ${{ secrets.CHARMHUB_TOKEN }} permissions: - contents: write # Needed to update git tags + contents: write # Needed to edit GitHub releases diff --git a/.github/workflows/release.yaml b/.github/workflows/release.yaml index 59bf9ca91b..7e1a01dc5a 100644 --- a/.github/workflows/release.yaml +++ b/.github/workflows/release.yaml @@ -27,7 +27,7 @@ jobs: name: Release charm needs: - ci-tests - uses: canonical/data-platform-workflows/.github/workflows/release_charm.yaml@v30.0.0 + uses: canonical/data-platform-workflows/.github/workflows/release_charm.yaml@v30.0.2 with: channel: 14/edge artifact-prefix: ${{ needs.ci-tests.outputs.artifact-prefix }} diff --git a/.github/workflows/sync_docs.yaml b/.github/workflows/sync_docs.yaml index ec2b7b82ef..77119ab239 100644 --- a/.github/workflows/sync_docs.yaml +++ b/.github/workflows/sync_docs.yaml @@ -10,7 +10,7 @@ on: jobs: sync-docs: name: Sync docs from Discourse - uses: canonical/data-platform-workflows/.github/workflows/sync_docs.yaml@v30.0.0 + uses: canonical/data-platform-workflows/.github/workflows/sync_docs.yaml@v30.0.2 with: reviewers: a-velasco,izmalk permissions: From 0057d879a60a3937bbc3b083311a918fb809065b Mon Sep 17 00:00:00 2001 From: Dragomir Penev <6687393+dragomirp@users.noreply.github.com> Date: Wed, 19 Feb 2025 13:19:41 +0200 Subject: [PATCH 47/74] [DPE-6323] Handle missing stanza output (#727) * Handle missing stanza output * Update libs * Unit tests --- .../tempo_coordinator_k8s/v0/charm_tracing.py | 123 ++++++++++++------ src/backups.py | 5 +- tests/unit/test_backups.py | 13 ++ tests/unit/test_charm.py | 3 +- 4 files changed, 101 insertions(+), 43 deletions(-) diff --git a/lib/charms/tempo_coordinator_k8s/v0/charm_tracing.py b/lib/charms/tempo_coordinator_k8s/v0/charm_tracing.py index a9b6deeb64..ebf80ede2e 100644 --- a/lib/charms/tempo_coordinator_k8s/v0/charm_tracing.py +++ b/lib/charms/tempo_coordinator_k8s/v0/charm_tracing.py @@ -19,15 +19,19 @@ ```python # import the necessary charm libs -from charms.tempo_coordinator_k8s.v0.tracing import TracingEndpointRequirer, charm_tracing_config +from charms.tempo_coordinator_k8s.v0.tracing import ( + TracingEndpointRequirer, + charm_tracing_config, +) from charms.tempo_coordinator_k8s.v0.charm_tracing import charm_tracing + # decorate your charm class with charm_tracing: @charm_tracing( # forward-declare the instance attributes that the instrumentor will look up to obtain the # tempo endpoint and server certificate tracing_endpoint="tracing_endpoint", - server_cert="server_cert" + server_cert="server_cert", ) class MyCharm(CharmBase): _path_to_cert = "/path/to/cert.crt" @@ -37,10 +41,12 @@ class MyCharm(CharmBase): # If you do support TLS, you'll need to make sure that the server cert is copied to this location # and kept up to date so the instrumentor can use it. - def __init__(self, ...): - ... - self.tracing = TracingEndpointRequirer(self, ...) - self.tracing_endpoint, self.server_cert = charm_tracing_config(self.tracing, self._path_to_cert) + def __init__(self, framework): + # ... + self.tracing = TracingEndpointRequirer(self) + self.tracing_endpoint, self.server_cert = charm_tracing_config( + self.tracing, self._path_to_cert + ) ``` # Detailed usage @@ -226,12 +232,6 @@ def my_tracing_endpoint(self) -> Optional[str]: 3) If you were passing a certificate (str) using `server_cert`, you need to change it to provide an *absolute* path to the certificate file instead. """ -import typing - -from opentelemetry.exporter.otlp.proto.common._internal.trace_encoder import ( - encode_spans, -) -from opentelemetry.sdk.trace.export.in_memory_span_exporter import InMemorySpanExporter def _remove_stale_otel_sdk_packages(): @@ -286,12 +286,15 @@ def _remove_stale_otel_sdk_packages(): # apply hacky patch to remove stale opentelemetry sdk packages on upgrade-charm. # it could be trouble if someone ever decides to implement their own tracer parallel to # ours and before the charm has inited. We assume they won't. +# !!IMPORTANT!! keep all otlp imports UNDER this call. _remove_stale_otel_sdk_packages() import functools import inspect import logging import os +import typing +from collections import deque from contextlib import contextmanager from contextvars import Context, ContextVar, copy_context from pathlib import Path @@ -310,6 +313,9 @@ def _remove_stale_otel_sdk_packages(): import opentelemetry import ops +from opentelemetry.exporter.otlp.proto.common._internal.trace_encoder import ( + encode_spans, +) from opentelemetry.exporter.otlp.proto.http.trace_exporter import OTLPSpanExporter from opentelemetry.sdk.resources import Resource from opentelemetry.sdk.trace import ReadableSpan, Span, TracerProvider @@ -318,6 +324,7 @@ def _remove_stale_otel_sdk_packages(): SpanExporter, SpanExportResult, ) +from opentelemetry.sdk.trace.export.in_memory_span_exporter import InMemorySpanExporter from opentelemetry.trace import INVALID_SPAN, Tracer from opentelemetry.trace import get_current_span as otlp_get_current_span from opentelemetry.trace import ( @@ -338,7 +345,7 @@ def _remove_stale_otel_sdk_packages(): # Increment this PATCH version before using `charmcraft publish-lib` or reset # to 0 if you are raising the major API version -LIBPATCH = 5 +LIBPATCH = 6 PYDEPS = ["opentelemetry-exporter-otlp-proto-http==1.21.0"] @@ -366,7 +373,9 @@ def _remove_stale_otel_sdk_packages(): BUFFER_DEFAULT_MAX_EVENT_HISTORY_LENGTH = 100 _MiB_TO_B = 2**20 # megabyte to byte conversion rate _OTLP_SPAN_EXPORTER_TIMEOUT = 1 -"""Timeout in seconds that the OTLP span exporter has to push traces to the backend.""" + + +# Timeout in seconds that the OTLP span exporter has to push traces to the backend. class _Buffer: @@ -398,45 +407,75 @@ def save(self, spans: typing.Sequence[ReadableSpan]): if self._max_event_history_length < 1: dev_logger.debug("buffer disabled: max history length < 1") return - - current_history_length = len(self.load()) - new_history_length = current_history_length + len(spans) - if (diff := self._max_event_history_length - new_history_length) < 0: - self.drop(diff) self._save(spans) def _serialize(self, spans: Sequence[ReadableSpan]) -> bytes: # encode because otherwise we can't json-dump them return encode_spans(spans).SerializeToString() + def _prune(self, queue: Sequence[bytes]) -> Sequence[bytes]: + """Prune the queue until it fits in our constraints.""" + n_dropped_spans = 0 + # drop older events if we are past the max history length + overflow = len(queue) - self._max_event_history_length + if overflow > 0: + n_dropped_spans += overflow + logger.warning( + f"charm tracing buffer exceeds max history length ({self._max_event_history_length} events)" + ) + + new_spans = deque(queue[-self._max_event_history_length :]) + + # drop older events if the buffer is too big; all units are bytes + logged_drop = False + target_size = self._max_buffer_size_mib * _MiB_TO_B + current_size = sum(len(span) for span in new_spans) + while current_size > target_size: + current_size -= len(new_spans.popleft()) + n_dropped_spans += 1 + + # only do this once + if not logged_drop: + logger.warning( + f"charm tracing buffer exceeds size limit ({self._max_buffer_size_mib}MiB)." + ) + logged_drop = True + + if n_dropped_spans > 0: + dev_logger.debug( + f"charm tracing buffer overflow: dropped {n_dropped_spans} older spans. " + f"Please increase the buffer limits, or ensure the spans can be flushed." + ) + return new_spans + def _save(self, spans: Sequence[ReadableSpan], replace: bool = False): dev_logger.debug(f"saving {len(spans)} new spans to buffer") old = [] if replace else self.load() - new = self._serialize(spans) + queue = old + [self._serialize(spans)] + new_buffer = self._prune(queue) - try: - # if the buffer exceeds the size limit, we start dropping old spans until it does - - while len((new + self._SPANSEP.join(old))) > (self._max_buffer_size_mib * _MiB_TO_B): - if not old: - # if we've already dropped all spans and still we can't get under the - # size limit, we can't save this span - logger.error( - f"span exceeds total buffer size limit ({self._max_buffer_size_mib}MiB); " - f"buffering FAILED" - ) - return - - old = old[1:] - logger.warning( - f"buffer size exceeds {self._max_buffer_size_mib}MiB; dropping older spans... " - f"Please increase the buffer size, disable buffering, or ensure the spans can be flushed." - ) + if queue and not new_buffer: + # this means that, given our constraints, we are pruning so much that there are no events left. + logger.error( + "No charm events could be buffered into charm traces buffer. Please increase the memory or history size limits." + ) + return - self._db_file.write_bytes(new + self._SPANSEP.join(old)) + try: + self._write(new_buffer) except Exception: logger.exception("error buffering spans") + def _write(self, spans: Sequence[bytes]): + """Write the spans to the db file.""" + # ensure the destination folder exists + db_file_dir = self._db_file.parent + if not db_file_dir.exists(): + dev_logger.info(f"creating buffer dir: {db_file_dir}") + db_file_dir.mkdir(parents=True) + + self._db_file.write_bytes(self._SPANSEP.join(spans)) + def load(self) -> List[bytes]: """Load currently buffered spans from the cache file. @@ -461,8 +500,10 @@ def drop(self, n_spans: Optional[int] = None): else: dev_logger.debug("emptying buffer") new = [] - - self._db_file.write_bytes(self._SPANSEP.join(new)) + try: + self._write(new) + except Exception: + logger.exception("error writing charm traces buffer") def flush(self) -> Optional[bool]: """Export all buffered spans to the given exporter, then clear the buffer. diff --git a/src/backups.py b/src/backups.py index 214fe7f082..903ba028f6 100644 --- a/src/backups.py +++ b/src/backups.py @@ -212,7 +212,10 @@ def can_use_s3_repository(self) -> tuple[bool, str | None]: for line in system_identifier_from_instance.splitlines() if "Database system identifier" in line ).split(" ")[-1] - system_identifier_from_stanza = str(stanza.get("db")[0]["system-id"]) + stanza_dbs = stanza.get("db") + system_identifier_from_stanza = ( + str(stanza_dbs[0]["system-id"]) if len(stanza_dbs) else None + ) if system_identifier_from_instance != system_identifier_from_stanza: logger.debug( f"can_use_s3_repository: incompatible system identifier s3={system_identifier_from_stanza}, local={system_identifier_from_instance}" diff --git a/tests/unit/test_backups.py b/tests/unit/test_backups.py index f7931348a9..3df0653190 100644 --- a/tests/unit/test_backups.py +++ b/tests/unit/test_backups.py @@ -297,6 +297,19 @@ def test_can_use_s3_repository(harness): ] assert harness.charm.backup.can_use_s3_repository() == (True, None) + # Empty db + _execute_command.side_effect = None + _execute_command.return_value = (1, "", "") + pgbackrest_info_other_cluster_name_backup_output = ( + 0, + f'[{{"db": [], "name": "another-model.{harness.charm.cluster_name}"}}]', + "", + ) + assert harness.charm.backup.can_use_s3_repository() == ( + False, + FAILED_TO_INITIALIZE_STANZA_ERROR_MESSAGE, + ) + def test_construct_endpoint(harness): # Test with an AWS endpoint without region. diff --git a/tests/unit/test_charm.py b/tests/unit/test_charm.py index ffa465bf88..7de1a502cf 100644 --- a/tests/unit/test_charm.py +++ b/tests/unit/test_charm.py @@ -333,7 +333,8 @@ def test_on_config_changed(harness): harness.charm.on.config_changed.emit() _enable_disable_extensions.assert_called_once() _set_up_relation.assert_called_once() - harness.remove_relation(db_relation_id) + with harness.hooks_disabled(): + harness.remove_relation(db_relation_id) _enable_disable_extensions.reset_mock() _set_up_relation.reset_mock() From d65d5310bfc2e9efa61bd67676fa66379b073da4 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Wed, 19 Feb 2025 09:07:13 -0300 Subject: [PATCH 48/74] Update canonical/has-signed-canonical-cla action to v2 (#773) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- .github/workflows/cla-check.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/cla-check.yml b/.github/workflows/cla-check.yml index f0590d5b65..2567517472 100644 --- a/.github/workflows/cla-check.yml +++ b/.github/workflows/cla-check.yml @@ -9,4 +9,4 @@ jobs: runs-on: ubuntu-24.04 steps: - name: Check if Canonical's Contributor License Agreement has been signed - uses: canonical/has-signed-canonical-cla@v1 + uses: canonical/has-signed-canonical-cla@v2 From d96bfcb911721697b4f79b9e5c5875e7395175b0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sinclert=20P=C3=A9rez?= Date: Wed, 19 Feb 2025 14:57:24 +0100 Subject: [PATCH 49/74] [MISC] Define charm constants (#774) --- lib/charms/postgresql_k8s/v0/postgresql.py | 17 +++++++++------ src/charm.py | 3 ++- src/constants.py | 1 + tests/integration/helpers.py | 4 +++- .../new_relations/test_new_relations_1.py | 21 ++++++++++++------- .../new_relations/test_relations_coherence.py | 8 ++++--- 6 files changed, 36 insertions(+), 18 deletions(-) diff --git a/lib/charms/postgresql_k8s/v0/postgresql.py b/lib/charms/postgresql_k8s/v0/postgresql.py index bdfef9afbb..e395d6892f 100644 --- a/lib/charms/postgresql_k8s/v0/postgresql.py +++ b/lib/charms/postgresql_k8s/v0/postgresql.py @@ -35,7 +35,10 @@ # Increment this PATCH version before using `charmcraft publish-lib` or reset # to 0 if you are raising the major API version -LIBPATCH = 42 +LIBPATCH = 43 + +# Groups to distinguish database permissions +PERMISSIONS_GROUP_ADMIN = "admin" INVALID_EXTRA_USER_ROLE_BLOCKING_MESSAGE = "invalid role(s) for extra user roles" @@ -187,7 +190,7 @@ def create_database( Identifier(database) ) ) - for user_to_grant_access in [user, "admin", *self.system_users]: + for user_to_grant_access in [user, PERMISSIONS_GROUP_ADMIN, *self.system_users]: cursor.execute( SQL("GRANT ALL PRIVILEGES ON DATABASE {} TO {};").format( Identifier(database), Identifier(user_to_grant_access) @@ -236,15 +239,17 @@ def create_user( roles = privileges = None if extra_user_roles: extra_user_roles = tuple(extra_user_roles.lower().split(",")) - admin_role = "admin" in extra_user_roles + admin_role = PERMISSIONS_GROUP_ADMIN in extra_user_roles valid_privileges, valid_roles = self.list_valid_privileges_and_roles() roles = [ - role for role in extra_user_roles if role in valid_roles and role != "admin" + role + for role in extra_user_roles + if role in valid_roles and role != PERMISSIONS_GROUP_ADMIN ] privileges = { extra_user_role for extra_user_role in extra_user_roles - if extra_user_role not in roles and extra_user_role != "admin" + if extra_user_role not in roles and extra_user_role != PERMISSIONS_GROUP_ADMIN } invalid_privileges = [ privilege for privilege in privileges if privilege not in valid_privileges @@ -566,7 +571,7 @@ def set_up_database(self) -> None: ) ) self.create_user( - "admin", + PERMISSIONS_GROUP_ADMIN, extra_user_roles="pg_read_all_data,pg_write_all_data", ) cursor.execute("GRANT CONNECT ON DATABASE postgres TO admin;") diff --git a/src/charm.py b/src/charm.py index 349c24a609..68dcef9999 100755 --- a/src/charm.py +++ b/src/charm.py @@ -71,6 +71,7 @@ from constants import ( APP_SCOPE, BACKUP_USER, + DATABASE_DEFAULT_NAME, METRICS_PORT, MONITORING_PASSWORD_KEY, MONITORING_SNAP_SERVICE, @@ -373,7 +374,7 @@ def postgresql(self) -> PostgreSQL: current_host=self._unit_ip, user=USER, password=self.get_secret(APP_SCOPE, f"{USER}-password"), - database="postgres", + database=DATABASE_DEFAULT_NAME, system_users=SYSTEM_USERS, ) diff --git a/src/constants.py b/src/constants.py index ebced6479d..a6ce304027 100644 --- a/src/constants.py +++ b/src/constants.py @@ -6,6 +6,7 @@ BACKUP_ID_FORMAT = "%Y-%m-%dT%H:%M:%SZ" PGBACKREST_BACKUP_ID_FORMAT = "%Y%m%d-%H%M%S" DATABASE = "database" +DATABASE_DEFAULT_NAME = "postgres" DATABASE_PORT = "5432" LEGACY_DB = "db" LEGACY_DB_ADMIN = "db-admin" diff --git a/tests/integration/helpers.py b/tests/integration/helpers.py index acd4efcf01..6fe8fd144f 100644 --- a/tests/integration/helpers.py +++ b/tests/integration/helpers.py @@ -30,6 +30,8 @@ wait_fixed, ) +from constants import DATABASE_DEFAULT_NAME + CHARM_BASE = "ubuntu@22.04" METADATA = yaml.safe_load(Path("./metadata.yaml").read_text()) DATABASE_APP_NAME = METADATA["name"] @@ -497,7 +499,7 @@ async def execute_query_on_unit( unit_address: str, password: str, query: str, - database: str = "postgres", + database: str = DATABASE_DEFAULT_NAME, sslmode: str | None = None, ): """Execute given PostgreSQL query on a unit. diff --git a/tests/integration/new_relations/test_new_relations_1.py b/tests/integration/new_relations/test_new_relations_1.py index 42571a80b9..7d051a0211 100644 --- a/tests/integration/new_relations/test_new_relations_1.py +++ b/tests/integration/new_relations/test_new_relations_1.py @@ -12,6 +12,8 @@ from pytest_operator.plugin import OpsTest from tenacity import Retrying, stop_after_attempt, wait_fixed +from constants import DATABASE_DEFAULT_NAME + from ..helpers import ( CHARM_BASE, assert_sync_standbys, @@ -277,7 +279,10 @@ async def test_two_applications_doesnt_share_the_same_relation_data(ops_test: Op (another_application_app_name, f"{APPLICATION_APP_NAME.replace('-', '_')}_database"), ]: connection_string = await build_connection_string( - ops_test, application, FIRST_DATABASE_RELATION_NAME, database="postgres" + ops_test, + application, + FIRST_DATABASE_RELATION_NAME, + database=DATABASE_DEFAULT_NAME, ) with pytest.raises(psycopg2.Error): psycopg2.connect(connection_string) @@ -487,7 +492,7 @@ async def test_admin_role(ops_test: OpsTest): # Check that the user can access all the databases. for database in [ - "postgres", + DATABASE_DEFAULT_NAME, f"{APPLICATION_APP_NAME.replace('-', '_')}_database", "another_application_database", ]: @@ -511,11 +516,11 @@ async def test_admin_role(ops_test: OpsTest): ) assert version == data - # Write some data (it should fail in the "postgres" database). + # Write some data (it should fail in the default database name). random_name = ( f"test_{''.join(secrets.choice(string.ascii_lowercase) for _ in range(10))}" ) - should_fail = database == "postgres" + should_fail = database == DATABASE_DEFAULT_NAME cursor.execute(f"CREATE TABLE {random_name}(data TEXT);") if should_fail: assert False, ( @@ -533,7 +538,7 @@ async def test_admin_role(ops_test: OpsTest): # Test the creation and deletion of databases. connection_string = await build_connection_string( - ops_test, DATA_INTEGRATOR_APP_NAME, "postgresql", database="postgres" + ops_test, DATA_INTEGRATOR_APP_NAME, "postgresql", database=DATABASE_DEFAULT_NAME ) connection = psycopg2.connect(connection_string) connection.autocommit = True @@ -542,8 +547,10 @@ async def test_admin_role(ops_test: OpsTest): cursor.execute(f"CREATE DATABASE {random_name};") cursor.execute(f"DROP DATABASE {random_name};") try: - cursor.execute("DROP DATABASE postgres;") - assert False, "the admin extra user role was able to drop the `postgres` system database" + cursor.execute(f"DROP DATABASE {DATABASE_DEFAULT_NAME};") + assert False, ( + f"the admin extra user role was able to drop the `{DATABASE_DEFAULT_NAME}` system database" + ) except psycopg2.errors.InsufficientPrivilege: # Ignore the error, as the admin extra user role mustn't be able to drop # the "postgres" system database. diff --git a/tests/integration/new_relations/test_relations_coherence.py b/tests/integration/new_relations/test_relations_coherence.py index fa44d33399..74fd202fa6 100644 --- a/tests/integration/new_relations/test_relations_coherence.py +++ b/tests/integration/new_relations/test_relations_coherence.py @@ -9,6 +9,8 @@ import pytest from pytest_operator.plugin import OpsTest +from constants import DATABASE_DEFAULT_NAME + from ..helpers import CHARM_BASE, DATABASE_APP_NAME from .helpers import build_connection_string from .test_new_relations_1 import DATA_INTEGRATOR_APP_NAME @@ -125,14 +127,14 @@ async def test_relations(ops_test: OpsTest, charm): for database in [ DATA_INTEGRATOR_APP_NAME.replace("-", "_"), - "postgres", + DATABASE_DEFAULT_NAME, ]: logger.info(f"connecting to the following database: {database}") connection_string = await build_connection_string( ops_test, DATA_INTEGRATOR_APP_NAME, "postgresql", database=database ) connection = None - should_fail = database == "postgres" + should_fail = database == DATABASE_DEFAULT_NAME try: with ( psycopg2.connect(connection_string) as connection, @@ -142,7 +144,7 @@ async def test_relations(ops_test: OpsTest, charm): data = cursor.fetchone() assert data[0] == "some data" - # Write some data (it should fail in the "postgres" database). + # Write some data (it should fail in the default database name). random_name = f"test_{''.join(secrets.choice(string.ascii_lowercase) for _ in range(10))}" cursor.execute(f"CREATE TABLE {random_name}(data TEXT);") if should_fail: From 32d33d407331800fe6d12bc97a73e4fdf3af6232 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Wed, 19 Feb 2025 23:50:16 +0200 Subject: [PATCH 50/74] Lock file maintenance Python dependencies (#743) * Lock file maintenance Python dependencies * Backoff boto3 1.36 --------- Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> Co-authored-by: Dragomir Penev --- poetry.lock | 387 +++++++++++++++++++++++++------------------------ pyproject.toml | 14 +- 2 files changed, 201 insertions(+), 200 deletions(-) diff --git a/poetry.lock b/poetry.lock index 497afa866c..61225ccb89 100644 --- a/poetry.lock +++ b/poetry.lock @@ -173,18 +173,18 @@ typecheck = ["mypy"] [[package]] name = "boto3" -version = "1.35.87" +version = "1.35.99" description = "The AWS SDK for Python" optional = false python-versions = ">=3.8" groups = ["main", "integration"] files = [ - {file = "boto3-1.35.87-py3-none-any.whl", hash = "sha256:588ab05e2771c50fca5c242be14e7a25200ffd3dd95c45950ce40993473864c7"}, - {file = "boto3-1.35.87.tar.gz", hash = "sha256:341c58602889078a4a25dc4331b832b5b600a33acd73471d2532c6f01b16fbb4"}, + {file = "boto3-1.35.99-py3-none-any.whl", hash = "sha256:83e560faaec38a956dfb3d62e05e1703ee50432b45b788c09e25107c5058bd71"}, + {file = "boto3-1.35.99.tar.gz", hash = "sha256:e0abd794a7a591d90558e92e29a9f8837d25ece8e3c120e530526fe27eba5fca"}, ] [package.dependencies] -botocore = ">=1.35.87,<1.36.0" +botocore = ">=1.35.99,<1.36.0" jmespath = ">=0.7.1,<2.0.0" s3transfer = ">=0.10.0,<0.11.0" @@ -225,14 +225,14 @@ files = [ [[package]] name = "certifi" -version = "2024.12.14" +version = "2025.1.31" description = "Python package for providing Mozilla's CA Bundle." optional = false python-versions = ">=3.6" groups = ["main", "charm-libs", "integration"] files = [ - {file = "certifi-2024.12.14-py3-none-any.whl", hash = "sha256:1275f7a45be9464efc1173084eaa30f866fe2e47d389406136d332ed4967ec56"}, - {file = "certifi-2024.12.14.tar.gz", hash = "sha256:b650d30f370c2b724812bee08008be0c4163b163ddaec3f2546c1caf65f191db"}, + {file = "certifi-2025.1.31-py3-none-any.whl", hash = "sha256:ca78db4565a652026a4db2bcdf68f2fb589ea80d0be70e03929ed730746b84fe"}, + {file = "certifi-2025.1.31.tar.gz", hash = "sha256:3d5da6925056f6f18f119200434a4780a94263f10d1c21d032a6f6b2baa20651"}, ] [[package]] @@ -420,14 +420,14 @@ files = [ [[package]] name = "codespell" -version = "2.4.0" +version = "2.4.1" description = "Fix common misspellings in text files" optional = false python-versions = ">=3.8" groups = ["lint"] files = [ - {file = "codespell-2.4.0-py3-none-any.whl", hash = "sha256:b4c5b779f747dd481587aeecb5773301183f52b94b96ed51a28126d0482eec1d"}, - {file = "codespell-2.4.0.tar.gz", hash = "sha256:587d45b14707fb8ce51339ba4cce50ae0e98ce228ef61f3c5e160e34f681be58"}, + {file = "codespell-2.4.1-py3-none-any.whl", hash = "sha256:3dadafa67df7e4a3dbf51e0d7315061b80d265f9552ebd699b3dd6834b47e425"}, + {file = "codespell-2.4.1.tar.gz", hash = "sha256:299fcdcb09d23e81e35a671bbe746d5ad7e8385972e65dbb833a2eaac33c01e5"}, ] [package.extras] @@ -451,14 +451,14 @@ files = [ [[package]] name = "cosl" -version = "0.0.54" +version = "0.0.55" description = "Utils for COS Lite charms" optional = false python-versions = ">=3.8" groups = ["charm-libs"] files = [ - {file = "cosl-0.0.54-py3-none-any.whl", hash = "sha256:b16520d73c72ac83cb42f0abe997d36510732d4f8499f70e9068cfa05f0d02fa"}, - {file = "cosl-0.0.54.tar.gz", hash = "sha256:6baa889cc4468b0c0f746cc6319892a30ea8fbe38cbf5c49c6885f6fdf89d6a9"}, + {file = "cosl-0.0.55-py3-none-any.whl", hash = "sha256:bf641d611f982c8f494f3cf72ac4181b24e30c69504cfbd55aa8f54964797f90"}, + {file = "cosl-0.0.55.tar.gz", hash = "sha256:d3b8ee6f78302ac111d3a15d36c42a38c298a806161d762869513d348d778316"}, ] [package.dependencies] @@ -471,74 +471,75 @@ typing-extensions = "*" [[package]] name = "coverage" -version = "7.6.10" +version = "7.6.12" description = "Code coverage measurement for Python" optional = false python-versions = ">=3.9" groups = ["unit"] files = [ - {file = "coverage-7.6.10-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:5c912978f7fbf47ef99cec50c4401340436d200d41d714c7a4766f377c5b7b78"}, - {file = "coverage-7.6.10-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:a01ec4af7dfeb96ff0078ad9a48810bb0cc8abcb0115180c6013a6b26237626c"}, - {file = "coverage-7.6.10-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a3b204c11e2b2d883946fe1d97f89403aa1811df28ce0447439178cc7463448a"}, - {file = "coverage-7.6.10-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:32ee6d8491fcfc82652a37109f69dee9a830e9379166cb73c16d8dc5c2915165"}, - {file = "coverage-7.6.10-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:675cefc4c06e3b4c876b85bfb7c59c5e2218167bbd4da5075cbe3b5790a28988"}, - {file = "coverage-7.6.10-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:f4f620668dbc6f5e909a0946a877310fb3d57aea8198bde792aae369ee1c23b5"}, - {file = "coverage-7.6.10-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:4eea95ef275de7abaef630c9b2c002ffbc01918b726a39f5a4353916ec72d2f3"}, - {file = "coverage-7.6.10-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:e2f0280519e42b0a17550072861e0bc8a80a0870de260f9796157d3fca2733c5"}, - {file = "coverage-7.6.10-cp310-cp310-win32.whl", hash = "sha256:bc67deb76bc3717f22e765ab3e07ee9c7a5e26b9019ca19a3b063d9f4b874244"}, - {file = "coverage-7.6.10-cp310-cp310-win_amd64.whl", hash = "sha256:0f460286cb94036455e703c66988851d970fdfd8acc2a1122ab7f4f904e4029e"}, - {file = "coverage-7.6.10-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:ea3c8f04b3e4af80e17bab607c386a830ffc2fb88a5484e1df756478cf70d1d3"}, - {file = "coverage-7.6.10-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:507a20fc863cae1d5720797761b42d2d87a04b3e5aeb682ef3b7332e90598f43"}, - {file = "coverage-7.6.10-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d37a84878285b903c0fe21ac8794c6dab58150e9359f1aaebbeddd6412d53132"}, - {file = "coverage-7.6.10-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a534738b47b0de1995f85f582d983d94031dffb48ab86c95bdf88dc62212142f"}, - {file = "coverage-7.6.10-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0d7a2bf79378d8fb8afaa994f91bfd8215134f8631d27eba3e0e2c13546ce994"}, - {file = "coverage-7.6.10-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:6713ba4b4ebc330f3def51df1d5d38fad60b66720948112f114968feb52d3f99"}, - {file = "coverage-7.6.10-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:ab32947f481f7e8c763fa2c92fd9f44eeb143e7610c4ca9ecd6a36adab4081bd"}, - {file = "coverage-7.6.10-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:7bbd8c8f1b115b892e34ba66a097b915d3871db7ce0e6b9901f462ff3a975377"}, - {file = "coverage-7.6.10-cp311-cp311-win32.whl", hash = "sha256:299e91b274c5c9cdb64cbdf1b3e4a8fe538a7a86acdd08fae52301b28ba297f8"}, - {file = "coverage-7.6.10-cp311-cp311-win_amd64.whl", hash = "sha256:489a01f94aa581dbd961f306e37d75d4ba16104bbfa2b0edb21d29b73be83609"}, - {file = "coverage-7.6.10-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:27c6e64726b307782fa5cbe531e7647aee385a29b2107cd87ba7c0105a5d3853"}, - {file = "coverage-7.6.10-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:c56e097019e72c373bae32d946ecf9858fda841e48d82df7e81c63ac25554078"}, - {file = "coverage-7.6.10-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c7827a5bc7bdb197b9e066cdf650b2887597ad124dd99777332776f7b7c7d0d0"}, - {file = "coverage-7.6.10-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:204a8238afe787323a8b47d8be4df89772d5c1e4651b9ffa808552bdf20e1d50"}, - {file = "coverage-7.6.10-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e67926f51821b8e9deb6426ff3164870976fe414d033ad90ea75e7ed0c2e5022"}, - {file = "coverage-7.6.10-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:e78b270eadb5702938c3dbe9367f878249b5ef9a2fcc5360ac7bff694310d17b"}, - {file = "coverage-7.6.10-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:714f942b9c15c3a7a5fe6876ce30af831c2ad4ce902410b7466b662358c852c0"}, - {file = "coverage-7.6.10-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:abb02e2f5a3187b2ac4cd46b8ced85a0858230b577ccb2c62c81482ca7d18852"}, - {file = "coverage-7.6.10-cp312-cp312-win32.whl", hash = "sha256:55b201b97286cf61f5e76063f9e2a1d8d2972fc2fcfd2c1272530172fd28c359"}, - {file = "coverage-7.6.10-cp312-cp312-win_amd64.whl", hash = "sha256:e4ae5ac5e0d1e4edfc9b4b57b4cbecd5bc266a6915c500f358817a8496739247"}, - {file = "coverage-7.6.10-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:05fca8ba6a87aabdd2d30d0b6c838b50510b56cdcfc604d40760dae7153b73d9"}, - {file = "coverage-7.6.10-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:9e80eba8801c386f72e0712a0453431259c45c3249f0009aff537a517b52942b"}, - {file = "coverage-7.6.10-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a372c89c939d57abe09e08c0578c1d212e7a678135d53aa16eec4430adc5e690"}, - {file = "coverage-7.6.10-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ec22b5e7fe7a0fa8509181c4aac1db48f3dd4d3a566131b313d1efc102892c18"}, - {file = "coverage-7.6.10-cp313-cp313-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:26bcf5c4df41cad1b19c84af71c22cbc9ea9a547fc973f1f2cc9a290002c8b3c"}, - {file = "coverage-7.6.10-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:4e4630c26b6084c9b3cb53b15bd488f30ceb50b73c35c5ad7871b869cb7365fd"}, - {file = "coverage-7.6.10-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:2396e8116db77789f819d2bc8a7e200232b7a282c66e0ae2d2cd84581a89757e"}, - {file = "coverage-7.6.10-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:79109c70cc0882e4d2d002fe69a24aa504dec0cc17169b3c7f41a1d341a73694"}, - {file = "coverage-7.6.10-cp313-cp313-win32.whl", hash = "sha256:9e1747bab246d6ff2c4f28b4d186b205adced9f7bd9dc362051cc37c4a0c7bd6"}, - {file = "coverage-7.6.10-cp313-cp313-win_amd64.whl", hash = "sha256:254f1a3b1eef5f7ed23ef265eaa89c65c8c5b6b257327c149db1ca9d4a35f25e"}, - {file = "coverage-7.6.10-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:2ccf240eb719789cedbb9fd1338055de2761088202a9a0b73032857e53f612fe"}, - {file = "coverage-7.6.10-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:0c807ca74d5a5e64427c8805de15b9ca140bba13572d6d74e262f46f50b13273"}, - {file = "coverage-7.6.10-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2bcfa46d7709b5a7ffe089075799b902020b62e7ee56ebaed2f4bdac04c508d8"}, - {file = "coverage-7.6.10-cp313-cp313t-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4e0de1e902669dccbf80b0415fb6b43d27edca2fbd48c74da378923b05316098"}, - {file = "coverage-7.6.10-cp313-cp313t-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3f7b444c42bbc533aaae6b5a2166fd1a797cdb5eb58ee51a92bee1eb94a1e1cb"}, - {file = "coverage-7.6.10-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:b330368cb99ef72fcd2dc3ed260adf67b31499584dc8a20225e85bfe6f6cfed0"}, - {file = "coverage-7.6.10-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:9a7cfb50515f87f7ed30bc882f68812fd98bc2852957df69f3003d22a2aa0abf"}, - {file = "coverage-7.6.10-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:6f93531882a5f68c28090f901b1d135de61b56331bba82028489bc51bdd818d2"}, - {file = "coverage-7.6.10-cp313-cp313t-win32.whl", hash = "sha256:89d76815a26197c858f53c7f6a656686ec392b25991f9e409bcef020cd532312"}, - {file = "coverage-7.6.10-cp313-cp313t-win_amd64.whl", hash = "sha256:54a5f0f43950a36312155dae55c505a76cd7f2b12d26abeebbe7a0b36dbc868d"}, - {file = "coverage-7.6.10-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:656c82b8a0ead8bba147de9a89bda95064874c91a3ed43a00e687f23cc19d53a"}, - {file = "coverage-7.6.10-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:ccc2b70a7ed475c68ceb548bf69cec1e27305c1c2606a5eb7c3afff56a1b3b27"}, - {file = "coverage-7.6.10-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a5e37dc41d57ceba70956fa2fc5b63c26dba863c946ace9705f8eca99daecdc4"}, - {file = "coverage-7.6.10-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0aa9692b4fdd83a4647eeb7db46410ea1322b5ed94cd1715ef09d1d5922ba87f"}, - {file = "coverage-7.6.10-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:aa744da1820678b475e4ba3dfd994c321c5b13381d1041fe9c608620e6676e25"}, - {file = "coverage-7.6.10-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:c0b1818063dc9e9d838c09e3a473c1422f517889436dd980f5d721899e66f315"}, - {file = "coverage-7.6.10-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:59af35558ba08b758aec4d56182b222976330ef8d2feacbb93964f576a7e7a90"}, - {file = "coverage-7.6.10-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:7ed2f37cfce1ce101e6dffdfd1c99e729dd2ffc291d02d3e2d0af8b53d13840d"}, - {file = "coverage-7.6.10-cp39-cp39-win32.whl", hash = "sha256:4bcc276261505d82f0ad426870c3b12cb177752834a633e737ec5ee79bbdff18"}, - {file = "coverage-7.6.10-cp39-cp39-win_amd64.whl", hash = "sha256:457574f4599d2b00f7f637a0700a6422243b3565509457b2dbd3f50703e11f59"}, - {file = "coverage-7.6.10-pp39.pp310-none-any.whl", hash = "sha256:fd34e7b3405f0cc7ab03d54a334c17a9e802897580d964bd8c2001f4b9fd488f"}, - {file = "coverage-7.6.10.tar.gz", hash = "sha256:7fb105327c8f8f0682e29843e2ff96af9dcbe5bab8eeb4b398c6a33a16d80a23"}, + {file = "coverage-7.6.12-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:704c8c8c6ce6569286ae9622e534b4f5b9759b6f2cd643f1c1a61f666d534fe8"}, + {file = "coverage-7.6.12-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:ad7525bf0241e5502168ae9c643a2f6c219fa0a283001cee4cf23a9b7da75879"}, + {file = "coverage-7.6.12-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:06097c7abfa611c91edb9e6920264e5be1d6ceb374efb4986f38b09eed4cb2fe"}, + {file = "coverage-7.6.12-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:220fa6c0ad7d9caef57f2c8771918324563ef0d8272c94974717c3909664e674"}, + {file = "coverage-7.6.12-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3688b99604a24492bcfe1c106278c45586eb819bf66a654d8a9a1433022fb2eb"}, + {file = "coverage-7.6.12-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:d1a987778b9c71da2fc8948e6f2656da6ef68f59298b7e9786849634c35d2c3c"}, + {file = "coverage-7.6.12-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:cec6b9ce3bd2b7853d4a4563801292bfee40b030c05a3d29555fd2a8ee9bd68c"}, + {file = "coverage-7.6.12-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:ace9048de91293e467b44bce0f0381345078389814ff6e18dbac8fdbf896360e"}, + {file = "coverage-7.6.12-cp310-cp310-win32.whl", hash = "sha256:ea31689f05043d520113e0552f039603c4dd71fa4c287b64cb3606140c66f425"}, + {file = "coverage-7.6.12-cp310-cp310-win_amd64.whl", hash = "sha256:676f92141e3c5492d2a1596d52287d0d963df21bf5e55c8b03075a60e1ddf8aa"}, + {file = "coverage-7.6.12-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:e18aafdfb3e9ec0d261c942d35bd7c28d031c5855dadb491d2723ba54f4c3015"}, + {file = "coverage-7.6.12-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:66fe626fd7aa5982cdebad23e49e78ef7dbb3e3c2a5960a2b53632f1f703ea45"}, + {file = "coverage-7.6.12-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0ef01d70198431719af0b1f5dcbefc557d44a190e749004042927b2a3fed0702"}, + {file = "coverage-7.6.12-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:07e92ae5a289a4bc4c0aae710c0948d3c7892e20fd3588224ebe242039573bf0"}, + {file = "coverage-7.6.12-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e695df2c58ce526eeab11a2e915448d3eb76f75dffe338ea613c1201b33bab2f"}, + {file = "coverage-7.6.12-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:d74c08e9aaef995f8c4ef6d202dbd219c318450fe2a76da624f2ebb9c8ec5d9f"}, + {file = "coverage-7.6.12-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:e995b3b76ccedc27fe4f477b349b7d64597e53a43fc2961db9d3fbace085d69d"}, + {file = "coverage-7.6.12-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:b1f097878d74fe51e1ddd1be62d8e3682748875b461232cf4b52ddc6e6db0bba"}, + {file = "coverage-7.6.12-cp311-cp311-win32.whl", hash = "sha256:1f7ffa05da41754e20512202c866d0ebfc440bba3b0ed15133070e20bf5aeb5f"}, + {file = "coverage-7.6.12-cp311-cp311-win_amd64.whl", hash = "sha256:e216c5c45f89ef8971373fd1c5d8d1164b81f7f5f06bbf23c37e7908d19e8558"}, + {file = "coverage-7.6.12-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:b172f8e030e8ef247b3104902cc671e20df80163b60a203653150d2fc204d1ad"}, + {file = "coverage-7.6.12-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:641dfe0ab73deb7069fb972d4d9725bf11c239c309ce694dd50b1473c0f641c3"}, + {file = "coverage-7.6.12-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0e549f54ac5f301e8e04c569dfdb907f7be71b06b88b5063ce9d6953d2d58574"}, + {file = "coverage-7.6.12-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:959244a17184515f8c52dcb65fb662808767c0bd233c1d8a166e7cf74c9ea985"}, + {file = "coverage-7.6.12-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bda1c5f347550c359f841d6614fb8ca42ae5cb0b74d39f8a1e204815ebe25750"}, + {file = "coverage-7.6.12-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:1ceeb90c3eda1f2d8c4c578c14167dbd8c674ecd7d38e45647543f19839dd6ea"}, + {file = "coverage-7.6.12-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:0f16f44025c06792e0fb09571ae454bcc7a3ec75eeb3c36b025eccf501b1a4c3"}, + {file = "coverage-7.6.12-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:b076e625396e787448d27a411aefff867db2bffac8ed04e8f7056b07024eed5a"}, + {file = "coverage-7.6.12-cp312-cp312-win32.whl", hash = "sha256:00b2086892cf06c7c2d74983c9595dc511acca00665480b3ddff749ec4fb2a95"}, + {file = "coverage-7.6.12-cp312-cp312-win_amd64.whl", hash = "sha256:7ae6eabf519bc7871ce117fb18bf14e0e343eeb96c377667e3e5dd12095e0288"}, + {file = "coverage-7.6.12-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:488c27b3db0ebee97a830e6b5a3ea930c4a6e2c07f27a5e67e1b3532e76b9ef1"}, + {file = "coverage-7.6.12-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:5d1095bbee1851269f79fd8e0c9b5544e4c00c0c24965e66d8cba2eb5bb535fd"}, + {file = "coverage-7.6.12-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0533adc29adf6a69c1baa88c3d7dbcaadcffa21afbed3ca7a225a440e4744bf9"}, + {file = "coverage-7.6.12-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:53c56358d470fa507a2b6e67a68fd002364d23c83741dbc4c2e0680d80ca227e"}, + {file = "coverage-7.6.12-cp313-cp313-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:64cbb1a3027c79ca6310bf101014614f6e6e18c226474606cf725238cf5bc2d4"}, + {file = "coverage-7.6.12-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:79cac3390bfa9836bb795be377395f28410811c9066bc4eefd8015258a7578c6"}, + {file = "coverage-7.6.12-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:9b148068e881faa26d878ff63e79650e208e95cf1c22bd3f77c3ca7b1d9821a3"}, + {file = "coverage-7.6.12-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:8bec2ac5da793c2685ce5319ca9bcf4eee683b8a1679051f8e6ec04c4f2fd7dc"}, + {file = "coverage-7.6.12-cp313-cp313-win32.whl", hash = "sha256:200e10beb6ddd7c3ded322a4186313d5ca9e63e33d8fab4faa67ef46d3460af3"}, + {file = "coverage-7.6.12-cp313-cp313-win_amd64.whl", hash = "sha256:2b996819ced9f7dbb812c701485d58f261bef08f9b85304d41219b1496b591ef"}, + {file = "coverage-7.6.12-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:299cf973a7abff87a30609879c10df0b3bfc33d021e1adabc29138a48888841e"}, + {file = "coverage-7.6.12-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:4b467a8c56974bf06e543e69ad803c6865249d7a5ccf6980457ed2bc50312703"}, + {file = "coverage-7.6.12-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2458f275944db8129f95d91aee32c828a408481ecde3b30af31d552c2ce284a0"}, + {file = "coverage-7.6.12-cp313-cp313t-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0a9d8be07fb0832636a0f72b80d2a652fe665e80e720301fb22b191c3434d924"}, + {file = "coverage-7.6.12-cp313-cp313t-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:14d47376a4f445e9743f6c83291e60adb1b127607a3618e3185bbc8091f0467b"}, + {file = "coverage-7.6.12-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:b95574d06aa9d2bd6e5cc35a5bbe35696342c96760b69dc4287dbd5abd4ad51d"}, + {file = "coverage-7.6.12-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:ecea0c38c9079570163d663c0433a9af4094a60aafdca491c6a3d248c7432827"}, + {file = "coverage-7.6.12-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:2251fabcfee0a55a8578a9d29cecfee5f2de02f11530e7d5c5a05859aa85aee9"}, + {file = "coverage-7.6.12-cp313-cp313t-win32.whl", hash = "sha256:eb5507795caabd9b2ae3f1adc95f67b1104971c22c624bb354232d65c4fc90b3"}, + {file = "coverage-7.6.12-cp313-cp313t-win_amd64.whl", hash = "sha256:f60a297c3987c6c02ffb29effc70eadcbb412fe76947d394a1091a3615948e2f"}, + {file = "coverage-7.6.12-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:e7575ab65ca8399c8c4f9a7d61bbd2d204c8b8e447aab9d355682205c9dd948d"}, + {file = "coverage-7.6.12-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:8161d9fbc7e9fe2326de89cd0abb9f3599bccc1287db0aba285cb68d204ce929"}, + {file = "coverage-7.6.12-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3a1e465f398c713f1b212400b4e79a09829cd42aebd360362cd89c5bdc44eb87"}, + {file = "coverage-7.6.12-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f25d8b92a4e31ff1bd873654ec367ae811b3a943583e05432ea29264782dc32c"}, + {file = "coverage-7.6.12-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1a936309a65cc5ca80fa9f20a442ff9e2d06927ec9a4f54bcba9c14c066323f2"}, + {file = "coverage-7.6.12-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:aa6f302a3a0b5f240ee201297fff0bbfe2fa0d415a94aeb257d8b461032389bd"}, + {file = "coverage-7.6.12-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:f973643ef532d4f9be71dd88cf7588936685fdb576d93a79fe9f65bc337d9d73"}, + {file = "coverage-7.6.12-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:78f5243bb6b1060aed6213d5107744c19f9571ec76d54c99cc15938eb69e0e86"}, + {file = "coverage-7.6.12-cp39-cp39-win32.whl", hash = "sha256:69e62c5034291c845fc4df7f8155e8544178b6c774f97a99e2734b05eb5bed31"}, + {file = "coverage-7.6.12-cp39-cp39-win_amd64.whl", hash = "sha256:b01a840ecc25dce235ae4c1b6a0daefb2a203dba0e6e980637ee9c2f6ee0df57"}, + {file = "coverage-7.6.12-pp39.pp310-none-any.whl", hash = "sha256:7e39e845c4d764208e7b8f6a21c541ade741e2c41afabdfa1caa28687a3c98cf"}, + {file = "coverage-7.6.12-py3-none-any.whl", hash = "sha256:eb8668cfbc279a536c633137deeb9435d2962caec279c3f8cf8b91fff6ff8953"}, + {file = "coverage-7.6.12.tar.gz", hash = "sha256:48cfc4641d95d34766ad41d9573cc0f22a48aa88d22657a1fe01dca0dbae4de2"}, ] [package.dependencies] @@ -615,14 +616,14 @@ files = [ [[package]] name = "deprecated" -version = "1.2.17" +version = "1.2.18" description = "Python @deprecated decorator to deprecate old python classes, functions or methods." optional = false python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,>=2.7" groups = ["charm-libs"] files = [ - {file = "Deprecated-1.2.17-py2.py3-none-any.whl", hash = "sha256:69cdc0a751671183f569495e2efb14baee4344b0236342eec29f1fde25d61818"}, - {file = "deprecated-1.2.17.tar.gz", hash = "sha256:0114a10f0bbb750b90b2c2296c90cf7e9eaeb0abb5cf06c80de2c60138de0a82"}, + {file = "Deprecated-1.2.18-py2.py3-none-any.whl", hash = "sha256:bd5011788200372a32418f888e326a09ff80d0214bd961147cfed01b5c018eec"}, + {file = "deprecated-1.2.18.tar.gz", hash = "sha256:422b6f6d859da6f2ef57857761bfb392480502a64c3028ca9bbe86085d72115d"}, ] [package.dependencies] @@ -689,14 +690,14 @@ requests = ["requests (>=2.20.0,<3.0.0.dev0)"] [[package]] name = "googleapis-common-protos" -version = "1.66.0" +version = "1.67.0" description = "Common protobufs used in Google APIs" optional = false python-versions = ">=3.7" groups = ["charm-libs"] files = [ - {file = "googleapis_common_protos-1.66.0-py2.py3-none-any.whl", hash = "sha256:d7abcd75fabb2e0ec9f74466401f6c119a0b498e27370e9be4c94cb7e382b8ed"}, - {file = "googleapis_common_protos-1.66.0.tar.gz", hash = "sha256:c3e7b33d15fdca5374cc0a7346dd92ffa847425cc4ea941d970f13680052ec8c"}, + {file = "googleapis_common_protos-1.67.0-py2.py3-none-any.whl", hash = "sha256:579de760800d13616f51cf8be00c876f00a9f146d3e6510e19d1f4111758b741"}, + {file = "googleapis_common_protos-1.67.0.tar.gz", hash = "sha256:21398025365f138be356d5923e9168737d94d46a72aefee4a6110a1f23463c86"}, ] [package.dependencies] @@ -848,14 +849,14 @@ tomli = {version = "*", markers = "python_version > \"3.6\" and python_version < [[package]] name = "ipython" -version = "8.31.0" +version = "8.32.0" description = "IPython: Productive Interactive Computing" optional = false python-versions = ">=3.10" groups = ["integration"] files = [ - {file = "ipython-8.31.0-py3-none-any.whl", hash = "sha256:46ec58f8d3d076a61d128fe517a51eb730e3aaf0c184ea8c17d16e366660c6a6"}, - {file = "ipython-8.31.0.tar.gz", hash = "sha256:b6a2274606bec6166405ff05e54932ed6e5cfecaca1fc05f2cacde7bb074d70b"}, + {file = "ipython-8.32.0-py3-none-any.whl", hash = "sha256:cae85b0c61eff1fc48b0a8002de5958b6528fa9c8defb1894da63f42613708aa"}, + {file = "ipython-8.32.0.tar.gz", hash = "sha256:be2c91895b0b9ea7ba49d33b23e2040c352b33eb6a519cca7ce6e0c743444251"}, ] [package.dependencies] @@ -1334,14 +1335,14 @@ files = [ [[package]] name = "ops" -version = "2.17.1" +version = "2.18.1" description = "The Python library behind great charms" optional = false python-versions = ">=3.8" groups = ["main", "charm-libs"] files = [ - {file = "ops-2.17.1-py3-none-any.whl", hash = "sha256:0fabc45740d59619c3265328f51f71f99b06557e22493cdd32d10c2b25bcd553"}, - {file = "ops-2.17.1.tar.gz", hash = "sha256:de2d1dd382b4a5f3df3ba78a5266d59462644f3f8ea0f4e7479a248998862a3f"}, + {file = "ops-2.18.1-py3-none-any.whl", hash = "sha256:ba0312366e25b3ae90cf4b8d0af6ea6b612d4951500f856bce609cdb25c9bdeb"}, + {file = "ops-2.18.1.tar.gz", hash = "sha256:5619deb370c00ea851f9579b780a09b88b1a1d020e58e1ed81d31c8fb7b28c8a"}, ] [package.dependencies] @@ -1349,7 +1350,7 @@ PyYAML = "==6.*" websocket-client = "==1.*" [package.extras] -docs = ["canonical-sphinx-extensions", "furo", "linkify-it-py", "myst-parser", "ops-scenario (>=7.0.5,<8)", "pyspelling", "sphinx (>=8.0.0,<8.1.0)", "sphinx-autobuild", "sphinx-copybutton", "sphinx-design", "sphinx-notfound-page", "sphinx-tabs", "sphinxcontrib-jquery", "sphinxext-opengraph"] +docs = ["canonical-sphinx-extensions", "furo", "linkify-it-py", "myst-parser", "pyspelling", "sphinx (>=8.0.0,<8.1.0)", "sphinx-autobuild", "sphinx-copybutton", "sphinx-design", "sphinx-notfound-page", "sphinx-tabs", "sphinxcontrib-jquery", "sphinxext-opengraph"] testing = ["ops-scenario (>=7.0.5,<8)"] [[package]] @@ -1381,14 +1382,14 @@ dev = ["jinja2"] [[package]] name = "paramiko" -version = "3.5.0" +version = "3.5.1" description = "SSH2 protocol library" optional = false python-versions = ">=3.6" groups = ["integration"] files = [ - {file = "paramiko-3.5.0-py3-none-any.whl", hash = "sha256:1fedf06b085359051cd7d0d270cebe19e755a8a921cc2ddbfa647fb0cd7d68f9"}, - {file = "paramiko-3.5.0.tar.gz", hash = "sha256:ad11e540da4f55cedda52931f1a3f812a8238a7af7f62a60de538cd80bb28124"}, + {file = "paramiko-3.5.1-py3-none-any.whl", hash = "sha256:43b9a0501fc2b5e70680388d9346cf252cfb7d00b0667c39e80eb43a408b8f61"}, + {file = "paramiko-3.5.1.tar.gz", hash = "sha256:b2c665bc45b2b215bd7d7f039901b14b067da00f3a11e6640995fd58f2664822"}, ] [package.dependencies] @@ -1463,14 +1464,14 @@ testing = ["pytest", "pytest-benchmark"] [[package]] name = "poetry-core" -version = "2.0.1" +version = "2.1.1" description = "Poetry PEP 517 Build Backend" optional = false python-versions = "<4.0,>=3.9" groups = ["charm-libs"] files = [ - {file = "poetry_core-2.0.1-py3-none-any.whl", hash = "sha256:a3c7009536522cda4eb0fb3805c9dc935b5537f8727dd01efb9c15e51a17552b"}, - {file = "poetry_core-2.0.1.tar.gz", hash = "sha256:10177c2772469d9032a49f0d8707af761b1c597cea3b4fb31546e5cd436eb157"}, + {file = "poetry_core-2.1.1-py3-none-any.whl", hash = "sha256:bc3b0382ab4d00d5d780277fd0aad1580eb4403613b37fc60fec407b5bee1fe6"}, + {file = "poetry_core-2.1.1.tar.gz", hash = "sha256:c1a1f6f00e4254742f40988a8caf665549101cf9991122cd5de1198897768b1a"}, ] [[package]] @@ -1841,13 +1842,13 @@ pytz = "*" [[package]] name = "pysyncobj" -version = "0.3.13" +version = "0.3.14" description = "A library for replicating your python class between multiple servers, based on raft protocol" optional = false python-versions = "*" groups = ["main"] files = [ - {file = "pysyncobj-0.3.13.tar.gz", hash = "sha256:1785930b738fa21af298ebb04c213af25c31af148faa32f53af337ed1492d5a2"}, + {file = "pysyncobj-0.3.14.tar.gz", hash = "sha256:69d34e672257694f83f50dfb5e6e7ce446a68dba09ed48e142c782380d6428d4"}, ] [[package]] @@ -1894,14 +1895,14 @@ testing = ["coverage (>=6.2)", "flaky (>=3.5.0)", "hypothesis (>=5.7.1)", "mypy [[package]] name = "pytest-operator" -version = "0.39.0" +version = "0.40.0" description = "Fixtures for Operators" optional = false python-versions = "*" groups = ["integration"] files = [ - {file = "pytest_operator-0.39.0-py3-none-any.whl", hash = "sha256:ade76e1896eaf7f71704b537fd6661a705d81a045b8db71531d9e4741913fa19"}, - {file = "pytest_operator-0.39.0.tar.gz", hash = "sha256:b66bd8c6d161593c258a5714118a51e9f37721e7cd9e503299423d8a7d900f90"}, + {file = "pytest_operator-0.40.0-py3-none-any.whl", hash = "sha256:1cfa93ab61b11e8d7bf58dbb1a39e75fcbfcc084781bb571fde08fda7e236713"}, + {file = "pytest_operator-0.40.0.tar.gz", hash = "sha256:45394ade32b7765b6ba89871b676d1fb8aa7578589f74df26ff0fca4692d1c7b"}, ] [package.dependencies] @@ -1929,14 +1930,14 @@ six = ">=1.5" [[package]] name = "pytz" -version = "2024.2" +version = "2025.1" description = "World timezone definitions, modern and historical" optional = false python-versions = "*" groups = ["integration"] files = [ - {file = "pytz-2024.2-py2.py3-none-any.whl", hash = "sha256:31c7c1817eb7fae7ca4b8c7ee50c72f93aa2dd863de768e1ef4245d426aa0725"}, - {file = "pytz-2024.2.tar.gz", hash = "sha256:2aa355083c50a0f93fa581709deac0c9ad65cca8a9e9beac660adcbd493c798a"}, + {file = "pytz-2025.1-py2.py3-none-any.whl", hash = "sha256:89dd22dca55b46eac6eda23b2d72721bf1bdfef212645d81513ef5d03038de57"}, + {file = "pytz-2025.1.tar.gz", hash = "sha256:c2db42be2a2518b28e65f9207c4d05e6ff547d1efa4086469ef855e4ab70178e"}, ] [[package]] @@ -2190,30 +2191,30 @@ pyasn1 = ">=0.1.3" [[package]] name = "ruff" -version = "0.9.3" +version = "0.9.6" description = "An extremely fast Python linter and code formatter, written in Rust." optional = false python-versions = ">=3.7" groups = ["format"] files = [ - {file = "ruff-0.9.3-py3-none-linux_armv6l.whl", hash = "sha256:7f39b879064c7d9670197d91124a75d118d00b0990586549949aae80cdc16624"}, - {file = "ruff-0.9.3-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:a187171e7c09efa4b4cc30ee5d0d55a8d6c5311b3e1b74ac5cb96cc89bafc43c"}, - {file = "ruff-0.9.3-py3-none-macosx_11_0_arm64.whl", hash = "sha256:c59ab92f8e92d6725b7ded9d4a31be3ef42688a115c6d3da9457a5bda140e2b4"}, - {file = "ruff-0.9.3-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2dc153c25e715be41bb228bc651c1e9b1a88d5c6e5ed0194fa0dfea02b026439"}, - {file = "ruff-0.9.3-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:646909a1e25e0dc28fbc529eab8eb7bb583079628e8cbe738192853dbbe43af5"}, - {file = "ruff-0.9.3-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:5a5a46e09355695fbdbb30ed9889d6cf1c61b77b700a9fafc21b41f097bfbba4"}, - {file = "ruff-0.9.3-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:c4bb09d2bbb394e3730d0918c00276e79b2de70ec2a5231cd4ebb51a57df9ba1"}, - {file = "ruff-0.9.3-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:96a87ec31dc1044d8c2da2ebbed1c456d9b561e7d087734336518181b26b3aa5"}, - {file = "ruff-0.9.3-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:9bb7554aca6f842645022fe2d301c264e6925baa708b392867b7a62645304df4"}, - {file = "ruff-0.9.3-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:cabc332b7075a914ecea912cd1f3d4370489c8018f2c945a30bcc934e3bc06a6"}, - {file = "ruff-0.9.3-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:33866c3cc2a575cbd546f2cd02bdd466fed65118e4365ee538a3deffd6fcb730"}, - {file = "ruff-0.9.3-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:006e5de2621304c8810bcd2ee101587712fa93b4f955ed0985907a36c427e0c2"}, - {file = "ruff-0.9.3-py3-none-musllinux_1_2_i686.whl", hash = "sha256:ba6eea4459dbd6b1be4e6bfc766079fb9b8dd2e5a35aff6baee4d9b1514ea519"}, - {file = "ruff-0.9.3-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:90230a6b8055ad47d3325e9ee8f8a9ae7e273078a66401ac66df68943ced029b"}, - {file = "ruff-0.9.3-py3-none-win32.whl", hash = "sha256:eabe5eb2c19a42f4808c03b82bd313fc84d4e395133fb3fc1b1516170a31213c"}, - {file = "ruff-0.9.3-py3-none-win_amd64.whl", hash = "sha256:040ceb7f20791dfa0e78b4230ee9dce23da3b64dd5848e40e3bf3ab76468dcf4"}, - {file = "ruff-0.9.3-py3-none-win_arm64.whl", hash = "sha256:800d773f6d4d33b0a3c60e2c6ae8f4c202ea2de056365acfa519aa48acf28e0b"}, - {file = "ruff-0.9.3.tar.gz", hash = "sha256:8293f89985a090ebc3ed1064df31f3b4b56320cdfcec8b60d3295bddb955c22a"}, + {file = "ruff-0.9.6-py3-none-linux_armv6l.whl", hash = "sha256:2f218f356dd2d995839f1941322ff021c72a492c470f0b26a34f844c29cdf5ba"}, + {file = "ruff-0.9.6-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:b908ff4df65dad7b251c9968a2e4560836d8f5487c2f0cc238321ed951ea0504"}, + {file = "ruff-0.9.6-py3-none-macosx_11_0_arm64.whl", hash = "sha256:b109c0ad2ececf42e75fa99dc4043ff72a357436bb171900714a9ea581ddef83"}, + {file = "ruff-0.9.6-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1de4367cca3dac99bcbd15c161404e849bb0bfd543664db39232648dc00112dc"}, + {file = "ruff-0.9.6-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:ac3ee4d7c2c92ddfdaedf0bf31b2b176fa7aa8950efc454628d477394d35638b"}, + {file = "ruff-0.9.6-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:5dc1edd1775270e6aa2386119aea692039781429f0be1e0949ea5884e011aa8e"}, + {file = "ruff-0.9.6-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:4a091729086dffa4bd070aa5dab7e39cc6b9d62eb2bef8f3d91172d30d599666"}, + {file = "ruff-0.9.6-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d1bbc6808bf7b15796cef0815e1dfb796fbd383e7dbd4334709642649625e7c5"}, + {file = "ruff-0.9.6-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:589d1d9f25b5754ff230dce914a174a7c951a85a4e9270613a2b74231fdac2f5"}, + {file = "ruff-0.9.6-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dc61dd5131742e21103fbbdcad683a8813be0e3c204472d520d9a5021ca8b217"}, + {file = "ruff-0.9.6-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:5e2d9126161d0357e5c8f30b0bd6168d2c3872372f14481136d13de9937f79b6"}, + {file = "ruff-0.9.6-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:68660eab1a8e65babb5229a1f97b46e3120923757a68b5413d8561f8a85d4897"}, + {file = "ruff-0.9.6-py3-none-musllinux_1_2_i686.whl", hash = "sha256:c4cae6c4cc7b9b4017c71114115db0445b00a16de3bcde0946273e8392856f08"}, + {file = "ruff-0.9.6-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:19f505b643228b417c1111a2a536424ddde0db4ef9023b9e04a46ed8a1cb4656"}, + {file = "ruff-0.9.6-py3-none-win32.whl", hash = "sha256:194d8402bceef1b31164909540a597e0d913c0e4952015a5b40e28c146121b5d"}, + {file = "ruff-0.9.6-py3-none-win_amd64.whl", hash = "sha256:03482d5c09d90d4ee3f40d97578423698ad895c87314c4de39ed2af945633caa"}, + {file = "ruff-0.9.6-py3-none-win_arm64.whl", hash = "sha256:0e2bb706a2be7ddfea4a4af918562fdc1bcb16df255e5fa595bbd800ce322a5a"}, + {file = "ruff-0.9.6.tar.gz", hash = "sha256:81761592f72b620ec8fa1068a6fd00e98a5ebee342a3642efd84454f3031dca9"}, ] [[package]] @@ -2443,81 +2444,81 @@ test = ["websockets"] [[package]] name = "websockets" -version = "14.2" +version = "15.0" description = "An implementation of the WebSocket Protocol (RFC 6455 & 7692)" optional = false python-versions = ">=3.9" groups = ["integration"] files = [ - {file = "websockets-14.2-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:e8179f95323b9ab1c11723e5d91a89403903f7b001828161b480a7810b334885"}, - {file = "websockets-14.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:0d8c3e2cdb38f31d8bd7d9d28908005f6fa9def3324edb9bf336d7e4266fd397"}, - {file = "websockets-14.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:714a9b682deb4339d39ffa674f7b674230227d981a37d5d174a4a83e3978a610"}, - {file = "websockets-14.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f2e53c72052f2596fb792a7acd9704cbc549bf70fcde8a99e899311455974ca3"}, - {file = "websockets-14.2-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e3fbd68850c837e57373d95c8fe352203a512b6e49eaae4c2f4088ef8cf21980"}, - {file = "websockets-14.2-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4b27ece32f63150c268593d5fdb82819584831a83a3f5809b7521df0685cd5d8"}, - {file = "websockets-14.2-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:4daa0faea5424d8713142b33825fff03c736f781690d90652d2c8b053345b0e7"}, - {file = "websockets-14.2-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:bc63cee8596a6ec84d9753fd0fcfa0452ee12f317afe4beae6b157f0070c6c7f"}, - {file = "websockets-14.2-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:7a570862c325af2111343cc9b0257b7119b904823c675b22d4ac547163088d0d"}, - {file = "websockets-14.2-cp310-cp310-win32.whl", hash = "sha256:75862126b3d2d505e895893e3deac0a9339ce750bd27b4ba515f008b5acf832d"}, - {file = "websockets-14.2-cp310-cp310-win_amd64.whl", hash = "sha256:cc45afb9c9b2dc0852d5c8b5321759cf825f82a31bfaf506b65bf4668c96f8b2"}, - {file = "websockets-14.2-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:3bdc8c692c866ce5fefcaf07d2b55c91d6922ac397e031ef9b774e5b9ea42166"}, - {file = "websockets-14.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:c93215fac5dadc63e51bcc6dceca72e72267c11def401d6668622b47675b097f"}, - {file = "websockets-14.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:1c9b6535c0e2cf8a6bf938064fb754aaceb1e6a4a51a80d884cd5db569886910"}, - {file = "websockets-14.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0a52a6d7cf6938e04e9dceb949d35fbdf58ac14deea26e685ab6368e73744e4c"}, - {file = "websockets-14.2-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:9f05702e93203a6ff5226e21d9b40c037761b2cfb637187c9802c10f58e40473"}, - {file = "websockets-14.2-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:22441c81a6748a53bfcb98951d58d1af0661ab47a536af08920d129b4d1c3473"}, - {file = "websockets-14.2-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:efd9b868d78b194790e6236d9cbc46d68aba4b75b22497eb4ab64fa640c3af56"}, - {file = "websockets-14.2-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:1a5a20d5843886d34ff8c57424cc65a1deda4375729cbca4cb6b3353f3ce4142"}, - {file = "websockets-14.2-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:34277a29f5303d54ec6468fb525d99c99938607bc96b8d72d675dee2b9f5bf1d"}, - {file = "websockets-14.2-cp311-cp311-win32.whl", hash = "sha256:02687db35dbc7d25fd541a602b5f8e451a238ffa033030b172ff86a93cb5dc2a"}, - {file = "websockets-14.2-cp311-cp311-win_amd64.whl", hash = "sha256:862e9967b46c07d4dcd2532e9e8e3c2825e004ffbf91a5ef9dde519ee2effb0b"}, - {file = "websockets-14.2-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:1f20522e624d7ffbdbe259c6b6a65d73c895045f76a93719aa10cd93b3de100c"}, - {file = "websockets-14.2-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:647b573f7d3ada919fd60e64d533409a79dcf1ea21daeb4542d1d996519ca967"}, - {file = "websockets-14.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:6af99a38e49f66be5a64b1e890208ad026cda49355661549c507152113049990"}, - {file = "websockets-14.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:091ab63dfc8cea748cc22c1db2814eadb77ccbf82829bac6b2fbe3401d548eda"}, - {file = "websockets-14.2-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b374e8953ad477d17e4851cdc66d83fdc2db88d9e73abf755c94510ebddceb95"}, - {file = "websockets-14.2-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a39d7eceeea35db85b85e1169011bb4321c32e673920ae9c1b6e0978590012a3"}, - {file = "websockets-14.2-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:0a6f3efd47ffd0d12080594f434faf1cd2549b31e54870b8470b28cc1d3817d9"}, - {file = "websockets-14.2-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:065ce275e7c4ffb42cb738dd6b20726ac26ac9ad0a2a48e33ca632351a737267"}, - {file = "websockets-14.2-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:e9d0e53530ba7b8b5e389c02282f9d2aa47581514bd6049d3a7cffe1385cf5fe"}, - {file = "websockets-14.2-cp312-cp312-win32.whl", hash = "sha256:20e6dd0984d7ca3037afcb4494e48c74ffb51e8013cac71cf607fffe11df7205"}, - {file = "websockets-14.2-cp312-cp312-win_amd64.whl", hash = "sha256:44bba1a956c2c9d268bdcdf234d5e5ff4c9b6dc3e300545cbe99af59dda9dcce"}, - {file = "websockets-14.2-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:6f1372e511c7409a542291bce92d6c83320e02c9cf392223272287ce55bc224e"}, - {file = "websockets-14.2-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:4da98b72009836179bb596a92297b1a61bb5a830c0e483a7d0766d45070a08ad"}, - {file = "websockets-14.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:f8a86a269759026d2bde227652b87be79f8a734e582debf64c9d302faa1e9f03"}, - {file = "websockets-14.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:86cf1aaeca909bf6815ea714d5c5736c8d6dd3a13770e885aafe062ecbd04f1f"}, - {file = "websockets-14.2-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a9b0f6c3ba3b1240f602ebb3971d45b02cc12bd1845466dd783496b3b05783a5"}, - {file = "websockets-14.2-cp313-cp313-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:669c3e101c246aa85bc8534e495952e2ca208bd87994650b90a23d745902db9a"}, - {file = "websockets-14.2-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:eabdb28b972f3729348e632ab08f2a7b616c7e53d5414c12108c29972e655b20"}, - {file = "websockets-14.2-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:2066dc4cbcc19f32c12a5a0e8cc1b7ac734e5b64ac0a325ff8353451c4b15ef2"}, - {file = "websockets-14.2-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:ab95d357cd471df61873dadf66dd05dd4709cae001dd6342edafc8dc6382f307"}, - {file = "websockets-14.2-cp313-cp313-win32.whl", hash = "sha256:a9e72fb63e5f3feacdcf5b4ff53199ec8c18d66e325c34ee4c551ca748623bbc"}, - {file = "websockets-14.2-cp313-cp313-win_amd64.whl", hash = "sha256:b439ea828c4ba99bb3176dc8d9b933392a2413c0f6b149fdcba48393f573377f"}, - {file = "websockets-14.2-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:7cd5706caec1686c5d233bc76243ff64b1c0dc445339bd538f30547e787c11fe"}, - {file = "websockets-14.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:ec607328ce95a2f12b595f7ae4c5d71bf502212bddcea528290b35c286932b12"}, - {file = "websockets-14.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:da85651270c6bfb630136423037dd4975199e5d4114cae6d3066641adcc9d1c7"}, - {file = "websockets-14.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c3ecadc7ce90accf39903815697917643f5b7cfb73c96702318a096c00aa71f5"}, - {file = "websockets-14.2-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1979bee04af6a78608024bad6dfcc0cc930ce819f9e10342a29a05b5320355d0"}, - {file = "websockets-14.2-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2dddacad58e2614a24938a50b85969d56f88e620e3f897b7d80ac0d8a5800258"}, - {file = "websockets-14.2-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:89a71173caaf75fa71a09a5f614f450ba3ec84ad9fca47cb2422a860676716f0"}, - {file = "websockets-14.2-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:6af6a4b26eea4fc06c6818a6b962a952441e0e39548b44773502761ded8cc1d4"}, - {file = "websockets-14.2-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:80c8efa38957f20bba0117b48737993643204645e9ec45512579132508477cfc"}, - {file = "websockets-14.2-cp39-cp39-win32.whl", hash = "sha256:2e20c5f517e2163d76e2729104abc42639c41cf91f7b1839295be43302713661"}, - {file = "websockets-14.2-cp39-cp39-win_amd64.whl", hash = "sha256:b4c8cef610e8d7c70dea92e62b6814a8cd24fbd01d7103cc89308d2bfe1659ef"}, - {file = "websockets-14.2-pp310-pypy310_pp73-macosx_10_15_x86_64.whl", hash = "sha256:d7d9cafbccba46e768be8a8ad4635fa3eae1ffac4c6e7cb4eb276ba41297ed29"}, - {file = "websockets-14.2-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:c76193c1c044bd1e9b3316dcc34b174bbf9664598791e6fb606d8d29000e070c"}, - {file = "websockets-14.2-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:fd475a974d5352390baf865309fe37dec6831aafc3014ffac1eea99e84e83fc2"}, - {file = "websockets-14.2-pp310-pypy310_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:2c6c0097a41968b2e2b54ed3424739aab0b762ca92af2379f152c1aef0187e1c"}, - {file = "websockets-14.2-pp310-pypy310_pp73-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6d7ff794c8b36bc402f2e07c0b2ceb4a2424147ed4785ff03e2a7af03711d60a"}, - {file = "websockets-14.2-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:dec254fcabc7bd488dab64846f588fc5b6fe0d78f641180030f8ea27b76d72c3"}, - {file = "websockets-14.2-pp39-pypy39_pp73-macosx_10_15_x86_64.whl", hash = "sha256:bbe03eb853e17fd5b15448328b4ec7fb2407d45fb0245036d06a3af251f8e48f"}, - {file = "websockets-14.2-pp39-pypy39_pp73-macosx_11_0_arm64.whl", hash = "sha256:a3c4aa3428b904d5404a0ed85f3644d37e2cb25996b7f096d77caeb0e96a3b42"}, - {file = "websockets-14.2-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:577a4cebf1ceaf0b65ffc42c54856214165fb8ceeba3935852fc33f6b0c55e7f"}, - {file = "websockets-14.2-pp39-pypy39_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ad1c1d02357b7665e700eca43a31d52814ad9ad9b89b58118bdabc365454b574"}, - {file = "websockets-14.2-pp39-pypy39_pp73-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f390024a47d904613577df83ba700bd189eedc09c57af0a904e5c39624621270"}, - {file = "websockets-14.2-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:3c1426c021c38cf92b453cdf371228d3430acd775edee6bac5a4d577efc72365"}, - {file = "websockets-14.2-py3-none-any.whl", hash = "sha256:7a6ceec4ea84469f15cf15807a747e9efe57e369c384fa86e022b3bea679b79b"}, - {file = "websockets-14.2.tar.gz", hash = "sha256:5059ed9c54945efb321f097084b4c7e52c246f2c869815876a69d1efc4ad6eb5"}, + {file = "websockets-15.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:5e6ee18a53dd5743e6155b8ff7e8e477c25b29b440f87f65be8165275c87fef0"}, + {file = "websockets-15.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:ee06405ea2e67366a661ed313e14cf2a86e84142a3462852eb96348f7219cee3"}, + {file = "websockets-15.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:8711682a629bbcaf492f5e0af72d378e976ea1d127a2d47584fa1c2c080b436b"}, + {file = "websockets-15.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:94c4a9b01eede952442c088d415861b0cf2053cbd696b863f6d5022d4e4e2453"}, + {file = "websockets-15.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:45535fead66e873f411c1d3cf0d3e175e66f4dd83c4f59d707d5b3e4c56541c4"}, + {file = "websockets-15.0-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0e389efe46ccb25a1f93d08c7a74e8123a2517f7b7458f043bd7529d1a63ffeb"}, + {file = "websockets-15.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:67a04754d121ea5ca39ddedc3f77071651fb5b0bc6b973c71c515415b44ed9c5"}, + {file = "websockets-15.0-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:bd66b4865c8b853b8cca7379afb692fc7f52cf898786537dfb5e5e2d64f0a47f"}, + {file = "websockets-15.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:a4cc73a6ae0a6751b76e69cece9d0311f054da9b22df6a12f2c53111735657c8"}, + {file = "websockets-15.0-cp310-cp310-win32.whl", hash = "sha256:89da58e4005e153b03fe8b8794330e3f6a9774ee9e1c3bd5bc52eb098c3b0c4f"}, + {file = "websockets-15.0-cp310-cp310-win_amd64.whl", hash = "sha256:4ff380aabd7a74a42a760ee76c68826a8f417ceb6ea415bd574a035a111fd133"}, + {file = "websockets-15.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:dd24c4d256558429aeeb8d6c24ebad4e982ac52c50bc3670ae8646c181263965"}, + {file = "websockets-15.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:f83eca8cbfd168e424dfa3b3b5c955d6c281e8fc09feb9d870886ff8d03683c7"}, + {file = "websockets-15.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:4095a1f2093002c2208becf6f9a178b336b7572512ee0a1179731acb7788e8ad"}, + {file = "websockets-15.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:fb915101dfbf318486364ce85662bb7b020840f68138014972c08331458d41f3"}, + {file = "websockets-15.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:45d464622314973d78f364689d5dbb9144e559f93dca11b11af3f2480b5034e1"}, + {file = "websockets-15.0-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ace960769d60037ca9625b4c578a6f28a14301bd2a1ff13bb00e824ac9f73e55"}, + {file = "websockets-15.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:c7cd4b1015d2f60dfe539ee6c95bc968d5d5fad92ab01bb5501a77393da4f596"}, + {file = "websockets-15.0-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:4f7290295794b5dec470867c7baa4a14182b9732603fd0caf2a5bf1dc3ccabf3"}, + {file = "websockets-15.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:3abd670ca7ce230d5a624fd3d55e055215d8d9b723adee0a348352f5d8d12ff4"}, + {file = "websockets-15.0-cp311-cp311-win32.whl", hash = "sha256:110a847085246ab8d4d119632145224d6b49e406c64f1bbeed45c6f05097b680"}, + {file = "websockets-15.0-cp311-cp311-win_amd64.whl", hash = "sha256:8d7bbbe2cd6ed80aceef2a14e9f1c1b61683194c216472ed5ff33b700e784e37"}, + {file = "websockets-15.0-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:cccc18077acd34c8072578394ec79563664b1c205f7a86a62e94fafc7b59001f"}, + {file = "websockets-15.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:d4c22992e24f12de340ca5f824121a5b3e1a37ad4360b4e1aaf15e9d1c42582d"}, + {file = "websockets-15.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:1206432cc6c644f6fc03374b264c5ff805d980311563202ed7fef91a38906276"}, + {file = "websockets-15.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5d3cc75ef3e17490042c47e0523aee1bcc4eacd2482796107fd59dd1100a44bc"}, + {file = "websockets-15.0-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b89504227a5311610e4be16071465885a0a3d6b0e82e305ef46d9b064ce5fb72"}, + {file = "websockets-15.0-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:56e3efe356416bc67a8e093607315951d76910f03d2b3ad49c4ade9207bf710d"}, + {file = "websockets-15.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:0f2205cdb444a42a7919690238fb5979a05439b9dbb73dd47c863d39640d85ab"}, + {file = "websockets-15.0-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:aea01f40995fa0945c020228ab919b8dfc93fc8a9f2d3d705ab5b793f32d9e99"}, + {file = "websockets-15.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:a9f8e33747b1332db11cf7fcf4a9512bef9748cb5eb4d3f7fbc8c30d75dc6ffc"}, + {file = "websockets-15.0-cp312-cp312-win32.whl", hash = "sha256:32e02a2d83f4954aa8c17e03fe8ec6962432c39aca4be7e8ee346b05a3476904"}, + {file = "websockets-15.0-cp312-cp312-win_amd64.whl", hash = "sha256:ffc02b159b65c05f2ed9ec176b715b66918a674bd4daed48a9a7a590dd4be1aa"}, + {file = "websockets-15.0-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:d2244d8ab24374bed366f9ff206e2619345f9cd7fe79aad5225f53faac28b6b1"}, + {file = "websockets-15.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:3a302241fbe825a3e4fe07666a2ab513edfdc6d43ce24b79691b45115273b5e7"}, + {file = "websockets-15.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:10552fed076757a70ba2c18edcbc601c7637b30cdfe8c24b65171e824c7d6081"}, + {file = "websockets-15.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c53f97032b87a406044a1c33d1e9290cc38b117a8062e8a8b285175d7e2f99c9"}, + {file = "websockets-15.0-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1caf951110ca757b8ad9c4974f5cac7b8413004d2f29707e4d03a65d54cedf2b"}, + {file = "websockets-15.0-cp313-cp313-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8bf1ab71f9f23b0a1d52ec1682a3907e0c208c12fef9c3e99d2b80166b17905f"}, + {file = "websockets-15.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:bfcd3acc1a81f106abac6afd42327d2cf1e77ec905ae11dc1d9142a006a496b6"}, + {file = "websockets-15.0-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:c8c5c8e1bac05ef3c23722e591ef4f688f528235e2480f157a9cfe0a19081375"}, + {file = "websockets-15.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:86bfb52a9cfbcc09aba2b71388b0a20ea5c52b6517c0b2e316222435a8cdab72"}, + {file = "websockets-15.0-cp313-cp313-win32.whl", hash = "sha256:26ba70fed190708551c19a360f9d7eca8e8c0f615d19a574292b7229e0ae324c"}, + {file = "websockets-15.0-cp313-cp313-win_amd64.whl", hash = "sha256:ae721bcc8e69846af00b7a77a220614d9b2ec57d25017a6bbde3a99473e41ce8"}, + {file = "websockets-15.0-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:c348abc5924caa02a62896300e32ea80a81521f91d6db2e853e6b1994017c9f6"}, + {file = "websockets-15.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:5294fcb410ed0a45d5d1cdedc4e51a60aab5b2b3193999028ea94afc2f554b05"}, + {file = "websockets-15.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:c24ba103ecf45861e2e1f933d40b2d93f5d52d8228870c3e7bf1299cd1cb8ff1"}, + {file = "websockets-15.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:cc8821a03bcfb36e4e4705316f6b66af28450357af8a575dc8f4b09bf02a3dee"}, + {file = "websockets-15.0-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ffc5ae23ada6515f31604f700009e2df90b091b67d463a8401c1d8a37f76c1d7"}, + {file = "websockets-15.0-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7ac67b542505186b3bbdaffbc303292e1ee9c8729e5d5df243c1f20f4bb9057e"}, + {file = "websockets-15.0-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:c86dc2068f1c5ca2065aca34f257bbf4f78caf566eb230f692ad347da191f0a1"}, + {file = "websockets-15.0-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:30cff3ef329682b6182c01c568f551481774c476722020b8f7d0daacbed07a17"}, + {file = "websockets-15.0-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:98dcf978d4c6048965d1762abd534c9d53bae981a035bfe486690ba11f49bbbb"}, + {file = "websockets-15.0-cp39-cp39-win32.whl", hash = "sha256:37d66646f929ae7c22c79bc73ec4074d6db45e6384500ee3e0d476daf55482a9"}, + {file = "websockets-15.0-cp39-cp39-win_amd64.whl", hash = "sha256:24d5333a9b2343330f0f4eb88546e2c32a7f5c280f8dd7d3cc079beb0901781b"}, + {file = "websockets-15.0-pp310-pypy310_pp73-macosx_10_15_x86_64.whl", hash = "sha256:b499caef4bca9cbd0bd23cd3386f5113ee7378094a3cb613a2fa543260fe9506"}, + {file = "websockets-15.0-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:17f2854c6bd9ee008c4b270f7010fe2da6c16eac5724a175e75010aacd905b31"}, + {file = "websockets-15.0-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:89f72524033abbfde880ad338fd3c2c16e31ae232323ebdfbc745cbb1b3dcc03"}, + {file = "websockets-15.0-pp310-pypy310_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1657a9eecb29d7838e3b415458cc494e6d1b194f7ac73a34aa55c6fb6c72d1f3"}, + {file = "websockets-15.0-pp310-pypy310_pp73-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e413352a921f5ad5d66f9e2869b977e88d5103fc528b6deb8423028a2befd842"}, + {file = "websockets-15.0-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:8561c48b0090993e3b2a54db480cab1d23eb2c5735067213bb90f402806339f5"}, + {file = "websockets-15.0-pp39-pypy39_pp73-macosx_10_15_x86_64.whl", hash = "sha256:190bc6ef8690cd88232a038d1b15714c258f79653abad62f7048249b09438af3"}, + {file = "websockets-15.0-pp39-pypy39_pp73-macosx_11_0_arm64.whl", hash = "sha256:327adab7671f3726b0ba69be9e865bba23b37a605b585e65895c428f6e47e766"}, + {file = "websockets-15.0-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2bd8ef197c87afe0a9009f7a28b5dc613bfc585d329f80b7af404e766aa9e8c7"}, + {file = "websockets-15.0-pp39-pypy39_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:789c43bf4a10cd067c24c321238e800b8b2716c863ddb2294d2fed886fa5a689"}, + {file = "websockets-15.0-pp39-pypy39_pp73-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7394c0b7d460569c9285fa089a429f58465db930012566c03046f9e3ab0ed181"}, + {file = "websockets-15.0-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:2ea4f210422b912ebe58ef0ad33088bc8e5c5ff9655a8822500690abc3b1232d"}, + {file = "websockets-15.0-py3-none-any.whl", hash = "sha256:51ffd53c53c4442415b613497a34ba0aa7b99ac07f1e4a62db5dcd640ae6c3c3"}, + {file = "websockets-15.0.tar.gz", hash = "sha256:ca36151289a15b39d8d683fd8b7abbe26fc50be311066c5f8dcf3cb8cee107ab"}, ] [[package]] @@ -2632,4 +2633,4 @@ type = ["pytest-mypy"] [metadata] lock-version = "2.1" python-versions = "^3.10" -content-hash = "69624ae0414471d65adc54e447287151f49a62de192bc317c279a062cd483268" +content-hash = "32f3b67d60393e53e28f1c3856b0c6f5c32ea538d4ae6cae1847cdd37a001ceb" diff --git a/pyproject.toml b/pyproject.toml index 01067270d8..82e0129586 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -7,15 +7,15 @@ requires-poetry = ">=2.0.0" [tool.poetry.dependencies] python = "^3.10" -ops = "^2.17.1" -boto3 = "^1.35.87" +ops = "^2.18.1" +boto3 = "^1.35.99" pgconnstr = "^1.0.1" requests = "^2.32.3" tenacity = "^9.0.0" psycopg2 = "^2.9.10" pydantic = "^1.10.21" jinja2 = "^3.1.5" -pysyncobj = "^0.3.13" +pysyncobj = "^0.3.14" psutil = "^7.0.0" [tool.poetry.group.charm-libs.dependencies] @@ -38,19 +38,19 @@ opentelemetry-exporter-otlp-proto-http = "1.21.0" optional = true [tool.poetry.group.format.dependencies] -ruff = "^0.9.3" +ruff = "^0.9.6" [tool.poetry.group.lint] optional = true [tool.poetry.group.lint.dependencies] -codespell = "^2.4.0" +codespell = "^2.4.1" [tool.poetry.group.unit] optional = true [tool.poetry.group.unit.dependencies] -coverage = {extras = ["toml"], version = "^7.6.10"} +coverage = {extras = ["toml"], version = "^7.6.12"} pytest = "^8.3.4" pytest-asyncio = "*" parameterized = "^0.9.0" @@ -61,7 +61,7 @@ optional = true [tool.poetry.group.integration.dependencies] pytest = "^8.3.4" -pytest-operator = "^0.39.0" +pytest-operator = "^0.40.0" # renovate caret doesn't work: https://github.com/renovatebot/renovate/issues/26940 juju = "<=3.6.1.0" boto3 = "*" From 0588cd07ec09c5a2ec6cd1de7a656b7e02a5cbf9 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Sun, 23 Feb 2025 10:33:28 -0300 Subject: [PATCH 51/74] Update charmcraft.yaml build tools (#768) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- charmcraft.yaml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/charmcraft.yaml b/charmcraft.yaml index 1de48d3e3e..5664e1e153 100644 --- a/charmcraft.yaml +++ b/charmcraft.yaml @@ -27,7 +27,7 @@ parts: PIP_BREAK_SYSTEM_PACKAGES=true python3 -m pip install --user --upgrade pip==25.0.1 # renovate: charmcraft-pip-latest # Use uv to install poetry so that a newer version of Python can be installed if needed by poetry - curl --proto '=https' --tlsv1.2 -LsSf https://github.com/astral-sh/uv/releases/download/0.5.30/uv-installer.sh | sh # renovate: charmcraft-uv-latest + curl --proto '=https' --tlsv1.2 -LsSf https://github.com/astral-sh/uv/releases/download/0.6.2/uv-installer.sh | sh # renovate: charmcraft-uv-latest # poetry 2.0.0 requires Python >=3.9 if ! "$HOME/.local/bin/uv" python find '>=3.9' then @@ -35,7 +35,7 @@ parts: # (to reduce the number of Python versions we use) "$HOME/.local/bin/uv" python install 3.10.12 # renovate: charmcraft-python-ubuntu-22.04 fi - "$HOME/.local/bin/uv" tool install --no-python-downloads --python '>=3.9' poetry==2.0.1 --with poetry-plugin-export==1.9.0 # renovate: charmcraft-poetry-latest + "$HOME/.local/bin/uv" tool install --no-python-downloads --python '>=3.9' poetry==2.1.1 --with poetry-plugin-export==1.9.0 # renovate: charmcraft-poetry-latest ln -sf "$HOME/.local/bin/poetry" /usr/local/bin/poetry # "charm-poetry" part name is arbitrary; use for consistency @@ -75,7 +75,7 @@ parts: # rpds-py (Python package) >=0.19.0 requires rustc >=1.76, which is not available in the # Ubuntu 22.04 archive. Install rustc and cargo using rustup instead of the Ubuntu archive rustup set profile minimal - rustup default 1.84.1 # renovate: charmcraft-rust-latest + rustup default 1.85.0 # renovate: charmcraft-rust-latest craftctl default # Include requirements.txt in *.charm artifact for easier debugging From 7e0d0e8c977d3bff6b6d6a987f38f721b7e7fd51 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Sun, 23 Feb 2025 21:56:15 +0200 Subject: [PATCH 52/74] Update canonical/data-platform-workflows action to v30.1.3 (#776) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- .github/workflows/check_pr.yaml | 2 +- .github/workflows/ci.yaml | 4 ++-- .github/workflows/promote.yaml | 2 +- .github/workflows/release.yaml | 2 +- .github/workflows/sync_docs.yaml | 2 +- 5 files changed, 6 insertions(+), 6 deletions(-) diff --git a/.github/workflows/check_pr.yaml b/.github/workflows/check_pr.yaml index 5389215315..e3bf9febe0 100644 --- a/.github/workflows/check_pr.yaml +++ b/.github/workflows/check_pr.yaml @@ -15,4 +15,4 @@ on: jobs: check-pr: name: Check pull request - uses: canonical/data-platform-workflows/.github/workflows/check_charm_pr.yaml@v30.0.2 + uses: canonical/data-platform-workflows/.github/workflows/check_charm_pr.yaml@v30.1.3 diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index 70c38dcb6d..82dca5072d 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -27,7 +27,7 @@ on: jobs: lint: name: Lint - uses: canonical/data-platform-workflows/.github/workflows/lint.yaml@v30.0.2 + uses: canonical/data-platform-workflows/.github/workflows/lint.yaml@v30.1.3 unit-test: name: Unit test charm @@ -49,7 +49,7 @@ jobs: build: name: Build charm - uses: canonical/data-platform-workflows/.github/workflows/build_charm.yaml@v30.0.2 + uses: canonical/data-platform-workflows/.github/workflows/build_charm.yaml@v30.1.3 integration-test: name: Integration test charm diff --git a/.github/workflows/promote.yaml b/.github/workflows/promote.yaml index bbbda02abd..a3ef15303a 100644 --- a/.github/workflows/promote.yaml +++ b/.github/workflows/promote.yaml @@ -25,7 +25,7 @@ on: jobs: promote: name: Promote charm - uses: canonical/data-platform-workflows/.github/workflows/_promote_charm.yaml@v30.0.2 + uses: canonical/data-platform-workflows/.github/workflows/_promote_charm.yaml@v30.1.3 with: track: '14' from-risk: ${{ inputs.from-risk }} diff --git a/.github/workflows/release.yaml b/.github/workflows/release.yaml index 7e1a01dc5a..80100a62c6 100644 --- a/.github/workflows/release.yaml +++ b/.github/workflows/release.yaml @@ -27,7 +27,7 @@ jobs: name: Release charm needs: - ci-tests - uses: canonical/data-platform-workflows/.github/workflows/release_charm.yaml@v30.0.2 + uses: canonical/data-platform-workflows/.github/workflows/release_charm.yaml@v30.1.3 with: channel: 14/edge artifact-prefix: ${{ needs.ci-tests.outputs.artifact-prefix }} diff --git a/.github/workflows/sync_docs.yaml b/.github/workflows/sync_docs.yaml index 77119ab239..d80ca5aece 100644 --- a/.github/workflows/sync_docs.yaml +++ b/.github/workflows/sync_docs.yaml @@ -10,7 +10,7 @@ on: jobs: sync-docs: name: Sync docs from Discourse - uses: canonical/data-platform-workflows/.github/workflows/sync_docs.yaml@v30.0.2 + uses: canonical/data-platform-workflows/.github/workflows/sync_docs.yaml@v30.1.3 with: reviewers: a-velasco,izmalk permissions: From 6cce771de5d4a33f751de0ce34af85f0c17fd3d3 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Tue, 25 Feb 2025 17:17:48 +0200 Subject: [PATCH 53/74] Update dependency uv to v0.6.3 (#780) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- charmcraft.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/charmcraft.yaml b/charmcraft.yaml index 5664e1e153..6b27dc71d6 100644 --- a/charmcraft.yaml +++ b/charmcraft.yaml @@ -27,7 +27,7 @@ parts: PIP_BREAK_SYSTEM_PACKAGES=true python3 -m pip install --user --upgrade pip==25.0.1 # renovate: charmcraft-pip-latest # Use uv to install poetry so that a newer version of Python can be installed if needed by poetry - curl --proto '=https' --tlsv1.2 -LsSf https://github.com/astral-sh/uv/releases/download/0.6.2/uv-installer.sh | sh # renovate: charmcraft-uv-latest + curl --proto '=https' --tlsv1.2 -LsSf https://github.com/astral-sh/uv/releases/download/0.6.3/uv-installer.sh | sh # renovate: charmcraft-uv-latest # poetry 2.0.0 requires Python >=3.9 if ! "$HOME/.local/bin/uv" python find '>=3.9' then From d1cc07bfcfaf2df4bb76a7fb6fc8ee8da41f4a65 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sinclert=20P=C3=A9rez?= Date: Tue, 4 Mar 2025 12:16:00 +0100 Subject: [PATCH 54/74] [MISC] Sanitize PostgreSQL extra-user-roles arg (#782) --- lib/charms/postgresql_k8s/v0/postgresql.py | 7 +++---- src/charm.py | 2 +- src/relations/postgresql_provider.py | 16 ++++++++++++---- tests/unit/test_postgresql_provider.py | 9 +++++++-- 4 files changed, 23 insertions(+), 11 deletions(-) diff --git a/lib/charms/postgresql_k8s/v0/postgresql.py b/lib/charms/postgresql_k8s/v0/postgresql.py index e395d6892f..c0f09af804 100644 --- a/lib/charms/postgresql_k8s/v0/postgresql.py +++ b/lib/charms/postgresql_k8s/v0/postgresql.py @@ -35,7 +35,7 @@ # Increment this PATCH version before using `charmcraft publish-lib` or reset # to 0 if you are raising the major API version -LIBPATCH = 43 +LIBPATCH = 44 # Groups to distinguish database permissions PERMISSIONS_GROUP_ADMIN = "admin" @@ -223,7 +223,7 @@ def create_user( user: str, password: Optional[str] = None, admin: bool = False, - extra_user_roles: Optional[str] = None, + extra_user_roles: Optional[list[str]] = None, ) -> None: """Creates a database user. @@ -238,7 +238,6 @@ def create_user( admin_role = False roles = privileges = None if extra_user_roles: - extra_user_roles = tuple(extra_user_roles.lower().split(",")) admin_role = PERMISSIONS_GROUP_ADMIN in extra_user_roles valid_privileges, valid_roles = self.list_valid_privileges_and_roles() roles = [ @@ -572,7 +571,7 @@ def set_up_database(self) -> None: ) self.create_user( PERMISSIONS_GROUP_ADMIN, - extra_user_roles="pg_read_all_data,pg_write_all_data", + extra_user_roles=["pg_read_all_data", "pg_write_all_data"], ) cursor.execute("GRANT CONNECT ON DATABASE postgres TO admin;") except psycopg2.Error as e: diff --git a/src/charm.py b/src/charm.py index 68dcef9999..1dbb411356 100755 --- a/src/charm.py +++ b/src/charm.py @@ -1353,7 +1353,7 @@ def _start_primary(self, event: StartEvent) -> None: self.postgresql.create_user( MONITORING_USER, self.get_secret(APP_SCOPE, MONITORING_PASSWORD_KEY), - extra_user_roles="pg_monitor", + extra_user_roles=["pg_monitor"], ) except PostgreSQLCreateUserError as e: logger.exception(e) diff --git a/src/relations/postgresql_provider.py b/src/relations/postgresql_provider.py index 487c6fb9e5..0f2fa040c1 100644 --- a/src/relations/postgresql_provider.py +++ b/src/relations/postgresql_provider.py @@ -65,6 +65,14 @@ def __init__(self, charm: CharmBase, relation_name: str = "database") -> None: self.database_provides.on.database_requested, self._on_database_requested ) + @staticmethod + def _sanitize_extra_roles(extra_roles: str | None) -> list[str]: + """Standardize and sanitize user extra-roles.""" + if extra_roles is None: + return [] + + return [role.lower() for role in extra_roles.split(",")] + def _on_database_requested(self, event: DatabaseRequestedEvent) -> None: """Generate password and handle user and database creation for the related application.""" # Check for some conditions before trying to access the PostgreSQL instance. @@ -84,7 +92,9 @@ def _on_database_requested(self, event: DatabaseRequestedEvent) -> None: # Retrieve the database name and extra user roles using the charm library. database = event.database - extra_user_roles = event.extra_user_roles + + # Make sure that certain groups are not in the list + extra_user_roles = self._sanitize_extra_roles(event.extra_user_roles) try: # Creates the user and the database for this specific relation. @@ -275,9 +285,7 @@ def check_for_invalid_extra_user_roles(self, relation_id: int) -> bool: continue for data in relation.data.values(): extra_user_roles = data.get("extra-user-roles") - if extra_user_roles is None: - continue - extra_user_roles = extra_user_roles.lower().split(",") + extra_user_roles = self._sanitize_extra_roles(extra_user_roles) for extra_user_role in extra_user_roles: if ( extra_user_role not in valid_privileges diff --git a/tests/unit/test_postgresql_provider.py b/tests/unit/test_postgresql_provider.py index 2aaed01083..399ebc2759 100644 --- a/tests/unit/test_postgresql_provider.py +++ b/tests/unit/test_postgresql_provider.py @@ -125,12 +125,17 @@ def test_on_database_requested(harness): # Assert that the correct calls were made. user = f"relation-{rel_id}" postgresql_mock.create_user.assert_called_once_with( - user, "test-password", extra_user_roles=EXTRA_USER_ROLES + user, + "test-password", + extra_user_roles=[role.lower() for role in EXTRA_USER_ROLES.split(",")], ) database_relation = harness.model.get_relation(RELATION_NAME) client_relations = [database_relation] postgresql_mock.create_database.assert_called_once_with( - DATABASE, user, plugins=["pgaudit"], client_relations=client_relations + DATABASE, + user, + plugins=["pgaudit"], + client_relations=client_relations, ) postgresql_mock.get_postgresql_version.assert_called_once() _update_endpoints.assert_called_once() From 32252a5ca24521bc31b7ef94b10865711385adcc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sinclert=20P=C3=A9rez?= Date: Wed, 5 Mar 2025 09:07:34 +0100 Subject: [PATCH 55/74] [MISC] Fix PostgreSQL lib function signature (#786) --- lib/charms/postgresql_k8s/v0/postgresql.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/charms/postgresql_k8s/v0/postgresql.py b/lib/charms/postgresql_k8s/v0/postgresql.py index c0f09af804..8e2b7072ad 100644 --- a/lib/charms/postgresql_k8s/v0/postgresql.py +++ b/lib/charms/postgresql_k8s/v0/postgresql.py @@ -35,7 +35,7 @@ # Increment this PATCH version before using `charmcraft publish-lib` or reset # to 0 if you are raising the major API version -LIBPATCH = 44 +LIBPATCH = 45 # Groups to distinguish database permissions PERMISSIONS_GROUP_ADMIN = "admin" @@ -223,7 +223,7 @@ def create_user( user: str, password: Optional[str] = None, admin: bool = False, - extra_user_roles: Optional[list[str]] = None, + extra_user_roles: Optional[List[str]] = None, ) -> None: """Creates a database user. From 5a7defefa77c0ded1c28b982a3277fb680850a73 Mon Sep 17 00:00:00 2001 From: Dragomir Penev <6687393+dragomirp@users.noreply.github.com> Date: Mon, 10 Mar 2025 16:15:35 +0200 Subject: [PATCH 56/74] [MISC] Skip backup and subordinate tests without creds (#789) * Bump libs * Skip backup tests without creds * Skip subordinate tests * Update tests/integration/test_subordinates.py Co-authored-by: Carl Csaposs --------- Co-authored-by: Carl Csaposs --- lib/charms/grafana_agent/v0/cos_agent.py | 9 +- lib/charms/rolling_ops/v0/rollingops.py | 8 +- .../tempo_coordinator_k8s/v0/charm_tracing.py | 7 +- tests/integration/conftest.py | 83 +++++++++++++++++++ tests/integration/test_backups_aws.py | 59 +------------ tests/integration/test_backups_gcp.py | 70 +++------------- tests/integration/test_backups_pitr_aws.py | 60 ++------------ tests/integration/test_backups_pitr_gcp.py | 60 ++------------ tests/integration/test_subordinates.py | 16 +++- 9 files changed, 138 insertions(+), 234 deletions(-) diff --git a/lib/charms/grafana_agent/v0/cos_agent.py b/lib/charms/grafana_agent/v0/cos_agent.py index f1344c06ac..b18c271342 100644 --- a/lib/charms/grafana_agent/v0/cos_agent.py +++ b/lib/charms/grafana_agent/v0/cos_agent.py @@ -254,7 +254,7 @@ class _MetricsEndpointDict(TypedDict): LIBID = "dc15fa84cef84ce58155fb84f6c6213a" LIBAPI = 0 -LIBPATCH = 19 +LIBPATCH = 20 PYDEPS = ["cosl >= 0.0.50", "pydantic"] @@ -758,6 +758,13 @@ def _dashboards(self) -> List[str]: # because there is currently no other way to communicate the dashboard path separately. # https://github.com/canonical/grafana-k8s-operator/pull/363 dashboard["uid"] = DashboardPath40UID.generate(self._charm.meta.name, rel_path) + + # Add tags + tags: List[str] = dashboard.get("tags", []) + if not any(tag.startswith("charm: ") for tag in tags): + tags.append(f"charm: {self._charm.meta.name}") + dashboard["tags"] = tags + dashboards.append(LZMABase64.compress(json.dumps(dashboard))) return dashboards diff --git a/lib/charms/rolling_ops/v0/rollingops.py b/lib/charms/rolling_ops/v0/rollingops.py index 57aa9bf352..13b51a3051 100644 --- a/lib/charms/rolling_ops/v0/rollingops.py +++ b/lib/charms/rolling_ops/v0/rollingops.py @@ -63,13 +63,14 @@ def _on_trigger_restart(self, event): juju run-action some-charm/0 some-charm/1 <... some-charm/n> restart ``` -Note that all units that plan to restart must receive the action and emit the aquire +Note that all units that plan to restart must receive the action and emit the acquire event. Any units that do not run their acquire handler will be left out of the rolling restart. (An operator might take advantage of this fact to recover from a failed rolling operation without restarting workloads that were able to successfully restart -- simply omit the successful units from a subsequent run-action call.) """ + import logging from enum import Enum from typing import AnyStr, Callable, Optional @@ -88,7 +89,7 @@ def _on_trigger_restart(self, event): # Increment this PATCH version before using `charmcraft publish-lib` or reset # to 0 if you are raising the major API version -LIBPATCH = 7 +LIBPATCH = 8 class LockNoRelationError(Exception): @@ -149,7 +150,6 @@ class Lock: """ def __init__(self, manager, unit=None): - self.relation = manager.model.relations[manager.name][0] if not self.relation: # TODO: defer caller in this case (probably just fired too soon). @@ -246,7 +246,7 @@ def __init__(self, manager): # Gather all the units. relation = manager.model.relations[manager.name][0] - units = [unit for unit in relation.units] + units = list(relation.units) # Plus our unit ... units.append(manager.model.unit) diff --git a/lib/charms/tempo_coordinator_k8s/v0/charm_tracing.py b/lib/charms/tempo_coordinator_k8s/v0/charm_tracing.py index ebf80ede2e..e2208f756f 100644 --- a/lib/charms/tempo_coordinator_k8s/v0/charm_tracing.py +++ b/lib/charms/tempo_coordinator_k8s/v0/charm_tracing.py @@ -325,7 +325,10 @@ def _remove_stale_otel_sdk_packages(): SpanExportResult, ) from opentelemetry.sdk.trace.export.in_memory_span_exporter import InMemorySpanExporter -from opentelemetry.trace import INVALID_SPAN, Tracer +from opentelemetry.trace import ( + INVALID_SPAN, + Tracer, +) from opentelemetry.trace import get_current_span as otlp_get_current_span from opentelemetry.trace import ( get_tracer, @@ -345,7 +348,7 @@ def _remove_stale_otel_sdk_packages(): # Increment this PATCH version before using `charmcraft publish-lib` or reset # to 0 if you are raising the major API version -LIBPATCH = 6 +LIBPATCH = 7 PYDEPS = ["opentelemetry-exporter-otlp-proto-http==1.21.0"] diff --git a/tests/integration/conftest.py b/tests/integration/conftest.py index bdce9d8e13..e644af55cd 100644 --- a/tests/integration/conftest.py +++ b/tests/integration/conftest.py @@ -1,8 +1,20 @@ # Copyright 2022 Canonical Ltd. # See LICENSE file for licensing details. +import logging +import os +import uuid + +import boto3 import pytest +from pytest_operator.plugin import OpsTest from . import architecture +from .helpers import construct_endpoint + +AWS = "AWS" +GCP = "GCP" + +logger = logging.getLogger(__name__) @pytest.fixture(scope="session") @@ -11,3 +23,74 @@ def charm(): # juju bundle files expect local charms to begin with `./` or `/` to distinguish them from # Charmhub charms. return f"./postgresql_ubuntu@22.04-{architecture.architecture}.charm" + + +def get_cloud_config(cloud: str) -> tuple[dict[str, str], dict[str, str]]: + # Define some configurations and credentials. + if cloud == AWS: + return { + "endpoint": "https://s3.amazonaws.com", + "bucket": "data-charms-testing", + "path": f"/postgresql-k8s/{uuid.uuid1()}", + "region": "us-east-1", + }, { + "access-key": os.environ["AWS_ACCESS_KEY"], + "secret-key": os.environ["AWS_SECRET_KEY"], + } + elif cloud == GCP: + return { + "endpoint": "https://storage.googleapis.com", + "bucket": "data-charms-testing", + "path": f"/postgresql-k8s/{uuid.uuid1()}", + "region": "", + }, { + "access-key": os.environ["GCP_ACCESS_KEY"], + "secret-key": os.environ["GCP_SECRET_KEY"], + } + + +def cleanup_cloud(config: dict[str, str], credentials: dict[str, str]) -> None: + # Delete the previously created objects. + logger.info("deleting the previously created backups") + session = boto3.session.Session( + aws_access_key_id=credentials["access-key"], + aws_secret_access_key=credentials["secret-key"], + region_name=config["region"], + ) + s3 = session.resource( + "s3", endpoint_url=construct_endpoint(config["endpoint"], config["region"]) + ) + bucket = s3.Bucket(config["bucket"]) + # GCS doesn't support batch delete operation, so delete the objects one by one. + for bucket_object in bucket.objects.filter(Prefix=config["path"].lstrip("/")): + bucket_object.delete() + + +@pytest.fixture(scope="module") +async def aws_cloud_configs(ops_test: OpsTest) -> None: + if ( + not os.environ.get("AWS_ACCESS_KEY", "").strip() + or not os.environ.get("AWS_SECRET_KEY", "").strip() + ): + pytest.skip("AWS configs not set") + return + + config, credentials = get_cloud_config(AWS) + yield config, credentials + + cleanup_cloud(config, credentials) + + +@pytest.fixture(scope="module") +async def gcp_cloud_configs(ops_test: OpsTest) -> None: + if ( + not os.environ.get("GCP_ACCESS_KEY", "").strip() + or not os.environ.get("GCP_SECRET_KEY", "").strip() + ): + pytest.skip("GCP configs not set") + return + + config, credentials = get_cloud_config(GCP) + yield config, credentials + + cleanup_cloud(config, credentials) diff --git a/tests/integration/test_backups_aws.py b/tests/integration/test_backups_aws.py index 76343af0ac..49ed3545a2 100644 --- a/tests/integration/test_backups_aws.py +++ b/tests/integration/test_backups_aws.py @@ -2,19 +2,16 @@ # Copyright 2023 Canonical Ltd. # See LICENSE file for licensing details. import logging -import os -import uuid -import boto3 import pytest as pytest from pytest_operator.plugin import OpsTest from tenacity import Retrying, stop_after_attempt, wait_exponential from . import architecture +from .conftest import AWS from .helpers import ( DATABASE_APP_NAME, backup_operations, - construct_endpoint, db_connect, get_password, get_primary, @@ -40,60 +37,12 @@ logger = logging.getLogger(__name__) -AWS = "AWS" -GCP = "GCP" - - -@pytest.fixture(scope="module") -async def cloud_configs() -> None: - # Define some configurations and credentials. - configs = { - AWS: { - "endpoint": "https://s3.amazonaws.com", - "bucket": "data-charms-testing", - "path": f"/postgresql-vm/{uuid.uuid1()}", - "region": "us-east-1", - }, - GCP: { - "endpoint": "https://storage.googleapis.com", - "bucket": "data-charms-testing", - "path": f"/postgresql-vm/{uuid.uuid1()}", - "region": "", - }, - } - credentials = { - AWS: { - "access-key": os.environ["AWS_ACCESS_KEY"], - "secret-key": os.environ["AWS_SECRET_KEY"], - }, - GCP: { - "access-key": os.environ["GCP_ACCESS_KEY"], - "secret-key": os.environ["GCP_SECRET_KEY"], - }, - } - yield configs, credentials - # Delete the previously created objects. - logger.info("deleting the previously created backups") - for cloud, config in configs.items(): - session = boto3.session.Session( - aws_access_key_id=credentials[cloud]["access-key"], - aws_secret_access_key=credentials[cloud]["secret-key"], - region_name=config["region"], - ) - s3 = session.resource( - "s3", endpoint_url=construct_endpoint(config["endpoint"], config["region"]) - ) - bucket = s3.Bucket(config["bucket"]) - # GCS doesn't support batch delete operation, so delete the objects one by one. - for bucket_object in bucket.objects.filter(Prefix=config["path"].lstrip("/")): - bucket_object.delete() - @pytest.mark.abort_on_fail -async def test_backup_aws(ops_test: OpsTest, cloud_configs: tuple[dict, dict], charm) -> None: +async def test_backup_aws(ops_test: OpsTest, aws_cloud_configs: tuple[dict, dict], charm) -> None: """Build and deploy two units of PostgreSQL in AWS, test backup and restore actions.""" - config = cloud_configs[0][AWS] - credentials = cloud_configs[1][AWS] + config = aws_cloud_configs[0] + credentials = aws_cloud_configs[1] await backup_operations( ops_test, diff --git a/tests/integration/test_backups_gcp.py b/tests/integration/test_backups_gcp.py index 63cb3617bd..d88fd894de 100644 --- a/tests/integration/test_backups_gcp.py +++ b/tests/integration/test_backups_gcp.py @@ -2,20 +2,18 @@ # Copyright 2023 Canonical Ltd. # See LICENSE file for licensing details. import logging -import os import uuid -import boto3 import pytest as pytest from pytest_operator.plugin import OpsTest from tenacity import Retrying, stop_after_attempt, wait_exponential from . import architecture +from .conftest import GCP from .helpers import ( CHARM_BASE, DATABASE_APP_NAME, backup_operations, - construct_endpoint, db_connect, get_password, get_unit_address, @@ -39,60 +37,12 @@ logger = logging.getLogger(__name__) -AWS = "AWS" -GCP = "GCP" - - -@pytest.fixture(scope="module") -async def cloud_configs() -> None: - # Define some configurations and credentials. - configs = { - AWS: { - "endpoint": "https://s3.amazonaws.com", - "bucket": "data-charms-testing", - "path": f"/postgresql-vm/{uuid.uuid1()}", - "region": "us-east-1", - }, - GCP: { - "endpoint": "https://storage.googleapis.com", - "bucket": "data-charms-testing", - "path": f"/postgresql-vm/{uuid.uuid1()}", - "region": "", - }, - } - credentials = { - AWS: { - "access-key": os.environ["AWS_ACCESS_KEY"], - "secret-key": os.environ["AWS_SECRET_KEY"], - }, - GCP: { - "access-key": os.environ["GCP_ACCESS_KEY"], - "secret-key": os.environ["GCP_SECRET_KEY"], - }, - } - yield configs, credentials - # Delete the previously created objects. - logger.info("deleting the previously created backups") - for cloud, config in configs.items(): - session = boto3.session.Session( - aws_access_key_id=credentials[cloud]["access-key"], - aws_secret_access_key=credentials[cloud]["secret-key"], - region_name=config["region"], - ) - s3 = session.resource( - "s3", endpoint_url=construct_endpoint(config["endpoint"], config["region"]) - ) - bucket = s3.Bucket(config["bucket"]) - # GCS doesn't support batch delete operation, so delete the objects one by one. - for bucket_object in bucket.objects.filter(Prefix=config["path"].lstrip("/")): - bucket_object.delete() - @pytest.mark.abort_on_fail -async def test_backup_gcp(ops_test: OpsTest, cloud_configs: tuple[dict, dict], charm) -> None: +async def test_backup_gcp(ops_test: OpsTest, gcp_cloud_configs: tuple[dict, dict], charm) -> None: """Build and deploy two units of PostgreSQL in GCP, test backup and restore actions.""" - config = cloud_configs[0][GCP] - credentials = cloud_configs[1][GCP] + config = gcp_cloud_configs[0] + credentials = gcp_cloud_configs[1] await backup_operations( ops_test, @@ -114,7 +64,9 @@ async def test_backup_gcp(ops_test: OpsTest, cloud_configs: tuple[dict, dict], c await ops_test.model.remove_application(tls_certificates_app_name, block_until_done=True) -async def test_restore_on_new_cluster(ops_test: OpsTest, charm) -> None: +async def test_restore_on_new_cluster( + ops_test: OpsTest, charm, gcp_cloud_configs: tuple[dict, dict] +) -> None: """Test that is possible to restore a backup to another PostgreSQL cluster.""" previous_database_app_name = f"{DATABASE_APP_NAME}-gcp" database_app_name = f"new-{DATABASE_APP_NAME}" @@ -212,7 +164,7 @@ async def test_restore_on_new_cluster(ops_test: OpsTest, charm) -> None: async def test_invalid_config_and_recovery_after_fixing_it( - ops_test: OpsTest, cloud_configs: tuple[dict, dict] + ops_test: OpsTest, gcp_cloud_configs: tuple[dict, dict] ) -> None: """Test that the charm can handle invalid and valid backup configurations.""" database_app_name = f"new-{DATABASE_APP_NAME}" @@ -243,10 +195,10 @@ async def test_invalid_config_and_recovery_after_fixing_it( logger.info( "configuring S3 integrator for a valid cloud, but with the path of another cluster repository" ) - await ops_test.model.applications[S3_INTEGRATOR_APP_NAME].set_config(cloud_configs[0][GCP]) + await ops_test.model.applications[S3_INTEGRATOR_APP_NAME].set_config(gcp_cloud_configs[0]) action = await ops_test.model.units.get(f"{S3_INTEGRATOR_APP_NAME}/0").run_action( "sync-s3-credentials", - **cloud_configs[1][GCP], + **gcp_cloud_configs[1], ) await action.wait() logger.info("waiting for the database charm to become blocked") @@ -257,7 +209,7 @@ async def test_invalid_config_and_recovery_after_fixing_it( # Provide valid backup configurations, with another path in the S3 bucket. logger.info("configuring S3 integrator for a valid cloud") - config = cloud_configs[0][GCP].copy() + config = gcp_cloud_configs[0].copy() config["path"] = f"/postgresql/{uuid.uuid1()}" await ops_test.model.applications[S3_INTEGRATOR_APP_NAME].set_config(config) logger.info("waiting for the database charm to become active") diff --git a/tests/integration/test_backups_pitr_aws.py b/tests/integration/test_backups_pitr_aws.py index 70da90c104..6220691ec9 100644 --- a/tests/integration/test_backups_pitr_aws.py +++ b/tests/integration/test_backups_pitr_aws.py @@ -2,19 +2,16 @@ # Copyright 2024 Canonical Ltd. # See LICENSE file for licensing details. import logging -import os -import uuid -import boto3 import pytest as pytest from pytest_operator.plugin import OpsTest from tenacity import Retrying, stop_after_attempt, wait_exponential from . import architecture +from .conftest import AWS from .helpers import ( CHARM_BASE, DATABASE_APP_NAME, - construct_endpoint, db_connect, get_password, get_primary, @@ -35,54 +32,6 @@ logger = logging.getLogger(__name__) -AWS = "AWS" -GCP = "GCP" - - -@pytest.fixture(scope="module") -async def cloud_configs() -> None: - # Define some configurations and credentials. - configs = { - AWS: { - "endpoint": "https://s3.amazonaws.com", - "bucket": "data-charms-testing", - "path": f"/postgresql-vm/{uuid.uuid1()}", - "region": "us-east-1", - }, - GCP: { - "endpoint": "https://storage.googleapis.com", - "bucket": "data-charms-testing", - "path": f"/postgresql-vm/{uuid.uuid1()}", - "region": "", - }, - } - credentials = { - AWS: { - "access-key": os.environ["AWS_ACCESS_KEY"], - "secret-key": os.environ["AWS_SECRET_KEY"], - }, - GCP: { - "access-key": os.environ["GCP_ACCESS_KEY"], - "secret-key": os.environ["GCP_SECRET_KEY"], - }, - } - yield configs, credentials - # Delete the previously created objects. - logger.info("deleting the previously created backups") - for cloud, config in configs.items(): - session = boto3.session.Session( - aws_access_key_id=credentials[cloud]["access-key"], - aws_secret_access_key=credentials[cloud]["secret-key"], - region_name=config["region"], - ) - s3 = session.resource( - "s3", endpoint_url=construct_endpoint(config["endpoint"], config["region"]) - ) - bucket = s3.Bucket(config["bucket"]) - # GCS doesn't support batch delete operation, so delete the objects one by one. - for bucket_object in bucket.objects.filter(Prefix=config["path"].lstrip("/")): - bucket_object.delete() - async def pitr_backup_operations( ops_test: OpsTest, @@ -376,10 +325,11 @@ async def pitr_backup_operations( @pytest.mark.abort_on_fail -async def test_pitr_backup_aws(ops_test: OpsTest, cloud_configs: tuple[dict, dict], charm) -> None: +async def test_pitr_backup_aws( + ops_test: OpsTest, gcp_cloud_configs: tuple[dict, dict], charm +) -> None: """Build, deploy two units of PostgreSQL and do backup in AWS. Then, write new data into DB, switch WAL file and test point-in-time-recovery restore action.""" - config = cloud_configs[0][AWS] - credentials = cloud_configs[1][AWS] + config, credentials = gcp_cloud_configs await pitr_backup_operations( ops_test, diff --git a/tests/integration/test_backups_pitr_gcp.py b/tests/integration/test_backups_pitr_gcp.py index e85ac25610..4194bdd068 100644 --- a/tests/integration/test_backups_pitr_gcp.py +++ b/tests/integration/test_backups_pitr_gcp.py @@ -2,19 +2,16 @@ # Copyright 2024 Canonical Ltd. # See LICENSE file for licensing details. import logging -import os -import uuid -import boto3 import pytest as pytest from pytest_operator.plugin import OpsTest from tenacity import Retrying, stop_after_attempt, wait_exponential from . import architecture +from .conftest import GCP from .helpers import ( CHARM_BASE, DATABASE_APP_NAME, - construct_endpoint, db_connect, get_password, get_primary, @@ -35,54 +32,6 @@ logger = logging.getLogger(__name__) -AWS = "AWS" -GCP = "GCP" - - -@pytest.fixture(scope="module") -async def cloud_configs() -> None: - # Define some configurations and credentials. - configs = { - AWS: { - "endpoint": "https://s3.amazonaws.com", - "bucket": "data-charms-testing", - "path": f"/postgresql-vm/{uuid.uuid1()}", - "region": "us-east-1", - }, - GCP: { - "endpoint": "https://storage.googleapis.com", - "bucket": "data-charms-testing", - "path": f"/postgresql-vm/{uuid.uuid1()}", - "region": "", - }, - } - credentials = { - AWS: { - "access-key": os.environ["AWS_ACCESS_KEY"], - "secret-key": os.environ["AWS_SECRET_KEY"], - }, - GCP: { - "access-key": os.environ["GCP_ACCESS_KEY"], - "secret-key": os.environ["GCP_SECRET_KEY"], - }, - } - yield configs, credentials - # Delete the previously created objects. - logger.info("deleting the previously created backups") - for cloud, config in configs.items(): - session = boto3.session.Session( - aws_access_key_id=credentials[cloud]["access-key"], - aws_secret_access_key=credentials[cloud]["secret-key"], - region_name=config["region"], - ) - s3 = session.resource( - "s3", endpoint_url=construct_endpoint(config["endpoint"], config["region"]) - ) - bucket = s3.Bucket(config["bucket"]) - # GCS doesn't support batch delete operation, so delete the objects one by one. - for bucket_object in bucket.objects.filter(Prefix=config["path"].lstrip("/")): - bucket_object.delete() - async def pitr_backup_operations( ops_test: OpsTest, @@ -376,10 +325,11 @@ async def pitr_backup_operations( @pytest.mark.abort_on_fail -async def test_pitr_backup_gcp(ops_test: OpsTest, cloud_configs: tuple[dict, dict], charm) -> None: +async def test_pitr_backup_gcp( + ops_test: OpsTest, gcp_cloud_configs: tuple[dict, dict], charm +) -> None: """Build, deploy two units of PostgreSQL and do backup in GCP. Then, write new data into DB, switch WAL file and test point-in-time-recovery restore action.""" - config = cloud_configs[0][GCP] - credentials = cloud_configs[1][GCP] + config, credentials = gcp_cloud_configs await pitr_backup_operations( ops_test, diff --git a/tests/integration/test_subordinates.py b/tests/integration/test_subordinates.py index c03288ae36..585bd765db 100644 --- a/tests/integration/test_subordinates.py +++ b/tests/integration/test_subordinates.py @@ -21,8 +21,18 @@ logger = logging.getLogger(__name__) +@pytest.fixture(scope="module") +async def check_subordinate_env_vars(ops_test: OpsTest) -> None: + if ( + not os.environ.get("UBUNTU_PRO_TOKEN", "").strip() + or not os.environ.get("LANDSCAPE_ACCOUNT_NAME", "").strip() + or not os.environ.get("LANDSCAPE_REGISTRATION_KEY", "").strip() + ): + pytest.skip("Subordinate configs not set") + + @pytest.mark.abort_on_fail -async def test_deploy(ops_test: OpsTest, charm: str): +async def test_deploy(ops_test: OpsTest, charm: str, check_subordinate_env_vars): await gather( ops_test.model.deploy( charm, @@ -60,7 +70,7 @@ async def test_deploy(ops_test: OpsTest, charm: str): ) -async def test_scale_up(ops_test: OpsTest): +async def test_scale_up(ops_test: OpsTest, check_subordinate_env_vars): await scale_application(ops_test, DATABASE_APP_NAME, 4) await ops_test.model.wait_for_idle( @@ -68,7 +78,7 @@ async def test_scale_up(ops_test: OpsTest): ) -async def test_scale_down(ops_test: OpsTest): +async def test_scale_down(ops_test: OpsTest, check_subordinate_env_vars): await scale_application(ops_test, DATABASE_APP_NAME, 3) await ops_test.model.wait_for_idle( From 220aafb6b1770fc7c0e289d5f2b8d84d55340cac Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Tue, 11 Mar 2025 12:19:00 +0100 Subject: [PATCH 57/74] Update dependency jinja2 to v3.1.6 [SECURITY] (#788) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- poetry.lock | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/poetry.lock b/poetry.lock index 61225ccb89..c776be907a 100644 --- a/poetry.lock +++ b/poetry.lock @@ -908,14 +908,14 @@ testing = ["Django", "attrs", "colorama", "docopt", "pytest (<9.0.0)"] [[package]] name = "jinja2" -version = "3.1.5" +version = "3.1.6" description = "A very fast and expressive template engine." optional = false python-versions = ">=3.7" groups = ["main", "integration"] files = [ - {file = "jinja2-3.1.5-py3-none-any.whl", hash = "sha256:aba0f4dc9ed8013c424088f68a5c226f7d6097ed89b246d7749c2ec4175c6adb"}, - {file = "jinja2-3.1.5.tar.gz", hash = "sha256:8fefff8dc3034e27bb80d67c671eb8a9bc424c0ef4c0826edbff304cceff43bb"}, + {file = "jinja2-3.1.6-py3-none-any.whl", hash = "sha256:85ece4451f492d0c13c5dd7c13a64681a86afae63a5f347908daf103ce6d2f67"}, + {file = "jinja2-3.1.6.tar.gz", hash = "sha256:0137fb05990d35f1275a587e9aee6d56da821fc83491a0fb838183be43f66d6d"}, ] [package.dependencies] From 11349301be06c286ab4a6b2ba7a1bc802bf4c33b Mon Sep 17 00:00:00 2001 From: Carl Csaposs Date: Tue, 11 Mar 2025 13:48:08 +0000 Subject: [PATCH 58/74] Reduce required approvals on Renovate pull requests by 1 (#787) --- .github/workflows/approve_renovate_pr.yaml | 15 +++++++++++++++ 1 file changed, 15 insertions(+) create mode 100644 .github/workflows/approve_renovate_pr.yaml diff --git a/.github/workflows/approve_renovate_pr.yaml b/.github/workflows/approve_renovate_pr.yaml new file mode 100644 index 0000000000..4449576ea3 --- /dev/null +++ b/.github/workflows/approve_renovate_pr.yaml @@ -0,0 +1,15 @@ +# Copyright 2025 Canonical Ltd. +# See LICENSE file for licensing details. +name: Approve Renovate pull request + +on: + pull_request: + types: + - opened + +jobs: + approve-pr: + name: Approve Renovate pull request + uses: canonical/data-platform-workflows/.github/workflows/approve_renovate_pr.yaml@v30.2.0 + permissions: + pull-requests: write # Needed to approve PR From e1ec7d3a8d284059d4185ef39f90c0fcb08a4d42 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Tue, 11 Mar 2025 15:01:35 +0100 Subject: [PATCH 59/74] Sync docs from Discourse (#748) Co-authored-by: GitHub Actions <41898282+github-actions[bot]@users.noreply.github.com> --- docs/explanation.md | 21 +++ docs/explanation/e-cryptography.md | 61 +++++++ docs/explanation/e-juju-details.md | 26 +-- docs/explanation/e-security.md | 92 ++++++++++ docs/how-to.md | 102 +++++++++++ docs/how-to/h-deploy-lxd.md | 44 ----- docs/how-to/h-deploy.md | 83 +++++++++ docs/how-to/h-upgrade.md | 12 ++ docs/overview.md | 45 ++--- .../{reference/r-overview.md => reference.md} | 10 +- docs/reference/r-releases.md | 47 ++--- docs/reference/r-revision-288.md | 50 ------ docs/reference/r-revision-336.md | 81 --------- docs/reference/r-revision-351.md | 57 ------ docs/reference/r-revision-363.md | 55 ------ docs/reference/r-revision-429-430.md | 98 ----------- docs/reference/r-revision-467-468.md | 155 ----------------- docs/reference/r-revision-544-545.md | 162 ------------------ docs/reference/r-system-requirements.md | 3 +- docs/{tutorial/t-overview.md => tutorial.md} | 2 +- 20 files changed, 441 insertions(+), 765 deletions(-) create mode 100644 docs/explanation.md create mode 100644 docs/explanation/e-cryptography.md create mode 100644 docs/explanation/e-security.md create mode 100644 docs/how-to.md delete mode 100644 docs/how-to/h-deploy-lxd.md create mode 100644 docs/how-to/h-deploy.md create mode 100644 docs/how-to/h-upgrade.md rename docs/{reference/r-overview.md => reference.md} (82%) delete mode 100644 docs/reference/r-revision-288.md delete mode 100644 docs/reference/r-revision-336.md delete mode 100644 docs/reference/r-revision-351.md delete mode 100644 docs/reference/r-revision-363.md delete mode 100644 docs/reference/r-revision-429-430.md delete mode 100644 docs/reference/r-revision-467-468.md delete mode 100644 docs/reference/r-revision-544-545.md rename docs/{tutorial/t-overview.md => tutorial.md} (98%) diff --git a/docs/explanation.md b/docs/explanation.md new file mode 100644 index 0000000000..8fc131b9e6 --- /dev/null +++ b/docs/explanation.md @@ -0,0 +1,21 @@ +# Explanation + +This section contains pages with more detailed explanations that provide additional context about some of the key concepts behind the PostgreSQL charm: + +* [Architecture] +* [Interfaces and endpoints] +* [Connection pooling] +* [Users] +* [Logs] +* [Juju] +* [Legacy charm] + + + +[Architecture]: /t/11857 +[Interfaces and endpoints]: /t/10251 +[Users]: /t/10798 +[Logs]: /t/12099 +[Juju]: /t/11985 +[Legacy charm]: /t/10690 +[Connection pooling]: /t/15777 \ No newline at end of file diff --git a/docs/explanation/e-cryptography.md b/docs/explanation/e-cryptography.md new file mode 100644 index 0000000000..51e3c6bed6 --- /dev/null +++ b/docs/explanation/e-cryptography.md @@ -0,0 +1,61 @@ +# Cryptography + +This document describes the cryptography used by Charmed PostgreSQL. + +## Resource checksums + +Charmed PostgreSQL and Charmed PgBouncer operators use pinned versions of the respective snaps to provide reproducible and secure environments. + +The snaps package their workloads along with the necessary dependencies and utilities required for the operators’ lifecycle. For more details, see the snaps content in the `snapcraft.yaml` file for [PostgreSQL](https://github.com/canonical/charmed-postgresql-snap/blob/14/edge/snap/snapcraft.yaml) and [PgBouncer](https://github.com/canonical/charmed-pgbouncer-snap/blob/1/edge/snap/snapcraft.yaml). + +Every artifact bundled into a snap is verified against its MD5, SHA256, or SHA512 checksum after download. The installation of certified snap into the rock is ensured by snap primitives that verify their squashfs filesystems images GPG signature. For more information on the snap verification process, refer to the [snapcraft.io documentation](https://snapcraft.io/docs/assertions). + +## Sources verification + +PostgreSQL and its extra components are built by Canonical from upstream source codes on [Launchpad](https://launchpad.net/ubuntu/+source/postgresql-common). PostgreSQL and PgBouncer are built as deb packages, other components - as PPAs. + +Charmed PostgreSQL and Charmed PgBouncer charms and snaps are published and released programmatically using release pipelines implemented via GitHub Actions in their respective repositories. + +All repositories in GitHub are set up with branch protection rules, requiring: + +* new commits to be merged to main branches via pull request with at least 2 approvals from repository maintainers +* new commits to be signed (e.g. using GPG keys) +* developers to sign the [Canonical Contributor License Agreement (CLA)](https://ubuntu.com/legal/contributors) + +## Encryption + +Charmed PostgreSQL can be used to deploy a secure PostgreSQL cluster that provides encryption-in-transit capabilities out of the box for: + +* Cluster internal communications +* PgBouncer connections +* External clients connections + +To set up a secure connection Charmed PostgreSQL and Charmed PgBouncer need to be integrated with TLS Certificate Provider charms, e.g. self-signed-certificates operator. Certificate Signing Requests (CSRs) are generated for every unit using the tls_certificates_interface library that uses the cryptography Python library to create X.509 compatible certificates. The CSR is signed by the TLS Certificate Provider, returned to the units, and stored in Juju secret. The relation also provides the CA certificate, which is loaded into Juju secret. + +Encryption at rest is currently not supported, although it can be provided by the substrate (cloud or on-premises). + +## Authentication + +In Charmed PostgreSQL, authentication layers can be enabled for: + +1. PgBouncer authentication to PostgreSQL +2. PostgreSQL cluster authentication +3. Clients authentication to PostgreSQL + +### PgBouncer authentication to PostgreSQL + +Authentication of PgBouncer to PostgreSQL is based on the password-based `scram-sha-256` authentication method. See the [PostgreSQL official documentation](https://www.postgresql.org/docs/14/auth-password.html) for more details. + +Credentials are exchanged via [Juju secrets](https://canonical-juju.readthedocs-hosted.com/en/latest/user/howto/manage-secrets/). + +### PostgreSQL cluster authentication + +Authentication among members of a PostgreSQL cluster is based on the password-based `scram-sha-256` authentication method. + +An internal user is used for this authentication with its hashed password stored in a system metadata database. These credentials are also stored as a plain text file on the disk of each unit for the Patroni HA service. + +### Clients authentication to PostgreSQL + +Authentication of clients to PostgreSQL is based on the password-based `scram-sha-256` authentication method. See the [PostgreSQL official documentation](https://www.postgresql.org/docs/14/auth-password.html) for more details. + +Credentials are exchanged via [Juju secrets](https://canonical-juju.readthedocs-hosted.com/en/latest/user/howto/manage-secrets/). \ No newline at end of file diff --git a/docs/explanation/e-juju-details.md b/docs/explanation/e-juju-details.md index 65f3b702cd..a8554674eb 100644 --- a/docs/explanation/e-juju-details.md +++ b/docs/explanation/e-juju-details.md @@ -1,13 +1,15 @@ -# Juju tech details +# Juju [Juju](https://juju.is/) is an open source orchestration engine for software operators that enables the deployment, integration and lifecycle management of applications at any scale, on any infrastructure using charms. -This [charm](https://charmhub.io/postgresql) is an operator - business logic encapsulated in reusable software packages that automate every aspect of an application's life. Charms are shared via [CharmHub](https://charmhub.io/). +> See also: [Juju client documentation](https://juju.is/docs/juju), [Juju blog](https://ubuntu.com/blog/tag/juju) -See also: +## Compatibility with PostgreSQL -* [Juju Documentation](https://juju.is/docs/juju) and [Blog](https://ubuntu.com/blog/tag/juju) -* [Charm SDK](https://juju.is/docs/sdk) +Current stable releases of this charm can still be deployed on Juju 2.9. However, newer features are not supported. +> See the [Releases page](/t/11875) for more information about the minimum Juju version required to operate the features of each revision. + +Additionally, there are limitations regarding integrations with other charms. For example, integration with [modern TLS charms](https://charmhub.io/topics/security-with-x-509-certificates) requires Juju 3.x. ## Breaking changes between Juju 2.9.x and 3.x @@ -15,18 +17,18 @@ As this charm documentation is written for Juju 3.x, users of 2.9.x will encount Breaking changes have been introduced in the Juju client between versions 2.9.x and 3.x. These are caused by the renaming and re-purposing of several commands - functionality and command options remain unchanged. -In the context of this guide, the pertinent changes are shown here: +In the context of this guide, the pertinent changes are as follows: -|2.9.x|3.x| +| v2.9.x | v3.x | | --- | --- | -|**add-relation**|**integrate**| -|**relate**|**integrate**| -|**run**|**exec**| -|**run-action --wait**|**run**| +|`add-relation`|`integrate`| +|`relate`|`integrate`| +|`run`|`exec`| +|`run-action --wait`|`run`| See the [Juju 3.0 release notes](https://juju.is/docs/juju/roadmap#heading--juju-3-0-0---22-oct-2022) for the comprehensive list of changes. -The response is to therefore substitute the documented command with the equivalent 2.9.x command. For example: +Example substitutions: ### Juju 3.x: ```shell diff --git a/docs/explanation/e-security.md b/docs/explanation/e-security.md new file mode 100644 index 0000000000..24c97804cc --- /dev/null +++ b/docs/explanation/e-security.md @@ -0,0 +1,92 @@ +# Security hardening guide + +This document provides an overview of security features and guidance for hardening the security of [Charmed PostgreSQL](https://charmhub.io/postgresql) deployments, including setting up and managing a secure environment. + +## Environment + +The environment where Charmed PostgreSQL operates can be divided into two components: + +1. Cloud +2. Juju + +### Cloud + +Charmed PostgreSQL can be deployed on top of several clouds and virtualization layers: + +|Cloud|Security guides| +| --- | --- | +|OpenStack|[OpenStack Security Guide](https://docs.openstack.org/security-guide/)| +|AWS|[Best Practices for Security, Identity and Compliance](https://aws.amazon.com/architecture/security-identity-compliance), [AWS security credentials](https://docs.aws.amazon.com/IAM/latest/UserGuide/security-creds.html#access-keys-and-secret-access-keys)| +|Azure|[Azure security best practices and patterns](https://learn.microsoft.com/en-us/azure/security/fundamentals/best-practices-and-patterns), [Managed identities for Azure resource](https://learn.microsoft.com/en-us/entra/identity/managed-identities-azure-resources/)| +|GCP|[Google security overview](https://cloud.google.com/docs/security)| + +### Juju + +Juju is the component responsible for orchestrating the entire lifecycle, from deployment to Day 2 operations. For more information on Juju security hardening, see the [Juju security page](https://canonical-juju.readthedocs-hosted.com/en/latest/user/explanation/juju-security/) and the [How to harden your deployment](https://juju.is/docs/juju/harden-your-deployment) guide. + +#### Cloud credentials + +When configuring cloud credentials to be used with Juju, ensure that users have correct permissions to operate at the required level. Juju superusers responsible for bootstrapping and managing controllers require elevated permissions to manage several kinds of resources, such as virtual machines, networks, storages, etc. Please refer to the links below for more information on the policies required to be used depending on the cloud. + +|Cloud|Cloud user policies| +| --- | --- | +|OpenStack|N/A| +|AWS|[Juju AWS Permission](/t/juju-aws-permissions/5307), [AWS Instance Profiles](/t/using-aws-instance-profiles-with-juju-2-9/5185), [Juju on AWS](https://juju.is/docs/juju/amazon-ec2)| +|Azure|[Juju Azure Permission](https://juju.is/docs/juju/microsoft-azure), [How to use Juju with Microsoft Azure](/t/how-to-use-juju-with-microsoft-azure/15219)| +|GCP|[Google Cloud's Identity and Access Management](https://cloud.google.com/iam/docs/overview), [GCE role recommendations](https://cloud.google.com/policy-intelligence/docs/role-recommendations-overview), [Google GCE cloud and Juju](https://canonical-juju.readthedocs-hosted.com/en/latest/user/reference/cloud/list-of-supported-clouds/the-google-gce-cloud-and-juju/)| + +#### Juju users + +It is very important that Juju users are set up with minimal permissions depending on the scope of their operations. Please refer to the [User access levels](https://juju.is/docs/juju/user-permissions) documentation for more information on the access levels and corresponding abilities. + +Juju user credentials must be stored securely and rotated regularly to limit the chances of unauthorized access due to credentials leakage. + +## Applications + +In the following sections, we provide guidance on how to harden your deployment using: + +1. Operating system +2. Security upgrades +3. Encryption +4. Authentication +5. Monitoring and auditing + +### Operating system + +Charmed PostgreSQL and Charmed PgBouncer run on top of Ubuntu 22.04. Deploy a [Landscape Client Charm](https://charmhub.io/landscape-client?) to connect the underlying VM to a Landscape User Account to manage security upgrades and integrate [Ubuntu Pro](https://ubuntu.com/pro) subscriptions. + +### Security upgrades + +[Charmed PostgreSQL](https://charmhub.io/postgresql) and [Charmed PgBouncer](https://charmhub.io/pgbouncer) operators install pinned versions of their respective snaps to provide reproducible and secure environments. + +New versions (revisions) of the charmed operators can be released to update the operator's code, workloads, or both. It is important to refresh the charms regularly to make sure the workloads are as secure as possible. + +For more information on upgrading Charmed PostgreSQL, see the [How to upgrade PostgreSQL](https://canonical.com/data/docs/postgresql/iaas/h-upgrade) and [How to upgrade PgBouncer](https://charmhub.io/pgbouncer/docs/h-upgrade) guides, as well as the respective Release notes for [PostgreSQL](https://canonical.com/data/docs/postgresql/iaas/r-releases) and [PgBouncer](https://charmhub.io/pgbouncer/docs/r-releases). + +### Encryption + +To utilise encryption at transit for all internal and external cluster connections, integrate Charmed PostgreSQL with a TLS certificate provider. Please refer to the [Charming Security page](https://charmhub.io/topics/security-with-x-509-certificates) for more information on how to select the right certificate provider for your use case. + +Encryption in transit for backups is provided by the storage service (Charmed PostgreSQL is a client for an S3-compatible storage). + +For more information on encryption, see the [Cryptography](/t/charmed-postgresql-explanations-encryption/16853) explanation page and [How to enable encryption](https://canonical.com/data/docs/postgresql/iaas/h-enable-tls) guide. + +### Authentication + +Charmed PostgreSQL supports the password-based `scram-sha-256` authentication method for authentication between: + +* External connections to clients +* Internal connections between members of cluster +* PgBouncer connections + +For more implementation details, see the [PostgreSQL documentation](https://www.postgresql.org/docs/14/auth-password.html). + +### Monitoring and auditing + +Charmed PostgreSQL provides native integration with the [Canonical Observability Stack (COS)](https://charmhub.io/topics/canonical-observability-stack). To reduce the blast radius of infrastructure disruptions, the general recommendation is to deploy COS and the observed application into separate environments, isolated from one another. Refer to the [COS production deployments best practices](https://charmhub.io/topics/canonical-observability-stack/reference/best-practices) for more information or see the How to guides for PostgreSQL [monitoring](https://canonical.com/data/docs/postgresql/iaas/h-enable-monitoring), [alert rules](https://canonical.com/data/docs/postgresql/iaas/h-enable-alert-rules), and [tracing](https://canonical.com/data/docs/postgresql/iaas/h-enable-tracing) for practical instructions. + +PostgreSQL logs are stored in `/var/snap/charmed-postgresql/common/var/log/postgresql` within the postgresql container of each unit. It’s recommended to integrate the charm with [COS](/t/10600), from where the logs can be easily persisted and queried using [Loki](https://charmhub.io/loki-k8s)/[Grafana](https://charmhub.io/grafana). + +## Additional Resources + +For details on the cryptography used by Charmed PostgreSQL, see the [Cryptography](/t/charmed-postgresql-explanations-encryption/16853) explanation page. \ No newline at end of file diff --git a/docs/how-to.md b/docs/how-to.md new file mode 100644 index 0000000000..497d33ce18 --- /dev/null +++ b/docs/how-to.md @@ -0,0 +1,102 @@ +# How-to guides + +The following guides cover key processes and common tasks for managing and using Charmed PostgreSQL on machines. + +## Deployment and setup + +The following guides walk you through the details of how to install different cloud services and bootstrap them to Juju: +* [Sunbeam] +* [LXD] +* [MAAS] +* [AWS EC2] +* [GCE] +* [Azure] +* [Multi-availability zones (AZ)][Multi-AZ] + +The following guides cover some specific deployment scenarios and architectures: +* [Terraform] +* [Air-gapped] +* [TLS VIP access] + +## Usage and maintenance + +* [Integrate with another application] +* [External access] +* [Scale replicas] +* [Enable TLS] +* [Enable plugins/extensions] + +## Backup and restore +* [Configure S3 AWS] +* [Configure S3 RadosGW] +* [Create a backup] +* [Restore a backup] +* [Manage backup retention] +* [Migrate a cluster] + +## Monitoring (COS) + +* [Enable monitoring] +* [Enable alert rules] +* [Enable tracing] + +## Minor upgrades +* [Perform a minor upgrade] +* [Perform a minor rollback] + +## Cross-regional (cluster-cluster) async replication + +* [Cross-regional async replication] + * [Set up clusters] + * [Integrate with a client app] + * [Remove or recover a cluster] + * [Enable plugins/extensions] + +## Development + +This section is aimed at charm developers looking to support PostgreSQL integrations with their charm. + +* [Integrate with your charm] +* [Migrate data via pg_dump] +* [Migrate data via backup/restore] + + + +[Sunbeam]: /t/15972 +[LXD]: /t/11861 +[MAAS]: /t/14293 +[AWS EC2]: /t/15703 +[GCE]: /t/15722 +[Azure]: /t/15733 +[Multi-AZ]: /t/15749 +[Terraform]: /t/14916 +[Air-gapped]: /t/15746 +[TLS VIP access]: /t/16576 +[Integrate with another application]: /t/9687 +[External access]: /t/15802 +[Scale replicas]: /t/9689 +[Enable TLS]: /t/9685 + +[Configure S3 AWS]: /t/9681 +[Configure S3 RadosGW]: /t/10313 +[Create a backup]: /t/9683 +[Restore a backup]: /t/9693 +[Manage backup retention]: /t/14249 +[Migrate a cluster]: /t/9691 + +[Enable monitoring]: /t/10600 +[Enable alert rules]: /t/13084 +[Enable tracing]: /t/14521 + +[Perform a minor upgrade]: /t/12089 +[Perform a minor rollback]: /t/12090 + +[Cross-regional async replication]: /t/15412 +[Set up clusters]: /t/13991 +[Integrate with a client app]: /t/13992 +[Remove or recover a cluster]: /t/13994 +[Enable plugins/extensions]: /t/10906 + +[Integrate with your charm]: /t/11865 +[Migrate data via pg_dump]: /t/12163 +[Migrate data via backup/restore]: /t/12164 \ No newline at end of file diff --git a/docs/how-to/h-deploy-lxd.md b/docs/how-to/h-deploy-lxd.md deleted file mode 100644 index 02a1b4323c..0000000000 --- a/docs/how-to/h-deploy-lxd.md +++ /dev/null @@ -1,44 +0,0 @@ -# How to deploy on LXD - -This guide assumes you have a running Juju and LXD environment. - -For a detailed walkthrough of setting up an environment and deploying the charm on LXD, refer to the [Tutorial](/t/9707). - -## Prerequisites -* Canonical LXD 5.12+ -* Fulfil the general [system requirements](/t/11743) - ---- - -[Bootstrap](https://juju.is/docs/juju/juju-bootstrap) a juju controller and create a [model](https://juju.is/docs/juju/juju-add-model) if you haven't already: -```shell -juju bootstrap localhost -juju add-model -``` - -Deploy PostgreSQL: -```shell -juju deploy postgresql -``` -> See the [`juju deploy` documentation](https://juju.is/docs/juju/juju-deploy) for all available options at deploy time. -> -> See the [Configurations tab](https://charmhub.io/postgresql/configurations) for specific PostgreSQL parameters. - -Sample output of `juju status --watch 1s`: -```shell -Model Controller Cloud/Region Version SLA Timestamp -postgresql overlord localhost/localhost 2.9.42 unsupported 09:41:53+01:00 - -App Version Status Scale Charm Channel Rev Exposed Message -postgresql active 1 postgresql 14/stable 281 no - -Unit Workload Agent Machine Public address Ports Message -postgresql/0* active idle 0 10.89.49.129 - -Machine State Address Inst id Series AZ Message -0 started 10.89.49.129 juju-a8a31d-0 jammy Running -``` - -[note] -If you expect having several concurrent connections frequently, it is highly recommended to deploy [PgBouncer](https://charmhub.io/pgbouncer?channel=1/stable) alongside PostgreSQL. For more information, read our explanation about [Connection pooling](/t/15777). -[/note] \ No newline at end of file diff --git a/docs/how-to/h-deploy.md b/docs/how-to/h-deploy.md new file mode 100644 index 0000000000..4769d863c6 --- /dev/null +++ b/docs/how-to/h-deploy.md @@ -0,0 +1,83 @@ +# How to deploy + +This page aims to provide an introduction to the PostgreSQL deployment process and lists all the related guides. It contains the following sections: +* [General deployment instructions](#general-deployment-instructions) +* [Clouds](#clouds) +* [Special deployments](#special-deployments) + +--- + +## General deployment instructions + +The basic requirements for deploying a charm are the [**Juju client**](https://juju.is/docs/juju) and a machine [**cloud**](https://juju.is/docs/juju/cloud). + +First, [bootstrap](https://juju.is/docs/juju/juju-bootstrap) the cloud controller and create a [model](https://canonical-juju.readthedocs-hosted.com/en/latest/user/reference/model/): +```shell +juju bootstrap +juju add-model +``` + +Then, either continue with the `juju` client **or** use the `terraform juju` client to deploy the PostgreSQL charm. + +To deploy with the `juju` client: +```shell +juju deploy postgresql +``` +> See also: [`juju deploy` command](https://canonical-juju.readthedocs-hosted.com/en/latest/user/reference/juju-cli/list-of-juju-cli-commands/deploy/) + +To deploy with `terraform juju`, follow the guide [How to deploy using Terraform]. +> See also: [Terraform Provider for Juju documentation](https://canonical-terraform-provider-juju.readthedocs-hosted.com/en/latest/) + +If you are not sure where to start or would like a more guided walkthrough for setting up your environment, see the [Charmed PostgreSQL tutorial][Tutorial]. + +## Clouds + +The guides below go through the steps to install different cloud services and bootstrap them to Juju: +* [Sunbeam] +* [Canonical MAAS] +* [Amazon Web Services EC2] +* [Google Cloud Engine] +* [Azure] + +[How to deploy on multiple availability zones (AZ)] demonstrates how to deploy a cluster on a cloud using different AZs for high availability. + +## Special deployments + +These guides cover some specific deployment scenarios and architectures. + +### External TLS access +[How to deploy for external TLS VIP access] goes over an example deployment of PostgreSQL, PgBouncer and HAcluster that require external TLS/SSL access via [Virtual IP (VIP)](https://en.wikipedia.org/wiki/Virtual_IP_address). + +See also: +* [How to enable TLS] +* [How to connect from outside the local network] + +### Airgapped +[How to deploy in an offline or air-gapped environment] goes over the special configuration steps for installing PostgreSQL in an airgapped environment via CharmHub and the Snap Store Proxy. + +### Cluster-cluster replication +Cluster-cluster, cross-regional, or multi-server asynchronous replication focuses on disaster recovery by distributing data across different servers. + +The [Cross-regional async replication] guide goes through the steps to set up clusters for cluster-cluster replication, integrate with a client, and remove or recover a failed cluster. + +--- + + + +[Tutorial]: /t/9707 + +[How to deploy using Terraform]: /t/14916 + +[Sunbeam]: /t/15972 +[Canonical MAAS]: /t/14293 +[Amazon Web Services EC2]: /t/15703 +[Google Cloud Engine]: /t/15722 +[Azure]: /t/15733 +[How to deploy on multiple availability zones (AZ)]: /t/15749 + +[How to deploy for external TLS VIP access]: /t/16576 +[How to enable TLS]: /t/9685 +[How to connect from outside the local network]: /t/15802 + +[How to deploy in an offline or air-gapped environment]: /t/15746 +[Cross-regional async replication]: /t/15412 \ No newline at end of file diff --git a/docs/how-to/h-upgrade.md b/docs/how-to/h-upgrade.md new file mode 100644 index 0000000000..9a95915cac --- /dev/null +++ b/docs/how-to/h-upgrade.md @@ -0,0 +1,12 @@ +# Upgrade + +Currently, the charm supports PostgreSQL major version 14 only. Therefore, in-place upgrades/rollbacks are not possible for major versions. + +> **Note**: Canonical is not planning to support in-place upgrades for major version change. The new PostgreSQL charm will have to be installed nearby, and the data will be copied from the old to the new installation. After announcing the next PostgreSQL major version support, the appropriate documentation for data migration will be published. + +For instructions on carrying out **minor version upgrades**, see the following guides: + +* [Minor upgrade](/t/12089), e.g. PostgreSQL 14.8 -> PostgreSQL 14.9
+(including charm revision bump 42 -> 43). +* [Minor rollback](/t/12090), e.g. PostgreSQL 14.9 -> PostgreSQL 14.8
+(including charm revision return 43 -> 42). \ No newline at end of file diff --git a/docs/overview.md b/docs/overview.md index 949bf3c098..273a9b0f53 100644 --- a/docs/overview.md +++ b/docs/overview.md @@ -41,8 +41,7 @@ PostgreSQL is a trademark or registered trademark of PostgreSQL Global Developme | Level | Path | Navlink | |--------|--------|-------------| -| 1 | tutorial | [Tutorial]() | -| 2 | t-overview | [Overview](/t/9707) | +| 1 | tutorial | [Tutorial](/t/9707) | | 2 | t-set-up | [1. Set up environment](/t/9709) | | 2 | t-deploy | [2. Deploy PostgreSQL](/t/9697) | | 2 | t-access| [3. Access PostgreSQL](/t/15798) | @@ -51,10 +50,9 @@ PostgreSQL is a trademark or registered trademark of PostgreSQL Global Developme | 2 | t-integrate | [6. Integrate with other applications](/t/9701) | | 2 | t-enable-tls | [7. Enable TLS encryption](/t/9699) | | 2 | t-clean-up | [8. Clean up environment](/t/9695) | -| 1 | how-to | [How to]() | -| 2 | h-deploy | [Deploy]() | +| 1 | how-to | [How-to guides](/t/16766) | +| 2 | h-deploy | [Deploy](/t/16811) | | 3 | h-deploy-sunbeam | [Sunbeam](/t/15972) | -| 3 | h-deploy-lxd | [LXD](/t/11861) | | 3 | h-deploy-maas | [MAAS](/t/14293) | | 3 | h-deploy-ec2 | [AWS EC2](/t/15703) | | 3 | h-deploy-gce | [GCE](/t/15722) | @@ -64,9 +62,10 @@ PostgreSQL is a trademark or registered trademark of PostgreSQL Global Developme | 3 | h-deploy-airgapped | [Air-gapped](/t/15746) | | 3 | h-deploy-tls-vip-access | [TLS VIP access](/t/16576) | | 2 | h-integrate | [Integrate with another application](/t/9687) | -| 2 | h-external-access | [External access](/t/15802) | +| 2 | h-external-access | [External network access](/t/15802) | | 2 | h-scale | [Scale replicas](/t/9689) | | 2 | h-enable-tls | [Enable TLS](/t/9685) | +| 2 | h-enable-plugins-extensions | [Enable plugins/extensions](/t/10906) | | 2 | h-backup | [Back up and restore]() | | 3 | h-configure-s3-aws | [Configure S3 AWS](/t/9681) | | 3 | h-configure-s3-radosgw | [Configure S3 RadosGW](/t/10313) | @@ -78,28 +77,19 @@ PostgreSQL is a trademark or registered trademark of PostgreSQL Global Developme | 3 | h-enable-monitoring | [Enable monitoring](/t/10600) | | 3 | h-enable-alert-rules | [Enable alert rules](/t/13084) | | 3 | h-enable-tracing | [Enable tracing](/t/14521) | -| 2 | h-upgrade | [Minor upgrades]() | +| 2 | h-upgrade | [Upgrade](/t/12086) | | 3 | h-upgrade-minor | [Perform a minor upgrade](/t/12089) | | 3 | h-rollback-minor | [Perform a minor rollback](/t/12090) | | 2 | h-async | [Cross-regional async replication](/t/15412) | | 3 | h-async-set-up | [Set up clusters](/t/13991) | | 3 | h-async-integrate | [Integrate with a client app](/t/13992) | | 3 | h-async-remove-recover | [Remove or recover a cluster](/t/13994) | -| 2 | h-enable-plugins-extensions | [Enable plugins/extensions](/t/10906) | | 2 | h-development| [Development]() | | 3 | h-development-integrate | [Integrate with your charm](/t/11865) | | 3 | h-migrate-pgdump | [Migrate data via pg_dump](/t/12163) | | 3 | h-migrate-backup-restore | [Migrate data via backup/restore](/t/12164) | -| 1 | reference | [Reference]() | -| 2 | r-overview | [Overview](/t/13976) | -| 2 | r-releases | [Release Notes](/t/11875) | -| 3 | r-revision-544-545| [Revision 544/545](/t/16007) | -| 3 | r-revision-467-468 | [Revision 467/468](/t/15378) | -| 3 | r-revision-429-430 | [Revision 429/430](/t/14067) | -| 3 | r-revision-363 | [Revision 363](/t/13124) | -| 3 | r-revision-351 | [Revision 351](/t/12823) | -| 3 | r-revision-336 | [Revision 336](/t/11877) | -| 3 | r-revision-288 | [Revision 288](/t/11876) | +| 1 | reference | [Reference](/t/13976) | +| 2 | r-releases | [Releases](/t/11875) | | 2 | r-system-requirements | [System requirements](/t/11743) | | 2 | r-software-testing | [Software testing](/t/11773) | | 2 | r-performance | [Performance and resources](/t/11974) | @@ -108,20 +98,31 @@ PostgreSQL is a trademark or registered trademark of PostgreSQL Global Developme | 2 | r-alert-rules | [Alert rules](/t/15841) | | 2 | r-statuses | [Statuses](/t/10844) | | 2 | r-contacts | [Contacts](/t/11863) | -| 1 | explanation | [Explanation]() | +| 1 | explanation | [Explanation](/t/16768) | | 2 | e-architecture | [Architecture](/t/11857) | +| 2 | e-security | [Security](/t/16852) | +| 2 | e-cryptography | [Cryptography](/t/16853) | | 2 | e-interfaces-endpoints | [Interfaces and endpoints](/t/10251) | +| 2 | e-connection-pooling| [Connection pooling](/t/15777) | | 2 | e-users | [Users](/t/10798) | | 2 | e-logs | [Logs](/t/12099) | | 2 | e-juju-details | [Juju](/t/11985) | | 2 | e-legacy-charm | [Legacy charm](/t/10690) | -| 2 | e-connection-pooling| [Connection pooling](/t/15777) | | 1 | search | [Search](https://canonical.com/data/docs/postgresql/iaas) | [/details] - \ No newline at end of file diff --git a/docs/reference/r-overview.md b/docs/reference.md similarity index 82% rename from docs/reference/r-overview.md rename to docs/reference.md index 8392dce767..f686a8f86b 100644 --- a/docs/reference/r-overview.md +++ b/docs/reference.md @@ -1,4 +1,4 @@ -# Overview +# Reference The Reference section of our documentation contains pages for technical specifications, APIs, release notes, and other reference material for fast lookup. @@ -7,14 +7,16 @@ The Reference section of our documentation contains pages for technical specific |---------------------------|---------------------------------------------------| | [Release Notes](/t/11875) | Release notes for major revisions of this charm | | [System requirements](/t/11743) | Software and hardware requirements | -| [Testing](/t/11773) | Software tests (e.g. smoke, unit, performance...) | +| [Software testing](/t/11773) | Software tests (e.g. smoke, unit, performance...) | +| [Performance and resources](/t/11974) | Config profiles related to performance | | [Troubleshooting](/t/11864) | Troubleshooting tips and tricks | -| [Profiles](/t/11974) | Config profiles related to performance | | [Plugins/extensions](/t/10946) | Plugins/extensions supported by each charm revision | +| [Alert rules](/t/15841) | Pre-configured Prometheus alert rules | +| [Charm statuses](/t/10844) | Juju application statuses | | [Contacts](/t/11863) | Contact information | -**In the tabs at the top of the page**, you can find the following automatically generated API references: +**In the tabs at the top of the page**, you will find the following automatically generated API references: | Page | Description | |----------------------------------------------------------------------------|---------------------------------------------------------| diff --git a/docs/reference/r-releases.md b/docs/reference/r-releases.md index 5c4b104362..f67d6fc623 100644 --- a/docs/reference/r-releases.md +++ b/docs/reference/r-releases.md @@ -1,4 +1,4 @@ -# Release Notes +# Releases This page provides high-level overviews of the dependencies and features that are supported by each revision in every stable release. @@ -9,14 +9,14 @@ To see all releases and commits, check the [Charmed PostgreSQL Releases page on ## Dependencies and supported features For a given release, this table shows: -* The PostgreSQL version packaged inside +* The PostgreSQL version packaged inside. * The minimum Juju 3 version required to reliably operate **all** features of the release > This charm still supports older versions of Juju down to 2.9. See the [Juju section of the system requirements](/t/11743) for more details. * Support for specific features | Release | PostgreSQL version | Juju 3 version | [TLS encryption](/t/9685)* | [COS monitoring](/t/10600) | [Minor version upgrades](/t/12089) | [Cross-regional async replication](/t/15412) | [Point-in-time recovery](/t/9693) | |:---:|:---:|:---:|:---:|:---:|:---:|:---:|:---:| -| [544], [545] (14/candidate) | 14.15 | `3.6.1+` | ![check] | ![check] | ![check] | ![check] | ![check] | +| [552], [553] | 14.15 | `3.6.1+` | ![check] | ![check] | ![check] | ![check] | ![check] | | [467], [468] | 14.12 | `3.4.3+` | ![check] | ![check] | ![check] | ![check] | ![check] | | [429], [430] | 14.11 | `3.4.2+` | ![check] | ![check] | ![check] | ![check] | | | [363] | 14.10 | `3.4.2+` | ![check] | ![check] | ![check] | ![check] | | @@ -36,23 +36,22 @@ Several [revisions](https://juju.is/docs/sdk/revision) are released simultaneous > If you deploy a specific revision, **you must make sure it matches your base and architecture** via the tables below or with [`juju info`](https://juju.is/docs/juju/juju-info) - +|[553] | ![check] | | ![check] | +|[552] | | ![check] | ![check] | -### Release 467-468 (`14/stable`) +[details=Older releases] + +### Release 467-468 | Revision | amd64 | arm64 | Ubuntu 22.04 LTS |:--------:|:-----:|:-----:|:-----:| |[468] |![check] | | ![check] | |[467] | | ![check]| ![check] | -[details=Older releases] ### Release 429-430 | Revision | amd64 | arm64 | Ubuntu 22.04 LTS @@ -93,21 +92,23 @@ Several [revisions](https://juju.is/docs/sdk/revision) are released simultaneous For a list of all plugins supported for each revision, see the reference page [Plugins/extensions](/t/10946). -[note] - Our release notes are an ongoing work in progress. If there is any additional information about releases that you would like to see or suggestions for other improvements, don't hesitate to contact us on [Matrix ](https://matrix.to/#/#charmhub-data-platform:ubuntu.com) or [leave a comment](https://discourse.charmhub.io/t/charmed-postgresql-reference-release-notes/11875). -[/note] +> **Note**: Our release notes are an ongoing work in progress. If there is any additional information about releases that you would like to see or suggestions for other improvements, don't hesitate to contact us on [Matrix ](https://matrix.to/#/#charmhub-data-platform:ubuntu.com) or [leave a comment](https://discourse.charmhub.io/t/charmed-postgresql-reference-release-notes/11875). -[545]: /t/16007 -[544]: /t/16007 -[468]: /t/15378 -[467]: /t/15378 -[430]: /t/14067 -[429]: /t/14067 -[363]: /t/13124 -[351]: /t/12823 -[336]: /t/11877 -[288]: /t/11876 +[553]: https://github.com/canonical/postgresql-operator/releases/tag/rev552 +[552]: https://github.com/canonical/postgresql-operator/releases/tag/rev552 + +[468]: https://github.com/canonical/postgresql-operator/releases/tag/rev467 +[467]: https://github.com/canonical/postgresql-operator/releases/tag/rev467 + +[430]: https://github.com/canonical/postgresql-operator/releases/tag/rev429 +[429]: https://github.com/canonical/postgresql-operator/releases/tag/rev429 + +[363]: https://github.com/canonical/postgresql-operator/releases/tag/rev363 +[351]: https://github.com/canonical/postgresql-operator/releases/tag/rev351 +[336]: https://github.com/canonical/postgresql-operator/releases/tag/rev336 +[288]: https://github.com/canonical/postgresql-operator/releases/tag/rev288 + [check]: https://img.icons8.com/color/20/checkmark--v1.png \ No newline at end of file diff --git a/docs/reference/r-revision-288.md b/docs/reference/r-revision-288.md deleted file mode 100644 index 3297b52ac6..0000000000 --- a/docs/reference/r-revision-288.md +++ /dev/null @@ -1,50 +0,0 @@ ->Reference > Release Notes > [All revisions](/t/11875) > [Revision 288](/t/11876) -# Revision 288 -Thursday, April 20, 2023 - -Dear community, - -We'd like to announce that Canonical's newest Charmed PostgreSQL operator for IAAS/VM has been published in the `14/stable` [channel](https://charmhub.io/postgresql?channel=14/stable). :tada: - -## Features you can start using today -* Deploying on VM (tested with LXD, MAAS) -* Scaling up/down in one simple juju command -* HA using [Patroni](https://github.com/zalando/patroni) -* Full backups and restores are supported when using any S3-compatible storage -* TLS support (using “[tls-certificates](https://charmhub.io/tls-certificates-operator)” operator) -* DB access outside of Juju using “[data-integrator](https://charmhub.io/data-integrator)” -* Data import using standard tools e.g. “psql”. -* Documentation: - - -## Inside the charms: - -* Charmed PostgreSQL charm ships the latest PostgreSQL “14.7-0ubuntu0.22.04.1” -* VM charms [based on our](https://snapcraft.io/publisher/dataplatformbot) SNAP (Ubuntu LTS “22.04” - core22-based) -* Principal charms supports the latest LTS series “22.04” only. -* Subordinate charms support LTS “22.04” and “20.04” only. - -## Technical notes - - * The new PostgreSQL charm is also a juju interface-compatible replacement for legacy PostgreSQL charms (using legacy interface `pgsql`, via endpoints `db` and `db-admin`). -However, **it is highly recommended to migrate to the modern interface [`postgresql_client`](https://github.com/canonical/charm-relation-interfaces)** (endpoint `database`). - * Please [contact us](#heading--contact) if you are considering migrating from other “legacy” charms not mentioned above. -* Charmed PostgreSQL follows SNAP track “14”. -* No “latest” track in use (no surprises in tracking “latest/stable”)! - * PostgreSQL charm provide [legacy charm](/t/10690) through “latest/stable”. -* You can find charm lifecycle flowchart diagrams [here](https://github.com/canonical/postgresql-k8s-operator/tree/main/docs/reference). -* Modern interfaces are well described in the [Interfaces catalogue](https://github.com/canonical/charm-relation-interfaces) and implemented by [`data-platform-libs`](https://github.com/canonical/data-platform-libs/). -* Known limitation: PostgreSQL extensions are not yet supported. - -

Contact us

-Charmed PostgreSQL is an open source project that warmly welcomes community contributions, suggestions, fixes, and constructive feedback. - -* Raise software issues or feature requests on [**GitHub**](https://github.com/canonical/postgresql-operator/issues/new/choose) -* Report security issues through [**Launchpad**](https://wiki.ubuntu.com/DebuggingSecurity#How%20to%20File) -* Contact the Canonical Data Platform team through our [Matrix](https://matrix.to/#/#charmhub-data-platform:ubuntu.com) channel! - - \ No newline at end of file diff --git a/docs/reference/r-revision-336.md b/docs/reference/r-revision-336.md deleted file mode 100644 index 4c3d98fa3a..0000000000 --- a/docs/reference/r-revision-336.md +++ /dev/null @@ -1,81 +0,0 @@ ->Reference > Release Notes > [All revisions](/t/11875) > [Revision 336](/t/11877) -# Revision 336 -Wednesday, October 18, 2023 - -Dear community, - -We'd like to announce that Canonical's newest Charmed PostgreSQL operator for IAAS/VM has been published in the `14/stable` [channel](https://charmhub.io/postgresql?channel=14/stable). :tada: - -If you are jumping over several stable revisions, make sure to check [previous release notes](/t/11875) before upgrading to this revision. - -## Features you can start using today -* [Add Juju 3 support](/t/11743) (Juju 2 is still supported) [[DPE-1758](https://warthogs.atlassian.net/browse/DPE-1758)] -* All secrets are now stored in [Juju secrets](https://juju.is/docs/juju/manage-secrets) [[DPE-1758](https://warthogs.atlassian.net/browse/DPE-1758)] -* Charm [minor upgrades](/t/12089) and [minor rollbacks](/t/12090) [[DPE-1767](https://warthogs.atlassian.net/browse/DPE-1767)] -* [Canonical Observability Stack (COS)](https://charmhub.io/topics/canonical-observability-stack) support [[DPE-1775](https://warthogs.atlassian.net/browse/DPE-1775)] -* [PostgreSQL plugins support](/t/10906) [[DPE-1373](https://warthogs.atlassian.net/browse/DPE-1373)] -* [Profiles configuration](/t/11974) support [[DPE-2655](https://warthogs.atlassian.net/browse/DPE-2655)] -* [Logs rotation](/t/12099) [[DPE-1754](https://warthogs.atlassian.net/browse/DPE-1754)] -* Workload updated to [PostgreSQL 14.9](https://www.postgresql.org/docs/14/release-14-9.html) [[PR#18](https://github.com/canonical/charmed-postgresql-snap/pull/18)] -* Add '`admin`' [extra user role](https://github.com/canonical/postgresql-operator/pull/199) [[DPE-2167](https://warthogs.atlassian.net/browse/DPE-2167)] -* New charm '[PostgreSQL Test App](https://charmhub.io/postgresql-test-app)' -* New documentation: - * [Architecture (HLD/LLD)](/t/11857) - * [Upgrade section](/t/12086) - * [Release Notes](/t/11875) - * [Requirements](/t/11743) - * [Profiles](/t/11974) - * [Users](/t/10798) - * [Logs](/t/12099) - * [Statuses](/t/10844) - * [Development](/t/11862) - * [Testing reference](/t/11773) - * [Legacy charm](/t/10690) - * [Plugins/extensions](/t/10906), [supported](/t/10946) - * [Juju 2.x vs 3.x hints](/t/11985) - * [Contacts](/t/11863) -* All the functionality from [the previous revisions](/t/11875) - -## Bugfixes -* [DPE-1624](https://warthogs.atlassian.net/browse/DPE-1624), [DPE-1625](https://warthogs.atlassian.net/browse/DPE-1625) Backup/restore fixes -* [DPE-1926](https://warthogs.atlassian.net/browse/DPE-1926) Remove fallback_application_name field from relation data -* [DPE-1712](https://warthogs.atlassian.net/browse/DPE-1712) Enabled the user to fix network issues and rerun stanza related hooks -* [DPE-2173](https://warthogs.atlassian.net/browse/DPE-2173) Fix allowed units relation data field -* [DPE-2127](https://warthogs.atlassian.net/browse/DPE-2127) Fixed databases access -* [DPE-2341](https://warthogs.atlassian.net/browse/DPE-2341) Populate extensions in unit databag -* [DPE-2218](https://warthogs.atlassian.net/browse/DPE-2218) Update charm libs to get s3 relation fix -* [DPE-1210](https://warthogs.atlassian.net/browse/DPE-1210), [DPE-2330](https://warthogs.atlassian.net/browse/DPE-2330), [DPE-2212](https://warthogs.atlassian.net/browse/DPE-2212) Open port (ability to expose charm) -* [DPE-2614](https://warthogs.atlassian.net/browse/DPE-2614) Split stanza create and stanza check -* [DPE-2721](https://warthogs.atlassian.net/browse/DPE-2721) Allow network access for pg_dump, pg_dumpall and pg_restore -* [DPE-2717](https://warthogs.atlassian.net/browse/DPE-2717) Copy dashboard changes from K8s and use the correct topology dispatcher -* [MISC] Copy fixes of DPE-2626 and DPE-2627 from k8s -* [MISC] Don't fail if the unit is already missing -* [MISC] More resilient topology observer - -Canonical Data issues are now public on both [Jira](https://warthogs.atlassian.net/jira/software/c/projects/DPE/issues/) and [GitHub](https://github.com/canonical/postgresql-operator/issues) platforms. - -[GitHub Releases](https://github.com/canonical/postgresql-operator/releases) provide a detailed list of bugfixes, PRs, and commits for each revision. - -## Inside the charms - -* Charmed PostgreSQL ships the latest PostgreSQL “14.9-0ubuntu0.22.04.1” -* PostgreSQL cluster manager Patroni updated to "3.0.2" -* Backup tools pgBackRest updated to "2.47" -* The Prometheus postgres-exporter is "0.12.1-0ubuntu0.22.04.1~ppa1" -* VM charms based on [Charmed PostgreSQL](https://snapcraft.io/charmed-postgresql) SNAP (Ubuntu LTS “22.04” - ubuntu:22.04-based) -* Principal charms supports the latest LTS series “22.04” only. -* Subordinate charms support LTS “22.04” and “20.04” only. - -## Technical notes - -* `juju refresh` from the old-stable revision 288 to the current-revision 324 is **NOT** supported!!!
The [upgrade](/t/12086) functionality is new and supported for revision 324+ only! -* Please check additionally [the previously posted restrictions](/t/11876). -* Ensure [the charm requirements](/t/11743) met - -## Contact us - -Charmed PostgreSQL is an open source project that warmly welcomes community contributions, suggestions, fixes, and constructive feedback. - -* Raise software issues or feature requests on [**GitHub**](https://github.com/canonical/postgresql-operator/issues/new/choose) -* Report security issues through [**Launchpad**](https://wiki.ubuntu.com/DebuggingSecurity#How%20to%20File) -* Contact the Canonical Data Platform team through our [Matrix](https://matrix.to/#/#charmhub-data-platform:ubuntu.com) channel. \ No newline at end of file diff --git a/docs/reference/r-revision-351.md b/docs/reference/r-revision-351.md deleted file mode 100644 index 1e3648b1ba..0000000000 --- a/docs/reference/r-revision-351.md +++ /dev/null @@ -1,57 +0,0 @@ ->Reference > Release Notes > [All revisions](/t/11875) > [Revision 351](/t/12823) -# Revision 351 -January 3, 2024 - -Dear community, - -We'd like to announce that Canonical's newest Charmed PostgreSQL operator for IAAS/VM has been published in the `14/stable` [channel](https://charmhub.io/postgresql?channel=14/stable). - -If you are jumping over several stable revisions, make sure to check [previous release notes](/t/11875) before upgrading to this revision. - -## Features you can start using today - -* [Core] Updated `Charmed PostgreSQL` SNAP image ([PR#291](https://github.com/canonical/postgresql-operator/pull/291))([DPE-3039](https://warthogs.atlassian.net/browse/DPE-3039)): - * `Patroni` updated from 3.0.2 to 3.1.2 - * `Pgbackrest` updated from 2.47 to 2.48 -* [Plugins] [Add 24 new plugins/extension](https://charmhub.io/postgresql/docs/r-plugins-extensions) in ([PR#251](https://github.com/canonical/postgresql-operator/pull/251)) -* [Plugins] **NOTE**: extension `plpython3u` is deprecated and will be removed from [list of supported plugins](/t/10946) soon! -* [Config] [Add 29 new configuration options](https://charmhub.io/postgresql/configure) in ([PR#239](https://github.com/canonical/postgresql-operator/pull/239))([DPE-1781](https://warthogs.atlassian.net/browse/DPE-1781)) -* [Config] **NOTE:** the config option `profile-limit-memory` is deprecated. Use `profile_limit_memory` (to follow the [naming conventions](https://juju.is/docs/sdk/naming))! ([PR#306](https://github.com/canonical/postgresql-operator/pull/306))([DPE-3096](https://warthogs.atlassian.net/browse/DPE-3096)) -* [Charm] Add Juju Secret labels in ([PR#270](https://github.com/canonical/postgresql-operator/pull/270))([DPE-2838](https://warthogs.atlassian.net/browse/DPE-2838)) -* [Charm] Update Python dependencies in ([PR#293](https://github.com/canonical/postgresql-operator/pull/293)) -* [DB] Add handling of tables ownership in ([PR#298](https://github.com/canonical/postgresql-operator/pull/298))([DPE-2740](https://warthogs.atlassian.net/browse/DPE-2740)) -* ([COS](https://charmhub.io/topics/canonical-observability-stack)) Moved Grafana dashboard legends to the bottom of the graph in ([PR#295](https://github.com/canonical/postgresql-operator/pull/295))([DPE-2622](https://warthogs.atlassian.net/browse/DPE-2622)) -* ([COS](https://charmhub.io/topics/canonical-observability-stack)) Add Patroni COS support ([#261](https://github.com/canonical/postgresql-operator/pull/261))([DPE-1993](https://warthogs.atlassian.net/browse/DPE-1993)) -* [CI/CD] Charm migrated to GitHub Data reusable workflow in ([PR#263](https://github.com/canonical/postgresql-operator/pull/263))([DPE-2789](https://warthogs.atlassian.net/browse/DPE-2789)) -* All the functionality from [the previous revisions](/t/11875) - -## Bugfixes - -* Fixed enabling extensions when new database is created ([PR#252](https://github.com/canonical/postgresql-operator/pull/252))([DPE-2569](https://warthogs.atlassian.net/browse/DPE-2569)) -* Block the charm if the legacy interface requests [roles](https://discourse.charmhub.io/t/charmed-postgresql-explanations-interfaces-endpoints/10251) ([DPE-3077](https://warthogs.atlassian.net/browse/DPE-3077)) - -Canonical Data issues are now public on both [Jira](https://warthogs.atlassian.net/jira/software/c/projects/DPE/issues/) and [GitHub](https://github.com/canonical/postgresql-operator/issues) platforms. -[GitHub Releases](https://github.com/canonical/postgresql-operator/releases) provide a detailed list of bugfixes, PRs, and commits for each revision. -## Inside the charms - -* Charmed PostgreSQL ships the latest PostgreSQL “14.9-0ubuntu0.22.04.1” -* PostgreSQL cluster manager Patroni updated to "3.2.1" -* Backup tools pgBackRest updated to "2.48" -* The Prometheus postgres-exporter is "0.12.1-0ubuntu0.22.04.1~ppa1" -* VM charms based on [Charmed PostgreSQL](https://snapcraft.io/charmed-postgresql) SNAP (Ubuntu LTS “22.04” - ubuntu:22.04-based) revision 89 -* Principal charms supports the latest LTS series “22.04” only -* Subordinate charms support LTS “22.04” and “20.04” only - -## Technical notes - -* Upgrade (`juju refresh`) is possible from this revision 336+ -* Use this operator together with a modern operator "[pgBouncer](https://charmhub.io/pgbouncer?channel=1/stable)" -* Please check additionally [the previously posted restrictions](/t/11875) -* Ensure [the charm requirements](/t/11743) met - -## Contact us - -Charmed PostgreSQL is an open source project that warmly welcomes community contributions, suggestions, fixes, and constructive feedback. -* Raise software issues or feature requests on [**GitHub**](https://github.com/canonical/postgresql-operator/issues/new/choose) -* Report security issues through [**Launchpad**](https://wiki.ubuntu.com/DebuggingSecurity#How%20to%20File) -* Contact the Canonical Data Platform team through our [Matrix](https://matrix.to/#/#charmhub-data-platform:ubuntu.com) channel. \ No newline at end of file diff --git a/docs/reference/r-revision-363.md b/docs/reference/r-revision-363.md deleted file mode 100644 index e3614ea343..0000000000 --- a/docs/reference/r-revision-363.md +++ /dev/null @@ -1,55 +0,0 @@ ->Reference > Release Notes > [All revisions](/t/11875) > [Revision 363](/t/13124) -# Revision 363 -February 21, 2024 - -Dear community, - -We'd like to announce that Canonical's newest Charmed PostgreSQL operator for IAAS/VM has been published in the `14/stable` [channel](https://charmhub.io/postgresql?channel=14/stable) :tada: - -If you are jumping over several stable revisions, make sure to check [previous release notes](/t/11875) before upgrading to this revision. - -## Features you can start using today -* [CORE] PostgreSQL upgrade 14.9 -> 14.10. ([DPE-3217](https://warthogs.atlassian.net/browse/DPE-3217)) - * **Note**: It is advisable to REINDEX potentially-affected indexes after installing this update! (See [PostgreSQL changelog](https://changelogs.ubuntu.com/changelogs/pool/main/p/postgresql-14/postgresql-14_14.10-0ubuntu0.22.04.1/changelog)) -* [CORE] Juju 3.1.7+ support ([#2037120](https://bugs.launchpad.net/juju/+bug/2037120)) -* [PLUGINS] pgVector extension/plugin ([DPE-3159](https://warthogs.atlassian.net/browse/DPE-3159)) -* [PLUGINS] New PostGIS plugin ([#312](https://github.com/canonical/postgresql-operator/pull/312)) -* [PLUGINS] More new plugins - [50 in total](/t/10946) -* [MONITORING] COS Awesome Alert rules ([DPE-3160](https://warthogs.atlassian.net/browse/DPE-3160)) -* [SECURITY] Updated TLS libraries for compatibility with new charms - * [manual-tls-certificates](https://charmhub.io/manual-tls-certificates) - * [self-signed-certificates](https://charmhub.io/self-signed-certificates) - * Any charms compatible with [ tls_certificates_interface.v2.tls_certificates](https://charmhub.io/tls-certificates-interface/libraries/tls_certificates) -* All functionality from [previous revisions](/t/11875) - -## Bugfixes - -* [DPE-3199](https://warthogs.atlassian.net/browse/DPE-3199) Stabilized internal Juju secrets management -* [DPE-3258](https://warthogs.atlassian.net/browse/DPE-3258) Check system identifier in stanza (backups setup stabilization) - -Canonical Data issues are now public on both [Jira](https://warthogs.atlassian.net/jira/software/c/projects/DPE/issues/) and [GitHub](https://github.com/canonical/postgresql-operator/issues) platforms. -[GitHub Releases](https://github.com/canonical/postgresql-operator/releases) provide a detailed list of bugfixes, PRs, and commits for each revision. - -## What is inside the charms - -* Charmed PostgreSQL ships the latest PostgreSQL `14.10-0ubuntu0.22.04.1` -* PostgreSQL cluster manager Patroni updated to `v.3.1.2` -* Backup tools pgBackRest updated to `v.2.48` -* The Prometheus postgres-exporter is `0.12.1-0ubuntu0.22.04.1~ppa1` -* VM charms based on [Charmed PostgreSQL](https://snapcraft.io/charmed-postgresql) SNAP (Ubuntu LTS 22.04 - `ubuntu:22.04-based`) revision 96 -* Principal charms supports the latest LTS series 22.04 only -* Subordinate charms support LTS 22.04 and 20.04 only - -## Technical notes - -* Starting with this revision (336+), you can use `juju refresh` to upgrade Charmed PostgreSQL -* Use this operator together with modern [Charmed PgBouncer operator](https://charmhub.io/pgbouncer?channel=1/stable) -* Please check [the previously posted restrictions](/t/11875) -* Ensure [the charm requirements](/t/11743) met - -## Contact us - -Charmed PostgreSQL is an open source project that warmly welcomes community contributions, suggestions, fixes, and constructive feedback. -* Raise software issues or feature requests on [**GitHub**](https://github.com/canonical/postgresql-operator/issues/new/choose) -* Report security issues through [**Launchpad**](https://wiki.ubuntu.com/DebuggingSecurity#How%20to%20File) -* Contact the Canonical Data Platform team through our [Matrix](https://matrix.to/#/#charmhub-data-platform:ubuntu.com) channel. \ No newline at end of file diff --git a/docs/reference/r-revision-429-430.md b/docs/reference/r-revision-429-430.md deleted file mode 100644 index a1efd30106..0000000000 --- a/docs/reference/r-revision-429-430.md +++ /dev/null @@ -1,98 +0,0 @@ ->Reference > Release Notes > [All revisions](t/11875) > Revision 429/430 - -# Revision 429/430 - -June 28, 2024 - -Dear community, - -Canonical's newest Charmed PostgreSQL operator has been published in the 14/stable [channel](https://charmhub.io/postgresql?channel=14/stable) :tada: - -Due to the newly added support for `arm64` architecture, the PostgreSQL charm now releases two revisions simultaneously: -* Revision 429 is built for `amd64` -* Revision 430 is built for for `arm64` - -To make sure you deploy for the right architecture, we recommend setting an [architecture constraint](https://juju.is/docs/juju/constraint#heading--arch) for your entire juju model. - -Otherwise, it can be done at deploy time with the `--constraints` flag: -```shell -juju deploy postgresql --constraints arch= -``` -where `` can be `amd64` or `arm64`. - ---- - -## Highlights -Below are the major highlights of this release. To see all changes since the previous stable release, check the [release notes on GitHub](https://github.com/canonical/postgresql-operator/releases/tag/rev430). - -* Upgraded PostgreSQL from v.14.10 → v.14.11 ([PR #432](https://github.com/canonical/postgresql-operator/pull/432)) - * Check the official [PostgreSQL release notes](https://www.postgresql.org/docs/release/14.11/) -* Added support for ARM64 architecture ([PR #381](https://github.com/canonical/postgresql-operator/pull/381)) -* Added support for cross-regional asynchronous replication ([PR #452](https://github.com/canonical/postgresql-operator/pull/452)) ([DPE-2953](https://warthogs.atlassian.net/browse/DPE-2953)) - * This feature focuses on disaster recovery by distributing data across different servers. Check our [new how-to guides](https://charmhub.io/postgresql/docs/h-async-set-up) for a walkthrough of the cross-model setup, promotion, switchover, and other details. -* Added support for tracing with Tempo K8s ([PR #485](https://github.com/canonical/postgresql-operator/pull/485)) ([DPE-4616](https://warthogs.atlassian.net/browse/DPE-4616)) - * Check our new guide: [How to enable tracing](https://charmhub.io/postgresql/docs/h-enable-tracing) -* Released new [Charmed Sysbench operator](https://charmhub.io/sysbench) for easy performance testing - -### Enhancements -* Added timescaledb plugin/extension ([PR#470](https://github.com/canonical/postgresql-operator/pull/470)) - * See the [Configuration tab]((https://charmhub.io/postgresql/configuration?channel=14/candidate#plugin_timescaledb_enable)) for a full list of supported plugins/extensions -* Added incremental and differential backup support ([PR #479](https://github.com/canonical/postgresql-operator/pull/479)) ([DPE-4462](https://warthogs.atlassian.net/browse/DPE-4462)) - * Check our guide: [How to create and list backups](https://charmhub.io/postgresql/docs/h-create-backup) -* Added support for disabling the operator ([PR#412](https://github.com/canonical/postgresql-operator/pull/412)) ([DPE-2469](https://warthogs.atlassian.net/browse/DPE-2469)) -* Added support for subordination with: - * `ubuntu-advantage` ([PR#397](https://github.com/canonical/postgresql-operator/pull/397)) ([DPE-3644](https://warthogs.atlassian.net/browse/DPE-3644)) - * `landscape-client` ([PR#388](https://github.com/canonical/postgresql-operator/pull/388)) ([DPE-3644](https://warthogs.atlassian.net/browse/DPE-3644)) -* Added configuration option for backup retention time ([PR#474](https://github.com/canonical/postgresql-operator/pull/474))([DPE-4401](https://warthogs.atlassian.net/browse/DPE-4401)) -* Added `experimental_max_connections` config option ([PR#472](https://github.com/canonical/postgresql-operator/pull/472)) -* Added check for replicas encrypted connection ([PR#437](https://github.com/canonical/postgresql-operator/pull/437)) - -### Bugfixes -* Fixed slow charm bootstrap time ([PR#413](https://github.com/canonical/postgresql-operator/pull/413)) -* Fixed large objects ownership ([PR#349](https://github.com/canonical/postgresql-operator/pull/349)) -* Fixed secrets crash for "certificates-relation-changed" after the refresh ([PR#475](https://github.com/canonical/postgresql-operator/pull/475)) -* Fixed network cut tests ([PR#346](https://github.com/canonical/postgresql-operator/pull/346)) ([DPE-3257](https://warthogs.atlassian.net/browse/DPE-3257)) - -Canonical Data issues are now public on both [Jira](https://warthogs.atlassian.net/jira/software/c/projects/DPE/issues/) and [GitHub](https://github.com/canonical/postgresql-operator/issues). - -For a full list of all changes in this revision, see the [GitHub Release](https://github.com/canonical/postgresql-operator/releases/tag/rev430). - -## Technical details -This section contains some technical details about the charm's contents and dependencies. Make sure to also check the [system requirements](/t/11743). - -If you are jumping over several stable revisions, check [previous release notes](/t/11875) before upgrading. - -### Packaging -This charm is based on the [`charmed-postgresql` snap](https://snapcraft.io/charmed-postgresql) (pinned revision 113). It packages: -* postgresql `v.14.11` - * [`14.11-0ubuntu0.22.04.1`](https://launchpad.net/ubuntu/+source/postgresql-14/14.11-0ubuntu0.22.04.1) -* pgbouncer `v.1.21` - * [`1.21.0-0ubuntu0.22.04.1~ppa1`](https://launchpad.net/~data-platform/+archive/ubuntu/pgbouncer) -* patroni `v.3.1.2 ` - * [`3.1.2-0ubuntu0.22.04.1~ppa2`](https://launchpad.net/~data-platform/+archive/ubuntu/patroni) -* pgBackRest `v.2.48` - * [`2.48-0ubuntu0.22.04.1~ppa1`](https://launchpad.net/~data-platform/+archive/ubuntu/pgbackrest) -* prometheus-postgres-exporter `v.0.12.1` - -### Libraries and interfaces -This charm revision imports the following libraries: - -* **grafana_agent `v0`** for integration with Grafana - * Implements `cos_agent` interface -* **rolling_ops `v0`** for rolling operations across units - * Implements `rolling_op` interface -* **tempo_k8s `v1`, `v2`** for integration with Tempo charm - * Implements `tracing` interface -* **tls_certificates_interface `v2`** for integration with TLS charms - * Implements `tls-certificates` interface - -See the [`/lib/charms` directory on GitHub](https://github.com/canonical/postgresql-operator/tree/main/lib/charms) for more details about all supported libraries. - -See the [`metadata.yaml` file on GitHub](https://github.com/canonical/postgresql-operator/blob/main/metadata.yaml) for a full list of supported interfaces - -## Contact us - -Charmed PostgreSQL is an open source project that warmly welcomes community contributions, suggestions, fixes, and constructive feedback. -* Raise software issues or feature requests on [**GitHub**](https://github.com/canonical/postgresql-operator/issues) -* Report security issues through [**Launchpad**](https://wiki.ubuntu.com/DebuggingSecurity#How%20to%20File) -* Contact the Canonical Data Platform team through our [Matrix](https://matrix.to/#/#charmhub-data-platform:ubuntu.com) channel. \ No newline at end of file diff --git a/docs/reference/r-revision-467-468.md b/docs/reference/r-revision-467-468.md deleted file mode 100644 index 5f5e73d693..0000000000 --- a/docs/reference/r-revision-467-468.md +++ /dev/null @@ -1,155 +0,0 @@ ->Reference > Release Notes > [All revisions] > Revision 467/468 - -# Revision 467/468 -September 11, 2024 - -Canonical's newest Charmed PostgreSQL operator has been published in the [14/stable channel]. - -Due to the newly added support for `arm64` architecture, the PostgreSQL charm now releases multiple revisions simultaneously: -* Revision 468 is built for `amd64` on Ubuntu 22.04 LTS -* Revision 467 is built for `arm64` on Ubuntu 22.04 LTS - -To make sure you deploy for the right architecture, we recommend setting an [architecture constraint](https://juju.is/docs/juju/constraint#heading--arch) for your entire juju model. - -Otherwise, it can be done at deploy time with the `--constraints` flag: -```shell -juju deploy postgresql --constraints arch= -``` -where `` can be `amd64` or `arm64`. - ---- - -## Highlights -* Upgraded PostgreSQL from v.14.11 → v.14.12 ([PR #530](https://github.com/canonical/postgresql-operator/pull/530)) - * Check the official [PostgreSQL release notes](https://www.postgresql.org/docs/release/14.12/) -* Added support for Point In Time Recovery ([PR #391](https://github.com/canonical/postgresql-operator/pull/391)) ([DPE-2582](https://warthogs.atlassian.net/browse/DPE-2582)) -* Secure Syncobj and Patroni with passwords ([PR #596](https://github.com/canonical/postgresql-operator/pull/596)) ([DPE-5269](https://warthogs.atlassian.net/browse/DPE-5269)) -* Removed deprecated config option `profile-limit-memory` ([PR #564](https://github.com/canonical/postgresql-operator/pull/564)) ([DPE-4889](https://warthogs.atlassian.net/browse/DPE-4889)) - -## Features - -* Added URI connection string to relations ([PR #527](https://github.com/canonical/postgresql-operator/pull/527)) ([DPE-2278](https://warthogs.atlassian.net/browse/DPE-2278)) -* Improve `list-backups` action output ([PR #522](https://github.com/canonical/postgresql-operator/pull/522)) ([DPE-4479](https://warthogs.atlassian.net/browse/DPE-4479)) -* Show start/end time in UTC time in list-backups output ([PR #551](https://github.com/canonical/postgresql-operator/pull/551)) -* Switched to constant snap locales ([PR #559](https://github.com/canonical/postgresql-operator/pull/559)) ([DPE-4198](https://warthogs.atlassian.net/browse/DPE-4198)) -* Moved URI generation to update endpoints ([PR #584](https://github.com/canonical/postgresql-operator/pull/584)) - -## Bugfixes - -* Wait for exact number of units after scale down ([PR #565](https://github.com/canonical/postgresql-operator/pull/565)) ([DPE-5029](https://warthogs.atlassian.net/browse/DPE-5029)) -* Improved test stability by pausing Patroni in the TLS test ([PR #534](https://github.com/canonical/postgresql-operator/pull/534)) ([DPE-4533](https://warthogs.atlassian.net/browse/DPE-4533)) -* Block charm if it detects objects dependent on disabled plugins ([PR #560](https://github.com/canonical/postgresql-operator/pull/560)) ([DPE-4967](https://warthogs.atlassian.net/browse/DPE-4967)) -* Disabled pgBackRest service initialization ([PR #530](https://github.com/canonical/postgresql-operator/pull/530)) ([DPE-4345](https://warthogs.atlassian.net/browse/DPE-4345)) -* Increased timeout and terminate processes that are still up ([PR #514](https://github.com/canonical/postgresql-operator/pull/514)) ([DPE-4532](https://warthogs.atlassian.net/browse/DPE-4532)) -* Fixed GCP backup test ([PR #521](https://github.com/canonical/postgresql-operator/pull/521)) ([DPE-4820](https://warthogs.atlassian.net/browse/DPE-4820)) -* Handled on start secret exception and remove stale test ([PR #550](https://github.com/canonical/postgresql-operator/pull/550)) -* Removed block on failure to get the db version ([PR #578](https://github.com/canonical/postgresql-operator/pull/578)) ([DPE-3562](https://warthogs.atlassian.net/browse/DPE-3562)) -* Updated unit tests after fixing GCP backup test ([PR #528](https://github.com/canonical/postgresql-operator/pull/528)) ([DPE-4820](https://warthogs.atlassian.net/browse/DPE-4820)) -* Ported some `test_self_healing` CI fixes + update check for invalid extra user credentials ([PR #546](https://github.com/canonical/postgresql-operator/pull/546)) ([DPE-4856](https://warthogs.atlassian.net/browse/DPE-4856)) -* Fixed slow bootstrap of replicas ([PR #510](https://github.com/canonical/postgresql-operator/pull/510)) ([DPE-4759](https://warthogs.atlassian.net/browse/DPE-4759)) -* Fixed conditional password ([PR #604](https://github.com/canonical/postgresql-operator/pull/604)) -* Added enforcement of Juju versions ([PR #518](https://github.com/canonical/postgresql-operator/pull/518)) ([DPE-4809](https://warthogs.atlassian.net/browse/DPE-4809)) -* Fixed a missing case for peer to secrets translation. ([PR #533](https://github.com/canonical/postgresql-operator/pull/533)) -* Updated README.md ([PR #538](https://github.com/canonical/postgresql-operator/pull/538)) ([DPE-4901](https://warthogs.atlassian.net/browse/DPE-4901)) -* Increased test coverage ([PR #505](https://github.com/canonical/postgresql-operator/pull/505)) - -## Known limitations - - * The unit action `resume-upgrade` randomly raises a [harmless error message](https://warthogs.atlassian.net/browse/DPE-5420): `terminated`. - * The [charm sysbench](https://charmhub.io/sysbench) may [crash](https://warthogs.atlassian.net/browse/DPE-5436) during a PostgreSQL charm refresh. - * Make sure that [cluster-cluster replication](/t/13991) is requested for the same charm/workload revisions. An automated check is [planned](https://warthogs.atlassian.net/browse/DPE-5418). - * [Contact us](/t/11863) to schedule [the cluster-cluster replication](/t/13991) upgrade with you. - -If you are jumping over several stable revisions, check [previous release notes][All revisions] before upgrading. - -## Requirements and compatibility - -This charm revision features the following changes in dependencies: -* (increased) The minimum Juju version required to reliably operate **all** features of the release is `v3.4.5` - > You can upgrade to this revision on Juju `v2.9.50+`, but it will not support newer features like cross-regional asynchronous replication, point-in-time recovery, and modern TLS certificate charm integrations. -* (increased) PostgreSQL version 14.12 - -Check the [system requirements] page for more details, such as supported minor versions of Juju and hardware requirements. - -### Integration tests -Below are the charm integrations tested with this revision on different Juju environments and architectures: -* Juju `v.2.9.50` on `amd64` -* Juju `v.3.4.5` on `amd64` and `arm64` - -| Software | Version | Notes | -|-----|-----|-----| -| [lxd] | `5.12/stable` | | -| [nextcloud] | `v29.0.5.1`, `rev 26` | | -| [mailman3-core] | `rev 18` | | -| [data-integrator] | `rev 41` | | -| [s3-integrator] | `rev 31` | | -| [postgresql-test-app] | `rev 237` | | - -See the [`/lib/charms` directory on GitHub] for details about all supported libraries. - -See the [`metadata.yaml` file on GitHub] for a full list of supported interfaces. - -### Packaging - -This charm is based on the Charmed PostgreSQL [snap Revision 120/121]. It packages: -* [postgresql `v.14.12`] -* [pgbouncer `v.1.21`] -* [patroni `v.3.1.2 `] -* [pgBackRest `v.2.48`] -* [prometheus-postgres-exporter `v.0.12.1`] - -### Dependencies and automations - -[details=This section contains a list of updates to libs, dependencies, actions, and workflows.] - -* Added jinja2 as a dependency ([PR #520](https://github.com/canonical/postgresql-operator/pull/520)) ([DPE-4816](https://warthogs.atlassian.net/browse/DPE-4816)) -* Switched Jira issue sync from workflow to bot ([PR #586](https://github.com/canonical/postgresql-operator/pull/586)) -* Updated canonical/charming-actions action to v2.6.2 ([PR #523](https://github.com/canonical/postgresql-operator/pull/523)) -* Updated data-platform-workflows to v21.0.1 ([PR #599](https://github.com/canonical/postgresql-operator/pull/599)) -* Updated dependency cryptography to v43 ([PR #539](https://github.com/canonical/postgresql-operator/pull/539)) -* Updated dependency tenacity to v9 ([PR #558](https://github.com/canonical/postgresql-operator/pull/558)) -* Updated Juju agents (patch) ([PR #553](https://github.com/canonical/postgresql-operator/pull/553)) -* Switch to resusable presets ([PR #513](https://github.com/canonical/postgresql-operator/pull/513)) -* Use poetry package-mode=false ([PR #556](https://github.com/canonical/postgresql-operator/pull/556)) -* Switched test-app interface ([PR #557](https://github.com/canonical/postgresql-operator/pull/557)) -[/details] - - -[All revisions]: /t/11875 -[system requirements]: /t/11743 - - -[`/lib/charms` directory on GitHub]: https://github.com/canonical/postgresql-operator/tree/rev468/lib/charms -[`metadata.yaml` file on GitHub]: https://github.com/canonical/postgresql-operator/blob/rev468/metadata.yaml - - -[14/stable channel]: https://charmhub.io/postgresql?channel=14/stable - - -[`charmed-postgresql` packaging]: https://github.com/canonical/charmed-postgresql-snap -[snap Revision 120/121]: https://github.com/canonical/charmed-postgresql-snap/releases/tag/rev121 -[rock image]: ghcr.io/canonical/charmed-postgresql@sha256:7ef86a352c94e2a664f621a1cc683d7a983fd86e923d98c32b863f717cb1c173 - -[postgresql `v.14.12`]: https://launchpad.net/ubuntu/+source/postgresql-14/14.12-0ubuntu0.22.04.1 -[pgbouncer `v.1.21`]: https://launchpad.net/~data-platform/+archive/ubuntu/pgbouncer -[patroni `v.3.1.2 `]: https://launchpad.net/~data-platform/+archive/ubuntu/patroni -[pgBackRest `v.2.48`]: https://launchpad.net/~data-platform/+archive/ubuntu/pgbackrest -[prometheus-postgres-exporter `v.0.12.1`]: https://launchpad.net/~data-platform/+archive/ubuntu/postgres-exporter - - -[juju]: https://juju.is/docs/juju/ -[lxd]: https://documentation.ubuntu.com/lxd/en/latest/ -[nextcloud]: https://charmhub.io/nextcloud -[mailman3-core]: https://charmhub.io/mailman3-core -[data-integrator]: https://charmhub.io/data-integrator -[s3-integrator]: https://charmhub.io/s3-integrator -[postgresql-test-app]: https://charmhub.io/postgresql-test-app -[discourse-k8s]: https://charmhub.io/discourse-k8s -[indico]: https://charmhub.io/indico -[microk8s]: https://charmhub.io/microk8s -[tls-certificates-operator]: https://charmhub.io/tls-certificates-operator -[self-signed-certificates]: https://charmhub.io/self-signed-certificates - - -[amd64]: https://img.shields.io/badge/amd64-darkgreen -[arm64]: https://img.shields.io/badge/arm64-blue \ No newline at end of file diff --git a/docs/reference/r-revision-544-545.md b/docs/reference/r-revision-544-545.md deleted file mode 100644 index 39732165fc..0000000000 --- a/docs/reference/r-revision-544-545.md +++ /dev/null @@ -1,162 +0,0 @@ ->Reference > Release Notes > [All revisions] > Revision 544/545 - -[note type=caution] -This page is a work in progress for a **future release**. Please revisit at a later date! -[/note] - -# Revision 544/545 - - -Canonical's newest Charmed PostgreSQL operator has been published in the [14/stable channel]. - -Due to the newly added support for `arm64` architecture, the PostgreSQL charm now releases multiple revisions simultaneously: -* Revision is built for `amd64` on Ubuntu 22.04 LTS -* Revision is built for `arm64` on Ubuntu 22.04 LTS - -> See also: [How to perform a minor upgrade] - -### Contents -* [Highlights](#highlights) -* [Features and improvements](#features-and-improvements) -* [Bugfixes and maintenance](#bugfixes-and-maintenance) -* [Known limitations](#known-limitations) -* [Requirements and compatibility](#requirements-and-compatibility) - * [Integration tests](#integration-tests) - * [Packaging](#packaging) ---- - -## Highlights - -* Upgraded PostgreSQL from v.14.12 → v.14.15 ([PR #730](https://github.com/canonical/postgresql-operator/pull/730)) - * Check the official [PostgreSQL 14.13 release notes](https://www.postgresql.org/docs/release/14.13/) - * Check the official [PostgreSQL 14.14 release notes](https://www.postgresql.org/docs/release/14.14/) - * Check the official [PostgreSQL 14.15 release notes](https://www.postgresql.org/docs/release/14.15/) -* Added timeline management to point-in-time recovery (PITR) ([PR #629](https://github.com/canonical/postgresql-operator/pull/629)) ([DPE-5561](https://warthogs.atlassian.net/browse/DPE-5561)) -* Added pgAudit plugin/extension ([PR #612](https://github.com/canonical/postgresql-operator/pull/612)) ([DPE-5248](https://warthogs.atlassian.net/browse/DPE-5248)) -* Observability stack (COS) improvements - * Polished built-in Grafana dashboard ([PR #646](https://github.com/canonical/postgresql-operator/pull/646)) - * Improved COS alert rule descriptions ([PR #651](https://github.com/canonical/postgresql-operator/pull/651)) ([DPE-5658](https://warthogs.atlassian.net/browse/DPE-5658)) -* Added fully-featured terraform module ([PR #643](https://github.com/canonical/postgresql-operator/pull/643)) -* Several S3 stability improvements ([PR #642](https://github.com/canonical/postgresql-operator/pull/642)) - -## Features and improvements -* Removed patching of private ops class. ([PR #617](https://github.com/canonical/postgresql-operator/pull/617)) -* Switched charm libs from `tempo_k8s` to `tempo_coordinator_k8s` and relay tracing traffic through `grafana-agent` ([PR #640](https://github.com/canonical/postgresql-operator/pull/640)) -* Implemented more meaningful group naming for multi-group tests ([PR #625](https://github.com/canonical/postgresql-operator/pull/625)) -* Ignoring alias error in case alias is already existing ([PR #637](https://github.com/canonical/postgresql-operator/pull/637)) -* Stopped tracking channel for held snaps ([PR #638](https://github.com/canonical/postgresql-operator/pull/638)) -* Added pgBackRest logrotate configuration ([PR #645](https://github.com/canonical/postgresql-operator/pull/645)) ([DPE-5601](https://warthogs.atlassian.net/browse/DPE-5601)) -* Grant priviledges to non-public schemas ([PR #647](https://github.com/canonical/postgresql-operator/pull/647)) ([DPE-5387](https://warthogs.atlassian.net/browse/DPE-5387)) -* Added `tls` and `tls-ca` fields to databag ([PR #666](https://github.com/canonical/postgresql-operator/pull/666)) -* Merged `update_tls_flag` into `update_endpoints` ([PR #669](https://github.com/canonical/postgresql-operator/pull/669)) -* Made tox commands resilient to white-space paths ([PR #678](https://github.com/canonical/postgresql-operator/pull/678)) ([DPE-6042](https://warthogs.atlassian.net/browse/DPE-6042)) -* Added microceph (local backup) integration test + bump snap version ([PR #633](https://github.com/canonical/postgresql-operator/pull/633)) ([DPE-5386](https://warthogs.atlassian.net/browse/DPE-5386)) -* Add `max_locks_per_transaction` config option in ([PR#718](https://github.com/canonical/postgresql-operator/pull/718)) ([DPE-5533](https://warthogs.atlassian.net/browse/DPE-5533)) -* Split PITR backup test in AWS and GCP ([PR #605](https://github.com/canonical/postgresql-operator/pull/605)) ([DPE-5181](https://warthogs.atlassian.net/browse/DPE-5181)) - - -## Bugfixes and maintenance -* Juju secrets resetting fix on Juju 3.6 in ([PR#726](https://github.com/canonical/postgresql-operator/pull/726)) ([DPE-6320](https://warthogs.atlassian.net/browse/DPE-6320)) ([DPE-6325](https://warthogs.atlassian.net/browse/DPE-6325)) -* Fallback to trying to create bucket without LocationConstraint in ([PR#690](https://github.com/canonical/postgresql-operator/pull/690)) -* Added warning logs to Patroni reinitialisation ([PR #660](https://github.com/canonical/postgresql-operator/pull/660)) -* Fixed some `postgresql.conf` parameters for hardening ([PR #621](https://github.com/canonical/postgresql-operator/pull/621)) ([DPE-5512](https://warthogs.atlassian.net/browse/DPE-5512)) -* Fixed lib check ([PR #627](https://github.com/canonical/postgresql-operator/pull/627)) -* Allow `--restore-to-time=latest` without a `backup-id` in ([PR#683](https://github.com/canonical/postgresql-operator/pull/683)) -* Clean-up duplicated Patroni config reloads in ([PR#682](https://github.com/canonical/postgresql-operator/pull/682)) -* Filter out degraded read only endpoints in ([PR#679](https://github.com/canonical/postgresql-operator/pull/679)) ([DPE-5714](https://warthogs.atlassian.net/browse/DPE-5714)) - -[details=Libraries, testing, and CI] -* Data Interafces v40 ([PR #615](https://github.com/canonical/postgresql-operator/pull/615)) ([DPE-5306](https://warthogs.atlassian.net/browse/DPE-5306)) -* Bump libs and remove TestCase ([PR #622](https://github.com/canonical/postgresql-operator/pull/622)) -* Run tests against juju 3.6 on a nightly schedule ([PR #601](https://github.com/canonical/postgresql-operator/pull/601)) ([DPE-4977](https://warthogs.atlassian.net/browse/DPE-4977)) -* Test against juju 3.6/candidate + upgrade dpw to v23.0.5 ([PR #675](https://github.com/canonical/postgresql-operator/pull/675)) -* Lock file maintenance Python dependencies ([PR #644](https://github.com/canonical/postgresql-operator/pull/644)) -* Migrate config .github/renovate.json5 ([PR #673](https://github.com/canonical/postgresql-operator/pull/673)) -* Switch from tox build wrapper to charmcraft.yaml overrides ([PR #626](https://github.com/canonical/postgresql-operator/pull/626)) -* Update canonical/charming-actions action to v2.6.3 ([PR #608](https://github.com/canonical/postgresql-operator/pull/608)) -* Update codecov/codecov-action action to v5 ([PR #674](https://github.com/canonical/postgresql-operator/pull/674)) -* Update data-platform-workflows to v23.0.5 ([PR #676](https://github.com/canonical/postgresql-operator/pull/676)) -* Update dependency cryptography to v43.0.1 [SECURITY] ([PR #614](https://github.com/canonical/postgresql-operator/pull/614)) -* Update dependency ubuntu to v24 ([PR #631](https://github.com/canonical/postgresql-operator/pull/631)) -* Update Juju agents ([PR #634](https://github.com/canonical/postgresql-operator/pull/634)) -* Bump libs ([PR #677](https://github.com/canonical/postgresql-operator/pull/677)) -* Increase linting rules ([PR #649](https://github.com/canonical/postgresql-operator/pull/649)) ([DPE-5324](https://warthogs.atlassian.net/browse/DPE-5324)) -[/details] - -## Requirements and compatibility -* (no change) Minimum Juju 2 version: `v.2.9.49` -* (no change) Minimum Juju 3 version: `v.3.4.3` -* (recommended) Juju LTS 3.6.1+ - -See the [system requirements] for more details about Juju versions and other software and hardware prerequisites. - -### Integration tests -Below are some of the charm integrations tested with this revision on different Juju environments and architectures: -* Juju `v.2.9.51` on `amd64` -* Juju `v.3.4.6` on `amd64` and `arm64` - -| Software | Revision | Tested on | | -|-----|-----|----|---| -| [postgresql-test-app] | `rev 281` | ![juju-2_amd64] ![juju-3_amd64] | -| | `rev 279` | ![juju-2_amd64] ![juju-3_amd64] | -| | `rev 280` | ![juju-3_arm64] | -| | `rev 278` | ![juju-3_arm64] | -| [data-integrator] | `rev 41` | ![juju-2_amd64] ![juju-3_amd64] | -| | `rev 40` | ![juju-3_arm64] | -| [nextcloud] | `rev 26` | ![juju-2_amd64] ![juju-3_amd64] | | -| [s3-integrator] | `rev 77` | ![juju-2_amd64] ![juju-3_amd64] | -| | `rev 78` | ![juju-3_arm64] | -| [tls-certificates-operator] | `rev 22` | ![juju-2_amd64] | -| [self-signed-certificates] | `rev 155` | ![juju-3_amd64] | -| | `rev 205` | ![juju-3_arm64] | -| [mailman3-core] | `rev 18` | ![juju-2_amd64] ![juju-3_amd64] ![juju-3_arm64] | -| [landscape-client] | `rev 70` | ![juju-2_amd64] ![juju-3_amd64] ![juju-3_arm64] | -| [ubuntu-advantage] | `rev 137` | ![juju-2_amd64] ![juju-3_amd64] | -| | `rev 139` | ![juju-3_arm64]| - -See the [`/lib/charms` directory on GitHub] for details about all supported libraries. - -See the [`metadata.yaml` file on GitHub] for a full list of supported interfaces. - -### Packaging -This charm is based on the Charmed PostgreSQL [snap revision 132/133](https://github.com/canonical/charmed-postgresql-snap/tree/rev121). It packages: -* [postgresql] `v.14.12` -* [pgbouncer] `v.1.21` -* [patroni] `v.3.1.2 ` -* [pgBackRest] `v.2.53` -* [prometheus-postgres-exporter] `v.0.12.1` - - -[14/stable channel]: https://charmhub.io/postgresql?channel=14/stable - -[All revisions]: /t/11875 -[system requirements]: /t/11743 -[How to perform a minor upgrade]: /t/12089 - -[juju]: https://juju.is/docs/juju/ -[lxd]: https://documentation.ubuntu.com/lxd/en/latest/ -[nextcloud]: https://charmhub.io/nextcloud -[mailman3-core]: https://charmhub.io/mailman3-core -[data-integrator]: https://charmhub.io/data-integrator -[s3-integrator]: https://charmhub.io/s3-integrator -[postgresql-test-app]: https://charmhub.io/postgresql-test-app -[discourse-k8s]: https://charmhub.io/discourse-k8s -[indico]: https://charmhub.io/indico -[microk8s]: https://charmhub.io/microk8s -[tls-certificates-operator]: https://charmhub.io/tls-certificates-operator -[self-signed-certificates]: https://charmhub.io/self-signed-certificates -[landscape-client]: https://charmhub.io/landscape-client -[ubuntu-advantage]: https://charmhub.io/ubuntu-advantage - -[`/lib/charms` directory on GitHub]: https://github.com/canonical/postgresql-operator/tree/rev518/lib/charms -[`metadata.yaml` file on GitHub]: https://github.com/canonical/postgresql-operator/blob/rev518/metadata.yaml - -[postgresql]: https://launchpad.net/ubuntu/+source/postgresql-14/ -[pgbouncer]: https://launchpad.net/~data-platform/+archive/ubuntu/pgbouncer -[patroni]: https://launchpad.net/~data-platform/+archive/ubuntu/patroni -[pgBackRest]: https://launchpad.net/~data-platform/+archive/ubuntu/pgbackrest -[prometheus-postgres-exporter]: https://launchpad.net/~data-platform/+archive/ubuntu/postgres-exporter - -[juju-2_amd64]: https://img.shields.io/badge/Juju_2.9.51-amd64-darkgreen?labelColor=ea7d56 -[juju-3_amd64]: https://img.shields.io/badge/Juju_3.4.6-amd64-darkgreen?labelColor=E95420 -[juju-3_arm64]: https://img.shields.io/badge/Juju_3.4.6-arm64-blue?labelColor=E95420 \ No newline at end of file diff --git a/docs/reference/r-system-requirements.md b/docs/reference/r-system-requirements.md index 0c659fee12..5d4c4a16b2 100644 --- a/docs/reference/r-system-requirements.md +++ b/docs/reference/r-system-requirements.md @@ -11,7 +11,7 @@ The charm supports several Juju releases from [2.9 LTS](https://juju.is/docs/juj | Juju major release | Supported minor versions | Compatible charm revisions |Comment | |:--------|:-----|:-----|:-----| -| ![3.6 LTS] | `3.6.0-beta2` | [363]+ | No known issues, but still in beta. Not recommended for production. | +| ![3.6 LTS] | `3.6.1+` | [552]+ | `3.6.0` is not recommended, while `3.6.1+` works excellent. Recommended for production! | | [![3.5]](https://juju.is/docs/juju/roadmap#juju-juju-35) | `3.5.1+` | [363]+ | [Known Juju issue](https://bugs.launchpad.net/juju/+bug/2066517) in `3.5.0` | | [![3.4]](https://juju.is/docs/juju/roadmap#juju-juju-34) | `3.4.3+` | [363]+ | Know Juju issues with previous minor versions | | [![3.3]](https://juju.is/docs/juju/roadmap#juju-juju-33) | `3.3.0+` | from [363] to [430] | No known issues | @@ -52,6 +52,7 @@ The charm is based on the [charmed-postgresql snap](https://snapcraft.io/charmed [3.6 LTS]: https://img.shields.io/badge/3.6_LTS-%23E95420?label=Juju +[552]: /t/16007 [288]: /t/11876 [336]: /t/11877 [363]: /t/13124 diff --git a/docs/tutorial/t-overview.md b/docs/tutorial.md similarity index 98% rename from docs/tutorial/t-overview.md rename to docs/tutorial.md index d8a7a11511..f532210bb6 100644 --- a/docs/tutorial/t-overview.md +++ b/docs/tutorial.md @@ -1,4 +1,4 @@ -# Charmed PostgreSQL Tutorial +# Tutorial This section of our documentation contains comprehensive, hands-on tutorials to help you learn how to deploy Charmed PostgreSQL on machines and become familiar with its available operations. From 3779a92d0d081b49f2277067b3bba750a8e539d1 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Thu, 13 Mar 2025 00:23:12 +0200 Subject: [PATCH 60/74] Update canonical/data-platform-workflows action to v30.2.0 (#792) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- .github/workflows/check_pr.yaml | 2 +- .github/workflows/ci.yaml | 4 ++-- .github/workflows/promote.yaml | 2 +- .github/workflows/release.yaml | 2 +- .github/workflows/sync_docs.yaml | 2 +- 5 files changed, 6 insertions(+), 6 deletions(-) diff --git a/.github/workflows/check_pr.yaml b/.github/workflows/check_pr.yaml index e3bf9febe0..6eb3823585 100644 --- a/.github/workflows/check_pr.yaml +++ b/.github/workflows/check_pr.yaml @@ -15,4 +15,4 @@ on: jobs: check-pr: name: Check pull request - uses: canonical/data-platform-workflows/.github/workflows/check_charm_pr.yaml@v30.1.3 + uses: canonical/data-platform-workflows/.github/workflows/check_charm_pr.yaml@v30.2.0 diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index 82dca5072d..3af5adff47 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -27,7 +27,7 @@ on: jobs: lint: name: Lint - uses: canonical/data-platform-workflows/.github/workflows/lint.yaml@v30.1.3 + uses: canonical/data-platform-workflows/.github/workflows/lint.yaml@v30.2.0 unit-test: name: Unit test charm @@ -49,7 +49,7 @@ jobs: build: name: Build charm - uses: canonical/data-platform-workflows/.github/workflows/build_charm.yaml@v30.1.3 + uses: canonical/data-platform-workflows/.github/workflows/build_charm.yaml@v30.2.0 integration-test: name: Integration test charm diff --git a/.github/workflows/promote.yaml b/.github/workflows/promote.yaml index a3ef15303a..7f0de3c67e 100644 --- a/.github/workflows/promote.yaml +++ b/.github/workflows/promote.yaml @@ -25,7 +25,7 @@ on: jobs: promote: name: Promote charm - uses: canonical/data-platform-workflows/.github/workflows/_promote_charm.yaml@v30.1.3 + uses: canonical/data-platform-workflows/.github/workflows/_promote_charm.yaml@v30.2.0 with: track: '14' from-risk: ${{ inputs.from-risk }} diff --git a/.github/workflows/release.yaml b/.github/workflows/release.yaml index 80100a62c6..af6e9cea0a 100644 --- a/.github/workflows/release.yaml +++ b/.github/workflows/release.yaml @@ -27,7 +27,7 @@ jobs: name: Release charm needs: - ci-tests - uses: canonical/data-platform-workflows/.github/workflows/release_charm.yaml@v30.1.3 + uses: canonical/data-platform-workflows/.github/workflows/release_charm.yaml@v30.2.0 with: channel: 14/edge artifact-prefix: ${{ needs.ci-tests.outputs.artifact-prefix }} diff --git a/.github/workflows/sync_docs.yaml b/.github/workflows/sync_docs.yaml index d80ca5aece..99dce73a21 100644 --- a/.github/workflows/sync_docs.yaml +++ b/.github/workflows/sync_docs.yaml @@ -10,7 +10,7 @@ on: jobs: sync-docs: name: Sync docs from Discourse - uses: canonical/data-platform-workflows/.github/workflows/sync_docs.yaml@v30.1.3 + uses: canonical/data-platform-workflows/.github/workflows/sync_docs.yaml@v30.2.0 with: reviewers: a-velasco,izmalk permissions: From 0b60f3cb572d0059ffbe52dcf416b329264b0feb Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Thu, 13 Mar 2025 01:01:18 +0200 Subject: [PATCH 61/74] Update dependency uv to v0.6.5 (#785) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- charmcraft.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/charmcraft.yaml b/charmcraft.yaml index 6b27dc71d6..9c58385065 100644 --- a/charmcraft.yaml +++ b/charmcraft.yaml @@ -27,7 +27,7 @@ parts: PIP_BREAK_SYSTEM_PACKAGES=true python3 -m pip install --user --upgrade pip==25.0.1 # renovate: charmcraft-pip-latest # Use uv to install poetry so that a newer version of Python can be installed if needed by poetry - curl --proto '=https' --tlsv1.2 -LsSf https://github.com/astral-sh/uv/releases/download/0.6.3/uv-installer.sh | sh # renovate: charmcraft-uv-latest + curl --proto '=https' --tlsv1.2 -LsSf https://github.com/astral-sh/uv/releases/download/0.6.5/uv-installer.sh | sh # renovate: charmcraft-uv-latest # poetry 2.0.0 requires Python >=3.9 if ! "$HOME/.local/bin/uv" python find '>=3.9' then From 655d550bb024a4fe52ce1289e68ebde8db9eb0da Mon Sep 17 00:00:00 2001 From: Dragomir Penev <6687393+dragomirp@users.noreply.github.com> Date: Thu, 13 Mar 2025 03:46:31 +0200 Subject: [PATCH 62/74] Pg 16 sync main (#793) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Use `charmcraft test` & concierge (#762) * Update charmcraft.yaml build tools (#760) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> * [DPE-6020] Better promote-to-primary unit scope error handling (#759) * Bump libs * Flip default scope * Better action failure * Wrong attr * Revert scope * Bump libs * Handle async replica switchover * Unit tests * Bump cosl * Disable Nextcloud test (#767) * Update canonical/data-platform-workflows action to v30 (#770) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> * Use _promote_charm.yaml (#771) Use `charmcraft promote` and auto-generate release notes * [DPE-5827] Set all nodes to synchronous replicas (#672) * Set all nodes to synchronous replicas * Fix template var * Also change config patching * Update sync nodes during upgrade * Revert are_writes_increasing changes * Add back logging * Try without logs * Tactical sleep * Log removal error * Remove logs * Tweak replication test * Pass down unit * Wait for test app to idle * Add comment * Port config changes * Copy policy test * Fix import * Missed param removal * Unit test * Missing attr * Add logs * Add timeout to connection * Log conn str * Fix num of standbys * Charm fixture * Remove stepdown hook * Config description * Revert conn str * Add async scaling test * Typo * Don't remove standby and primary * Update dependency psutil to v7 (#772) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> * Update dependency cryptography to v44.0.1 [SECURITY] (#764) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> * Update canonical/data-platform-workflows action to v30.0.2 (#765) * Update canonical/data-platform-workflows action to v30.0.2 * Update promote.yaml --------- Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> Co-authored-by: Carl Csaposs * [DPE-6323] Handle missing stanza output (#727) * Handle missing stanza output * Update libs * Unit tests * Update canonical/has-signed-canonical-cla action to v2 (#773) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> * [MISC] Define charm constants (#774) * Lock file maintenance Python dependencies (#743) * Lock file maintenance Python dependencies * Backoff boto3 1.36 --------- Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> Co-authored-by: Dragomir Penev * Update charmcraft.yaml build tools (#768) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> * Update canonical/data-platform-workflows action to v30.1.3 (#776) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> * Update dependency uv to v0.6.3 (#780) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> * [MISC] Sanitize PostgreSQL extra-user-roles arg (#782) * [MISC] Fix PostgreSQL lib function signature (#786) * [MISC] Skip backup and subordinate tests without creds (#789) * Bump libs * Skip backup tests without creds * Skip subordinate tests * Update tests/integration/test_subordinates.py Co-authored-by: Carl Csaposs --------- Co-authored-by: Carl Csaposs * Update dependency jinja2 to v3.1.6 [SECURITY] (#788) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> * Disable cache * Reduce required approvals on Renovate pull requests by 1 (#787) * Sync docs from Discourse (#748) Co-authored-by: GitHub Actions <41898282+github-actions[bot]@users.noreply.github.com> * Cleanup juju 2 tests * Linting * Integration test diffs * Try with series for ubuntu pro subordinate * Filter terminated units * Bump PG version * Disable pgaudit for timescale and postgis * Linting * Remove tests * Remove param for secrets * Linting * Idle when disabling pgaudit * Actually disable audit * Disable timescale in object test * Try to disable plugins between tests * Update canonical/data-platform-workflows action to v30.2.0 (#792) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> * Try to disable pgaudit in general --------- Co-authored-by: Carl Csaposs Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> Co-authored-by: Sinclert Pérez Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> --- .github/release.yaml | 8 + .github/workflows/approve_renovate_pr.yaml | 15 + .github/workflows/check_pr.yaml | 18 + .github/workflows/ci.yaml | 44 +- .github/workflows/cla-check.yml | 2 +- .github/workflows/integration_test.yaml | 316 ++++++++++ .github/workflows/promote.yaml | 36 ++ .github/workflows/release.yaml | 4 +- .github/workflows/sync_docs.yaml | 2 +- CONTRIBUTING.md | 2 +- charmcraft.yaml | 8 +- concierge.yaml | 13 + config.yaml | 6 + docs/explanation.md | 21 + docs/explanation/e-cryptography.md | 61 ++ docs/explanation/e-juju-details.md | 26 +- docs/explanation/e-security.md | 92 +++ docs/how-to.md | 102 ++++ docs/how-to/h-deploy-lxd.md | 44 -- docs/how-to/h-deploy.md | 83 +++ docs/how-to/h-upgrade.md | 12 + docs/overview.md | 45 +- .../{reference/r-overview.md => reference.md} | 10 +- docs/reference/r-releases.md | 47 +- docs/reference/r-revision-288.md | 50 -- docs/reference/r-revision-336.md | 81 --- docs/reference/r-revision-351.md | 57 -- docs/reference/r-revision-363.md | 55 -- docs/reference/r-revision-429-430.md | 98 --- docs/reference/r-revision-467-468.md | 155 ----- docs/reference/r-revision-544-545.md | 162 ----- docs/reference/r-system-requirements.md | 3 +- docs/{tutorial/t-overview.md => tutorial.md} | 2 +- .../data_platform_libs/v0/data_interfaces.py | 5 +- .../data_platform_libs/v0/data_models.py | 6 +- lib/charms/grafana_agent/v0/cos_agent.py | 170 ++++-- lib/charms/operator_libs_linux/v2/snap.py | 545 ++++++++++------- lib/charms/postgresql_k8s/v0/postgresql.py | 73 ++- lib/charms/rolling_ops/v0/rollingops.py | 8 +- .../tempo_coordinator_k8s/v0/charm_tracing.py | 141 +++-- metadata.yaml | 3 - poetry.lock | 566 ++++++++---------- pyproject.toml | 25 +- spread.yaml | 128 ++++ src/backups.py | 5 +- src/charm.py | 79 +-- src/cluster.py | 34 +- src/config.py | 3 +- src/constants.py | 3 +- src/relations/postgresql_provider.py | 16 +- src/upgrade.py | 1 + templates/patroni.yml.j2 | 2 +- tests/integration/conftest.py | 94 ++- tests/integration/ha_tests/helpers.py | 2 +- .../ha_tests/test_async_replication.py | 17 +- .../integration/ha_tests/test_replication.py | 21 +- .../ha_tests/test_restore_cluster.py | 9 +- tests/integration/ha_tests/test_scaling.py | 85 +-- .../ha_tests/test_scaling_three_units.py | 24 +- .../test_scaling_three_units_async.py | 133 ++++ .../integration/ha_tests/test_self_healing.py | 17 +- tests/integration/ha_tests/test_smoke.py | 65 +- .../ha_tests/test_synchronous_policy.py | 78 +++ tests/integration/ha_tests/test_upgrade.py | 19 +- .../ha_tests/test_upgrade_from_stable.py | 55 +- tests/integration/helpers.py | 79 +-- ...w_relations.py => test_new_relations_1.py} | 119 ++-- .../new_relations/test_new_relations_2.py | 69 +++ .../new_relations/test_relations_coherence.py | 16 +- tests/integration/relations/__init__.py | 0 tests/integration/relations/helpers.py | 52 -- tests/integration/relations/test_relations.py | 138 ----- tests/integration/test_audit.py | 1 - tests/integration/test_backups_aws.py | 111 ++++ tests/integration/test_backups_ceph.py | 13 +- .../{test_backups.py => test_backups_gcp.py} | 170 +----- ...ckups_pitr.py => test_backups_pitr_aws.py} | 94 +-- tests/integration/test_backups_pitr_gcp.py | 384 ++++++++++++ tests/integration/test_charm.py | 11 +- tests/integration/test_config.py | 11 +- tests/integration/test_db.py | 316 ---------- tests/integration/test_db_admin.py | 174 ------ tests/integration/test_password_rotation.py | 13 +- tests/integration/test_plugins.py | 11 +- tests/integration/test_subordinates.py | 48 +- tests/integration/test_tls.py | 22 +- .../test_async_replication.py/task.yaml | 9 + tests/spread/test_audit.py/task.yaml | 7 + tests/spread/test_backups_aws.py/task.yaml | 9 + tests/spread/test_backups_ceph.py/task.yaml | 9 + tests/spread/test_backups_gcp.py/task.yaml | 9 + .../spread/test_backups_pitr_aws.py/task.yaml | 9 + .../spread/test_backups_pitr_gcp.py/task.yaml | 9 + tests/spread/test_charm.py/task.yaml | 7 + tests/spread/test_config.py/task.yaml | 7 + .../spread/test_new_relations_1.py/task.yaml | 7 + .../spread/test_new_relations_2.py/task.yaml | 9 + .../test_password_rotation.py/task.yaml | 7 + tests/spread/test_plugins.py/task.yaml | 7 + .../test_relations_coherence.py/task.yaml | 7 + tests/spread/test_replication.py/task.yaml | 7 + .../spread/test_restore_cluster.py/task.yaml | 7 + tests/spread/test_scaling.py/task.yaml | 9 + .../test_scaling_three_units.py/task.yaml | 9 + .../task.yaml | 9 + tests/spread/test_self_healing.py/task.yaml | 7 + tests/spread/test_smoke.py/task.yaml | 7 + tests/spread/test_subordinates.py/task.yaml | 9 + .../test_synchronous_policy.py/task.yaml | 7 + tests/spread/test_tls.py/task.yaml | 7 + tests/spread/test_upgrade.py/task.yaml | 7 + .../test_upgrade_from_stable.py/task.yaml | 7 + tests/unit/conftest.py | 22 +- tests/unit/test_backups.py | 13 + tests/unit/test_charm.py | 166 ++--- tests/unit/test_cluster.py | 26 +- tests/unit/test_postgresql_provider.py | 9 +- tests/unit/test_upgrade.py | 5 + tox.ini | 9 +- 119 files changed, 3305 insertions(+), 3113 deletions(-) create mode 100644 .github/release.yaml create mode 100644 .github/workflows/approve_renovate_pr.yaml create mode 100644 .github/workflows/check_pr.yaml create mode 100644 .github/workflows/integration_test.yaml create mode 100644 .github/workflows/promote.yaml create mode 100644 concierge.yaml create mode 100644 docs/explanation.md create mode 100644 docs/explanation/e-cryptography.md create mode 100644 docs/explanation/e-security.md create mode 100644 docs/how-to.md delete mode 100644 docs/how-to/h-deploy-lxd.md create mode 100644 docs/how-to/h-deploy.md create mode 100644 docs/how-to/h-upgrade.md rename docs/{reference/r-overview.md => reference.md} (82%) delete mode 100644 docs/reference/r-revision-288.md delete mode 100644 docs/reference/r-revision-336.md delete mode 100644 docs/reference/r-revision-351.md delete mode 100644 docs/reference/r-revision-363.md delete mode 100644 docs/reference/r-revision-429-430.md delete mode 100644 docs/reference/r-revision-467-468.md delete mode 100644 docs/reference/r-revision-544-545.md rename docs/{tutorial/t-overview.md => tutorial.md} (98%) create mode 100644 spread.yaml create mode 100644 tests/integration/ha_tests/test_scaling_three_units_async.py create mode 100644 tests/integration/ha_tests/test_synchronous_policy.py rename tests/integration/new_relations/{test_new_relations.py => test_new_relations_1.py} (89%) create mode 100644 tests/integration/new_relations/test_new_relations_2.py delete mode 100644 tests/integration/relations/__init__.py delete mode 100644 tests/integration/relations/helpers.py delete mode 100644 tests/integration/relations/test_relations.py create mode 100644 tests/integration/test_backups_aws.py rename tests/integration/{test_backups.py => test_backups_gcp.py} (55%) rename tests/integration/{test_backups_pitr.py => test_backups_pitr_aws.py} (82%) create mode 100644 tests/integration/test_backups_pitr_gcp.py delete mode 100644 tests/integration/test_db.py delete mode 100644 tests/integration/test_db_admin.py create mode 100644 tests/spread/test_async_replication.py/task.yaml create mode 100644 tests/spread/test_audit.py/task.yaml create mode 100644 tests/spread/test_backups_aws.py/task.yaml create mode 100644 tests/spread/test_backups_ceph.py/task.yaml create mode 100644 tests/spread/test_backups_gcp.py/task.yaml create mode 100644 tests/spread/test_backups_pitr_aws.py/task.yaml create mode 100644 tests/spread/test_backups_pitr_gcp.py/task.yaml create mode 100644 tests/spread/test_charm.py/task.yaml create mode 100644 tests/spread/test_config.py/task.yaml create mode 100644 tests/spread/test_new_relations_1.py/task.yaml create mode 100644 tests/spread/test_new_relations_2.py/task.yaml create mode 100644 tests/spread/test_password_rotation.py/task.yaml create mode 100644 tests/spread/test_plugins.py/task.yaml create mode 100644 tests/spread/test_relations_coherence.py/task.yaml create mode 100644 tests/spread/test_replication.py/task.yaml create mode 100644 tests/spread/test_restore_cluster.py/task.yaml create mode 100644 tests/spread/test_scaling.py/task.yaml create mode 100644 tests/spread/test_scaling_three_units.py/task.yaml create mode 100644 tests/spread/test_scaling_three_units_async.py/task.yaml create mode 100644 tests/spread/test_self_healing.py/task.yaml create mode 100644 tests/spread/test_smoke.py/task.yaml create mode 100644 tests/spread/test_subordinates.py/task.yaml create mode 100644 tests/spread/test_synchronous_policy.py/task.yaml create mode 100644 tests/spread/test_tls.py/task.yaml create mode 100644 tests/spread/test_upgrade.py/task.yaml create mode 100644 tests/spread/test_upgrade_from_stable.py/task.yaml diff --git a/.github/release.yaml b/.github/release.yaml new file mode 100644 index 0000000000..9ef36aca6d --- /dev/null +++ b/.github/release.yaml @@ -0,0 +1,8 @@ +changelog: + categories: + - title: Features + labels: + - enhancement + - title: Bug fixes + labels: + - bug diff --git a/.github/workflows/approve_renovate_pr.yaml b/.github/workflows/approve_renovate_pr.yaml new file mode 100644 index 0000000000..4449576ea3 --- /dev/null +++ b/.github/workflows/approve_renovate_pr.yaml @@ -0,0 +1,15 @@ +# Copyright 2025 Canonical Ltd. +# See LICENSE file for licensing details. +name: Approve Renovate pull request + +on: + pull_request: + types: + - opened + +jobs: + approve-pr: + name: Approve Renovate pull request + uses: canonical/data-platform-workflows/.github/workflows/approve_renovate_pr.yaml@v30.2.0 + permissions: + pull-requests: write # Needed to approve PR diff --git a/.github/workflows/check_pr.yaml b/.github/workflows/check_pr.yaml new file mode 100644 index 0000000000..6eb3823585 --- /dev/null +++ b/.github/workflows/check_pr.yaml @@ -0,0 +1,18 @@ +# Copyright 2025 Canonical Ltd. +# See LICENSE file for licensing details. +name: Check pull request + +on: + pull_request: + types: + - opened + - labeled + - unlabeled + - edited + branches: + - main + +jobs: + check-pr: + name: Check pull request + uses: canonical/data-platform-workflows/.github/workflows/check_charm_pr.yaml@v30.2.0 diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index ba3a7e65d2..a90828304a 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -27,7 +27,7 @@ on: jobs: lint: name: Lint - uses: canonical/data-platform-workflows/.github/workflows/lint.yaml@v29.1.0 + uses: canonical/data-platform-workflows/.github/workflows/lint.yaml@v30.2.0 unit-test: name: Unit test charm @@ -49,47 +49,19 @@ jobs: build: name: Build charm - uses: canonical/data-platform-workflows/.github/workflows/build_charm.yaml@v29.1.0 + uses: canonical/data-platform-workflows/.github/workflows/build_charm.yaml@v30.2.0 with: - cache: false + cache: false # TODO remove when 16/edge branch is set up + integration-test: - strategy: - fail-fast: false - matrix: - juju: - - agent: 3.6.2 # renovate: juju-agent-pin-minor - libjuju: ==3.6.1.0 - allure_on_amd64: true - architecture: - - amd64 - include: - - juju: - agent: 3.6.2 # renovate: juju-agent-pin-minor - allure_on_amd64: true - architecture: arm64 - name: Integration | ${{ matrix.juju.agent }} | ${{ matrix.architecture }} + name: Integration test charm needs: - lint - unit-test - build - uses: canonical/data-platform-workflows/.github/workflows/integration_test_charm.yaml@v29.1.0 + uses: ./.github/workflows/integration_test.yaml with: artifact-prefix: ${{ needs.build.outputs.artifact-prefix }} - architecture: ${{ matrix.architecture }} - cloud: lxd - juju-agent-version: ${{ matrix.juju.agent }} - libjuju-version-constraint: ${{ matrix.juju.libjuju }} - _beta_allure_report: ${{ matrix.juju.allure_on_amd64 && matrix.architecture == 'amd64' }} - secrets: - integration-test: | - { - "AWS_ACCESS_KEY": "${{ secrets.AWS_ACCESS_KEY }}", - "AWS_SECRET_KEY": "${{ secrets.AWS_SECRET_KEY }}", - "GCP_ACCESS_KEY": "${{ secrets.GCP_ACCESS_KEY }}", - "GCP_SECRET_KEY": "${{ secrets.GCP_SECRET_KEY }}", - "UBUNTU_PRO_TOKEN" : "${{ secrets.UBUNTU_PRO_TOKEN }}", - "LANDSCAPE_ACCOUNT_NAME": "${{ secrets.LANDSCAPE_ACCOUNT_NAME }}", - "LANDSCAPE_REGISTRATION_KEY": "${{ secrets.LANDSCAPE_REGISTRATION_KEY }}", - } + secrets: inherit permissions: - contents: write # Needed for Allure Report beta + contents: write # Needed for Allure Report diff --git a/.github/workflows/cla-check.yml b/.github/workflows/cla-check.yml index f0590d5b65..2567517472 100644 --- a/.github/workflows/cla-check.yml +++ b/.github/workflows/cla-check.yml @@ -9,4 +9,4 @@ jobs: runs-on: ubuntu-24.04 steps: - name: Check if Canonical's Contributor License Agreement has been signed - uses: canonical/has-signed-canonical-cla@v1 + uses: canonical/has-signed-canonical-cla@v2 diff --git a/.github/workflows/integration_test.yaml b/.github/workflows/integration_test.yaml new file mode 100644 index 0000000000..a89194c525 --- /dev/null +++ b/.github/workflows/integration_test.yaml @@ -0,0 +1,316 @@ +on: + workflow_call: + inputs: + artifact-prefix: + description: | + Prefix for charm package GitHub artifact(s) + + Use canonical/data-platform-workflows build_charm.yaml to build the charm(s) + required: true + type: string + +jobs: + collect-integration-tests: + name: Collect integration test spread jobs + runs-on: ubuntu-latest + timeout-minutes: 5 + steps: + - name: Checkout + uses: actions/checkout@v4 + - name: Set up environment + run: | + sudo snap install charmcraft --classic + pipx install tox poetry + - name: Collect spread jobs + id: collect-jobs + shell: python + run: | + import json + import os + import subprocess + + spread_jobs = ( + subprocess.run( + ["charmcraft", "test", "--list", "github-ci"], capture_output=True, check=True, text=True + ) + .stdout.strip() + .split("\n") + ) + jobs = [] + for job in spread_jobs: + # Example `job`: "github-ci:ubuntu-24.04:tests/spread/test_charm.py:juju36" + _, runner, task, variant = job.split(":") + # Example: "test_charm.py" + task = task.removeprefix("tests/spread/") + if runner.endswith("-arm"): + architecture = "arm64" + else: + architecture = "amd64" + # Example: "test_charm.py:juju36 | amd64" + name = f"{task}:{variant} | {architecture}" + # ":" character not valid in GitHub Actions artifact + name_in_artifact = f"{task}-{variant}-{architecture}" + jobs.append({ + "spread_job": job, + "name": name, + "name_in_artifact": name_in_artifact, + "runner": runner, + }) + output = f"jobs={json.dumps(jobs)}" + print(output) + with open(os.environ["GITHUB_OUTPUT"], "a") as file: + file.write(output) + - name: Generate Allure default test results + if: ${{ github.event_name == 'schedule' && github.run_attempt == '1' }} + run: tox run -e integration -- tests/integration --allure-default-dir=allure-default-results + - name: Upload Allure default results + # Default test results in case the integration tests time out or runner set up fails + # (So that Allure report will show "unknown"/"failed" test result, instead of omitting the test) + if: ${{ github.event_name == 'schedule' && github.run_attempt == '1' }} + uses: actions/upload-artifact@v4 + with: + name: allure-default-results-integration-test + path: allure-default-results/ + if-no-files-found: error + outputs: + jobs: ${{ steps.collect-jobs.outputs.jobs }} + + integration-test: + strategy: + fail-fast: false + matrix: + job: ${{ fromJSON(needs.collect-integration-tests.outputs.jobs) }} + name: ${{ matrix.job.name }} + needs: + - collect-integration-tests + runs-on: ${{ matrix.job.runner }} + timeout-minutes: 217 # Sum of steps `timeout-minutes` + 5 + steps: + - name: Free up disk space + timeout-minutes: 1 + run: | + printf '\nDisk usage before cleanup\n' + df --human-readable + # Based on https://github.com/actions/runner-images/issues/2840#issuecomment-790492173 + rm -r /opt/hostedtoolcache/ + printf '\nDisk usage after cleanup\n' + df --human-readable + - name: Checkout + timeout-minutes: 3 + uses: actions/checkout@v4 + - name: Set up environment + timeout-minutes: 5 + run: sudo snap install charmcraft --classic + # TODO: remove when https://github.com/canonical/charmcraft/issues/2105 and + # https://github.com/canonical/charmcraft/issues/2130 fixed + - run: | + sudo snap install go --classic + go install github.com/snapcore/spread/cmd/spread@latest + - name: Download packed charm(s) + timeout-minutes: 5 + uses: actions/download-artifact@v4 + with: + pattern: ${{ inputs.artifact-prefix }}-* + merge-multiple: true + - name: Run spread job + timeout-minutes: 180 + id: spread + # TODO: replace with `charmcraft test` when + # https://github.com/canonical/charmcraft/issues/2105 and + # https://github.com/canonical/charmcraft/issues/2130 fixed + run: ~/go/bin/spread -vv -artifacts=artifacts '${{ matrix.job.spread_job }}' + env: + AWS_ACCESS_KEY: ${{ secrets.AWS_ACCESS_KEY }} + AWS_SECRET_KEY: ${{ secrets.AWS_SECRET_KEY }} + GCP_ACCESS_KEY: ${{ secrets.GCP_ACCESS_KEY }} + GCP_SECRET_KEY: ${{ secrets.GCP_SECRET_KEY }} + UBUNTU_PRO_TOKEN: ${{ secrets.UBUNTU_PRO_TOKEN }} + LANDSCAPE_ACCOUNT_NAME: ${{ secrets.LANDSCAPE_ACCOUNT_NAME }} + LANDSCAPE_REGISTRATION_KEY: ${{ secrets.LANDSCAPE_REGISTRATION_KEY }} + - name: Upload Allure results + timeout-minutes: 3 + # Only upload results from one spread system & one spread variant + # Allure can only process one result per pytest test ID. If parameterization is done via + # spread instead of pytest, there will be overlapping pytest test IDs. + if: ${{ (success() || (failure() && steps.spread.outcome == 'failure')) && startsWith(matrix.job.spread_job, 'github-ci:ubuntu-24.04:') && endsWith(matrix.job.spread_job, ':juju36') && github.event_name == 'schedule' && github.run_attempt == '1' }} + uses: actions/upload-artifact@v4 + with: + name: allure-results-integration-test-${{ matrix.job.name_in_artifact }} + path: artifacts/${{ matrix.job.spread_job }}/allure-results/ + if-no-files-found: error + - timeout-minutes: 1 + if: ${{ success() || (failure() && steps.spread.outcome == 'failure') }} + run: snap list + - name: Select model + timeout-minutes: 1 + # `!contains(matrix.job.spread_job, 'juju29')` workaround for juju 2 error: + # "ERROR cannot acquire lock file to read controller concierge-microk8s: unable to open + # /tmp/juju-store-lock-3635383939333230: permission denied" + # Unable to workaround error with `sudo rm /tmp/juju-*` + if: ${{ !contains(matrix.job.spread_job, 'juju29') && (success() || (failure() && steps.spread.outcome == 'failure')) }} + id: juju-switch + run: | + # sudo needed since spread runs scripts as root + # "testing" is default model created by concierge + sudo juju switch testing + mkdir ~/logs/ + - name: juju status + timeout-minutes: 1 + if: ${{ !contains(matrix.job.spread_job, 'juju29') && (success() || (failure() && steps.spread.outcome == 'failure')) }} + run: sudo juju status --color --relations | tee ~/logs/juju-status.txt + - name: juju debug-log + timeout-minutes: 3 + if: ${{ !contains(matrix.job.spread_job, 'juju29') && (success() || (failure() && steps.spread.outcome == 'failure')) }} + run: sudo juju debug-log --color --replay --no-tail | tee ~/logs/juju-debug-log.txt + - name: jhack tail + timeout-minutes: 3 + if: ${{ !contains(matrix.job.spread_job, 'juju29') && (success() || (failure() && steps.spread.outcome == 'failure')) }} + run: sudo jhack tail --printer raw --replay --no-watch | tee ~/logs/jhack-tail.txt + - name: Upload logs + timeout-minutes: 5 + if: ${{ !contains(matrix.job.spread_job, 'juju29') && (success() || (failure() && steps.spread.outcome == 'failure')) }} + uses: actions/upload-artifact@v4 + with: + name: logs-integration-test-${{ matrix.job.name_in_artifact }} + path: ~/logs/ + if-no-files-found: error + - name: Disk usage + timeout-minutes: 1 + if: ${{ success() || (failure() && steps.spread.outcome == 'failure') }} + run: df --human-readable + + allure-report: + # TODO future improvement: use concurrency group for job + name: Publish Allure report + if: ${{ !cancelled() && github.event_name == 'schedule' && github.run_attempt == '1' }} + needs: + - integration-test + runs-on: ubuntu-latest + timeout-minutes: 5 + steps: + - name: Download Allure + # Following instructions from https://allurereport.org/docs/install-for-linux/#install-from-a-deb-package + run: gh release download --repo allure-framework/allure2 --pattern 'allure_*.deb' + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + - name: Install Allure + run: | + sudo apt-get update + sudo apt-get install ./allure_*.deb -y + # For first run, manually create branch with no history + # (e.g. + # git checkout --orphan gh-pages-beta + # git rm -rf . + # touch .nojekyll + # git add .nojekyll + # git commit -m "Initial commit" + # git push origin gh-pages-beta + # ) + - name: Checkout GitHub pages branch + uses: actions/checkout@v4 + with: + ref: gh-pages-beta + path: repo/ + - name: Download default test results + # Default test results in case the integration tests time out or runner set up fails + # (So that Allure report will show "unknown"/"failed" test result, instead of omitting the test) + uses: actions/download-artifact@v4 + with: + path: allure-default-results/ + name: allure-default-results-integration-test + - name: Download test results + uses: actions/download-artifact@v4 + with: + path: allure-results/ + pattern: allure-results-integration-test-* + merge-multiple: true + - name: Combine Allure default results & actual results + # For every test: if actual result available, use that. Otherwise, use default result + # So that, if actual result not available, Allure report will show "unknown"/"failed" test result + # instead of omitting the test + shell: python + run: | + import dataclasses + import json + import pathlib + + + @dataclasses.dataclass(frozen=True) + class Result: + test_case_id: str + path: pathlib.Path + + def __eq__(self, other): + if not isinstance(other, type(self)): + return False + return self.test_case_id == other.test_case_id + + + actual_results = pathlib.Path("allure-results") + default_results = pathlib.Path("allure-default-results") + + results: dict[pathlib.Path, set[Result]] = { + actual_results: set(), + default_results: set(), + } + for directory, results_ in results.items(): + for path in directory.glob("*-result.json"): + with path.open("r") as file: + id_ = json.load(file)["testCaseId"] + results_.add(Result(id_, path)) + + actual_results.mkdir(exist_ok=True) + + missing_results = results[default_results] - results[actual_results] + for default_result in missing_results: + # Move to `actual_results` directory + default_result.path.rename(actual_results / default_result.path.name) + - name: Load test report history + run: | + if [[ -d repo/_latest/history/ ]] + then + echo 'Loading history' + cp -r repo/_latest/history/ allure-results/ + fi + - name: Create executor.json + shell: python + run: | + # Reverse engineered from https://github.com/simple-elf/allure-report-action/blob/eca283b643d577c69b8e4f048dd6cd8eb8457cfd/entrypoint.sh + import json + + DATA = { + "name": "GitHub Actions", + "type": "github", + "buildOrder": ${{ github.run_number }}, # TODO future improvement: use run ID + "buildName": "Run ${{ github.run_id }}", + "buildUrl": "https://github.com/${{ github.repository }}/actions/runs/${{ github.run_id }}", + "reportUrl": "../${{ github.run_number }}/", + } + with open("allure-results/executor.json", "w") as file: + json.dump(DATA, file) + - name: Generate Allure report + run: allure generate + - name: Create index.html + shell: python + run: | + DATA = f""" + + + + """ + with open("repo/index.html", "w") as file: + file.write(DATA) + - name: Update GitHub pages branch + working-directory: repo/ + # TODO future improvement: commit message + run: | + mkdir '${{ github.run_number }}' + rm -f _latest + ln -s '${{ github.run_number }}' _latest + cp -r ../allure-report/. _latest/ + git add . + git config user.name "GitHub Actions" + git config user.email "41898282+github-actions[bot]@users.noreply.github.com" + git commit -m "Allure report ${{ github.run_number }}" + # Uses token set in checkout step + git push origin gh-pages-beta diff --git a/.github/workflows/promote.yaml b/.github/workflows/promote.yaml new file mode 100644 index 0000000000..7f0de3c67e --- /dev/null +++ b/.github/workflows/promote.yaml @@ -0,0 +1,36 @@ +# Copyright 2025 Canonical Ltd. +# See LICENSE file for licensing details. +name: Promote charm + +on: + workflow_dispatch: + inputs: + from-risk: + description: Promote from this Charmhub risk + required: true + type: choice + options: + - edge + - beta + - candidate + to-risk: + description: Promote to this Charmhub risk + required: true + type: choice + options: + - beta + - candidate + - stable + +jobs: + promote: + name: Promote charm + uses: canonical/data-platform-workflows/.github/workflows/_promote_charm.yaml@v30.2.0 + with: + track: '14' + from-risk: ${{ inputs.from-risk }} + to-risk: ${{ inputs.to-risk }} + secrets: + charmhub-token: ${{ secrets.CHARMHUB_TOKEN }} + permissions: + contents: write # Needed to edit GitHub releases diff --git a/.github/workflows/release.yaml b/.github/workflows/release.yaml index 2a641f276c..0068821d81 100644 --- a/.github/workflows/release.yaml +++ b/.github/workflows/release.yaml @@ -23,13 +23,13 @@ jobs: uses: ./.github/workflows/ci.yaml secrets: inherit permissions: - contents: write # Needed for Allure Report beta + contents: write # Needed for Allure Report release: name: Release charm needs: - ci-tests - uses: canonical/data-platform-workflows/.github/workflows/release_charm.yaml@v29.1.0 + uses: canonical/data-platform-workflows/.github/workflows/release_charm.yaml@v30.2.0 with: channel: 16/edge artifact-prefix: ${{ needs.ci-tests.outputs.artifact-prefix }} diff --git a/.github/workflows/sync_docs.yaml b/.github/workflows/sync_docs.yaml index 179e10d527..99dce73a21 100644 --- a/.github/workflows/sync_docs.yaml +++ b/.github/workflows/sync_docs.yaml @@ -10,7 +10,7 @@ on: jobs: sync-docs: name: Sync docs from Discourse - uses: canonical/data-platform-workflows/.github/workflows/sync_docs.yaml@v29.1.0 + uses: canonical/data-platform-workflows/.github/workflows/sync_docs.yaml@v30.2.0 with: reviewers: a-velasco,izmalk permissions: diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 2e22c8c702..388378cdff 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -33,7 +33,7 @@ source venv/bin/activate tox run -e format # update your code according to linting rules tox run -e lint # code style tox run -e unit # unit tests -tox run -e integration # integration tests +charmcraft test lxd-vm: # integration tests tox # runs 'lint' and 'unit' environments ``` diff --git a/charmcraft.yaml b/charmcraft.yaml index 148f6971bd..62fab54b56 100644 --- a/charmcraft.yaml +++ b/charmcraft.yaml @@ -24,10 +24,10 @@ parts: # Use environment variable instead of `--break-system-packages` to avoid failing on older # versions of pip that do not recognize `--break-system-packages` # `--user` needed (in addition to `--break-system-packages`) for Ubuntu >=24.04 - PIP_BREAK_SYSTEM_PACKAGES=true python3 -m pip install --user --upgrade pip==25.0 # renovate: charmcraft-pip-latest + PIP_BREAK_SYSTEM_PACKAGES=true python3 -m pip install --user --upgrade pip==25.0.1 # renovate: charmcraft-pip-latest # Use uv to install poetry so that a newer version of Python can be installed if needed by poetry - curl --proto '=https' --tlsv1.2 -LsSf https://github.com/astral-sh/uv/releases/download/0.5.29/uv-installer.sh | sh # renovate: charmcraft-uv-latest + curl --proto '=https' --tlsv1.2 -LsSf https://github.com/astral-sh/uv/releases/download/0.6.3/uv-installer.sh | sh # renovate: charmcraft-uv-latest # poetry 2.0.0 requires Python >=3.9 if ! "$HOME/.local/bin/uv" python find '>=3.9' then @@ -35,7 +35,7 @@ parts: # (to reduce the number of Python versions we use) "$HOME/.local/bin/uv" python install 3.10.12 # renovate: charmcraft-python-ubuntu-22.04 fi - "$HOME/.local/bin/uv" tool install --no-python-downloads --python '>=3.9' poetry==2.0.1 --with poetry-plugin-export==1.9.0 # renovate: charmcraft-poetry-latest + "$HOME/.local/bin/uv" tool install --no-python-downloads --python '>=3.9' poetry==2.1.1 --with poetry-plugin-export==1.9.0 # renovate: charmcraft-poetry-latest ln -sf "$HOME/.local/bin/poetry" /usr/local/bin/poetry # "charm-poetry" part name is arbitrary; use for consistency @@ -75,7 +75,7 @@ parts: # rpds-py (Python package) >=0.19.0 requires rustc >=1.76, which is not available in the # Ubuntu 22.04 archive. Install rustc and cargo using rustup instead of the Ubuntu archive rustup set profile minimal - rustup default 1.84.1 # renovate: charmcraft-rust-latest + rustup default 1.85.0 # renovate: charmcraft-rust-latest craftctl default # Include requirements.txt in *.charm artifact for easier debugging diff --git a/concierge.yaml b/concierge.yaml new file mode 100644 index 0000000000..15a78cc947 --- /dev/null +++ b/concierge.yaml @@ -0,0 +1,13 @@ +juju: + model-defaults: + logging-config: =INFO; unit=DEBUG +providers: + lxd: + enable: true + bootstrap: true +host: + snaps: + jhack: + channel: latest/edge + connections: + - jhack:dot-local-share-juju snapd diff --git a/config.yaml b/config.yaml index 8b474ec962..46c3c9f3a7 100644 --- a/config.yaml +++ b/config.yaml @@ -2,6 +2,12 @@ # See LICENSE file for licensing details. options: + synchronous_node_count: + description: | + Sets the number of synchronous nodes to be maintained in the cluster. Should be + either "all", "majority" or a positive integer value. + type: string + default: "all" durability_synchronous_commit: description: | Sets the current transactions synchronization level. This charm allows only the diff --git a/docs/explanation.md b/docs/explanation.md new file mode 100644 index 0000000000..8fc131b9e6 --- /dev/null +++ b/docs/explanation.md @@ -0,0 +1,21 @@ +# Explanation + +This section contains pages with more detailed explanations that provide additional context about some of the key concepts behind the PostgreSQL charm: + +* [Architecture] +* [Interfaces and endpoints] +* [Connection pooling] +* [Users] +* [Logs] +* [Juju] +* [Legacy charm] + + + +[Architecture]: /t/11857 +[Interfaces and endpoints]: /t/10251 +[Users]: /t/10798 +[Logs]: /t/12099 +[Juju]: /t/11985 +[Legacy charm]: /t/10690 +[Connection pooling]: /t/15777 \ No newline at end of file diff --git a/docs/explanation/e-cryptography.md b/docs/explanation/e-cryptography.md new file mode 100644 index 0000000000..51e3c6bed6 --- /dev/null +++ b/docs/explanation/e-cryptography.md @@ -0,0 +1,61 @@ +# Cryptography + +This document describes the cryptography used by Charmed PostgreSQL. + +## Resource checksums + +Charmed PostgreSQL and Charmed PgBouncer operators use pinned versions of the respective snaps to provide reproducible and secure environments. + +The snaps package their workloads along with the necessary dependencies and utilities required for the operators’ lifecycle. For more details, see the snaps content in the `snapcraft.yaml` file for [PostgreSQL](https://github.com/canonical/charmed-postgresql-snap/blob/14/edge/snap/snapcraft.yaml) and [PgBouncer](https://github.com/canonical/charmed-pgbouncer-snap/blob/1/edge/snap/snapcraft.yaml). + +Every artifact bundled into a snap is verified against its MD5, SHA256, or SHA512 checksum after download. The installation of certified snap into the rock is ensured by snap primitives that verify their squashfs filesystems images GPG signature. For more information on the snap verification process, refer to the [snapcraft.io documentation](https://snapcraft.io/docs/assertions). + +## Sources verification + +PostgreSQL and its extra components are built by Canonical from upstream source codes on [Launchpad](https://launchpad.net/ubuntu/+source/postgresql-common). PostgreSQL and PgBouncer are built as deb packages, other components - as PPAs. + +Charmed PostgreSQL and Charmed PgBouncer charms and snaps are published and released programmatically using release pipelines implemented via GitHub Actions in their respective repositories. + +All repositories in GitHub are set up with branch protection rules, requiring: + +* new commits to be merged to main branches via pull request with at least 2 approvals from repository maintainers +* new commits to be signed (e.g. using GPG keys) +* developers to sign the [Canonical Contributor License Agreement (CLA)](https://ubuntu.com/legal/contributors) + +## Encryption + +Charmed PostgreSQL can be used to deploy a secure PostgreSQL cluster that provides encryption-in-transit capabilities out of the box for: + +* Cluster internal communications +* PgBouncer connections +* External clients connections + +To set up a secure connection Charmed PostgreSQL and Charmed PgBouncer need to be integrated with TLS Certificate Provider charms, e.g. self-signed-certificates operator. Certificate Signing Requests (CSRs) are generated for every unit using the tls_certificates_interface library that uses the cryptography Python library to create X.509 compatible certificates. The CSR is signed by the TLS Certificate Provider, returned to the units, and stored in Juju secret. The relation also provides the CA certificate, which is loaded into Juju secret. + +Encryption at rest is currently not supported, although it can be provided by the substrate (cloud or on-premises). + +## Authentication + +In Charmed PostgreSQL, authentication layers can be enabled for: + +1. PgBouncer authentication to PostgreSQL +2. PostgreSQL cluster authentication +3. Clients authentication to PostgreSQL + +### PgBouncer authentication to PostgreSQL + +Authentication of PgBouncer to PostgreSQL is based on the password-based `scram-sha-256` authentication method. See the [PostgreSQL official documentation](https://www.postgresql.org/docs/14/auth-password.html) for more details. + +Credentials are exchanged via [Juju secrets](https://canonical-juju.readthedocs-hosted.com/en/latest/user/howto/manage-secrets/). + +### PostgreSQL cluster authentication + +Authentication among members of a PostgreSQL cluster is based on the password-based `scram-sha-256` authentication method. + +An internal user is used for this authentication with its hashed password stored in a system metadata database. These credentials are also stored as a plain text file on the disk of each unit for the Patroni HA service. + +### Clients authentication to PostgreSQL + +Authentication of clients to PostgreSQL is based on the password-based `scram-sha-256` authentication method. See the [PostgreSQL official documentation](https://www.postgresql.org/docs/14/auth-password.html) for more details. + +Credentials are exchanged via [Juju secrets](https://canonical-juju.readthedocs-hosted.com/en/latest/user/howto/manage-secrets/). \ No newline at end of file diff --git a/docs/explanation/e-juju-details.md b/docs/explanation/e-juju-details.md index 65f3b702cd..a8554674eb 100644 --- a/docs/explanation/e-juju-details.md +++ b/docs/explanation/e-juju-details.md @@ -1,13 +1,15 @@ -# Juju tech details +# Juju [Juju](https://juju.is/) is an open source orchestration engine for software operators that enables the deployment, integration and lifecycle management of applications at any scale, on any infrastructure using charms. -This [charm](https://charmhub.io/postgresql) is an operator - business logic encapsulated in reusable software packages that automate every aspect of an application's life. Charms are shared via [CharmHub](https://charmhub.io/). +> See also: [Juju client documentation](https://juju.is/docs/juju), [Juju blog](https://ubuntu.com/blog/tag/juju) -See also: +## Compatibility with PostgreSQL -* [Juju Documentation](https://juju.is/docs/juju) and [Blog](https://ubuntu.com/blog/tag/juju) -* [Charm SDK](https://juju.is/docs/sdk) +Current stable releases of this charm can still be deployed on Juju 2.9. However, newer features are not supported. +> See the [Releases page](/t/11875) for more information about the minimum Juju version required to operate the features of each revision. + +Additionally, there are limitations regarding integrations with other charms. For example, integration with [modern TLS charms](https://charmhub.io/topics/security-with-x-509-certificates) requires Juju 3.x. ## Breaking changes between Juju 2.9.x and 3.x @@ -15,18 +17,18 @@ As this charm documentation is written for Juju 3.x, users of 2.9.x will encount Breaking changes have been introduced in the Juju client between versions 2.9.x and 3.x. These are caused by the renaming and re-purposing of several commands - functionality and command options remain unchanged. -In the context of this guide, the pertinent changes are shown here: +In the context of this guide, the pertinent changes are as follows: -|2.9.x|3.x| +| v2.9.x | v3.x | | --- | --- | -|**add-relation**|**integrate**| -|**relate**|**integrate**| -|**run**|**exec**| -|**run-action --wait**|**run**| +|`add-relation`|`integrate`| +|`relate`|`integrate`| +|`run`|`exec`| +|`run-action --wait`|`run`| See the [Juju 3.0 release notes](https://juju.is/docs/juju/roadmap#heading--juju-3-0-0---22-oct-2022) for the comprehensive list of changes. -The response is to therefore substitute the documented command with the equivalent 2.9.x command. For example: +Example substitutions: ### Juju 3.x: ```shell diff --git a/docs/explanation/e-security.md b/docs/explanation/e-security.md new file mode 100644 index 0000000000..24c97804cc --- /dev/null +++ b/docs/explanation/e-security.md @@ -0,0 +1,92 @@ +# Security hardening guide + +This document provides an overview of security features and guidance for hardening the security of [Charmed PostgreSQL](https://charmhub.io/postgresql) deployments, including setting up and managing a secure environment. + +## Environment + +The environment where Charmed PostgreSQL operates can be divided into two components: + +1. Cloud +2. Juju + +### Cloud + +Charmed PostgreSQL can be deployed on top of several clouds and virtualization layers: + +|Cloud|Security guides| +| --- | --- | +|OpenStack|[OpenStack Security Guide](https://docs.openstack.org/security-guide/)| +|AWS|[Best Practices for Security, Identity and Compliance](https://aws.amazon.com/architecture/security-identity-compliance), [AWS security credentials](https://docs.aws.amazon.com/IAM/latest/UserGuide/security-creds.html#access-keys-and-secret-access-keys)| +|Azure|[Azure security best practices and patterns](https://learn.microsoft.com/en-us/azure/security/fundamentals/best-practices-and-patterns), [Managed identities for Azure resource](https://learn.microsoft.com/en-us/entra/identity/managed-identities-azure-resources/)| +|GCP|[Google security overview](https://cloud.google.com/docs/security)| + +### Juju + +Juju is the component responsible for orchestrating the entire lifecycle, from deployment to Day 2 operations. For more information on Juju security hardening, see the [Juju security page](https://canonical-juju.readthedocs-hosted.com/en/latest/user/explanation/juju-security/) and the [How to harden your deployment](https://juju.is/docs/juju/harden-your-deployment) guide. + +#### Cloud credentials + +When configuring cloud credentials to be used with Juju, ensure that users have correct permissions to operate at the required level. Juju superusers responsible for bootstrapping and managing controllers require elevated permissions to manage several kinds of resources, such as virtual machines, networks, storages, etc. Please refer to the links below for more information on the policies required to be used depending on the cloud. + +|Cloud|Cloud user policies| +| --- | --- | +|OpenStack|N/A| +|AWS|[Juju AWS Permission](/t/juju-aws-permissions/5307), [AWS Instance Profiles](/t/using-aws-instance-profiles-with-juju-2-9/5185), [Juju on AWS](https://juju.is/docs/juju/amazon-ec2)| +|Azure|[Juju Azure Permission](https://juju.is/docs/juju/microsoft-azure), [How to use Juju with Microsoft Azure](/t/how-to-use-juju-with-microsoft-azure/15219)| +|GCP|[Google Cloud's Identity and Access Management](https://cloud.google.com/iam/docs/overview), [GCE role recommendations](https://cloud.google.com/policy-intelligence/docs/role-recommendations-overview), [Google GCE cloud and Juju](https://canonical-juju.readthedocs-hosted.com/en/latest/user/reference/cloud/list-of-supported-clouds/the-google-gce-cloud-and-juju/)| + +#### Juju users + +It is very important that Juju users are set up with minimal permissions depending on the scope of their operations. Please refer to the [User access levels](https://juju.is/docs/juju/user-permissions) documentation for more information on the access levels and corresponding abilities. + +Juju user credentials must be stored securely and rotated regularly to limit the chances of unauthorized access due to credentials leakage. + +## Applications + +In the following sections, we provide guidance on how to harden your deployment using: + +1. Operating system +2. Security upgrades +3. Encryption +4. Authentication +5. Monitoring and auditing + +### Operating system + +Charmed PostgreSQL and Charmed PgBouncer run on top of Ubuntu 22.04. Deploy a [Landscape Client Charm](https://charmhub.io/landscape-client?) to connect the underlying VM to a Landscape User Account to manage security upgrades and integrate [Ubuntu Pro](https://ubuntu.com/pro) subscriptions. + +### Security upgrades + +[Charmed PostgreSQL](https://charmhub.io/postgresql) and [Charmed PgBouncer](https://charmhub.io/pgbouncer) operators install pinned versions of their respective snaps to provide reproducible and secure environments. + +New versions (revisions) of the charmed operators can be released to update the operator's code, workloads, or both. It is important to refresh the charms regularly to make sure the workloads are as secure as possible. + +For more information on upgrading Charmed PostgreSQL, see the [How to upgrade PostgreSQL](https://canonical.com/data/docs/postgresql/iaas/h-upgrade) and [How to upgrade PgBouncer](https://charmhub.io/pgbouncer/docs/h-upgrade) guides, as well as the respective Release notes for [PostgreSQL](https://canonical.com/data/docs/postgresql/iaas/r-releases) and [PgBouncer](https://charmhub.io/pgbouncer/docs/r-releases). + +### Encryption + +To utilise encryption at transit for all internal and external cluster connections, integrate Charmed PostgreSQL with a TLS certificate provider. Please refer to the [Charming Security page](https://charmhub.io/topics/security-with-x-509-certificates) for more information on how to select the right certificate provider for your use case. + +Encryption in transit for backups is provided by the storage service (Charmed PostgreSQL is a client for an S3-compatible storage). + +For more information on encryption, see the [Cryptography](/t/charmed-postgresql-explanations-encryption/16853) explanation page and [How to enable encryption](https://canonical.com/data/docs/postgresql/iaas/h-enable-tls) guide. + +### Authentication + +Charmed PostgreSQL supports the password-based `scram-sha-256` authentication method for authentication between: + +* External connections to clients +* Internal connections between members of cluster +* PgBouncer connections + +For more implementation details, see the [PostgreSQL documentation](https://www.postgresql.org/docs/14/auth-password.html). + +### Monitoring and auditing + +Charmed PostgreSQL provides native integration with the [Canonical Observability Stack (COS)](https://charmhub.io/topics/canonical-observability-stack). To reduce the blast radius of infrastructure disruptions, the general recommendation is to deploy COS and the observed application into separate environments, isolated from one another. Refer to the [COS production deployments best practices](https://charmhub.io/topics/canonical-observability-stack/reference/best-practices) for more information or see the How to guides for PostgreSQL [monitoring](https://canonical.com/data/docs/postgresql/iaas/h-enable-monitoring), [alert rules](https://canonical.com/data/docs/postgresql/iaas/h-enable-alert-rules), and [tracing](https://canonical.com/data/docs/postgresql/iaas/h-enable-tracing) for practical instructions. + +PostgreSQL logs are stored in `/var/snap/charmed-postgresql/common/var/log/postgresql` within the postgresql container of each unit. It’s recommended to integrate the charm with [COS](/t/10600), from where the logs can be easily persisted and queried using [Loki](https://charmhub.io/loki-k8s)/[Grafana](https://charmhub.io/grafana). + +## Additional Resources + +For details on the cryptography used by Charmed PostgreSQL, see the [Cryptography](/t/charmed-postgresql-explanations-encryption/16853) explanation page. \ No newline at end of file diff --git a/docs/how-to.md b/docs/how-to.md new file mode 100644 index 0000000000..497d33ce18 --- /dev/null +++ b/docs/how-to.md @@ -0,0 +1,102 @@ +# How-to guides + +The following guides cover key processes and common tasks for managing and using Charmed PostgreSQL on machines. + +## Deployment and setup + +The following guides walk you through the details of how to install different cloud services and bootstrap them to Juju: +* [Sunbeam] +* [LXD] +* [MAAS] +* [AWS EC2] +* [GCE] +* [Azure] +* [Multi-availability zones (AZ)][Multi-AZ] + +The following guides cover some specific deployment scenarios and architectures: +* [Terraform] +* [Air-gapped] +* [TLS VIP access] + +## Usage and maintenance + +* [Integrate with another application] +* [External access] +* [Scale replicas] +* [Enable TLS] +* [Enable plugins/extensions] + +## Backup and restore +* [Configure S3 AWS] +* [Configure S3 RadosGW] +* [Create a backup] +* [Restore a backup] +* [Manage backup retention] +* [Migrate a cluster] + +## Monitoring (COS) + +* [Enable monitoring] +* [Enable alert rules] +* [Enable tracing] + +## Minor upgrades +* [Perform a minor upgrade] +* [Perform a minor rollback] + +## Cross-regional (cluster-cluster) async replication + +* [Cross-regional async replication] + * [Set up clusters] + * [Integrate with a client app] + * [Remove or recover a cluster] + * [Enable plugins/extensions] + +## Development + +This section is aimed at charm developers looking to support PostgreSQL integrations with their charm. + +* [Integrate with your charm] +* [Migrate data via pg_dump] +* [Migrate data via backup/restore] + + + +[Sunbeam]: /t/15972 +[LXD]: /t/11861 +[MAAS]: /t/14293 +[AWS EC2]: /t/15703 +[GCE]: /t/15722 +[Azure]: /t/15733 +[Multi-AZ]: /t/15749 +[Terraform]: /t/14916 +[Air-gapped]: /t/15746 +[TLS VIP access]: /t/16576 +[Integrate with another application]: /t/9687 +[External access]: /t/15802 +[Scale replicas]: /t/9689 +[Enable TLS]: /t/9685 + +[Configure S3 AWS]: /t/9681 +[Configure S3 RadosGW]: /t/10313 +[Create a backup]: /t/9683 +[Restore a backup]: /t/9693 +[Manage backup retention]: /t/14249 +[Migrate a cluster]: /t/9691 + +[Enable monitoring]: /t/10600 +[Enable alert rules]: /t/13084 +[Enable tracing]: /t/14521 + +[Perform a minor upgrade]: /t/12089 +[Perform a minor rollback]: /t/12090 + +[Cross-regional async replication]: /t/15412 +[Set up clusters]: /t/13991 +[Integrate with a client app]: /t/13992 +[Remove or recover a cluster]: /t/13994 +[Enable plugins/extensions]: /t/10906 + +[Integrate with your charm]: /t/11865 +[Migrate data via pg_dump]: /t/12163 +[Migrate data via backup/restore]: /t/12164 \ No newline at end of file diff --git a/docs/how-to/h-deploy-lxd.md b/docs/how-to/h-deploy-lxd.md deleted file mode 100644 index 02a1b4323c..0000000000 --- a/docs/how-to/h-deploy-lxd.md +++ /dev/null @@ -1,44 +0,0 @@ -# How to deploy on LXD - -This guide assumes you have a running Juju and LXD environment. - -For a detailed walkthrough of setting up an environment and deploying the charm on LXD, refer to the [Tutorial](/t/9707). - -## Prerequisites -* Canonical LXD 5.12+ -* Fulfil the general [system requirements](/t/11743) - ---- - -[Bootstrap](https://juju.is/docs/juju/juju-bootstrap) a juju controller and create a [model](https://juju.is/docs/juju/juju-add-model) if you haven't already: -```shell -juju bootstrap localhost -juju add-model -``` - -Deploy PostgreSQL: -```shell -juju deploy postgresql -``` -> See the [`juju deploy` documentation](https://juju.is/docs/juju/juju-deploy) for all available options at deploy time. -> -> See the [Configurations tab](https://charmhub.io/postgresql/configurations) for specific PostgreSQL parameters. - -Sample output of `juju status --watch 1s`: -```shell -Model Controller Cloud/Region Version SLA Timestamp -postgresql overlord localhost/localhost 2.9.42 unsupported 09:41:53+01:00 - -App Version Status Scale Charm Channel Rev Exposed Message -postgresql active 1 postgresql 14/stable 281 no - -Unit Workload Agent Machine Public address Ports Message -postgresql/0* active idle 0 10.89.49.129 - -Machine State Address Inst id Series AZ Message -0 started 10.89.49.129 juju-a8a31d-0 jammy Running -``` - -[note] -If you expect having several concurrent connections frequently, it is highly recommended to deploy [PgBouncer](https://charmhub.io/pgbouncer?channel=1/stable) alongside PostgreSQL. For more information, read our explanation about [Connection pooling](/t/15777). -[/note] \ No newline at end of file diff --git a/docs/how-to/h-deploy.md b/docs/how-to/h-deploy.md new file mode 100644 index 0000000000..4769d863c6 --- /dev/null +++ b/docs/how-to/h-deploy.md @@ -0,0 +1,83 @@ +# How to deploy + +This page aims to provide an introduction to the PostgreSQL deployment process and lists all the related guides. It contains the following sections: +* [General deployment instructions](#general-deployment-instructions) +* [Clouds](#clouds) +* [Special deployments](#special-deployments) + +--- + +## General deployment instructions + +The basic requirements for deploying a charm are the [**Juju client**](https://juju.is/docs/juju) and a machine [**cloud**](https://juju.is/docs/juju/cloud). + +First, [bootstrap](https://juju.is/docs/juju/juju-bootstrap) the cloud controller and create a [model](https://canonical-juju.readthedocs-hosted.com/en/latest/user/reference/model/): +```shell +juju bootstrap +juju add-model +``` + +Then, either continue with the `juju` client **or** use the `terraform juju` client to deploy the PostgreSQL charm. + +To deploy with the `juju` client: +```shell +juju deploy postgresql +``` +> See also: [`juju deploy` command](https://canonical-juju.readthedocs-hosted.com/en/latest/user/reference/juju-cli/list-of-juju-cli-commands/deploy/) + +To deploy with `terraform juju`, follow the guide [How to deploy using Terraform]. +> See also: [Terraform Provider for Juju documentation](https://canonical-terraform-provider-juju.readthedocs-hosted.com/en/latest/) + +If you are not sure where to start or would like a more guided walkthrough for setting up your environment, see the [Charmed PostgreSQL tutorial][Tutorial]. + +## Clouds + +The guides below go through the steps to install different cloud services and bootstrap them to Juju: +* [Sunbeam] +* [Canonical MAAS] +* [Amazon Web Services EC2] +* [Google Cloud Engine] +* [Azure] + +[How to deploy on multiple availability zones (AZ)] demonstrates how to deploy a cluster on a cloud using different AZs for high availability. + +## Special deployments + +These guides cover some specific deployment scenarios and architectures. + +### External TLS access +[How to deploy for external TLS VIP access] goes over an example deployment of PostgreSQL, PgBouncer and HAcluster that require external TLS/SSL access via [Virtual IP (VIP)](https://en.wikipedia.org/wiki/Virtual_IP_address). + +See also: +* [How to enable TLS] +* [How to connect from outside the local network] + +### Airgapped +[How to deploy in an offline or air-gapped environment] goes over the special configuration steps for installing PostgreSQL in an airgapped environment via CharmHub and the Snap Store Proxy. + +### Cluster-cluster replication +Cluster-cluster, cross-regional, or multi-server asynchronous replication focuses on disaster recovery by distributing data across different servers. + +The [Cross-regional async replication] guide goes through the steps to set up clusters for cluster-cluster replication, integrate with a client, and remove or recover a failed cluster. + +--- + + + +[Tutorial]: /t/9707 + +[How to deploy using Terraform]: /t/14916 + +[Sunbeam]: /t/15972 +[Canonical MAAS]: /t/14293 +[Amazon Web Services EC2]: /t/15703 +[Google Cloud Engine]: /t/15722 +[Azure]: /t/15733 +[How to deploy on multiple availability zones (AZ)]: /t/15749 + +[How to deploy for external TLS VIP access]: /t/16576 +[How to enable TLS]: /t/9685 +[How to connect from outside the local network]: /t/15802 + +[How to deploy in an offline or air-gapped environment]: /t/15746 +[Cross-regional async replication]: /t/15412 \ No newline at end of file diff --git a/docs/how-to/h-upgrade.md b/docs/how-to/h-upgrade.md new file mode 100644 index 0000000000..9a95915cac --- /dev/null +++ b/docs/how-to/h-upgrade.md @@ -0,0 +1,12 @@ +# Upgrade + +Currently, the charm supports PostgreSQL major version 14 only. Therefore, in-place upgrades/rollbacks are not possible for major versions. + +> **Note**: Canonical is not planning to support in-place upgrades for major version change. The new PostgreSQL charm will have to be installed nearby, and the data will be copied from the old to the new installation. After announcing the next PostgreSQL major version support, the appropriate documentation for data migration will be published. + +For instructions on carrying out **minor version upgrades**, see the following guides: + +* [Minor upgrade](/t/12089), e.g. PostgreSQL 14.8 -> PostgreSQL 14.9
+(including charm revision bump 42 -> 43). +* [Minor rollback](/t/12090), e.g. PostgreSQL 14.9 -> PostgreSQL 14.8
+(including charm revision return 43 -> 42). \ No newline at end of file diff --git a/docs/overview.md b/docs/overview.md index 949bf3c098..273a9b0f53 100644 --- a/docs/overview.md +++ b/docs/overview.md @@ -41,8 +41,7 @@ PostgreSQL is a trademark or registered trademark of PostgreSQL Global Developme | Level | Path | Navlink | |--------|--------|-------------| -| 1 | tutorial | [Tutorial]() | -| 2 | t-overview | [Overview](/t/9707) | +| 1 | tutorial | [Tutorial](/t/9707) | | 2 | t-set-up | [1. Set up environment](/t/9709) | | 2 | t-deploy | [2. Deploy PostgreSQL](/t/9697) | | 2 | t-access| [3. Access PostgreSQL](/t/15798) | @@ -51,10 +50,9 @@ PostgreSQL is a trademark or registered trademark of PostgreSQL Global Developme | 2 | t-integrate | [6. Integrate with other applications](/t/9701) | | 2 | t-enable-tls | [7. Enable TLS encryption](/t/9699) | | 2 | t-clean-up | [8. Clean up environment](/t/9695) | -| 1 | how-to | [How to]() | -| 2 | h-deploy | [Deploy]() | +| 1 | how-to | [How-to guides](/t/16766) | +| 2 | h-deploy | [Deploy](/t/16811) | | 3 | h-deploy-sunbeam | [Sunbeam](/t/15972) | -| 3 | h-deploy-lxd | [LXD](/t/11861) | | 3 | h-deploy-maas | [MAAS](/t/14293) | | 3 | h-deploy-ec2 | [AWS EC2](/t/15703) | | 3 | h-deploy-gce | [GCE](/t/15722) | @@ -64,9 +62,10 @@ PostgreSQL is a trademark or registered trademark of PostgreSQL Global Developme | 3 | h-deploy-airgapped | [Air-gapped](/t/15746) | | 3 | h-deploy-tls-vip-access | [TLS VIP access](/t/16576) | | 2 | h-integrate | [Integrate with another application](/t/9687) | -| 2 | h-external-access | [External access](/t/15802) | +| 2 | h-external-access | [External network access](/t/15802) | | 2 | h-scale | [Scale replicas](/t/9689) | | 2 | h-enable-tls | [Enable TLS](/t/9685) | +| 2 | h-enable-plugins-extensions | [Enable plugins/extensions](/t/10906) | | 2 | h-backup | [Back up and restore]() | | 3 | h-configure-s3-aws | [Configure S3 AWS](/t/9681) | | 3 | h-configure-s3-radosgw | [Configure S3 RadosGW](/t/10313) | @@ -78,28 +77,19 @@ PostgreSQL is a trademark or registered trademark of PostgreSQL Global Developme | 3 | h-enable-monitoring | [Enable monitoring](/t/10600) | | 3 | h-enable-alert-rules | [Enable alert rules](/t/13084) | | 3 | h-enable-tracing | [Enable tracing](/t/14521) | -| 2 | h-upgrade | [Minor upgrades]() | +| 2 | h-upgrade | [Upgrade](/t/12086) | | 3 | h-upgrade-minor | [Perform a minor upgrade](/t/12089) | | 3 | h-rollback-minor | [Perform a minor rollback](/t/12090) | | 2 | h-async | [Cross-regional async replication](/t/15412) | | 3 | h-async-set-up | [Set up clusters](/t/13991) | | 3 | h-async-integrate | [Integrate with a client app](/t/13992) | | 3 | h-async-remove-recover | [Remove or recover a cluster](/t/13994) | -| 2 | h-enable-plugins-extensions | [Enable plugins/extensions](/t/10906) | | 2 | h-development| [Development]() | | 3 | h-development-integrate | [Integrate with your charm](/t/11865) | | 3 | h-migrate-pgdump | [Migrate data via pg_dump](/t/12163) | | 3 | h-migrate-backup-restore | [Migrate data via backup/restore](/t/12164) | -| 1 | reference | [Reference]() | -| 2 | r-overview | [Overview](/t/13976) | -| 2 | r-releases | [Release Notes](/t/11875) | -| 3 | r-revision-544-545| [Revision 544/545](/t/16007) | -| 3 | r-revision-467-468 | [Revision 467/468](/t/15378) | -| 3 | r-revision-429-430 | [Revision 429/430](/t/14067) | -| 3 | r-revision-363 | [Revision 363](/t/13124) | -| 3 | r-revision-351 | [Revision 351](/t/12823) | -| 3 | r-revision-336 | [Revision 336](/t/11877) | -| 3 | r-revision-288 | [Revision 288](/t/11876) | +| 1 | reference | [Reference](/t/13976) | +| 2 | r-releases | [Releases](/t/11875) | | 2 | r-system-requirements | [System requirements](/t/11743) | | 2 | r-software-testing | [Software testing](/t/11773) | | 2 | r-performance | [Performance and resources](/t/11974) | @@ -108,20 +98,31 @@ PostgreSQL is a trademark or registered trademark of PostgreSQL Global Developme | 2 | r-alert-rules | [Alert rules](/t/15841) | | 2 | r-statuses | [Statuses](/t/10844) | | 2 | r-contacts | [Contacts](/t/11863) | -| 1 | explanation | [Explanation]() | +| 1 | explanation | [Explanation](/t/16768) | | 2 | e-architecture | [Architecture](/t/11857) | +| 2 | e-security | [Security](/t/16852) | +| 2 | e-cryptography | [Cryptography](/t/16853) | | 2 | e-interfaces-endpoints | [Interfaces and endpoints](/t/10251) | +| 2 | e-connection-pooling| [Connection pooling](/t/15777) | | 2 | e-users | [Users](/t/10798) | | 2 | e-logs | [Logs](/t/12099) | | 2 | e-juju-details | [Juju](/t/11985) | | 2 | e-legacy-charm | [Legacy charm](/t/10690) | -| 2 | e-connection-pooling| [Connection pooling](/t/15777) | | 1 | search | [Search](https://canonical.com/data/docs/postgresql/iaas) | [/details] - \ No newline at end of file diff --git a/docs/reference/r-overview.md b/docs/reference.md similarity index 82% rename from docs/reference/r-overview.md rename to docs/reference.md index 8392dce767..f686a8f86b 100644 --- a/docs/reference/r-overview.md +++ b/docs/reference.md @@ -1,4 +1,4 @@ -# Overview +# Reference The Reference section of our documentation contains pages for technical specifications, APIs, release notes, and other reference material for fast lookup. @@ -7,14 +7,16 @@ The Reference section of our documentation contains pages for technical specific |---------------------------|---------------------------------------------------| | [Release Notes](/t/11875) | Release notes for major revisions of this charm | | [System requirements](/t/11743) | Software and hardware requirements | -| [Testing](/t/11773) | Software tests (e.g. smoke, unit, performance...) | +| [Software testing](/t/11773) | Software tests (e.g. smoke, unit, performance...) | +| [Performance and resources](/t/11974) | Config profiles related to performance | | [Troubleshooting](/t/11864) | Troubleshooting tips and tricks | -| [Profiles](/t/11974) | Config profiles related to performance | | [Plugins/extensions](/t/10946) | Plugins/extensions supported by each charm revision | +| [Alert rules](/t/15841) | Pre-configured Prometheus alert rules | +| [Charm statuses](/t/10844) | Juju application statuses | | [Contacts](/t/11863) | Contact information | -**In the tabs at the top of the page**, you can find the following automatically generated API references: +**In the tabs at the top of the page**, you will find the following automatically generated API references: | Page | Description | |----------------------------------------------------------------------------|---------------------------------------------------------| diff --git a/docs/reference/r-releases.md b/docs/reference/r-releases.md index 5c4b104362..f67d6fc623 100644 --- a/docs/reference/r-releases.md +++ b/docs/reference/r-releases.md @@ -1,4 +1,4 @@ -# Release Notes +# Releases This page provides high-level overviews of the dependencies and features that are supported by each revision in every stable release. @@ -9,14 +9,14 @@ To see all releases and commits, check the [Charmed PostgreSQL Releases page on ## Dependencies and supported features For a given release, this table shows: -* The PostgreSQL version packaged inside +* The PostgreSQL version packaged inside. * The minimum Juju 3 version required to reliably operate **all** features of the release > This charm still supports older versions of Juju down to 2.9. See the [Juju section of the system requirements](/t/11743) for more details. * Support for specific features | Release | PostgreSQL version | Juju 3 version | [TLS encryption](/t/9685)* | [COS monitoring](/t/10600) | [Minor version upgrades](/t/12089) | [Cross-regional async replication](/t/15412) | [Point-in-time recovery](/t/9693) | |:---:|:---:|:---:|:---:|:---:|:---:|:---:|:---:| -| [544], [545] (14/candidate) | 14.15 | `3.6.1+` | ![check] | ![check] | ![check] | ![check] | ![check] | +| [552], [553] | 14.15 | `3.6.1+` | ![check] | ![check] | ![check] | ![check] | ![check] | | [467], [468] | 14.12 | `3.4.3+` | ![check] | ![check] | ![check] | ![check] | ![check] | | [429], [430] | 14.11 | `3.4.2+` | ![check] | ![check] | ![check] | ![check] | | | [363] | 14.10 | `3.4.2+` | ![check] | ![check] | ![check] | ![check] | | @@ -36,23 +36,22 @@ Several [revisions](https://juju.is/docs/sdk/revision) are released simultaneous > If you deploy a specific revision, **you must make sure it matches your base and architecture** via the tables below or with [`juju info`](https://juju.is/docs/juju/juju-info) - +|[553] | ![check] | | ![check] | +|[552] | | ![check] | ![check] | -### Release 467-468 (`14/stable`) +[details=Older releases] + +### Release 467-468 | Revision | amd64 | arm64 | Ubuntu 22.04 LTS |:--------:|:-----:|:-----:|:-----:| |[468] |![check] | | ![check] | |[467] | | ![check]| ![check] | -[details=Older releases] ### Release 429-430 | Revision | amd64 | arm64 | Ubuntu 22.04 LTS @@ -93,21 +92,23 @@ Several [revisions](https://juju.is/docs/sdk/revision) are released simultaneous For a list of all plugins supported for each revision, see the reference page [Plugins/extensions](/t/10946). -[note] - Our release notes are an ongoing work in progress. If there is any additional information about releases that you would like to see or suggestions for other improvements, don't hesitate to contact us on [Matrix ](https://matrix.to/#/#charmhub-data-platform:ubuntu.com) or [leave a comment](https://discourse.charmhub.io/t/charmed-postgresql-reference-release-notes/11875). -[/note] +> **Note**: Our release notes are an ongoing work in progress. If there is any additional information about releases that you would like to see or suggestions for other improvements, don't hesitate to contact us on [Matrix ](https://matrix.to/#/#charmhub-data-platform:ubuntu.com) or [leave a comment](https://discourse.charmhub.io/t/charmed-postgresql-reference-release-notes/11875). -[545]: /t/16007 -[544]: /t/16007 -[468]: /t/15378 -[467]: /t/15378 -[430]: /t/14067 -[429]: /t/14067 -[363]: /t/13124 -[351]: /t/12823 -[336]: /t/11877 -[288]: /t/11876 +[553]: https://github.com/canonical/postgresql-operator/releases/tag/rev552 +[552]: https://github.com/canonical/postgresql-operator/releases/tag/rev552 + +[468]: https://github.com/canonical/postgresql-operator/releases/tag/rev467 +[467]: https://github.com/canonical/postgresql-operator/releases/tag/rev467 + +[430]: https://github.com/canonical/postgresql-operator/releases/tag/rev429 +[429]: https://github.com/canonical/postgresql-operator/releases/tag/rev429 + +[363]: https://github.com/canonical/postgresql-operator/releases/tag/rev363 +[351]: https://github.com/canonical/postgresql-operator/releases/tag/rev351 +[336]: https://github.com/canonical/postgresql-operator/releases/tag/rev336 +[288]: https://github.com/canonical/postgresql-operator/releases/tag/rev288 + [check]: https://img.icons8.com/color/20/checkmark--v1.png \ No newline at end of file diff --git a/docs/reference/r-revision-288.md b/docs/reference/r-revision-288.md deleted file mode 100644 index 3297b52ac6..0000000000 --- a/docs/reference/r-revision-288.md +++ /dev/null @@ -1,50 +0,0 @@ ->Reference > Release Notes > [All revisions](/t/11875) > [Revision 288](/t/11876) -# Revision 288 -Thursday, April 20, 2023 - -Dear community, - -We'd like to announce that Canonical's newest Charmed PostgreSQL operator for IAAS/VM has been published in the `14/stable` [channel](https://charmhub.io/postgresql?channel=14/stable). :tada: - -## Features you can start using today -* Deploying on VM (tested with LXD, MAAS) -* Scaling up/down in one simple juju command -* HA using [Patroni](https://github.com/zalando/patroni) -* Full backups and restores are supported when using any S3-compatible storage -* TLS support (using “[tls-certificates](https://charmhub.io/tls-certificates-operator)” operator) -* DB access outside of Juju using “[data-integrator](https://charmhub.io/data-integrator)” -* Data import using standard tools e.g. “psql”. -* Documentation: - - -## Inside the charms: - -* Charmed PostgreSQL charm ships the latest PostgreSQL “14.7-0ubuntu0.22.04.1” -* VM charms [based on our](https://snapcraft.io/publisher/dataplatformbot) SNAP (Ubuntu LTS “22.04” - core22-based) -* Principal charms supports the latest LTS series “22.04” only. -* Subordinate charms support LTS “22.04” and “20.04” only. - -## Technical notes - - * The new PostgreSQL charm is also a juju interface-compatible replacement for legacy PostgreSQL charms (using legacy interface `pgsql`, via endpoints `db` and `db-admin`). -However, **it is highly recommended to migrate to the modern interface [`postgresql_client`](https://github.com/canonical/charm-relation-interfaces)** (endpoint `database`). - * Please [contact us](#heading--contact) if you are considering migrating from other “legacy” charms not mentioned above. -* Charmed PostgreSQL follows SNAP track “14”. -* No “latest” track in use (no surprises in tracking “latest/stable”)! - * PostgreSQL charm provide [legacy charm](/t/10690) through “latest/stable”. -* You can find charm lifecycle flowchart diagrams [here](https://github.com/canonical/postgresql-k8s-operator/tree/main/docs/reference). -* Modern interfaces are well described in the [Interfaces catalogue](https://github.com/canonical/charm-relation-interfaces) and implemented by [`data-platform-libs`](https://github.com/canonical/data-platform-libs/). -* Known limitation: PostgreSQL extensions are not yet supported. - -

Contact us

-Charmed PostgreSQL is an open source project that warmly welcomes community contributions, suggestions, fixes, and constructive feedback. - -* Raise software issues or feature requests on [**GitHub**](https://github.com/canonical/postgresql-operator/issues/new/choose) -* Report security issues through [**Launchpad**](https://wiki.ubuntu.com/DebuggingSecurity#How%20to%20File) -* Contact the Canonical Data Platform team through our [Matrix](https://matrix.to/#/#charmhub-data-platform:ubuntu.com) channel! - - \ No newline at end of file diff --git a/docs/reference/r-revision-336.md b/docs/reference/r-revision-336.md deleted file mode 100644 index 4c3d98fa3a..0000000000 --- a/docs/reference/r-revision-336.md +++ /dev/null @@ -1,81 +0,0 @@ ->Reference > Release Notes > [All revisions](/t/11875) > [Revision 336](/t/11877) -# Revision 336 -Wednesday, October 18, 2023 - -Dear community, - -We'd like to announce that Canonical's newest Charmed PostgreSQL operator for IAAS/VM has been published in the `14/stable` [channel](https://charmhub.io/postgresql?channel=14/stable). :tada: - -If you are jumping over several stable revisions, make sure to check [previous release notes](/t/11875) before upgrading to this revision. - -## Features you can start using today -* [Add Juju 3 support](/t/11743) (Juju 2 is still supported) [[DPE-1758](https://warthogs.atlassian.net/browse/DPE-1758)] -* All secrets are now stored in [Juju secrets](https://juju.is/docs/juju/manage-secrets) [[DPE-1758](https://warthogs.atlassian.net/browse/DPE-1758)] -* Charm [minor upgrades](/t/12089) and [minor rollbacks](/t/12090) [[DPE-1767](https://warthogs.atlassian.net/browse/DPE-1767)] -* [Canonical Observability Stack (COS)](https://charmhub.io/topics/canonical-observability-stack) support [[DPE-1775](https://warthogs.atlassian.net/browse/DPE-1775)] -* [PostgreSQL plugins support](/t/10906) [[DPE-1373](https://warthogs.atlassian.net/browse/DPE-1373)] -* [Profiles configuration](/t/11974) support [[DPE-2655](https://warthogs.atlassian.net/browse/DPE-2655)] -* [Logs rotation](/t/12099) [[DPE-1754](https://warthogs.atlassian.net/browse/DPE-1754)] -* Workload updated to [PostgreSQL 14.9](https://www.postgresql.org/docs/14/release-14-9.html) [[PR#18](https://github.com/canonical/charmed-postgresql-snap/pull/18)] -* Add '`admin`' [extra user role](https://github.com/canonical/postgresql-operator/pull/199) [[DPE-2167](https://warthogs.atlassian.net/browse/DPE-2167)] -* New charm '[PostgreSQL Test App](https://charmhub.io/postgresql-test-app)' -* New documentation: - * [Architecture (HLD/LLD)](/t/11857) - * [Upgrade section](/t/12086) - * [Release Notes](/t/11875) - * [Requirements](/t/11743) - * [Profiles](/t/11974) - * [Users](/t/10798) - * [Logs](/t/12099) - * [Statuses](/t/10844) - * [Development](/t/11862) - * [Testing reference](/t/11773) - * [Legacy charm](/t/10690) - * [Plugins/extensions](/t/10906), [supported](/t/10946) - * [Juju 2.x vs 3.x hints](/t/11985) - * [Contacts](/t/11863) -* All the functionality from [the previous revisions](/t/11875) - -## Bugfixes -* [DPE-1624](https://warthogs.atlassian.net/browse/DPE-1624), [DPE-1625](https://warthogs.atlassian.net/browse/DPE-1625) Backup/restore fixes -* [DPE-1926](https://warthogs.atlassian.net/browse/DPE-1926) Remove fallback_application_name field from relation data -* [DPE-1712](https://warthogs.atlassian.net/browse/DPE-1712) Enabled the user to fix network issues and rerun stanza related hooks -* [DPE-2173](https://warthogs.atlassian.net/browse/DPE-2173) Fix allowed units relation data field -* [DPE-2127](https://warthogs.atlassian.net/browse/DPE-2127) Fixed databases access -* [DPE-2341](https://warthogs.atlassian.net/browse/DPE-2341) Populate extensions in unit databag -* [DPE-2218](https://warthogs.atlassian.net/browse/DPE-2218) Update charm libs to get s3 relation fix -* [DPE-1210](https://warthogs.atlassian.net/browse/DPE-1210), [DPE-2330](https://warthogs.atlassian.net/browse/DPE-2330), [DPE-2212](https://warthogs.atlassian.net/browse/DPE-2212) Open port (ability to expose charm) -* [DPE-2614](https://warthogs.atlassian.net/browse/DPE-2614) Split stanza create and stanza check -* [DPE-2721](https://warthogs.atlassian.net/browse/DPE-2721) Allow network access for pg_dump, pg_dumpall and pg_restore -* [DPE-2717](https://warthogs.atlassian.net/browse/DPE-2717) Copy dashboard changes from K8s and use the correct topology dispatcher -* [MISC] Copy fixes of DPE-2626 and DPE-2627 from k8s -* [MISC] Don't fail if the unit is already missing -* [MISC] More resilient topology observer - -Canonical Data issues are now public on both [Jira](https://warthogs.atlassian.net/jira/software/c/projects/DPE/issues/) and [GitHub](https://github.com/canonical/postgresql-operator/issues) platforms. - -[GitHub Releases](https://github.com/canonical/postgresql-operator/releases) provide a detailed list of bugfixes, PRs, and commits for each revision. - -## Inside the charms - -* Charmed PostgreSQL ships the latest PostgreSQL “14.9-0ubuntu0.22.04.1” -* PostgreSQL cluster manager Patroni updated to "3.0.2" -* Backup tools pgBackRest updated to "2.47" -* The Prometheus postgres-exporter is "0.12.1-0ubuntu0.22.04.1~ppa1" -* VM charms based on [Charmed PostgreSQL](https://snapcraft.io/charmed-postgresql) SNAP (Ubuntu LTS “22.04” - ubuntu:22.04-based) -* Principal charms supports the latest LTS series “22.04” only. -* Subordinate charms support LTS “22.04” and “20.04” only. - -## Technical notes - -* `juju refresh` from the old-stable revision 288 to the current-revision 324 is **NOT** supported!!!
The [upgrade](/t/12086) functionality is new and supported for revision 324+ only! -* Please check additionally [the previously posted restrictions](/t/11876). -* Ensure [the charm requirements](/t/11743) met - -## Contact us - -Charmed PostgreSQL is an open source project that warmly welcomes community contributions, suggestions, fixes, and constructive feedback. - -* Raise software issues or feature requests on [**GitHub**](https://github.com/canonical/postgresql-operator/issues/new/choose) -* Report security issues through [**Launchpad**](https://wiki.ubuntu.com/DebuggingSecurity#How%20to%20File) -* Contact the Canonical Data Platform team through our [Matrix](https://matrix.to/#/#charmhub-data-platform:ubuntu.com) channel. \ No newline at end of file diff --git a/docs/reference/r-revision-351.md b/docs/reference/r-revision-351.md deleted file mode 100644 index 1e3648b1ba..0000000000 --- a/docs/reference/r-revision-351.md +++ /dev/null @@ -1,57 +0,0 @@ ->Reference > Release Notes > [All revisions](/t/11875) > [Revision 351](/t/12823) -# Revision 351 -January 3, 2024 - -Dear community, - -We'd like to announce that Canonical's newest Charmed PostgreSQL operator for IAAS/VM has been published in the `14/stable` [channel](https://charmhub.io/postgresql?channel=14/stable). - -If you are jumping over several stable revisions, make sure to check [previous release notes](/t/11875) before upgrading to this revision. - -## Features you can start using today - -* [Core] Updated `Charmed PostgreSQL` SNAP image ([PR#291](https://github.com/canonical/postgresql-operator/pull/291))([DPE-3039](https://warthogs.atlassian.net/browse/DPE-3039)): - * `Patroni` updated from 3.0.2 to 3.1.2 - * `Pgbackrest` updated from 2.47 to 2.48 -* [Plugins] [Add 24 new plugins/extension](https://charmhub.io/postgresql/docs/r-plugins-extensions) in ([PR#251](https://github.com/canonical/postgresql-operator/pull/251)) -* [Plugins] **NOTE**: extension `plpython3u` is deprecated and will be removed from [list of supported plugins](/t/10946) soon! -* [Config] [Add 29 new configuration options](https://charmhub.io/postgresql/configure) in ([PR#239](https://github.com/canonical/postgresql-operator/pull/239))([DPE-1781](https://warthogs.atlassian.net/browse/DPE-1781)) -* [Config] **NOTE:** the config option `profile-limit-memory` is deprecated. Use `profile_limit_memory` (to follow the [naming conventions](https://juju.is/docs/sdk/naming))! ([PR#306](https://github.com/canonical/postgresql-operator/pull/306))([DPE-3096](https://warthogs.atlassian.net/browse/DPE-3096)) -* [Charm] Add Juju Secret labels in ([PR#270](https://github.com/canonical/postgresql-operator/pull/270))([DPE-2838](https://warthogs.atlassian.net/browse/DPE-2838)) -* [Charm] Update Python dependencies in ([PR#293](https://github.com/canonical/postgresql-operator/pull/293)) -* [DB] Add handling of tables ownership in ([PR#298](https://github.com/canonical/postgresql-operator/pull/298))([DPE-2740](https://warthogs.atlassian.net/browse/DPE-2740)) -* ([COS](https://charmhub.io/topics/canonical-observability-stack)) Moved Grafana dashboard legends to the bottom of the graph in ([PR#295](https://github.com/canonical/postgresql-operator/pull/295))([DPE-2622](https://warthogs.atlassian.net/browse/DPE-2622)) -* ([COS](https://charmhub.io/topics/canonical-observability-stack)) Add Patroni COS support ([#261](https://github.com/canonical/postgresql-operator/pull/261))([DPE-1993](https://warthogs.atlassian.net/browse/DPE-1993)) -* [CI/CD] Charm migrated to GitHub Data reusable workflow in ([PR#263](https://github.com/canonical/postgresql-operator/pull/263))([DPE-2789](https://warthogs.atlassian.net/browse/DPE-2789)) -* All the functionality from [the previous revisions](/t/11875) - -## Bugfixes - -* Fixed enabling extensions when new database is created ([PR#252](https://github.com/canonical/postgresql-operator/pull/252))([DPE-2569](https://warthogs.atlassian.net/browse/DPE-2569)) -* Block the charm if the legacy interface requests [roles](https://discourse.charmhub.io/t/charmed-postgresql-explanations-interfaces-endpoints/10251) ([DPE-3077](https://warthogs.atlassian.net/browse/DPE-3077)) - -Canonical Data issues are now public on both [Jira](https://warthogs.atlassian.net/jira/software/c/projects/DPE/issues/) and [GitHub](https://github.com/canonical/postgresql-operator/issues) platforms. -[GitHub Releases](https://github.com/canonical/postgresql-operator/releases) provide a detailed list of bugfixes, PRs, and commits for each revision. -## Inside the charms - -* Charmed PostgreSQL ships the latest PostgreSQL “14.9-0ubuntu0.22.04.1” -* PostgreSQL cluster manager Patroni updated to "3.2.1" -* Backup tools pgBackRest updated to "2.48" -* The Prometheus postgres-exporter is "0.12.1-0ubuntu0.22.04.1~ppa1" -* VM charms based on [Charmed PostgreSQL](https://snapcraft.io/charmed-postgresql) SNAP (Ubuntu LTS “22.04” - ubuntu:22.04-based) revision 89 -* Principal charms supports the latest LTS series “22.04” only -* Subordinate charms support LTS “22.04” and “20.04” only - -## Technical notes - -* Upgrade (`juju refresh`) is possible from this revision 336+ -* Use this operator together with a modern operator "[pgBouncer](https://charmhub.io/pgbouncer?channel=1/stable)" -* Please check additionally [the previously posted restrictions](/t/11875) -* Ensure [the charm requirements](/t/11743) met - -## Contact us - -Charmed PostgreSQL is an open source project that warmly welcomes community contributions, suggestions, fixes, and constructive feedback. -* Raise software issues or feature requests on [**GitHub**](https://github.com/canonical/postgresql-operator/issues/new/choose) -* Report security issues through [**Launchpad**](https://wiki.ubuntu.com/DebuggingSecurity#How%20to%20File) -* Contact the Canonical Data Platform team through our [Matrix](https://matrix.to/#/#charmhub-data-platform:ubuntu.com) channel. \ No newline at end of file diff --git a/docs/reference/r-revision-363.md b/docs/reference/r-revision-363.md deleted file mode 100644 index e3614ea343..0000000000 --- a/docs/reference/r-revision-363.md +++ /dev/null @@ -1,55 +0,0 @@ ->Reference > Release Notes > [All revisions](/t/11875) > [Revision 363](/t/13124) -# Revision 363 -February 21, 2024 - -Dear community, - -We'd like to announce that Canonical's newest Charmed PostgreSQL operator for IAAS/VM has been published in the `14/stable` [channel](https://charmhub.io/postgresql?channel=14/stable) :tada: - -If you are jumping over several stable revisions, make sure to check [previous release notes](/t/11875) before upgrading to this revision. - -## Features you can start using today -* [CORE] PostgreSQL upgrade 14.9 -> 14.10. ([DPE-3217](https://warthogs.atlassian.net/browse/DPE-3217)) - * **Note**: It is advisable to REINDEX potentially-affected indexes after installing this update! (See [PostgreSQL changelog](https://changelogs.ubuntu.com/changelogs/pool/main/p/postgresql-14/postgresql-14_14.10-0ubuntu0.22.04.1/changelog)) -* [CORE] Juju 3.1.7+ support ([#2037120](https://bugs.launchpad.net/juju/+bug/2037120)) -* [PLUGINS] pgVector extension/plugin ([DPE-3159](https://warthogs.atlassian.net/browse/DPE-3159)) -* [PLUGINS] New PostGIS plugin ([#312](https://github.com/canonical/postgresql-operator/pull/312)) -* [PLUGINS] More new plugins - [50 in total](/t/10946) -* [MONITORING] COS Awesome Alert rules ([DPE-3160](https://warthogs.atlassian.net/browse/DPE-3160)) -* [SECURITY] Updated TLS libraries for compatibility with new charms - * [manual-tls-certificates](https://charmhub.io/manual-tls-certificates) - * [self-signed-certificates](https://charmhub.io/self-signed-certificates) - * Any charms compatible with [ tls_certificates_interface.v2.tls_certificates](https://charmhub.io/tls-certificates-interface/libraries/tls_certificates) -* All functionality from [previous revisions](/t/11875) - -## Bugfixes - -* [DPE-3199](https://warthogs.atlassian.net/browse/DPE-3199) Stabilized internal Juju secrets management -* [DPE-3258](https://warthogs.atlassian.net/browse/DPE-3258) Check system identifier in stanza (backups setup stabilization) - -Canonical Data issues are now public on both [Jira](https://warthogs.atlassian.net/jira/software/c/projects/DPE/issues/) and [GitHub](https://github.com/canonical/postgresql-operator/issues) platforms. -[GitHub Releases](https://github.com/canonical/postgresql-operator/releases) provide a detailed list of bugfixes, PRs, and commits for each revision. - -## What is inside the charms - -* Charmed PostgreSQL ships the latest PostgreSQL `14.10-0ubuntu0.22.04.1` -* PostgreSQL cluster manager Patroni updated to `v.3.1.2` -* Backup tools pgBackRest updated to `v.2.48` -* The Prometheus postgres-exporter is `0.12.1-0ubuntu0.22.04.1~ppa1` -* VM charms based on [Charmed PostgreSQL](https://snapcraft.io/charmed-postgresql) SNAP (Ubuntu LTS 22.04 - `ubuntu:22.04-based`) revision 96 -* Principal charms supports the latest LTS series 22.04 only -* Subordinate charms support LTS 22.04 and 20.04 only - -## Technical notes - -* Starting with this revision (336+), you can use `juju refresh` to upgrade Charmed PostgreSQL -* Use this operator together with modern [Charmed PgBouncer operator](https://charmhub.io/pgbouncer?channel=1/stable) -* Please check [the previously posted restrictions](/t/11875) -* Ensure [the charm requirements](/t/11743) met - -## Contact us - -Charmed PostgreSQL is an open source project that warmly welcomes community contributions, suggestions, fixes, and constructive feedback. -* Raise software issues or feature requests on [**GitHub**](https://github.com/canonical/postgresql-operator/issues/new/choose) -* Report security issues through [**Launchpad**](https://wiki.ubuntu.com/DebuggingSecurity#How%20to%20File) -* Contact the Canonical Data Platform team through our [Matrix](https://matrix.to/#/#charmhub-data-platform:ubuntu.com) channel. \ No newline at end of file diff --git a/docs/reference/r-revision-429-430.md b/docs/reference/r-revision-429-430.md deleted file mode 100644 index a1efd30106..0000000000 --- a/docs/reference/r-revision-429-430.md +++ /dev/null @@ -1,98 +0,0 @@ ->Reference > Release Notes > [All revisions](t/11875) > Revision 429/430 - -# Revision 429/430 - -June 28, 2024 - -Dear community, - -Canonical's newest Charmed PostgreSQL operator has been published in the 14/stable [channel](https://charmhub.io/postgresql?channel=14/stable) :tada: - -Due to the newly added support for `arm64` architecture, the PostgreSQL charm now releases two revisions simultaneously: -* Revision 429 is built for `amd64` -* Revision 430 is built for for `arm64` - -To make sure you deploy for the right architecture, we recommend setting an [architecture constraint](https://juju.is/docs/juju/constraint#heading--arch) for your entire juju model. - -Otherwise, it can be done at deploy time with the `--constraints` flag: -```shell -juju deploy postgresql --constraints arch= -``` -where `` can be `amd64` or `arm64`. - ---- - -## Highlights -Below are the major highlights of this release. To see all changes since the previous stable release, check the [release notes on GitHub](https://github.com/canonical/postgresql-operator/releases/tag/rev430). - -* Upgraded PostgreSQL from v.14.10 → v.14.11 ([PR #432](https://github.com/canonical/postgresql-operator/pull/432)) - * Check the official [PostgreSQL release notes](https://www.postgresql.org/docs/release/14.11/) -* Added support for ARM64 architecture ([PR #381](https://github.com/canonical/postgresql-operator/pull/381)) -* Added support for cross-regional asynchronous replication ([PR #452](https://github.com/canonical/postgresql-operator/pull/452)) ([DPE-2953](https://warthogs.atlassian.net/browse/DPE-2953)) - * This feature focuses on disaster recovery by distributing data across different servers. Check our [new how-to guides](https://charmhub.io/postgresql/docs/h-async-set-up) for a walkthrough of the cross-model setup, promotion, switchover, and other details. -* Added support for tracing with Tempo K8s ([PR #485](https://github.com/canonical/postgresql-operator/pull/485)) ([DPE-4616](https://warthogs.atlassian.net/browse/DPE-4616)) - * Check our new guide: [How to enable tracing](https://charmhub.io/postgresql/docs/h-enable-tracing) -* Released new [Charmed Sysbench operator](https://charmhub.io/sysbench) for easy performance testing - -### Enhancements -* Added timescaledb plugin/extension ([PR#470](https://github.com/canonical/postgresql-operator/pull/470)) - * See the [Configuration tab]((https://charmhub.io/postgresql/configuration?channel=14/candidate#plugin_timescaledb_enable)) for a full list of supported plugins/extensions -* Added incremental and differential backup support ([PR #479](https://github.com/canonical/postgresql-operator/pull/479)) ([DPE-4462](https://warthogs.atlassian.net/browse/DPE-4462)) - * Check our guide: [How to create and list backups](https://charmhub.io/postgresql/docs/h-create-backup) -* Added support for disabling the operator ([PR#412](https://github.com/canonical/postgresql-operator/pull/412)) ([DPE-2469](https://warthogs.atlassian.net/browse/DPE-2469)) -* Added support for subordination with: - * `ubuntu-advantage` ([PR#397](https://github.com/canonical/postgresql-operator/pull/397)) ([DPE-3644](https://warthogs.atlassian.net/browse/DPE-3644)) - * `landscape-client` ([PR#388](https://github.com/canonical/postgresql-operator/pull/388)) ([DPE-3644](https://warthogs.atlassian.net/browse/DPE-3644)) -* Added configuration option for backup retention time ([PR#474](https://github.com/canonical/postgresql-operator/pull/474))([DPE-4401](https://warthogs.atlassian.net/browse/DPE-4401)) -* Added `experimental_max_connections` config option ([PR#472](https://github.com/canonical/postgresql-operator/pull/472)) -* Added check for replicas encrypted connection ([PR#437](https://github.com/canonical/postgresql-operator/pull/437)) - -### Bugfixes -* Fixed slow charm bootstrap time ([PR#413](https://github.com/canonical/postgresql-operator/pull/413)) -* Fixed large objects ownership ([PR#349](https://github.com/canonical/postgresql-operator/pull/349)) -* Fixed secrets crash for "certificates-relation-changed" after the refresh ([PR#475](https://github.com/canonical/postgresql-operator/pull/475)) -* Fixed network cut tests ([PR#346](https://github.com/canonical/postgresql-operator/pull/346)) ([DPE-3257](https://warthogs.atlassian.net/browse/DPE-3257)) - -Canonical Data issues are now public on both [Jira](https://warthogs.atlassian.net/jira/software/c/projects/DPE/issues/) and [GitHub](https://github.com/canonical/postgresql-operator/issues). - -For a full list of all changes in this revision, see the [GitHub Release](https://github.com/canonical/postgresql-operator/releases/tag/rev430). - -## Technical details -This section contains some technical details about the charm's contents and dependencies. Make sure to also check the [system requirements](/t/11743). - -If you are jumping over several stable revisions, check [previous release notes](/t/11875) before upgrading. - -### Packaging -This charm is based on the [`charmed-postgresql` snap](https://snapcraft.io/charmed-postgresql) (pinned revision 113). It packages: -* postgresql `v.14.11` - * [`14.11-0ubuntu0.22.04.1`](https://launchpad.net/ubuntu/+source/postgresql-14/14.11-0ubuntu0.22.04.1) -* pgbouncer `v.1.21` - * [`1.21.0-0ubuntu0.22.04.1~ppa1`](https://launchpad.net/~data-platform/+archive/ubuntu/pgbouncer) -* patroni `v.3.1.2 ` - * [`3.1.2-0ubuntu0.22.04.1~ppa2`](https://launchpad.net/~data-platform/+archive/ubuntu/patroni) -* pgBackRest `v.2.48` - * [`2.48-0ubuntu0.22.04.1~ppa1`](https://launchpad.net/~data-platform/+archive/ubuntu/pgbackrest) -* prometheus-postgres-exporter `v.0.12.1` - -### Libraries and interfaces -This charm revision imports the following libraries: - -* **grafana_agent `v0`** for integration with Grafana - * Implements `cos_agent` interface -* **rolling_ops `v0`** for rolling operations across units - * Implements `rolling_op` interface -* **tempo_k8s `v1`, `v2`** for integration with Tempo charm - * Implements `tracing` interface -* **tls_certificates_interface `v2`** for integration with TLS charms - * Implements `tls-certificates` interface - -See the [`/lib/charms` directory on GitHub](https://github.com/canonical/postgresql-operator/tree/main/lib/charms) for more details about all supported libraries. - -See the [`metadata.yaml` file on GitHub](https://github.com/canonical/postgresql-operator/blob/main/metadata.yaml) for a full list of supported interfaces - -## Contact us - -Charmed PostgreSQL is an open source project that warmly welcomes community contributions, suggestions, fixes, and constructive feedback. -* Raise software issues or feature requests on [**GitHub**](https://github.com/canonical/postgresql-operator/issues) -* Report security issues through [**Launchpad**](https://wiki.ubuntu.com/DebuggingSecurity#How%20to%20File) -* Contact the Canonical Data Platform team through our [Matrix](https://matrix.to/#/#charmhub-data-platform:ubuntu.com) channel. \ No newline at end of file diff --git a/docs/reference/r-revision-467-468.md b/docs/reference/r-revision-467-468.md deleted file mode 100644 index 5f5e73d693..0000000000 --- a/docs/reference/r-revision-467-468.md +++ /dev/null @@ -1,155 +0,0 @@ ->Reference > Release Notes > [All revisions] > Revision 467/468 - -# Revision 467/468 -September 11, 2024 - -Canonical's newest Charmed PostgreSQL operator has been published in the [14/stable channel]. - -Due to the newly added support for `arm64` architecture, the PostgreSQL charm now releases multiple revisions simultaneously: -* Revision 468 is built for `amd64` on Ubuntu 22.04 LTS -* Revision 467 is built for `arm64` on Ubuntu 22.04 LTS - -To make sure you deploy for the right architecture, we recommend setting an [architecture constraint](https://juju.is/docs/juju/constraint#heading--arch) for your entire juju model. - -Otherwise, it can be done at deploy time with the `--constraints` flag: -```shell -juju deploy postgresql --constraints arch= -``` -where `` can be `amd64` or `arm64`. - ---- - -## Highlights -* Upgraded PostgreSQL from v.14.11 → v.14.12 ([PR #530](https://github.com/canonical/postgresql-operator/pull/530)) - * Check the official [PostgreSQL release notes](https://www.postgresql.org/docs/release/14.12/) -* Added support for Point In Time Recovery ([PR #391](https://github.com/canonical/postgresql-operator/pull/391)) ([DPE-2582](https://warthogs.atlassian.net/browse/DPE-2582)) -* Secure Syncobj and Patroni with passwords ([PR #596](https://github.com/canonical/postgresql-operator/pull/596)) ([DPE-5269](https://warthogs.atlassian.net/browse/DPE-5269)) -* Removed deprecated config option `profile-limit-memory` ([PR #564](https://github.com/canonical/postgresql-operator/pull/564)) ([DPE-4889](https://warthogs.atlassian.net/browse/DPE-4889)) - -## Features - -* Added URI connection string to relations ([PR #527](https://github.com/canonical/postgresql-operator/pull/527)) ([DPE-2278](https://warthogs.atlassian.net/browse/DPE-2278)) -* Improve `list-backups` action output ([PR #522](https://github.com/canonical/postgresql-operator/pull/522)) ([DPE-4479](https://warthogs.atlassian.net/browse/DPE-4479)) -* Show start/end time in UTC time in list-backups output ([PR #551](https://github.com/canonical/postgresql-operator/pull/551)) -* Switched to constant snap locales ([PR #559](https://github.com/canonical/postgresql-operator/pull/559)) ([DPE-4198](https://warthogs.atlassian.net/browse/DPE-4198)) -* Moved URI generation to update endpoints ([PR #584](https://github.com/canonical/postgresql-operator/pull/584)) - -## Bugfixes - -* Wait for exact number of units after scale down ([PR #565](https://github.com/canonical/postgresql-operator/pull/565)) ([DPE-5029](https://warthogs.atlassian.net/browse/DPE-5029)) -* Improved test stability by pausing Patroni in the TLS test ([PR #534](https://github.com/canonical/postgresql-operator/pull/534)) ([DPE-4533](https://warthogs.atlassian.net/browse/DPE-4533)) -* Block charm if it detects objects dependent on disabled plugins ([PR #560](https://github.com/canonical/postgresql-operator/pull/560)) ([DPE-4967](https://warthogs.atlassian.net/browse/DPE-4967)) -* Disabled pgBackRest service initialization ([PR #530](https://github.com/canonical/postgresql-operator/pull/530)) ([DPE-4345](https://warthogs.atlassian.net/browse/DPE-4345)) -* Increased timeout and terminate processes that are still up ([PR #514](https://github.com/canonical/postgresql-operator/pull/514)) ([DPE-4532](https://warthogs.atlassian.net/browse/DPE-4532)) -* Fixed GCP backup test ([PR #521](https://github.com/canonical/postgresql-operator/pull/521)) ([DPE-4820](https://warthogs.atlassian.net/browse/DPE-4820)) -* Handled on start secret exception and remove stale test ([PR #550](https://github.com/canonical/postgresql-operator/pull/550)) -* Removed block on failure to get the db version ([PR #578](https://github.com/canonical/postgresql-operator/pull/578)) ([DPE-3562](https://warthogs.atlassian.net/browse/DPE-3562)) -* Updated unit tests after fixing GCP backup test ([PR #528](https://github.com/canonical/postgresql-operator/pull/528)) ([DPE-4820](https://warthogs.atlassian.net/browse/DPE-4820)) -* Ported some `test_self_healing` CI fixes + update check for invalid extra user credentials ([PR #546](https://github.com/canonical/postgresql-operator/pull/546)) ([DPE-4856](https://warthogs.atlassian.net/browse/DPE-4856)) -* Fixed slow bootstrap of replicas ([PR #510](https://github.com/canonical/postgresql-operator/pull/510)) ([DPE-4759](https://warthogs.atlassian.net/browse/DPE-4759)) -* Fixed conditional password ([PR #604](https://github.com/canonical/postgresql-operator/pull/604)) -* Added enforcement of Juju versions ([PR #518](https://github.com/canonical/postgresql-operator/pull/518)) ([DPE-4809](https://warthogs.atlassian.net/browse/DPE-4809)) -* Fixed a missing case for peer to secrets translation. ([PR #533](https://github.com/canonical/postgresql-operator/pull/533)) -* Updated README.md ([PR #538](https://github.com/canonical/postgresql-operator/pull/538)) ([DPE-4901](https://warthogs.atlassian.net/browse/DPE-4901)) -* Increased test coverage ([PR #505](https://github.com/canonical/postgresql-operator/pull/505)) - -## Known limitations - - * The unit action `resume-upgrade` randomly raises a [harmless error message](https://warthogs.atlassian.net/browse/DPE-5420): `terminated`. - * The [charm sysbench](https://charmhub.io/sysbench) may [crash](https://warthogs.atlassian.net/browse/DPE-5436) during a PostgreSQL charm refresh. - * Make sure that [cluster-cluster replication](/t/13991) is requested for the same charm/workload revisions. An automated check is [planned](https://warthogs.atlassian.net/browse/DPE-5418). - * [Contact us](/t/11863) to schedule [the cluster-cluster replication](/t/13991) upgrade with you. - -If you are jumping over several stable revisions, check [previous release notes][All revisions] before upgrading. - -## Requirements and compatibility - -This charm revision features the following changes in dependencies: -* (increased) The minimum Juju version required to reliably operate **all** features of the release is `v3.4.5` - > You can upgrade to this revision on Juju `v2.9.50+`, but it will not support newer features like cross-regional asynchronous replication, point-in-time recovery, and modern TLS certificate charm integrations. -* (increased) PostgreSQL version 14.12 - -Check the [system requirements] page for more details, such as supported minor versions of Juju and hardware requirements. - -### Integration tests -Below are the charm integrations tested with this revision on different Juju environments and architectures: -* Juju `v.2.9.50` on `amd64` -* Juju `v.3.4.5` on `amd64` and `arm64` - -| Software | Version | Notes | -|-----|-----|-----| -| [lxd] | `5.12/stable` | | -| [nextcloud] | `v29.0.5.1`, `rev 26` | | -| [mailman3-core] | `rev 18` | | -| [data-integrator] | `rev 41` | | -| [s3-integrator] | `rev 31` | | -| [postgresql-test-app] | `rev 237` | | - -See the [`/lib/charms` directory on GitHub] for details about all supported libraries. - -See the [`metadata.yaml` file on GitHub] for a full list of supported interfaces. - -### Packaging - -This charm is based on the Charmed PostgreSQL [snap Revision 120/121]. It packages: -* [postgresql `v.14.12`] -* [pgbouncer `v.1.21`] -* [patroni `v.3.1.2 `] -* [pgBackRest `v.2.48`] -* [prometheus-postgres-exporter `v.0.12.1`] - -### Dependencies and automations - -[details=This section contains a list of updates to libs, dependencies, actions, and workflows.] - -* Added jinja2 as a dependency ([PR #520](https://github.com/canonical/postgresql-operator/pull/520)) ([DPE-4816](https://warthogs.atlassian.net/browse/DPE-4816)) -* Switched Jira issue sync from workflow to bot ([PR #586](https://github.com/canonical/postgresql-operator/pull/586)) -* Updated canonical/charming-actions action to v2.6.2 ([PR #523](https://github.com/canonical/postgresql-operator/pull/523)) -* Updated data-platform-workflows to v21.0.1 ([PR #599](https://github.com/canonical/postgresql-operator/pull/599)) -* Updated dependency cryptography to v43 ([PR #539](https://github.com/canonical/postgresql-operator/pull/539)) -* Updated dependency tenacity to v9 ([PR #558](https://github.com/canonical/postgresql-operator/pull/558)) -* Updated Juju agents (patch) ([PR #553](https://github.com/canonical/postgresql-operator/pull/553)) -* Switch to resusable presets ([PR #513](https://github.com/canonical/postgresql-operator/pull/513)) -* Use poetry package-mode=false ([PR #556](https://github.com/canonical/postgresql-operator/pull/556)) -* Switched test-app interface ([PR #557](https://github.com/canonical/postgresql-operator/pull/557)) -[/details] - - -[All revisions]: /t/11875 -[system requirements]: /t/11743 - - -[`/lib/charms` directory on GitHub]: https://github.com/canonical/postgresql-operator/tree/rev468/lib/charms -[`metadata.yaml` file on GitHub]: https://github.com/canonical/postgresql-operator/blob/rev468/metadata.yaml - - -[14/stable channel]: https://charmhub.io/postgresql?channel=14/stable - - -[`charmed-postgresql` packaging]: https://github.com/canonical/charmed-postgresql-snap -[snap Revision 120/121]: https://github.com/canonical/charmed-postgresql-snap/releases/tag/rev121 -[rock image]: ghcr.io/canonical/charmed-postgresql@sha256:7ef86a352c94e2a664f621a1cc683d7a983fd86e923d98c32b863f717cb1c173 - -[postgresql `v.14.12`]: https://launchpad.net/ubuntu/+source/postgresql-14/14.12-0ubuntu0.22.04.1 -[pgbouncer `v.1.21`]: https://launchpad.net/~data-platform/+archive/ubuntu/pgbouncer -[patroni `v.3.1.2 `]: https://launchpad.net/~data-platform/+archive/ubuntu/patroni -[pgBackRest `v.2.48`]: https://launchpad.net/~data-platform/+archive/ubuntu/pgbackrest -[prometheus-postgres-exporter `v.0.12.1`]: https://launchpad.net/~data-platform/+archive/ubuntu/postgres-exporter - - -[juju]: https://juju.is/docs/juju/ -[lxd]: https://documentation.ubuntu.com/lxd/en/latest/ -[nextcloud]: https://charmhub.io/nextcloud -[mailman3-core]: https://charmhub.io/mailman3-core -[data-integrator]: https://charmhub.io/data-integrator -[s3-integrator]: https://charmhub.io/s3-integrator -[postgresql-test-app]: https://charmhub.io/postgresql-test-app -[discourse-k8s]: https://charmhub.io/discourse-k8s -[indico]: https://charmhub.io/indico -[microk8s]: https://charmhub.io/microk8s -[tls-certificates-operator]: https://charmhub.io/tls-certificates-operator -[self-signed-certificates]: https://charmhub.io/self-signed-certificates - - -[amd64]: https://img.shields.io/badge/amd64-darkgreen -[arm64]: https://img.shields.io/badge/arm64-blue \ No newline at end of file diff --git a/docs/reference/r-revision-544-545.md b/docs/reference/r-revision-544-545.md deleted file mode 100644 index 39732165fc..0000000000 --- a/docs/reference/r-revision-544-545.md +++ /dev/null @@ -1,162 +0,0 @@ ->Reference > Release Notes > [All revisions] > Revision 544/545 - -[note type=caution] -This page is a work in progress for a **future release**. Please revisit at a later date! -[/note] - -# Revision 544/545 - - -Canonical's newest Charmed PostgreSQL operator has been published in the [14/stable channel]. - -Due to the newly added support for `arm64` architecture, the PostgreSQL charm now releases multiple revisions simultaneously: -* Revision is built for `amd64` on Ubuntu 22.04 LTS -* Revision is built for `arm64` on Ubuntu 22.04 LTS - -> See also: [How to perform a minor upgrade] - -### Contents -* [Highlights](#highlights) -* [Features and improvements](#features-and-improvements) -* [Bugfixes and maintenance](#bugfixes-and-maintenance) -* [Known limitations](#known-limitations) -* [Requirements and compatibility](#requirements-and-compatibility) - * [Integration tests](#integration-tests) - * [Packaging](#packaging) ---- - -## Highlights - -* Upgraded PostgreSQL from v.14.12 → v.14.15 ([PR #730](https://github.com/canonical/postgresql-operator/pull/730)) - * Check the official [PostgreSQL 14.13 release notes](https://www.postgresql.org/docs/release/14.13/) - * Check the official [PostgreSQL 14.14 release notes](https://www.postgresql.org/docs/release/14.14/) - * Check the official [PostgreSQL 14.15 release notes](https://www.postgresql.org/docs/release/14.15/) -* Added timeline management to point-in-time recovery (PITR) ([PR #629](https://github.com/canonical/postgresql-operator/pull/629)) ([DPE-5561](https://warthogs.atlassian.net/browse/DPE-5561)) -* Added pgAudit plugin/extension ([PR #612](https://github.com/canonical/postgresql-operator/pull/612)) ([DPE-5248](https://warthogs.atlassian.net/browse/DPE-5248)) -* Observability stack (COS) improvements - * Polished built-in Grafana dashboard ([PR #646](https://github.com/canonical/postgresql-operator/pull/646)) - * Improved COS alert rule descriptions ([PR #651](https://github.com/canonical/postgresql-operator/pull/651)) ([DPE-5658](https://warthogs.atlassian.net/browse/DPE-5658)) -* Added fully-featured terraform module ([PR #643](https://github.com/canonical/postgresql-operator/pull/643)) -* Several S3 stability improvements ([PR #642](https://github.com/canonical/postgresql-operator/pull/642)) - -## Features and improvements -* Removed patching of private ops class. ([PR #617](https://github.com/canonical/postgresql-operator/pull/617)) -* Switched charm libs from `tempo_k8s` to `tempo_coordinator_k8s` and relay tracing traffic through `grafana-agent` ([PR #640](https://github.com/canonical/postgresql-operator/pull/640)) -* Implemented more meaningful group naming for multi-group tests ([PR #625](https://github.com/canonical/postgresql-operator/pull/625)) -* Ignoring alias error in case alias is already existing ([PR #637](https://github.com/canonical/postgresql-operator/pull/637)) -* Stopped tracking channel for held snaps ([PR #638](https://github.com/canonical/postgresql-operator/pull/638)) -* Added pgBackRest logrotate configuration ([PR #645](https://github.com/canonical/postgresql-operator/pull/645)) ([DPE-5601](https://warthogs.atlassian.net/browse/DPE-5601)) -* Grant priviledges to non-public schemas ([PR #647](https://github.com/canonical/postgresql-operator/pull/647)) ([DPE-5387](https://warthogs.atlassian.net/browse/DPE-5387)) -* Added `tls` and `tls-ca` fields to databag ([PR #666](https://github.com/canonical/postgresql-operator/pull/666)) -* Merged `update_tls_flag` into `update_endpoints` ([PR #669](https://github.com/canonical/postgresql-operator/pull/669)) -* Made tox commands resilient to white-space paths ([PR #678](https://github.com/canonical/postgresql-operator/pull/678)) ([DPE-6042](https://warthogs.atlassian.net/browse/DPE-6042)) -* Added microceph (local backup) integration test + bump snap version ([PR #633](https://github.com/canonical/postgresql-operator/pull/633)) ([DPE-5386](https://warthogs.atlassian.net/browse/DPE-5386)) -* Add `max_locks_per_transaction` config option in ([PR#718](https://github.com/canonical/postgresql-operator/pull/718)) ([DPE-5533](https://warthogs.atlassian.net/browse/DPE-5533)) -* Split PITR backup test in AWS and GCP ([PR #605](https://github.com/canonical/postgresql-operator/pull/605)) ([DPE-5181](https://warthogs.atlassian.net/browse/DPE-5181)) - - -## Bugfixes and maintenance -* Juju secrets resetting fix on Juju 3.6 in ([PR#726](https://github.com/canonical/postgresql-operator/pull/726)) ([DPE-6320](https://warthogs.atlassian.net/browse/DPE-6320)) ([DPE-6325](https://warthogs.atlassian.net/browse/DPE-6325)) -* Fallback to trying to create bucket without LocationConstraint in ([PR#690](https://github.com/canonical/postgresql-operator/pull/690)) -* Added warning logs to Patroni reinitialisation ([PR #660](https://github.com/canonical/postgresql-operator/pull/660)) -* Fixed some `postgresql.conf` parameters for hardening ([PR #621](https://github.com/canonical/postgresql-operator/pull/621)) ([DPE-5512](https://warthogs.atlassian.net/browse/DPE-5512)) -* Fixed lib check ([PR #627](https://github.com/canonical/postgresql-operator/pull/627)) -* Allow `--restore-to-time=latest` without a `backup-id` in ([PR#683](https://github.com/canonical/postgresql-operator/pull/683)) -* Clean-up duplicated Patroni config reloads in ([PR#682](https://github.com/canonical/postgresql-operator/pull/682)) -* Filter out degraded read only endpoints in ([PR#679](https://github.com/canonical/postgresql-operator/pull/679)) ([DPE-5714](https://warthogs.atlassian.net/browse/DPE-5714)) - -[details=Libraries, testing, and CI] -* Data Interafces v40 ([PR #615](https://github.com/canonical/postgresql-operator/pull/615)) ([DPE-5306](https://warthogs.atlassian.net/browse/DPE-5306)) -* Bump libs and remove TestCase ([PR #622](https://github.com/canonical/postgresql-operator/pull/622)) -* Run tests against juju 3.6 on a nightly schedule ([PR #601](https://github.com/canonical/postgresql-operator/pull/601)) ([DPE-4977](https://warthogs.atlassian.net/browse/DPE-4977)) -* Test against juju 3.6/candidate + upgrade dpw to v23.0.5 ([PR #675](https://github.com/canonical/postgresql-operator/pull/675)) -* Lock file maintenance Python dependencies ([PR #644](https://github.com/canonical/postgresql-operator/pull/644)) -* Migrate config .github/renovate.json5 ([PR #673](https://github.com/canonical/postgresql-operator/pull/673)) -* Switch from tox build wrapper to charmcraft.yaml overrides ([PR #626](https://github.com/canonical/postgresql-operator/pull/626)) -* Update canonical/charming-actions action to v2.6.3 ([PR #608](https://github.com/canonical/postgresql-operator/pull/608)) -* Update codecov/codecov-action action to v5 ([PR #674](https://github.com/canonical/postgresql-operator/pull/674)) -* Update data-platform-workflows to v23.0.5 ([PR #676](https://github.com/canonical/postgresql-operator/pull/676)) -* Update dependency cryptography to v43.0.1 [SECURITY] ([PR #614](https://github.com/canonical/postgresql-operator/pull/614)) -* Update dependency ubuntu to v24 ([PR #631](https://github.com/canonical/postgresql-operator/pull/631)) -* Update Juju agents ([PR #634](https://github.com/canonical/postgresql-operator/pull/634)) -* Bump libs ([PR #677](https://github.com/canonical/postgresql-operator/pull/677)) -* Increase linting rules ([PR #649](https://github.com/canonical/postgresql-operator/pull/649)) ([DPE-5324](https://warthogs.atlassian.net/browse/DPE-5324)) -[/details] - -## Requirements and compatibility -* (no change) Minimum Juju 2 version: `v.2.9.49` -* (no change) Minimum Juju 3 version: `v.3.4.3` -* (recommended) Juju LTS 3.6.1+ - -See the [system requirements] for more details about Juju versions and other software and hardware prerequisites. - -### Integration tests -Below are some of the charm integrations tested with this revision on different Juju environments and architectures: -* Juju `v.2.9.51` on `amd64` -* Juju `v.3.4.6` on `amd64` and `arm64` - -| Software | Revision | Tested on | | -|-----|-----|----|---| -| [postgresql-test-app] | `rev 281` | ![juju-2_amd64] ![juju-3_amd64] | -| | `rev 279` | ![juju-2_amd64] ![juju-3_amd64] | -| | `rev 280` | ![juju-3_arm64] | -| | `rev 278` | ![juju-3_arm64] | -| [data-integrator] | `rev 41` | ![juju-2_amd64] ![juju-3_amd64] | -| | `rev 40` | ![juju-3_arm64] | -| [nextcloud] | `rev 26` | ![juju-2_amd64] ![juju-3_amd64] | | -| [s3-integrator] | `rev 77` | ![juju-2_amd64] ![juju-3_amd64] | -| | `rev 78` | ![juju-3_arm64] | -| [tls-certificates-operator] | `rev 22` | ![juju-2_amd64] | -| [self-signed-certificates] | `rev 155` | ![juju-3_amd64] | -| | `rev 205` | ![juju-3_arm64] | -| [mailman3-core] | `rev 18` | ![juju-2_amd64] ![juju-3_amd64] ![juju-3_arm64] | -| [landscape-client] | `rev 70` | ![juju-2_amd64] ![juju-3_amd64] ![juju-3_arm64] | -| [ubuntu-advantage] | `rev 137` | ![juju-2_amd64] ![juju-3_amd64] | -| | `rev 139` | ![juju-3_arm64]| - -See the [`/lib/charms` directory on GitHub] for details about all supported libraries. - -See the [`metadata.yaml` file on GitHub] for a full list of supported interfaces. - -### Packaging -This charm is based on the Charmed PostgreSQL [snap revision 132/133](https://github.com/canonical/charmed-postgresql-snap/tree/rev121). It packages: -* [postgresql] `v.14.12` -* [pgbouncer] `v.1.21` -* [patroni] `v.3.1.2 ` -* [pgBackRest] `v.2.53` -* [prometheus-postgres-exporter] `v.0.12.1` - - -[14/stable channel]: https://charmhub.io/postgresql?channel=14/stable - -[All revisions]: /t/11875 -[system requirements]: /t/11743 -[How to perform a minor upgrade]: /t/12089 - -[juju]: https://juju.is/docs/juju/ -[lxd]: https://documentation.ubuntu.com/lxd/en/latest/ -[nextcloud]: https://charmhub.io/nextcloud -[mailman3-core]: https://charmhub.io/mailman3-core -[data-integrator]: https://charmhub.io/data-integrator -[s3-integrator]: https://charmhub.io/s3-integrator -[postgresql-test-app]: https://charmhub.io/postgresql-test-app -[discourse-k8s]: https://charmhub.io/discourse-k8s -[indico]: https://charmhub.io/indico -[microk8s]: https://charmhub.io/microk8s -[tls-certificates-operator]: https://charmhub.io/tls-certificates-operator -[self-signed-certificates]: https://charmhub.io/self-signed-certificates -[landscape-client]: https://charmhub.io/landscape-client -[ubuntu-advantage]: https://charmhub.io/ubuntu-advantage - -[`/lib/charms` directory on GitHub]: https://github.com/canonical/postgresql-operator/tree/rev518/lib/charms -[`metadata.yaml` file on GitHub]: https://github.com/canonical/postgresql-operator/blob/rev518/metadata.yaml - -[postgresql]: https://launchpad.net/ubuntu/+source/postgresql-14/ -[pgbouncer]: https://launchpad.net/~data-platform/+archive/ubuntu/pgbouncer -[patroni]: https://launchpad.net/~data-platform/+archive/ubuntu/patroni -[pgBackRest]: https://launchpad.net/~data-platform/+archive/ubuntu/pgbackrest -[prometheus-postgres-exporter]: https://launchpad.net/~data-platform/+archive/ubuntu/postgres-exporter - -[juju-2_amd64]: https://img.shields.io/badge/Juju_2.9.51-amd64-darkgreen?labelColor=ea7d56 -[juju-3_amd64]: https://img.shields.io/badge/Juju_3.4.6-amd64-darkgreen?labelColor=E95420 -[juju-3_arm64]: https://img.shields.io/badge/Juju_3.4.6-arm64-blue?labelColor=E95420 \ No newline at end of file diff --git a/docs/reference/r-system-requirements.md b/docs/reference/r-system-requirements.md index 0c659fee12..5d4c4a16b2 100644 --- a/docs/reference/r-system-requirements.md +++ b/docs/reference/r-system-requirements.md @@ -11,7 +11,7 @@ The charm supports several Juju releases from [2.9 LTS](https://juju.is/docs/juj | Juju major release | Supported minor versions | Compatible charm revisions |Comment | |:--------|:-----|:-----|:-----| -| ![3.6 LTS] | `3.6.0-beta2` | [363]+ | No known issues, but still in beta. Not recommended for production. | +| ![3.6 LTS] | `3.6.1+` | [552]+ | `3.6.0` is not recommended, while `3.6.1+` works excellent. Recommended for production! | | [![3.5]](https://juju.is/docs/juju/roadmap#juju-juju-35) | `3.5.1+` | [363]+ | [Known Juju issue](https://bugs.launchpad.net/juju/+bug/2066517) in `3.5.0` | | [![3.4]](https://juju.is/docs/juju/roadmap#juju-juju-34) | `3.4.3+` | [363]+ | Know Juju issues with previous minor versions | | [![3.3]](https://juju.is/docs/juju/roadmap#juju-juju-33) | `3.3.0+` | from [363] to [430] | No known issues | @@ -52,6 +52,7 @@ The charm is based on the [charmed-postgresql snap](https://snapcraft.io/charmed [3.6 LTS]: https://img.shields.io/badge/3.6_LTS-%23E95420?label=Juju +[552]: /t/16007 [288]: /t/11876 [336]: /t/11877 [363]: /t/13124 diff --git a/docs/tutorial/t-overview.md b/docs/tutorial.md similarity index 98% rename from docs/tutorial/t-overview.md rename to docs/tutorial.md index d8a7a11511..f532210bb6 100644 --- a/docs/tutorial/t-overview.md +++ b/docs/tutorial.md @@ -1,4 +1,4 @@ -# Charmed PostgreSQL Tutorial +# Tutorial This section of our documentation contains comprehensive, hands-on tutorials to help you learn how to deploy Charmed PostgreSQL on machines and become familiar with its available operations. diff --git a/lib/charms/data_platform_libs/v0/data_interfaces.py b/lib/charms/data_platform_libs/v0/data_interfaces.py index 3bc2dd8503..9717119030 100644 --- a/lib/charms/data_platform_libs/v0/data_interfaces.py +++ b/lib/charms/data_platform_libs/v0/data_interfaces.py @@ -331,7 +331,7 @@ def _on_topic_requested(self, event: TopicRequestedEvent): # Increment this PATCH version before using `charmcraft publish-lib` or reset # to 0 if you are raising the major API version -LIBPATCH = 40 +LIBPATCH = 41 PYDEPS = ["ops>=2.0.0"] @@ -609,7 +609,7 @@ def get_group(self, group: str) -> Optional[SecretGroup]: class CachedSecret: """Locally cache a secret. - The data structure is precisely re-using/simulating as in the actual Secret Storage + The data structure is precisely reusing/simulating as in the actual Secret Storage """ KNOWN_MODEL_ERRORS = [MODEL_ERRORS["no_label_and_uri"], MODEL_ERRORS["owner_no_refresh"]] @@ -2363,7 +2363,6 @@ def _update_relation_data(self, relation: Relation, data: Dict[str, str]) -> Non def _delete_relation_data(self, relation: Relation, fields: List[str]) -> None: """Delete data available (directily or indirectly -- i.e. secrets) from the relation for owner/this_app.""" if self.secret_fields and self.deleted_label: - _, normal_fields = self._process_secret_fields( relation, self.secret_fields, diff --git a/lib/charms/data_platform_libs/v0/data_models.py b/lib/charms/data_platform_libs/v0/data_models.py index a1dbb8299a..087f6f3c58 100644 --- a/lib/charms/data_platform_libs/v0/data_models.py +++ b/lib/charms/data_platform_libs/v0/data_models.py @@ -168,7 +168,7 @@ class MergedDataBag(ProviderDataBag, RequirerDataBag): # Increment this PATCH version before using `charmcraft publish-lib` or reset # to 0 if you are raising the major API version -LIBPATCH = 4 +LIBPATCH = 5 PYDEPS = ["ops>=2.0.0", "pydantic>=1.10,<2"] @@ -209,7 +209,7 @@ def validate_params(cls: Type[T]): """ def decorator( - f: Callable[[CharmBase, ActionEvent, Union[T, ValidationError]], G] + f: Callable[[CharmBase, ActionEvent, Union[T, ValidationError]], G], ) -> Callable[[CharmBase, ActionEvent], G]: @wraps(f) def event_wrapper(self: CharmBase, event: ActionEvent): @@ -287,7 +287,7 @@ def decorator( Optional[Union[UnitModel, ValidationError]], ], G, - ] + ], ) -> Callable[[CharmBase, RelationEvent], G]: @wraps(f) def event_wrapper(self: CharmBase, event: RelationEvent): diff --git a/lib/charms/grafana_agent/v0/cos_agent.py b/lib/charms/grafana_agent/v0/cos_agent.py index 1ea79a625b..b18c271342 100644 --- a/lib/charms/grafana_agent/v0/cos_agent.py +++ b/lib/charms/grafana_agent/v0/cos_agent.py @@ -8,6 +8,8 @@ - `COSAgentProvider`: Use in machine charms that need to have a workload's metrics or logs scraped, or forward rule files or dashboards to Prometheus, Loki or Grafana through the Grafana Agent machine charm. + NOTE: Be sure to add `limit: 1` in your charm for the cos-agent relation. That is the only + way we currently have to prevent two different grafana agent apps deployed on the same VM. - `COSAgentConsumer`: Used in the Grafana Agent machine charm to manage the requirer side of the `cos_agent` interface. @@ -232,8 +234,8 @@ def __init__(self, *args): ) import pydantic -from cosl import GrafanaDashboard, JujuTopology -from cosl.rules import AlertRules +from cosl import DashboardPath40UID, JujuTopology, LZMABase64 +from cosl.rules import AlertRules, generic_alert_groups from ops.charm import RelationChangedEvent from ops.framework import EventBase, EventSource, Object, ObjectEvents from ops.model import ModelError, Relation @@ -252,9 +254,9 @@ class _MetricsEndpointDict(TypedDict): LIBID = "dc15fa84cef84ce58155fb84f6c6213a" LIBAPI = 0 -LIBPATCH = 12 +LIBPATCH = 20 -PYDEPS = ["cosl", "pydantic"] +PYDEPS = ["cosl >= 0.0.50", "pydantic"] DEFAULT_RELATION_NAME = "cos-agent" DEFAULT_PEER_RELATION_NAME = "peers" @@ -266,7 +268,6 @@ class _MetricsEndpointDict(TypedDict): logger = logging.getLogger(__name__) SnapEndpoint = namedtuple("SnapEndpoint", "owner, name") - # Note: MutableMapping is imported from the typing module and not collections.abc # because subscripting collections.abc.MutableMapping was added in python 3.9, but # most of our charms are based on 20.04, which has python 3.8. @@ -316,7 +317,11 @@ class NotReadyError(TracingError): """Raised by the provider wrapper if a requirer hasn't published the required data (yet).""" -class ProtocolNotRequestedError(TracingError): +class ProtocolNotFoundError(TracingError): + """Raised if the user doesn't receive an endpoint for a protocol it requested.""" + + +class ProtocolNotRequestedError(ProtocolNotFoundError): """Raised if the user attempts to obtain an endpoint for a protocol it did not request.""" @@ -475,7 +480,7 @@ class CosAgentProviderUnitData(DatabagModel): # this needs to make its way to the gagent leader metrics_alert_rules: dict log_alert_rules: dict - dashboards: List[GrafanaDashboard] + dashboards: List[str] # subordinate is no longer used but we should keep it until we bump the library to ensure # we don't break compatibility. subordinate: Optional[bool] = None @@ -508,7 +513,7 @@ class CosAgentPeersUnitData(DatabagModel): # of the outgoing o11y relations. metrics_alert_rules: Optional[dict] log_alert_rules: Optional[dict] - dashboards: Optional[List[GrafanaDashboard]] + dashboards: Optional[List[str]] # when this whole datastructure is dumped into a databag, it will be nested under this key. # while not strictly necessary (we could have it 'flattened out' into the databag), @@ -578,7 +583,7 @@ class Receiver(pydantic.BaseModel): """Specification of an active receiver.""" protocol: ProtocolType = pydantic.Field(..., description="Receiver protocol name and type.") - url: str = pydantic.Field( + url: Optional[str] = pydantic.Field( ..., description="""URL at which the receiver is reachable. If there's an ingress, it would be the external URL. Otherwise, it would be the service's fqdn or internal IP. @@ -726,6 +731,10 @@ def _metrics_alert_rules(self) -> Dict: query_type="promql", topology=JujuTopology.from_charm(self._charm) ) alert_rules.add_path(self._metrics_rules, recursive=self._recursive) + alert_rules.add( + generic_alert_groups.application_rules, + group_name_prefix=JujuTopology.from_charm(self._charm).identifier, + ) return alert_rules.as_dict() @property @@ -736,12 +745,27 @@ def _log_alert_rules(self) -> Dict: return alert_rules.as_dict() @property - def _dashboards(self) -> List[GrafanaDashboard]: - dashboards: List[GrafanaDashboard] = [] + def _dashboards(self) -> List[str]: + dashboards: List[str] = [] for d in self._dashboard_dirs: for path in Path(d).glob("*"): - dashboard = GrafanaDashboard._serialize(path.read_bytes()) - dashboards.append(dashboard) + with open(path, "rt") as fp: + dashboard = json.load(fp) + rel_path = str( + path.relative_to(self._charm.charm_dir) if path.is_absolute() else path + ) + # COSAgentProvider is somewhat analogous to GrafanaDashboardProvider. We need to overwrite the uid here + # because there is currently no other way to communicate the dashboard path separately. + # https://github.com/canonical/grafana-k8s-operator/pull/363 + dashboard["uid"] = DashboardPath40UID.generate(self._charm.meta.name, rel_path) + + # Add tags + tags: List[str] = dashboard.get("tags", []) + if not any(tag.startswith("charm: ") for tag in tags): + tags.append(f"charm: {self._charm.meta.name}") + dashboard["tags"] = tags + + dashboards.append(LZMABase64.compress(json.dumps(dashboard))) return dashboards @property @@ -767,7 +791,7 @@ def is_ready(self, relation: Optional[Relation] = None): """Is this endpoint ready?""" relation = relation or self._relation if not relation: - logger.debug(f"no relation on {self._relation_name !r}: tracing not ready") + logger.debug(f"no relation on {self._relation_name!r}: tracing not ready") return False if relation.data is None: logger.error(f"relation data is None for {relation}") @@ -801,29 +825,48 @@ def get_all_endpoints( def _get_tracing_endpoint( self, relation: Optional[Relation], protocol: ReceiverProtocol - ) -> Optional[str]: + ) -> str: + """Return a tracing endpoint URL if it is available or raise a ProtocolNotFoundError.""" unit_data = self.get_all_endpoints(relation) if not unit_data: - return None + # we didn't find the protocol because the remote end didn't publish any data yet + # it might also mean that grafana-agent doesn't have a relation to the tracing backend + raise ProtocolNotFoundError(protocol) receivers: List[Receiver] = [i for i in unit_data.receivers if i.protocol.name == protocol] if not receivers: - logger.error(f"no receiver found with protocol={protocol!r}") - return None + # we didn't find the protocol because grafana-agent didn't return us the protocol that we requested + # the caller might want to verify that we did indeed request this protocol + raise ProtocolNotFoundError(protocol) if len(receivers) > 1: - logger.error( + logger.warning( f"too many receivers with protocol={protocol!r}; using first one. Found: {receivers}" ) - return None receiver = receivers[0] + if not receiver.url: + # grafana-agent isn't connected to the tracing backend yet + raise ProtocolNotFoundError(protocol) return receiver.url def get_tracing_endpoint( self, protocol: ReceiverProtocol, relation: Optional[Relation] = None - ) -> Optional[str]: - """Receiver endpoint for the given protocol.""" - endpoint = self._get_tracing_endpoint(relation or self._relation, protocol=protocol) - if not endpoint: + ) -> str: + """Receiver endpoint for the given protocol. + + It could happen that this function gets called before the provider publishes the endpoints. + In such a scenario, if a non-leader unit calls this function, a permission denied exception will be raised due to + restricted access. To prevent this, this function needs to be guarded by the `is_ready` check. + + Raises: + ProtocolNotRequestedError: + If the charm unit is the leader unit and attempts to obtain an endpoint for a protocol it did not request. + ProtocolNotFoundError: + If the charm attempts to obtain an endpoint when grafana-agent isn't related to a tracing backend. + """ + try: + return self._get_tracing_endpoint(relation or self._relation, protocol=protocol) + except ProtocolNotFoundError: + # let's see if we didn't find it because we didn't request the endpoint requested_protocols = set() relations = [relation] if relation else self.relations for relation in relations: @@ -838,8 +881,7 @@ def get_tracing_endpoint( if protocol not in requested_protocols: raise ProtocolNotRequestedError(protocol, relation) - return None - return endpoint + raise class COSAgentDataChanged(EventBase): @@ -901,6 +943,8 @@ def __init__( events.relation_joined, self._on_relation_data_changed ) # TODO: do we need this? self.framework.observe(events.relation_changed, self._on_relation_data_changed) + self.framework.observe(events.relation_departed, self._on_relation_departed) + for event in self._refresh_events: self.framework.observe(event, self.trigger_refresh) # pyright: ignore @@ -928,6 +972,26 @@ def _on_peer_relation_changed(self, _): if self._charm.unit.is_leader(): self.on.data_changed.emit() # pyright: ignore + def _on_relation_departed(self, event): + """Remove provider's (principal's) alert rules and dashboards from peer data when the cos-agent relation to the principal is removed.""" + if not self.peer_relation: + event.defer() + return + # empty the departing unit's alert rules and dashboards from peer data + data = CosAgentPeersUnitData( + unit_name=event.unit.name, + relation_id=str(event.relation.id), + relation_name=event.relation.name, + metrics_alert_rules={}, + log_alert_rules={}, + dashboards=[], + ) + self.peer_relation.data[self._charm.unit][ + f"{CosAgentPeersUnitData.KEY}-{event.unit.name}" + ] = data.json() + + self.on.data_changed.emit() # pyright: ignore + def _on_relation_data_changed(self, event: RelationChangedEvent): # Peer data is the only means of communication between subordinate units. if not self.peer_relation: @@ -987,7 +1051,16 @@ def update_tracing_receivers(self): CosAgentRequirerUnitData( receivers=[ Receiver( - url=f"{self._get_tracing_receiver_url(protocol)}", + # if tracing isn't ready, we don't want the wrong receiver URLs present in the databag. + # however, because of the backwards compatibility requirements, we need to still provide + # the protocols list so that the charm with older cos_agent version doesn't error its hooks. + # before this change was added, the charm with old cos_agent version threw exceptions with + # connections to grafana-agent timing out. After the change, the charm will fail validating + # databag contents (as it expects a string in URL) but that won't cause any errors as + # tracing endpoints are the only content in the grafana-agent's side of the databag. + url=f"{self._get_tracing_receiver_url(protocol)}" + if self._charm.tracing.is_ready() # type: ignore + else None, protocol=ProtocolType( name=protocol, type=receiver_protocol_to_transport_protocol[protocol], @@ -1029,8 +1102,7 @@ def _get_requested_protocols(self, relation: Relation): if len(units) > 1: # should never happen raise ValueError( - f"unexpected error: subordinate relation {relation} " - f"should have exactly one unit" + f"unexpected error: subordinate relation {relation} should have exactly one unit" ) unit = next(iter(units), None) @@ -1286,7 +1358,7 @@ def dashboards(self) -> List[Dict[str, str]]: seen_apps.append(app_name) for encoded_dashboard in data.dashboards or (): - content = GrafanaDashboard(encoded_dashboard)._deserialize() + content = json.loads(LZMABase64.decompress(encoded_dashboard)) title = content.get("title", "no_title") @@ -1313,44 +1385,32 @@ def charm_tracing_config( If https endpoint is provided but cert_path is not found on disk: disable charm tracing. If https endpoint is provided and cert_path is None: - ERROR + raise TracingError Else: proceed with charm tracing (with or without tls, as appropriate) Usage: - If you are using charm_tracing >= v1.9: - >>> from lib.charms.tempo_k8s.v1.charm_tracing import trace_charm - >>> from lib.charms.tempo_k8s.v0.cos_agent import charm_tracing_config + >>> from lib.charms.tempo_coordinator_k8s.v0.charm_tracing import trace_charm + >>> from lib.charms.tempo_coordinator_k8s.v0.tracing import charm_tracing_config >>> @trace_charm(tracing_endpoint="my_endpoint", cert_path="cert_path") >>> class MyCharm(...): >>> _cert_path = "/path/to/cert/on/charm/container.crt" >>> def __init__(self, ...): - >>> self.cos_agent = COSAgentProvider(...) + >>> self.tracing = TracingEndpointRequirer(...) >>> self.my_endpoint, self.cert_path = charm_tracing_config( - ... self.cos_agent, self._cert_path) - - If you are using charm_tracing < v1.9: - >>> from lib.charms.tempo_k8s.v1.charm_tracing import trace_charm - >>> from lib.charms.tempo_k8s.v2.tracing import charm_tracing_config - >>> @trace_charm(tracing_endpoint="my_endpoint", cert_path="cert_path") - >>> class MyCharm(...): - >>> _cert_path = "/path/to/cert/on/charm/container.crt" - >>> def __init__(self, ...): - >>> self.cos_agent = COSAgentProvider(...) - >>> self.my_endpoint, self.cert_path = charm_tracing_config( - ... self.cos_agent, self._cert_path) - >>> @property - >>> def my_endpoint(self): - >>> return self._my_endpoint - >>> @property - >>> def cert_path(self): - >>> return self._cert_path - + ... self.tracing, self._cert_path) """ if not endpoint_requirer.is_ready(): return None, None - endpoint = endpoint_requirer.get_tracing_endpoint("otlp_http") + try: + endpoint = endpoint_requirer.get_tracing_endpoint("otlp_http") + except ProtocolNotFoundError: + logger.warn( + "Endpoint for tracing wasn't provided as tracing backend isn't ready yet. If grafana-agent isn't connected to a tracing backend, integrate it. Otherwise this issue should resolve itself in a few events." + ) + return None, None + if not endpoint: return None, None diff --git a/lib/charms/operator_libs_linux/v2/snap.py b/lib/charms/operator_libs_linux/v2/snap.py index d14f864fd9..5cd0ffd4b2 100644 --- a/lib/charms/operator_libs_linux/v2/snap.py +++ b/lib/charms/operator_libs_linux/v2/snap.py @@ -56,6 +56,8 @@ ``` """ +from __future__ import annotations + import http.client import json import logging @@ -65,14 +67,30 @@ import subprocess import sys import time +import typing import urllib.error import urllib.parse import urllib.request -from collections.abc import Mapping from datetime import datetime, timedelta, timezone from enum import Enum from subprocess import CalledProcessError, CompletedProcess -from typing import Any, Dict, Iterable, List, Optional, Union +from typing import ( + Callable, + Iterable, + Literal, + Mapping, + NoReturn, + Sequence, + TypedDict, + TypeVar, +) + +if typing.TYPE_CHECKING: + # avoid typing_extensions import at runtime + from typing_extensions import NotRequired, ParamSpec, Required, TypeAlias, Unpack + + _P = ParamSpec("_P") + _T = TypeVar("_T") logger = logging.getLogger(__name__) @@ -84,15 +102,15 @@ # Increment this PATCH version before using `charmcraft publish-lib` or reset # to 0 if you are raising the major API version -LIBPATCH = 9 +LIBPATCH = 10 # Regex to locate 7-bit C1 ANSI sequences ansi_filter = re.compile(r"\x1B(?:[@-Z\\-_]|\[[0-?]*[ -/]*[@-~])") -def _cache_init(func): - def inner(*args, **kwargs): +def _cache_init(func: Callable[_P, _T]) -> Callable[_P, _T]: + def inner(*args: _P.args, **kwargs: _P.kwargs) -> _T: if _Cache.cache is None: _Cache.cache = SnapCache() return func(*args, **kwargs) @@ -100,8 +118,59 @@ def inner(*args, **kwargs): return inner -# recursive hints seems to error out pytest -JSONType = Union[Dict[str, Any], List[Any], str, int, float] +# this is used for return types, so it (a) uses concrete types and (b) does not contain None +# because setting snap config values to null removes the key so a null value can't be returned +_JSONLeaf: TypeAlias = 'str | int | float | bool' +JSONType: TypeAlias = "dict[str, JSONType] | list[JSONType] | _JSONLeaf" +# we also need a jsonable type for arguments, +# which (a) uses abstract types and (b) may contain None +JSONAble: TypeAlias = "Mapping[str, JSONAble] | Sequence[JSONAble] | _JSONLeaf | None" + + +class _AsyncChangeDict(TypedDict, total=True): + """The subset of the json returned by GET changes that we care about internally.""" + + status: str + data: JSONType + + +class _SnapDict(TypedDict, total=True): + """The subset of the json returned by GET snap/find that we care about internally.""" + + name: str + channel: str + revision: str + confinement: str + apps: NotRequired[list[dict[str, JSONType]] | None] + + +class SnapServiceDict(TypedDict, total=True): + """Dictionary representation returned by SnapService.as_dict.""" + + daemon: str | None + daemon_scope: str | None + enabled: bool + active: bool + activators: list[str] + + +# TypedDicts with hyphenated keys +_SnapServiceKwargsDict = TypedDict("_SnapServiceKwargsDict", {"daemon-scope": str}, total=False) +# the kwargs accepted by SnapService +_SnapServiceAppDict = TypedDict( + # the data we expect a Snap._apps entry to contain for a daemon + "_SnapServiceAppDict", + { + "name": "Required[str]", + "daemon": str, + "daemon_scope": str, + "daemon-scope": str, + "enabled": bool, + "active": bool, + "activators": "list[str]", + }, + total=False, +) class SnapService: @@ -109,20 +178,20 @@ class SnapService: def __init__( self, - daemon: Optional[str] = None, - daemon_scope: Optional[str] = None, + daemon: str | None = None, + daemon_scope: str | None = None, enabled: bool = False, active: bool = False, - activators: List[str] = [], - **kwargs, + activators: list[str] | None = None, + **kwargs: Unpack[_SnapServiceKwargsDict], ): self.daemon = daemon - self.daemon_scope = kwargs.get("daemon-scope", None) or daemon_scope + self.daemon_scope = kwargs.get("daemon-scope") or daemon_scope self.enabled = enabled self.active = active - self.activators = activators + self.activators = activators if activators is not None else [] - def as_dict(self) -> Dict: + def as_dict(self) -> SnapServiceDict: """Return instance representation as dict.""" return { "daemon": self.daemon, @@ -137,57 +206,54 @@ class MetaCache(type): """MetaCache class used for initialising the snap cache.""" @property - def cache(cls) -> "SnapCache": + def cache(cls) -> SnapCache: """Property for returning the snap cache.""" return cls._cache @cache.setter - def cache(cls, cache: "SnapCache") -> None: + def cache(cls, cache: SnapCache) -> None: """Setter for the snap cache.""" cls._cache = cache - def __getitem__(cls, name) -> "Snap": + def __getitem__(cls, name: str) -> Snap: """Snap cache getter.""" return cls._cache[name] -class _Cache(object, metaclass=MetaCache): +class _Cache(metaclass=MetaCache): _cache = None class Error(Exception): """Base class of most errors raised by this library.""" - def __repr__(self): + def __init__(self, message: str = "", *args: object): + super().__init__(message, *args) + self.message = message + + def __repr__(self) -> str: """Represent the Error class.""" - return "<{}.{} {}>".format(type(self).__module__, type(self).__name__, self.args) + return f"<{type(self).__module__}.{type(self).__name__} {self.args}>" @property - def name(self): + def name(self) -> str: """Return a string representation of the model plus class.""" - return "<{}.{}>".format(type(self).__module__, type(self).__name__) - - @property - def message(self): - """Return the message passed as an argument.""" - return self.args[0] + return f"<{type(self).__module__}.{type(self).__name__}>" class SnapAPIError(Error): """Raised when an HTTP API error occurs talking to the Snapd server.""" - def __init__(self, body: Dict, code: int, status: str, message: str): + def __init__(self, body: Mapping[str, JSONAble], code: int, status: str, message: str): super().__init__(message) # Makes str(e) return message self.body = body self.code = code self.status = status self._message = message - def __repr__(self): + def __repr__(self) -> str: """Represent the SnapAPIError class.""" - return "APIError({!r}, {!r}, {!r}, {!r})".format( - self.body, self.code, self.status, self._message - ) + return f"APIError({self.body!r}, {self.code!r}, {self.status!r}, {self._message!r})" class SnapState(Enum): @@ -207,7 +273,7 @@ class SnapNotFoundError(Error): """Raised when a requested snap is not known to the system.""" -class Snap(object): +class Snap: """Represents a snap package and its properties. `Snap` exposes the following properties about a snap: @@ -220,49 +286,47 @@ class Snap(object): def __init__( self, - name, + name: str, state: SnapState, channel: str, revision: str, confinement: str, - apps: Optional[List[Dict[str, str]]] = None, - cohort: Optional[str] = "", + apps: list[dict[str, JSONType]] | None = None, + cohort: str | None = None, ) -> None: self._name = name self._state = state self._channel = channel self._revision = revision self._confinement = confinement - self._cohort = cohort + self._cohort = cohort or "" self._apps = apps or [] self._snap_client = SnapClient() - def __eq__(self, other) -> bool: + def __eq__(self, other: object) -> bool: """Equality for comparison.""" return isinstance(other, self.__class__) and ( self._name, self._revision, ) == (other._name, other._revision) - def __hash__(self): + def __hash__(self) -> int: """Calculate a hash for this snap.""" return hash((self._name, self._revision)) - def __repr__(self): + def __repr__(self) -> str: """Represent the object such that it can be reconstructed.""" - return "<{}.{}: {}>".format(self.__module__, self.__class__.__name__, self.__dict__) + return f"<{self.__module__}.{type(self).__name__}: {self.__dict__}>" - def __str__(self): + def __str__(self) -> str: """Represent the snap object as a string.""" - return "<{}: {}-{}.{} -- {}>".format( - self.__class__.__name__, - self._name, - self._revision, - self._channel, - str(self._state), + return ( + f"<{type(self).__name__}: " + f"{self._name}-{self._revision}.{self._channel}" + f" -- {self._state}>" ) - def _snap(self, command: str, optargs: Optional[Iterable[str]] = None) -> str: + def _snap(self, command: str, optargs: Iterable[str] | None = None) -> str: """Perform a snap operation. Args: @@ -276,19 +340,17 @@ def _snap(self, command: str, optargs: Optional[Iterable[str]] = None) -> str: optargs = optargs or [] args = ["snap", command, self._name, *optargs] try: - return subprocess.check_output(args, universal_newlines=True) + return subprocess.check_output(args, text=True) except CalledProcessError as e: raise SnapError( - "Snap: {!r}; command {!r} failed with output = {!r}".format( - self._name, args, e.output - ) - ) + f"Snap: {self._name!r}; command {args!r} failed with output = {e.output!r}" + ) from e def _snap_daemons( self, - command: List[str], - services: Optional[List[str]] = None, - ) -> CompletedProcess: + command: list[str], + services: list[str] | None = None, + ) -> CompletedProcess[str]: """Perform snap app commands. Args: @@ -300,18 +362,26 @@ def _snap_daemons( """ if services: # an attempt to keep the command constrained to the snap instance's services - services = ["{}.{}".format(self._name, service) for service in services] + services = [f"{self._name}.{service}" for service in services] else: services = [self._name] args = ["snap", *command, *services] try: - return subprocess.run(args, universal_newlines=True, check=True, capture_output=True) + return subprocess.run(args, text=True, check=True, capture_output=True) except CalledProcessError as e: - raise SnapError("Could not {} for snap [{}]: {}".format(args, self._name, e.stderr)) - - def get(self, key: Optional[str], *, typed: bool = False) -> Any: + raise SnapError(f"Could not {args} for snap [{self._name}]: {e.stderr}") from e + + @typing.overload + def get(self, key: None | Literal[""], *, typed: Literal[False] = False) -> NoReturn: ... + @typing.overload + def get(self, key: str, *, typed: Literal[False] = False) -> str: ... + @typing.overload + def get(self, key: None | Literal[""], *, typed: Literal[True]) -> dict[str, JSONType]: ... + @typing.overload + def get(self, key: str, *, typed: Literal[True]) -> JSONType: ... + def get(self, key: str | None, *, typed: bool = False) -> JSONType | str: """Fetch snap configuration values. Args: @@ -323,7 +393,7 @@ def get(self, key: Optional[str], *, typed: bool = False) -> Any: args = ["-d"] if key: args.append(key) - config = json.loads(self._snap("get", args)) + config = json.loads(self._snap("get", args)) # json.loads -> Any if key: return config.get(key) return config @@ -331,9 +401,10 @@ def get(self, key: Optional[str], *, typed: bool = False) -> Any: if not key: raise TypeError("Key must be provided when typed=False") + # return a string return self._snap("get", [key]).strip() - def set(self, config: Dict[str, Any], *, typed: bool = False) -> None: + def set(self, config: dict[str, JSONAble], *, typed: bool = False) -> None: """Set a snap configuration value. Args: @@ -345,7 +416,7 @@ def set(self, config: Dict[str, Any], *, typed: bool = False) -> None: config = {k: str(v) for k, v in config.items()} self._snap_client._put_snap_conf(self._name, config) - def unset(self, key) -> str: + def unset(self, key: str) -> str: """Unset a snap configuration value. Args: @@ -353,7 +424,7 @@ def unset(self, key) -> str: """ return self._snap("unset", [key]) - def start(self, services: Optional[List[str]] = None, enable: Optional[bool] = False) -> None: + def start(self, services: list[str] | None = None, enable: bool = False) -> None: """Start a snap's services. Args: @@ -363,7 +434,7 @@ def start(self, services: Optional[List[str]] = None, enable: Optional[bool] = F args = ["start", "--enable"] if enable else ["start"] self._snap_daemons(args, services) - def stop(self, services: Optional[List[str]] = None, disable: Optional[bool] = False) -> None: + def stop(self, services: list[str] | None = None, disable: bool = False) -> None: """Stop a snap's services. Args: @@ -373,7 +444,7 @@ def stop(self, services: Optional[List[str]] = None, disable: Optional[bool] = F args = ["stop", "--disable"] if disable else ["stop"] self._snap_daemons(args, services) - def logs(self, services: Optional[List[str]] = None, num_lines: Optional[int] = 10) -> str: + def logs(self, services: list[str] | None = None, num_lines: int = 10) -> str: """Fetch a snap services' logs. Args: @@ -381,12 +452,10 @@ def logs(self, services: Optional[List[str]] = None, num_lines: Optional[int] = (otherwise all) num_lines (int): (optional) integer number of log lines to return. Default `10` """ - args = ["logs", "-n={}".format(num_lines)] if num_lines else ["logs"] + args = ["logs", f"-n={num_lines}"] if num_lines else ["logs"] return self._snap_daemons(args, services).stdout - def connect( - self, plug: str, service: Optional[str] = None, slot: Optional[str] = None - ) -> None: + def connect(self, plug: str, service: str | None = None, slot: str | None = None) -> None: """Connect a plug to a slot. Args: @@ -397,20 +466,20 @@ def connect( Raises: SnapError if there is a problem encountered """ - command = ["connect", "{}:{}".format(self._name, plug)] + command = ["connect", f"{self._name}:{plug}"] if service and slot: - command = command + ["{}:{}".format(service, slot)] + command.append(f"{service}:{slot}") elif slot: - command = command + [slot] + command.append(slot) args = ["snap", *command] try: - subprocess.run(args, universal_newlines=True, check=True, capture_output=True) + subprocess.run(args, text=True, check=True, capture_output=True) except CalledProcessError as e: - raise SnapError("Could not {} for snap [{}]: {}".format(args, self._name, e.stderr)) + raise SnapError(f"Could not {args} for snap [{self._name}]: {e.stderr}") from e - def hold(self, duration: Optional[timedelta] = None) -> None: + def hold(self, duration: timedelta | None = None) -> None: """Add a refresh hold to a snap. Args: @@ -426,7 +495,7 @@ def unhold(self) -> None: """Remove the refresh hold of a snap.""" self._snap("refresh", ["--unhold"]) - def alias(self, application: str, alias: Optional[str] = None) -> None: + def alias(self, application: str, alias: str | None = None) -> None: """Create an alias for a given application. Args: @@ -437,17 +506,13 @@ def alias(self, application: str, alias: Optional[str] = None) -> None: alias = application args = ["snap", "alias", f"{self.name}.{application}", alias] try: - subprocess.check_output(args, universal_newlines=True) + subprocess.check_output(args, text=True) except CalledProcessError as e: raise SnapError( - "Snap: {!r}; command {!r} failed with output = {!r}".format( - self._name, args, e.output - ) - ) + f"Snap: {self._name!r}; command {args!r} failed with output = {e.output!r}" + ) from e - def restart( - self, services: Optional[List[str]] = None, reload: Optional[bool] = False - ) -> None: + def restart(self, services: list[str] | None = None, reload: bool = False) -> None: """Restarts a snap's services. Args: @@ -461,9 +526,9 @@ def restart( def _install( self, - channel: Optional[str] = "", - cohort: Optional[str] = "", - revision: Optional[str] = None, + channel: str = "", + cohort: str = "", + revision: str = "", ) -> None: """Add a snap to the system. @@ -474,27 +539,27 @@ def _install( """ cohort = cohort or self._cohort - args = [] + args: list[str] = [] if self.confinement == "classic": args.append("--classic") if self.confinement == "devmode": args.append("--devmode") if channel: - args.append('--channel="{}"'.format(channel)) + args.append(f'--channel="{channel}"') if revision: - args.append('--revision="{}"'.format(revision)) + args.append(f'--revision="{revision}"') if cohort: - args.append('--cohort="{}"'.format(cohort)) + args.append(f'--cohort="{cohort}"') self._snap("install", args) def _refresh( self, - channel: Optional[str] = "", - cohort: Optional[str] = "", - revision: Optional[str] = None, + channel: str = "", + cohort: str = "", + revision: str = "", devmode: bool = False, - leave_cohort: Optional[bool] = False, + leave_cohort: bool = False, ) -> None: """Refresh a snap. @@ -505,12 +570,12 @@ def _refresh( devmode: optionally, specify devmode confinement leave_cohort: leave the current cohort. """ - args = [] + args: list[str] = [] if channel: - args.append('--channel="{}"'.format(channel)) + args.append(f'--channel="{channel}"') if revision: - args.append('--revision="{}"'.format(revision)) + args.append(f'--revision="{revision}"') if devmode: args.append("--devmode") @@ -522,7 +587,7 @@ def _refresh( self._cohort = "" args.append("--leave-cohort") elif cohort: - args.append('--cohort="{}"'.format(cohort)) + args.append(f'--cohort="{cohort}"') self._snap("refresh", args) @@ -538,11 +603,11 @@ def name(self) -> str: def ensure( self, state: SnapState, - classic: Optional[bool] = False, + classic: bool = False, devmode: bool = False, - channel: Optional[str] = "", - cohort: Optional[str] = "", - revision: Optional[str] = None, + channel: str | None = None, + cohort: str | None = None, + revision: str | None = None, ): """Ensure that a snap is in a given state. @@ -560,6 +625,10 @@ def ensure( Raises: SnapError if an error is encountered """ + channel = channel or "" + cohort = cohort or "" + revision = revision or "" + if classic and devmode: raise ValueError("Cannot set both classic and devmode confinement") @@ -605,7 +674,7 @@ def _update_snap_apps(self) -> None: try: self._apps = self._snap_client.get_installed_snap_apps(self._name) except SnapAPIError: - logger.debug("Unable to retrieve snap apps for {}".format(self._name)) + logger.debug("Unable to retrieve snap apps for %s", self._name) self._apps = [] @property @@ -653,18 +722,19 @@ def confinement(self) -> str: return self._confinement @property - def apps(self) -> List: + def apps(self) -> list[dict[str, JSONType]]: """Returns (if any) the installed apps of the snap.""" self._update_snap_apps() return self._apps @property - def services(self) -> Dict: + def services(self) -> dict[str, SnapServiceDict]: """Returns (if any) the installed services of the snap.""" self._update_snap_apps() - services = {} + services: dict[str, SnapServiceDict] = {} for app in self._apps: if "daemon" in app: + app = typing.cast("_SnapServiceAppDict", app) services[app["name"]] = SnapService(**app).as_dict() return services @@ -679,7 +749,7 @@ def held(self) -> bool: class _UnixSocketConnection(http.client.HTTPConnection): """Implementation of HTTPConnection that connects to a named Unix socket.""" - def __init__(self, host, timeout=None, socket_path=None): + def __init__(self, host: str, timeout: float | None = None, socket_path: str | None = None): if timeout is None: super().__init__(host) else: @@ -689,7 +759,8 @@ def __init__(self, host, timeout=None, socket_path=None): def connect(self): """Override connect to use Unix socket (instead of TCP socket).""" if not hasattr(socket, "AF_UNIX"): - raise NotImplementedError("Unix sockets not supported on {}".format(sys.platform)) + raise NotImplementedError(f"Unix sockets not supported on {sys.platform}") + assert self.socket_path is not None # else TypeError on self.socket.connect self.sock = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM) self.sock.connect(self.socket_path) if self.timeout is not None: @@ -703,9 +774,13 @@ def __init__(self, socket_path: str): super().__init__() self.socket_path = socket_path - def http_open(self, req) -> http.client.HTTPResponse: + def http_open(self, req: urllib.request.Request) -> http.client.HTTPResponse: """Override http_open to use a Unix socket connection (instead of TCP).""" - return self.do_open(_UnixSocketConnection, req, socket_path=self.socket_path) + return self.do_open( + typing.cast("urllib.request._HTTPConnectionProtocol", _UnixSocketConnection), + req, + socket_path=self.socket_path, + ) class SnapClient: @@ -719,7 +794,7 @@ class SnapClient: def __init__( self, socket_path: str = "/run/snapd.socket", - opener: Optional[urllib.request.OpenerDirector] = None, + opener: urllib.request.OpenerDirector | None = None, base_url: str = "http://localhost/v2/", timeout: float = 30.0, ): @@ -728,18 +803,21 @@ def __init__( Args: socket_path: a path to the socket on the filesystem. Defaults to /run/snap/snapd.socket opener: specifies an opener for unix socket, if unspecified a default is used - base_url: base url for making requests to the snap client. Defaults to - http://localhost/v2/ + base_url: base URL for making requests to the snap client. Must be an HTTP(S) URL. + Defaults to http://localhost/v2/ timeout: timeout in seconds to use when making requests to the API. Default is 30.0s. """ if opener is None: opener = self._get_default_opener(socket_path) self.opener = opener + # Address ruff's suspicious-url-open-usage (S310) + if not base_url.startswith(("http:", "https:")): + raise ValueError("base_url must start with 'http:' or 'https:'") self.base_url = base_url self.timeout = timeout @classmethod - def _get_default_opener(cls, socket_path): + def _get_default_opener(cls, socket_path: str) -> urllib.request.OpenerDirector: """Build the default opener to use for requests (HTTP over Unix socket).""" opener = urllib.request.OpenerDirector() opener.add_handler(_UnixSocketHandler(socket_path)) @@ -752,9 +830,9 @@ def _request( self, method: str, path: str, - query: Dict = None, - body: Dict = None, - ) -> JSONType: + query: dict[str, str] | None = None, + body: dict[str, JSONAble] | None = None, + ) -> JSONType | None: """Make a JSON request to the Snapd server with the given HTTP method and path. If query dict is provided, it is encoded and appended as a query string @@ -769,12 +847,12 @@ def _request( headers["Content-Type"] = "application/json" response = self._request_raw(method, path, query, headers, data) - response = json.loads(response.read().decode()) + response = json.loads(response.read().decode()) # json.loads -> Any if response["type"] == "async": - return self._wait(response["change"]) + return self._wait(response["change"]) # may be `None` due to `get` return response["result"] - def _wait(self, change_id: str, timeout=300) -> JSONType: + def _wait(self, change_id: str, timeout: float = 300) -> JSONType | None: """Wait for an async change to complete. The poll time is 100 milliseconds, the same as in snap clients. @@ -784,6 +862,7 @@ def _wait(self, change_id: str, timeout=300) -> JSONType: if time.time() > deadline: raise TimeoutError(f"timeout waiting for snap change {change_id}") response = self._request("GET", f"changes/{change_id}") + response = typing.cast("_AsyncChangeDict", response) status = response["status"] if status == "Done": return response.get("data") @@ -801,9 +880,9 @@ def _request_raw( self, method: str, path: str, - query: Dict = None, - headers: Dict = None, - data: bytes = None, + query: dict[str, str] | None = None, + headers: dict[str, str] | None = None, + data: bytes | None = None, ) -> http.client.HTTPResponse: """Make a request to the Snapd server; return the raw HTTPResponse object.""" url = self.base_url + path @@ -812,7 +891,7 @@ def _request_raw( if headers is None: headers = {} - request = urllib.request.Request(url, method=method, data=data, headers=headers) + request = urllib.request.Request(url, method=method, data=data, headers=headers) # noqa: S310 try: response = self.opener.open(request, timeout=self.timeout) @@ -820,35 +899,36 @@ def _request_raw( code = e.code status = e.reason message = "" + body: dict[str, JSONType] try: - body = json.loads(e.read().decode())["result"] - except (IOError, ValueError, KeyError) as e2: + body = json.loads(e.read().decode())["result"] # json.loads -> Any + except (OSError, ValueError, KeyError) as e2: # Will only happen on read error or if Pebble sends invalid JSON. body = {} - message = "{} - {}".format(type(e2).__name__, e2) - raise SnapAPIError(body, code, status, message) + message = f"{type(e2).__name__} - {e2}" + raise SnapAPIError(body, code, status, message) from e except urllib.error.URLError as e: - raise SnapAPIError({}, 500, "Not found", e.reason) + raise SnapAPIError({}, 500, "Not found", str(e.reason)) from e return response - def get_installed_snaps(self) -> Dict: + def get_installed_snaps(self) -> list[dict[str, JSONType]]: """Get information about currently installed snaps.""" - return self._request("GET", "snaps") + return self._request("GET", "snaps") # type: ignore - def get_snap_information(self, name: str) -> Dict: + def get_snap_information(self, name: str) -> dict[str, JSONType]: """Query the snap server for information about single snap.""" - return self._request("GET", "find", {"name": name})[0] + return self._request("GET", "find", {"name": name})[0] # type: ignore - def get_installed_snap_apps(self, name: str) -> List: + def get_installed_snap_apps(self, name: str) -> list[dict[str, JSONType]]: """Query the snap server for apps belonging to a named, currently installed snap.""" - return self._request("GET", "apps", {"names": name, "select": "service"}) + return self._request("GET", "apps", {"names": name, "select": "service"}) # type: ignore - def _put_snap_conf(self, name: str, conf: Dict[str, Any]): + def _put_snap_conf(self, name: str, conf: dict[str, JSONAble]) -> None: """Set the configuration details for an installed snap.""" - return self._request("PUT", f"snaps/{name}/conf", body=conf) + self._request("PUT", f"snaps/{name}/conf", body=conf) -class SnapCache(Mapping): +class SnapCache(Mapping[str, Snap]): """An abstraction to represent installed/available packages. When instantiated, `SnapCache` iterates through the list of installed @@ -861,12 +941,12 @@ def __init__(self): if not self.snapd_installed: raise SnapError("snapd is not installed or not in /usr/bin") from None self._snap_client = SnapClient() - self._snap_map = {} + self._snap_map: dict[str, Snap | None] = {} if self.snapd_installed: self._load_available_snaps() self._load_installed_snaps() - def __contains__(self, key: str) -> bool: + def __contains__(self, key: object) -> bool: """Check if a given snap is in the cache.""" return key in self._snap_map @@ -874,26 +954,26 @@ def __len__(self) -> int: """Report number of items in the snap cache.""" return len(self._snap_map) - def __iter__(self) -> Iterable["Snap"]: + def __iter__(self) -> Iterable[Snap | None]: # pyright: ignore[reportIncompatibleMethodOverride] """Provide iterator for the snap cache.""" return iter(self._snap_map.values()) def __getitem__(self, snap_name: str) -> Snap: """Return either the installed version or latest version for a given snap.""" - snap = self._snap_map.get(snap_name, None) - if snap is None: - # The snapd cache file may not have existed when _snap_map was - # populated. This is normal. - try: - self._snap_map[snap_name] = self._load_info(snap_name) - except SnapAPIError: - raise SnapNotFoundError("Snap '{}' not found!".format(snap_name)) - - return self._snap_map[snap_name] + snap = self._snap_map.get(snap_name) + if snap is not None: + return snap + # The snapd cache file may not have existed when _snap_map was + # populated. This is normal. + try: + snap = self._snap_map[snap_name] = self._load_info(snap_name) + except SnapAPIError as e: + raise SnapNotFoundError(f"Snap '{snap_name}' not found!") from e + return snap @property def snapd_installed(self) -> bool: - """Check whether snapd has been installled on the system.""" + """Check whether snapd has been installed on the system.""" return os.path.isfile("/usr/bin/snap") def _load_available_snaps(self) -> None: @@ -907,7 +987,7 @@ def _load_available_snaps(self) -> None: # currently exist. return - with open("/var/cache/snapd/names", "r") as f: + with open("/var/cache/snapd/names") as f: for line in f: if line.strip(): self._snap_map[line.strip()] = None @@ -917,23 +997,25 @@ def _load_installed_snaps(self) -> None: installed = self._snap_client.get_installed_snaps() for i in installed: + i = typing.cast("_SnapDict", i) snap = Snap( name=i["name"], state=SnapState.Latest, channel=i["channel"], revision=i["revision"], confinement=i["confinement"], - apps=i.get("apps", None), + apps=i.get("apps"), ) self._snap_map[snap.name] = snap - def _load_info(self, name) -> Snap: + def _load_info(self, name: str) -> Snap: """Load info for snaps which are not installed if requested. Args: name: a string representing the name of the snap """ info = self._snap_client.get_snap_information(name) + info = typing.cast("_SnapDict", info) return Snap( name=info["name"], @@ -945,16 +1027,36 @@ def _load_info(self, name) -> Snap: ) +@typing.overload +def add( # return a single Snap if snap name is given as a string + snap_names: str, + state: str | SnapState = SnapState.Latest, + channel: str | None = None, + classic: bool = False, + devmode: bool = False, + cohort: str | None = None, + revision: str | None = None, +) -> Snap: ... +@typing.overload +def add( # may return a single Snap or a list depending if one or more snap names were given + snap_names: list[str], + state: str | SnapState = SnapState.Latest, + channel: str | None = None, + classic: bool = False, + devmode: bool = False, + cohort: str | None = None, + revision: str | None = None, +) -> Snap | list[Snap]: ... @_cache_init def add( - snap_names: Union[str, List[str]], - state: Union[str, SnapState] = SnapState.Latest, - channel: Optional[str] = "", - classic: Optional[bool] = False, + snap_names: str | list[str], + state: str | SnapState = SnapState.Latest, + channel: str | None = None, + classic: bool = False, devmode: bool = False, - cohort: Optional[str] = "", - revision: Optional[str] = None, -) -> Union[Snap, List[Snap]]: + cohort: str | None = None, + revision: str | None = None, +) -> Snap | list[Snap]: """Add a snap to the system. Args: @@ -982,11 +1084,25 @@ def add( if isinstance(state, str): state = SnapState(state) - return _wrap_snap_operations(snap_names, state, channel, classic, devmode, cohort, revision) + return _wrap_snap_operations( + snap_names=snap_names, + state=state, + channel=channel or "", + classic=classic, + devmode=devmode, + cohort=cohort or "", + revision=revision or "", + ) +@typing.overload +def remove(snap_names: str) -> Snap: ... +# return a single Snap if snap name is given as a string +@typing.overload +def remove(snap_names: list[str]) -> Snap | list[Snap]: ... +# may return a single Snap or a list depending if one or more snap names were given @_cache_init -def remove(snap_names: Union[str, List[str]]) -> Union[Snap, List[Snap]]: +def remove(snap_names: str | list[str]) -> Snap | list[Snap]: """Remove specified snap(s) from the system. Args: @@ -1007,16 +1123,36 @@ def remove(snap_names: Union[str, List[str]]) -> Union[Snap, List[Snap]]: ) +@typing.overload +def ensure( # return a single Snap if snap name is given as a string + snap_names: str, + state: str, + channel: str | None = None, + classic: bool = False, + devmode: bool = False, + cohort: str | None = None, + revision: int | None = None, +) -> Snap: ... +@typing.overload +def ensure( # may return a single Snap or a list depending if one or more snap names were given + snap_names: list[str], + state: str, + channel: str | None = None, + classic: bool = False, + devmode: bool = False, + cohort: str | None = None, + revision: int | None = None, +) -> Snap | list[Snap]: ... @_cache_init def ensure( - snap_names: Union[str, List[str]], + snap_names: str | list[str], state: str, - channel: Optional[str] = "", - classic: Optional[bool] = False, + channel: str | None = None, + classic: bool = False, devmode: bool = False, - cohort: Optional[str] = "", - revision: Optional[int] = None, -) -> Union[Snap, List[Snap]]: + cohort: str | None = None, + revision: int | None = None, +) -> Snap | list[Snap]: """Ensure specified snaps are in a given state on the system. Args: @@ -1047,23 +1183,24 @@ def ensure( classic=classic, devmode=devmode, cohort=cohort, - revision=revision, + revision=str(revision) if revision is not None else None, ) else: return remove(snap_names) def _wrap_snap_operations( - snap_names: List[str], + snap_names: list[str], state: SnapState, channel: str, classic: bool, devmode: bool, - cohort: Optional[str] = "", - revision: Optional[str] = None, -) -> Union[Snap, List[Snap]]: + cohort: str = "", + revision: str = "", +) -> Snap | list[Snap]: """Wrap common operations for bare commands.""" - snaps = {"success": [], "failed": []} + snaps: list[Snap] = [] + errors: list[str] = [] op = "remove" if state is SnapState.Absent else "install or refresh" @@ -1081,27 +1218,25 @@ def _wrap_snap_operations( cohort=cohort, revision=revision, ) - snaps["success"].append(snap) - except SnapError as e: - logger.warning("Failed to {} snap {}: {}!".format(op, s, e.message)) - snaps["failed"].append(s) + snaps.append(snap) + except SnapError as e: # noqa: PERF203 + logger.warning("Failed to %s snap %s: %s!", op, s, e.message) + errors.append(s) except SnapNotFoundError: - logger.warning("Snap '{}' not found in cache!".format(s)) - snaps["failed"].append(s) + logger.warning("Snap '%s' not found in cache!", s) + errors.append(s) - if len(snaps["failed"]): - raise SnapError( - "Failed to install or refresh snap(s): {}".format(", ".join(list(snaps["failed"]))) - ) + if errors: + raise SnapError(f"Failed to install or refresh snap(s): {', '.join(errors)}") - return snaps["success"] if len(snaps["success"]) > 1 else snaps["success"][0] + return snaps if len(snaps) > 1 else snaps[0] def install_local( filename: str, - classic: Optional[bool] = False, - devmode: Optional[bool] = False, - dangerous: Optional[bool] = False, + classic: bool = False, + devmode: bool = False, + dangerous: bool = False, ) -> Snap: """Perform a snap operation. @@ -1126,7 +1261,7 @@ def install_local( if dangerous: args.append("--dangerous") try: - result = subprocess.check_output(args, universal_newlines=True).splitlines()[-1] + result = subprocess.check_output(args, text=True).splitlines()[-1] snap_name, _ = result.split(" ", 1) snap_name = ansi_filter.sub("", snap_name) @@ -1136,11 +1271,13 @@ def install_local( return c[snap_name] except SnapAPIError as e: logger.error( - "Could not find snap {} when querying Snapd socket: {}".format(snap_name, e.body) + "Could not find snap %s when querying Snapd socket: %s", + snap_name, + e.body, ) - raise SnapError("Failed to find snap {} in Snap cache".format(snap_name)) + raise SnapError(f"Failed to find snap {snap_name} in Snap cache") from e except CalledProcessError as e: - raise SnapError("Could not install snap {}: {}".format(filename, e.output)) + raise SnapError(f"Could not install snap {filename}: {e.output}") from e def _system_set(config_item: str, value: str) -> None: @@ -1150,14 +1287,14 @@ def _system_set(config_item: str, value: str) -> None: config_item: name of snap system setting. E.g. 'refresh.hold' value: value to assign """ - args = ["snap", "set", "system", "{}={}".format(config_item, value)] + args = ["snap", "set", "system", f"{config_item}={value}"] try: - subprocess.check_call(args, universal_newlines=True) - except CalledProcessError: - raise SnapError("Failed setting system config '{}' to '{}'".format(config_item, value)) + subprocess.check_call(args, text=True) + except CalledProcessError as e: + raise SnapError(f"Failed setting system config '{config_item}' to '{value}'") from e -def hold_refresh(days: int = 90, forever: bool = False) -> bool: +def hold_refresh(days: int = 90, forever: bool = False) -> None: """Set the system-wide snap refresh hold. Args: @@ -1183,7 +1320,7 @@ def hold_refresh(days: int = 90, forever: bool = False) -> bool: # Format for the correct datetime format hold_date = target_date.strftime("%Y-%m-%dT%H:%M:%S%z") # Python dumps the offset in format '+0100', we need '+01:00' - hold_date = "{0}:{1}".format(hold_date[:-2], hold_date[-2:]) + hold_date = f"{hold_date[:-2]}:{hold_date[-2:]}" # Actually set the hold date _system_set("refresh.hold", hold_date) logger.info("Set system-wide snap refresh hold to: %s", hold_date) diff --git a/lib/charms/postgresql_k8s/v0/postgresql.py b/lib/charms/postgresql_k8s/v0/postgresql.py index 2fd8219bca..e3ba9e6c88 100644 --- a/lib/charms/postgresql_k8s/v0/postgresql.py +++ b/lib/charms/postgresql_k8s/v0/postgresql.py @@ -35,7 +35,10 @@ # Increment this PATCH version before using `charmcraft publish-lib` or reset # to 0 if you are raising the major API version -LIBPATCH = 43 +LIBPATCH = 47 + +# Groups to distinguish database permissions +PERMISSIONS_GROUP_ADMIN = "admin" INVALID_EXTRA_USER_ROLE_BLOCKING_MESSAGE = "invalid role(s) for extra user roles" @@ -187,7 +190,7 @@ def create_database( Identifier(database) ) ) - for user_to_grant_access in [user, "admin", *self.system_users]: + for user_to_grant_access in [user, PERMISSIONS_GROUP_ADMIN, *self.system_users]: cursor.execute( SQL("GRANT ALL PRIVILEGES ON DATABASE {} TO {};").format( Identifier(database), Identifier(user_to_grant_access) @@ -220,7 +223,7 @@ def create_user( user: str, password: Optional[str] = None, admin: bool = False, - extra_user_roles: Optional[str] = None, + extra_user_roles: Optional[List[str]] = None, ) -> None: """Creates a database user. @@ -235,16 +238,17 @@ def create_user( admin_role = False roles = privileges = None if extra_user_roles: - extra_user_roles = tuple(extra_user_roles.lower().split(",")) - admin_role = "admin" in extra_user_roles + admin_role = PERMISSIONS_GROUP_ADMIN in extra_user_roles valid_privileges, valid_roles = self.list_valid_privileges_and_roles() roles = [ - role for role in extra_user_roles if role in valid_roles and role != "admin" + role + for role in extra_user_roles + if role in valid_roles and role != PERMISSIONS_GROUP_ADMIN ] privileges = { extra_user_role for extra_user_role in extra_user_roles - if extra_user_role not in roles and extra_user_role != "admin" + if extra_user_role not in roles and extra_user_role != PERMISSIONS_GROUP_ADMIN } invalid_privileges = [ privilege for privilege in privileges if privilege not in valid_privileges @@ -300,9 +304,10 @@ def delete_user(self, user: str) -> None: # Existing objects need to be reassigned in each database # before the user can be deleted. for database in databases: - with self._connect_to_database( - database - ) as connection, connection.cursor() as cursor: + with ( + self._connect_to_database(database) as connection, + connection.cursor() as cursor, + ): cursor.execute( SQL("REASSIGN OWNED BY {} TO {};").format( Identifier(user), Identifier(self.user) @@ -464,9 +469,10 @@ def get_postgresql_text_search_configs(self) -> Set[str]: Returns: Set of PostgreSQL text search configs. """ - with self._connect_to_database( - database_host=self.current_host - ) as connection, connection.cursor() as cursor: + with ( + self._connect_to_database(database_host=self.current_host) as connection, + connection.cursor() as cursor, + ): cursor.execute("SELECT CONCAT('pg_catalog.', cfgname) FROM pg_ts_config;") text_search_configs = cursor.fetchall() return {text_search_config[0] for text_search_config in text_search_configs} @@ -477,9 +483,10 @@ def get_postgresql_timezones(self) -> Set[str]: Returns: Set of PostgreSQL timezones. """ - with self._connect_to_database( - database_host=self.current_host - ) as connection, connection.cursor() as cursor: + with ( + self._connect_to_database(database_host=self.current_host) as connection, + connection.cursor() as cursor, + ): cursor.execute("SELECT name FROM pg_timezone_names;") timezones = cursor.fetchall() return {timezone[0] for timezone in timezones} @@ -492,9 +499,10 @@ def get_postgresql_version(self, current_host=True) -> str: """ host = self.current_host if current_host else None try: - with self._connect_to_database( - database_host=host - ) as connection, connection.cursor() as cursor: + with ( + self._connect_to_database(database_host=host) as connection, + connection.cursor() as cursor, + ): cursor.execute("SELECT version();") # Split to get only the version number. return cursor.fetchone()[0].split(" ")[1] @@ -513,9 +521,12 @@ def is_tls_enabled(self, check_current_host: bool = False) -> bool: whether TLS is enabled. """ try: - with self._connect_to_database( - database_host=self.current_host if check_current_host else None - ) as connection, connection.cursor() as cursor: + with ( + self._connect_to_database( + database_host=self.current_host if check_current_host else None + ) as connection, + connection.cursor() as cursor, + ): cursor.execute("SHOW ssl;") return "on" in cursor.fetchone()[0] except psycopg2.Error: @@ -571,8 +582,8 @@ def set_up_database(self) -> None: ) ) self.create_user( - "admin", - extra_user_roles="pg_read_all_data,pg_write_all_data", + PERMISSIONS_GROUP_ADMIN, + extra_user_roles=["pg_read_all_data", "pg_write_all_data"], ) cursor.execute("GRANT CONNECT ON DATABASE postgres TO admin;") except psycopg2.Error as e: @@ -597,9 +608,10 @@ def update_user_password( """ connection = None try: - with self._connect_to_database( - database_host=database_host - ) as connection, connection.cursor() as cursor: + with ( + self._connect_to_database(database_host=database_host) as connection, + connection.cursor() as cursor, + ): cursor.execute(SQL("BEGIN;")) cursor.execute(SQL("SET LOCAL log_statement = 'none';")) cursor.execute( @@ -696,9 +708,10 @@ def validate_date_style(self, date_style: str) -> bool: Whether the date style is valid. """ try: - with self._connect_to_database( - database_host=self.current_host - ) as connection, connection.cursor() as cursor: + with ( + self._connect_to_database(database_host=self.current_host) as connection, + connection.cursor() as cursor, + ): cursor.execute( SQL( "SET DateStyle to {};", diff --git a/lib/charms/rolling_ops/v0/rollingops.py b/lib/charms/rolling_ops/v0/rollingops.py index 57aa9bf352..13b51a3051 100644 --- a/lib/charms/rolling_ops/v0/rollingops.py +++ b/lib/charms/rolling_ops/v0/rollingops.py @@ -63,13 +63,14 @@ def _on_trigger_restart(self, event): juju run-action some-charm/0 some-charm/1 <... some-charm/n> restart ``` -Note that all units that plan to restart must receive the action and emit the aquire +Note that all units that plan to restart must receive the action and emit the acquire event. Any units that do not run their acquire handler will be left out of the rolling restart. (An operator might take advantage of this fact to recover from a failed rolling operation without restarting workloads that were able to successfully restart -- simply omit the successful units from a subsequent run-action call.) """ + import logging from enum import Enum from typing import AnyStr, Callable, Optional @@ -88,7 +89,7 @@ def _on_trigger_restart(self, event): # Increment this PATCH version before using `charmcraft publish-lib` or reset # to 0 if you are raising the major API version -LIBPATCH = 7 +LIBPATCH = 8 class LockNoRelationError(Exception): @@ -149,7 +150,6 @@ class Lock: """ def __init__(self, manager, unit=None): - self.relation = manager.model.relations[manager.name][0] if not self.relation: # TODO: defer caller in this case (probably just fired too soon). @@ -246,7 +246,7 @@ def __init__(self, manager): # Gather all the units. relation = manager.model.relations[manager.name][0] - units = [unit for unit in relation.units] + units = list(relation.units) # Plus our unit ... units.append(manager.model.unit) diff --git a/lib/charms/tempo_coordinator_k8s/v0/charm_tracing.py b/lib/charms/tempo_coordinator_k8s/v0/charm_tracing.py index cf8def11ac..e2208f756f 100644 --- a/lib/charms/tempo_coordinator_k8s/v0/charm_tracing.py +++ b/lib/charms/tempo_coordinator_k8s/v0/charm_tracing.py @@ -10,24 +10,28 @@ in real time from the Grafana dashboard the execution flow of your charm. # Quickstart -Fetch the following charm libs (and ensure the minimum version/revision numbers are satisfied): +Fetch the following charm libs: - charmcraft fetch-lib charms.tempo_coordinator_k8s.v0.tracing # >= 1.10 - charmcraft fetch-lib charms.tempo_coordinator_k8s.v0.charm_tracing # >= 2.7 + charmcraft fetch-lib charms.tempo_coordinator_k8s.v0.tracing + charmcraft fetch-lib charms.tempo_coordinator_k8s.v0.charm_tracing Then edit your charm code to include: ```python # import the necessary charm libs -from charms.tempo_coordinator_k8s.v0.tracing import TracingEndpointRequirer, charm_tracing_config +from charms.tempo_coordinator_k8s.v0.tracing import ( + TracingEndpointRequirer, + charm_tracing_config, +) from charms.tempo_coordinator_k8s.v0.charm_tracing import charm_tracing + # decorate your charm class with charm_tracing: @charm_tracing( # forward-declare the instance attributes that the instrumentor will look up to obtain the # tempo endpoint and server certificate tracing_endpoint="tracing_endpoint", - server_cert="server_cert" + server_cert="server_cert", ) class MyCharm(CharmBase): _path_to_cert = "/path/to/cert.crt" @@ -37,10 +41,12 @@ class MyCharm(CharmBase): # If you do support TLS, you'll need to make sure that the server cert is copied to this location # and kept up to date so the instrumentor can use it. - def __init__(self, ...): - ... - self.tracing = TracingEndpointRequirer(self, ...) - self.tracing_endpoint, self.server_cert = charm_tracing_config(self.tracing, self._path_to_cert) + def __init__(self, framework): + # ... + self.tracing = TracingEndpointRequirer(self) + self.tracing_endpoint, self.server_cert = charm_tracing_config( + self.tracing, self._path_to_cert + ) ``` # Detailed usage @@ -168,9 +174,10 @@ class MyCharm(CharmBase): ... ``` -## Upgrading from `v0` +## Upgrading from `tempo_k8s.v0` -If you are upgrading from `charm_tracing` v0, you need to take the following steps (assuming you already +If you are upgrading from `tempo_k8s.v0.charm_tracing` (note that since then, the charm library moved to +`tempo_coordinator_k8s.v0.charm_tracing`), you need to take the following steps (assuming you already have the newest version of the library in your charm): 1) If you need the dependency for your tests, add the following dependency to your charm project (or, if your project had a dependency on `opentelemetry-exporter-otlp-proto-grpc` only because @@ -183,7 +190,7 @@ class MyCharm(CharmBase): For example: ``` - from charms.tempo_coordinator_k8s.v0.charm_tracing import trace_charm + from charms.tempo_k8s.v0.charm_tracing import trace_charm @trace_charm( tracing_endpoint="my_tracing_endpoint", @@ -225,12 +232,6 @@ def my_tracing_endpoint(self) -> Optional[str]: 3) If you were passing a certificate (str) using `server_cert`, you need to change it to provide an *absolute* path to the certificate file instead. """ -import typing - -from opentelemetry.exporter.otlp.proto.common._internal.trace_encoder import ( - encode_spans, -) -from opentelemetry.sdk.trace.export.in_memory_span_exporter import InMemorySpanExporter def _remove_stale_otel_sdk_packages(): @@ -285,12 +286,15 @@ def _remove_stale_otel_sdk_packages(): # apply hacky patch to remove stale opentelemetry sdk packages on upgrade-charm. # it could be trouble if someone ever decides to implement their own tracer parallel to # ours and before the charm has inited. We assume they won't. +# !!IMPORTANT!! keep all otlp imports UNDER this call. _remove_stale_otel_sdk_packages() import functools import inspect import logging import os +import typing +from collections import deque from contextlib import contextmanager from contextvars import Context, ContextVar, copy_context from pathlib import Path @@ -309,6 +313,9 @@ def _remove_stale_otel_sdk_packages(): import opentelemetry import ops +from opentelemetry.exporter.otlp.proto.common._internal.trace_encoder import ( + encode_spans, +) from opentelemetry.exporter.otlp.proto.http.trace_exporter import OTLPSpanExporter from opentelemetry.sdk.resources import Resource from opentelemetry.sdk.trace import ReadableSpan, Span, TracerProvider @@ -317,7 +324,11 @@ def _remove_stale_otel_sdk_packages(): SpanExporter, SpanExportResult, ) -from opentelemetry.trace import INVALID_SPAN, Tracer +from opentelemetry.sdk.trace.export.in_memory_span_exporter import InMemorySpanExporter +from opentelemetry.trace import ( + INVALID_SPAN, + Tracer, +) from opentelemetry.trace import get_current_span as otlp_get_current_span from opentelemetry.trace import ( get_tracer, @@ -337,7 +348,7 @@ def _remove_stale_otel_sdk_packages(): # Increment this PATCH version before using `charmcraft publish-lib` or reset # to 0 if you are raising the major API version -LIBPATCH = 4 +LIBPATCH = 7 PYDEPS = ["opentelemetry-exporter-otlp-proto-http==1.21.0"] @@ -365,7 +376,9 @@ def _remove_stale_otel_sdk_packages(): BUFFER_DEFAULT_MAX_EVENT_HISTORY_LENGTH = 100 _MiB_TO_B = 2**20 # megabyte to byte conversion rate _OTLP_SPAN_EXPORTER_TIMEOUT = 1 -"""Timeout in seconds that the OTLP span exporter has to push traces to the backend.""" + + +# Timeout in seconds that the OTLP span exporter has to push traces to the backend. class _Buffer: @@ -397,45 +410,75 @@ def save(self, spans: typing.Sequence[ReadableSpan]): if self._max_event_history_length < 1: dev_logger.debug("buffer disabled: max history length < 1") return - - current_history_length = len(self.load()) - new_history_length = current_history_length + len(spans) - if (diff := self._max_event_history_length - new_history_length) < 0: - self.drop(diff) self._save(spans) def _serialize(self, spans: Sequence[ReadableSpan]) -> bytes: # encode because otherwise we can't json-dump them return encode_spans(spans).SerializeToString() + def _prune(self, queue: Sequence[bytes]) -> Sequence[bytes]: + """Prune the queue until it fits in our constraints.""" + n_dropped_spans = 0 + # drop older events if we are past the max history length + overflow = len(queue) - self._max_event_history_length + if overflow > 0: + n_dropped_spans += overflow + logger.warning( + f"charm tracing buffer exceeds max history length ({self._max_event_history_length} events)" + ) + + new_spans = deque(queue[-self._max_event_history_length :]) + + # drop older events if the buffer is too big; all units are bytes + logged_drop = False + target_size = self._max_buffer_size_mib * _MiB_TO_B + current_size = sum(len(span) for span in new_spans) + while current_size > target_size: + current_size -= len(new_spans.popleft()) + n_dropped_spans += 1 + + # only do this once + if not logged_drop: + logger.warning( + f"charm tracing buffer exceeds size limit ({self._max_buffer_size_mib}MiB)." + ) + logged_drop = True + + if n_dropped_spans > 0: + dev_logger.debug( + f"charm tracing buffer overflow: dropped {n_dropped_spans} older spans. " + f"Please increase the buffer limits, or ensure the spans can be flushed." + ) + return new_spans + def _save(self, spans: Sequence[ReadableSpan], replace: bool = False): dev_logger.debug(f"saving {len(spans)} new spans to buffer") old = [] if replace else self.load() - new = self._serialize(spans) + queue = old + [self._serialize(spans)] + new_buffer = self._prune(queue) - try: - # if the buffer exceeds the size limit, we start dropping old spans until it does - - while len((new + self._SPANSEP.join(old))) > (self._max_buffer_size_mib * _MiB_TO_B): - if not old: - # if we've already dropped all spans and still we can't get under the - # size limit, we can't save this span - logger.error( - f"span exceeds total buffer size limit ({self._max_buffer_size_mib}MiB); " - f"buffering FAILED" - ) - return - - old = old[1:] - logger.warning( - f"buffer size exceeds {self._max_buffer_size_mib}MiB; dropping older spans... " - f"Please increase the buffer size, disable buffering, or ensure the spans can be flushed." - ) + if queue and not new_buffer: + # this means that, given our constraints, we are pruning so much that there are no events left. + logger.error( + "No charm events could be buffered into charm traces buffer. Please increase the memory or history size limits." + ) + return - self._db_file.write_bytes(new + self._SPANSEP.join(old)) + try: + self._write(new_buffer) except Exception: logger.exception("error buffering spans") + def _write(self, spans: Sequence[bytes]): + """Write the spans to the db file.""" + # ensure the destination folder exists + db_file_dir = self._db_file.parent + if not db_file_dir.exists(): + dev_logger.info(f"creating buffer dir: {db_file_dir}") + db_file_dir.mkdir(parents=True) + + self._db_file.write_bytes(self._SPANSEP.join(spans)) + def load(self) -> List[bytes]: """Load currently buffered spans from the cache file. @@ -460,8 +503,10 @@ def drop(self, n_spans: Optional[int] = None): else: dev_logger.debug("emptying buffer") new = [] - - self._db_file.write_bytes(self._SPANSEP.join(new)) + try: + self._write(new) + except Exception: + logger.exception("error writing charm traces buffer") def flush(self) -> Optional[bool]: """Export all buffered spans to the given exporter, then clear the buffer. diff --git a/metadata.yaml b/metadata.yaml index 94cb47ec89..f44eb9e1f1 100644 --- a/metadata.yaml +++ b/metadata.yaml @@ -66,9 +66,6 @@ storage: assumes: - juju - any-of: - - all-of: - - juju >= 2.9.49 - - juju < 3 - all-of: - juju >= 3.4.3 - juju < 3.5 diff --git a/poetry.lock b/poetry.lock index c3e2e6d8ba..c776be907a 100644 --- a/poetry.lock +++ b/poetry.lock @@ -17,26 +17,21 @@ allure-python-commons = "2.13.5" pytest = ">=4.5.0" [[package]] -name = "allure-pytest-collection-report" -version = "0.1.0" -description = "" +name = "allure-pytest-default-results" +version = "0.1.2" +description = "Generate default \"unknown\" results to show in Allure Report if test case does not run" optional = false python-versions = ">=3.8" groups = ["integration"] -files = [] -develop = false +files = [ + {file = "allure_pytest_default_results-0.1.2-py3-none-any.whl", hash = "sha256:8dc6c5a5d548661c38111a2890509e794204586fa81cefbe61315fb63996e50c"}, + {file = "allure_pytest_default_results-0.1.2.tar.gz", hash = "sha256:eb6c16aa1c2ede69e653a0ee38094791685eaacb0ac6b2cae5c6da1379dbdbfd"}, +] [package.dependencies] allure-pytest = ">=2.13.5" pytest = "*" -[package.source] -type = "git" -url = "https://github.com/canonical/data-platform-workflows" -reference = "v29.1.0" -resolved_reference = "cf3e292107a8d420c452e35cf7552c225add7fbd" -subdirectory = "python/pytest_plugins/allure_pytest_collection_report" - [[package]] name = "allure-python-commons" version = "2.13.5" @@ -178,18 +173,18 @@ typecheck = ["mypy"] [[package]] name = "boto3" -version = "1.35.87" +version = "1.35.99" description = "The AWS SDK for Python" optional = false python-versions = ">=3.8" groups = ["main", "integration"] files = [ - {file = "boto3-1.35.87-py3-none-any.whl", hash = "sha256:588ab05e2771c50fca5c242be14e7a25200ffd3dd95c45950ce40993473864c7"}, - {file = "boto3-1.35.87.tar.gz", hash = "sha256:341c58602889078a4a25dc4331b832b5b600a33acd73471d2532c6f01b16fbb4"}, + {file = "boto3-1.35.99-py3-none-any.whl", hash = "sha256:83e560faaec38a956dfb3d62e05e1703ee50432b45b788c09e25107c5058bd71"}, + {file = "boto3-1.35.99.tar.gz", hash = "sha256:e0abd794a7a591d90558e92e29a9f8837d25ece8e3c120e530526fe27eba5fca"}, ] [package.dependencies] -botocore = ">=1.35.87,<1.36.0" +botocore = ">=1.35.99,<1.36.0" jmespath = ">=0.7.1,<2.0.0" s3transfer = ">=0.10.0,<0.11.0" @@ -230,14 +225,14 @@ files = [ [[package]] name = "certifi" -version = "2024.12.14" +version = "2025.1.31" description = "Python package for providing Mozilla's CA Bundle." optional = false python-versions = ">=3.6" groups = ["main", "charm-libs", "integration"] files = [ - {file = "certifi-2024.12.14-py3-none-any.whl", hash = "sha256:1275f7a45be9464efc1173084eaa30f866fe2e47d389406136d332ed4967ec56"}, - {file = "certifi-2024.12.14.tar.gz", hash = "sha256:b650d30f370c2b724812bee08008be0c4163b163ddaec3f2546c1caf65f191db"}, + {file = "certifi-2025.1.31-py3-none-any.whl", hash = "sha256:ca78db4565a652026a4db2bcdf68f2fb589ea80d0be70e03929ed730746b84fe"}, + {file = "certifi-2025.1.31.tar.gz", hash = "sha256:3d5da6925056f6f18f119200434a4780a94263f10d1c21d032a6f6b2baa20651"}, ] [[package]] @@ -425,14 +420,14 @@ files = [ [[package]] name = "codespell" -version = "2.4.0" +version = "2.4.1" description = "Fix common misspellings in text files" optional = false python-versions = ">=3.8" groups = ["lint"] files = [ - {file = "codespell-2.4.0-py3-none-any.whl", hash = "sha256:b4c5b779f747dd481587aeecb5773301183f52b94b96ed51a28126d0482eec1d"}, - {file = "codespell-2.4.0.tar.gz", hash = "sha256:587d45b14707fb8ce51339ba4cce50ae0e98ce228ef61f3c5e160e34f681be58"}, + {file = "codespell-2.4.1-py3-none-any.whl", hash = "sha256:3dadafa67df7e4a3dbf51e0d7315061b80d265f9552ebd699b3dd6834b47e425"}, + {file = "codespell-2.4.1.tar.gz", hash = "sha256:299fcdcb09d23e81e35a671bbe746d5ad7e8385972e65dbb833a2eaac33c01e5"}, ] [package.extras] @@ -456,14 +451,14 @@ files = [ [[package]] name = "cosl" -version = "0.0.51" +version = "0.0.55" description = "Utils for COS Lite charms" optional = false python-versions = ">=3.8" groups = ["charm-libs"] files = [ - {file = "cosl-0.0.51-py3-none-any.whl", hash = "sha256:2ef43a94f0ca130fb4f2af924b75329f3c5e74b5c40ad4036af16713ad7d47d4"}, - {file = "cosl-0.0.51.tar.gz", hash = "sha256:32af380475bba32df7334d53ff16fb93466a169c7433e79a9fef8dbbecfdd43c"}, + {file = "cosl-0.0.55-py3-none-any.whl", hash = "sha256:bf641d611f982c8f494f3cf72ac4181b24e30c69504cfbd55aa8f54964797f90"}, + {file = "cosl-0.0.55.tar.gz", hash = "sha256:d3b8ee6f78302ac111d3a15d36c42a38c298a806161d762869513d348d778316"}, ] [package.dependencies] @@ -476,74 +471,75 @@ typing-extensions = "*" [[package]] name = "coverage" -version = "7.6.10" +version = "7.6.12" description = "Code coverage measurement for Python" optional = false python-versions = ">=3.9" groups = ["unit"] files = [ - {file = "coverage-7.6.10-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:5c912978f7fbf47ef99cec50c4401340436d200d41d714c7a4766f377c5b7b78"}, - {file = "coverage-7.6.10-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:a01ec4af7dfeb96ff0078ad9a48810bb0cc8abcb0115180c6013a6b26237626c"}, - {file = "coverage-7.6.10-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a3b204c11e2b2d883946fe1d97f89403aa1811df28ce0447439178cc7463448a"}, - {file = "coverage-7.6.10-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:32ee6d8491fcfc82652a37109f69dee9a830e9379166cb73c16d8dc5c2915165"}, - {file = "coverage-7.6.10-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:675cefc4c06e3b4c876b85bfb7c59c5e2218167bbd4da5075cbe3b5790a28988"}, - {file = "coverage-7.6.10-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:f4f620668dbc6f5e909a0946a877310fb3d57aea8198bde792aae369ee1c23b5"}, - {file = "coverage-7.6.10-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:4eea95ef275de7abaef630c9b2c002ffbc01918b726a39f5a4353916ec72d2f3"}, - {file = "coverage-7.6.10-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:e2f0280519e42b0a17550072861e0bc8a80a0870de260f9796157d3fca2733c5"}, - {file = "coverage-7.6.10-cp310-cp310-win32.whl", hash = "sha256:bc67deb76bc3717f22e765ab3e07ee9c7a5e26b9019ca19a3b063d9f4b874244"}, - {file = "coverage-7.6.10-cp310-cp310-win_amd64.whl", hash = "sha256:0f460286cb94036455e703c66988851d970fdfd8acc2a1122ab7f4f904e4029e"}, - {file = "coverage-7.6.10-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:ea3c8f04b3e4af80e17bab607c386a830ffc2fb88a5484e1df756478cf70d1d3"}, - {file = "coverage-7.6.10-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:507a20fc863cae1d5720797761b42d2d87a04b3e5aeb682ef3b7332e90598f43"}, - {file = "coverage-7.6.10-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d37a84878285b903c0fe21ac8794c6dab58150e9359f1aaebbeddd6412d53132"}, - {file = "coverage-7.6.10-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a534738b47b0de1995f85f582d983d94031dffb48ab86c95bdf88dc62212142f"}, - {file = "coverage-7.6.10-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0d7a2bf79378d8fb8afaa994f91bfd8215134f8631d27eba3e0e2c13546ce994"}, - {file = "coverage-7.6.10-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:6713ba4b4ebc330f3def51df1d5d38fad60b66720948112f114968feb52d3f99"}, - {file = "coverage-7.6.10-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:ab32947f481f7e8c763fa2c92fd9f44eeb143e7610c4ca9ecd6a36adab4081bd"}, - {file = "coverage-7.6.10-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:7bbd8c8f1b115b892e34ba66a097b915d3871db7ce0e6b9901f462ff3a975377"}, - {file = "coverage-7.6.10-cp311-cp311-win32.whl", hash = "sha256:299e91b274c5c9cdb64cbdf1b3e4a8fe538a7a86acdd08fae52301b28ba297f8"}, - {file = "coverage-7.6.10-cp311-cp311-win_amd64.whl", hash = "sha256:489a01f94aa581dbd961f306e37d75d4ba16104bbfa2b0edb21d29b73be83609"}, - {file = "coverage-7.6.10-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:27c6e64726b307782fa5cbe531e7647aee385a29b2107cd87ba7c0105a5d3853"}, - {file = "coverage-7.6.10-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:c56e097019e72c373bae32d946ecf9858fda841e48d82df7e81c63ac25554078"}, - {file = "coverage-7.6.10-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c7827a5bc7bdb197b9e066cdf650b2887597ad124dd99777332776f7b7c7d0d0"}, - {file = "coverage-7.6.10-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:204a8238afe787323a8b47d8be4df89772d5c1e4651b9ffa808552bdf20e1d50"}, - {file = "coverage-7.6.10-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e67926f51821b8e9deb6426ff3164870976fe414d033ad90ea75e7ed0c2e5022"}, - {file = "coverage-7.6.10-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:e78b270eadb5702938c3dbe9367f878249b5ef9a2fcc5360ac7bff694310d17b"}, - {file = "coverage-7.6.10-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:714f942b9c15c3a7a5fe6876ce30af831c2ad4ce902410b7466b662358c852c0"}, - {file = "coverage-7.6.10-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:abb02e2f5a3187b2ac4cd46b8ced85a0858230b577ccb2c62c81482ca7d18852"}, - {file = "coverage-7.6.10-cp312-cp312-win32.whl", hash = "sha256:55b201b97286cf61f5e76063f9e2a1d8d2972fc2fcfd2c1272530172fd28c359"}, - {file = "coverage-7.6.10-cp312-cp312-win_amd64.whl", hash = "sha256:e4ae5ac5e0d1e4edfc9b4b57b4cbecd5bc266a6915c500f358817a8496739247"}, - {file = "coverage-7.6.10-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:05fca8ba6a87aabdd2d30d0b6c838b50510b56cdcfc604d40760dae7153b73d9"}, - {file = "coverage-7.6.10-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:9e80eba8801c386f72e0712a0453431259c45c3249f0009aff537a517b52942b"}, - {file = "coverage-7.6.10-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a372c89c939d57abe09e08c0578c1d212e7a678135d53aa16eec4430adc5e690"}, - {file = "coverage-7.6.10-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ec22b5e7fe7a0fa8509181c4aac1db48f3dd4d3a566131b313d1efc102892c18"}, - {file = "coverage-7.6.10-cp313-cp313-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:26bcf5c4df41cad1b19c84af71c22cbc9ea9a547fc973f1f2cc9a290002c8b3c"}, - {file = "coverage-7.6.10-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:4e4630c26b6084c9b3cb53b15bd488f30ceb50b73c35c5ad7871b869cb7365fd"}, - {file = "coverage-7.6.10-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:2396e8116db77789f819d2bc8a7e200232b7a282c66e0ae2d2cd84581a89757e"}, - {file = "coverage-7.6.10-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:79109c70cc0882e4d2d002fe69a24aa504dec0cc17169b3c7f41a1d341a73694"}, - {file = "coverage-7.6.10-cp313-cp313-win32.whl", hash = "sha256:9e1747bab246d6ff2c4f28b4d186b205adced9f7bd9dc362051cc37c4a0c7bd6"}, - {file = "coverage-7.6.10-cp313-cp313-win_amd64.whl", hash = "sha256:254f1a3b1eef5f7ed23ef265eaa89c65c8c5b6b257327c149db1ca9d4a35f25e"}, - {file = "coverage-7.6.10-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:2ccf240eb719789cedbb9fd1338055de2761088202a9a0b73032857e53f612fe"}, - {file = "coverage-7.6.10-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:0c807ca74d5a5e64427c8805de15b9ca140bba13572d6d74e262f46f50b13273"}, - {file = "coverage-7.6.10-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2bcfa46d7709b5a7ffe089075799b902020b62e7ee56ebaed2f4bdac04c508d8"}, - {file = "coverage-7.6.10-cp313-cp313t-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4e0de1e902669dccbf80b0415fb6b43d27edca2fbd48c74da378923b05316098"}, - {file = "coverage-7.6.10-cp313-cp313t-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3f7b444c42bbc533aaae6b5a2166fd1a797cdb5eb58ee51a92bee1eb94a1e1cb"}, - {file = "coverage-7.6.10-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:b330368cb99ef72fcd2dc3ed260adf67b31499584dc8a20225e85bfe6f6cfed0"}, - {file = "coverage-7.6.10-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:9a7cfb50515f87f7ed30bc882f68812fd98bc2852957df69f3003d22a2aa0abf"}, - {file = "coverage-7.6.10-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:6f93531882a5f68c28090f901b1d135de61b56331bba82028489bc51bdd818d2"}, - {file = "coverage-7.6.10-cp313-cp313t-win32.whl", hash = "sha256:89d76815a26197c858f53c7f6a656686ec392b25991f9e409bcef020cd532312"}, - {file = "coverage-7.6.10-cp313-cp313t-win_amd64.whl", hash = "sha256:54a5f0f43950a36312155dae55c505a76cd7f2b12d26abeebbe7a0b36dbc868d"}, - {file = "coverage-7.6.10-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:656c82b8a0ead8bba147de9a89bda95064874c91a3ed43a00e687f23cc19d53a"}, - {file = "coverage-7.6.10-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:ccc2b70a7ed475c68ceb548bf69cec1e27305c1c2606a5eb7c3afff56a1b3b27"}, - {file = "coverage-7.6.10-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a5e37dc41d57ceba70956fa2fc5b63c26dba863c946ace9705f8eca99daecdc4"}, - {file = "coverage-7.6.10-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0aa9692b4fdd83a4647eeb7db46410ea1322b5ed94cd1715ef09d1d5922ba87f"}, - {file = "coverage-7.6.10-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:aa744da1820678b475e4ba3dfd994c321c5b13381d1041fe9c608620e6676e25"}, - {file = "coverage-7.6.10-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:c0b1818063dc9e9d838c09e3a473c1422f517889436dd980f5d721899e66f315"}, - {file = "coverage-7.6.10-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:59af35558ba08b758aec4d56182b222976330ef8d2feacbb93964f576a7e7a90"}, - {file = "coverage-7.6.10-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:7ed2f37cfce1ce101e6dffdfd1c99e729dd2ffc291d02d3e2d0af8b53d13840d"}, - {file = "coverage-7.6.10-cp39-cp39-win32.whl", hash = "sha256:4bcc276261505d82f0ad426870c3b12cb177752834a633e737ec5ee79bbdff18"}, - {file = "coverage-7.6.10-cp39-cp39-win_amd64.whl", hash = "sha256:457574f4599d2b00f7f637a0700a6422243b3565509457b2dbd3f50703e11f59"}, - {file = "coverage-7.6.10-pp39.pp310-none-any.whl", hash = "sha256:fd34e7b3405f0cc7ab03d54a334c17a9e802897580d964bd8c2001f4b9fd488f"}, - {file = "coverage-7.6.10.tar.gz", hash = "sha256:7fb105327c8f8f0682e29843e2ff96af9dcbe5bab8eeb4b398c6a33a16d80a23"}, + {file = "coverage-7.6.12-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:704c8c8c6ce6569286ae9622e534b4f5b9759b6f2cd643f1c1a61f666d534fe8"}, + {file = "coverage-7.6.12-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:ad7525bf0241e5502168ae9c643a2f6c219fa0a283001cee4cf23a9b7da75879"}, + {file = "coverage-7.6.12-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:06097c7abfa611c91edb9e6920264e5be1d6ceb374efb4986f38b09eed4cb2fe"}, + {file = "coverage-7.6.12-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:220fa6c0ad7d9caef57f2c8771918324563ef0d8272c94974717c3909664e674"}, + {file = "coverage-7.6.12-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3688b99604a24492bcfe1c106278c45586eb819bf66a654d8a9a1433022fb2eb"}, + {file = "coverage-7.6.12-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:d1a987778b9c71da2fc8948e6f2656da6ef68f59298b7e9786849634c35d2c3c"}, + {file = "coverage-7.6.12-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:cec6b9ce3bd2b7853d4a4563801292bfee40b030c05a3d29555fd2a8ee9bd68c"}, + {file = "coverage-7.6.12-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:ace9048de91293e467b44bce0f0381345078389814ff6e18dbac8fdbf896360e"}, + {file = "coverage-7.6.12-cp310-cp310-win32.whl", hash = "sha256:ea31689f05043d520113e0552f039603c4dd71fa4c287b64cb3606140c66f425"}, + {file = "coverage-7.6.12-cp310-cp310-win_amd64.whl", hash = "sha256:676f92141e3c5492d2a1596d52287d0d963df21bf5e55c8b03075a60e1ddf8aa"}, + {file = "coverage-7.6.12-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:e18aafdfb3e9ec0d261c942d35bd7c28d031c5855dadb491d2723ba54f4c3015"}, + {file = "coverage-7.6.12-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:66fe626fd7aa5982cdebad23e49e78ef7dbb3e3c2a5960a2b53632f1f703ea45"}, + {file = "coverage-7.6.12-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0ef01d70198431719af0b1f5dcbefc557d44a190e749004042927b2a3fed0702"}, + {file = "coverage-7.6.12-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:07e92ae5a289a4bc4c0aae710c0948d3c7892e20fd3588224ebe242039573bf0"}, + {file = "coverage-7.6.12-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e695df2c58ce526eeab11a2e915448d3eb76f75dffe338ea613c1201b33bab2f"}, + {file = "coverage-7.6.12-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:d74c08e9aaef995f8c4ef6d202dbd219c318450fe2a76da624f2ebb9c8ec5d9f"}, + {file = "coverage-7.6.12-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:e995b3b76ccedc27fe4f477b349b7d64597e53a43fc2961db9d3fbace085d69d"}, + {file = "coverage-7.6.12-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:b1f097878d74fe51e1ddd1be62d8e3682748875b461232cf4b52ddc6e6db0bba"}, + {file = "coverage-7.6.12-cp311-cp311-win32.whl", hash = "sha256:1f7ffa05da41754e20512202c866d0ebfc440bba3b0ed15133070e20bf5aeb5f"}, + {file = "coverage-7.6.12-cp311-cp311-win_amd64.whl", hash = "sha256:e216c5c45f89ef8971373fd1c5d8d1164b81f7f5f06bbf23c37e7908d19e8558"}, + {file = "coverage-7.6.12-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:b172f8e030e8ef247b3104902cc671e20df80163b60a203653150d2fc204d1ad"}, + {file = "coverage-7.6.12-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:641dfe0ab73deb7069fb972d4d9725bf11c239c309ce694dd50b1473c0f641c3"}, + {file = "coverage-7.6.12-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0e549f54ac5f301e8e04c569dfdb907f7be71b06b88b5063ce9d6953d2d58574"}, + {file = "coverage-7.6.12-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:959244a17184515f8c52dcb65fb662808767c0bd233c1d8a166e7cf74c9ea985"}, + {file = "coverage-7.6.12-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bda1c5f347550c359f841d6614fb8ca42ae5cb0b74d39f8a1e204815ebe25750"}, + {file = "coverage-7.6.12-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:1ceeb90c3eda1f2d8c4c578c14167dbd8c674ecd7d38e45647543f19839dd6ea"}, + {file = "coverage-7.6.12-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:0f16f44025c06792e0fb09571ae454bcc7a3ec75eeb3c36b025eccf501b1a4c3"}, + {file = "coverage-7.6.12-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:b076e625396e787448d27a411aefff867db2bffac8ed04e8f7056b07024eed5a"}, + {file = "coverage-7.6.12-cp312-cp312-win32.whl", hash = "sha256:00b2086892cf06c7c2d74983c9595dc511acca00665480b3ddff749ec4fb2a95"}, + {file = "coverage-7.6.12-cp312-cp312-win_amd64.whl", hash = "sha256:7ae6eabf519bc7871ce117fb18bf14e0e343eeb96c377667e3e5dd12095e0288"}, + {file = "coverage-7.6.12-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:488c27b3db0ebee97a830e6b5a3ea930c4a6e2c07f27a5e67e1b3532e76b9ef1"}, + {file = "coverage-7.6.12-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:5d1095bbee1851269f79fd8e0c9b5544e4c00c0c24965e66d8cba2eb5bb535fd"}, + {file = "coverage-7.6.12-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0533adc29adf6a69c1baa88c3d7dbcaadcffa21afbed3ca7a225a440e4744bf9"}, + {file = "coverage-7.6.12-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:53c56358d470fa507a2b6e67a68fd002364d23c83741dbc4c2e0680d80ca227e"}, + {file = "coverage-7.6.12-cp313-cp313-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:64cbb1a3027c79ca6310bf101014614f6e6e18c226474606cf725238cf5bc2d4"}, + {file = "coverage-7.6.12-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:79cac3390bfa9836bb795be377395f28410811c9066bc4eefd8015258a7578c6"}, + {file = "coverage-7.6.12-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:9b148068e881faa26d878ff63e79650e208e95cf1c22bd3f77c3ca7b1d9821a3"}, + {file = "coverage-7.6.12-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:8bec2ac5da793c2685ce5319ca9bcf4eee683b8a1679051f8e6ec04c4f2fd7dc"}, + {file = "coverage-7.6.12-cp313-cp313-win32.whl", hash = "sha256:200e10beb6ddd7c3ded322a4186313d5ca9e63e33d8fab4faa67ef46d3460af3"}, + {file = "coverage-7.6.12-cp313-cp313-win_amd64.whl", hash = "sha256:2b996819ced9f7dbb812c701485d58f261bef08f9b85304d41219b1496b591ef"}, + {file = "coverage-7.6.12-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:299cf973a7abff87a30609879c10df0b3bfc33d021e1adabc29138a48888841e"}, + {file = "coverage-7.6.12-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:4b467a8c56974bf06e543e69ad803c6865249d7a5ccf6980457ed2bc50312703"}, + {file = "coverage-7.6.12-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2458f275944db8129f95d91aee32c828a408481ecde3b30af31d552c2ce284a0"}, + {file = "coverage-7.6.12-cp313-cp313t-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0a9d8be07fb0832636a0f72b80d2a652fe665e80e720301fb22b191c3434d924"}, + {file = "coverage-7.6.12-cp313-cp313t-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:14d47376a4f445e9743f6c83291e60adb1b127607a3618e3185bbc8091f0467b"}, + {file = "coverage-7.6.12-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:b95574d06aa9d2bd6e5cc35a5bbe35696342c96760b69dc4287dbd5abd4ad51d"}, + {file = "coverage-7.6.12-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:ecea0c38c9079570163d663c0433a9af4094a60aafdca491c6a3d248c7432827"}, + {file = "coverage-7.6.12-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:2251fabcfee0a55a8578a9d29cecfee5f2de02f11530e7d5c5a05859aa85aee9"}, + {file = "coverage-7.6.12-cp313-cp313t-win32.whl", hash = "sha256:eb5507795caabd9b2ae3f1adc95f67b1104971c22c624bb354232d65c4fc90b3"}, + {file = "coverage-7.6.12-cp313-cp313t-win_amd64.whl", hash = "sha256:f60a297c3987c6c02ffb29effc70eadcbb412fe76947d394a1091a3615948e2f"}, + {file = "coverage-7.6.12-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:e7575ab65ca8399c8c4f9a7d61bbd2d204c8b8e447aab9d355682205c9dd948d"}, + {file = "coverage-7.6.12-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:8161d9fbc7e9fe2326de89cd0abb9f3599bccc1287db0aba285cb68d204ce929"}, + {file = "coverage-7.6.12-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3a1e465f398c713f1b212400b4e79a09829cd42aebd360362cd89c5bdc44eb87"}, + {file = "coverage-7.6.12-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f25d8b92a4e31ff1bd873654ec367ae811b3a943583e05432ea29264782dc32c"}, + {file = "coverage-7.6.12-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1a936309a65cc5ca80fa9f20a442ff9e2d06927ec9a4f54bcba9c14c066323f2"}, + {file = "coverage-7.6.12-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:aa6f302a3a0b5f240ee201297fff0bbfe2fa0d415a94aeb257d8b461032389bd"}, + {file = "coverage-7.6.12-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:f973643ef532d4f9be71dd88cf7588936685fdb576d93a79fe9f65bc337d9d73"}, + {file = "coverage-7.6.12-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:78f5243bb6b1060aed6213d5107744c19f9571ec76d54c99cc15938eb69e0e86"}, + {file = "coverage-7.6.12-cp39-cp39-win32.whl", hash = "sha256:69e62c5034291c845fc4df7f8155e8544178b6c774f97a99e2734b05eb5bed31"}, + {file = "coverage-7.6.12-cp39-cp39-win_amd64.whl", hash = "sha256:b01a840ecc25dce235ae4c1b6a0daefb2a203dba0e6e980637ee9c2f6ee0df57"}, + {file = "coverage-7.6.12-pp39.pp310-none-any.whl", hash = "sha256:7e39e845c4d764208e7b8f6a21c541ade741e2c41afabdfa1caa28687a3c98cf"}, + {file = "coverage-7.6.12-py3-none-any.whl", hash = "sha256:eb8668cfbc279a536c633137deeb9435d2962caec279c3f8cf8b91fff6ff8953"}, + {file = "coverage-7.6.12.tar.gz", hash = "sha256:48cfc4641d95d34766ad41d9573cc0f22a48aa88d22657a1fe01dca0dbae4de2"}, ] [package.dependencies] @@ -554,39 +550,43 @@ toml = ["tomli"] [[package]] name = "cryptography" -version = "44.0.0" +version = "44.0.1" description = "cryptography is a package which provides cryptographic recipes and primitives to Python developers." optional = false python-versions = "!=3.9.0,!=3.9.1,>=3.7" groups = ["charm-libs", "integration"] files = [ - {file = "cryptography-44.0.0-cp37-abi3-macosx_10_9_universal2.whl", hash = "sha256:84111ad4ff3f6253820e6d3e58be2cc2a00adb29335d4cacb5ab4d4d34f2a123"}, - {file = "cryptography-44.0.0-cp37-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b15492a11f9e1b62ba9d73c210e2416724633167de94607ec6069ef724fad092"}, - {file = "cryptography-44.0.0-cp37-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:831c3c4d0774e488fdc83a1923b49b9957d33287de923d58ebd3cec47a0ae43f"}, - {file = "cryptography-44.0.0-cp37-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:761817a3377ef15ac23cd7834715081791d4ec77f9297ee694ca1ee9c2c7e5eb"}, - {file = "cryptography-44.0.0-cp37-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:3c672a53c0fb4725a29c303be906d3c1fa99c32f58abe008a82705f9ee96f40b"}, - {file = "cryptography-44.0.0-cp37-abi3-manylinux_2_34_aarch64.whl", hash = "sha256:4ac4c9f37eba52cb6fbeaf5b59c152ea976726b865bd4cf87883a7e7006cc543"}, - {file = "cryptography-44.0.0-cp37-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:ed3534eb1090483c96178fcb0f8893719d96d5274dfde98aa6add34614e97c8e"}, - {file = "cryptography-44.0.0-cp37-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:f3f6fdfa89ee2d9d496e2c087cebef9d4fcbb0ad63c40e821b39f74bf48d9c5e"}, - {file = "cryptography-44.0.0-cp37-abi3-win32.whl", hash = "sha256:eb33480f1bad5b78233b0ad3e1b0be21e8ef1da745d8d2aecbb20671658b9053"}, - {file = "cryptography-44.0.0-cp37-abi3-win_amd64.whl", hash = "sha256:abc998e0c0eee3c8a1904221d3f67dcfa76422b23620173e28c11d3e626c21bd"}, - {file = "cryptography-44.0.0-cp39-abi3-macosx_10_9_universal2.whl", hash = "sha256:660cb7312a08bc38be15b696462fa7cc7cd85c3ed9c576e81f4dc4d8b2b31591"}, - {file = "cryptography-44.0.0-cp39-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1923cb251c04be85eec9fda837661c67c1049063305d6be5721643c22dd4e2b7"}, - {file = "cryptography-44.0.0-cp39-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:404fdc66ee5f83a1388be54300ae978b2efd538018de18556dde92575e05defc"}, - {file = "cryptography-44.0.0-cp39-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:c5eb858beed7835e5ad1faba59e865109f3e52b3783b9ac21e7e47dc5554e289"}, - {file = "cryptography-44.0.0-cp39-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:f53c2c87e0fb4b0c00fa9571082a057e37690a8f12233306161c8f4b819960b7"}, - {file = "cryptography-44.0.0-cp39-abi3-manylinux_2_34_aarch64.whl", hash = "sha256:9e6fc8a08e116fb7c7dd1f040074c9d7b51d74a8ea40d4df2fc7aa08b76b9e6c"}, - {file = "cryptography-44.0.0-cp39-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:d2436114e46b36d00f8b72ff57e598978b37399d2786fd39793c36c6d5cb1c64"}, - {file = "cryptography-44.0.0-cp39-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:a01956ddfa0a6790d594f5b34fc1bfa6098aca434696a03cfdbe469b8ed79285"}, - {file = "cryptography-44.0.0-cp39-abi3-win32.whl", hash = "sha256:eca27345e1214d1b9f9490d200f9db5a874479be914199194e746c893788d417"}, - {file = "cryptography-44.0.0-cp39-abi3-win_amd64.whl", hash = "sha256:708ee5f1bafe76d041b53a4f95eb28cdeb8d18da17e597d46d7833ee59b97ede"}, - {file = "cryptography-44.0.0-pp310-pypy310_pp73-macosx_10_9_x86_64.whl", hash = "sha256:37d76e6863da3774cd9db5b409a9ecfd2c71c981c38788d3fcfaf177f447b731"}, - {file = "cryptography-44.0.0-pp310-pypy310_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:f677e1268c4e23420c3acade68fac427fffcb8d19d7df95ed7ad17cdef8404f4"}, - {file = "cryptography-44.0.0-pp310-pypy310_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:f5e7cb1e5e56ca0933b4873c0220a78b773b24d40d186b6738080b73d3d0a756"}, - {file = "cryptography-44.0.0-pp310-pypy310_pp73-manylinux_2_34_aarch64.whl", hash = "sha256:8b3e6eae66cf54701ee7d9c83c30ac0a1e3fa17be486033000f2a73a12ab507c"}, - {file = "cryptography-44.0.0-pp310-pypy310_pp73-manylinux_2_34_x86_64.whl", hash = "sha256:be4ce505894d15d5c5037167ffb7f0ae90b7be6f2a98f9a5c3442395501c32fa"}, - {file = "cryptography-44.0.0-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:62901fb618f74d7d81bf408c8719e9ec14d863086efe4185afd07c352aee1d2c"}, - {file = "cryptography-44.0.0.tar.gz", hash = "sha256:cd4e834f340b4293430701e772ec543b0fbe6c2dea510a5286fe0acabe153a02"}, + {file = "cryptography-44.0.1-cp37-abi3-macosx_10_9_universal2.whl", hash = "sha256:bf688f615c29bfe9dfc44312ca470989279f0e94bb9f631f85e3459af8efc009"}, + {file = "cryptography-44.0.1-cp37-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:dd7c7e2d71d908dc0f8d2027e1604102140d84b155e658c20e8ad1304317691f"}, + {file = "cryptography-44.0.1-cp37-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:887143b9ff6bad2b7570da75a7fe8bbf5f65276365ac259a5d2d5147a73775f2"}, + {file = "cryptography-44.0.1-cp37-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:322eb03ecc62784536bc173f1483e76747aafeb69c8728df48537eb431cd1911"}, + {file = "cryptography-44.0.1-cp37-abi3-manylinux_2_28_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:21377472ca4ada2906bc313168c9dc7b1d7ca417b63c1c3011d0c74b7de9ae69"}, + {file = "cryptography-44.0.1-cp37-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:df978682c1504fc93b3209de21aeabf2375cb1571d4e61907b3e7a2540e83026"}, + {file = "cryptography-44.0.1-cp37-abi3-manylinux_2_34_aarch64.whl", hash = "sha256:eb3889330f2a4a148abead555399ec9a32b13b7c8ba969b72d8e500eb7ef84cd"}, + {file = "cryptography-44.0.1-cp37-abi3-manylinux_2_34_x86_64.whl", hash = "sha256:8e6a85a93d0642bd774460a86513c5d9d80b5c002ca9693e63f6e540f1815ed0"}, + {file = "cryptography-44.0.1-cp37-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:6f76fdd6fd048576a04c5210d53aa04ca34d2ed63336d4abd306d0cbe298fddf"}, + {file = "cryptography-44.0.1-cp37-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:6c8acf6f3d1f47acb2248ec3ea261171a671f3d9428e34ad0357148d492c7864"}, + {file = "cryptography-44.0.1-cp37-abi3-win32.whl", hash = "sha256:24979e9f2040c953a94bf3c6782e67795a4c260734e5264dceea65c8f4bae64a"}, + {file = "cryptography-44.0.1-cp37-abi3-win_amd64.whl", hash = "sha256:fd0ee90072861e276b0ff08bd627abec29e32a53b2be44e41dbcdf87cbee2b00"}, + {file = "cryptography-44.0.1-cp39-abi3-macosx_10_9_universal2.whl", hash = "sha256:a2d8a7045e1ab9b9f803f0d9531ead85f90c5f2859e653b61497228b18452008"}, + {file = "cryptography-44.0.1-cp39-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b8272f257cf1cbd3f2e120f14c68bff2b6bdfcc157fafdee84a1b795efd72862"}, + {file = "cryptography-44.0.1-cp39-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1e8d181e90a777b63f3f0caa836844a1182f1f265687fac2115fcf245f5fbec3"}, + {file = "cryptography-44.0.1-cp39-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:436df4f203482f41aad60ed1813811ac4ab102765ecae7a2bbb1dbb66dcff5a7"}, + {file = "cryptography-44.0.1-cp39-abi3-manylinux_2_28_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:4f422e8c6a28cf8b7f883eb790695d6d45b0c385a2583073f3cec434cc705e1a"}, + {file = "cryptography-44.0.1-cp39-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:72198e2b5925155497a5a3e8c216c7fb3e64c16ccee11f0e7da272fa93b35c4c"}, + {file = "cryptography-44.0.1-cp39-abi3-manylinux_2_34_aarch64.whl", hash = "sha256:2a46a89ad3e6176223b632056f321bc7de36b9f9b93b2cc1cccf935a3849dc62"}, + {file = "cryptography-44.0.1-cp39-abi3-manylinux_2_34_x86_64.whl", hash = "sha256:53f23339864b617a3dfc2b0ac8d5c432625c80014c25caac9082314e9de56f41"}, + {file = "cryptography-44.0.1-cp39-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:888fcc3fce0c888785a4876ca55f9f43787f4c5c1cc1e2e0da71ad481ff82c5b"}, + {file = "cryptography-44.0.1-cp39-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:00918d859aa4e57db8299607086f793fa7813ae2ff5a4637e318a25ef82730f7"}, + {file = "cryptography-44.0.1-cp39-abi3-win32.whl", hash = "sha256:9b336599e2cb77b1008cb2ac264b290803ec5e8e89d618a5e978ff5eb6f715d9"}, + {file = "cryptography-44.0.1-cp39-abi3-win_amd64.whl", hash = "sha256:e403f7f766ded778ecdb790da786b418a9f2394f36e8cc8b796cc056ab05f44f"}, + {file = "cryptography-44.0.1-pp310-pypy310_pp73-macosx_10_9_x86_64.whl", hash = "sha256:1f9a92144fa0c877117e9748c74501bea842f93d21ee00b0cf922846d9d0b183"}, + {file = "cryptography-44.0.1-pp310-pypy310_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:610a83540765a8d8ce0f351ce42e26e53e1f774a6efb71eb1b41eb01d01c3d12"}, + {file = "cryptography-44.0.1-pp310-pypy310_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:5fed5cd6102bb4eb843e3315d2bf25fede494509bddadb81e03a859c1bc17b83"}, + {file = "cryptography-44.0.1-pp310-pypy310_pp73-manylinux_2_34_aarch64.whl", hash = "sha256:f4daefc971c2d1f82f03097dc6f216744a6cd2ac0f04c68fb935ea2ba2a0d420"}, + {file = "cryptography-44.0.1-pp310-pypy310_pp73-manylinux_2_34_x86_64.whl", hash = "sha256:94f99f2b943b354a5b6307d7e8d19f5c423a794462bde2bf310c770ba052b1c4"}, + {file = "cryptography-44.0.1-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:d9c5b9f698a83c8bd71e0f4d3f9f839ef244798e5ffe96febfa9714717db7af7"}, + {file = "cryptography-44.0.1.tar.gz", hash = "sha256:f51f5705ab27898afda1aaa430f34ad90dc117421057782022edf0600bec5f14"}, ] [package.dependencies] @@ -599,7 +599,7 @@ nox = ["nox (>=2024.4.15)", "nox[uv] (>=2024.3.2)"] pep8test = ["check-sdist", "click (>=8.0.1)", "mypy (>=1.4)", "ruff (>=0.3.6)"] sdist = ["build (>=1.0.0)"] ssh = ["bcrypt (>=3.1.5)"] -test = ["certifi (>=2024)", "cryptography-vectors (==44.0.0)", "pretend (>=0.7)", "pytest (>=7.4.0)", "pytest-benchmark (>=4.0)", "pytest-cov (>=2.10.1)", "pytest-xdist (>=3.5.0)"] +test = ["certifi (>=2024)", "cryptography-vectors (==44.0.1)", "pretend (>=0.7)", "pytest (>=7.4.0)", "pytest-benchmark (>=4.0)", "pytest-cov (>=2.10.1)", "pytest-xdist (>=3.5.0)"] test-randomorder = ["pytest-randomly"] [[package]] @@ -616,14 +616,14 @@ files = [ [[package]] name = "deprecated" -version = "1.2.17" +version = "1.2.18" description = "Python @deprecated decorator to deprecate old python classes, functions or methods." optional = false python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,>=2.7" groups = ["charm-libs"] files = [ - {file = "Deprecated-1.2.17-py2.py3-none-any.whl", hash = "sha256:69cdc0a751671183f569495e2efb14baee4344b0236342eec29f1fde25d61818"}, - {file = "deprecated-1.2.17.tar.gz", hash = "sha256:0114a10f0bbb750b90b2c2296c90cf7e9eaeb0abb5cf06c80de2c60138de0a82"}, + {file = "Deprecated-1.2.18-py2.py3-none-any.whl", hash = "sha256:bd5011788200372a32418f888e326a09ff80d0214bd961147cfed01b5c018eec"}, + {file = "deprecated-1.2.18.tar.gz", hash = "sha256:422b6f6d859da6f2ef57857761bfb392480502a64c3028ca9bbe86085d72115d"}, ] [package.dependencies] @@ -690,14 +690,14 @@ requests = ["requests (>=2.20.0,<3.0.0.dev0)"] [[package]] name = "googleapis-common-protos" -version = "1.66.0" +version = "1.67.0" description = "Common protobufs used in Google APIs" optional = false python-versions = ">=3.7" groups = ["charm-libs"] files = [ - {file = "googleapis_common_protos-1.66.0-py2.py3-none-any.whl", hash = "sha256:d7abcd75fabb2e0ec9f74466401f6c119a0b498e27370e9be4c94cb7e382b8ed"}, - {file = "googleapis_common_protos-1.66.0.tar.gz", hash = "sha256:c3e7b33d15fdca5374cc0a7346dd92ffa847425cc4ea941d970f13680052ec8c"}, + {file = "googleapis_common_protos-1.67.0-py2.py3-none-any.whl", hash = "sha256:579de760800d13616f51cf8be00c876f00a9f146d3e6510e19d1f4111758b741"}, + {file = "googleapis_common_protos-1.67.0.tar.gz", hash = "sha256:21398025365f138be356d5923e9168737d94d46a72aefee4a6110a1f23463c86"}, ] [package.dependencies] @@ -849,14 +849,14 @@ tomli = {version = "*", markers = "python_version > \"3.6\" and python_version < [[package]] name = "ipython" -version = "8.31.0" +version = "8.32.0" description = "IPython: Productive Interactive Computing" optional = false python-versions = ">=3.10" groups = ["integration"] files = [ - {file = "ipython-8.31.0-py3-none-any.whl", hash = "sha256:46ec58f8d3d076a61d128fe517a51eb730e3aaf0c184ea8c17d16e366660c6a6"}, - {file = "ipython-8.31.0.tar.gz", hash = "sha256:b6a2274606bec6166405ff05e54932ed6e5cfecaca1fc05f2cacde7bb074d70b"}, + {file = "ipython-8.32.0-py3-none-any.whl", hash = "sha256:cae85b0c61eff1fc48b0a8002de5958b6528fa9c8defb1894da63f42613708aa"}, + {file = "ipython-8.32.0.tar.gz", hash = "sha256:be2c91895b0b9ea7ba49d33b23e2040c352b33eb6a519cca7ce6e0c743444251"}, ] [package.dependencies] @@ -908,14 +908,14 @@ testing = ["Django", "attrs", "colorama", "docopt", "pytest (<9.0.0)"] [[package]] name = "jinja2" -version = "3.1.5" +version = "3.1.6" description = "A very fast and expressive template engine." optional = false python-versions = ">=3.7" groups = ["main", "integration"] files = [ - {file = "jinja2-3.1.5-py3-none-any.whl", hash = "sha256:aba0f4dc9ed8013c424088f68a5c226f7d6097ed89b246d7749c2ec4175c6adb"}, - {file = "jinja2-3.1.5.tar.gz", hash = "sha256:8fefff8dc3034e27bb80d67c671eb8a9bc424c0ef4c0826edbff304cceff43bb"}, + {file = "jinja2-3.1.6-py3-none-any.whl", hash = "sha256:85ece4451f492d0c13c5dd7c13a64681a86afae63a5f347908daf103ce6d2f67"}, + {file = "jinja2-3.1.6.tar.gz", hash = "sha256:0137fb05990d35f1275a587e9aee6d56da821fc83491a0fb838183be43f66d6d"}, ] [package.dependencies] @@ -1335,14 +1335,14 @@ files = [ [[package]] name = "ops" -version = "2.17.1" +version = "2.18.1" description = "The Python library behind great charms" optional = false python-versions = ">=3.8" groups = ["main", "charm-libs"] files = [ - {file = "ops-2.17.1-py3-none-any.whl", hash = "sha256:0fabc45740d59619c3265328f51f71f99b06557e22493cdd32d10c2b25bcd553"}, - {file = "ops-2.17.1.tar.gz", hash = "sha256:de2d1dd382b4a5f3df3ba78a5266d59462644f3f8ea0f4e7479a248998862a3f"}, + {file = "ops-2.18.1-py3-none-any.whl", hash = "sha256:ba0312366e25b3ae90cf4b8d0af6ea6b612d4951500f856bce609cdb25c9bdeb"}, + {file = "ops-2.18.1.tar.gz", hash = "sha256:5619deb370c00ea851f9579b780a09b88b1a1d020e58e1ed81d31c8fb7b28c8a"}, ] [package.dependencies] @@ -1350,7 +1350,7 @@ PyYAML = "==6.*" websocket-client = "==1.*" [package.extras] -docs = ["canonical-sphinx-extensions", "furo", "linkify-it-py", "myst-parser", "ops-scenario (>=7.0.5,<8)", "pyspelling", "sphinx (>=8.0.0,<8.1.0)", "sphinx-autobuild", "sphinx-copybutton", "sphinx-design", "sphinx-notfound-page", "sphinx-tabs", "sphinxcontrib-jquery", "sphinxext-opengraph"] +docs = ["canonical-sphinx-extensions", "furo", "linkify-it-py", "myst-parser", "pyspelling", "sphinx (>=8.0.0,<8.1.0)", "sphinx-autobuild", "sphinx-copybutton", "sphinx-design", "sphinx-notfound-page", "sphinx-tabs", "sphinxcontrib-jquery", "sphinxext-opengraph"] testing = ["ops-scenario (>=7.0.5,<8)"] [[package]] @@ -1382,14 +1382,14 @@ dev = ["jinja2"] [[package]] name = "paramiko" -version = "3.5.0" +version = "3.5.1" description = "SSH2 protocol library" optional = false python-versions = ">=3.6" groups = ["integration"] files = [ - {file = "paramiko-3.5.0-py3-none-any.whl", hash = "sha256:1fedf06b085359051cd7d0d270cebe19e755a8a921cc2ddbfa647fb0cd7d68f9"}, - {file = "paramiko-3.5.0.tar.gz", hash = "sha256:ad11e540da4f55cedda52931f1a3f812a8238a7af7f62a60de538cd80bb28124"}, + {file = "paramiko-3.5.1-py3-none-any.whl", hash = "sha256:43b9a0501fc2b5e70680388d9346cf252cfb7d00b0667c39e80eb43a408b8f61"}, + {file = "paramiko-3.5.1.tar.gz", hash = "sha256:b2c665bc45b2b215bd7d7f039901b14b067da00f3a11e6640995fd58f2664822"}, ] [package.dependencies] @@ -1464,14 +1464,14 @@ testing = ["pytest", "pytest-benchmark"] [[package]] name = "poetry-core" -version = "2.0.1" +version = "2.1.1" description = "Poetry PEP 517 Build Backend" optional = false python-versions = "<4.0,>=3.9" groups = ["charm-libs"] files = [ - {file = "poetry_core-2.0.1-py3-none-any.whl", hash = "sha256:a3c7009536522cda4eb0fb3805c9dc935b5537f8727dd01efb9c15e51a17552b"}, - {file = "poetry_core-2.0.1.tar.gz", hash = "sha256:10177c2772469d9032a49f0d8707af761b1c597cea3b4fb31546e5cd436eb157"}, + {file = "poetry_core-2.1.1-py3-none-any.whl", hash = "sha256:bc3b0382ab4d00d5d780277fd0aad1580eb4403613b37fc60fec407b5bee1fe6"}, + {file = "poetry_core-2.1.1.tar.gz", hash = "sha256:c1a1f6f00e4254742f40988a8caf665549101cf9991122cd5de1198897768b1a"}, ] [[package]] @@ -1512,33 +1512,26 @@ files = [ [[package]] name = "psutil" -version = "6.1.1" -description = "Cross-platform lib for process and system monitoring in Python." +version = "7.0.0" +description = "Cross-platform lib for process and system monitoring in Python. NOTE: the syntax of this script MUST be kept compatible with Python 2.7." optional = false -python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,>=2.7" +python-versions = ">=3.6" groups = ["main"] files = [ - {file = "psutil-6.1.1-cp27-cp27m-macosx_10_9_x86_64.whl", hash = "sha256:9ccc4316f24409159897799b83004cb1e24f9819b0dcf9c0b68bdcb6cefee6a8"}, - {file = "psutil-6.1.1-cp27-cp27m-manylinux2010_i686.whl", hash = "sha256:ca9609c77ea3b8481ab005da74ed894035936223422dc591d6772b147421f777"}, - {file = "psutil-6.1.1-cp27-cp27m-manylinux2010_x86_64.whl", hash = "sha256:8df0178ba8a9e5bc84fed9cfa61d54601b371fbec5c8eebad27575f1e105c0d4"}, - {file = "psutil-6.1.1-cp27-cp27mu-manylinux2010_i686.whl", hash = "sha256:1924e659d6c19c647e763e78670a05dbb7feaf44a0e9c94bf9e14dfc6ba50468"}, - {file = "psutil-6.1.1-cp27-cp27mu-manylinux2010_x86_64.whl", hash = "sha256:018aeae2af92d943fdf1da6b58665124897cfc94faa2ca92098838f83e1b1bca"}, - {file = "psutil-6.1.1-cp27-none-win32.whl", hash = "sha256:6d4281f5bbca041e2292be3380ec56a9413b790579b8e593b1784499d0005dac"}, - {file = "psutil-6.1.1-cp27-none-win_amd64.whl", hash = "sha256:c777eb75bb33c47377c9af68f30e9f11bc78e0f07fbf907be4a5d70b2fe5f030"}, - {file = "psutil-6.1.1-cp36-abi3-macosx_10_9_x86_64.whl", hash = "sha256:fc0ed7fe2231a444fc219b9c42d0376e0a9a1a72f16c5cfa0f68d19f1a0663e8"}, - {file = "psutil-6.1.1-cp36-abi3-macosx_11_0_arm64.whl", hash = "sha256:0bdd4eab935276290ad3cb718e9809412895ca6b5b334f5a9111ee6d9aff9377"}, - {file = "psutil-6.1.1-cp36-abi3-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b6e06c20c05fe95a3d7302d74e7097756d4ba1247975ad6905441ae1b5b66003"}, - {file = "psutil-6.1.1-cp36-abi3-manylinux_2_12_x86_64.manylinux2010_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:97f7cb9921fbec4904f522d972f0c0e1f4fabbdd4e0287813b21215074a0f160"}, - {file = "psutil-6.1.1-cp36-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:33431e84fee02bc84ea36d9e2c4a6d395d479c9dd9bba2376c1f6ee8f3a4e0b3"}, - {file = "psutil-6.1.1-cp36-cp36m-win32.whl", hash = "sha256:384636b1a64b47814437d1173be1427a7c83681b17a450bfc309a1953e329603"}, - {file = "psutil-6.1.1-cp36-cp36m-win_amd64.whl", hash = "sha256:8be07491f6ebe1a693f17d4f11e69d0dc1811fa082736500f649f79df7735303"}, - {file = "psutil-6.1.1-cp37-abi3-win32.whl", hash = "sha256:eaa912e0b11848c4d9279a93d7e2783df352b082f40111e078388701fd479e53"}, - {file = "psutil-6.1.1-cp37-abi3-win_amd64.whl", hash = "sha256:f35cfccb065fff93529d2afb4a2e89e363fe63ca1e4a5da22b603a85833c2649"}, - {file = "psutil-6.1.1.tar.gz", hash = "sha256:cf8496728c18f2d0b45198f06895be52f36611711746b7f30c464b422b50e2f5"}, + {file = "psutil-7.0.0-cp36-abi3-macosx_10_9_x86_64.whl", hash = "sha256:101d71dc322e3cffd7cea0650b09b3d08b8e7c4109dd6809fe452dfd00e58b25"}, + {file = "psutil-7.0.0-cp36-abi3-macosx_11_0_arm64.whl", hash = "sha256:39db632f6bb862eeccf56660871433e111b6ea58f2caea825571951d4b6aa3da"}, + {file = "psutil-7.0.0-cp36-abi3-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1fcee592b4c6f146991ca55919ea3d1f8926497a713ed7faaf8225e174581e91"}, + {file = "psutil-7.0.0-cp36-abi3-manylinux_2_12_x86_64.manylinux2010_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4b1388a4f6875d7e2aff5c4ca1cc16c545ed41dd8bb596cefea80111db353a34"}, + {file = "psutil-7.0.0-cp36-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a5f098451abc2828f7dc6b58d44b532b22f2088f4999a937557b603ce72b1993"}, + {file = "psutil-7.0.0-cp36-cp36m-win32.whl", hash = "sha256:84df4eb63e16849689f76b1ffcb36db7b8de703d1bc1fe41773db487621b6c17"}, + {file = "psutil-7.0.0-cp36-cp36m-win_amd64.whl", hash = "sha256:1e744154a6580bc968a0195fd25e80432d3afec619daf145b9e5ba16cc1d688e"}, + {file = "psutil-7.0.0-cp37-abi3-win32.whl", hash = "sha256:ba3fcef7523064a6c9da440fc4d6bd07da93ac726b5733c29027d7dc95b39d99"}, + {file = "psutil-7.0.0-cp37-abi3-win_amd64.whl", hash = "sha256:4cf3d4eb1aa9b348dec30105c55cd9b7d4629285735a102beb4441e38db90553"}, + {file = "psutil-7.0.0.tar.gz", hash = "sha256:7be9c3eba38beccb6495ea33afd982a44074b78f28c434a1f51cc07fd315c456"}, ] [package.extras] -dev = ["abi3audit", "black", "check-manifest", "coverage", "packaging", "pylint", "pyperf", "pypinfo", "pytest-cov", "requests", "rstcheck", "ruff", "sphinx", "sphinx_rtd_theme", "toml-sort", "twine", "virtualenv", "vulture", "wheel"] +dev = ["abi3audit", "black (==24.10.0)", "check-manifest", "coverage", "packaging", "pylint", "pyperf", "pypinfo", "pytest", "pytest-cov", "pytest-xdist", "requests", "rstcheck", "ruff", "setuptools", "sphinx", "sphinx_rtd_theme", "toml-sort", "twine", "virtualenv", "vulture", "wheel"] test = ["pytest", "pytest-xdist", "setuptools"] [[package]] @@ -1849,13 +1842,13 @@ pytz = "*" [[package]] name = "pysyncobj" -version = "0.3.13" +version = "0.3.14" description = "A library for replicating your python class between multiple servers, based on raft protocol" optional = false python-versions = "*" groups = ["main"] files = [ - {file = "pysyncobj-0.3.13.tar.gz", hash = "sha256:1785930b738fa21af298ebb04c213af25c31af148faa32f53af337ed1492d5a2"}, + {file = "pysyncobj-0.3.14.tar.gz", hash = "sha256:69d34e672257694f83f50dfb5e6e7ce446a68dba09ed48e142c782380d6428d4"}, ] [[package]] @@ -1900,33 +1893,16 @@ pytest = ">=7.0.0" docs = ["sphinx (>=5.3)", "sphinx-rtd-theme (>=1.0)"] testing = ["coverage (>=6.2)", "flaky (>=3.5.0)", "hypothesis (>=5.7.1)", "mypy (>=0.931)", "pytest-trio (>=0.7.0)"] -[[package]] -name = "pytest-github-secrets" -version = "0.1.0" -description = "" -optional = false -python-versions = ">=3.8" -groups = ["integration"] -files = [] -develop = false - -[package.source] -type = "git" -url = "https://github.com/canonical/data-platform-workflows" -reference = "v29.1.0" -resolved_reference = "cf3e292107a8d420c452e35cf7552c225add7fbd" -subdirectory = "python/pytest_plugins/github_secrets" - [[package]] name = "pytest-operator" -version = "0.39.0" +version = "0.40.0" description = "Fixtures for Operators" optional = false python-versions = "*" groups = ["integration"] files = [ - {file = "pytest_operator-0.39.0-py3-none-any.whl", hash = "sha256:ade76e1896eaf7f71704b537fd6661a705d81a045b8db71531d9e4741913fa19"}, - {file = "pytest_operator-0.39.0.tar.gz", hash = "sha256:b66bd8c6d161593c258a5714118a51e9f37721e7cd9e503299423d8a7d900f90"}, + {file = "pytest_operator-0.40.0-py3-none-any.whl", hash = "sha256:1cfa93ab61b11e8d7bf58dbb1a39e75fcbfcc084781bb571fde08fda7e236713"}, + {file = "pytest_operator-0.40.0.tar.gz", hash = "sha256:45394ade32b7765b6ba89871b676d1fb8aa7578589f74df26ff0fca4692d1c7b"}, ] [package.dependencies] @@ -1937,46 +1913,6 @@ pytest = "*" pytest-asyncio = "<0.23" pyyaml = "*" -[[package]] -name = "pytest-operator-cache" -version = "0.1.0" -description = "" -optional = false -python-versions = ">=3.8" -groups = ["integration"] -files = [] -develop = false - -[package.dependencies] -pyyaml = "*" - -[package.source] -type = "git" -url = "https://github.com/canonical/data-platform-workflows" -reference = "v29.1.0" -resolved_reference = "cf3e292107a8d420c452e35cf7552c225add7fbd" -subdirectory = "python/pytest_plugins/pytest_operator_cache" - -[[package]] -name = "pytest-operator-groups" -version = "0.1.0" -description = "" -optional = false -python-versions = ">=3.8" -groups = ["integration"] -files = [] -develop = false - -[package.dependencies] -pytest = "*" - -[package.source] -type = "git" -url = "https://github.com/canonical/data-platform-workflows" -reference = "v29.1.0" -resolved_reference = "cf3e292107a8d420c452e35cf7552c225add7fbd" -subdirectory = "python/pytest_plugins/pytest_operator_groups" - [[package]] name = "python-dateutil" version = "2.9.0.post0" @@ -1994,14 +1930,14 @@ six = ">=1.5" [[package]] name = "pytz" -version = "2024.2" +version = "2025.1" description = "World timezone definitions, modern and historical" optional = false python-versions = "*" groups = ["integration"] files = [ - {file = "pytz-2024.2-py2.py3-none-any.whl", hash = "sha256:31c7c1817eb7fae7ca4b8c7ee50c72f93aa2dd863de768e1ef4245d426aa0725"}, - {file = "pytz-2024.2.tar.gz", hash = "sha256:2aa355083c50a0f93fa581709deac0c9ad65cca8a9e9beac660adcbd493c798a"}, + {file = "pytz-2025.1-py2.py3-none-any.whl", hash = "sha256:89dd22dca55b46eac6eda23b2d72721bf1bdfef212645d81513ef5d03038de57"}, + {file = "pytz-2025.1.tar.gz", hash = "sha256:c2db42be2a2518b28e65f9207c4d05e6ff547d1efa4086469ef855e4ab70178e"}, ] [[package]] @@ -2255,30 +2191,30 @@ pyasn1 = ">=0.1.3" [[package]] name = "ruff" -version = "0.9.3" +version = "0.9.6" description = "An extremely fast Python linter and code formatter, written in Rust." optional = false python-versions = ">=3.7" groups = ["format"] files = [ - {file = "ruff-0.9.3-py3-none-linux_armv6l.whl", hash = "sha256:7f39b879064c7d9670197d91124a75d118d00b0990586549949aae80cdc16624"}, - {file = "ruff-0.9.3-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:a187171e7c09efa4b4cc30ee5d0d55a8d6c5311b3e1b74ac5cb96cc89bafc43c"}, - {file = "ruff-0.9.3-py3-none-macosx_11_0_arm64.whl", hash = "sha256:c59ab92f8e92d6725b7ded9d4a31be3ef42688a115c6d3da9457a5bda140e2b4"}, - {file = "ruff-0.9.3-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2dc153c25e715be41bb228bc651c1e9b1a88d5c6e5ed0194fa0dfea02b026439"}, - {file = "ruff-0.9.3-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:646909a1e25e0dc28fbc529eab8eb7bb583079628e8cbe738192853dbbe43af5"}, - {file = "ruff-0.9.3-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:5a5a46e09355695fbdbb30ed9889d6cf1c61b77b700a9fafc21b41f097bfbba4"}, - {file = "ruff-0.9.3-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:c4bb09d2bbb394e3730d0918c00276e79b2de70ec2a5231cd4ebb51a57df9ba1"}, - {file = "ruff-0.9.3-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:96a87ec31dc1044d8c2da2ebbed1c456d9b561e7d087734336518181b26b3aa5"}, - {file = "ruff-0.9.3-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:9bb7554aca6f842645022fe2d301c264e6925baa708b392867b7a62645304df4"}, - {file = "ruff-0.9.3-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:cabc332b7075a914ecea912cd1f3d4370489c8018f2c945a30bcc934e3bc06a6"}, - {file = "ruff-0.9.3-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:33866c3cc2a575cbd546f2cd02bdd466fed65118e4365ee538a3deffd6fcb730"}, - {file = "ruff-0.9.3-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:006e5de2621304c8810bcd2ee101587712fa93b4f955ed0985907a36c427e0c2"}, - {file = "ruff-0.9.3-py3-none-musllinux_1_2_i686.whl", hash = "sha256:ba6eea4459dbd6b1be4e6bfc766079fb9b8dd2e5a35aff6baee4d9b1514ea519"}, - {file = "ruff-0.9.3-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:90230a6b8055ad47d3325e9ee8f8a9ae7e273078a66401ac66df68943ced029b"}, - {file = "ruff-0.9.3-py3-none-win32.whl", hash = "sha256:eabe5eb2c19a42f4808c03b82bd313fc84d4e395133fb3fc1b1516170a31213c"}, - {file = "ruff-0.9.3-py3-none-win_amd64.whl", hash = "sha256:040ceb7f20791dfa0e78b4230ee9dce23da3b64dd5848e40e3bf3ab76468dcf4"}, - {file = "ruff-0.9.3-py3-none-win_arm64.whl", hash = "sha256:800d773f6d4d33b0a3c60e2c6ae8f4c202ea2de056365acfa519aa48acf28e0b"}, - {file = "ruff-0.9.3.tar.gz", hash = "sha256:8293f89985a090ebc3ed1064df31f3b4b56320cdfcec8b60d3295bddb955c22a"}, + {file = "ruff-0.9.6-py3-none-linux_armv6l.whl", hash = "sha256:2f218f356dd2d995839f1941322ff021c72a492c470f0b26a34f844c29cdf5ba"}, + {file = "ruff-0.9.6-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:b908ff4df65dad7b251c9968a2e4560836d8f5487c2f0cc238321ed951ea0504"}, + {file = "ruff-0.9.6-py3-none-macosx_11_0_arm64.whl", hash = "sha256:b109c0ad2ececf42e75fa99dc4043ff72a357436bb171900714a9ea581ddef83"}, + {file = "ruff-0.9.6-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1de4367cca3dac99bcbd15c161404e849bb0bfd543664db39232648dc00112dc"}, + {file = "ruff-0.9.6-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:ac3ee4d7c2c92ddfdaedf0bf31b2b176fa7aa8950efc454628d477394d35638b"}, + {file = "ruff-0.9.6-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:5dc1edd1775270e6aa2386119aea692039781429f0be1e0949ea5884e011aa8e"}, + {file = "ruff-0.9.6-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:4a091729086dffa4bd070aa5dab7e39cc6b9d62eb2bef8f3d91172d30d599666"}, + {file = "ruff-0.9.6-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d1bbc6808bf7b15796cef0815e1dfb796fbd383e7dbd4334709642649625e7c5"}, + {file = "ruff-0.9.6-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:589d1d9f25b5754ff230dce914a174a7c951a85a4e9270613a2b74231fdac2f5"}, + {file = "ruff-0.9.6-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dc61dd5131742e21103fbbdcad683a8813be0e3c204472d520d9a5021ca8b217"}, + {file = "ruff-0.9.6-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:5e2d9126161d0357e5c8f30b0bd6168d2c3872372f14481136d13de9937f79b6"}, + {file = "ruff-0.9.6-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:68660eab1a8e65babb5229a1f97b46e3120923757a68b5413d8561f8a85d4897"}, + {file = "ruff-0.9.6-py3-none-musllinux_1_2_i686.whl", hash = "sha256:c4cae6c4cc7b9b4017c71114115db0445b00a16de3bcde0946273e8392856f08"}, + {file = "ruff-0.9.6-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:19f505b643228b417c1111a2a536424ddde0db4ef9023b9e04a46ed8a1cb4656"}, + {file = "ruff-0.9.6-py3-none-win32.whl", hash = "sha256:194d8402bceef1b31164909540a597e0d913c0e4952015a5b40e28c146121b5d"}, + {file = "ruff-0.9.6-py3-none-win_amd64.whl", hash = "sha256:03482d5c09d90d4ee3f40d97578423698ad895c87314c4de39ed2af945633caa"}, + {file = "ruff-0.9.6-py3-none-win_arm64.whl", hash = "sha256:0e2bb706a2be7ddfea4a4af918562fdc1bcb16df255e5fa595bbd800ce322a5a"}, + {file = "ruff-0.9.6.tar.gz", hash = "sha256:81761592f72b620ec8fa1068a6fd00e98a5ebee342a3642efd84454f3031dca9"}, ] [[package]] @@ -2508,81 +2444,81 @@ test = ["websockets"] [[package]] name = "websockets" -version = "14.2" +version = "15.0" description = "An implementation of the WebSocket Protocol (RFC 6455 & 7692)" optional = false python-versions = ">=3.9" groups = ["integration"] files = [ - {file = "websockets-14.2-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:e8179f95323b9ab1c11723e5d91a89403903f7b001828161b480a7810b334885"}, - {file = "websockets-14.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:0d8c3e2cdb38f31d8bd7d9d28908005f6fa9def3324edb9bf336d7e4266fd397"}, - {file = "websockets-14.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:714a9b682deb4339d39ffa674f7b674230227d981a37d5d174a4a83e3978a610"}, - {file = "websockets-14.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f2e53c72052f2596fb792a7acd9704cbc549bf70fcde8a99e899311455974ca3"}, - {file = "websockets-14.2-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e3fbd68850c837e57373d95c8fe352203a512b6e49eaae4c2f4088ef8cf21980"}, - {file = "websockets-14.2-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4b27ece32f63150c268593d5fdb82819584831a83a3f5809b7521df0685cd5d8"}, - {file = "websockets-14.2-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:4daa0faea5424d8713142b33825fff03c736f781690d90652d2c8b053345b0e7"}, - {file = "websockets-14.2-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:bc63cee8596a6ec84d9753fd0fcfa0452ee12f317afe4beae6b157f0070c6c7f"}, - {file = "websockets-14.2-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:7a570862c325af2111343cc9b0257b7119b904823c675b22d4ac547163088d0d"}, - {file = "websockets-14.2-cp310-cp310-win32.whl", hash = "sha256:75862126b3d2d505e895893e3deac0a9339ce750bd27b4ba515f008b5acf832d"}, - {file = "websockets-14.2-cp310-cp310-win_amd64.whl", hash = "sha256:cc45afb9c9b2dc0852d5c8b5321759cf825f82a31bfaf506b65bf4668c96f8b2"}, - {file = "websockets-14.2-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:3bdc8c692c866ce5fefcaf07d2b55c91d6922ac397e031ef9b774e5b9ea42166"}, - {file = "websockets-14.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:c93215fac5dadc63e51bcc6dceca72e72267c11def401d6668622b47675b097f"}, - {file = "websockets-14.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:1c9b6535c0e2cf8a6bf938064fb754aaceb1e6a4a51a80d884cd5db569886910"}, - {file = "websockets-14.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0a52a6d7cf6938e04e9dceb949d35fbdf58ac14deea26e685ab6368e73744e4c"}, - {file = "websockets-14.2-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:9f05702e93203a6ff5226e21d9b40c037761b2cfb637187c9802c10f58e40473"}, - {file = "websockets-14.2-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:22441c81a6748a53bfcb98951d58d1af0661ab47a536af08920d129b4d1c3473"}, - {file = "websockets-14.2-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:efd9b868d78b194790e6236d9cbc46d68aba4b75b22497eb4ab64fa640c3af56"}, - {file = "websockets-14.2-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:1a5a20d5843886d34ff8c57424cc65a1deda4375729cbca4cb6b3353f3ce4142"}, - {file = "websockets-14.2-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:34277a29f5303d54ec6468fb525d99c99938607bc96b8d72d675dee2b9f5bf1d"}, - {file = "websockets-14.2-cp311-cp311-win32.whl", hash = "sha256:02687db35dbc7d25fd541a602b5f8e451a238ffa033030b172ff86a93cb5dc2a"}, - {file = "websockets-14.2-cp311-cp311-win_amd64.whl", hash = "sha256:862e9967b46c07d4dcd2532e9e8e3c2825e004ffbf91a5ef9dde519ee2effb0b"}, - {file = "websockets-14.2-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:1f20522e624d7ffbdbe259c6b6a65d73c895045f76a93719aa10cd93b3de100c"}, - {file = "websockets-14.2-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:647b573f7d3ada919fd60e64d533409a79dcf1ea21daeb4542d1d996519ca967"}, - {file = "websockets-14.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:6af99a38e49f66be5a64b1e890208ad026cda49355661549c507152113049990"}, - {file = "websockets-14.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:091ab63dfc8cea748cc22c1db2814eadb77ccbf82829bac6b2fbe3401d548eda"}, - {file = "websockets-14.2-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b374e8953ad477d17e4851cdc66d83fdc2db88d9e73abf755c94510ebddceb95"}, - {file = "websockets-14.2-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a39d7eceeea35db85b85e1169011bb4321c32e673920ae9c1b6e0978590012a3"}, - {file = "websockets-14.2-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:0a6f3efd47ffd0d12080594f434faf1cd2549b31e54870b8470b28cc1d3817d9"}, - {file = "websockets-14.2-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:065ce275e7c4ffb42cb738dd6b20726ac26ac9ad0a2a48e33ca632351a737267"}, - {file = "websockets-14.2-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:e9d0e53530ba7b8b5e389c02282f9d2aa47581514bd6049d3a7cffe1385cf5fe"}, - {file = "websockets-14.2-cp312-cp312-win32.whl", hash = "sha256:20e6dd0984d7ca3037afcb4494e48c74ffb51e8013cac71cf607fffe11df7205"}, - {file = "websockets-14.2-cp312-cp312-win_amd64.whl", hash = "sha256:44bba1a956c2c9d268bdcdf234d5e5ff4c9b6dc3e300545cbe99af59dda9dcce"}, - {file = "websockets-14.2-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:6f1372e511c7409a542291bce92d6c83320e02c9cf392223272287ce55bc224e"}, - {file = "websockets-14.2-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:4da98b72009836179bb596a92297b1a61bb5a830c0e483a7d0766d45070a08ad"}, - {file = "websockets-14.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:f8a86a269759026d2bde227652b87be79f8a734e582debf64c9d302faa1e9f03"}, - {file = "websockets-14.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:86cf1aaeca909bf6815ea714d5c5736c8d6dd3a13770e885aafe062ecbd04f1f"}, - {file = "websockets-14.2-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a9b0f6c3ba3b1240f602ebb3971d45b02cc12bd1845466dd783496b3b05783a5"}, - {file = "websockets-14.2-cp313-cp313-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:669c3e101c246aa85bc8534e495952e2ca208bd87994650b90a23d745902db9a"}, - {file = "websockets-14.2-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:eabdb28b972f3729348e632ab08f2a7b616c7e53d5414c12108c29972e655b20"}, - {file = "websockets-14.2-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:2066dc4cbcc19f32c12a5a0e8cc1b7ac734e5b64ac0a325ff8353451c4b15ef2"}, - {file = "websockets-14.2-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:ab95d357cd471df61873dadf66dd05dd4709cae001dd6342edafc8dc6382f307"}, - {file = "websockets-14.2-cp313-cp313-win32.whl", hash = "sha256:a9e72fb63e5f3feacdcf5b4ff53199ec8c18d66e325c34ee4c551ca748623bbc"}, - {file = "websockets-14.2-cp313-cp313-win_amd64.whl", hash = "sha256:b439ea828c4ba99bb3176dc8d9b933392a2413c0f6b149fdcba48393f573377f"}, - {file = "websockets-14.2-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:7cd5706caec1686c5d233bc76243ff64b1c0dc445339bd538f30547e787c11fe"}, - {file = "websockets-14.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:ec607328ce95a2f12b595f7ae4c5d71bf502212bddcea528290b35c286932b12"}, - {file = "websockets-14.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:da85651270c6bfb630136423037dd4975199e5d4114cae6d3066641adcc9d1c7"}, - {file = "websockets-14.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c3ecadc7ce90accf39903815697917643f5b7cfb73c96702318a096c00aa71f5"}, - {file = "websockets-14.2-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1979bee04af6a78608024bad6dfcc0cc930ce819f9e10342a29a05b5320355d0"}, - {file = "websockets-14.2-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2dddacad58e2614a24938a50b85969d56f88e620e3f897b7d80ac0d8a5800258"}, - {file = "websockets-14.2-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:89a71173caaf75fa71a09a5f614f450ba3ec84ad9fca47cb2422a860676716f0"}, - {file = "websockets-14.2-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:6af6a4b26eea4fc06c6818a6b962a952441e0e39548b44773502761ded8cc1d4"}, - {file = "websockets-14.2-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:80c8efa38957f20bba0117b48737993643204645e9ec45512579132508477cfc"}, - {file = "websockets-14.2-cp39-cp39-win32.whl", hash = "sha256:2e20c5f517e2163d76e2729104abc42639c41cf91f7b1839295be43302713661"}, - {file = "websockets-14.2-cp39-cp39-win_amd64.whl", hash = "sha256:b4c8cef610e8d7c70dea92e62b6814a8cd24fbd01d7103cc89308d2bfe1659ef"}, - {file = "websockets-14.2-pp310-pypy310_pp73-macosx_10_15_x86_64.whl", hash = "sha256:d7d9cafbccba46e768be8a8ad4635fa3eae1ffac4c6e7cb4eb276ba41297ed29"}, - {file = "websockets-14.2-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:c76193c1c044bd1e9b3316dcc34b174bbf9664598791e6fb606d8d29000e070c"}, - {file = "websockets-14.2-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:fd475a974d5352390baf865309fe37dec6831aafc3014ffac1eea99e84e83fc2"}, - {file = "websockets-14.2-pp310-pypy310_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:2c6c0097a41968b2e2b54ed3424739aab0b762ca92af2379f152c1aef0187e1c"}, - {file = "websockets-14.2-pp310-pypy310_pp73-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6d7ff794c8b36bc402f2e07c0b2ceb4a2424147ed4785ff03e2a7af03711d60a"}, - {file = "websockets-14.2-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:dec254fcabc7bd488dab64846f588fc5b6fe0d78f641180030f8ea27b76d72c3"}, - {file = "websockets-14.2-pp39-pypy39_pp73-macosx_10_15_x86_64.whl", hash = "sha256:bbe03eb853e17fd5b15448328b4ec7fb2407d45fb0245036d06a3af251f8e48f"}, - {file = "websockets-14.2-pp39-pypy39_pp73-macosx_11_0_arm64.whl", hash = "sha256:a3c4aa3428b904d5404a0ed85f3644d37e2cb25996b7f096d77caeb0e96a3b42"}, - {file = "websockets-14.2-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:577a4cebf1ceaf0b65ffc42c54856214165fb8ceeba3935852fc33f6b0c55e7f"}, - {file = "websockets-14.2-pp39-pypy39_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ad1c1d02357b7665e700eca43a31d52814ad9ad9b89b58118bdabc365454b574"}, - {file = "websockets-14.2-pp39-pypy39_pp73-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f390024a47d904613577df83ba700bd189eedc09c57af0a904e5c39624621270"}, - {file = "websockets-14.2-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:3c1426c021c38cf92b453cdf371228d3430acd775edee6bac5a4d577efc72365"}, - {file = "websockets-14.2-py3-none-any.whl", hash = "sha256:7a6ceec4ea84469f15cf15807a747e9efe57e369c384fa86e022b3bea679b79b"}, - {file = "websockets-14.2.tar.gz", hash = "sha256:5059ed9c54945efb321f097084b4c7e52c246f2c869815876a69d1efc4ad6eb5"}, + {file = "websockets-15.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:5e6ee18a53dd5743e6155b8ff7e8e477c25b29b440f87f65be8165275c87fef0"}, + {file = "websockets-15.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:ee06405ea2e67366a661ed313e14cf2a86e84142a3462852eb96348f7219cee3"}, + {file = "websockets-15.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:8711682a629bbcaf492f5e0af72d378e976ea1d127a2d47584fa1c2c080b436b"}, + {file = "websockets-15.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:94c4a9b01eede952442c088d415861b0cf2053cbd696b863f6d5022d4e4e2453"}, + {file = "websockets-15.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:45535fead66e873f411c1d3cf0d3e175e66f4dd83c4f59d707d5b3e4c56541c4"}, + {file = "websockets-15.0-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0e389efe46ccb25a1f93d08c7a74e8123a2517f7b7458f043bd7529d1a63ffeb"}, + {file = "websockets-15.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:67a04754d121ea5ca39ddedc3f77071651fb5b0bc6b973c71c515415b44ed9c5"}, + {file = "websockets-15.0-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:bd66b4865c8b853b8cca7379afb692fc7f52cf898786537dfb5e5e2d64f0a47f"}, + {file = "websockets-15.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:a4cc73a6ae0a6751b76e69cece9d0311f054da9b22df6a12f2c53111735657c8"}, + {file = "websockets-15.0-cp310-cp310-win32.whl", hash = "sha256:89da58e4005e153b03fe8b8794330e3f6a9774ee9e1c3bd5bc52eb098c3b0c4f"}, + {file = "websockets-15.0-cp310-cp310-win_amd64.whl", hash = "sha256:4ff380aabd7a74a42a760ee76c68826a8f417ceb6ea415bd574a035a111fd133"}, + {file = "websockets-15.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:dd24c4d256558429aeeb8d6c24ebad4e982ac52c50bc3670ae8646c181263965"}, + {file = "websockets-15.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:f83eca8cbfd168e424dfa3b3b5c955d6c281e8fc09feb9d870886ff8d03683c7"}, + {file = "websockets-15.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:4095a1f2093002c2208becf6f9a178b336b7572512ee0a1179731acb7788e8ad"}, + {file = "websockets-15.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:fb915101dfbf318486364ce85662bb7b020840f68138014972c08331458d41f3"}, + {file = "websockets-15.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:45d464622314973d78f364689d5dbb9144e559f93dca11b11af3f2480b5034e1"}, + {file = "websockets-15.0-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ace960769d60037ca9625b4c578a6f28a14301bd2a1ff13bb00e824ac9f73e55"}, + {file = "websockets-15.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:c7cd4b1015d2f60dfe539ee6c95bc968d5d5fad92ab01bb5501a77393da4f596"}, + {file = "websockets-15.0-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:4f7290295794b5dec470867c7baa4a14182b9732603fd0caf2a5bf1dc3ccabf3"}, + {file = "websockets-15.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:3abd670ca7ce230d5a624fd3d55e055215d8d9b723adee0a348352f5d8d12ff4"}, + {file = "websockets-15.0-cp311-cp311-win32.whl", hash = "sha256:110a847085246ab8d4d119632145224d6b49e406c64f1bbeed45c6f05097b680"}, + {file = "websockets-15.0-cp311-cp311-win_amd64.whl", hash = "sha256:8d7bbbe2cd6ed80aceef2a14e9f1c1b61683194c216472ed5ff33b700e784e37"}, + {file = "websockets-15.0-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:cccc18077acd34c8072578394ec79563664b1c205f7a86a62e94fafc7b59001f"}, + {file = "websockets-15.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:d4c22992e24f12de340ca5f824121a5b3e1a37ad4360b4e1aaf15e9d1c42582d"}, + {file = "websockets-15.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:1206432cc6c644f6fc03374b264c5ff805d980311563202ed7fef91a38906276"}, + {file = "websockets-15.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5d3cc75ef3e17490042c47e0523aee1bcc4eacd2482796107fd59dd1100a44bc"}, + {file = "websockets-15.0-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b89504227a5311610e4be16071465885a0a3d6b0e82e305ef46d9b064ce5fb72"}, + {file = "websockets-15.0-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:56e3efe356416bc67a8e093607315951d76910f03d2b3ad49c4ade9207bf710d"}, + {file = "websockets-15.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:0f2205cdb444a42a7919690238fb5979a05439b9dbb73dd47c863d39640d85ab"}, + {file = "websockets-15.0-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:aea01f40995fa0945c020228ab919b8dfc93fc8a9f2d3d705ab5b793f32d9e99"}, + {file = "websockets-15.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:a9f8e33747b1332db11cf7fcf4a9512bef9748cb5eb4d3f7fbc8c30d75dc6ffc"}, + {file = "websockets-15.0-cp312-cp312-win32.whl", hash = "sha256:32e02a2d83f4954aa8c17e03fe8ec6962432c39aca4be7e8ee346b05a3476904"}, + {file = "websockets-15.0-cp312-cp312-win_amd64.whl", hash = "sha256:ffc02b159b65c05f2ed9ec176b715b66918a674bd4daed48a9a7a590dd4be1aa"}, + {file = "websockets-15.0-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:d2244d8ab24374bed366f9ff206e2619345f9cd7fe79aad5225f53faac28b6b1"}, + {file = "websockets-15.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:3a302241fbe825a3e4fe07666a2ab513edfdc6d43ce24b79691b45115273b5e7"}, + {file = "websockets-15.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:10552fed076757a70ba2c18edcbc601c7637b30cdfe8c24b65171e824c7d6081"}, + {file = "websockets-15.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c53f97032b87a406044a1c33d1e9290cc38b117a8062e8a8b285175d7e2f99c9"}, + {file = "websockets-15.0-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1caf951110ca757b8ad9c4974f5cac7b8413004d2f29707e4d03a65d54cedf2b"}, + {file = "websockets-15.0-cp313-cp313-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8bf1ab71f9f23b0a1d52ec1682a3907e0c208c12fef9c3e99d2b80166b17905f"}, + {file = "websockets-15.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:bfcd3acc1a81f106abac6afd42327d2cf1e77ec905ae11dc1d9142a006a496b6"}, + {file = "websockets-15.0-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:c8c5c8e1bac05ef3c23722e591ef4f688f528235e2480f157a9cfe0a19081375"}, + {file = "websockets-15.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:86bfb52a9cfbcc09aba2b71388b0a20ea5c52b6517c0b2e316222435a8cdab72"}, + {file = "websockets-15.0-cp313-cp313-win32.whl", hash = "sha256:26ba70fed190708551c19a360f9d7eca8e8c0f615d19a574292b7229e0ae324c"}, + {file = "websockets-15.0-cp313-cp313-win_amd64.whl", hash = "sha256:ae721bcc8e69846af00b7a77a220614d9b2ec57d25017a6bbde3a99473e41ce8"}, + {file = "websockets-15.0-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:c348abc5924caa02a62896300e32ea80a81521f91d6db2e853e6b1994017c9f6"}, + {file = "websockets-15.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:5294fcb410ed0a45d5d1cdedc4e51a60aab5b2b3193999028ea94afc2f554b05"}, + {file = "websockets-15.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:c24ba103ecf45861e2e1f933d40b2d93f5d52d8228870c3e7bf1299cd1cb8ff1"}, + {file = "websockets-15.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:cc8821a03bcfb36e4e4705316f6b66af28450357af8a575dc8f4b09bf02a3dee"}, + {file = "websockets-15.0-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ffc5ae23ada6515f31604f700009e2df90b091b67d463a8401c1d8a37f76c1d7"}, + {file = "websockets-15.0-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7ac67b542505186b3bbdaffbc303292e1ee9c8729e5d5df243c1f20f4bb9057e"}, + {file = "websockets-15.0-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:c86dc2068f1c5ca2065aca34f257bbf4f78caf566eb230f692ad347da191f0a1"}, + {file = "websockets-15.0-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:30cff3ef329682b6182c01c568f551481774c476722020b8f7d0daacbed07a17"}, + {file = "websockets-15.0-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:98dcf978d4c6048965d1762abd534c9d53bae981a035bfe486690ba11f49bbbb"}, + {file = "websockets-15.0-cp39-cp39-win32.whl", hash = "sha256:37d66646f929ae7c22c79bc73ec4074d6db45e6384500ee3e0d476daf55482a9"}, + {file = "websockets-15.0-cp39-cp39-win_amd64.whl", hash = "sha256:24d5333a9b2343330f0f4eb88546e2c32a7f5c280f8dd7d3cc079beb0901781b"}, + {file = "websockets-15.0-pp310-pypy310_pp73-macosx_10_15_x86_64.whl", hash = "sha256:b499caef4bca9cbd0bd23cd3386f5113ee7378094a3cb613a2fa543260fe9506"}, + {file = "websockets-15.0-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:17f2854c6bd9ee008c4b270f7010fe2da6c16eac5724a175e75010aacd905b31"}, + {file = "websockets-15.0-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:89f72524033abbfde880ad338fd3c2c16e31ae232323ebdfbc745cbb1b3dcc03"}, + {file = "websockets-15.0-pp310-pypy310_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1657a9eecb29d7838e3b415458cc494e6d1b194f7ac73a34aa55c6fb6c72d1f3"}, + {file = "websockets-15.0-pp310-pypy310_pp73-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e413352a921f5ad5d66f9e2869b977e88d5103fc528b6deb8423028a2befd842"}, + {file = "websockets-15.0-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:8561c48b0090993e3b2a54db480cab1d23eb2c5735067213bb90f402806339f5"}, + {file = "websockets-15.0-pp39-pypy39_pp73-macosx_10_15_x86_64.whl", hash = "sha256:190bc6ef8690cd88232a038d1b15714c258f79653abad62f7048249b09438af3"}, + {file = "websockets-15.0-pp39-pypy39_pp73-macosx_11_0_arm64.whl", hash = "sha256:327adab7671f3726b0ba69be9e865bba23b37a605b585e65895c428f6e47e766"}, + {file = "websockets-15.0-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2bd8ef197c87afe0a9009f7a28b5dc613bfc585d329f80b7af404e766aa9e8c7"}, + {file = "websockets-15.0-pp39-pypy39_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:789c43bf4a10cd067c24c321238e800b8b2716c863ddb2294d2fed886fa5a689"}, + {file = "websockets-15.0-pp39-pypy39_pp73-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7394c0b7d460569c9285fa089a429f58465db930012566c03046f9e3ab0ed181"}, + {file = "websockets-15.0-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:2ea4f210422b912ebe58ef0ad33088bc8e5c5ff9655a8822500690abc3b1232d"}, + {file = "websockets-15.0-py3-none-any.whl", hash = "sha256:51ffd53c53c4442415b613497a34ba0aa7b99ac07f1e4a62db5dcd640ae6c3c3"}, + {file = "websockets-15.0.tar.gz", hash = "sha256:ca36151289a15b39d8d683fd8b7abbe26fc50be311066c5f8dcf3cb8cee107ab"}, ] [[package]] @@ -2697,4 +2633,4 @@ type = ["pytest-mypy"] [metadata] lock-version = "2.1" python-versions = "^3.10" -content-hash = "b89b458ed0f91e834c027be0d2541d464e97d65aaf56b0454c1ed07b004828e1" +content-hash = "32f3b67d60393e53e28f1c3856b0c6f5c32ea538d4ae6cae1847cdd37a001ceb" diff --git a/pyproject.toml b/pyproject.toml index 11a4e50151..82e0129586 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -7,16 +7,16 @@ requires-poetry = ">=2.0.0" [tool.poetry.dependencies] python = "^3.10" -ops = "^2.17.1" -boto3 = "^1.35.87" +ops = "^2.18.1" +boto3 = "^1.35.99" pgconnstr = "^1.0.1" requests = "^2.32.3" tenacity = "^9.0.0" psycopg2 = "^2.9.10" pydantic = "^1.10.21" jinja2 = "^3.1.5" -pysyncobj = "^0.3.13" -psutil = "^6.1.1" +pysyncobj = "^0.3.14" +psutil = "^7.0.0" [tool.poetry.group.charm-libs.dependencies] # data_platform_libs/v0/data_interfaces.py @@ -27,7 +27,7 @@ poetry-core = "*" # data_platform_libs/v0/data_models.py requires pydantic ^1.10 pydantic = "^1.10" # grafana_agent/v0/cos_agent.py -cosl = "*" +cosl = ">=0.0.50" # tls_certificates_interface/v2/tls_certificates.py cryptography = "*" jsonschema = "*" @@ -38,19 +38,19 @@ opentelemetry-exporter-otlp-proto-http = "1.21.0" optional = true [tool.poetry.group.format.dependencies] -ruff = "^0.9.3" +ruff = "^0.9.6" [tool.poetry.group.lint] optional = true [tool.poetry.group.lint.dependencies] -codespell = "^2.4.0" +codespell = "^2.4.1" [tool.poetry.group.unit] optional = true [tool.poetry.group.unit.dependencies] -coverage = {extras = ["toml"], version = "^7.6.10"} +coverage = {extras = ["toml"], version = "^7.6.12"} pytest = "^8.3.4" pytest-asyncio = "*" parameterized = "^0.9.0" @@ -61,10 +61,7 @@ optional = true [tool.poetry.group.integration.dependencies] pytest = "^8.3.4" -pytest-github-secrets = {git = "https://github.com/canonical/data-platform-workflows", tag = "v29.1.0", subdirectory = "python/pytest_plugins/github_secrets"} -pytest-operator = "^0.39.0" -pytest-operator-cache = {git = "https://github.com/canonical/data-platform-workflows", tag = "v29.1.0", subdirectory = "python/pytest_plugins/pytest_operator_cache"} -pytest-operator-groups = {git = "https://github.com/canonical/data-platform-workflows", tag = "v29.1.0", subdirectory = "python/pytest_plugins/pytest_operator_groups"} +pytest-operator = "^0.40.0" # renovate caret doesn't work: https://github.com/renovatebot/renovate/issues/26940 juju = "<=3.6.1.0" boto3 = "*" @@ -73,7 +70,7 @@ landscape-api-py3 = "^0.9.0" mailmanclient = "^3.3.5" psycopg2-binary = "^2.9.10" allure-pytest = "^2.13.5" -allure-pytest-collection-report = {git = "https://github.com/canonical/data-platform-workflows", tag = "v29.1.0", subdirectory = "python/pytest_plugins/allure_pytest_collection_report"} +allure-pytest-default-results = "^0.1.2" # Testing tools configuration [tool.coverage.run] @@ -89,7 +86,7 @@ exclude_lines = [ minversion = "6.0" log_cli_level = "INFO" asyncio_mode = "auto" -markers = ["unstable", "juju2", "juju3", "juju_secrets"] +markers = ["juju2", "juju3", "juju_secrets"] # Formatting tools configuration [tool.black] diff --git a/spread.yaml b/spread.yaml new file mode 100644 index 0000000000..8e1352e7b3 --- /dev/null +++ b/spread.yaml @@ -0,0 +1,128 @@ +project: postgresql-operator + +backends: + # Derived from https://github.com/jnsgruk/zinc-k8s-operator/blob/a21eae8399eb3b9df4ddb934b837af25ef831976/spread.yaml#L11 + lxd-vm: + # TODO: remove after https://github.com/canonical/spread/pull/185 merged & in charmcraft + type: adhoc + allocate: | + hash=$(python3 -c "import hashlib; print(hashlib.sha256('$SPREAD_PASSWORD'.encode()).hexdigest()[:6])") + VM_NAME="${VM_NAME:-${SPREAD_SYSTEM//./-}-${hash}}" + DISK="${DISK:-20}" + CPU="${CPU:-4}" + MEM="${MEM:-8}" + + cloud_config="#cloud-config + ssh_pwauth: true + users: + - default + - name: runner + plain_text_passwd: $SPREAD_PASSWORD + lock_passwd: false + sudo: ALL=(ALL) NOPASSWD:ALL + " + + lxc launch --vm \ + "${SPREAD_SYSTEM//-/:}" \ + "${VM_NAME}" \ + -c user.user-data="${cloud_config}" \ + -c limits.cpu="${CPU}" \ + -c limits.memory="${MEM}GiB" \ + -d root,size="${DISK}GiB" + + # Wait for the runner user + while ! lxc exec "${VM_NAME}" -- id -u runner &>/dev/null; do sleep 0.5; done + + # Set the instance address for spread + ADDRESS "$(lxc ls -f csv | grep "${VM_NAME}" | cut -d"," -f3 | cut -d" " -f1)" + discard: | + hash=$(python3 -c "import hashlib; print(hashlib.sha256('$SPREAD_PASSWORD'.encode()).hexdigest()[:6])") + VM_NAME="${VM_NAME:-${SPREAD_SYSTEM//./-}-${hash}}" + lxc delete --force "${VM_NAME}" + environment: + CONCIERGE_EXTRA_SNAPS: charmcraft + CONCIERGE_EXTRA_DEBS: pipx + systems: + - ubuntu-24.04: + username: runner + prepare: | + systemctl disable --now unattended-upgrades.service + systemctl mask unattended-upgrades.service + pipx install charmcraftcache + cd "$SPREAD_PATH" + charmcraftcache pack -v + restore-each: | + cd "$SPREAD_PATH" + # Revert python-libjuju version override + git restore pyproject.toml poetry.lock + + # Use instead of `concierge restore` to save time between tests + # For example, with microk8s, using `concierge restore` takes twice as long as this (e.g. 6 + # min instead of 3 min between every spread job) + juju destroy-model --force --no-wait --destroy-storage --no-prompt testing + juju kill-controller --no-prompt concierge-lxd + restore: | + rm -rf "$SPREAD_PATH" + + github-ci: + type: adhoc + # Only run on CI + manual: true + # HACK: spread requires runners to be accessible via SSH + # Configure local sshd & instruct spread to connect to the same machine spread is running on + # (spread cannot provision GitHub Actions runners, so we provision a GitHub Actions runner for + # each spread job & select a single job when running spread) + # Derived from https://github.com/jnsgruk/zinc-k8s-operator/blob/a21eae8399eb3b9df4ddb934b837af25ef831976/spread.yaml#L47 + allocate: | + sudo tee /etc/ssh/sshd_config.d/10-spread-github-ci.conf << 'EOF' + PasswordAuthentication yes + PermitEmptyPasswords yes + EOF + + ADDRESS localhost + # HACK: spread does not pass environment variables set on runner + # Manually pass specific environment variables + environment: + CI: '$(HOST: echo $CI)' + AWS_ACCESS_KEY: '$(HOST: echo $AWS_ACCESS_KEY)' + AWS_SECRET_KEY: '$(HOST: echo $AWS_SECRET_KEY)' + GCP_ACCESS_KEY: '$(HOST: echo $GCP_ACCESS_KEY)' + GCP_SECRET_KEY: '$(HOST: echo $GCP_SECRET_KEY)' + UBUNTU_PRO_TOKEN: '$(HOST: echo $UBUNTU_PRO_TOKEN)' + LANDSCAPE_ACCOUNT_NAME: '$(HOST: echo $LANDSCAPE_ACCOUNT_NAME)' + LANDSCAPE_REGISTRATION_KEY: '$(HOST: echo $LANDSCAPE_REGISTRATION_KEY)' + systems: + - ubuntu-24.04: + username: runner + - ubuntu-24.04-arm: + username: runner + +suites: + tests/spread/: + summary: Spread tests + +path: /root/spread_project + +kill-timeout: 3h +environment: + PATH: $PATH:$(pipx environment --value PIPX_BIN_DIR) + CONCIERGE_JUJU_CHANNEL/juju36: 3.6/stable +prepare: | + snap refresh --hold + chown -R root:root "$SPREAD_PATH" + cd "$SPREAD_PATH" + snap install --classic concierge + + # Install charmcraft & pipx (on lxd-vm backend) + concierge prepare --trace + + pipx install tox poetry +prepare-each: | + cd "$SPREAD_PATH" + # `concierge prepare` needs to be run for each spread job in case Juju version changed + concierge prepare --trace + + # Unable to set constraint on all models because of Juju bug: + # https://bugs.launchpad.net/juju/+bug/2065050 + juju set-model-constraints arch="$(dpkg --print-architecture)" +# Only restore on lxd backend—no need to restore on CI diff --git a/src/backups.py b/src/backups.py index 214fe7f082..903ba028f6 100644 --- a/src/backups.py +++ b/src/backups.py @@ -212,7 +212,10 @@ def can_use_s3_repository(self) -> tuple[bool, str | None]: for line in system_identifier_from_instance.splitlines() if "Database system identifier" in line ).split(" ")[-1] - system_identifier_from_stanza = str(stanza.get("db")[0]["system-id"]) + stanza_dbs = stanza.get("db") + system_identifier_from_stanza = ( + str(stanza_dbs[0]["system-id"]) if len(stanza_dbs) else None + ) if system_identifier_from_instance != system_identifier_from_stanza: logger.debug( f"can_use_s3_repository: incompatible system identifier s3={system_identifier_from_stanza}, local={system_identifier_from_instance}" diff --git a/src/charm.py b/src/charm.py index bdfd56b3b6..00437c4e0e 100755 --- a/src/charm.py +++ b/src/charm.py @@ -61,6 +61,7 @@ Patroni, RemoveRaftMemberFailedError, SwitchoverFailedError, + SwitchoverNotSyncError, ) from cluster_topology_observer import ( ClusterTopologyChangeCharmEvents, @@ -70,6 +71,7 @@ from constants import ( APP_SCOPE, BACKUP_USER, + DATABASE_DEFAULT_NAME, METRICS_PORT, MONITORING_PASSWORD_KEY, MONITORING_SNAP_SERVICE, @@ -181,7 +183,6 @@ def __init__(self, *args): self.framework.observe(self.on[PEER].relation_changed, self._on_peer_relation_changed) self.framework.observe(self.on.secret_changed, self._on_peer_relation_changed) self.framework.observe(self.on[PEER].relation_departed, self._on_peer_relation_departed) - self.framework.observe(self.on.pgdata_storage_detaching, self._on_pgdata_storage_detaching) self.framework.observe(self.on.start, self._on_start) self.framework.observe(self.on.get_password_action, self._on_get_password) self.framework.observe(self.on.set_password_action, self._on_set_password) @@ -294,8 +295,6 @@ def peer_relation_data(self, scope: Scopes) -> DataPeerData: def _translate_field_to_secret_key(self, key: str) -> str: """Change 'key' to secrets-compatible key field.""" - if not JujuVersion.from_environ().has_secrets: - return key key = SECRET_KEY_OVERRIDES.get(key, key) new_key = key.replace("_", "-") return new_key.strip("-") @@ -308,9 +307,6 @@ def get_secret(self, scope: Scopes, key: str) -> str | None: if not (peers := self.model.get_relation(PEER)): return None secret_key = self._translate_field_to_secret_key(key) - # Old translation in databag is to be taken - if result := self.peer_relation_data(scope).fetch_my_relation_field(peers.id, key): - return result return self.peer_relation_data(scope).get_secret(peers.id, secret_key) @@ -325,8 +321,6 @@ def set_secret(self, scope: Scopes, key: str, value: str | None) -> str | None: if not (peers := self.model.get_relation(PEER)): return None secret_key = self._translate_field_to_secret_key(key) - # Old translation in databag is to be deleted - self.scoped_peer_data(scope).pop(key, None) self.peer_relation_data(scope).set_secret(peers.id, secret_key, value) def remove_secret(self, scope: Scopes, key: str) -> None: @@ -373,7 +367,7 @@ def postgresql(self) -> PostgreSQL: current_host=self._unit_ip, user=USER, password=self.get_secret(APP_SCOPE, f"{USER}-password"), - database="postgres", + database=DATABASE_DEFAULT_NAME, system_users=SYSTEM_USERS, ) @@ -431,10 +425,10 @@ def _on_get_primary(self, event: ActionEvent) -> None: except RetryError as e: logger.error(f"failed to get primary with error {e}") - def _updated_synchronous_node_count(self, num_units: int | None = None) -> bool: + def updated_synchronous_node_count(self) -> bool: """Tries to update synchronous_node_count configuration and reports the result.""" try: - self._patroni.update_synchronous_node_count(num_units) + self._patroni.update_synchronous_node_count() return True except RetryError: logger.debug("Unable to set synchronous_node_count") @@ -472,9 +466,7 @@ def _on_peer_relation_departed(self, event: RelationDepartedEvent) -> None: if not self.unit.is_leader(): return - if not self.is_cluster_initialised or not self._updated_synchronous_node_count( - len(self._units_ips) - ): + if not self.is_cluster_initialised or not self.updated_synchronous_node_count(): logger.debug("Deferring on_peer_relation_departed: cluster not initialized") event.defer() return @@ -500,52 +492,6 @@ def _on_peer_relation_departed(self, event: RelationDepartedEvent) -> None: # Update the sync-standby endpoint in the async replication data. self.async_replication.update_async_replication_data() - def _on_pgdata_storage_detaching(self, _) -> None: - # Change the primary if it's the unit that is being removed. - try: - primary = self._patroni.get_primary(unit_name_pattern=True) - except RetryError: - # Ignore the event if the primary couldn't be retrieved. - # If a switchover is needed, an automatic failover will be triggered - # when the unit is removed. - logger.debug("Early exit on_pgdata_storage_detaching: primary cannot be retrieved") - return - - if self.unit.name != primary: - return - - if not self._patroni.are_all_members_ready(): - logger.warning( - "could not switchover because not all members are ready" - " - an automatic failover will be triggered" - ) - return - - # Try to switchover to another member and raise an exception if it doesn't succeed. - # If it doesn't happen on time, Patroni will automatically run a fail-over. - try: - # Get the current primary to check if it has changed later. - current_primary = self._patroni.get_primary() - - # Trigger the switchover. - self._patroni.switchover() - - # Wait for the switchover to complete. - self._patroni.primary_changed(current_primary) - - logger.info("successful switchover") - except (RetryError, SwitchoverFailedError) as e: - logger.warning( - f"switchover failed with reason: {e} - an automatic failover will be triggered" - ) - return - - # Only update the connection endpoints if there is a primary. - # A cluster can have all members as replicas for some time after - # a failed switchover, so wait until the primary is elected. - if self.primary_endpoint: - self._update_relation_endpoints() - def _stuck_raft_cluster_check(self) -> None: """Check for stuck raft cluster and reinitialise if safe.""" raft_stuck = False @@ -1183,6 +1129,11 @@ def _on_config_changed(self, event) -> None: logger.error("Invalid configuration: %s", str(e)) return + if not self.updated_synchronous_node_count(): + logger.debug("Defer on_config_changed: unable to set synchronous node count") + event.defer() + return + if self.is_blocked and "Configuration Error" in self.unit.status.message: self.unit.status = ActiveStatus() @@ -1194,7 +1145,9 @@ def _on_config_changed(self, event) -> None: # Enable and/or disable the extensions. self.enable_disable_extensions() + self._unblock_extensions() + def _unblock_extensions(self) -> None: # Unblock the charm after extensions are enabled (only if it's blocked due to application # charms requesting extensions). if self.unit.status.message != EXTENSIONS_BLOCKING_MESSAGE: @@ -1393,7 +1346,7 @@ def _start_primary(self, event: StartEvent) -> None: self.postgresql.create_user( MONITORING_USER, self.get_secret(APP_SCOPE, MONITORING_PASSWORD_KEY), - extra_user_roles="pg_monitor", + extra_user_roles=["pg_monitor"], ) except PostgreSQLCreateUserError as e: logger.exception(e) @@ -1552,8 +1505,10 @@ def promote_primary_unit(self, event: ActionEvent) -> None: return try: self._patroni.switchover(self._member_name) - except SwitchoverFailedError: + except SwitchoverNotSyncError: event.fail("Unit is not sync standby") + except SwitchoverFailedError: + event.fail("Switchover failed or timed out, check the logs for details") def _on_update_status(self, _) -> None: """Update the unit status message and users list in the database.""" diff --git a/src/cluster.py b/src/cluster.py index 04a07d8aff..6d227cda26 100644 --- a/src/cluster.py +++ b/src/cluster.py @@ -94,6 +94,10 @@ class SwitchoverFailedError(Exception): """Raised when a switchover failed for some reason.""" +class SwitchoverNotSyncError(SwitchoverFailedError): + """Raised when a switchover failed because node is not sync.""" + + class UpdateSyncNodeCountError(Exception): """Raised when updating synchronous_node_count failed for some reason.""" @@ -668,7 +672,7 @@ def render_patroni_yml_file( stanza=stanza, restore_stanza=restore_stanza, version=self.get_postgresql_version().split(".")[0], - minority_count=self.planned_units // 2, + synchronous_node_count=self._synchronous_node_count, pg_parameters=parameters, primary_cluster_endpoint=self.charm.async_replication.get_primary_cluster_endpoint(), extra_replication_endpoints=self.charm.async_replication.get_standby_endpoints(), @@ -766,6 +770,13 @@ def switchover(self, candidate: str | None = None) -> None: # Check whether the switchover was unsuccessful. if r.status_code != 200: + if ( + r.status_code == 412 + and r.text == "candidate name does not match with sync_standby" + ): + logger.debug("Unit is not sync standby") + raise SwitchoverNotSyncError() + logger.warning(f"Switchover call failed with code {r.status_code} {r.text}") raise SwitchoverFailedError(f"received {r.status_code}") @retry( @@ -915,6 +926,7 @@ def remove_raft_member(self, member_ip: str) -> None: raise RemoveRaftMemberFailedError() from None if not result.startswith("SUCCESS"): + logger.debug("Remove raft member: Remove call not successful") raise RemoveRaftMemberFailedError() @retry(stop=stop_after_attempt(20), wait=wait_exponential(multiplier=1, min=2, max=10)) @@ -977,16 +989,28 @@ def bulk_update_parameters_controller_by_patroni(self, parameters: dict[str, Any timeout=PATRONI_TIMEOUT, ) - def update_synchronous_node_count(self, units: int | None = None) -> None: + @property + def _synchronous_node_count(self) -> int: + planned_units = self.charm.app.planned_units() + if self.charm.config.synchronous_node_count == "all": + return planned_units - 1 + elif self.charm.config.synchronous_node_count == "majority": + return planned_units // 2 + # -1 for leader + return ( + self.charm.config.synchronous_node_count + if self.charm.config.synchronous_node_count < planned_units - 1 + else planned_units - 1 + ) + + def update_synchronous_node_count(self) -> None: """Update synchronous_node_count to the minority of the planned cluster.""" - if units is None: - units = self.planned_units # Try to update synchronous_node_count. for attempt in Retrying(stop=stop_after_delay(60), wait=wait_fixed(3)): with attempt: r = requests.patch( f"{self._patroni_url}/config", - json={"synchronous_node_count": units // 2}, + json={"synchronous_node_count": self._synchronous_node_count}, verify=self.verify, auth=self._patroni_auth, timeout=PATRONI_TIMEOUT, diff --git a/src/config.py b/src/config.py index 45b7b04566..6306243df2 100644 --- a/src/config.py +++ b/src/config.py @@ -8,7 +8,7 @@ from typing import Literal from charms.data_platform_libs.v0.data_models import BaseConfigModel -from pydantic import validator +from pydantic import PositiveInt, validator from locales import SNAP_LOCALES @@ -18,6 +18,7 @@ class CharmConfig(BaseConfigModel): """Manager for the structured configuration.""" + synchronous_node_count: Literal["all", "majority"] | PositiveInt durability_synchronous_commit: str | None instance_default_text_search_config: str | None instance_max_locks_per_transaction: int | None diff --git a/src/constants.py b/src/constants.py index 6de25f4cd6..a16bd5ab70 100644 --- a/src/constants.py +++ b/src/constants.py @@ -6,6 +6,7 @@ BACKUP_ID_FORMAT = "%Y-%m-%dT%H:%M:%SZ" PGBACKREST_BACKUP_ID_FORMAT = "%Y%m%d-%H%M%S" DATABASE = "database" +DATABASE_DEFAULT_NAME = "postgres" DATABASE_PORT = "5432" LEGACY_DB = "db" LEGACY_DB_ADMIN = "db-admin" @@ -34,7 +35,7 @@ SNAP_PACKAGES = [ ( POSTGRESQL_SNAP_NAME, - {"revision": {"aarch64": "138", "x86_64": "139"}}, + {"revision": {"aarch64": "158", "x86_64": "159"}}, ) ] diff --git a/src/relations/postgresql_provider.py b/src/relations/postgresql_provider.py index 487c6fb9e5..0f2fa040c1 100644 --- a/src/relations/postgresql_provider.py +++ b/src/relations/postgresql_provider.py @@ -65,6 +65,14 @@ def __init__(self, charm: CharmBase, relation_name: str = "database") -> None: self.database_provides.on.database_requested, self._on_database_requested ) + @staticmethod + def _sanitize_extra_roles(extra_roles: str | None) -> list[str]: + """Standardize and sanitize user extra-roles.""" + if extra_roles is None: + return [] + + return [role.lower() for role in extra_roles.split(",")] + def _on_database_requested(self, event: DatabaseRequestedEvent) -> None: """Generate password and handle user and database creation for the related application.""" # Check for some conditions before trying to access the PostgreSQL instance. @@ -84,7 +92,9 @@ def _on_database_requested(self, event: DatabaseRequestedEvent) -> None: # Retrieve the database name and extra user roles using the charm library. database = event.database - extra_user_roles = event.extra_user_roles + + # Make sure that certain groups are not in the list + extra_user_roles = self._sanitize_extra_roles(event.extra_user_roles) try: # Creates the user and the database for this specific relation. @@ -275,9 +285,7 @@ def check_for_invalid_extra_user_roles(self, relation_id: int) -> bool: continue for data in relation.data.values(): extra_user_roles = data.get("extra-user-roles") - if extra_user_roles is None: - continue - extra_user_roles = extra_user_roles.lower().split(",") + extra_user_roles = self._sanitize_extra_roles(extra_user_roles) for extra_user_role in extra_user_roles: if ( extra_user_role not in valid_privileges diff --git a/src/upgrade.py b/src/upgrade.py index 3f7c183e16..d2de69bfde 100644 --- a/src/upgrade.py +++ b/src/upgrade.py @@ -146,6 +146,7 @@ def _on_upgrade_granted(self, event: UpgradeGrantedEvent) -> None: # Update the configuration. self.charm.unit.status = MaintenanceStatus("updating configuration") self.charm.update_config() + self.charm.updated_synchronous_node_count() self.charm.unit.status = MaintenanceStatus("refreshing the snap") self.charm._install_snap_packages(packages=SNAP_PACKAGES, refresh=True) diff --git a/templates/patroni.yml.j2 b/templates/patroni.yml.j2 index 75aa7ac1ba..9de9d71d86 100644 --- a/templates/patroni.yml.j2 +++ b/templates/patroni.yml.j2 @@ -59,7 +59,7 @@ bootstrap: retry_timeout: 10 maximum_lag_on_failover: 1048576 synchronous_mode: true - synchronous_node_count: {{ minority_count }} + synchronous_node_count: {{ synchronous_node_count }} postgresql: use_pg_rewind: true remove_data_directory_on_rewind_failure: true diff --git a/tests/integration/conftest.py b/tests/integration/conftest.py index 99ceda68b0..18fd8c71e3 100644 --- a/tests/integration/conftest.py +++ b/tests/integration/conftest.py @@ -1,14 +1,96 @@ -#!/usr/bin/env python3 # Copyright 2022 Canonical Ltd. # See LICENSE file for licensing details. +import logging +import os +import uuid + +import boto3 import pytest from pytest_operator.plugin import OpsTest -from .helpers import build_charm +from . import architecture +from .helpers import construct_endpoint + +AWS = "AWS" +GCP = "GCP" + +logger = logging.getLogger(__name__) + + +@pytest.fixture(scope="session") +def charm(): + # Return str instead of pathlib.Path since python-libjuju's model.deploy(), juju deploy, and + # juju bundle files expect local charms to begin with `./` or `/` to distinguish them from + # Charmhub charms. + return f"./postgresql_ubuntu@24.04-{architecture.architecture}.charm" + + +def get_cloud_config(cloud: str) -> tuple[dict[str, str], dict[str, str]]: + # Define some configurations and credentials. + if cloud == AWS: + return { + "endpoint": "https://s3.amazonaws.com", + "bucket": "data-charms-testing", + "path": f"/postgresql-k8s/{uuid.uuid1()}", + "region": "us-east-1", + }, { + "access-key": os.environ["AWS_ACCESS_KEY"], + "secret-key": os.environ["AWS_SECRET_KEY"], + } + elif cloud == GCP: + return { + "endpoint": "https://storage.googleapis.com", + "bucket": "data-charms-testing", + "path": f"/postgresql-k8s/{uuid.uuid1()}", + "region": "", + }, { + "access-key": os.environ["GCP_ACCESS_KEY"], + "secret-key": os.environ["GCP_SECRET_KEY"], + } + + +def cleanup_cloud(config: dict[str, str], credentials: dict[str, str]) -> None: + # Delete the previously created objects. + logger.info("deleting the previously created backups") + session = boto3.session.Session( + aws_access_key_id=credentials["access-key"], + aws_secret_access_key=credentials["secret-key"], + region_name=config["region"], + ) + s3 = session.resource( + "s3", endpoint_url=construct_endpoint(config["endpoint"], config["region"]) + ) + bucket = s3.Bucket(config["bucket"]) + # GCS doesn't support batch delete operation, so delete the objects one by one. + for bucket_object in bucket.objects.filter(Prefix=config["path"].lstrip("/")): + bucket_object.delete() + + +@pytest.fixture(scope="module") +async def aws_cloud_configs(ops_test: OpsTest) -> None: + if ( + not os.environ.get("AWS_ACCESS_KEY", "").strip() + or not os.environ.get("AWS_SECRET_KEY", "").strip() + ): + pytest.skip("AWS configs not set") + return + + config, credentials = get_cloud_config(AWS) + yield config, credentials + + cleanup_cloud(config, credentials) @pytest.fixture(scope="module") -async def charm(ops_test: OpsTest): - """Build the charm-under-test.""" - # Build charm from local source folder. - yield await build_charm(".") +async def gcp_cloud_configs(ops_test: OpsTest) -> None: + if ( + not os.environ.get("GCP_ACCESS_KEY", "").strip() + or not os.environ.get("GCP_SECRET_KEY", "").strip() + ): + pytest.skip("GCP configs not set") + return + + config, credentials = get_cloud_config(GCP) + yield config, credentials + + cleanup_cloud(config, credentials) diff --git a/tests/integration/ha_tests/helpers.py b/tests/integration/ha_tests/helpers.py index 57ddac6dd9..951659ee45 100644 --- a/tests/integration/ha_tests/helpers.py +++ b/tests/integration/ha_tests/helpers.py @@ -106,7 +106,7 @@ async def are_writes_increasing( with attempt: more_writes, _ = await count_writes( ops_test, - down_unit=down_unit, + down_unit=down_units[0], use_ip_from_inside=use_ip_from_inside, extra_model=extra_model, ) diff --git a/tests/integration/ha_tests/test_async_replication.py b/tests/integration/ha_tests/test_async_replication.py index 89e8f87a67..7ac7ec4dbe 100644 --- a/tests/integration/ha_tests/test_async_replication.py +++ b/tests/integration/ha_tests/test_async_replication.py @@ -16,7 +16,6 @@ from ..helpers import ( APPLICATION_NAME, DATABASE_APP_NAME, - build_charm, get_leader_unit, get_password, get_primary, @@ -100,7 +99,6 @@ async def second_model_continuous_writes(second_model) -> None: assert action.results["result"] == "True", "Unable to clear up continuous_writes table" -@pytest.mark.group(1) @markers.juju3 @pytest.mark.abort_on_fail async def test_deploy_async_replication_setup( @@ -108,7 +106,6 @@ async def test_deploy_async_replication_setup( ) -> None: """Build and deploy two PostgreSQL cluster in two separate models to test async replication.""" if not await app_name(ops_test): - charm = await build_charm(".") await ops_test.model.deploy( charm, num_units=CLUSTER_SIZE, @@ -123,7 +120,6 @@ async def test_deploy_async_replication_setup( ) await ops_test.model.relate(DATABASE_APP_NAME, DATA_INTEGRATOR_APP_NAME) if not await app_name(ops_test, model=second_model): - charm = await build_charm(".") await second_model.deploy( charm, num_units=CLUSTER_SIZE, @@ -147,7 +143,6 @@ async def test_deploy_async_replication_setup( ) -@pytest.mark.group(1) @markers.juju3 @pytest.mark.abort_on_fail async def test_async_replication( @@ -225,7 +220,6 @@ async def test_async_replication( await check_writes(ops_test, extra_model=second_model) -@pytest.mark.group(1) @markers.juju3 @pytest.mark.abort_on_fail async def test_get_data_integrator_credentials( @@ -238,7 +232,6 @@ async def test_get_data_integrator_credentials( data_integrator_credentials = result.results -@pytest.mark.group(1) @markers.juju3 @pytest.mark.abort_on_fail async def test_switchover( @@ -270,7 +263,7 @@ async def test_switchover( leader_unit = await get_leader_unit(ops_test, DATABASE_APP_NAME, model=second_model) assert leader_unit is not None, "No leader unit found" logger.info("promoting the second cluster") - run_action = await leader_unit.run_action("promote-to-primary", **{"force": True}) + run_action = await leader_unit.run_action("promote-to-primary", scope="cluster", force=True) await run_action.wait() assert (run_action.results.get("return-code", None) == 0) or ( run_action.results.get("Code", None) == "0" @@ -293,7 +286,6 @@ async def test_switchover( await are_writes_increasing(ops_test, extra_model=second_model) -@pytest.mark.group(1) @markers.juju3 @pytest.mark.abort_on_fail async def test_data_integrator_creds_keep_on_working( @@ -316,7 +308,6 @@ async def test_data_integrator_creds_keep_on_working( connection.close() -@pytest.mark.group(1) @markers.juju3 @pytest.mark.abort_on_fail async def test_promote_standby( @@ -351,7 +342,7 @@ async def test_promote_standby( leader_unit = await get_leader_unit(ops_test, DATABASE_APP_NAME) assert leader_unit is not None, "No leader unit found" logger.info("promoting the first cluster") - run_action = await leader_unit.run_action("promote-to-primary") + run_action = await leader_unit.run_action("promote-to-primary", scope="cluster") await run_action.wait() assert (run_action.results.get("return-code", None) == 0) or ( run_action.results.get("Code", None) == "0" @@ -394,7 +385,6 @@ async def test_promote_standby( await are_writes_increasing(ops_test) -@pytest.mark.group(1) @markers.juju3 @pytest.mark.abort_on_fail async def test_reestablish_relation( @@ -452,7 +442,6 @@ async def test_reestablish_relation( await check_writes(ops_test, extra_model=second_model) -@pytest.mark.group(1) @markers.juju3 @pytest.mark.abort_on_fail async def test_async_replication_failover_in_main_cluster( @@ -498,7 +487,6 @@ async def test_async_replication_failover_in_main_cluster( await check_writes(ops_test, extra_model=second_model) -@pytest.mark.group(1) @markers.juju3 @pytest.mark.abort_on_fail async def test_async_replication_failover_in_secondary_cluster( @@ -535,7 +523,6 @@ async def test_async_replication_failover_in_secondary_cluster( await check_writes(ops_test, extra_model=second_model) -@pytest.mark.group(1) @markers.juju3 @pytest.mark.abort_on_fail async def test_scaling( diff --git a/tests/integration/ha_tests/test_replication.py b/tests/integration/ha_tests/test_replication.py index 558d5cb718..fed67a8019 100644 --- a/tests/integration/ha_tests/test_replication.py +++ b/tests/integration/ha_tests/test_replication.py @@ -6,7 +6,7 @@ from pytest_operator.plugin import OpsTest from tenacity import Retrying, stop_after_delay, wait_fixed -from ..helpers import APPLICATION_NAME, build_charm, db_connect, scale_application +from ..helpers import APPLICATION_NAME, CHARM_BASE, db_connect, scale_application from .helpers import ( app_name, are_writes_increasing, @@ -18,20 +18,19 @@ ) -@pytest.mark.group(1) @pytest.mark.abort_on_fail -async def test_build_and_deploy(ops_test: OpsTest) -> None: +async def test_build_and_deploy(ops_test: OpsTest, charm) -> None: """Build and deploy three unit of PostgreSQL.""" wait_for_apps = False # It is possible for users to provide their own cluster for HA testing. Hence, check if there # is a pre-existing cluster. if not await app_name(ops_test): wait_for_apps = True - charm = await build_charm(".") async with ops_test.fast_forward(): await ops_test.model.deploy( charm, num_units=3, + base=CHARM_BASE, config={"profile": "testing"}, ) # Deploy the continuous writes application charm if it wasn't already deployed. @@ -41,6 +40,7 @@ async def test_build_and_deploy(ops_test: OpsTest) -> None: await ops_test.model.deploy( APPLICATION_NAME, application_name=APPLICATION_NAME, + base=CHARM_BASE, channel="edge", ) @@ -49,7 +49,6 @@ async def test_build_and_deploy(ops_test: OpsTest) -> None: await ops_test.model.wait_for_idle(status="active", timeout=1500) -@pytest.mark.group(1) async def test_reelection(ops_test: OpsTest, continuous_writes, primary_start_timeout) -> None: """Kill primary unit, check reelection.""" app = await app_name(ops_test) @@ -61,9 +60,7 @@ async def test_reelection(ops_test: OpsTest, continuous_writes, primary_start_ti # Remove the primary unit. primary_name = await get_primary(ops_test, app) - await ops_test.model.destroy_units( - primary_name, - ) + await ops_test.model.destroy_units(primary_name) # Wait and get the primary again (which can be any unit, including the previous primary). async with ops_test.fast_forward(): @@ -87,7 +84,6 @@ async def test_reelection(ops_test: OpsTest, continuous_writes, primary_start_ti await check_writes(ops_test) -@pytest.mark.group(1) async def test_consistency(ops_test: OpsTest, continuous_writes) -> None: """Write to primary, read data from secondaries (check consistency).""" # Locate primary unit. @@ -104,8 +100,9 @@ async def test_consistency(ops_test: OpsTest, continuous_writes) -> None: await check_writes(ops_test) -@pytest.mark.group(1) -async def test_no_data_replicated_between_clusters(ops_test: OpsTest, continuous_writes) -> None: +async def test_no_data_replicated_between_clusters( + ops_test: OpsTest, charm, continuous_writes +) -> None: """Check that writes in one cluster are not replicated to another cluster.""" # Locate primary unit. app = await app_name(ops_test) @@ -114,12 +111,12 @@ async def test_no_data_replicated_between_clusters(ops_test: OpsTest, continuous # Deploy another cluster. new_cluster_app = f"second-{app}" if not await app_name(ops_test, new_cluster_app): - charm = await build_charm(".") async with ops_test.fast_forward(): await ops_test.model.deploy( charm, application_name=new_cluster_app, num_units=2, + base=CHARM_BASE, config={"profile": "testing"}, ) await ops_test.model.wait_for_idle( diff --git a/tests/integration/ha_tests/test_restore_cluster.py b/tests/integration/ha_tests/test_restore_cluster.py index 898aa27d62..8a26b15cb5 100644 --- a/tests/integration/ha_tests/test_restore_cluster.py +++ b/tests/integration/ha_tests/test_restore_cluster.py @@ -7,7 +7,7 @@ from pytest_operator.plugin import OpsTest from ..helpers import ( - build_charm, + CHARM_BASE, db_connect, get_password, get_patroni_cluster, @@ -29,18 +29,17 @@ charm = None -@pytest.mark.group(1) @pytest.mark.abort_on_fail -async def test_build_and_deploy(ops_test: OpsTest) -> None: +async def test_build_and_deploy(ops_test: OpsTest, charm) -> None: """Build and deploy two PostgreSQL clusters.""" # This is a potentially destructive test, so it shouldn't be run against existing clusters - charm = await build_charm(".") async with ops_test.fast_forward(): # Deploy the first cluster with reusable storage await ops_test.model.deploy( charm, application_name=FIRST_APPLICATION, num_units=3, + base=CHARM_BASE, storage={"pgdata": {"pool": "lxd-btrfs", "size": 2048}}, config={"profile": "testing"}, ) @@ -50,6 +49,7 @@ async def test_build_and_deploy(ops_test: OpsTest) -> None: charm, application_name=SECOND_APPLICATION, num_units=1, + base=CHARM_BASE, config={"profile": "testing"}, ) @@ -66,7 +66,6 @@ async def test_build_and_deploy(ops_test: OpsTest) -> None: await ops_test.model.destroy_unit(second_primary) -@pytest.mark.group(1) async def test_cluster_restore(ops_test): """Recreates the cluster from storage volumes.""" # Write some data. diff --git a/tests/integration/ha_tests/test_scaling.py b/tests/integration/ha_tests/test_scaling.py index d6c2d7601d..8c85dd931e 100644 --- a/tests/integration/ha_tests/test_scaling.py +++ b/tests/integration/ha_tests/test_scaling.py @@ -9,8 +9,8 @@ from .. import markers from ..helpers import ( + CHARM_BASE, DATABASE_APP_NAME, - build_charm, ) from .conftest import APPLICATION_NAME from .helpers import ( @@ -27,13 +27,11 @@ charm = None -@pytest.mark.group(1) @markers.juju3 @pytest.mark.abort_on_fail -async def test_build_and_deploy(ops_test: OpsTest) -> None: +async def test_build_and_deploy(ops_test: OpsTest, charm) -> None: """Build and deploy two PostgreSQL clusters.""" # This is a potentially destructive test, so it shouldn't be run against existing clusters - charm = await build_charm(".") async with ops_test.fast_forward(): # Deploy the first cluster with reusable storage await gather( @@ -41,11 +39,13 @@ async def test_build_and_deploy(ops_test: OpsTest) -> None: charm, application_name=DATABASE_APP_NAME, num_units=2, + base=CHARM_BASE, config={"profile": "testing"}, ), ops_test.model.deploy( APPLICATION_NAME, application_name=APPLICATION_NAME, + base=CHARM_BASE, channel="edge", ), ) @@ -53,7 +53,6 @@ async def test_build_and_deploy(ops_test: OpsTest) -> None: await ops_test.model.wait_for_idle(status="active", timeout=1500) -@pytest.mark.group(1) @markers.juju3 @pytest.mark.abort_on_fail async def test_removing_stereo_primary(ops_test: OpsTest, continuous_writes) -> None: @@ -103,7 +102,6 @@ async def test_removing_stereo_primary(ops_test: OpsTest, continuous_writes) -> await check_writes(ops_test) -@pytest.mark.group(1) @markers.juju3 @pytest.mark.abort_on_fail async def test_removing_stereo_sync_standby(ops_test: OpsTest, continuous_writes) -> None: @@ -138,7 +136,6 @@ async def test_removing_stereo_sync_standby(ops_test: OpsTest, continuous_writes await check_writes(ops_test) -@pytest.mark.group(1) @markers.juju3 @pytest.mark.abort_on_fail async def test_scale_to_five_units(ops_test: OpsTest) -> None: @@ -146,7 +143,6 @@ async def test_scale_to_five_units(ops_test: OpsTest) -> None: await ops_test.model.wait_for_idle(status="active", timeout=1500) -@pytest.mark.group(1) @markers.juju3 @pytest.mark.abort_on_fail async def test_removing_raft_majority(ops_test: OpsTest, continuous_writes) -> None: @@ -162,73 +158,15 @@ async def test_removing_raft_majority(ops_test: OpsTest, continuous_writes) -> N ops_test.model.destroy_unit( original_roles["primaries"][0], force=True, destroy_storage=False, max_wait=1500 ), - ops_test.model.destroy_unit( - original_roles["replicas"][0], force=True, destroy_storage=False, max_wait=1500 - ), ops_test.model.destroy_unit( original_roles["sync_standbys"][0], force=True, destroy_storage=False, max_wait=1500 ), - ) - - left_unit = ops_test.model.units[original_roles["sync_standbys"][1]] - await ops_test.model.block_until( - lambda: left_unit.workload_status == "blocked" - and left_unit.workload_status_message == "Raft majority loss, run: promote-to-primary", - timeout=600, - ) - - run_action = await left_unit.run_action("promote-to-primary", scope="unit", force=True) - await run_action.wait() - - await ops_test.model.wait_for_idle(status="active", timeout=900, idle_period=45) - - await are_writes_increasing( - ops_test, - [ - original_roles["primaries"][0], - original_roles["replicas"][0], - original_roles["sync_standbys"][0], - ], - ) - - logger.info("Scaling back up") - await ops_test.model.applications[DATABASE_APP_NAME].add_unit(count=3) - await ops_test.model.wait_for_idle(status="active", timeout=1500) - - await check_writes(ops_test) - new_roles = await get_cluster_roles( - ops_test, ops_test.model.applications[DATABASE_APP_NAME].units[0].name - ) - assert len(new_roles["primaries"]) == 1 - assert len(new_roles["sync_standbys"]) == 2 - assert new_roles["primaries"][0] == original_roles["sync_standbys"][1] - - -@pytest.mark.group(1) -@markers.juju3 -@pytest.mark.abort_on_fail -async def test_removing_raft_majority_async(ops_test: OpsTest, continuous_writes) -> None: - # Start an application that continuously writes data to the database. - app = await app_name(ops_test) - original_roles = await get_cluster_roles( - ops_test, ops_test.model.applications[DATABASE_APP_NAME].units[0].name - ) - - await start_continuous_writes(ops_test, app) - logger.info("Deleting primary") - await gather( ops_test.model.destroy_unit( - original_roles["primaries"][0], force=True, destroy_storage=False, max_wait=1500 - ), - ops_test.model.destroy_unit( - original_roles["replicas"][0], force=True, destroy_storage=False, max_wait=1500 - ), - ops_test.model.destroy_unit( - original_roles["replicas"][1], force=True, destroy_storage=False, max_wait=1500 + original_roles["sync_standbys"][1], force=True, destroy_storage=False, max_wait=1500 ), ) - left_unit = ops_test.model.units[original_roles["sync_standbys"][0]] + left_unit = ops_test.model.units[original_roles["sync_standbys"][2]] await ops_test.model.block_until( lambda: left_unit.workload_status == "blocked" and left_unit.workload_status_message == "Raft majority loss, run: promote-to-primary", @@ -244,8 +182,8 @@ async def test_removing_raft_majority_async(ops_test: OpsTest, continuous_writes ops_test, [ original_roles["primaries"][0], - original_roles["replicas"][0], - original_roles["replicas"][1], + original_roles["sync_standbys"][0], + original_roles["sync_standbys"][1], ], ) @@ -258,8 +196,5 @@ async def test_removing_raft_majority_async(ops_test: OpsTest, continuous_writes ops_test, ops_test.model.applications[DATABASE_APP_NAME].units[0].name ) assert len(new_roles["primaries"]) == 1 - assert len(new_roles["sync_standbys"]) == 2 - assert ( - new_roles["primaries"][0] == original_roles["sync_standbys"][0] - or new_roles["primaries"][0] == original_roles["sync_standbys"][1] - ) + assert len(new_roles["sync_standbys"]) == 4 + assert new_roles["primaries"][0] == original_roles["sync_standbys"][2] diff --git a/tests/integration/ha_tests/test_scaling_three_units.py b/tests/integration/ha_tests/test_scaling_three_units.py index 41acfa74b8..7a44d0f5c2 100644 --- a/tests/integration/ha_tests/test_scaling_three_units.py +++ b/tests/integration/ha_tests/test_scaling_three_units.py @@ -3,14 +3,15 @@ # See LICENSE file for licensing details. import logging from asyncio import exceptions, gather, sleep +from copy import deepcopy import pytest from pytest_operator.plugin import OpsTest from .. import markers from ..helpers import ( + CHARM_BASE, DATABASE_APP_NAME, - build_charm, get_machine_from_unit, stop_machine, ) @@ -28,13 +29,11 @@ charm = None -@pytest.mark.group(1) @markers.juju3 @pytest.mark.abort_on_fail -async def test_build_and_deploy(ops_test: OpsTest) -> None: +async def test_build_and_deploy(ops_test: OpsTest, charm) -> None: """Build and deploy two PostgreSQL clusters.""" # This is a potentially destructive test, so it shouldn't be run against existing clusters - charm = await build_charm(".") async with ops_test.fast_forward(): # Deploy the first cluster with reusable storage await gather( @@ -42,11 +41,13 @@ async def test_build_and_deploy(ops_test: OpsTest) -> None: charm, application_name=DATABASE_APP_NAME, num_units=3, + base=CHARM_BASE, config={"profile": "testing"}, ), ops_test.model.deploy( APPLICATION_NAME, application_name=APPLICATION_NAME, + base=CHARM_BASE, channel="edge", ), ) @@ -54,16 +55,14 @@ async def test_build_and_deploy(ops_test: OpsTest) -> None: await ops_test.model.wait_for_idle(status="active", timeout=1500) -@pytest.mark.group(1) @markers.juju3 @pytest.mark.parametrize( "roles", [ ["primaries"], ["sync_standbys"], - ["replicas"], - ["primaries", "replicas"], - ["sync_standbys", "replicas"], + ["primaries", "sync_standbys"], + ["sync_standbys", "sync_standbys"], ], ) @pytest.mark.abort_on_fail @@ -74,8 +73,9 @@ async def test_removing_unit(ops_test: OpsTest, roles: list[str], continuous_wri original_roles = await get_cluster_roles( ops_test, ops_test.model.applications[DATABASE_APP_NAME].units[0].name ) + copied_roles = deepcopy(original_roles) await start_continuous_writes(ops_test, app) - units = [original_roles[role][0] for role in roles] + units = [copied_roles[role].pop(0) for role in roles] for unit in units: logger.info(f"Stopping unit {unit}") await stop_machine(ops_test, await get_machine_from_unit(ops_test, unit)) @@ -122,10 +122,10 @@ async def test_removing_unit(ops_test: OpsTest, roles: list[str], continuous_wri ops_test, ops_test.model.applications[DATABASE_APP_NAME].units[0].name ) assert len(new_roles["primaries"]) == 1 - assert len(new_roles["sync_standbys"]) == 1 - assert len(new_roles["replicas"]) == 1 + assert len(new_roles["sync_standbys"]) == 2 + assert len(new_roles["replicas"]) == 0 if "primaries" in roles: - assert new_roles["primaries"][0] == original_roles["sync_standbys"][0] + assert new_roles["primaries"][0] in original_roles["sync_standbys"] else: assert new_roles["primaries"][0] == original_roles["primaries"][0] diff --git a/tests/integration/ha_tests/test_scaling_three_units_async.py b/tests/integration/ha_tests/test_scaling_three_units_async.py new file mode 100644 index 0000000000..41fc03451d --- /dev/null +++ b/tests/integration/ha_tests/test_scaling_three_units_async.py @@ -0,0 +1,133 @@ +#!/usr/bin/env python3 +# Copyright 2024 Canonical Ltd. +# See LICENSE file for licensing details. +import logging +from asyncio import exceptions, gather, sleep +from copy import deepcopy + +import pytest +from pytest_operator.plugin import OpsTest + +from .. import markers +from ..helpers import ( + CHARM_BASE, + DATABASE_APP_NAME, + get_machine_from_unit, + stop_machine, +) +from .conftest import APPLICATION_NAME +from .helpers import ( + app_name, + are_writes_increasing, + check_writes, + get_cluster_roles, + start_continuous_writes, +) + +logger = logging.getLogger(__name__) + +charm = None + + +@markers.juju3 +@pytest.mark.abort_on_fail +async def test_build_and_deploy(ops_test: OpsTest, charm) -> None: + """Build and deploy two PostgreSQL clusters.""" + # This is a potentially destructive test, so it shouldn't be run against existing clusters + async with ops_test.fast_forward(): + # Deploy the first cluster with reusable storage + await gather( + ops_test.model.deploy( + charm, + application_name=DATABASE_APP_NAME, + num_units=3, + base=CHARM_BASE, + config={"profile": "testing", "synchronous_node_count": "majority"}, + ), + ops_test.model.deploy( + APPLICATION_NAME, + application_name=APPLICATION_NAME, + base=CHARM_BASE, + channel="edge", + ), + ) + + await ops_test.model.wait_for_idle(status="active", timeout=1500) + + +@markers.juju3 +@pytest.mark.parametrize( + "roles", + [ + ["primaries"], + ["sync_standbys"], + ["replicas"], + ["primaries", "replicas"], + ["sync_standbys", "replicas"], + ], +) +@pytest.mark.abort_on_fail +async def test_removing_unit(ops_test: OpsTest, roles: list[str], continuous_writes) -> None: + logger.info(f"removing {', '.join(roles)}") + # Start an application that continuously writes data to the database. + app = await app_name(ops_test) + original_roles = await get_cluster_roles( + ops_test, ops_test.model.applications[DATABASE_APP_NAME].units[0].name + ) + copied_roles = deepcopy(original_roles) + await start_continuous_writes(ops_test, app) + units = [copied_roles[role].pop(0) for role in roles] + for unit in units: + logger.info(f"Stopping unit {unit}") + await stop_machine(ops_test, await get_machine_from_unit(ops_test, unit)) + await sleep(15) + for unit in units: + logger.info(f"Deleting unit {unit}") + await ops_test.model.destroy_unit(unit, force=True, destroy_storage=False, max_wait=1500) + + if len(roles) > 1: + for left_unit in ops_test.model.applications[DATABASE_APP_NAME].units: + if left_unit.name not in units: + break + try: + await ops_test.model.block_until( + lambda: left_unit.workload_status == "blocked" + and left_unit.workload_status_message + == "Raft majority loss, run: promote-to-primary", + timeout=600, + ) + + run_action = ( + await ops_test.model.applications[DATABASE_APP_NAME] + .units[0] + .run_action("promote-to-primary", scope="unit", force=True) + ) + await run_action.wait() + except exceptions.TimeoutError: + # Check if Patroni self healed + assert ( + left_unit.workload_status == "active" + and left_unit.workload_status_message == "Primary" + ) + logger.warning(f"Patroni self-healed without raft reinitialisation for roles {roles}") + + await ops_test.model.wait_for_idle(status="active", timeout=600, idle_period=45) + + await are_writes_increasing(ops_test, units) + + logger.info("Scaling back up") + await ops_test.model.applications[DATABASE_APP_NAME].add_unit(count=len(roles)) + await ops_test.model.wait_for_idle(status="active", timeout=1500) + + new_roles = await get_cluster_roles( + ops_test, ops_test.model.applications[DATABASE_APP_NAME].units[0].name + ) + assert len(new_roles["primaries"]) == 1 + assert len(new_roles["sync_standbys"]) == 1 + assert len(new_roles["replicas"]) == 1 + if "primaries" in roles: + assert new_roles["primaries"][0] in original_roles["sync_standbys"] + else: + assert new_roles["primaries"][0] == original_roles["primaries"][0] + + await check_writes(ops_test) diff --git a/tests/integration/ha_tests/test_self_healing.py b/tests/integration/ha_tests/test_self_healing.py index 3b46eaa2ad..f3ddc6fe88 100644 --- a/tests/integration/ha_tests/test_self_healing.py +++ b/tests/integration/ha_tests/test_self_healing.py @@ -10,7 +10,7 @@ from tenacity import Retrying, stop_after_delay, wait_fixed from ..helpers import ( - build_charm, + CHARM_BASE, db_connect, get_machine_from_unit, get_password, @@ -62,20 +62,19 @@ MEDIAN_ELECTION_TIME = 10 -@pytest.mark.group(1) @pytest.mark.abort_on_fail -async def test_build_and_deploy(ops_test: OpsTest) -> None: +async def test_build_and_deploy(ops_test: OpsTest, charm) -> None: """Build and deploy three unit of PostgreSQL.""" wait_for_apps = False # It is possible for users to provide their own cluster for HA testing. Hence, check if there # is a pre-existing cluster. if not await app_name(ops_test): wait_for_apps = True - charm = await build_charm(".") async with ops_test.fast_forward(): await ops_test.model.deploy( charm, num_units=3, + base=CHARM_BASE, storage={"pgdata": {"pool": "lxd-btrfs", "size": 2048}}, config={"profile": "testing"}, ) @@ -86,6 +85,7 @@ async def test_build_and_deploy(ops_test: OpsTest) -> None: await ops_test.model.deploy( APPLICATION_NAME, application_name=APPLICATION_NAME, + base=CHARM_BASE, channel="edge", ) @@ -94,7 +94,6 @@ async def test_build_and_deploy(ops_test: OpsTest) -> None: await ops_test.model.wait_for_idle(status="active", timeout=1500) -@pytest.mark.group(1) @pytest.mark.abort_on_fail async def test_storage_re_use(ops_test, continuous_writes): """Verifies that database units with attached storage correctly repurpose storage. @@ -142,7 +141,6 @@ async def test_storage_re_use(ops_test, continuous_writes): ) -@pytest.mark.group(1) @pytest.mark.abort_on_fail @pytest.mark.parametrize("process", DB_PROCESSES) @pytest.mark.parametrize("signal", ["SIGTERM", "SIGKILL"]) @@ -177,7 +175,6 @@ async def test_interruption_db_process( await is_cluster_updated(ops_test, primary_name) -@pytest.mark.group(1) @pytest.mark.abort_on_fail @pytest.mark.parametrize("process", DB_PROCESSES) async def test_freeze_db_process( @@ -219,7 +216,6 @@ async def test_freeze_db_process( await is_cluster_updated(ops_test, primary_name) -@pytest.mark.group(1) @pytest.mark.abort_on_fail @pytest.mark.parametrize("process", DB_PROCESSES) @pytest.mark.parametrize("signal", ["SIGTERM", "SIGKILL"]) @@ -307,9 +303,8 @@ async def test_full_cluster_restart( await check_writes(ops_test) -@pytest.mark.group(1) @pytest.mark.abort_on_fail -@pytest.mark.unstable +@pytest.mark.skip(reason="Unstable") async def test_forceful_restart_without_data_and_transaction_logs( ops_test: OpsTest, continuous_writes, @@ -384,7 +379,6 @@ async def test_forceful_restart_without_data_and_transaction_logs( await is_cluster_updated(ops_test, primary_name) -@pytest.mark.group(1) @pytest.mark.abort_on_fail async def test_network_cut(ops_test: OpsTest, continuous_writes, primary_start_timeout): """Completely cut and restore network.""" @@ -473,7 +467,6 @@ async def test_network_cut(ops_test: OpsTest, continuous_writes, primary_start_t await is_cluster_updated(ops_test, primary_name, use_ip_from_inside=True) -@pytest.mark.group(1) @pytest.mark.abort_on_fail async def test_network_cut_without_ip_change( ops_test: OpsTest, continuous_writes, primary_start_timeout diff --git a/tests/integration/ha_tests/test_smoke.py b/tests/integration/ha_tests/test_smoke.py index 2db47958fe..b160d38f2b 100644 --- a/tests/integration/ha_tests/test_smoke.py +++ b/tests/integration/ha_tests/test_smoke.py @@ -12,8 +12,8 @@ from ..helpers import ( APPLICATION_NAME, + CHARM_BASE, ) -from ..juju_ import juju_major_version from .helpers import ( add_unit_with_storage, check_db, @@ -22,7 +22,6 @@ get_any_deatached_storage, is_postgresql_ready, is_storage_exists, - remove_unit_force, storage_id, ) @@ -32,7 +31,6 @@ logger = logging.getLogger(__name__) -@pytest.mark.group(1) @pytest.mark.abort_on_fail async def test_app_force_removal(ops_test: OpsTest, charm: str): """Remove unit with force while storage is alive.""" @@ -43,6 +41,7 @@ async def test_app_force_removal(ops_test: OpsTest, charm: str): charm, application_name=APPLICATION_NAME, num_units=1, + base=CHARM_BASE, storage={"pgdata": {"pool": "lxd-btrfs", "size": 8046}}, config={"profile": "testing"}, ) @@ -77,12 +76,9 @@ async def test_app_force_removal(ops_test: OpsTest, charm: str): # Destroy charm logger.info("force removing charm") - if juju_major_version == 2: - await remove_unit_force(ops_test, primary_name) - else: - await ops_test.model.destroy_unit( - primary_name, force=True, destroy_storage=False, max_wait=1500 - ) + await ops_test.model.destroy_unit( + primary_name, force=True, destroy_storage=False, max_wait=1500 + ) # Storage should remain logger.info("werifing is storage exists") @@ -91,7 +87,6 @@ async def test_app_force_removal(ops_test: OpsTest, charm: str): assert await is_storage_exists(ops_test, storage_id_str) -@pytest.mark.group(1) @pytest.mark.abort_on_fail async def test_charm_garbage_ignorance(ops_test: OpsTest, charm: str): """Test charm deploy in dirty environment with garbage storage.""" @@ -131,9 +126,7 @@ async def test_charm_garbage_ignorance(ops_test: OpsTest, charm: str): await ops_test.model.destroy_unit(primary_name) -@pytest.mark.group(1) @pytest.mark.abort_on_fail -@pytest.mark.skipif(juju_major_version < 3, reason="Requires juju 3 or higher") async def test_app_resources_conflicts_v3(ops_test: OpsTest, charm: str): """Test application deploy in dirty environment with garbage storage from another application.""" async with ops_test.fast_forward(): @@ -148,6 +141,7 @@ async def test_app_resources_conflicts_v3(ops_test: OpsTest, charm: str): charm, application_name=DUP_APPLICATION_NAME, num_units=1, + base=CHARM_BASE, attach_storage=[tag.storage(garbage_storage)], config={"profile": "testing"}, ) @@ -168,50 +162,3 @@ async def test_app_resources_conflicts_v3(ops_test: OpsTest, charm: str): assert not await check_password_auth( ops_test, ops_test.model.applications[DUP_APPLICATION_NAME].units[0].name ) - - -@pytest.mark.group(1) -@pytest.mark.abort_on_fail -@pytest.mark.skipif(juju_major_version != 2, reason="Requires juju 2") -async def test_app_resources_conflicts_v2(ops_test: OpsTest, charm: str): - """Test application deploy in dirty environment with garbage storage from another application.""" - async with ops_test.fast_forward(): - logger.info("checking garbage storage") - garbage_storage = None - for attempt in Retrying(stop=stop_after_delay(30 * 3), wait=wait_fixed(3), reraise=True): - with attempt: - garbage_storage = await get_any_deatached_storage(ops_test) - - # Deploy duplicaate charm - logger.info("deploying duplicate application") - await ops_test.model.deploy( - charm, - application_name=DUP_APPLICATION_NAME, - num_units=1, - config={"profile": "testing"}, - ) - - logger.info("force removing charm") - await remove_unit_force( - ops_test, ops_test.model.applications[DUP_APPLICATION_NAME].units[0].name - ) - - # Add unit with garbage storage - logger.info("adding charm with attached storage") - add_unit_cmd = f"add-unit {DUP_APPLICATION_NAME} --model={ops_test.model.info.name} --attach-storage={garbage_storage}".split() - return_code, _, _ = await ops_test.juju(*add_unit_cmd) - assert return_code == 0, "Failed to add unit with storage" - - logger.info("waiting for duplicate application to be blocked") - try: - await ops_test.model.wait_for_idle( - apps=[DUP_APPLICATION_NAME], timeout=1000, status="blocked" - ) - except asyncio.TimeoutError: - logger.info("Application is not in blocked state. Checking logs...") - - # Since application have postgresql db in storage from external application it should not be able to connect due to new password - logger.info("checking operator password auth") - assert not await check_password_auth( - ops_test, ops_test.model.applications[DUP_APPLICATION_NAME].units[0].name - ) diff --git a/tests/integration/ha_tests/test_synchronous_policy.py b/tests/integration/ha_tests/test_synchronous_policy.py new file mode 100644 index 0000000000..920642d81e --- /dev/null +++ b/tests/integration/ha_tests/test_synchronous_policy.py @@ -0,0 +1,78 @@ +#!/usr/bin/env python3 +# Copyright 2024 Canonical Ltd. +# See LICENSE file for licensing details. +import pytest +from pytest_operator.plugin import OpsTest +from tenacity import Retrying, stop_after_attempt, wait_fixed + +from ..helpers import CHARM_BASE +from .helpers import app_name, get_cluster_roles + + +@pytest.mark.abort_on_fail +async def test_build_and_deploy(ops_test: OpsTest, charm: str) -> None: + """Build and deploy two PostgreSQL clusters.""" + async with ops_test.fast_forward(): + # Deploy the first cluster with reusable storage + await ops_test.model.deploy( + charm, + num_units=3, + base=CHARM_BASE, + config={"profile": "testing"}, + ) + await ops_test.model.wait_for_idle(status="active", timeout=1500) + + +async def test_default_all(ops_test: OpsTest) -> None: + app = await app_name(ops_test) + + async with ops_test.fast_forward(): + await ops_test.model.wait_for_idle(apps=[app], status="active", timeout=300) + + for attempt in Retrying(stop=stop_after_attempt(3), wait=wait_fixed(5), reraise=True): + with attempt: + roles = await get_cluster_roles( + ops_test, ops_test.model.applications[app].units[0].name + ) + + assert len(roles["primaries"]) == 1 + assert len(roles["sync_standbys"]) == 2 + assert len(roles["replicas"]) == 0 + + +async def test_majority(ops_test: OpsTest) -> None: + app = await app_name(ops_test) + + await ops_test.model.applications[app].set_config({"synchronous_node_count": "majority"}) + + async with ops_test.fast_forward(): + await ops_test.model.wait_for_idle(apps=[app], status="active") + + for attempt in Retrying(stop=stop_after_attempt(3), wait=wait_fixed(5), reraise=True): + with attempt: + roles = await get_cluster_roles( + ops_test, ops_test.model.applications[app].units[0].name + ) + + assert len(roles["primaries"]) == 1 + assert len(roles["sync_standbys"]) == 1 + assert len(roles["replicas"]) == 1 + + +async def test_constant(ops_test: OpsTest) -> None: + app = await app_name(ops_test) + + await ops_test.model.applications[app].set_config({"synchronous_node_count": "2"}) + + async with ops_test.fast_forward(): + await ops_test.model.wait_for_idle(apps=[app], status="active", timeout=300) + + for attempt in Retrying(stop=stop_after_attempt(3), wait=wait_fixed(5), reraise=True): + with attempt: + roles = await get_cluster_roles( + ops_test, ops_test.model.applications[app].units[0].name + ) + + assert len(roles["primaries"]) == 1 + assert len(roles["sync_standbys"]) == 2 + assert len(roles["replicas"]) == 0 diff --git a/tests/integration/ha_tests/test_upgrade.py b/tests/integration/ha_tests/test_upgrade.py index ce6498e46c..e639b8ff60 100644 --- a/tests/integration/ha_tests/test_upgrade.py +++ b/tests/integration/ha_tests/test_upgrade.py @@ -13,7 +13,6 @@ from ..helpers import ( APPLICATION_NAME, DATABASE_APP_NAME, - build_charm, count_switchovers, get_leader_unit, get_primary, @@ -30,7 +29,6 @@ TIMEOUT = 600 -@pytest.mark.group(1) @pytest.mark.abort_on_fail async def test_deploy_latest(ops_test: OpsTest) -> None: """Simple test to ensure that the PostgreSQL and application charms get deployed.""" @@ -53,7 +51,6 @@ async def test_deploy_latest(ops_test: OpsTest) -> None: assert len(ops_test.model.applications[DATABASE_APP_NAME].units) == 3 -@pytest.mark.group(1) @pytest.mark.abort_on_fail async def test_pre_upgrade_check(ops_test: OpsTest) -> None: """Test that the pre-upgrade-check action runs successfully.""" @@ -66,9 +63,8 @@ async def test_pre_upgrade_check(ops_test: OpsTest) -> None: await action.wait() -@pytest.mark.group(1) @pytest.mark.abort_on_fail -async def test_upgrade_from_edge(ops_test: OpsTest, continuous_writes) -> None: +async def test_upgrade_from_edge(ops_test: OpsTest, continuous_writes, charm) -> None: # Start an application that continuously writes data to the database. logger.info("starting continuous writes to the database") await start_continuous_writes(ops_test, DATABASE_APP_NAME) @@ -82,9 +78,6 @@ async def test_upgrade_from_edge(ops_test: OpsTest, continuous_writes) -> None: application = ops_test.model.applications[DATABASE_APP_NAME] - logger.info("Build charm locally") - charm = await build_charm(".") - logger.info("Refresh the charm") await application.refresh(path=charm) @@ -116,9 +109,8 @@ async def test_upgrade_from_edge(ops_test: OpsTest, continuous_writes) -> None: ) -@pytest.mark.group(1) @pytest.mark.abort_on_fail -async def test_fail_and_rollback(ops_test, continuous_writes) -> None: +async def test_fail_and_rollback(ops_test, charm, continuous_writes) -> None: # Start an application that continuously writes data to the database. logger.info("starting continuous writes to the database") await start_continuous_writes(ops_test, DATABASE_APP_NAME) @@ -135,10 +127,9 @@ async def test_fail_and_rollback(ops_test, continuous_writes) -> None: action = await leader_unit.run_action("pre-upgrade-check") await action.wait() - local_charm = await build_charm(".") - filename = local_charm.split("/")[-1] if isinstance(local_charm, str) else local_charm.name + filename = Path(charm).name fault_charm = Path("/tmp/", filename) - shutil.copy(local_charm, fault_charm) + shutil.copy(charm, fault_charm) logger.info("Inject dependency fault") await inject_dependency_fault(ops_test, DATABASE_APP_NAME, fault_charm) @@ -163,7 +154,7 @@ async def test_fail_and_rollback(ops_test, continuous_writes) -> None: await action.wait() logger.info("Re-refresh the charm") - await application.refresh(path=local_charm) + await application.refresh(path=charm) logger.info("Wait for upgrade to start") await ops_test.model.block_until( diff --git a/tests/integration/ha_tests/test_upgrade_from_stable.py b/tests/integration/ha_tests/test_upgrade_from_stable.py index d250bdf81b..17f51a51ab 100644 --- a/tests/integration/ha_tests/test_upgrade_from_stable.py +++ b/tests/integration/ha_tests/test_upgrade_from_stable.py @@ -9,11 +9,9 @@ from ..helpers import ( APPLICATION_NAME, DATABASE_APP_NAME, - build_charm, count_switchovers, get_leader_unit, get_primary, - remove_chown_workaround, ) from .helpers import ( are_writes_increasing, @@ -26,45 +24,27 @@ TIMEOUT = 900 -@pytest.mark.group(1) -@pytest.mark.unstable @pytest.mark.abort_on_fail async def test_deploy_stable(ops_test: OpsTest) -> None: """Simple test to ensure that the PostgreSQL and application charms get deployed.""" + # TODO remove once we release to stable + pytest.skip("No 16/stable yet.") return_code, charm_info, stderr = await ops_test.juju("info", "postgresql", "--format=json") if return_code != 0: raise Exception(f"failed to get charm info with error: {stderr}") # Revisions lower than 315 have a currently broken workaround for chown. parsed_charm_info = json.loads(charm_info) revision = ( - parsed_charm_info["channels"]["16"]["stable"][0]["revision"] + parsed_charm_info["channels"]["14"]["stable"][0]["revision"] if "channels" in parsed_charm_info - else parsed_charm_info["channel-map"]["16/stable"]["revision"] + else parsed_charm_info["channel-map"]["14/stable"]["revision"] + ) + logger.info(f"14/stable revision: {revision}") + await ops_test.model.deploy( + DATABASE_APP_NAME, + num_units=3, + channel="16/stable", ) - logger.info(f"16/stable revision: {revision}") - if int(revision) < 315: - original_charm_name = "./postgresql.charm" - return_code, _, stderr = await ops_test.juju( - "download", - "postgresql", - "--channel=16/stable", - f"--filepath={original_charm_name}", - ) - if return_code != 0: - raise Exception( - f"failed to download charm from 16/stable channel with error: {stderr}" - ) - patched_charm_name = "./modified_postgresql.charm" - remove_chown_workaround(original_charm_name, patched_charm_name) - return_code, _, stderr = await ops_test.juju("deploy", patched_charm_name, "-n", "3") - if return_code != 0: - raise Exception(f"failed to deploy charm from 16/stable channel with error: {stderr}") - else: - await ops_test.model.deploy( - DATABASE_APP_NAME, - num_units=3, - channel="16/stable", - ) await ops_test.model.deploy( APPLICATION_NAME, num_units=1, @@ -78,14 +58,14 @@ async def test_deploy_stable(ops_test: OpsTest) -> None: assert len(ops_test.model.applications[DATABASE_APP_NAME].units) == 3 -@pytest.mark.group(1) -@pytest.mark.unstable @pytest.mark.abort_on_fail async def test_pre_upgrade_check(ops_test: OpsTest) -> None: """Test that the pre-upgrade-check action runs successfully.""" + # TODO remove once we release to stable + pytest.skip("No 16/stable yet.") application = ops_test.model.applications[DATABASE_APP_NAME] if "pre-upgrade-check" not in await application.get_actions(): - logger.info("skipping the test because the charm from 16/stable doesn't support upgrade") + logger.info("skipping the test because the charm from 14/stable doesn't support upgrade") return logger.info("Get leader unit") @@ -97,11 +77,11 @@ async def test_pre_upgrade_check(ops_test: OpsTest) -> None: await action.wait() -@pytest.mark.group(1) -@pytest.mark.unstable @pytest.mark.abort_on_fail -async def test_upgrade_from_stable(ops_test: OpsTest): +async def test_upgrade_from_stable(ops_test: OpsTest, charm): """Test updating from stable channel.""" + # TODO remove once we release to stable + pytest.skip("No 16/stable yet.") # Start an application that continuously writes data to the database. logger.info("starting continuous writes to the database") await start_continuous_writes(ops_test, DATABASE_APP_NAME) @@ -116,9 +96,6 @@ async def test_upgrade_from_stable(ops_test: OpsTest): application = ops_test.model.applications[DATABASE_APP_NAME] actions = await application.get_actions() - logger.info("Build charm locally") - charm = await build_charm(".") - logger.info("Refresh the charm") await application.refresh(path=charm) diff --git a/tests/integration/helpers.py b/tests/integration/helpers.py index ade0742da5..d777c0bb81 100644 --- a/tests/integration/helpers.py +++ b/tests/integration/helpers.py @@ -30,7 +30,9 @@ wait_fixed, ) -CHARM_BASE = "ubuntu@24.04" +from constants import DATABASE_DEFAULT_NAME + +CHARM_BASE = "ubuntu@22.04" METADATA = yaml.safe_load(Path("./metadata.yaml").read_text()) DATABASE_APP_NAME = METADATA["name"] STORAGE_PATH = METADATA["storage"]["pgdata"]["location"] @@ -39,37 +41,6 @@ logger = logging.getLogger(__name__) -async def build_charm(charm_path) -> Path: - charm_path = Path(charm_path) - architecture = subprocess.run( - ["dpkg", "--print-architecture"], - capture_output=True, - check=True, - encoding="utf-8", - ).stdout.strip() - assert architecture in ("amd64", "arm64") - # 24.04 pin is temporary solution while multi-base integration testing not supported by data-platform-workflows - packed_charms = list(charm_path.glob(f"*ubuntu@24.04-{architecture}.charm")) - if len(packed_charms) == 1: - # python-libjuju's model.deploy(), juju deploy, and juju bundle files expect local charms - # to begin with `./` or `/` to distinguish them from Charmhub charms. - # Therefore, we need to return an absolute path—a relative `pathlib.Path` does not start - # with `./` when cast to a str. - # (python-libjuju model.deploy() expects a str but will cast any input to a str as a - # workaround for pytest-operator's non-compliant `build_charm` return type of - # `pathlib.Path`.) - return packed_charms[0].resolve(strict=True) - elif len(packed_charms) > 1: - raise ValueError( - f"More than one matching .charm file found at {charm_path=} for {architecture=} and " - f"Ubuntu 24.04: {packed_charms}." - ) - else: - raise ValueError( - f"Unable to find .charm file for {architecture=} and Ubuntu 24.04 at {charm_path=}" - ) - - async def build_connection_string( ops_test: OpsTest, application_name: str, @@ -528,7 +499,7 @@ async def execute_query_on_unit( unit_address: str, password: str, query: str, - database: str = "postgres", + database: str = DATABASE_DEFAULT_NAME, sslmode: str | None = None, ): """Execute given PostgreSQL query on a unit. @@ -882,44 +853,6 @@ def has_relation_exited( return True -def remove_chown_workaround(original_charm_filename: str, patched_charm_filename: str) -> None: - """Remove the chown workaround from the charm.""" - with ( - zipfile.ZipFile(original_charm_filename, "r") as charm_file, - zipfile.ZipFile(patched_charm_filename, "w") as modified_charm_file, - ): - # Iterate the input files - unix_attributes = {} - for charm_info in charm_file.infolist(): - # Read input file - with charm_file.open(charm_info) as file: - if charm_info.filename == "src/charm.py": - content = file.read() - # Modify the content of the file by replacing a string - content = ( - content.decode() - .replace( - """ try: - self._patch_snap_seccomp_profile() - except subprocess.CalledProcessError as e: - logger.exception(e) - self.unit.status = BlockedStatus("failed to patch snap seccomp profile") - return""", - "", - ) - .encode() - ) - # Write content. - modified_charm_file.writestr(charm_info.filename, content) - else: # Other file, don't want to modify => just copy it. - content = file.read() - modified_charm_file.writestr(charm_info.filename, content) - unix_attributes[charm_info.filename] = charm_info.external_attr >> 16 - - for modified_charm_info in modified_charm_file.infolist(): - modified_charm_info.external_attr = unix_attributes[modified_charm_info.filename] << 16 - - @retry( retry=retry_if_result(lambda x: not x), stop=stop_after_attempt(10), @@ -1084,7 +1017,6 @@ def switchover( ) assert response.status_code == 200 app_name = current_primary.split("/")[0] - minority_count = len(ops_test.model.applications[app_name].units) // 2 for attempt in Retrying(stop=stop_after_attempt(30), wait=wait_fixed(2), reraise=True): with attempt: response = requests.get(f"http://{primary_ip}:8008/cluster") @@ -1092,7 +1024,7 @@ def switchover( standbys = len([ member for member in response.json()["members"] if member["role"] == "sync_standby" ]) - assert standbys >= minority_count + assert standbys == len(ops_test.model.applications[app_name].units) - 1 async def wait_for_idle_on_blocked( @@ -1157,6 +1089,7 @@ async def backup_operations( charm, application_name=database_app_name, num_units=2, + base=CHARM_BASE, config={"profile": "testing"}, ) diff --git a/tests/integration/new_relations/test_new_relations.py b/tests/integration/new_relations/test_new_relations_1.py similarity index 89% rename from tests/integration/new_relations/test_new_relations.py rename to tests/integration/new_relations/test_new_relations_1.py index be6bd96231..4566286df6 100644 --- a/tests/integration/new_relations/test_new_relations.py +++ b/tests/integration/new_relations/test_new_relations_1.py @@ -1,4 +1,3 @@ -#!/usr/bin/env python3 # Copyright 2022 Canonical Ltd. # See LICENSE file for licensing details. import asyncio @@ -13,8 +12,10 @@ from pytest_operator.plugin import OpsTest from tenacity import Retrying, stop_after_attempt, wait_fixed -from .. import markers +from constants import DATABASE_DEFAULT_NAME + from ..helpers import ( + CHARM_BASE, assert_sync_standbys, get_leader_unit, get_machine_from_unit, @@ -23,7 +24,6 @@ start_machine, stop_machine, ) -from ..juju_ import juju_major_version from .helpers import ( build_connection_string, check_relation_data_existence, @@ -46,7 +46,6 @@ INVALID_EXTRA_USER_ROLE_BLOCKING_MESSAGE = "invalid role(s) for extra user roles" -@pytest.mark.group("new_relations_tests") @pytest.mark.abort_on_fail async def test_deploy_charms(ops_test: OpsTest, charm): """Deploy both charms (application and database) to use in the tests.""" @@ -58,18 +57,21 @@ async def test_deploy_charms(ops_test: OpsTest, charm): APPLICATION_APP_NAME, application_name=APPLICATION_APP_NAME, num_units=2, + base=CHARM_BASE, channel="edge", ), ops_test.model.deploy( charm, application_name=DATABASE_APP_NAME, num_units=1, + base=CHARM_BASE, config={"profile": "testing"}, ), ops_test.model.deploy( charm, application_name=ANOTHER_DATABASE_APP_NAME, num_units=2, + base=CHARM_BASE, config={"profile": "testing"}, ), ) @@ -77,7 +79,6 @@ async def test_deploy_charms(ops_test: OpsTest, charm): await ops_test.model.wait_for_idle(apps=APP_NAMES, status="active", timeout=3000) -@pytest.mark.group("new_relations_tests") async def test_no_read_only_endpoint_in_standalone_cluster(ops_test: OpsTest): """Test that there is no read-only endpoint in a standalone cluster.""" async with ops_test.fast_forward(): @@ -94,24 +95,23 @@ async def test_no_read_only_endpoint_in_standalone_cluster(ops_test: OpsTest): await ops_test.model.wait_for_idle(apps=APP_NAMES, status="active") # Check that on juju 3 we have secrets and no username and password in the rel databag - if juju_major_version > 2: - logger.info("checking for secrets") - secret_uri, password = await asyncio.gather( - get_application_relation_data( - ops_test, - APPLICATION_APP_NAME, - FIRST_DATABASE_RELATION_NAME, - "secret-user", - ), - get_application_relation_data( - ops_test, - APPLICATION_APP_NAME, - FIRST_DATABASE_RELATION_NAME, - "password", - ), - ) - assert secret_uri is not None - assert password is None + logger.info("checking for secrets") + secret_uri, password = await asyncio.gather( + get_application_relation_data( + ops_test, + APPLICATION_APP_NAME, + FIRST_DATABASE_RELATION_NAME, + "secret-user", + ), + get_application_relation_data( + ops_test, + APPLICATION_APP_NAME, + FIRST_DATABASE_RELATION_NAME, + "password", + ), + ) + assert secret_uri is not None + assert password is None # Try to get the connection string of the database using the read-only endpoint. # It should not be available. @@ -124,7 +124,6 @@ async def test_no_read_only_endpoint_in_standalone_cluster(ops_test: OpsTest): ) -@pytest.mark.group("new_relations_tests") async def test_read_only_endpoint_in_scaled_up_cluster(ops_test: OpsTest): """Test that there is read-only endpoint in a scaled up cluster.""" async with ops_test.fast_forward(): @@ -142,7 +141,6 @@ async def test_read_only_endpoint_in_scaled_up_cluster(ops_test: OpsTest): ) -@pytest.mark.group("new_relations_tests") async def test_database_relation_with_charm_libraries(ops_test: OpsTest): """Test basic functionality of database relation interface.""" # Get the connection string to connect to the database using the read/write endpoint. @@ -190,7 +188,6 @@ async def test_database_relation_with_charm_libraries(ops_test: OpsTest): cursor.execute("DROP TABLE test;") -@pytest.mark.group("new_relations_tests") @pytest.mark.abort_on_fail async def test_filter_out_degraded_replicas(ops_test: OpsTest): primary = await get_primary(ops_test, f"{DATABASE_APP_NAME}/0") @@ -221,7 +218,6 @@ async def test_filter_out_degraded_replicas(ops_test: OpsTest): ) -@pytest.mark.group("new_relations_tests") async def test_user_with_extra_roles(ops_test: OpsTest): """Test superuser actions and the request for more permissions.""" # Get the connection string to connect to the database. @@ -242,7 +238,6 @@ async def test_user_with_extra_roles(ops_test: OpsTest): connection.close() -@pytest.mark.group("new_relations_tests") async def test_two_applications_doesnt_share_the_same_relation_data(ops_test: OpsTest): """Test that two different application connect to the database with different credentials.""" # Set some variables to use in this test. @@ -255,6 +250,7 @@ async def test_two_applications_doesnt_share_the_same_relation_data(ops_test: Op APPLICATION_APP_NAME, application_name=another_application_app_name, channel="edge", + base=CHARM_BASE, ) await ops_test.model.wait_for_idle(apps=all_app_names, status="active") @@ -281,7 +277,10 @@ async def test_two_applications_doesnt_share_the_same_relation_data(ops_test: Op (another_application_app_name, f"{APPLICATION_APP_NAME.replace('-', '_')}_database"), ]: connection_string = await build_connection_string( - ops_test, application, FIRST_DATABASE_RELATION_NAME, database="postgres" + ops_test, + application, + FIRST_DATABASE_RELATION_NAME, + database=DATABASE_DEFAULT_NAME, ) with pytest.raises(psycopg2.Error): psycopg2.connect(connection_string) @@ -295,7 +294,6 @@ async def test_two_applications_doesnt_share_the_same_relation_data(ops_test: Op psycopg2.connect(connection_string) -@pytest.mark.group("new_relations_tests") async def test_an_application_can_connect_to_multiple_database_clusters(ops_test: OpsTest): """Test that an application can connect to different clusters of the same database.""" # Relate the application with both database clusters @@ -326,7 +324,6 @@ async def test_an_application_can_connect_to_multiple_database_clusters(ops_test assert application_connection_string != another_application_connection_string -@pytest.mark.group("new_relations_tests") async def test_an_application_can_connect_to_multiple_aliased_database_clusters(ops_test: OpsTest): """Test that an application can connect to different clusters of the same database.""" # Relate the application with both database clusters @@ -360,7 +357,6 @@ async def test_an_application_can_connect_to_multiple_aliased_database_clusters( assert application_connection_string != another_application_connection_string -@pytest.mark.group("new_relations_tests") async def test_an_application_can_request_multiple_databases(ops_test: OpsTest): """Test that an application can request additional databases using the same interface.""" # Relate the charms using another relation and wait for them exchanging some connection data. @@ -381,7 +377,6 @@ async def test_an_application_can_request_multiple_databases(ops_test: OpsTest): assert first_database_connection_string != second_database_connection_string -@pytest.mark.group("new_relations_tests") @pytest.mark.abort_on_fail async def test_relation_data_is_updated_correctly_when_scaling(ops_test: OpsTest): """Test that relation data, like connection data, is updated correctly when scaling.""" @@ -462,7 +457,6 @@ async def test_relation_data_is_updated_correctly_when_scaling(ops_test: OpsTest psycopg2.connect(primary_connection_string) -@pytest.mark.group("new_relations_tests") async def test_relation_with_no_database_name(ops_test: OpsTest): """Test that a relation with no database name doesn't block the charm.""" async with ops_test.fast_forward(): @@ -479,13 +473,12 @@ async def test_relation_with_no_database_name(ops_test: OpsTest): await ops_test.model.wait_for_idle(apps=APP_NAMES, status="active", raise_on_blocked=True) -@pytest.mark.group("new_relations_tests") async def test_admin_role(ops_test: OpsTest): """Test that the admin role gives access to all the databases.""" all_app_names = [DATA_INTEGRATOR_APP_NAME] all_app_names.extend(APP_NAMES) async with ops_test.fast_forward(): - await ops_test.model.deploy(DATA_INTEGRATOR_APP_NAME) + await ops_test.model.deploy(DATA_INTEGRATOR_APP_NAME, base=CHARM_BASE) await ops_test.model.wait_for_idle(apps=[DATA_INTEGRATOR_APP_NAME], status="blocked") await ops_test.model.applications[DATA_INTEGRATOR_APP_NAME].set_config({ "database-name": DATA_INTEGRATOR_APP_NAME.replace("-", "_"), @@ -497,7 +490,7 @@ async def test_admin_role(ops_test: OpsTest): # Check that the user can access all the databases. for database in [ - "postgres", + DATABASE_DEFAULT_NAME, f"{APPLICATION_APP_NAME.replace('-', '_')}_database", "another_application_database", ]: @@ -521,11 +514,11 @@ async def test_admin_role(ops_test: OpsTest): ) assert version == data - # Write some data (it should fail in the "postgres" database). + # Write some data (it should fail in the default database name). random_name = ( f"test_{''.join(secrets.choice(string.ascii_lowercase) for _ in range(10))}" ) - should_fail = database == "postgres" + should_fail = database == DATABASE_DEFAULT_NAME cursor.execute(f"CREATE TABLE {random_name}(data TEXT);") if should_fail: assert False, ( @@ -543,7 +536,7 @@ async def test_admin_role(ops_test: OpsTest): # Test the creation and deletion of databases. connection_string = await build_connection_string( - ops_test, DATA_INTEGRATOR_APP_NAME, "postgresql", database="postgres" + ops_test, DATA_INTEGRATOR_APP_NAME, "postgresql", database=DATABASE_DEFAULT_NAME ) connection = psycopg2.connect(connection_string) connection.autocommit = True @@ -552,8 +545,10 @@ async def test_admin_role(ops_test: OpsTest): cursor.execute(f"CREATE DATABASE {random_name};") cursor.execute(f"DROP DATABASE {random_name};") try: - cursor.execute("DROP DATABASE postgres;") - assert False, "the admin extra user role was able to drop the `postgres` system database" + cursor.execute(f"DROP DATABASE {DATABASE_DEFAULT_NAME};") + assert False, ( + f"the admin extra user role was able to drop the `{DATABASE_DEFAULT_NAME}` system database" + ) except psycopg2.errors.InsufficientPrivilege: # Ignore the error, as the admin extra user role mustn't be able to drop # the "postgres" system database. @@ -562,7 +557,6 @@ async def test_admin_role(ops_test: OpsTest): connection.close() -@pytest.mark.group("new_relations_tests") async def test_invalid_extra_user_roles(ops_test: OpsTest): async with ops_test.fast_forward(): # Remove the relation between the database and the first data integrator. @@ -576,6 +570,7 @@ async def test_invalid_extra_user_roles(ops_test: OpsTest): await ops_test.model.deploy( DATA_INTEGRATOR_APP_NAME, application_name=another_data_integrator_app_name, + base=CHARM_BASE, ) await ops_test.model.wait_for_idle( apps=[another_data_integrator_app_name], status="blocked" @@ -621,41 +616,3 @@ async def test_invalid_extra_user_roles(ops_test: OpsTest): raise_on_blocked=False, timeout=1000, ) - - -@pytest.mark.group("nextcloud_blocked") -@markers.amd64_only # nextcloud charm not available for arm64 -async def test_nextcloud_db_blocked(ops_test: OpsTest, charm: str) -> None: - # Deploy Database Charm and Nextcloud - await asyncio.gather( - ops_test.model.deploy( - charm, - application_name=DATABASE_APP_NAME, - num_units=1, - config={"profile": "testing"}, - ), - ops_test.model.deploy( - "nextcloud", - channel="edge", - application_name="nextcloud", - num_units=1, - ), - ) - await asyncio.gather( - ops_test.model.wait_for_idle(apps=[DATABASE_APP_NAME], status="active", timeout=2000), - ops_test.model.wait_for_idle( - apps=["nextcloud"], - status="blocked", - raise_on_blocked=False, - timeout=2000, - ), - ) - - await ops_test.model.relate("nextcloud:database", f"{DATABASE_APP_NAME}:database") - - await ops_test.model.wait_for_idle( - apps=[DATABASE_APP_NAME, "nextcloud"], - status="active", - raise_on_blocked=False, - timeout=1000, - ) diff --git a/tests/integration/new_relations/test_new_relations_2.py b/tests/integration/new_relations/test_new_relations_2.py new file mode 100644 index 0000000000..4a9134a2d1 --- /dev/null +++ b/tests/integration/new_relations/test_new_relations_2.py @@ -0,0 +1,69 @@ +# Copyright 2022 Canonical Ltd. +# See LICENSE file for licensing details. +import asyncio +import logging +from pathlib import Path + +import pytest +import yaml +from pytest_operator.plugin import OpsTest + +from .. import markers +from ..helpers import ( + CHARM_BASE, +) + +logger = logging.getLogger(__name__) + +APPLICATION_APP_NAME = "postgresql-test-app" +DATABASE_APP_NAME = "database" +ANOTHER_DATABASE_APP_NAME = "another-database" +DATA_INTEGRATOR_APP_NAME = "data-integrator" +APP_NAMES = [APPLICATION_APP_NAME, DATABASE_APP_NAME, ANOTHER_DATABASE_APP_NAME] +DATABASE_APP_METADATA = yaml.safe_load(Path("./metadata.yaml").read_text()) +FIRST_DATABASE_RELATION_NAME = "database" +SECOND_DATABASE_RELATION_NAME = "second-database" +MULTIPLE_DATABASE_CLUSTERS_RELATION_NAME = "multiple-database-clusters" +ALIASED_MULTIPLE_DATABASE_CLUSTERS_RELATION_NAME = "aliased-multiple-database-clusters" +NO_DATABASE_RELATION_NAME = "no-database" +INVALID_EXTRA_USER_ROLE_BLOCKING_MESSAGE = "invalid role(s) for extra user roles" + + +@markers.amd64_only # nextcloud charm not available for arm64 +@pytest.mark.skip(reason="Unstable") +async def test_nextcloud_db_blocked(ops_test: OpsTest, charm: str) -> None: + # Deploy Database Charm and Nextcloud + await asyncio.gather( + ops_test.model.deploy( + charm, + application_name=DATABASE_APP_NAME, + num_units=1, + base=CHARM_BASE, + config={"profile": "testing"}, + ), + ops_test.model.deploy( + "nextcloud", + channel="edge", + application_name="nextcloud", + num_units=1, + base=CHARM_BASE, + ), + ) + await asyncio.gather( + ops_test.model.wait_for_idle(apps=[DATABASE_APP_NAME], status="active", timeout=2000), + ops_test.model.wait_for_idle( + apps=["nextcloud"], + status="blocked", + raise_on_blocked=False, + timeout=2000, + ), + ) + + await ops_test.model.relate("nextcloud:database", f"{DATABASE_APP_NAME}:database") + + await ops_test.model.wait_for_idle( + apps=[DATABASE_APP_NAME, "nextcloud"], + status="active", + raise_on_blocked=False, + timeout=1000, + ) diff --git a/tests/integration/new_relations/test_relations_coherence.py b/tests/integration/new_relations/test_relations_coherence.py index ce280b8122..74fd202fa6 100644 --- a/tests/integration/new_relations/test_relations_coherence.py +++ b/tests/integration/new_relations/test_relations_coherence.py @@ -9,9 +9,11 @@ import pytest from pytest_operator.plugin import OpsTest -from ..helpers import DATABASE_APP_NAME +from constants import DATABASE_DEFAULT_NAME + +from ..helpers import CHARM_BASE, DATABASE_APP_NAME from .helpers import build_connection_string -from .test_new_relations import DATA_INTEGRATOR_APP_NAME +from .test_new_relations_1 import DATA_INTEGRATOR_APP_NAME logger = logging.getLogger(__name__) @@ -20,7 +22,6 @@ FIRST_DATABASE_RELATION_NAME = "database" -@pytest.mark.group(1) @pytest.mark.abort_on_fail async def test_relations(ops_test: OpsTest, charm): """Test that check relation data.""" @@ -29,12 +30,13 @@ async def test_relations(ops_test: OpsTest, charm): charm, application_name=DATABASE_APP_NAME, num_units=1, + base=CHARM_BASE, config={"profile": "testing"}, ) await ops_test.model.wait_for_idle(apps=[DATABASE_APP_NAME], status="active", timeout=3000) # Creating first time relation with user role - await ops_test.model.deploy(DATA_INTEGRATOR_APP_NAME) + await ops_test.model.deploy(DATA_INTEGRATOR_APP_NAME, base=CHARM_BASE) await ops_test.model.applications[DATA_INTEGRATOR_APP_NAME].set_config({ "database-name": DATA_INTEGRATOR_APP_NAME.replace("-", "_"), }) @@ -125,14 +127,14 @@ async def test_relations(ops_test: OpsTest, charm): for database in [ DATA_INTEGRATOR_APP_NAME.replace("-", "_"), - "postgres", + DATABASE_DEFAULT_NAME, ]: logger.info(f"connecting to the following database: {database}") connection_string = await build_connection_string( ops_test, DATA_INTEGRATOR_APP_NAME, "postgresql", database=database ) connection = None - should_fail = database == "postgres" + should_fail = database == DATABASE_DEFAULT_NAME try: with ( psycopg2.connect(connection_string) as connection, @@ -142,7 +144,7 @@ async def test_relations(ops_test: OpsTest, charm): data = cursor.fetchone() assert data[0] == "some data" - # Write some data (it should fail in the "postgres" database). + # Write some data (it should fail in the default database name). random_name = f"test_{''.join(secrets.choice(string.ascii_lowercase) for _ in range(10))}" cursor.execute(f"CREATE TABLE {random_name}(data TEXT);") if should_fail: diff --git a/tests/integration/relations/__init__.py b/tests/integration/relations/__init__.py deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/tests/integration/relations/helpers.py b/tests/integration/relations/helpers.py deleted file mode 100644 index 1bafe6e79a..0000000000 --- a/tests/integration/relations/helpers.py +++ /dev/null @@ -1,52 +0,0 @@ -#!/usr/bin/env python3 -# Copyright 2024 Canonical Ltd. -# See LICENSE file for licensing details. - -import yaml -from pytest_operator.plugin import OpsTest - - -async def get_legacy_db_connection_str( - ops_test: OpsTest, - application_name: str, - relation_name: str, - read_only_endpoint: bool = False, - remote_unit_name: str | None = None, -) -> str | None: - """Returns a PostgreSQL connection string. - - Args: - ops_test: The ops test framework instance - application_name: The name of the application - relation_name: name of the relation to get connection data from - read_only_endpoint: whether to choose the read-only endpoint - instead of the read/write endpoint - remote_unit_name: Optional remote unit name used to retrieve - unit data instead of application data - - Returns: - a PostgreSQL connection string - """ - unit_name = f"{application_name}/0" - raw_data = (await ops_test.juju("show-unit", unit_name))[1] - if not raw_data: - raise ValueError(f"no unit info could be grabbed for {unit_name}") - data = yaml.safe_load(raw_data) - # Filter the data based on the relation name. - relation_data = [ - v for v in data[unit_name]["relation-info"] if v["related-endpoint"] == relation_name - ] - if len(relation_data) == 0: - raise ValueError( - f"no relation data could be grabbed on relation with endpoint {relation_name}" - ) - if remote_unit_name: - data = relation_data[0]["related-units"][remote_unit_name]["data"] - else: - data = relation_data[0]["application-data"] - if read_only_endpoint: - if data.get("standbys") is None: - return None - return data.get("standbys").split(",")[0] - else: - return data.get("master") diff --git a/tests/integration/relations/test_relations.py b/tests/integration/relations/test_relations.py deleted file mode 100644 index c3d08d0210..0000000000 --- a/tests/integration/relations/test_relations.py +++ /dev/null @@ -1,138 +0,0 @@ -#!/usr/bin/env python3 -# Copyright 2024 Canonical Ltd. -# See LICENSE file for licensing details. -import asyncio -import logging - -import psycopg2 -import pytest -from pytest_operator.plugin import OpsTest -from tenacity import Retrying, stop_after_delay, wait_fixed - -from ..helpers import METADATA -from ..new_relations.test_new_relations import APPLICATION_APP_NAME, build_connection_string -from ..relations.helpers import get_legacy_db_connection_str - -logger = logging.getLogger(__name__) - -APP_NAME = METADATA["name"] -# MAILMAN3_CORE_APP_NAME = "mailman3-core" -DB_RELATION = "db" -DATABASE_RELATION = "database" -FIRST_DATABASE_RELATION = "database" -DATABASE_APP_NAME = "database-app" -DB_APP_NAME = "db-app" -APP_NAMES = [APP_NAME, DATABASE_APP_NAME, DB_APP_NAME] - - -@pytest.mark.group(1) -@pytest.mark.abort_on_fail -async def test_deploy_charms(ops_test: OpsTest, charm): - """Deploy both charms (application and database) to use in the tests.""" - # Deploy both charms (multiple units for each application to test that later they correctly - # set data in the relation application databag using only the leader unit). - async with ops_test.fast_forward(): - await asyncio.gather( - ops_test.model.deploy( - APPLICATION_APP_NAME, - application_name=DATABASE_APP_NAME, - num_units=1, - channel="edge", - ), - ops_test.model.deploy( - charm, - application_name=APP_NAME, - num_units=1, - config={ - "profile": "testing", - "plugin_unaccent_enable": "True", - "plugin_pg_trgm_enable": "True", - }, - ), - ops_test.model.deploy( - APPLICATION_APP_NAME, - application_name=DB_APP_NAME, - num_units=1, - channel="edge", - ), - ) - - await ops_test.model.wait_for_idle(apps=APP_NAMES, status="active", timeout=3000) - - -@pytest.mark.group(1) -async def test_legacy_endpoint_with_multiple_related_endpoints(ops_test: OpsTest): - await ops_test.model.relate(f"{DB_APP_NAME}:{DB_RELATION}", f"{APP_NAME}:{DB_RELATION}") - await ops_test.model.relate(APP_NAME, f"{DATABASE_APP_NAME}:{FIRST_DATABASE_RELATION}") - - app = ops_test.model.applications[APP_NAME] - await ops_test.model.block_until( - lambda: "blocked" in {unit.workload_status for unit in app.units}, - timeout=1500, - ) - - logger.info(" remove relation with modern endpoints") - await ops_test.model.applications[APP_NAME].remove_relation( - f"{APP_NAME}:{DATABASE_RELATION}", f"{DATABASE_APP_NAME}:{FIRST_DATABASE_RELATION}" - ) - async with ops_test.fast_forward(): - await ops_test.model.wait_for_idle( - status="active", - timeout=1500, - raise_on_error=False, - ) - - legacy_interface_connect = await get_legacy_db_connection_str( - ops_test, DB_APP_NAME, DB_RELATION, remote_unit_name=f"{APP_NAME}/0" - ) - logger.info(f" check connect to = {legacy_interface_connect}") - for attempt in Retrying(stop=stop_after_delay(60 * 3), wait=wait_fixed(10)): - with attempt, psycopg2.connect(legacy_interface_connect) as connection: - assert connection.status == psycopg2.extensions.STATUS_READY - - logger.info(f" remove relation {DB_APP_NAME}:{DB_RELATION}") - async with ops_test.fast_forward(): - await ops_test.model.applications[APP_NAME].remove_relation( - f"{APP_NAME}:{DB_RELATION}", f"{DB_APP_NAME}:{DB_RELATION}" - ) - await ops_test.model.wait_for_idle(apps=[APP_NAME], status="active", timeout=1000) - for attempt in Retrying(stop=stop_after_delay(60 * 5), wait=wait_fixed(10)): - with attempt, pytest.raises(psycopg2.OperationalError): - psycopg2.connect(legacy_interface_connect) - - -@pytest.mark.group(1) -async def test_modern_endpoint_with_multiple_related_endpoints(ops_test: OpsTest): - await ops_test.model.relate(f"{DB_APP_NAME}:{DB_RELATION}", f"{APP_NAME}:{DB_RELATION}") - await ops_test.model.relate(APP_NAME, f"{DATABASE_APP_NAME}:{FIRST_DATABASE_RELATION}") - - app = ops_test.model.applications[APP_NAME] - await ops_test.model.block_until( - lambda: "blocked" in {unit.workload_status for unit in app.units}, - timeout=1500, - ) - - logger.info(" remove relation with legacy endpoints") - await ops_test.model.applications[APP_NAME].remove_relation( - f"{DB_APP_NAME}:{DB_RELATION}", f"{APP_NAME}:{DB_RELATION}" - ) - async with ops_test.fast_forward(): - await ops_test.model.wait_for_idle(status="active", timeout=3000, raise_on_error=False) - - modern_interface_connect = await build_connection_string( - ops_test, DATABASE_APP_NAME, FIRST_DATABASE_RELATION - ) - logger.info(f"check connect to = {modern_interface_connect}") - for attempt in Retrying(stop=stop_after_delay(60 * 3), wait=wait_fixed(10)): - with attempt, psycopg2.connect(modern_interface_connect) as connection: - assert connection.status == psycopg2.extensions.STATUS_READY - - logger.info(f"remove relation {DATABASE_APP_NAME}:{FIRST_DATABASE_RELATION}") - async with ops_test.fast_forward(): - await ops_test.model.applications[APP_NAME].remove_relation( - f"{APP_NAME}:{DATABASE_RELATION}", f"{DATABASE_APP_NAME}:{FIRST_DATABASE_RELATION}" - ) - await ops_test.model.wait_for_idle(apps=[APP_NAME], status="active", timeout=1000) - for attempt in Retrying(stop=stop_after_delay(60 * 5), wait=wait_fixed(10)): - with attempt, pytest.raises(psycopg2.OperationalError): - psycopg2.connect(modern_interface_connect) diff --git a/tests/integration/test_audit.py b/tests/integration/test_audit.py index 257ef9cf70..4b4c3ae5e0 100644 --- a/tests/integration/test_audit.py +++ b/tests/integration/test_audit.py @@ -21,7 +21,6 @@ RELATION_ENDPOINT = "database" -@pytest.mark.group(1) @pytest.mark.abort_on_fail async def test_audit_plugin(ops_test: OpsTest, charm) -> None: """Test the audit plugin.""" diff --git a/tests/integration/test_backups_aws.py b/tests/integration/test_backups_aws.py new file mode 100644 index 0000000000..a6cd122393 --- /dev/null +++ b/tests/integration/test_backups_aws.py @@ -0,0 +1,111 @@ +#!/usr/bin/env python3 +# Copyright 2023 Canonical Ltd. +# See LICENSE file for licensing details. +import logging + +import pytest as pytest +from pytest_operator.plugin import OpsTest +from tenacity import Retrying, stop_after_attempt, wait_exponential + +from . import architecture +from .conftest import AWS +from .helpers import ( + DATABASE_APP_NAME, + backup_operations, + db_connect, + get_password, + get_primary, + get_unit_address, + scale_application, + switchover, +) + +ANOTHER_CLUSTER_REPOSITORY_ERROR_MESSAGE = "the S3 repository has backups from another cluster" +FAILED_TO_ACCESS_CREATE_BUCKET_ERROR_MESSAGE = ( + "failed to access/create the bucket, check your S3 settings" +) +S3_INTEGRATOR_APP_NAME = "s3-integrator" +tls_certificates_app_name = "self-signed-certificates" +tls_channel = "latest/edge" if architecture.architecture == "arm64" else "latest/stable" +tls_config = {"ca-common-name": "Test CA"} + +logger = logging.getLogger(__name__) + + +@pytest.mark.abort_on_fail +async def test_backup_aws(ops_test: OpsTest, aws_cloud_configs: tuple[dict, dict], charm) -> None: + """Build and deploy two units of PostgreSQL in AWS, test backup and restore actions.""" + config = aws_cloud_configs[0] + credentials = aws_cloud_configs[1] + + await backup_operations( + ops_test, + S3_INTEGRATOR_APP_NAME, + tls_certificates_app_name, + tls_config, + tls_channel, + credentials, + AWS, + config, + charm, + ) + database_app_name = f"{DATABASE_APP_NAME}-aws" + + # Remove the relation to the TLS certificates operator. + await ops_test.model.applications[database_app_name].remove_relation( + f"{database_app_name}:certificates", f"{tls_certificates_app_name}:certificates" + ) + + new_unit_name = f"{database_app_name}/2" + + # Scale up to be able to test primary and leader being different. + async with ops_test.fast_forward(): + await scale_application(ops_test, database_app_name, 2) + + # Ensure replication is working correctly. + address = get_unit_address(ops_test, new_unit_name) + password = await get_password(ops_test, new_unit_name) + patroni_password = await get_password(ops_test, new_unit_name, "patroni") + with db_connect(host=address, password=password) as connection, connection.cursor() as cursor: + cursor.execute( + "SELECT EXISTS (SELECT FROM information_schema.tables" + " WHERE table_schema = 'public' AND table_name = 'backup_table_1');" + ) + assert cursor.fetchone()[0], ( + f"replication isn't working correctly: table 'backup_table_1' doesn't exist in {new_unit_name}" + ) + cursor.execute( + "SELECT EXISTS (SELECT FROM information_schema.tables" + " WHERE table_schema = 'public' AND table_name = 'backup_table_2');" + ) + assert not cursor.fetchone()[0], ( + f"replication isn't working correctly: table 'backup_table_2' exists in {new_unit_name}" + ) + connection.close() + + old_primary = await get_primary(ops_test, new_unit_name) + switchover(ops_test, old_primary, patroni_password, new_unit_name) + + # Get the new primary unit. + primary = await get_primary(ops_test, new_unit_name) + # Check that the primary changed. + for attempt in Retrying( + stop=stop_after_attempt(3), wait=wait_exponential(multiplier=1, min=2, max=30) + ): + with attempt: + assert primary == new_unit_name + + # Ensure stanza is working correctly. + logger.info("listing the available backups") + action = await ops_test.model.units.get(new_unit_name).run_action("list-backups") + await action.wait() + backups = action.results.get("backups") + assert backups, "backups not outputted" + + await ops_test.model.wait_for_idle(status="active", timeout=1000) + + # Remove the database app. + await ops_test.model.remove_application(database_app_name, block_until_done=True) + + # Remove the TLS operator. + await ops_test.model.remove_application(tls_certificates_app_name, block_until_done=True) diff --git a/tests/integration/test_backups_ceph.py b/tests/integration/test_backups_ceph.py index 99a57fb1e1..3b3741e37a 100644 --- a/tests/integration/test_backups_ceph.py +++ b/tests/integration/test_backups_ceph.py @@ -18,19 +18,13 @@ from .helpers import ( backup_operations, ) -from .juju_ import juju_major_version logger = logging.getLogger(__name__) S3_INTEGRATOR_APP_NAME = "s3-integrator" -if juju_major_version < 3: - tls_certificates_app_name = "tls-certificates-operator" - tls_channel = "legacy/edge" if architecture.architecture == "arm64" else "legacy/stable" - tls_config = {"generate-self-signed-certificates": "true", "ca-common-name": "Test CA"} -else: - tls_certificates_app_name = "self-signed-certificates" - tls_channel = "latest/edge" if architecture.architecture == "arm64" else "latest/stable" - tls_config = {"ca-common-name": "Test CA"} +tls_certificates_app_name = "self-signed-certificates" +tls_channel = "latest/edge" if architecture.architecture == "arm64" else "latest/stable" +tls_config = {"ca-common-name": "Test CA"} backup_id, value_before_backup, value_after_backup = "", None, None @@ -190,7 +184,6 @@ def cloud_configs(microceph: ConnectionInformation): } -@pytest.mark.group("ceph") @markers.amd64_only async def test_backup_ceph(ops_test: OpsTest, cloud_configs, cloud_credentials, charm) -> None: """Build and deploy two units of PostgreSQL in microceph, test backup and restore actions.""" diff --git a/tests/integration/test_backups.py b/tests/integration/test_backups_gcp.py similarity index 55% rename from tests/integration/test_backups.py rename to tests/integration/test_backups_gcp.py index d71497f936..3f8aa3111f 100644 --- a/tests/integration/test_backups.py +++ b/tests/integration/test_backups_gcp.py @@ -4,177 +4,39 @@ import logging import uuid -import boto3 import pytest as pytest from pytest_operator.plugin import OpsTest from tenacity import Retrying, stop_after_attempt, wait_exponential from . import architecture +from .conftest import GCP from .helpers import ( + CHARM_BASE, DATABASE_APP_NAME, backup_operations, - construct_endpoint, db_connect, get_password, - get_primary, get_unit_address, - scale_application, - switchover, wait_for_idle_on_blocked, ) -from .juju_ import juju_major_version ANOTHER_CLUSTER_REPOSITORY_ERROR_MESSAGE = "the S3 repository has backups from another cluster" FAILED_TO_ACCESS_CREATE_BUCKET_ERROR_MESSAGE = ( "failed to access/create the bucket, check your S3 settings" ) S3_INTEGRATOR_APP_NAME = "s3-integrator" -if juju_major_version < 3: - tls_certificates_app_name = "tls-certificates-operator" - tls_channel = "legacy/edge" if architecture.architecture == "arm64" else "legacy/stable" - tls_config = {"generate-self-signed-certificates": "true", "ca-common-name": "Test CA"} -else: - tls_certificates_app_name = "self-signed-certificates" - tls_channel = "latest/edge" if architecture.architecture == "arm64" else "latest/stable" - tls_config = {"ca-common-name": "Test CA"} +tls_certificates_app_name = "self-signed-certificates" +tls_channel = "latest/edge" if architecture.architecture == "arm64" else "latest/stable" +tls_config = {"ca-common-name": "Test CA"} logger = logging.getLogger(__name__) -AWS = "AWS" -GCP = "GCP" - -@pytest.fixture(scope="module") -async def cloud_configs(github_secrets) -> None: - # Define some configurations and credentials. - configs = { - AWS: { - "endpoint": "https://s3.amazonaws.com", - "bucket": "data-charms-testing", - "path": f"/postgresql-vm/{uuid.uuid1()}", - "region": "us-east-1", - }, - GCP: { - "endpoint": "https://storage.googleapis.com", - "bucket": "data-charms-testing", - "path": f"/postgresql-vm/{uuid.uuid1()}", - "region": "", - }, - } - credentials = { - AWS: { - "access-key": github_secrets["AWS_ACCESS_KEY"], - "secret-key": github_secrets["AWS_SECRET_KEY"], - }, - GCP: { - "access-key": github_secrets["GCP_ACCESS_KEY"], - "secret-key": github_secrets["GCP_SECRET_KEY"], - }, - } - yield configs, credentials - # Delete the previously created objects. - logger.info("deleting the previously created backups") - for cloud, config in configs.items(): - session = boto3.session.Session( - aws_access_key_id=credentials[cloud]["access-key"], - aws_secret_access_key=credentials[cloud]["secret-key"], - region_name=config["region"], - ) - s3 = session.resource( - "s3", endpoint_url=construct_endpoint(config["endpoint"], config["region"]) - ) - bucket = s3.Bucket(config["bucket"]) - # GCS doesn't support batch delete operation, so delete the objects one by one. - for bucket_object in bucket.objects.filter(Prefix=config["path"].lstrip("/")): - bucket_object.delete() - - -@pytest.mark.group("AWS") @pytest.mark.abort_on_fail -async def test_backup_aws(ops_test: OpsTest, cloud_configs: tuple[dict, dict], charm) -> None: - """Build and deploy two units of PostgreSQL in AWS, test backup and restore actions.""" - config = cloud_configs[0][AWS] - credentials = cloud_configs[1][AWS] - - await backup_operations( - ops_test, - S3_INTEGRATOR_APP_NAME, - tls_certificates_app_name, - tls_config, - tls_channel, - credentials, - AWS, - config, - charm, - ) - database_app_name = f"{DATABASE_APP_NAME}-aws" - - # Remove the relation to the TLS certificates operator. - await ops_test.model.applications[database_app_name].remove_relation( - f"{database_app_name}:certificates", f"{tls_certificates_app_name}:certificates" - ) - - new_unit_name = f"{database_app_name}/2" - - # Scale up to be able to test primary and leader being different. - async with ops_test.fast_forward(): - await scale_application(ops_test, database_app_name, 2) - - # Ensure replication is working correctly. - address = get_unit_address(ops_test, new_unit_name) - password = await get_password(ops_test, new_unit_name) - patroni_password = await get_password(ops_test, new_unit_name, "patroni") - with db_connect(host=address, password=password) as connection, connection.cursor() as cursor: - cursor.execute( - "SELECT EXISTS (SELECT FROM information_schema.tables" - " WHERE table_schema = 'public' AND table_name = 'backup_table_1');" - ) - assert cursor.fetchone()[0], ( - f"replication isn't working correctly: table 'backup_table_1' doesn't exist in {new_unit_name}" - ) - cursor.execute( - "SELECT EXISTS (SELECT FROM information_schema.tables" - " WHERE table_schema = 'public' AND table_name = 'backup_table_2');" - ) - assert not cursor.fetchone()[0], ( - f"replication isn't working correctly: table 'backup_table_2' exists in {new_unit_name}" - ) - connection.close() - - old_primary = await get_primary(ops_test, new_unit_name) - switchover(ops_test, old_primary, patroni_password, new_unit_name) - - # Get the new primary unit. - primary = await get_primary(ops_test, new_unit_name) - # Check that the primary changed. - for attempt in Retrying( - stop=stop_after_attempt(3), wait=wait_exponential(multiplier=1, min=2, max=30) - ): - with attempt: - assert primary == new_unit_name - - # Ensure stanza is working correctly. - logger.info("listing the available backups") - action = await ops_test.model.units.get(new_unit_name).run_action("list-backups") - await action.wait() - backups = action.results.get("backups") - assert backups, "backups not outputted" - - await ops_test.model.wait_for_idle(status="active", timeout=1000) - - # Remove the database app. - await ops_test.model.remove_application(database_app_name, block_until_done=True) - - # Remove the TLS operator. - await ops_test.model.remove_application(tls_certificates_app_name, block_until_done=True) - - -@pytest.mark.group("GCP") -@pytest.mark.abort_on_fail -async def test_backup_gcp(ops_test: OpsTest, cloud_configs: tuple[dict, dict], charm) -> None: +async def test_backup_gcp(ops_test: OpsTest, gcp_cloud_configs: tuple[dict, dict], charm) -> None: """Build and deploy two units of PostgreSQL in GCP, test backup and restore actions.""" - config = cloud_configs[0][GCP] - credentials = cloud_configs[1][GCP] + config = gcp_cloud_configs[0] + credentials = gcp_cloud_configs[1] await backup_operations( ops_test, @@ -196,19 +58,22 @@ async def test_backup_gcp(ops_test: OpsTest, cloud_configs: tuple[dict, dict], c await ops_test.model.remove_application(tls_certificates_app_name, block_until_done=True) -@pytest.mark.group("GCP") -async def test_restore_on_new_cluster(ops_test: OpsTest, github_secrets, charm) -> None: +async def test_restore_on_new_cluster( + ops_test: OpsTest, charm, gcp_cloud_configs: tuple[dict, dict] +) -> None: """Test that is possible to restore a backup to another PostgreSQL cluster.""" previous_database_app_name = f"{DATABASE_APP_NAME}-gcp" database_app_name = f"new-{DATABASE_APP_NAME}" await ops_test.model.deploy( charm, application_name=previous_database_app_name, + base=CHARM_BASE, config={"profile": "testing"}, ) await ops_test.model.deploy( charm, application_name=database_app_name, + base=CHARM_BASE, config={"profile": "testing"}, ) await ops_test.model.relate(previous_database_app_name, S3_INTEGRATOR_APP_NAME) @@ -292,9 +157,8 @@ async def test_restore_on_new_cluster(ops_test: OpsTest, github_secrets, charm) connection.close() -@pytest.mark.group("GCP") async def test_invalid_config_and_recovery_after_fixing_it( - ops_test: OpsTest, cloud_configs: tuple[dict, dict] + ops_test: OpsTest, gcp_cloud_configs: tuple[dict, dict] ) -> None: """Test that the charm can handle invalid and valid backup configurations.""" database_app_name = f"new-{DATABASE_APP_NAME}" @@ -325,10 +189,10 @@ async def test_invalid_config_and_recovery_after_fixing_it( logger.info( "configuring S3 integrator for a valid cloud, but with the path of another cluster repository" ) - await ops_test.model.applications[S3_INTEGRATOR_APP_NAME].set_config(cloud_configs[0][GCP]) + await ops_test.model.applications[S3_INTEGRATOR_APP_NAME].set_config(gcp_cloud_configs[0]) action = await ops_test.model.units.get(f"{S3_INTEGRATOR_APP_NAME}/0").run_action( "sync-s3-credentials", - **cloud_configs[1][GCP], + **gcp_cloud_configs[1], ) await action.wait() logger.info("waiting for the database charm to become blocked") @@ -339,7 +203,7 @@ async def test_invalid_config_and_recovery_after_fixing_it( # Provide valid backup configurations, with another path in the S3 bucket. logger.info("configuring S3 integrator for a valid cloud") - config = cloud_configs[0][GCP].copy() + config = gcp_cloud_configs[0].copy() config["path"] = f"/postgresql/{uuid.uuid1()}" await ops_test.model.applications[S3_INTEGRATOR_APP_NAME].set_config(config) logger.info("waiting for the database charm to become active") diff --git a/tests/integration/test_backups_pitr.py b/tests/integration/test_backups_pitr_aws.py similarity index 82% rename from tests/integration/test_backups_pitr.py rename to tests/integration/test_backups_pitr_aws.py index 4398a3aec6..3938912d06 100644 --- a/tests/integration/test_backups_pitr.py +++ b/tests/integration/test_backups_pitr_aws.py @@ -2,85 +2,30 @@ # Copyright 2024 Canonical Ltd. # See LICENSE file for licensing details. import logging -import uuid -import boto3 import pytest as pytest from pytest_operator.plugin import OpsTest from tenacity import Retrying, stop_after_attempt, wait_exponential from . import architecture +from .conftest import AWS from .helpers import ( + CHARM_BASE, DATABASE_APP_NAME, - construct_endpoint, db_connect, get_password, get_primary, get_unit_address, ) -from .juju_ import juju_major_version CANNOT_RESTORE_PITR = "cannot restore PITR, juju debug-log for details" S3_INTEGRATOR_APP_NAME = "s3-integrator" -if juju_major_version < 3: - TLS_CERTIFICATES_APP_NAME = "tls-certificates-operator" - TLS_CHANNEL = "legacy/edge" if architecture.architecture == "arm64" else "legacy/stable" - TLS_CONFIG = {"generate-self-signed-certificates": "true", "ca-common-name": "Test CA"} -else: - TLS_CERTIFICATES_APP_NAME = "self-signed-certificates" - TLS_CHANNEL = "latest/edge" if architecture.architecture == "arm64" else "latest/stable" - TLS_CONFIG = {"ca-common-name": "Test CA"} +TLS_CERTIFICATES_APP_NAME = "self-signed-certificates" +TLS_CHANNEL = "latest/edge" if architecture.architecture == "arm64" else "latest/stable" +TLS_CONFIG = {"ca-common-name": "Test CA"} logger = logging.getLogger(__name__) -AWS = "AWS" -GCP = "GCP" - - -@pytest.fixture(scope="module") -async def cloud_configs(github_secrets) -> None: - # Define some configurations and credentials. - configs = { - AWS: { - "endpoint": "https://s3.amazonaws.com", - "bucket": "data-charms-testing", - "path": f"/postgresql-vm/{uuid.uuid1()}", - "region": "us-east-1", - }, - GCP: { - "endpoint": "https://storage.googleapis.com", - "bucket": "data-charms-testing", - "path": f"/postgresql-vm/{uuid.uuid1()}", - "region": "", - }, - } - credentials = { - AWS: { - "access-key": github_secrets["AWS_ACCESS_KEY"], - "secret-key": github_secrets["AWS_SECRET_KEY"], - }, - GCP: { - "access-key": github_secrets["GCP_ACCESS_KEY"], - "secret-key": github_secrets["GCP_SECRET_KEY"], - }, - } - yield configs, credentials - # Delete the previously created objects. - logger.info("deleting the previously created backups") - for cloud, config in configs.items(): - session = boto3.session.Session( - aws_access_key_id=credentials[cloud]["access-key"], - aws_secret_access_key=credentials[cloud]["secret-key"], - region_name=config["region"], - ) - s3 = session.resource( - "s3", endpoint_url=construct_endpoint(config["endpoint"], config["region"]) - ) - bucket = s3.Bucket(config["bucket"]) - # GCS doesn't support batch delete operation, so delete the objects one by one. - for bucket_object in bucket.objects.filter(Prefix=config["path"].lstrip("/")): - bucket_object.delete() - async def pitr_backup_operations( ops_test: OpsTest, @@ -111,6 +56,7 @@ async def pitr_backup_operations( charm, application_name=database_app_name, num_units=2, + base=CHARM_BASE, config={"profile": "testing"}, ) @@ -372,12 +318,12 @@ async def pitr_backup_operations( await ops_test.model.remove_application(tls_certificates_app_name, block_until_done=True) -@pytest.mark.group("AWS") @pytest.mark.abort_on_fail -async def test_pitr_backup_aws(ops_test: OpsTest, cloud_configs: tuple[dict, dict], charm) -> None: +async def test_pitr_backup_aws( + ops_test: OpsTest, gcp_cloud_configs: tuple[dict, dict], charm +) -> None: """Build, deploy two units of PostgreSQL and do backup in AWS. Then, write new data into DB, switch WAL file and test point-in-time-recovery restore action.""" - config = cloud_configs[0][AWS] - credentials = cloud_configs[1][AWS] + config, credentials = gcp_cloud_configs await pitr_backup_operations( ops_test, @@ -392,26 +338,6 @@ async def test_pitr_backup_aws(ops_test: OpsTest, cloud_configs: tuple[dict, dic ) -@pytest.mark.group("GCP") -@pytest.mark.abort_on_fail -async def test_pitr_backup_gcp(ops_test: OpsTest, cloud_configs: tuple[dict, dict], charm) -> None: - """Build, deploy two units of PostgreSQL and do backup in GCP. Then, write new data into DB, switch WAL file and test point-in-time-recovery restore action.""" - config = cloud_configs[0][GCP] - credentials = cloud_configs[1][GCP] - - await pitr_backup_operations( - ops_test, - S3_INTEGRATOR_APP_NAME, - TLS_CERTIFICATES_APP_NAME, - TLS_CONFIG, - TLS_CHANNEL, - credentials, - GCP, - config, - charm, - ) - - def _create_table(host: str, password: str): with db_connect(host=host, password=password) as connection: connection.autocommit = True diff --git a/tests/integration/test_backups_pitr_gcp.py b/tests/integration/test_backups_pitr_gcp.py new file mode 100644 index 0000000000..4e5e03d582 --- /dev/null +++ b/tests/integration/test_backups_pitr_gcp.py @@ -0,0 +1,384 @@ +#!/usr/bin/env python3 +# Copyright 2024 Canonical Ltd. +# See LICENSE file for licensing details. +import logging + +import pytest as pytest +from pytest_operator.plugin import OpsTest +from tenacity import Retrying, stop_after_attempt, wait_exponential + +from . import architecture +from .conftest import GCP +from .helpers import ( + CHARM_BASE, + DATABASE_APP_NAME, + db_connect, + get_password, + get_primary, + get_unit_address, +) + +CANNOT_RESTORE_PITR = "cannot restore PITR, juju debug-log for details" +S3_INTEGRATOR_APP_NAME = "s3-integrator" +TLS_CERTIFICATES_APP_NAME = "self-signed-certificates" +TLS_CHANNEL = "latest/edge" if architecture.architecture == "arm64" else "latest/stable" +TLS_CONFIG = {"ca-common-name": "Test CA"} + +logger = logging.getLogger(__name__) + + +async def pitr_backup_operations( + ops_test: OpsTest, + s3_integrator_app_name: str, + tls_certificates_app_name: str, + tls_config, + tls_channel, + credentials, + cloud, + config, + charm, +) -> None: + """Basic set of operations for PITR backup and timelines management testing. + + Below is presented algorithm in the next format: "(timeline): action_1 -> action_2". + 1: table -> backup_b1 -> test_data_td1 -> timestamp_ts1 -> test_data_td2 -> restore_ts1 => 2 + 2: check_td1 -> check_not_td2 -> test_data_td3 -> restore_b1_latest => 3 + 3: check_td1 -> check_td2 -> check_not_td3 -> test_data_td4 -> restore_t2_latest => 4 + 4: check_td1 -> check_not_td2 -> check_td3 -> check_not_td4 + """ + # Set-up environment + database_app_name = f"{DATABASE_APP_NAME}-{cloud.lower()}" + + logger.info("deploying the next charms: s3-integrator, self-signed-certificates, postgresql") + await ops_test.model.deploy(s3_integrator_app_name) + await ops_test.model.deploy(tls_certificates_app_name, config=tls_config, channel=tls_channel) + await ops_test.model.deploy( + charm, + application_name=database_app_name, + num_units=2, + base=CHARM_BASE, + config={"profile": "testing"}, + ) + + logger.info( + "integrating self-signed-certificates with postgresql and waiting them to stabilize" + ) + await ops_test.model.relate(database_app_name, tls_certificates_app_name) + async with ops_test.fast_forward(fast_interval="60s"): + await ops_test.model.wait_for_idle( + apps=[database_app_name, tls_certificates_app_name], status="active", timeout=1000 + ) + + logger.info(f"configuring s3-integrator for {cloud}") + await ops_test.model.applications[s3_integrator_app_name].set_config(config) + action = await ops_test.model.units.get(f"{s3_integrator_app_name}/0").run_action( + "sync-s3-credentials", + **credentials, + ) + await action.wait() + + logger.info("integrating s3-integrator with postgresql and waiting model to stabilize") + await ops_test.model.relate(database_app_name, s3_integrator_app_name) + async with ops_test.fast_forward(fast_interval="60s"): + await ops_test.model.wait_for_idle(status="active", timeout=1000) + + primary = await get_primary(ops_test, f"{database_app_name}/0") + for unit in ops_test.model.applications[database_app_name].units: + if unit.name != primary: + replica = unit.name + break + password = await get_password(ops_test, primary) + address = get_unit_address(ops_test, primary) + + logger.info("1: creating table") + _create_table(address, password) + + logger.info("1: creating backup b1") + action = await ops_test.model.units.get(replica).run_action("create-backup") + await action.wait() + backup_status = action.results.get("backup-status") + assert backup_status, "backup hasn't succeeded" + await ops_test.model.wait_for_idle(status="active", timeout=1000) + backup_b1 = await _get_most_recent_backup(ops_test, ops_test.model.units.get(replica)) + + logger.info("1: creating test data td1") + _insert_test_data("test_data_td1", address, password) + + logger.info("1: get timestamp ts1") + with db_connect(host=address, password=password) as connection, connection.cursor() as cursor: + cursor.execute("SELECT current_timestamp;") + timestamp_ts1 = str(cursor.fetchone()[0]) + connection.close() + # Wrong timestamp pointing to one year ahead + unreachable_timestamp_ts1 = timestamp_ts1.replace( + timestamp_ts1[:4], str(int(timestamp_ts1[:4]) + 1), 1 + ) + + logger.info("1: creating test data td2") + _insert_test_data("test_data_td2", address, password) + + logger.info("1: switching wal") + _switch_wal(address, password) + + logger.info("1: scaling down to do restore") + async with ops_test.fast_forward(): + await ops_test.model.destroy_unit(replica) + await ops_test.model.wait_for_idle(status="active", timeout=1000) + for unit in ops_test.model.applications[database_app_name].units: + remaining_unit = unit + break + + logger.info("1: restoring the backup b1 with bad restore-to-time parameter") + action = await remaining_unit.run_action( + "restore", **{"backup-id": backup_b1, "restore-to-time": "bad data"} + ) + await action.wait() + assert action.status == "failed", ( + "1: restore must fail with bad restore-to-time parameter, but that action succeeded" + ) + + logger.info("1: restoring the backup b1 with unreachable restore-to-time parameter") + action = await remaining_unit.run_action( + "restore", **{"backup-id": backup_b1, "restore-to-time": unreachable_timestamp_ts1} + ) + await action.wait() + logger.info("1: waiting for the database charm to become blocked after restore") + async with ops_test.fast_forward(): + await ops_test.model.block_until( + lambda: remaining_unit.workload_status_message == CANNOT_RESTORE_PITR, + timeout=1000, + ) + logger.info( + "1: database charm become in blocked state after restore, as supposed to be with unreachable PITR parameter" + ) + + for attempt in Retrying( + stop=stop_after_attempt(10), wait=wait_exponential(multiplier=1, min=2, max=30) + ): + with attempt: + logger.info("1: restoring to the timestamp ts1") + action = await remaining_unit.run_action( + "restore", **{"restore-to-time": timestamp_ts1} + ) + await action.wait() + restore_status = action.results.get("restore-status") + assert restore_status, "1: restore to the timestamp ts1 hasn't succeeded" + await ops_test.model.wait_for_idle(status="active", timeout=1000, idle_period=30) + + logger.info("2: successful restore") + primary = await get_primary(ops_test, remaining_unit.name) + address = get_unit_address(ops_test, primary) + timeline_t2 = await _get_most_recent_backup(ops_test, remaining_unit) + assert backup_b1 != timeline_t2, "2: timeline 2 do not exist in list-backups action or bad" + + logger.info("2: checking test data td1") + assert _check_test_data("test_data_td1", address, password), "2: test data td1 should exist" + + logger.info("2: checking not test data td2") + assert not _check_test_data("test_data_td2", address, password), ( + "2: test data td2 shouldn't exist" + ) + + logger.info("2: creating test data td3") + _insert_test_data("test_data_td3", address, password) + + logger.info("2: get timestamp ts2") + with db_connect(host=address, password=password) as connection, connection.cursor() as cursor: + cursor.execute("SELECT current_timestamp;") + timestamp_ts2 = str(cursor.fetchone()[0]) + connection.close() + + logger.info("2: creating test data td4") + _insert_test_data("test_data_td4", address, password) + + logger.info("2: switching wal") + _switch_wal(address, password) + + for attempt in Retrying( + stop=stop_after_attempt(10), wait=wait_exponential(multiplier=1, min=2, max=30) + ): + with attempt: + logger.info("2: restoring the backup b1 to the latest") + action = await remaining_unit.run_action( + "restore", **{"backup-id": backup_b1, "restore-to-time": "latest"} + ) + await action.wait() + restore_status = action.results.get("restore-status") + assert restore_status, "2: restore the backup b1 to the latest hasn't succeeded" + await ops_test.model.wait_for_idle(status="active", timeout=1000, idle_period=30) + + logger.info("3: successful restore") + primary = await get_primary(ops_test, remaining_unit.name) + address = get_unit_address(ops_test, primary) + timeline_t3 = await _get_most_recent_backup(ops_test, remaining_unit) + assert backup_b1 != timeline_t3 and timeline_t2 != timeline_t3, ( + "3: timeline 3 do not exist in list-backups action or bad" + ) + + logger.info("3: checking test data td1") + assert _check_test_data("test_data_td1", address, password), "3: test data td1 should exist" + + logger.info("3: checking test data td2") + assert _check_test_data("test_data_td2", address, password), "3: test data td2 should exist" + + logger.info("3: checking not test data td3") + assert not _check_test_data("test_data_td3", address, password), ( + "3: test data td3 shouldn't exist" + ) + + logger.info("3: checking not test data td4") + assert not _check_test_data("test_data_td4", address, password), ( + "3: test data td4 shouldn't exist" + ) + + logger.info("3: switching wal") + _switch_wal(address, password) + + for attempt in Retrying( + stop=stop_after_attempt(10), wait=wait_exponential(multiplier=1, min=2, max=30) + ): + with attempt: + logger.info("3: restoring the timeline 2 to the latest") + action = await remaining_unit.run_action( + "restore", **{"backup-id": timeline_t2, "restore-to-time": "latest"} + ) + await action.wait() + restore_status = action.results.get("restore-status") + assert restore_status, "3: restore the timeline 2 to the latest hasn't succeeded" + await ops_test.model.wait_for_idle(status="active", timeout=1000, idle_period=30) + + logger.info("4: successful restore") + primary = await get_primary(ops_test, remaining_unit.name) + address = get_unit_address(ops_test, primary) + timeline_t4 = await _get_most_recent_backup(ops_test, remaining_unit) + assert ( + backup_b1 != timeline_t4 and timeline_t2 != timeline_t4 and timeline_t3 != timeline_t4 + ), "4: timeline 4 do not exist in list-backups action or bad" + + logger.info("4: checking test data td1") + assert _check_test_data("test_data_td1", address, password), "4: test data td1 should exist" + + logger.info("4: checking not test data td2") + assert not _check_test_data("test_data_td2", address, password), ( + "4: test data td2 shouldn't exist" + ) + + logger.info("4: checking test data td3") + assert _check_test_data("test_data_td3", address, password), "4: test data td3 should exist" + + logger.info("4: checking test data td4") + assert _check_test_data("test_data_td4", address, password), "4: test data td4 should exist" + + logger.info("4: switching wal") + _switch_wal(address, password) + + for attempt in Retrying( + stop=stop_after_attempt(10), wait=wait_exponential(multiplier=1, min=2, max=30) + ): + with attempt: + logger.info("4: restoring to the timestamp ts2") + action = await remaining_unit.run_action( + "restore", **{"restore-to-time": timestamp_ts2} + ) + await action.wait() + restore_status = action.results.get("restore-status") + assert restore_status, "4: restore to the timestamp ts2 hasn't succeeded" + await ops_test.model.wait_for_idle(status="active", timeout=1000, idle_period=30) + + logger.info("5: successful restore") + primary = await get_primary(ops_test, remaining_unit.name) + address = get_unit_address(ops_test, primary) + timeline_t5 = await _get_most_recent_backup(ops_test, remaining_unit) + assert ( + backup_b1 != timeline_t5 + and timeline_t2 != timeline_t5 + and timeline_t3 != timeline_t5 + and timeline_t4 != timeline_t5 + ), "5: timeline 5 do not exist in list-backups action or bad" + + logger.info("5: checking test data td1") + assert _check_test_data("test_data_td1", address, password), "5: test data td1 should exist" + + logger.info("5: checking not test data td2") + assert not _check_test_data("test_data_td2", address, password), ( + "5: test data td2 shouldn't exist" + ) + + logger.info("5: checking test data td3") + assert _check_test_data("test_data_td3", address, password), "5: test data td3 should exist" + + logger.info("5: checking not test data td4") + assert not _check_test_data("test_data_td4", address, password), ( + "5: test data td4 shouldn't exist" + ) + + # Remove the database app. + await ops_test.model.remove_application(database_app_name, block_until_done=True) + # Remove the TLS operator. + await ops_test.model.remove_application(tls_certificates_app_name, block_until_done=True) + + +@pytest.mark.abort_on_fail +async def test_pitr_backup_gcp( + ops_test: OpsTest, gcp_cloud_configs: tuple[dict, dict], charm +) -> None: + """Build, deploy two units of PostgreSQL and do backup in GCP. Then, write new data into DB, switch WAL file and test point-in-time-recovery restore action.""" + config, credentials = gcp_cloud_configs + + await pitr_backup_operations( + ops_test, + S3_INTEGRATOR_APP_NAME, + TLS_CERTIFICATES_APP_NAME, + TLS_CONFIG, + TLS_CHANNEL, + credentials, + GCP, + config, + charm, + ) + + +def _create_table(host: str, password: str): + with db_connect(host=host, password=password) as connection: + connection.autocommit = True + connection.cursor().execute("CREATE TABLE IF NOT EXISTS backup_table (test_column TEXT);") + connection.close() + + +def _insert_test_data(td: str, host: str, password: str): + with db_connect(host=host, password=password) as connection: + connection.autocommit = True + connection.cursor().execute( + "INSERT INTO backup_table (test_column) VALUES (%s);", + (td,), + ) + connection.close() + + +def _check_test_data(td: str, host: str, password: str) -> bool: + with db_connect(host=host, password=password) as connection, connection.cursor() as cursor: + cursor.execute( + "SELECT EXISTS (SELECT 1 FROM backup_table WHERE test_column = %s);", + (td,), + ) + res = cursor.fetchone()[0] + connection.close() + return res + + +def _switch_wal(host: str, password: str): + with db_connect(host=host, password=password) as connection: + connection.autocommit = True + connection.cursor().execute("SELECT pg_switch_wal();") + connection.close() + + +async def _get_most_recent_backup(ops_test: OpsTest, unit: any) -> str: + logger.info("listing the available backups") + action = await unit.run_action("list-backups") + await action.wait() + backups = action.results.get("backups") + assert backups, "backups not outputted" + await ops_test.model.wait_for_idle(status="active", timeout=1000) + most_recent_backup = backups.split("\n")[-1] + return most_recent_backup.split()[0] diff --git a/tests/integration/test_charm.py b/tests/integration/test_charm.py index 935670f196..743bbdb242 100644 --- a/tests/integration/test_charm.py +++ b/tests/integration/test_charm.py @@ -17,6 +17,7 @@ from .ha_tests.helpers import get_cluster_roles from .helpers import ( + CHARM_BASE, DATABASE_APP_NAME, STORAGE_PATH, check_cluster_members, @@ -36,7 +37,6 @@ UNIT_IDS = [0, 1, 2] -@pytest.mark.group(1) @pytest.mark.abort_on_fail @pytest.mark.skip_if_deployed async def test_deploy(ops_test: OpsTest, charm: str): @@ -49,6 +49,7 @@ async def test_deploy(ops_test: OpsTest, charm: str): charm, application_name=DATABASE_APP_NAME, num_units=3, + base=CHARM_BASE, config={"profile": "testing"}, ) @@ -59,7 +60,6 @@ async def test_deploy(ops_test: OpsTest, charm: str): assert ops_test.model.applications[DATABASE_APP_NAME].units[0].workload_status == "active" -@pytest.mark.group(1) @pytest.mark.abort_on_fail @pytest.mark.parametrize("unit_id", UNIT_IDS) async def test_database_is_up(ops_test: OpsTest, unit_id: int): @@ -70,7 +70,6 @@ async def test_database_is_up(ops_test: OpsTest, unit_id: int): assert result.status_code == 200 -@pytest.mark.group(1) @pytest.mark.parametrize("unit_id", UNIT_IDS) async def test_exporter_is_up(ops_test: OpsTest, unit_id: int): # Query Patroni REST API and check the status that indicates @@ -83,7 +82,6 @@ async def test_exporter_is_up(ops_test: OpsTest, unit_id: int): ) -@pytest.mark.group(1) @pytest.mark.parametrize("unit_id", UNIT_IDS) async def test_settings_are_correct(ops_test: OpsTest, unit_id: int): # Connect to the PostgreSQL instance. @@ -169,7 +167,6 @@ async def test_settings_are_correct(ops_test: OpsTest, unit_id: int): assert unit.data["port-ranges"][0]["protocol"] == "tcp" -@pytest.mark.group(1) async def test_postgresql_locales(ops_test: OpsTest) -> None: raw_locales = await run_command_on_unit( ops_test, @@ -186,7 +183,6 @@ async def test_postgresql_locales(ops_test: OpsTest) -> None: assert locales == SNAP_LOCALES -@pytest.mark.group(1) async def test_postgresql_parameters_change(ops_test: OpsTest) -> None: """Test that's possible to change PostgreSQL parameters.""" await ops_test.model.applications[DATABASE_APP_NAME].set_config({ @@ -234,7 +230,6 @@ async def test_postgresql_parameters_change(ops_test: OpsTest) -> None: connection.close() -@pytest.mark.group(1) async def test_scale_down_and_up(ops_test: OpsTest): """Test data is replicated to new units after a scale up.""" # Ensure the initial number of units in the application. @@ -322,7 +317,6 @@ async def test_scale_down_and_up(ops_test: OpsTest): await scale_application(ops_test, DATABASE_APP_NAME, initial_scale) -@pytest.mark.group(1) async def test_switchover_sync_standby(ops_test: OpsTest): original_roles = await get_cluster_roles( ops_test, ops_test.model.applications[DATABASE_APP_NAME].units[0].name @@ -340,7 +334,6 @@ async def test_switchover_sync_standby(ops_test: OpsTest): assert new_roles["primaries"][0] == original_roles["sync_standbys"][0] -@pytest.mark.group(1) async def test_persist_data_through_primary_deletion(ops_test: OpsTest): """Test data persists through a primary deletion.""" # Set a composite application name in order to test in more than one series at the same time. diff --git a/tests/integration/test_config.py b/tests/integration/test_config.py index 19cf0b4e61..f9dba0acbb 100644 --- a/tests/integration/test_config.py +++ b/tests/integration/test_config.py @@ -7,24 +7,23 @@ from pytest_operator.plugin import OpsTest from .helpers import ( + CHARM_BASE, DATABASE_APP_NAME, - build_charm, get_leader_unit, ) logger = logging.getLogger(__name__) -@pytest.mark.group(1) @pytest.mark.abort_on_fail -async def test_config_parameters(ops_test: OpsTest) -> None: +async def test_config_parameters(ops_test: OpsTest, charm) -> None: """Build and deploy one unit of PostgreSQL and then test config with wrong parameters.""" # Build and deploy the PostgreSQL charm. async with ops_test.fast_forward(): - charm = await build_charm(".") await ops_test.model.deploy( charm, num_units=1, + base=CHARM_BASE, config={"profile": "testing"}, ) await ops_test.model.wait_for_idle(apps=[DATABASE_APP_NAME], status="active", timeout=1500) @@ -33,6 +32,10 @@ async def test_config_parameters(ops_test: OpsTest) -> None: test_string = "abcXYZ123" configs = [ + {"synchronous_node_count": ["0", "1"]}, # config option is greater than 0 + { + "synchronous_node_count": [test_string, "all"] + }, # config option is one of `all`, `minority` or `majority` { "durability_synchronous_commit": [test_string, "on"] }, # config option is one of `on`, `remote_apply` or `remote_write` diff --git a/tests/integration/test_db.py b/tests/integration/test_db.py deleted file mode 100644 index d06c5d127d..0000000000 --- a/tests/integration/test_db.py +++ /dev/null @@ -1,316 +0,0 @@ -#!/usr/bin/env python3 -# Copyright 2022 Canonical Ltd. -# See LICENSE file for licensing details. -import asyncio -import logging - -import psycopg2 as psycopg2 -import pytest as pytest -from mailmanclient import Client -from pytest_operator.plugin import OpsTest -from tenacity import Retrying, stop_after_delay, wait_fixed - -from . import markers -from .helpers import ( - APPLICATION_NAME, - CHARM_BASE, - DATABASE_APP_NAME, - assert_sync_standbys, - build_connection_string, - check_database_users_existence, - check_databases_creation, - deploy_and_relate_application_with_postgresql, - deploy_and_relate_bundle_with_postgresql, - get_leader_unit, - run_command_on_unit, -) - -logger = logging.getLogger(__name__) - -LIVEPATCH_APP_NAME = "livepatch" -MAILMAN3_CORE_APP_NAME = "mailman3-core" -APPLICATION_UNITS = 1 -DATABASE_UNITS = 2 -RELATION_NAME = "db" - -EXTENSIONS_BLOCKING_MESSAGE = ( - "extensions requested through relation, enable them through config options" -) -ROLES_BLOCKING_MESSAGE = ( - "roles requested through relation, use postgresql_client interface instead" -) - - -@pytest.mark.group(1) -@pytest.mark.abort_on_fail -async def test_mailman3_core_db(ops_test: OpsTest, charm: str) -> None: - """Deploy Mailman3 Core to test the 'db' relation.""" - async with ops_test.fast_forward(): - await ops_test.model.deploy( - charm, - application_name=DATABASE_APP_NAME, - num_units=DATABASE_UNITS, - config={"profile": "testing"}, - ) - - # Wait until the PostgreSQL charm is successfully deployed. - await ops_test.model.wait_for_idle( - apps=[DATABASE_APP_NAME], - status="active", - timeout=1500, - wait_for_exact_units=DATABASE_UNITS, - ) - - # Extra config option for Mailman3 Core. - config = {"hostname": "example.org"} - # Deploy and test the deployment of Mailman3 Core. - relation_id = await deploy_and_relate_application_with_postgresql( - ops_test, - "mailman3-core", - MAILMAN3_CORE_APP_NAME, - APPLICATION_UNITS, - config, - series="focal", - ) - await check_databases_creation(ops_test, ["mailman3"]) - - mailman3_core_users = [f"relation-{relation_id}"] - - await check_database_users_existence(ops_test, mailman3_core_users, []) - - # Assert Mailman3 Core is configured to use PostgreSQL instead of SQLite. - mailman_unit = ops_test.model.applications[MAILMAN3_CORE_APP_NAME].units[0] - result = await run_command_on_unit(ops_test, mailman_unit.name, "mailman info") - assert "db url: postgres://" in result - - # Do some CRUD operations using Mailman3 Core client. - domain_name = "canonical.com" - list_name = "postgresql-list" - credentials = ( - result.split("credentials: ")[1].strip().split(":") - ) # This outputs a list containing username and password. - client = Client( - f"http://{mailman_unit.public_address}:8001/3.1", credentials[0], credentials[1] - ) - - # Create a domain and list the domains to check that the new one is there. - domain = client.create_domain(domain_name) - assert domain_name in [domain.mail_host for domain in client.domains] - - # Update the domain by creating a mailing list into it. - mailing_list = domain.create_list(list_name) - assert mailing_list.fqdn_listname in [ - mailing_list.fqdn_listname for mailing_list in domain.lists - ] - - # Delete the domain and check that the change was persisted. - domain.delete() - assert domain_name not in [domain.mail_host for domain in client.domains] - - -@pytest.mark.group(1) -@pytest.mark.abort_on_fail -async def test_relation_data_is_updated_correctly_when_scaling(ops_test: OpsTest): - """Test that relation data, like connection data, is updated correctly when scaling.""" - # Retrieve the list of current database unit names. - units_to_remove = [unit.name for unit in ops_test.model.applications[DATABASE_APP_NAME].units] - - async with ops_test.fast_forward(): - # Add two more units. - await ops_test.model.applications[DATABASE_APP_NAME].add_units(2) - await ops_test.model.wait_for_idle( - apps=[DATABASE_APP_NAME], status="active", timeout=1500, wait_for_exact_units=4 - ) - - assert_sync_standbys( - ops_test.model.applications[DATABASE_APP_NAME].units[0].public_address, 2 - ) - - # Remove the original units. - leader_unit = await get_leader_unit(ops_test, DATABASE_APP_NAME) - await ops_test.model.applications[DATABASE_APP_NAME].destroy_units(*[ - unit for unit in units_to_remove if unit != leader_unit.name - ]) - await ops_test.model.wait_for_idle( - apps=[DATABASE_APP_NAME], status="active", timeout=600, wait_for_exact_units=3 - ) - await ops_test.model.applications[DATABASE_APP_NAME].destroy_units(leader_unit.name) - await ops_test.model.wait_for_idle( - apps=[DATABASE_APP_NAME], status="active", timeout=600, wait_for_exact_units=2 - ) - - # Get the updated connection data and assert it can be used - # to write and read some data properly. - database_unit_name = ops_test.model.applications[DATABASE_APP_NAME].units[0].name - primary_connection_string = await build_connection_string( - ops_test, MAILMAN3_CORE_APP_NAME, RELATION_NAME, remote_unit_name=database_unit_name - ) - replica_connection_string = await build_connection_string( - ops_test, - MAILMAN3_CORE_APP_NAME, - RELATION_NAME, - read_only_endpoint=True, - remote_unit_name=database_unit_name, - ) - - # Connect to the database using the primary connection string. - with psycopg2.connect(primary_connection_string) as connection: - connection.autocommit = True - with connection.cursor() as cursor: - # Check that it's possible to write and read data from the database that - # was created for the application. - cursor.execute("DROP TABLE IF EXISTS test;") - cursor.execute("CREATE TABLE test(data TEXT);") - cursor.execute("INSERT INTO test(data) VALUES('some data');") - cursor.execute("SELECT data FROM test;") - data = cursor.fetchone() - assert data[0] == "some data" - connection.close() - - # Connect to the database using the replica endpoint. - with psycopg2.connect(replica_connection_string) as connection, connection.cursor() as cursor: - # Read some data. - cursor.execute("SELECT data FROM test;") - data = cursor.fetchone() - assert data[0] == "some data" - - # Try to alter some data in a read-only transaction. - with pytest.raises(psycopg2.errors.ReadOnlySqlTransaction): - cursor.execute("DROP TABLE test;") - connection.close() - - # Remove the relation and test that its user was deleted - # (by checking that the connection string doesn't work anymore). - async with ops_test.fast_forward(): - await ops_test.model.applications[DATABASE_APP_NAME].remove_relation( - f"{DATABASE_APP_NAME}:{RELATION_NAME}", f"{MAILMAN3_CORE_APP_NAME}:{RELATION_NAME}" - ) - await ops_test.model.wait_for_idle(apps=[DATABASE_APP_NAME], status="active", timeout=1000) - for attempt in Retrying(stop=stop_after_delay(60 * 3), wait=wait_fixed(10)): - with attempt, pytest.raises(psycopg2.OperationalError): - psycopg2.connect(primary_connection_string) - - -@pytest.mark.group(1) -async def test_roles_blocking(ops_test: OpsTest, charm: str) -> None: - await ops_test.model.deploy( - APPLICATION_NAME, - application_name=APPLICATION_NAME, - config={"legacy_roles": True}, - channel="edge", - ) - await ops_test.model.deploy( - APPLICATION_NAME, - application_name=f"{APPLICATION_NAME}2", - config={"legacy_roles": True}, - channel="edge", - ) - - await ops_test.model.wait_for_idle( - apps=[DATABASE_APP_NAME, APPLICATION_NAME, f"{APPLICATION_NAME}2"], - status="active", - timeout=1000, - ) - - await asyncio.gather( - ops_test.model.relate(f"{DATABASE_APP_NAME}:db", f"{APPLICATION_NAME}:db"), - ops_test.model.relate(f"{DATABASE_APP_NAME}:db", f"{APPLICATION_NAME}2:db"), - ) - - leader_unit = await get_leader_unit(ops_test, DATABASE_APP_NAME) - await ops_test.model.block_until( - lambda: leader_unit.workload_status_message == ROLES_BLOCKING_MESSAGE, timeout=1000 - ) - - assert leader_unit.workload_status_message == ROLES_BLOCKING_MESSAGE - - logger.info("Verify that the charm remains blocked if there are other blocking relations") - await ops_test.model.applications[DATABASE_APP_NAME].destroy_relation( - f"{DATABASE_APP_NAME}:db", f"{APPLICATION_NAME}:db" - ) - - await ops_test.model.block_until( - lambda: leader_unit.workload_status_message == ROLES_BLOCKING_MESSAGE, timeout=1000 - ) - - assert leader_unit.workload_status_message == ROLES_BLOCKING_MESSAGE - - logger.info("Verify that active status is restored when all blocking relations are gone") - await ops_test.model.applications[DATABASE_APP_NAME].destroy_relation( - f"{DATABASE_APP_NAME}:db", f"{APPLICATION_NAME}2:db" - ) - - await ops_test.model.wait_for_idle( - apps=[DATABASE_APP_NAME], - status="active", - timeout=1000, - ) - - -@pytest.mark.group(1) -async def test_extensions_blocking(ops_test: OpsTest, charm: str) -> None: - await asyncio.gather( - ops_test.model.applications[APPLICATION_NAME].set_config({"legacy_roles": "False"}), - ops_test.model.applications[f"{APPLICATION_NAME}2"].set_config({"legacy_roles": "False"}), - ) - await ops_test.model.wait_for_idle( - apps=[APPLICATION_NAME, f"{APPLICATION_NAME}2"], - status="active", - timeout=1000, - ) - - await asyncio.gather( - ops_test.model.relate(f"{DATABASE_APP_NAME}:db", f"{APPLICATION_NAME}:db"), - ops_test.model.relate(f"{DATABASE_APP_NAME}:db", f"{APPLICATION_NAME}2:db"), - ) - - leader_unit = await get_leader_unit(ops_test, DATABASE_APP_NAME) - await ops_test.model.block_until( - lambda: leader_unit.workload_status_message == EXTENSIONS_BLOCKING_MESSAGE, timeout=1000 - ) - - assert leader_unit.workload_status_message == EXTENSIONS_BLOCKING_MESSAGE - - logger.info("Verify that the charm remains blocked if there are other blocking relations") - await ops_test.model.applications[DATABASE_APP_NAME].destroy_relation( - f"{DATABASE_APP_NAME}:db", f"{APPLICATION_NAME}:db" - ) - - await ops_test.model.block_until( - lambda: leader_unit.workload_status_message == EXTENSIONS_BLOCKING_MESSAGE, timeout=1000 - ) - - assert leader_unit.workload_status_message == EXTENSIONS_BLOCKING_MESSAGE - - logger.info("Verify that active status is restored when all blocking relations are gone") - await ops_test.model.applications[DATABASE_APP_NAME].destroy_relation( - f"{DATABASE_APP_NAME}:db", f"{APPLICATION_NAME}2:db" - ) - - -@markers.juju2 -@pytest.mark.group(1) -@pytest.mark.unstable -@markers.amd64_only # canonical-livepatch-server charm (in bundle) not available for arm64 -async def test_canonical_livepatch_onprem_bundle_db(ops_test: OpsTest) -> None: - # Deploy and test the Livepatch onprem bundle (using this PostgreSQL charm - # and an overlay to make the Ubuntu Advantage charm work with PostgreSQL). - # We intentionally wait for the `✘ sync_token not set` status message as we - # aren't providing an Ubuntu Pro token (as this is just a test to ensure - # the database works in the context of the relation with the Livepatch charm). - overlay = { - "applications": {"ubuntu-advantage": {"charm": "ubuntu-advantage", "base": CHARM_BASE}} - } - await deploy_and_relate_bundle_with_postgresql( - ops_test, - "canonical-livepatch-onprem", - LIVEPATCH_APP_NAME, - relation_name="db", - status="blocked", - status_message="✘ sync_token not set", - overlay=overlay, - ) - - action = await ops_test.model.units.get(f"{LIVEPATCH_APP_NAME}/0").run_action("schema-upgrade") - await action.wait() - assert action.results.get("Code") == "0", "schema-upgrade action hasn't succeeded" diff --git a/tests/integration/test_db_admin.py b/tests/integration/test_db_admin.py deleted file mode 100644 index 09882403e9..0000000000 --- a/tests/integration/test_db_admin.py +++ /dev/null @@ -1,174 +0,0 @@ -#!/usr/bin/env python3 -# Copyright 2022 Canonical Ltd. -# See LICENSE file for licensing details. -import json -import logging - -import psycopg2 -import pytest -from landscape_api.base import HTTPError, run_query -from pytest_operator.plugin import OpsTest -from tenacity import Retrying, stop_after_delay, wait_fixed - -from .helpers import ( - DATABASE_APP_NAME, - build_connection_string, - check_database_users_existence, - check_databases_creation, - deploy_and_relate_bundle_with_postgresql, - ensure_correct_relation_data, - get_landscape_api_credentials, - get_machine_from_unit, - get_password, - get_primary, - primary_changed, - start_machine, - stop_machine, - switchover, -) - -logger = logging.getLogger(__name__) - -HAPROXY_APP_NAME = "haproxy" -LANDSCAPE_APP_NAME = "landscape-server" -RABBITMQ_APP_NAME = "rabbitmq-server" -DATABASE_UNITS = 3 -RELATION_NAME = "db-admin" - - -@pytest.mark.group(1) -async def test_landscape_scalable_bundle_db(ops_test: OpsTest, charm: str) -> None: - """Deploy Landscape Scalable Bundle to test the 'db-admin' relation.""" - await ops_test.model.deploy( - charm, - application_name=DATABASE_APP_NAME, - num_units=DATABASE_UNITS, - config={"profile": "testing"}, - ) - - # Deploy and test the Landscape Scalable bundle (using this PostgreSQL charm). - relation_id = await deploy_and_relate_bundle_with_postgresql( - ops_test, - "ch:landscape-scalable", - LANDSCAPE_APP_NAME, - main_application_num_units=2, - relation_name=RELATION_NAME, - timeout=3000, - ) - await ops_test.model.wait_for_idle(apps=[DATABASE_APP_NAME], status="active") - - await check_databases_creation( - ops_test, - [ - "landscape-standalone-account-1", - "landscape-standalone-knowledge", - "landscape-standalone-main", - "landscape-standalone-package", - "landscape-standalone-resource-1", - "landscape-standalone-session", - ], - ) - - landscape_users = [f"relation-{relation_id}"] - - await check_database_users_existence(ops_test, landscape_users, []) - - # Create the admin user on Landscape through configs. - await ops_test.model.applications["landscape-server"].set_config({ - "admin_email": "admin@canonical.com", - "admin_name": "Admin", - "admin_password": "test1234", - }) - await ops_test.model.wait_for_idle( - apps=["landscape-server", DATABASE_APP_NAME], - status="active", - timeout=1200, - ) - - # Connect to the Landscape API through HAProxy and do some CRUD calls (without the update). - key, secret = await get_landscape_api_credentials(ops_test) - haproxy_unit = ops_test.model.applications[HAPROXY_APP_NAME].units[0] - api_uri = f"https://{haproxy_unit.public_address}/api/" - - # Create a role and list the available roles later to check that the new one is there. - role_name = "User1" - run_query(key, secret, "CreateRole", {"name": role_name}, api_uri, False) - api_response = run_query(key, secret, "GetRoles", {}, api_uri, False) - assert role_name in [user["name"] for user in json.loads(api_response)] - - # Remove the role and assert it isn't part of the roles list anymore. - run_query(key, secret, "RemoveRole", {"name": role_name}, api_uri, False) - api_response = run_query(key, secret, "GetRoles", {}, api_uri, False) - assert role_name not in [user["name"] for user in json.loads(api_response)] - - await ensure_correct_relation_data(ops_test, DATABASE_UNITS, LANDSCAPE_APP_NAME, RELATION_NAME) - - # Stop the primary unit machine. - logger.info("restarting primary") - former_primary = await get_primary(ops_test, f"{DATABASE_APP_NAME}/0") - former_primary_machine = await get_machine_from_unit(ops_test, former_primary) - patroni_password = await get_password(ops_test, former_primary, "patroni") - - await stop_machine(ops_test, former_primary_machine) - - # Await for a new primary to be elected. - assert await primary_changed(ops_test, former_primary) - - # Start the former primary unit machine again. - await start_machine(ops_test, former_primary_machine) - - # Wait for the unit to be ready again. Some errors in the start hook may happen due to - # rebooting the unit machine in the middle of a hook (what is needed when the issue from - # https://bugs.launchpad.net/juju/+bug/1999758 happens). - await ops_test.model.wait_for_idle( - apps=[DATABASE_APP_NAME], status="active", timeout=1500, raise_on_error=False - ) - - await ensure_correct_relation_data(ops_test, DATABASE_UNITS, LANDSCAPE_APP_NAME, RELATION_NAME) - - # Trigger a switchover. - logger.info("triggering a switchover") - primary = await get_primary(ops_test, f"{DATABASE_APP_NAME}/0") - switchover(ops_test, primary, patroni_password) - - # Await for a new primary to be elected. - assert await primary_changed(ops_test, primary) - - await ensure_correct_relation_data(ops_test, DATABASE_UNITS, LANDSCAPE_APP_NAME, RELATION_NAME) - - # Trigger a config change to start the Landscape API service again. - # The Landscape API was stopped after a new primary (postgresql) was elected. - await ops_test.model.applications["landscape-server"].set_config({ - "admin_name": "Admin 1", - }) - await ops_test.model.wait_for_idle( - apps=["landscape-server", DATABASE_APP_NAME], timeout=1500, status="active" - ) - - # Create a role and list the available roles later to check that the new one is there. - role_name = "User2" - try: - run_query(key, secret, "CreateRole", {"name": role_name}, api_uri, False) - except HTTPError as e: - assert False, f"error when trying to create role on Landscape: {e}" - - database_unit_name = ops_test.model.applications[DATABASE_APP_NAME].units[0].name - connection_string = await build_connection_string( - ops_test, LANDSCAPE_APP_NAME, RELATION_NAME, remote_unit_name=database_unit_name - ) - - # Remove the applications from the bundle. - await ops_test.model.remove_application(LANDSCAPE_APP_NAME, block_until_done=True) - await ops_test.model.remove_application(HAPROXY_APP_NAME, block_until_done=True) - await ops_test.model.remove_application(RABBITMQ_APP_NAME, block_until_done=True) - - # Remove the relation and test that its user was deleted - # (by checking that the connection string doesn't work anymore). - async with ops_test.fast_forward(): - await ops_test.model.wait_for_idle(apps=[DATABASE_APP_NAME], status="active", timeout=1000) - for attempt in Retrying(stop=stop_after_delay(60 * 3), wait=wait_fixed(10)): - with attempt, pytest.raises(psycopg2.OperationalError): - psycopg2.connect(connection_string) - - # Remove the PostgreSQL application. - await ops_test.model.remove_application(DATABASE_APP_NAME, block_until_done=True) diff --git a/tests/integration/test_password_rotation.py b/tests/integration/test_password_rotation.py index 079a8cf92f..563626b229 100644 --- a/tests/integration/test_password_rotation.py +++ b/tests/integration/test_password_rotation.py @@ -11,8 +11,8 @@ from . import markers from .helpers import ( + CHARM_BASE, METADATA, - build_charm, check_patroni, db_connect, get_leader_unit, @@ -27,23 +27,21 @@ APP_NAME = METADATA["name"] -@pytest.mark.group(1) @pytest.mark.abort_on_fail @pytest.mark.skip_if_deployed -async def test_deploy_active(ops_test: OpsTest): +async def test_deploy_active(ops_test: OpsTest, charm): """Build the charm and deploy it.""" - charm = await build_charm(".") async with ops_test.fast_forward(): await ops_test.model.deploy( charm, application_name=APP_NAME, num_units=3, + base=CHARM_BASE, config={"profile": "testing"}, ) await ops_test.model.wait_for_idle(apps=[APP_NAME], status="active", timeout=1500) -@pytest.mark.group(1) async def test_password_rotation(ops_test: OpsTest): """Test password rotation action.""" # Get the initial passwords set for the system users. @@ -119,7 +117,6 @@ async def test_password_rotation(ops_test: OpsTest): assert check_patroni(ops_test, unit.name, restart_time) -@pytest.mark.group(1) @markers.juju_secrets async def test_password_from_secret_same_as_cli(ops_test: OpsTest): """Checking if password is same as returned by CLI. @@ -146,7 +143,6 @@ async def test_password_from_secret_same_as_cli(ops_test: OpsTest): assert data[secret_id]["content"]["Data"]["replication-password"] == password -@pytest.mark.group(1) async def test_empty_password(ops_test: OpsTest) -> None: """Test that the password can't be set to an empty string.""" leader_unit = await get_leader_unit(ops_test, APP_NAME) @@ -159,7 +155,6 @@ async def test_empty_password(ops_test: OpsTest) -> None: assert password == "None" -@pytest.mark.group(1) async def test_db_connection_with_empty_password(ops_test: OpsTest): """Test that user can't connect with empty password.""" primary = await get_primary(ops_test, f"{APP_NAME}/0") @@ -168,7 +163,6 @@ async def test_db_connection_with_empty_password(ops_test: OpsTest): connection.close() -@pytest.mark.group(1) async def test_no_password_change_on_invalid_password(ops_test: OpsTest) -> None: """Test that in general, there is no change when password validation fails.""" leader_unit = await get_leader_unit(ops_test, APP_NAME) @@ -181,7 +175,6 @@ async def test_no_password_change_on_invalid_password(ops_test: OpsTest) -> None assert password1 == password2 -@pytest.mark.group(1) async def test_no_password_exposed_on_logs(ops_test: OpsTest) -> None: """Test that passwords don't get exposed on postgresql logs.""" for unit in ops_test.model.applications[APP_NAME].units: diff --git a/tests/integration/test_plugins.py b/tests/integration/test_plugins.py index 5e8d2f2d48..4dedc11a7e 100644 --- a/tests/integration/test_plugins.py +++ b/tests/integration/test_plugins.py @@ -8,8 +8,8 @@ from pytest_operator.plugin import OpsTest from .helpers import ( + CHARM_BASE, DATABASE_APP_NAME, - build_charm, db_connect, get_password, get_primary, @@ -90,17 +90,17 @@ TIMESCALEDB_EXTENSION_STATEMENT = "CREATE TABLE test_timescaledb (time TIMESTAMPTZ NOT NULL); SELECT create_hypertable('test_timescaledb', 'time');" -@pytest.mark.group(1) @pytest.mark.abort_on_fail -async def test_plugins(ops_test: OpsTest) -> None: +async def test_plugins(ops_test: OpsTest, charm) -> None: """Build and deploy one unit of PostgreSQL and then test the available plugins.""" # Build and deploy the PostgreSQL charm. async with ops_test.fast_forward(): - charm = await build_charm(".") await ops_test.model.deploy( charm, num_units=2, - config={"profile": "testing"}, + base=CHARM_BASE, + # TODO Figure out how to deal with pgaudit + config={"profile": "testing", "plugin_audit_enable": "False"}, ) await ops_test.model.wait_for_idle(apps=[DATABASE_APP_NAME], status="active", timeout=1500) @@ -211,7 +211,6 @@ def enable_disable_config(enabled: False): connection.close() -@pytest.mark.group(1) async def test_plugin_objects(ops_test: OpsTest) -> None: """Checks if charm gets blocked when trying to disable a plugin in use.""" primary = await get_primary(ops_test, f"{DATABASE_APP_NAME}/0") diff --git a/tests/integration/test_subordinates.py b/tests/integration/test_subordinates.py index 48463bc0b1..c3caeb2d0f 100644 --- a/tests/integration/test_subordinates.py +++ b/tests/integration/test_subordinates.py @@ -3,60 +3,86 @@ # See LICENSE file for licensing details. import logging +import os from asyncio import gather import pytest from pytest_operator.plugin import OpsTest from .helpers import ( + CHARM_BASE, scale_application, ) DATABASE_APP_NAME = "pg" LS_CLIENT = "landscape-client" +UBUNTU_PRO_APP_NAME = "ubuntu-advantage" logger = logging.getLogger(__name__) -@pytest.mark.group(1) +@pytest.fixture(scope="module") +async def check_subordinate_env_vars(ops_test: OpsTest) -> None: + if ( + not os.environ.get("UBUNTU_PRO_TOKEN", "").strip() + or not os.environ.get("LANDSCAPE_ACCOUNT_NAME", "").strip() + or not os.environ.get("LANDSCAPE_REGISTRATION_KEY", "").strip() + ): + pytest.skip("Subordinate configs not set") + + @pytest.mark.abort_on_fail -async def test_deploy(ops_test: OpsTest, charm: str, github_secrets): +async def test_deploy(ops_test: OpsTest, charm: str, check_subordinate_env_vars): await gather( ops_test.model.deploy( charm, application_name=DATABASE_APP_NAME, num_units=3, + base=CHARM_BASE, + ), + ops_test.model.deploy( + UBUNTU_PRO_APP_NAME, + config={"token": os.environ["UBUNTU_PRO_TOKEN"]}, + channel="latest/edge", + num_units=0, + # TODO switch back to series when pylib juju can figure out the base: + # https://github.com/juju/python-libjuju/issues/1240 + series="noble", ), ops_test.model.deploy( LS_CLIENT, config={ - "account-name": github_secrets["LANDSCAPE_ACCOUNT_NAME"], - "registration-key": github_secrets["LANDSCAPE_REGISTRATION_KEY"], + "account-name": os.environ["LANDSCAPE_ACCOUNT_NAME"], + "registration-key": os.environ["LANDSCAPE_REGISTRATION_KEY"], "ppa": "ppa:landscape/self-hosted-beta", }, channel="latest/edge", num_units=0, + base=CHARM_BASE, ), ) await ops_test.model.wait_for_idle(apps=[DATABASE_APP_NAME], status="active", timeout=2000) await ops_test.model.relate(f"{DATABASE_APP_NAME}:juju-info", f"{LS_CLIENT}:container") - await ops_test.model.wait_for_idle(apps=[LS_CLIENT, DATABASE_APP_NAME], status="active") + await ops_test.model.relate( + f"{DATABASE_APP_NAME}:juju-info", f"{UBUNTU_PRO_APP_NAME}:juju-info" + ) + await ops_test.model.wait_for_idle( + apps=[LS_CLIENT, UBUNTU_PRO_APP_NAME, DATABASE_APP_NAME], status="active" + ) -@pytest.mark.group(1) -async def test_scale_up(ops_test: OpsTest, github_secrets): +async def test_scale_up(ops_test: OpsTest, check_subordinate_env_vars): await scale_application(ops_test, DATABASE_APP_NAME, 4) await ops_test.model.wait_for_idle( - apps=[LS_CLIENT, DATABASE_APP_NAME], status="active", timeout=1500 + apps=[LS_CLIENT, UBUNTU_PRO_APP_NAME, DATABASE_APP_NAME], status="active", timeout=1500 ) -@pytest.mark.group(1) -async def test_scale_down(ops_test: OpsTest, github_secrets): +async def test_scale_down(ops_test: OpsTest, check_subordinate_env_vars): await scale_application(ops_test, DATABASE_APP_NAME, 3) await ops_test.model.wait_for_idle( - apps=[LS_CLIENT, DATABASE_APP_NAME], status="active", timeout=1500 + apps=[LS_CLIENT, UBUNTU_PRO_APP_NAME, DATABASE_APP_NAME], status="active", timeout=1500 ) diff --git a/tests/integration/test_tls.py b/tests/integration/test_tls.py index f8cf495948..1052df7900 100644 --- a/tests/integration/test_tls.py +++ b/tests/integration/test_tls.py @@ -12,9 +12,9 @@ change_patroni_setting, ) from .helpers import ( + CHARM_BASE, DATABASE_APP_NAME, METADATA, - build_charm, change_primary_start_timeout, check_tls, check_tls_patroni_api, @@ -26,46 +26,38 @@ primary_changed, run_command_on_unit, ) -from .juju_ import juju_major_version logger = logging.getLogger(__name__) APP_NAME = METADATA["name"] -if juju_major_version < 3: - tls_certificates_app_name = "tls-certificates-operator" - tls_channel = "legacy/edge" if architecture.architecture == "arm64" else "legacy/stable" - tls_config = {"generate-self-signed-certificates": "true", "ca-common-name": "Test CA"} -else: - tls_certificates_app_name = "self-signed-certificates" - tls_channel = "latest/edge" if architecture.architecture == "arm64" else "latest/stable" - tls_config = {"ca-common-name": "Test CA"} +tls_certificates_app_name = "self-signed-certificates" +tls_channel = "latest/edge" if architecture.architecture == "arm64" else "latest/stable" +tls_config = {"ca-common-name": "Test CA"} -@pytest.mark.group(1) @pytest.mark.abort_on_fail @pytest.mark.skip_if_deployed -async def test_deploy_active(ops_test: OpsTest): +async def test_deploy_active(ops_test: OpsTest, charm): """Build the charm and deploy it.""" - charm = await build_charm(".") async with ops_test.fast_forward(): await ops_test.model.deploy( charm, application_name=APP_NAME, num_units=3, + base=CHARM_BASE, config={"profile": "testing"}, ) # No wait between deploying charms, since we can't guarantee users will wait. Furthermore, # bundles don't wait between deploying charms. -@pytest.mark.group(1) @pytest.mark.abort_on_fail async def test_tls_enabled(ops_test: OpsTest) -> None: """Test that TLS is enabled when relating to the TLS Certificates Operator.""" async with ops_test.fast_forward(): # Deploy TLS Certificates operator. await ops_test.model.deploy( - tls_certificates_app_name, config=tls_config, channel=tls_channel + tls_certificates_app_name, config=tls_config, channel=tls_channel, base=CHARM_BASE ) # Relate it to the PostgreSQL to enable TLS. diff --git a/tests/spread/test_async_replication.py/task.yaml b/tests/spread/test_async_replication.py/task.yaml new file mode 100644 index 0000000000..4fbf3b6b36 --- /dev/null +++ b/tests/spread/test_async_replication.py/task.yaml @@ -0,0 +1,9 @@ +summary: test_async_replication.py +environment: + TEST_MODULE: ha_tests/test_async_replication.py +execute: | + tox run -e integration -- "tests/integration/$TEST_MODULE" --model testing --alluredir="$SPREAD_TASK/allure-results" +artifacts: + - allure-results +variants: + - -juju29 diff --git a/tests/spread/test_audit.py/task.yaml b/tests/spread/test_audit.py/task.yaml new file mode 100644 index 0000000000..9cbc84e43d --- /dev/null +++ b/tests/spread/test_audit.py/task.yaml @@ -0,0 +1,7 @@ +summary: test_audit.py +environment: + TEST_MODULE: test_audit.py +execute: | + tox run -e integration -- "tests/integration/$TEST_MODULE" --model testing --alluredir="$SPREAD_TASK/allure-results" +artifacts: + - allure-results diff --git a/tests/spread/test_backups_aws.py/task.yaml b/tests/spread/test_backups_aws.py/task.yaml new file mode 100644 index 0000000000..c7eb541232 --- /dev/null +++ b/tests/spread/test_backups_aws.py/task.yaml @@ -0,0 +1,9 @@ +summary: test_backups_aws.py +environment: + TEST_MODULE: test_backups_aws.py +execute: | + tox run -e integration -- "tests/integration/$TEST_MODULE" --model testing --alluredir="$SPREAD_TASK/allure-results" +artifacts: + - allure-results +backends: + - -lxd-vm # Requires CI secrets diff --git a/tests/spread/test_backups_ceph.py/task.yaml b/tests/spread/test_backups_ceph.py/task.yaml new file mode 100644 index 0000000000..8f6c8a387d --- /dev/null +++ b/tests/spread/test_backups_ceph.py/task.yaml @@ -0,0 +1,9 @@ +summary: test_backups_ceph.py +environment: + TEST_MODULE: test_backups_ceph.py +execute: | + tox run -e integration -- "tests/integration/$TEST_MODULE" --model testing --alluredir="$SPREAD_TASK/allure-results" +artifacts: + - allure-results +systems: + - -ubuntu-24.04-arm diff --git a/tests/spread/test_backups_gcp.py/task.yaml b/tests/spread/test_backups_gcp.py/task.yaml new file mode 100644 index 0000000000..c0dc3ac976 --- /dev/null +++ b/tests/spread/test_backups_gcp.py/task.yaml @@ -0,0 +1,9 @@ +summary: test_backups_gcp.py +environment: + TEST_MODULE: test_backups_gcp.py +execute: | + tox run -e integration -- "tests/integration/$TEST_MODULE" --model testing --alluredir="$SPREAD_TASK/allure-results" +artifacts: + - allure-results +backends: + - -lxd-vm # Requires CI secrets diff --git a/tests/spread/test_backups_pitr_aws.py/task.yaml b/tests/spread/test_backups_pitr_aws.py/task.yaml new file mode 100644 index 0000000000..4ac59fbf85 --- /dev/null +++ b/tests/spread/test_backups_pitr_aws.py/task.yaml @@ -0,0 +1,9 @@ +summary: test_backups_pitr_aws.py +environment: + TEST_MODULE: test_backups_pitr_aws.py +execute: | + tox run -e integration -- "tests/integration/$TEST_MODULE" --model testing --alluredir="$SPREAD_TASK/allure-results" +artifacts: + - allure-results +backends: + - -lxd-vm # Requires CI secrets diff --git a/tests/spread/test_backups_pitr_gcp.py/task.yaml b/tests/spread/test_backups_pitr_gcp.py/task.yaml new file mode 100644 index 0000000000..a6b31a59a6 --- /dev/null +++ b/tests/spread/test_backups_pitr_gcp.py/task.yaml @@ -0,0 +1,9 @@ +summary: test_backups_pitr_gcp.py +environment: + TEST_MODULE: test_backups_pitr_gcp.py +execute: | + tox run -e integration -- "tests/integration/$TEST_MODULE" --model testing --alluredir="$SPREAD_TASK/allure-results" +artifacts: + - allure-results +backends: + - -lxd-vm # Requires CI secrets diff --git a/tests/spread/test_charm.py/task.yaml b/tests/spread/test_charm.py/task.yaml new file mode 100644 index 0000000000..96450bdc32 --- /dev/null +++ b/tests/spread/test_charm.py/task.yaml @@ -0,0 +1,7 @@ +summary: test_charm.py +environment: + TEST_MODULE: test_charm.py +execute: | + tox run -e integration -- "tests/integration/$TEST_MODULE" --model testing --alluredir="$SPREAD_TASK/allure-results" +artifacts: + - allure-results diff --git a/tests/spread/test_config.py/task.yaml b/tests/spread/test_config.py/task.yaml new file mode 100644 index 0000000000..f330f89b38 --- /dev/null +++ b/tests/spread/test_config.py/task.yaml @@ -0,0 +1,7 @@ +summary: test_config.py +environment: + TEST_MODULE: test_config.py +execute: | + tox run -e integration -- "tests/integration/$TEST_MODULE" --model testing --alluredir="$SPREAD_TASK/allure-results" +artifacts: + - allure-results diff --git a/tests/spread/test_new_relations_1.py/task.yaml b/tests/spread/test_new_relations_1.py/task.yaml new file mode 100644 index 0000000000..0c64fe771f --- /dev/null +++ b/tests/spread/test_new_relations_1.py/task.yaml @@ -0,0 +1,7 @@ +summary: test_new_relations_1.py +environment: + TEST_MODULE: new_relations/test_new_relations_1.py +execute: | + tox run -e integration -- "tests/integration/$TEST_MODULE" --model testing --alluredir="$SPREAD_TASK/allure-results" +artifacts: + - allure-results diff --git a/tests/spread/test_new_relations_2.py/task.yaml b/tests/spread/test_new_relations_2.py/task.yaml new file mode 100644 index 0000000000..0b7af326a4 --- /dev/null +++ b/tests/spread/test_new_relations_2.py/task.yaml @@ -0,0 +1,9 @@ +summary: test_new_relations_2.py +environment: + TEST_MODULE: new_relations/test_new_relations_2.py +execute: | + tox run -e integration -- "tests/integration/$TEST_MODULE" --model testing --alluredir="$SPREAD_TASK/allure-results" +artifacts: + - allure-results +systems: + - -ubuntu-24.04-arm diff --git a/tests/spread/test_password_rotation.py/task.yaml b/tests/spread/test_password_rotation.py/task.yaml new file mode 100644 index 0000000000..439559b4e6 --- /dev/null +++ b/tests/spread/test_password_rotation.py/task.yaml @@ -0,0 +1,7 @@ +summary: test_password_rotation.py +environment: + TEST_MODULE: test_password_rotation.py +execute: | + tox run -e integration -- "tests/integration/$TEST_MODULE" --model testing --alluredir="$SPREAD_TASK/allure-results" +artifacts: + - allure-results diff --git a/tests/spread/test_plugins.py/task.yaml b/tests/spread/test_plugins.py/task.yaml new file mode 100644 index 0000000000..e9dce8e28f --- /dev/null +++ b/tests/spread/test_plugins.py/task.yaml @@ -0,0 +1,7 @@ +summary: test_plugins.py +environment: + TEST_MODULE: test_plugins.py +execute: | + tox run -e integration -- "tests/integration/$TEST_MODULE" --model testing --alluredir="$SPREAD_TASK/allure-results" +artifacts: + - allure-results diff --git a/tests/spread/test_relations_coherence.py/task.yaml b/tests/spread/test_relations_coherence.py/task.yaml new file mode 100644 index 0000000000..bff0e492b3 --- /dev/null +++ b/tests/spread/test_relations_coherence.py/task.yaml @@ -0,0 +1,7 @@ +summary: test_relations_coherence.py +environment: + TEST_MODULE: new_relations/test_relations_coherence.py +execute: | + tox run -e integration -- "tests/integration/$TEST_MODULE" --model testing --alluredir="$SPREAD_TASK/allure-results" +artifacts: + - allure-results diff --git a/tests/spread/test_replication.py/task.yaml b/tests/spread/test_replication.py/task.yaml new file mode 100644 index 0000000000..237cc3981b --- /dev/null +++ b/tests/spread/test_replication.py/task.yaml @@ -0,0 +1,7 @@ +summary: test_replication.py +environment: + TEST_MODULE: ha_tests/test_replication.py +execute: | + tox run -e integration -- "tests/integration/$TEST_MODULE" --model testing --alluredir="$SPREAD_TASK/allure-results" +artifacts: + - allure-results diff --git a/tests/spread/test_restore_cluster.py/task.yaml b/tests/spread/test_restore_cluster.py/task.yaml new file mode 100644 index 0000000000..bce2ec14d4 --- /dev/null +++ b/tests/spread/test_restore_cluster.py/task.yaml @@ -0,0 +1,7 @@ +summary: test_restore_cluster.py +environment: + TEST_MODULE: ha_tests/test_restore_cluster.py +execute: | + tox run -e integration -- "tests/integration/$TEST_MODULE" --model testing --alluredir="$SPREAD_TASK/allure-results" +artifacts: + - allure-results diff --git a/tests/spread/test_scaling.py/task.yaml b/tests/spread/test_scaling.py/task.yaml new file mode 100644 index 0000000000..32358243db --- /dev/null +++ b/tests/spread/test_scaling.py/task.yaml @@ -0,0 +1,9 @@ +summary: test_scaling.py +environment: + TEST_MODULE: ha_tests/test_scaling.py +execute: | + tox run -e integration -- "tests/integration/$TEST_MODULE" --model testing --alluredir="$SPREAD_TASK/allure-results" +artifacts: + - allure-results +variants: + - -juju29 diff --git a/tests/spread/test_scaling_three_units.py/task.yaml b/tests/spread/test_scaling_three_units.py/task.yaml new file mode 100644 index 0000000000..ae8dcc1006 --- /dev/null +++ b/tests/spread/test_scaling_three_units.py/task.yaml @@ -0,0 +1,9 @@ +summary: test_scaling_three_units.py +environment: + TEST_MODULE: ha_tests/test_scaling_three_units.py +execute: | + tox run -e integration -- "tests/integration/$TEST_MODULE" --model testing --alluredir="$SPREAD_TASK/allure-results" +artifacts: + - allure-results +variants: + - -juju29 diff --git a/tests/spread/test_scaling_three_units_async.py/task.yaml b/tests/spread/test_scaling_three_units_async.py/task.yaml new file mode 100644 index 0000000000..cd8a7ba5aa --- /dev/null +++ b/tests/spread/test_scaling_three_units_async.py/task.yaml @@ -0,0 +1,9 @@ +summary: test_scaling_three_units.py +environment: + TEST_MODULE: ha_tests/test_scaling_three_units_async.py +execute: | + tox run -e integration -- "tests/integration/$TEST_MODULE" --model testing --alluredir="$SPREAD_TASK/allure-results" +artifacts: + - allure-results +variants: + - -juju29 diff --git a/tests/spread/test_self_healing.py/task.yaml b/tests/spread/test_self_healing.py/task.yaml new file mode 100644 index 0000000000..d8fca3acea --- /dev/null +++ b/tests/spread/test_self_healing.py/task.yaml @@ -0,0 +1,7 @@ +summary: test_self_healing.py +environment: + TEST_MODULE: ha_tests/test_self_healing.py +execute: | + tox run -e integration -- "tests/integration/$TEST_MODULE" --model testing --alluredir="$SPREAD_TASK/allure-results" +artifacts: + - allure-results diff --git a/tests/spread/test_smoke.py/task.yaml b/tests/spread/test_smoke.py/task.yaml new file mode 100644 index 0000000000..d2fe9793d1 --- /dev/null +++ b/tests/spread/test_smoke.py/task.yaml @@ -0,0 +1,7 @@ +summary: test_smoke.py +environment: + TEST_MODULE: ha_tests/test_smoke.py +execute: | + tox run -e integration -- "tests/integration/$TEST_MODULE" --model testing --alluredir="$SPREAD_TASK/allure-results" +artifacts: + - allure-results diff --git a/tests/spread/test_subordinates.py/task.yaml b/tests/spread/test_subordinates.py/task.yaml new file mode 100644 index 0000000000..a7477d7bab --- /dev/null +++ b/tests/spread/test_subordinates.py/task.yaml @@ -0,0 +1,9 @@ +summary: test_subordinates.py +environment: + TEST_MODULE: test_subordinates.py +execute: | + tox run -e integration -- "tests/integration/$TEST_MODULE" --model testing --alluredir="$SPREAD_TASK/allure-results" +artifacts: + - allure-results +backends: + - -lxd-vm # Requires CI secrets diff --git a/tests/spread/test_synchronous_policy.py/task.yaml b/tests/spread/test_synchronous_policy.py/task.yaml new file mode 100644 index 0000000000..e4ce19458f --- /dev/null +++ b/tests/spread/test_synchronous_policy.py/task.yaml @@ -0,0 +1,7 @@ +summary: test_scaling.py +environment: + TEST_MODULE: ha_tests/test_synchronous_policy.py +execute: | + tox run -e integration -- "tests/integration/$TEST_MODULE" --model testing --alluredir="$SPREAD_TASK/allure-results" +artifacts: + - allure-results diff --git a/tests/spread/test_tls.py/task.yaml b/tests/spread/test_tls.py/task.yaml new file mode 100644 index 0000000000..a605744913 --- /dev/null +++ b/tests/spread/test_tls.py/task.yaml @@ -0,0 +1,7 @@ +summary: test_tls.py +environment: + TEST_MODULE: test_tls.py +execute: | + tox run -e integration -- "tests/integration/$TEST_MODULE" --model testing --alluredir="$SPREAD_TASK/allure-results" +artifacts: + - allure-results diff --git a/tests/spread/test_upgrade.py/task.yaml b/tests/spread/test_upgrade.py/task.yaml new file mode 100644 index 0000000000..b3be366921 --- /dev/null +++ b/tests/spread/test_upgrade.py/task.yaml @@ -0,0 +1,7 @@ +summary: test_upgrade.py +environment: + TEST_MODULE: ha_tests/test_upgrade.py +execute: | + tox run -e integration -- "tests/integration/$TEST_MODULE" --model testing --alluredir="$SPREAD_TASK/allure-results" +artifacts: + - allure-results diff --git a/tests/spread/test_upgrade_from_stable.py/task.yaml b/tests/spread/test_upgrade_from_stable.py/task.yaml new file mode 100644 index 0000000000..047617ab39 --- /dev/null +++ b/tests/spread/test_upgrade_from_stable.py/task.yaml @@ -0,0 +1,7 @@ +summary: test_upgrade_from_stable.py +environment: + TEST_MODULE: ha_tests/test_upgrade_from_stable.py +execute: | + tox run -e integration -- "tests/integration/$TEST_MODULE" --model testing --alluredir="$SPREAD_TASK/allure-results" +artifacts: + - allure-results diff --git a/tests/unit/conftest.py b/tests/unit/conftest.py index 03c68492e9..479919633d 100644 --- a/tests/unit/conftest.py +++ b/tests/unit/conftest.py @@ -9,27 +9,9 @@ # This causes every test defined in this file to run 2 times, each with # charm.JujuVersion.has_secrets set as True or as False -@pytest.fixture(params=[True, False], autouse=True) +@pytest.fixture(autouse=True) def _has_secrets(request, monkeypatch): - monkeypatch.setattr("charm.JujuVersion.has_secrets", PropertyMock(return_value=request.param)) - return request.param - - -@pytest.fixture -def only_with_juju_secrets(_has_secrets): - """Pretty way to skip Juju 3 tests.""" - if not _has_secrets: - pytest.skip("Secrets test only applies on Juju 3.x") - - -@pytest.fixture -def only_without_juju_secrets(_has_secrets): - """Pretty way to skip Juju 2-specific tests. - - Typically: to save CI time, when the same check were executed in a Juju 3-specific way already - """ - if _has_secrets: - pytest.skip("Skipping legacy secrets tests") + monkeypatch.setattr("charm.JujuVersion.has_secrets", PropertyMock(return_value=True)) @pytest.fixture(autouse=True) diff --git a/tests/unit/test_backups.py b/tests/unit/test_backups.py index 13de4d1c01..27320474a3 100644 --- a/tests/unit/test_backups.py +++ b/tests/unit/test_backups.py @@ -297,6 +297,19 @@ def test_can_use_s3_repository(harness): ] assert harness.charm.backup.can_use_s3_repository() == (True, None) + # Empty db + _execute_command.side_effect = None + _execute_command.return_value = (1, "", "") + pgbackrest_info_other_cluster_name_backup_output = ( + 0, + f'[{{"db": [], "name": "another-model.{harness.charm.cluster_name}"}}]', + "", + ) + assert harness.charm.backup.can_use_s3_repository() == ( + False, + FAILED_TO_INITIALIZE_STANZA_ERROR_MESSAGE, + ) + def test_construct_endpoint(harness): # Test with an AWS endpoint without region. diff --git a/tests/unit/test_charm.py b/tests/unit/test_charm.py index b070e30641..e8841246ce 100644 --- a/tests/unit/test_charm.py +++ b/tests/unit/test_charm.py @@ -35,7 +35,12 @@ PRIMARY_NOT_REACHABLE_MESSAGE, PostgresqlOperatorCharm, ) -from cluster import NotReadyError, RemoveRaftMemberFailedError, SwitchoverFailedError +from cluster import ( + NotReadyError, + RemoveRaftMemberFailedError, + SwitchoverFailedError, + SwitchoverNotSyncError, +) from constants import PEER, POSTGRESQL_SNAP_NAME, SECRET_INTERNAL_LABEL, SNAP_PACKAGES CREATE_CLUSTER_CONF_PATH = "/etc/postgresql-common/createcluster.d/pgcharm.conf" @@ -271,6 +276,9 @@ def test_on_config_changed(harness): "charm.PostgresqlOperatorCharm._validate_config_options" ) as _validate_config_options, patch("charm.PostgresqlOperatorCharm.update_config") as _update_config, + patch( + "charm.PostgresqlOperatorCharm.updated_synchronous_node_count", return_value=True + ) as _updated_synchronous_node_count, patch("relations.db.DbProvides.set_up_relation") as _set_up_relation, patch( "charm.PostgresqlOperatorCharm.enable_disable_extensions" @@ -298,6 +306,7 @@ def test_on_config_changed(harness): harness.charm.on.config_changed.emit() assert not _update_config.called _validate_config_options.side_effect = None + _updated_synchronous_node_count.assert_called_once_with() # Test after the cluster was initialised. with harness.hooks_disabled(): @@ -324,7 +333,8 @@ def test_on_config_changed(harness): harness.charm.on.config_changed.emit() _enable_disable_extensions.assert_called_once() _set_up_relation.assert_called_once() - harness.remove_relation(db_relation_id) + with harness.hooks_disabled(): + harness.remove_relation(db_relation_id) _enable_disable_extensions.reset_mock() _set_up_relation.reset_mock() @@ -333,7 +343,7 @@ def test_on_config_changed(harness): _enable_disable_extensions.assert_called_once() _set_up_relation.assert_called_once() - # Test when there are established legacy relations, + # Test when there are established legacy relations, # but the charm fails to set up one of them. _enable_disable_extensions.reset_mock() _set_up_relation.reset_mock() @@ -409,6 +419,9 @@ def test_enable_disable_extensions(harness, caplog): postgresql_mock.enable_disable_extensions.side_effect = None with caplog.at_level(logging.ERROR): config = """options: + synchronous_node_count: + type: string + default: "all" plugin_citext_enable: default: false type: boolean @@ -1822,29 +1835,6 @@ def test_client_relations(harness): assert harness.charm.client_relations == [database_relation, db_relation, db_admin_relation] -def test_on_pgdata_storage_detaching(harness): - with ( - patch( - "charm.PostgresqlOperatorCharm._update_relation_endpoints" - ) as _update_relation_endpoints, - patch("charm.PostgresqlOperatorCharm.primary_endpoint", new_callable=PropertyMock), - patch("charm.Patroni.are_all_members_ready") as _are_all_members_ready, - patch("charm.Patroni.get_primary", return_value="primary") as _get_primary, - patch("charm.Patroni.switchover") as _switchover, - patch("charm.Patroni.primary_changed") as _primary_changed, - ): - # Early exit if not primary - event = Mock() - harness.charm._on_pgdata_storage_detaching(event) - assert not _are_all_members_ready.called - - _get_primary.side_effect = [harness.charm.unit.name, "primary"] - harness.charm._on_pgdata_storage_detaching(event) - _switchover.assert_called_once_with() - _primary_changed.assert_called_once_with("primary") - _update_relation_endpoints.assert_called_once_with() - - def test_add_cluster_member(harness): with ( patch("charm.PostgresqlOperatorCharm.update_config") as _update_config, @@ -2056,32 +2046,6 @@ def test_scope_obj(harness): assert harness.charm._scope_obj("test") is None -def test_get_secret_from_databag(harness): - """Asserts that get_secret method can read secrets from databag. - - This must be backwards-compatible so it runs on both juju2 and juju3. - """ - with patch("charm.PostgresqlOperatorCharm._on_leader_elected"): - rel_id = harness.model.get_relation(PEER).id - # App level changes require leader privileges - harness.set_leader() - # Test application scope. - assert harness.charm.get_secret("app", "operator_password") is None - harness.update_relation_data( - rel_id, harness.charm.app.name, {"operator_password": "test-password"} - ) - assert harness.charm.get_secret("app", "operator_password") == "test-password" - - # Unit level changes don't require leader privileges - harness.set_leader(False) - # Test unit scope. - assert harness.charm.get_secret("unit", "operator_password") is None - harness.update_relation_data( - rel_id, harness.charm.unit.name, {"operator_password": "test-password"} - ) - assert harness.charm.get_secret("unit", "operator_password") == "test-password" - - def test_on_get_password_secrets(harness): with ( patch("charm.PostgresqlOperatorCharm._on_leader_elected"), @@ -2123,39 +2087,6 @@ def test_get_secret_secrets(harness, scope, field): assert harness.charm.get_secret(scope, field) == "test" -def test_set_secret_in_databag(harness, only_without_juju_secrets): - """Asserts that set_secret method writes to relation databag. - - This is juju2 specific. In juju3, set_secret writes to juju secrets. - """ - with patch("charm.PostgresqlOperatorCharm._on_leader_elected"): - rel_id = harness.model.get_relation(PEER).id - harness.set_leader() - - # Test application scope. - assert "password" not in harness.get_relation_data(rel_id, harness.charm.app.name) - harness.charm.set_secret("app", "password", "test-password") - assert ( - harness.get_relation_data(rel_id, harness.charm.app.name)["password"] - == "test-password" - ) - harness.charm.set_secret("app", "password", None) - assert "password" not in harness.get_relation_data(rel_id, harness.charm.app.name) - - # Test unit scope. - assert "password" not in harness.get_relation_data(rel_id, harness.charm.unit.name) - harness.charm.set_secret("unit", "password", "test-password") - assert ( - harness.get_relation_data(rel_id, harness.charm.unit.name)["password"] - == "test-password" - ) - harness.charm.set_secret("unit", "password", None) - assert "password" not in harness.get_relation_data(rel_id, harness.charm.unit.name) - - with pytest.raises(RuntimeError): - harness.charm.set_secret("test", "password", "test") - - @pytest.mark.parametrize("scope,is_leader", [("app", True), ("unit", True), ("unit", False)]) def test_set_reset_new_secret(harness, scope, is_leader): with ( @@ -2192,7 +2123,7 @@ def test_invalid_secret(harness, scope, is_leader): assert harness.charm.get_secret(scope, "somekey") is None -def test_delete_password(harness, _has_secrets, caplog): +def test_delete_password(harness, caplog): with ( patch("charm.PostgresqlOperatorCharm._on_leader_elected"), ): @@ -2209,14 +2140,7 @@ def test_delete_password(harness, _has_secrets, caplog): harness.set_leader(True) with caplog.at_level(logging.DEBUG): - if _has_secrets: - error_message = ( - "Non-existing secret operator-password was attempted to be removed." - ) - else: - error_message = ( - "Non-existing field 'operator-password' was attempted to be removed" - ) + error_message = "Non-existing secret operator-password was attempted to be removed." harness.charm.remove_secret("app", "operator-password") assert error_message in caplog.text @@ -2238,34 +2162,7 @@ def test_delete_password(harness, _has_secrets, caplog): @pytest.mark.parametrize("scope,is_leader", [("app", True), ("unit", True), ("unit", False)]) -def test_migration_from_databag(harness, only_with_juju_secrets, scope, is_leader): - """Check if we're moving on to use secrets when live upgrade from databag to Secrets usage. - - Since it checks for a migration from databag to juju secrets, it's specific to juju3. - """ - with ( - patch("charm.PostgresqlOperatorCharm._on_leader_elected"), - ): - rel_id = harness.model.get_relation(PEER).id - # App has to be leader, unit can be either - harness.set_leader(is_leader) - - # Getting current password - entity = getattr(harness.charm, scope) - harness.update_relation_data(rel_id, entity.name, {"operator_password": "bla"}) - assert harness.charm.get_secret(scope, "operator_password") == "bla" - - # Reset new secret - harness.charm.set_secret(scope, "operator-password", "blablabla") - assert harness.charm.model.get_secret(label=f"{PEER}.postgresql.{scope}") - assert harness.charm.get_secret(scope, "operator-password") == "blablabla" - assert "operator-password" not in harness.get_relation_data( - rel_id, getattr(harness.charm, scope).name - ) - - -@pytest.mark.parametrize("scope,is_leader", [("app", True), ("unit", True), ("unit", False)]) -def test_migration_from_single_secret(harness, only_with_juju_secrets, scope, is_leader): +def test_migration_from_single_secret(harness, scope, is_leader): """Check if we're moving on to use secrets when live upgrade from databag to Secrets usage. Since it checks for a migration from databag to juju secrets, it's specific to juju3. @@ -2367,7 +2264,7 @@ def test_on_peer_relation_departed(harness): patch("charm.Patroni.are_all_members_ready") as _are_all_members_ready, patch("charm.PostgresqlOperatorCharm._get_ips_to_remove") as _get_ips_to_remove, patch( - "charm.PostgresqlOperatorCharm._updated_synchronous_node_count" + "charm.PostgresqlOperatorCharm.updated_synchronous_node_count" ) as _updated_synchronous_node_count, patch("charm.Patroni.remove_raft_member") as _remove_raft_member, patch("charm.PostgresqlOperatorCharm._unit_ip") as _unit_ip, @@ -2446,7 +2343,7 @@ def test_on_peer_relation_departed(harness): harness.charm._on_peer_relation_departed(event) _remove_raft_member.assert_called_once_with(mock_ip_address) event.defer.assert_called_once() - _updated_synchronous_node_count.assert_called_once_with(1) + _updated_synchronous_node_count.assert_called_once_with() _get_ips_to_remove.assert_not_called() _remove_from_members_ips.assert_not_called() _update_config.assert_not_called() @@ -2461,7 +2358,7 @@ def test_on_peer_relation_departed(harness): harness.charm._on_peer_relation_departed(event) _remove_raft_member.assert_called_once_with(mock_ip_address) event.defer.assert_called_once() - _updated_synchronous_node_count.assert_called_once_with(2) + _updated_synchronous_node_count.assert_called_once_with() _get_ips_to_remove.assert_not_called() _remove_from_members_ips.assert_not_called() _update_config.assert_not_called() @@ -2477,7 +2374,7 @@ def test_on_peer_relation_departed(harness): harness.charm._on_peer_relation_departed(event) _remove_raft_member.assert_called_once_with(mock_ip_address) event.defer.assert_not_called() - _updated_synchronous_node_count.assert_called_once_with(2) + _updated_synchronous_node_count.assert_called_once_with() _get_ips_to_remove.assert_called_once() _remove_from_members_ips.assert_not_called() _update_config.assert_not_called() @@ -2495,7 +2392,7 @@ def test_on_peer_relation_departed(harness): harness.charm._on_peer_relation_departed(event) _remove_raft_member.assert_called_once_with(mock_ip_address) event.defer.assert_called_once() - _updated_synchronous_node_count.assert_called_once_with(2) + _updated_synchronous_node_count.assert_called_once_with() _get_ips_to_remove.assert_called_once() _remove_from_members_ips.assert_not_called() _update_config.assert_not_called() @@ -2511,7 +2408,7 @@ def test_on_peer_relation_departed(harness): harness.charm._on_peer_relation_departed(event) _remove_raft_member.assert_called_once_with(mock_ip_address) event.defer.assert_not_called() - _updated_synchronous_node_count.assert_called_once_with(2) + _updated_synchronous_node_count.assert_called_once_with() _get_ips_to_remove.assert_called_once() _remove_from_members_ips.assert_has_calls([call(ips_to_remove[0]), call(ips_to_remove[1])]) assert _update_config.call_count == 2 @@ -2530,7 +2427,7 @@ def test_on_peer_relation_departed(harness): harness.charm._on_peer_relation_departed(event) _remove_raft_member.assert_called_once_with(mock_ip_address) event.defer.assert_not_called() - _updated_synchronous_node_count.assert_called_once_with(2) + _updated_synchronous_node_count.assert_called_once_with() _get_ips_to_remove.assert_called_once() _remove_from_members_ips.assert_called_once() _update_config.assert_called_once() @@ -2788,6 +2685,17 @@ def test_on_promote_to_primary(harness): harness.charm._on_promote_to_primary(event) + event.fail.assert_called_once_with( + "Switchover failed or timed out, check the logs for details" + ) + event.fail.reset_mock() + + # Unit, no force, not sync + event.params = {"scope": "unit"} + _switchover.side_effect = SwitchoverNotSyncError + + harness.charm._on_promote_to_primary(event) + event.fail.assert_called_once_with("Unit is not sync standby") event.fail.reset_mock() diff --git a/tests/unit/test_cluster.py b/tests/unit/test_cluster.py index d42b6085d3..92ab7dc212 100644 --- a/tests/unit/test_cluster.py +++ b/tests/unit/test_cluster.py @@ -20,7 +20,13 @@ ) from charm import PostgresqlOperatorCharm -from cluster import PATRONI_TIMEOUT, Patroni, RemoveRaftMemberFailedError +from cluster import ( + PATRONI_TIMEOUT, + Patroni, + RemoveRaftMemberFailedError, + SwitchoverFailedError, + SwitchoverNotSyncError, +) from constants import ( PATRONI_CONF_PATH, PATRONI_LOGS_PATH, @@ -346,7 +352,7 @@ def test_render_patroni_yml_file(peers_ips, patroni): rewind_user=REWIND_USER, rewind_password=rewind_password, version=postgresql_version, - minority_count=patroni.planned_units // 2, + synchronous_node_count=0, raft_password=raft_password, patroni_password=patroni_password, ) @@ -473,6 +479,22 @@ def test_switchover(peers_ips, patroni): timeout=PATRONI_TIMEOUT, ) + # Test candidate, not sync + response = _post.return_value + response.status_code = 412 + response.text = "candidate name does not match with sync_standby" + with pytest.raises(SwitchoverNotSyncError): + patroni.switchover("candidate") + assert False + + # Test general error + response = _post.return_value + response.status_code = 412 + response.text = "something else " + with pytest.raises(SwitchoverFailedError): + patroni.switchover() + assert False + def test_update_synchronous_node_count(peers_ips, patroni): with ( diff --git a/tests/unit/test_postgresql_provider.py b/tests/unit/test_postgresql_provider.py index 2aaed01083..399ebc2759 100644 --- a/tests/unit/test_postgresql_provider.py +++ b/tests/unit/test_postgresql_provider.py @@ -125,12 +125,17 @@ def test_on_database_requested(harness): # Assert that the correct calls were made. user = f"relation-{rel_id}" postgresql_mock.create_user.assert_called_once_with( - user, "test-password", extra_user_roles=EXTRA_USER_ROLES + user, + "test-password", + extra_user_roles=[role.lower() for role in EXTRA_USER_ROLES.split(",")], ) database_relation = harness.model.get_relation(RELATION_NAME) client_relations = [database_relation] postgresql_mock.create_database.assert_called_once_with( - DATABASE, user, plugins=["pgaudit"], client_relations=client_relations + DATABASE, + user, + plugins=["pgaudit"], + client_relations=client_relations, ) postgresql_mock.get_postgresql_version.assert_called_once() _update_endpoints.assert_called_once() diff --git a/tests/unit/test_upgrade.py b/tests/unit/test_upgrade.py index 7dfbfc521b..1baac2da9b 100644 --- a/tests/unit/test_upgrade.py +++ b/tests/unit/test_upgrade.py @@ -106,6 +106,9 @@ def test_on_upgrade_granted(harness): patch("charm.Patroni.start_patroni") as _start_patroni, patch("charm.PostgresqlOperatorCharm._install_snap_packages") as _install_snap_packages, patch("charm.PostgresqlOperatorCharm.update_config") as _update_config, + patch( + "charm.PostgresqlOperatorCharm.updated_synchronous_node_count" + ) as _updated_synchronous_node_count, ): # Test when the charm fails to start Patroni. mock_event = MagicMock() @@ -174,6 +177,7 @@ def test_on_upgrade_granted(harness): _member_started.reset_mock() _cluster_members.reset_mock() mock_event.defer.reset_mock() + _updated_synchronous_node_count.reset_mock() _is_replication_healthy.return_value = True with harness.hooks_disabled(): harness.set_leader(True) @@ -184,6 +188,7 @@ def test_on_upgrade_granted(harness): _set_unit_completed.assert_called_once() _set_unit_failed.assert_not_called() _on_upgrade_changed.assert_called_once() + _updated_synchronous_node_count.assert_called_once_with() def test_pre_upgrade_check(harness): diff --git a/tox.ini b/tox.ini index 508d1a645f..0f7b4d4bd4 100644 --- a/tox.ini +++ b/tox.ini @@ -57,8 +57,13 @@ commands = description = Run integration tests pass_env = CI - GITHUB_OUTPUT - SECRETS_FROM_GITHUB + AWS_ACCESS_KEY + AWS_SECRET_KEY + GCP_ACCESS_KEY + GCP_SECRET_KEY + UBUNTU_PRO_TOKEN + LANDSCAPE_ACCOUNT_NAME + LANDSCAPE_REGISTRATION_KEY commands_pre = poetry install --only integration --no-root commands = From 752483f042e17168955dc1414a923fc9b7e0e710 Mon Sep 17 00:00:00 2001 From: Dragomir Penev Date: Thu, 13 Mar 2025 14:35:46 +0200 Subject: [PATCH 63/74] Dual branch configs --- .github/renovate.json5 | 32 +------------------------------- .github/workflows/release.yaml | 4 ++-- 2 files changed, 3 insertions(+), 33 deletions(-) diff --git a/.github/renovate.json5 b/.github/renovate.json5 index 34085c9225..3932a46a8d 100644 --- a/.github/renovate.json5 +++ b/.github/renovate.json5 @@ -6,39 +6,9 @@ reviewers: [ 'team:data-platform-postgresql', ], + "baseBranches": ["main", "/^*\\/edge$/"], packageRules: [ - { - matchPackageNames: [ - 'pydantic', - ], - allowedVersions: '<2.0.0', - }, - { - matchManagers: [ - 'custom.regex', - ], - matchDepNames: [ - 'juju', - ], - matchDatasources: [ - 'pypi', - ], - allowedVersions: '<3', - groupName: 'Juju agents', - }, ], customManagers: [ - { - customType: 'regex', - fileMatch: [ - '^\\.github/workflows/[^/]+\\.ya?ml$', - ], - matchStrings: [ - '(libjuju: )==(?.*?) +# renovate: latest libjuju 2', - ], - depNameTemplate: 'juju', - datasourceTemplate: 'pypi', - versioningTemplate: 'loose', - }, ], } diff --git a/.github/workflows/release.yaml b/.github/workflows/release.yaml index 0068821d81..5907046788 100644 --- a/.github/workflows/release.yaml +++ b/.github/workflows/release.yaml @@ -5,7 +5,7 @@ name: Release to Charmhub on: push: branches: - - 16/edge + - '*/edge' paths-ignore: - 'tests/**' - 'docs/**' @@ -31,7 +31,7 @@ jobs: - ci-tests uses: canonical/data-platform-workflows/.github/workflows/release_charm.yaml@v30.2.0 with: - channel: 16/edge + channel: ${{ github.ref_name }} artifact-prefix: ${{ needs.ci-tests.outputs.artifact-prefix }} secrets: charmhub-token: ${{ secrets.CHARMHUB_TOKEN }} From 2f7ea10a49045223b2b1cc4b2fe0216df79276ba Mon Sep 17 00:00:00 2001 From: Dragomir Penev Date: Thu, 13 Mar 2025 14:36:27 +0200 Subject: [PATCH 64/74] Revert lib changes --- lib/charms/postgresql_k8s/v0/postgresql.py | 70 +++++++++------------- 1 file changed, 28 insertions(+), 42 deletions(-) diff --git a/lib/charms/postgresql_k8s/v0/postgresql.py b/lib/charms/postgresql_k8s/v0/postgresql.py index e3ba9e6c88..8e2b7072ad 100644 --- a/lib/charms/postgresql_k8s/v0/postgresql.py +++ b/lib/charms/postgresql_k8s/v0/postgresql.py @@ -35,7 +35,7 @@ # Increment this PATCH version before using `charmcraft publish-lib` or reset # to 0 if you are raising the major API version -LIBPATCH = 47 +LIBPATCH = 45 # Groups to distinguish database permissions PERMISSIONS_GROUP_ADMIN = "admin" @@ -304,10 +304,9 @@ def delete_user(self, user: str) -> None: # Existing objects need to be reassigned in each database # before the user can be deleted. for database in databases: - with ( - self._connect_to_database(database) as connection, - connection.cursor() as cursor, - ): + with self._connect_to_database( + database + ) as connection, connection.cursor() as cursor: cursor.execute( SQL("REASSIGN OWNED BY {} TO {};").format( Identifier(user), Identifier(self.user) @@ -322,7 +321,7 @@ def delete_user(self, user: str) -> None: logger.error(f"Failed to delete user: {e}") raise PostgreSQLDeleteUserError() from e - def enable_disable_extensions( # noqa: C901 + def enable_disable_extensions( self, extensions: Dict[str, bool], database: Optional[str] = None ) -> None: """Enables or disables a PostgreSQL extension. @@ -352,12 +351,10 @@ def enable_disable_extensions( # noqa: C901 # Enable/disabled the extension in each database. for database in databases: - connection = self._connect_to_database(database=database) - connection.autocommit = True - with connection.cursor() as cursor: + with self._connect_to_database( + database=database + ) as connection, connection.cursor() as cursor: for extension, enable in ordered_extensions.items(): - if extension == "postgis": - cursor.execute("SET pgaudit.log = 'none';") cursor.execute( f"CREATE EXTENSION IF NOT EXISTS {extension};" if enable @@ -379,7 +376,6 @@ def _generate_database_privileges_statements( ) -> List[Composed]: """Generates a list of databases privileges statements.""" statements = [] - statements.append(SQL("GRANT USAGE, CREATE ON SCHEMA public TO PUBLIC;")) if relations_accessing_this_database == 1: statements.append( SQL( @@ -436,10 +432,8 @@ def _generate_database_privileges_statements( SQL("GRANT ALL PRIVILEGES ON ALL FUNCTIONS IN SCHEMA {} TO {};").format( schema, Identifier(user) ), - SQL("GRANT USAGE, CREATE ON SCHEMA {} TO {};").format( - schema, Identifier(user) - ), - SQL("GRANT USAGE, CREATE ON SCHEMA {} TO admin;").format(schema), + SQL("GRANT USAGE ON SCHEMA {} TO {};").format(schema, Identifier(user)), + SQL("GRANT CREATE ON SCHEMA {} TO {};").format(schema, Identifier(user)), ]) return statements @@ -469,10 +463,9 @@ def get_postgresql_text_search_configs(self) -> Set[str]: Returns: Set of PostgreSQL text search configs. """ - with ( - self._connect_to_database(database_host=self.current_host) as connection, - connection.cursor() as cursor, - ): + with self._connect_to_database( + database_host=self.current_host + ) as connection, connection.cursor() as cursor: cursor.execute("SELECT CONCAT('pg_catalog.', cfgname) FROM pg_ts_config;") text_search_configs = cursor.fetchall() return {text_search_config[0] for text_search_config in text_search_configs} @@ -483,10 +476,9 @@ def get_postgresql_timezones(self) -> Set[str]: Returns: Set of PostgreSQL timezones. """ - with ( - self._connect_to_database(database_host=self.current_host) as connection, - connection.cursor() as cursor, - ): + with self._connect_to_database( + database_host=self.current_host + ) as connection, connection.cursor() as cursor: cursor.execute("SELECT name FROM pg_timezone_names;") timezones = cursor.fetchall() return {timezone[0] for timezone in timezones} @@ -499,10 +491,9 @@ def get_postgresql_version(self, current_host=True) -> str: """ host = self.current_host if current_host else None try: - with ( - self._connect_to_database(database_host=host) as connection, - connection.cursor() as cursor, - ): + with self._connect_to_database( + database_host=host + ) as connection, connection.cursor() as cursor: cursor.execute("SELECT version();") # Split to get only the version number. return cursor.fetchone()[0].split(" ")[1] @@ -521,12 +512,9 @@ def is_tls_enabled(self, check_current_host: bool = False) -> bool: whether TLS is enabled. """ try: - with ( - self._connect_to_database( - database_host=self.current_host if check_current_host else None - ) as connection, - connection.cursor() as cursor, - ): + with self._connect_to_database( + database_host=self.current_host if check_current_host else None + ) as connection, connection.cursor() as cursor: cursor.execute("SHOW ssl;") return "on" in cursor.fetchone()[0] except psycopg2.Error: @@ -608,10 +596,9 @@ def update_user_password( """ connection = None try: - with ( - self._connect_to_database(database_host=database_host) as connection, - connection.cursor() as cursor, - ): + with self._connect_to_database( + database_host=database_host + ) as connection, connection.cursor() as cursor: cursor.execute(SQL("BEGIN;")) cursor.execute(SQL("SET LOCAL log_statement = 'none';")) cursor.execute( @@ -708,10 +695,9 @@ def validate_date_style(self, date_style: str) -> bool: Whether the date style is valid. """ try: - with ( - self._connect_to_database(database_host=self.current_host) as connection, - connection.cursor() as cursor, - ): + with self._connect_to_database( + database_host=self.current_host + ) as connection, connection.cursor() as cursor: cursor.execute( SQL( "SET DateStyle to {};", From 9450a26ae8668e63a90a054f0e95bf2f5324cc20 Mon Sep 17 00:00:00 2001 From: Dragomir Penev Date: Thu, 13 Mar 2025 14:42:35 +0200 Subject: [PATCH 65/74] Remove legacy rels --- src/charm.py | 29 +- src/constants.py | 5 +- src/relations/db.py | 436 -------------------------- tests/unit/test_charm.py | 39 +-- tests/unit/test_db.py | 638 --------------------------------------- 5 files changed, 3 insertions(+), 1144 deletions(-) delete mode 100644 src/relations/db.py delete mode 100644 tests/unit/test_db.py diff --git a/src/charm.py b/src/charm.py index 00437c4e0e..891cf5bbfc 100755 --- a/src/charm.py +++ b/src/charm.py @@ -103,7 +103,6 @@ REPLICATION_OFFER_RELATION, PostgreSQLAsyncReplication, ) -from relations.db import EXTENSIONS_BLOCKING_MESSAGE, DbProvides from relations.postgresql_provider import PostgreSQLProvider from rotate_logs import RotateLogs from upgrade import PostgreSQLUpgrade, get_postgresql_dependencies_model @@ -128,7 +127,6 @@ class CannotConnectError(Exception): extra_types=( ClusterTopologyObserver, COSAgentProvider, - DbProvides, Patroni, PostgreSQL, PostgreSQLAsyncReplication, @@ -199,8 +197,6 @@ def __init__(self, *args): substrate="vm", ) self.postgresql_client_relation = PostgreSQLProvider(self) - self.legacy_db_relation = DbProvides(self, admin=False) - self.legacy_db_admin_relation = DbProvides(self, admin=True) self.backup = PostgreSQLBackups(self, "s3-parameters") self.tls = PostgreSQLTLS(self, PEER) self.async_replication = PostgreSQLAsyncReplication(self) @@ -1145,23 +1141,6 @@ def _on_config_changed(self, event) -> None: # Enable and/or disable the extensions. self.enable_disable_extensions() - self._unblock_extensions() - - def _unblock_extensions(self) -> None: - # Unblock the charm after extensions are enabled (only if it's blocked due to application - # charms requesting extensions). - if self.unit.status.message != EXTENSIONS_BLOCKING_MESSAGE: - return - - for relation in [ - *self.model.relations.get("db", []), - *self.model.relations.get("db-admin", []), - ]: - if not self.legacy_db_relation.set_up_relation(relation): - logger.debug( - "Early exit on_config_changed: legacy relation requested extensions that are still disabled" - ) - return def enable_disable_extensions(self, database: str | None = None) -> None: """Enable/disable PostgreSQL extensions set through config options. @@ -1995,8 +1974,6 @@ def _handle_postgresql_restart_need(self, enable_tls: bool) -> None: def _update_relation_endpoints(self) -> None: """Updates endpoints and read-only endpoint in all relations.""" self.postgresql_client_relation.update_endpoints() - self.legacy_db_relation.update_endpoints() - self.legacy_db_admin_relation.update_endpoints() def get_available_memory(self) -> int: """Returns the system available memory in bytes.""" @@ -2010,11 +1987,7 @@ def get_available_memory(self) -> int: @property def client_relations(self) -> list[Relation]: """Return the list of established client relations.""" - relations = [] - for relation_name in ["database", "db", "db-admin"]: - for relation in self.model.relations.get(relation_name, []): - relations.append(relation) - return relations + return self.model.relations.get("database", []) def override_patroni_restart_condition( self, new_condition: str, repeat_cause: str | None diff --git a/src/constants.py b/src/constants.py index a16bd5ab70..e0ffb6e111 100644 --- a/src/constants.py +++ b/src/constants.py @@ -8,11 +8,8 @@ DATABASE = "database" DATABASE_DEFAULT_NAME = "postgres" DATABASE_PORT = "5432" -LEGACY_DB = "db" -LEGACY_DB_ADMIN = "db-admin" PEER = "database-peers" -ALL_CLIENT_RELATIONS = [DATABASE, LEGACY_DB, LEGACY_DB_ADMIN] -ALL_LEGACY_RELATIONS = [LEGACY_DB, LEGACY_DB_ADMIN] +ALL_CLIENT_RELATIONS = [DATABASE] API_REQUEST_TIMEOUT = 5 PATRONI_CLUSTER_STATUS_ENDPOINT = "cluster" BACKUP_USER = "backup" diff --git a/src/relations/db.py b/src/relations/db.py deleted file mode 100644 index 5f7d8f9ea5..0000000000 --- a/src/relations/db.py +++ /dev/null @@ -1,436 +0,0 @@ -# Copyright 2022 Canonical Ltd. -# See LICENSE file for licensing details. - -"""Library containing the implementation of the legacy db and db-admin relations.""" - -import logging -from collections.abc import Iterable - -from charms.postgresql_k8s.v0.postgresql import ( - PostgreSQLCreateDatabaseError, - PostgreSQLCreateUserError, - PostgreSQLGetPostgreSQLVersionError, -) -from ops.charm import ( - CharmBase, - RelationBrokenEvent, - RelationChangedEvent, - RelationDepartedEvent, -) -from ops.framework import Object -from ops.model import ActiveStatus, BlockedStatus, Relation, Unit -from pgconnstr import ConnectionString - -from constants import ( - ALL_LEGACY_RELATIONS, - APP_SCOPE, - DATABASE_PORT, - ENDPOINT_SIMULTANEOUSLY_BLOCKING_MESSAGE, -) -from utils import new_password - -logger = logging.getLogger(__name__) - -EXTENSIONS_BLOCKING_MESSAGE = ( - "extensions requested through relation, enable them through config options" -) - -ROLES_BLOCKING_MESSAGE = ( - "roles requested through relation, use postgresql_client interface instead" -) - - -class DbProvides(Object): - """Defines functionality for the 'provides' side of the 'db' relation. - - Hook events observed: - - relation-changed - - relation-departed - - relation-broken - """ - - def __init__(self, charm: CharmBase, admin: bool = False): - """Constructor for DbProvides object. - - Args: - charm: the charm for which this relation is provided - admin: a boolean defining whether this relation has admin permissions, switching - between "db" and "db-admin" relations. - """ - if admin: - self.relation_name = "db-admin" - else: - self.relation_name = "db" - - super().__init__(charm, self.relation_name) - - self.framework.observe( - charm.on[self.relation_name].relation_changed, self._on_relation_changed - ) - self.framework.observe( - charm.on[self.relation_name].relation_departed, self._on_relation_departed - ) - self.framework.observe( - charm.on[self.relation_name].relation_broken, self._on_relation_broken - ) - - self.admin = admin - self.charm = charm - - def _check_for_blocking_relations(self, relation_id: int) -> bool: - """Checks if there are relations with extensions or roles. - - Args: - relation_id: current relation to be skipped - """ - for relname in ["db", "db-admin"]: - for relation in self.charm.model.relations.get(relname, []): - if relation.id == relation_id: - continue - for data in relation.data.values(): - if "extensions" in data or "roles" in data: - return True - return False - - def _check_exist_current_relation(self) -> bool: - return any(r in ALL_LEGACY_RELATIONS for r in self.charm.client_relations) - - def _check_multiple_endpoints(self) -> bool: - """Checks if there are relations with other endpoints.""" - is_exist = self._check_exist_current_relation() - for relation in self.charm.client_relations: - if relation.name not in ALL_LEGACY_RELATIONS and is_exist: - return True - return False - - def _on_relation_changed(self, event: RelationChangedEvent) -> None: - """Handle the legacy db/db-admin relation changed event. - - Generate password and handle user and database creation for the related application. - """ - # Check for some conditions before trying to access the PostgreSQL instance. - if not self.charm.unit.is_leader(): - return - - if self._check_multiple_endpoints(): - self.charm.unit.status = BlockedStatus(ENDPOINT_SIMULTANEOUSLY_BLOCKING_MESSAGE) - return - - if ( - not self.charm.is_cluster_initialised - or not self.charm._patroni.member_started - or not self.charm.primary_endpoint - ): - logger.debug( - "Deferring on_relation_changed: cluster not initialized, Patroni not started or primary endpoint not available" - ) - event.defer() - return - - logger.warning(f"DEPRECATION WARNING - `{self.relation_name}` is a legacy interface") - - self.set_up_relation(event.relation) - - def _get_extensions(self, relation: Relation) -> tuple[list, set]: - """Returns the list of required and disabled extensions.""" - requested_extensions = relation.data.get(relation.app, {}).get("extensions", "").split(",") - for unit in relation.units: - requested_extensions.extend( - relation.data.get(unit, {}).get("extensions", "").split(",") - ) - required_extensions = [] - for extension in requested_extensions: - if extension != "" and extension not in required_extensions: - required_extensions.append(extension) - disabled_extensions = set() - if required_extensions: - for extension in required_extensions: - extension_name = extension.split(":")[0] - if not self.charm.model.config.get(f"plugin_{extension_name}_enable"): - disabled_extensions.add(extension_name) - return required_extensions, disabled_extensions - - def _get_roles(self, relation: Relation) -> bool: - """Checks if relation required roles.""" - return "roles" in relation.data.get(relation.app, {}) - - def set_up_relation(self, relation: Relation) -> bool: - """Set up the relation to be used by the application charm.""" - # Do not allow apps requesting extensions to be installed - # (let them now about config options). - _, disabled_extensions = self._get_extensions(relation) - if disabled_extensions: - logger.error( - f"ERROR - `extensions` ({', '.join(disabled_extensions)}) cannot be requested through relations" - " - Please enable extensions through `juju config` and add the relation again." - ) - self.charm.unit.status = BlockedStatus(EXTENSIONS_BLOCKING_MESSAGE) - return False - if self._get_roles(relation): - self.charm.unit.status = BlockedStatus(ROLES_BLOCKING_MESSAGE) - return False - - user = f"relation-{relation.id}" - database = relation.data.get(relation.app, {}).get( - "database", self.charm.get_secret(APP_SCOPE, f"{user}-database") - ) - if not database: - for unit in relation.units: - unit_database = relation.data.get(unit, {}).get("database") - if unit_database: - database = unit_database - break - - # Sometimes a relation changed event is triggered, and it doesn't have - # a database name in it (like the relation with Landscape server charm), - # so create a database with the other application name. - if not database: - database = relation.app.name - - try: - unit_relation_databag = relation.data[self.charm.unit] - - # Creates the user and the database for this specific relation if it was not already - # created in a previous relation changed event. - password = unit_relation_databag.get("password", new_password()) - - # Store the user, password and database name in the secret store to be accessible by - # non-leader units when the cluster topology changes. - self.charm.set_secret(APP_SCOPE, user, password) - self.charm.set_secret(APP_SCOPE, f"{user}-database", database) - - self.charm.postgresql.create_user(user, password, self.admin) - plugins = self.charm.get_plugins() - - self.charm.postgresql.create_database( - database, user, plugins=plugins, client_relations=self.charm.client_relations - ) - - except (PostgreSQLCreateDatabaseError, PostgreSQLCreateUserError) as e: - logger.exception(e) - self.charm.unit.status = BlockedStatus( - f"Failed to initialize {self.relation_name} relation" - ) - return False - - self.update_endpoints(relation) - - self._update_unit_status(relation) - - return True - - def _on_relation_departed(self, event: RelationDepartedEvent) -> None: - """Handle the departure of legacy db and db-admin relations. - - Remove unit name from allowed_units key. - """ - # Set a flag to avoid deleting database users when this unit - # is removed and receives relation broken events from related applications. - # This is needed because of https://bugs.launchpad.net/juju/+bug/1979811. - # Neither peer relation data nor stored state are good solutions, - # just a temporary solution. - if event.departing_unit == self.charm.unit: - self.charm._peers.data[self.charm.unit].update({"departing": "True"}) - # Just run the rest of the logic for departing of remote units. - logger.debug("Early exit on_relation_departed: Skipping departing unit") - return - - # Check for some conditions before trying to access the PostgreSQL instance. - if not self.charm.unit.is_leader(): - return - - if ( - not self.charm.is_cluster_initialised - or not self.charm._patroni.member_started - or not self.charm.primary_endpoint - ): - logger.debug( - "Deferring on_relation_departed: cluster not initialized, Patroni not started or primary endpoint not available" - ) - event.defer() - return - - departing_unit = event.departing_unit.name - local_unit_data = event.relation.data[self.charm.unit] - local_app_data = event.relation.data[self.charm.app] - - current_allowed_units = local_unit_data.get("allowed_units", "") - - logger.debug(f"Removing unit {departing_unit} from allowed_units") - local_app_data["allowed_units"] = local_unit_data["allowed_units"] = " ".join({ - unit for unit in current_allowed_units.split() if unit != departing_unit - }) - - def _on_relation_broken(self, event: RelationBrokenEvent) -> None: - """Remove the user created for this relation.""" - # Check for some conditions before trying to access the PostgreSQL instance. - if ( - not self.charm.unit.is_leader() - or not self.charm.is_cluster_initialised - or not self.charm._patroni.member_started - or not self.charm.primary_endpoint - ): - logger.debug( - "Early exit on_relation_broken: Not leader, cluster not initialized, Patroni not started or no primary endpoint" - ) - return - - # Run this event only if this unit isn't being - # removed while the others from this application - # are still alive. This check is needed because of - # https://bugs.launchpad.net/juju/+bug/1979811. - # Neither peer relation data nor stored state - # are good solutions, just a temporary solution. - if self.charm.is_unit_departing: - logger.debug("Early exit on_relation_broken: Skipping departing unit") - return - - self._update_unit_status(event.relation) - - def _update_unit_status(self, relation: Relation) -> None: - """Clean up Blocked status if it's due to extensions request.""" - if ( - self.charm.is_blocked - and self.charm.unit.status.message - in [ - EXTENSIONS_BLOCKING_MESSAGE, - ROLES_BLOCKING_MESSAGE, - ] - and not self._check_for_blocking_relations(relation.id) - ): - self.charm.unit.status = ActiveStatus() - self._update_unit_status_on_blocking_endpoint_simultaneously() - - def _update_unit_status_on_blocking_endpoint_simultaneously(self): - """Clean up Blocked status if this is due related of multiple endpoints.""" - if ( - self.charm.is_blocked - and self.charm.unit.status.message == ENDPOINT_SIMULTANEOUSLY_BLOCKING_MESSAGE - and not self._check_multiple_endpoints() - ): - self.charm.unit.status = ActiveStatus() - - def update_endpoints(self, relation: Relation = None) -> None: - """Set the read/write and read-only endpoints.""" - # Get the current relation or all the relations - # if this is triggered by another type of event. - relations = [relation] if relation else self.model.relations[self.relation_name] - if len(relations) == 0: - return - - postgresql_version = None - try: - postgresql_version = self.charm.postgresql.get_postgresql_version() - except PostgreSQLGetPostgreSQLVersionError: - logger.exception( - f"Failed to retrieve the PostgreSQL version to initialise/update {self.relation_name} relation" - ) - - # List the replicas endpoints. - replicas_endpoint = list(self.charm.members_ips - {self.charm.primary_endpoint}) - replicas_endpoint.sort() - - for relation in relations: - # Retrieve some data from the relation. - unit_relation_databag = relation.data[self.charm.unit] - user = f"relation-{relation.id}" - password = self.charm.get_secret(APP_SCOPE, user) - database = self.charm.get_secret(APP_SCOPE, f"{user}-database") - - # If the relation data is not complete, the relations was not initialised yet. - if not database or not user or not password: - continue - - # Build the primary's connection string. - primary_endpoint = str( - ConnectionString( - host=self.charm.primary_endpoint, - dbname=database, - port=DATABASE_PORT, - user=user, - password=password, - ) - ) - - # If there are no replicas, remove the read-only endpoint. - read_only_endpoints = ( - ",".join( - str( - ConnectionString( - host=replica_endpoint, - dbname=database, - port=DATABASE_PORT, - user=user, - password=password, - ) - ) - for replica_endpoint in replicas_endpoint - ) - if len(replicas_endpoint) > 0 - else "" - ) - - required_extensions, _ = self._get_extensions(relation) - # Set the read/write endpoint. - allowed_subnets = self._get_allowed_subnets(relation) - allowed_units = self._get_allowed_units(relation) - data = { - "allowed-subnets": allowed_subnets, - "allowed-units": allowed_units, - "host": self.charm.primary_endpoint, - "port": DATABASE_PORT, - "user": user, - "schema_user": user, - "password": password, - "schema_password": password, - "database": database, - "master": primary_endpoint, - "standbys": read_only_endpoints, - "state": self._get_state(), - "extensions": ",".join(required_extensions), - } - if postgresql_version: - data["version"] = postgresql_version - - # Set the data only in the unit databag. - unit_relation_databag.update(data) - - def _get_allowed_subnets(self, relation: Relation) -> str: - """Build the list of allowed subnets as in the legacy charm.""" - - def _comma_split(s) -> Iterable[str]: - if s: - for b in s.split(","): - b = b.strip() - if b: - yield b - - subnets = set() - for unit, relation_data in relation.data.items(): - if isinstance(unit, Unit) and not unit.name.startswith(self.model.app.name): - # Egress-subnets is not always available. - subnets.update(set(_comma_split(relation_data.get("egress-subnets", "")))) - return ",".join(sorted(subnets)) - - def _get_allowed_units(self, relation: Relation) -> str: - """Build the list of allowed units as in the legacy charm.""" - return " ".join( - sorted( - unit.name - for unit in relation.data - if isinstance(unit, Unit) and not unit.name.startswith(self.model.app.name) - ) - ) - - def _get_state(self) -> str: - """Gets the given state for this unit. - - Returns: - The state of this unit. Can be 'standalone', 'master', or 'standby'. - """ - if len(self.charm._peers.units) == 0: - return "standalone" - if self.charm._patroni.get_primary(unit_name_pattern=True) == self.charm.unit.name: - return "master" - else: - return "hot standby" diff --git a/tests/unit/test_charm.py b/tests/unit/test_charm.py index e8841246ce..2e7f1cfcda 100644 --- a/tests/unit/test_charm.py +++ b/tests/unit/test_charm.py @@ -279,7 +279,6 @@ def test_on_config_changed(harness): patch( "charm.PostgresqlOperatorCharm.updated_synchronous_node_count", return_value=True ) as _updated_synchronous_node_count, - patch("relations.db.DbProvides.set_up_relation") as _set_up_relation, patch( "charm.PostgresqlOperatorCharm.enable_disable_extensions" ) as _enable_disable_extensions, @@ -291,14 +290,12 @@ def test_on_config_changed(harness): _is_cluster_initialised.return_value = False harness.charm.on.config_changed.emit() _enable_disable_extensions.assert_not_called() - _set_up_relation.assert_not_called() # Test when the unit is not the leader. _is_cluster_initialised.return_value = True harness.charm.on.config_changed.emit() _validate_config_options.assert_called_once() _enable_disable_extensions.assert_not_called() - _set_up_relation.assert_not_called() # Test unable to connect to db _update_config.reset_mock() @@ -313,7 +310,6 @@ def test_on_config_changed(harness): harness.set_leader() harness.charm.on.config_changed.emit() _enable_disable_extensions.assert_called_once() - _set_up_relation.assert_not_called() # Test when the unit is in a blocked state due to extensions request, # but there are no established legacy relations. @@ -323,35 +319,6 @@ def test_on_config_changed(harness): ) harness.charm.on.config_changed.emit() _enable_disable_extensions.assert_called_once() - _set_up_relation.assert_not_called() - - # Test when the unit is in a blocked state due to extensions request, - # but there are established legacy relations. - _enable_disable_extensions.reset_mock() - _set_up_relation.return_value = False - db_relation_id = harness.add_relation("db", "application") - harness.charm.on.config_changed.emit() - _enable_disable_extensions.assert_called_once() - _set_up_relation.assert_called_once() - with harness.hooks_disabled(): - harness.remove_relation(db_relation_id) - - _enable_disable_extensions.reset_mock() - _set_up_relation.reset_mock() - harness.add_relation("db-admin", "application") - harness.charm.on.config_changed.emit() - _enable_disable_extensions.assert_called_once() - _set_up_relation.assert_called_once() - - # Test when there are established legacy relations, - # but the charm fails to set up one of them. - _enable_disable_extensions.reset_mock() - _set_up_relation.reset_mock() - _set_up_relation.return_value = False - harness.add_relation("db", "application") - harness.charm.on.config_changed.emit() - _enable_disable_extensions.assert_called_once() - _set_up_relation.assert_called_once() def test_check_extension_dependencies(harness): @@ -1827,12 +1794,8 @@ def test_client_relations(harness): # Test when the charm has some relations. harness.add_relation("database", "application") - harness.add_relation("db", "legacy-application") - harness.add_relation("db-admin", "legacy-admin-application") database_relation = harness.model.get_relation("database") - db_relation = harness.model.get_relation("db") - db_admin_relation = harness.model.get_relation("db-admin") - assert harness.charm.client_relations == [database_relation, db_relation, db_admin_relation] + assert harness.charm.client_relations == [database_relation] def test_add_cluster_member(harness): diff --git a/tests/unit/test_db.py b/tests/unit/test_db.py deleted file mode 100644 index d5ebd95e97..0000000000 --- a/tests/unit/test_db.py +++ /dev/null @@ -1,638 +0,0 @@ -# Copyright 2022 Canonical Ltd. -# See LICENSE file for licensing details. - -from unittest.mock import Mock, PropertyMock, patch - -import pytest -from charms.postgresql_k8s.v0.postgresql import ( - PostgreSQLCreateDatabaseError, - PostgreSQLCreateUserError, - PostgreSQLGetPostgreSQLVersionError, -) -from ops.framework import EventBase -from ops.model import ActiveStatus, BlockedStatus -from ops.testing import Harness - -from charm import PostgresqlOperatorCharm -from constants import DATABASE_PORT, PEER - -DATABASE = "test_database" -RELATION_NAME = "db" -POSTGRESQL_VERSION = "12" - - -@pytest.fixture(autouse=True) -def harness(): - harness = Harness(PostgresqlOperatorCharm) - - # Set up the initial relation and hooks. - harness.set_leader(True) - harness.begin() - - # Define some relations. - rel_id = harness.add_relation(RELATION_NAME, "application") - harness.add_relation_unit(rel_id, "application/0") - peer_rel_id = harness.add_relation(PEER, harness.charm.app.name) - harness.add_relation_unit(peer_rel_id, f"{harness.charm.app.name}/1") - harness.add_relation_unit(peer_rel_id, harness.charm.unit.name) - harness.update_relation_data( - peer_rel_id, - harness.charm.app.name, - {"cluster_initialised": "True"}, - ) - yield harness - harness.cleanup() - - -def request_database(_harness): - # Reset the charm status. - _harness.model.unit.status = ActiveStatus() - rel_id = _harness.model.get_relation(RELATION_NAME).id - - with _harness.hooks_disabled(): - # Reset the application databag. - _harness.update_relation_data( - rel_id, - "application/0", - {"database": ""}, - ) - - # Reset the database databag. - _harness.update_relation_data( - rel_id, - _harness.charm.app.name, - { - "allowed-subnets": "", - "allowed-units": "", - "port": "", - "version": "", - "user": "", - "password": "", - "database": "", - }, - ) - - # Simulate the request of a new database. - _harness.update_relation_data( - rel_id, - "application/0", - {"database": DATABASE}, - ) - - -def test_on_relation_changed(harness): - with ( - patch("charm.DbProvides.set_up_relation") as _set_up_relation, - patch.object(EventBase, "defer") as _defer, - patch( - "charm.PostgresqlOperatorCharm.primary_endpoint", - new_callable=PropertyMock, - ) as _primary_endpoint, - patch("charm.Patroni.member_started", new_callable=PropertyMock) as _member_started, - ): - # Set some side effects to test multiple situations. - _member_started.side_effect = [False, True, True, True, True, True] - _primary_endpoint.side_effect = [ - None, - {"1.1.1.1"}, - {"1.1.1.1"}, - {"1.1.1.1"}, - {"1.1.1.1"}, - ] - # Request a database to a non leader unit. - with harness.hooks_disabled(): - harness.set_leader(False) - request_database(harness) - _defer.assert_not_called() - _set_up_relation.assert_not_called() - - # Request a database before the database is ready. - with harness.hooks_disabled(): - harness.set_leader() - request_database(harness) - _defer.assert_called_once() - _set_up_relation.assert_not_called() - - # Request a database before primary endpoint is available. - request_database(harness) - assert _defer.call_count == 2 - _set_up_relation.assert_not_called() - - # Request it again when the database is ready. - _defer.reset_mock() - request_database(harness) - _defer.assert_not_called() - _set_up_relation.assert_called_once() - - -def test_get_extensions(harness): - # Test when there are no extensions in the relation databags. - rel_id = harness.model.get_relation(RELATION_NAME).id - relation = harness.model.get_relation(RELATION_NAME, rel_id) - assert harness.charm.legacy_db_relation._get_extensions(relation) == ([], set()) - - # Test when there are extensions in the application relation databag. - extensions = ["", "citext:public", "debversion"] - with harness.hooks_disabled(): - harness.update_relation_data( - rel_id, - "application", - {"extensions": ",".join(extensions)}, - ) - assert harness.charm.legacy_db_relation._get_extensions(relation) == ( - [extensions[1], extensions[2]], - {extensions[1].split(":")[0], extensions[2]}, - ) - - # Test when there are extensions in the unit relation databag. - with harness.hooks_disabled(): - harness.update_relation_data( - rel_id, - "application", - {"extensions": ""}, - ) - harness.update_relation_data( - rel_id, - "application/0", - {"extensions": ",".join(extensions)}, - ) - assert harness.charm.legacy_db_relation._get_extensions(relation) == ( - [extensions[1], extensions[2]], - {extensions[1].split(":")[0], extensions[2]}, - ) - - # Test when one of the plugins/extensions is enabled. - config = """options: - plugin_citext_enable: - default: true - type: boolean - plugin_debversion_enable: - default: false - type: boolean""" - harness = Harness(PostgresqlOperatorCharm, config=config) - harness.cleanup() - harness.begin() - assert harness.charm.legacy_db_relation._get_extensions(relation) == ( - [extensions[1], extensions[2]], - {extensions[2]}, - ) - - -def test_set_up_relation(harness): - with ( - patch.object(PostgresqlOperatorCharm, "postgresql", Mock()) as postgresql_mock, - patch("subprocess.check_output", return_value=b"C"), - patch("relations.db.DbProvides._update_unit_status") as _update_unit_status, - patch("relations.db.DbProvides.update_endpoints") as _update_endpoints, - patch("relations.db.new_password", return_value="test-password") as _new_password, - patch("relations.db.DbProvides._get_extensions") as _get_extensions, - ): - rel_id = harness.model.get_relation(RELATION_NAME).id - # Define some mocks' side effects. - extensions = ["citext:public", "debversion"] - _get_extensions.side_effect = [ - (extensions, {"debversion"}), - (extensions, set()), - (extensions, set()), - (extensions, set()), - (extensions, set()), - (extensions, set()), - (extensions, set()), - ] - postgresql_mock.create_user = PropertyMock( - side_effect=[None, None, None, PostgreSQLCreateUserError, None, None] - ) - postgresql_mock.create_database = PropertyMock( - side_effect=[None, None, None, PostgreSQLCreateDatabaseError, None] - ) - - # Assert no operation is done when at least one of the requested extensions - # is disabled. - relation = harness.model.get_relation(RELATION_NAME, rel_id) - assert not harness.charm.legacy_db_relation.set_up_relation(relation) - postgresql_mock.create_user.assert_not_called() - postgresql_mock.create_database.assert_not_called() - postgresql_mock.get_postgresql_version.assert_not_called() - _update_endpoints.assert_not_called() - _update_unit_status.assert_not_called() - - # Assert that the correct calls were made in a successful setup. - harness.charm.unit.status = ActiveStatus() - with harness.hooks_disabled(): - harness.update_relation_data( - rel_id, - "application", - {"database": DATABASE}, - ) - assert harness.charm.legacy_db_relation.set_up_relation(relation) - user = f"relation-{rel_id}" - postgresql_mock.create_user.assert_called_once_with(user, "test-password", False) - postgresql_mock.create_database.assert_called_once_with( - DATABASE, user, plugins=["pgaudit"], client_relations=[relation] - ) - _update_endpoints.assert_called_once() - _update_unit_status.assert_called_once() - assert not isinstance(harness.model.unit.status, BlockedStatus) - - # Assert that the correct calls were made when the database name is not - # provided in both application and unit databags. - postgresql_mock.create_user.reset_mock() - postgresql_mock.create_database.reset_mock() - postgresql_mock.get_postgresql_version.reset_mock() - _update_endpoints.reset_mock() - _update_unit_status.reset_mock() - with harness.hooks_disabled(): - harness.update_relation_data( - rel_id, - "application", - {"database": ""}, - ) - harness.update_relation_data( - rel_id, - "application/0", - {"database": DATABASE}, - ) - assert harness.charm.legacy_db_relation.set_up_relation(relation) - postgresql_mock.create_user.assert_called_once_with(user, "test-password", False) - postgresql_mock.create_database.assert_called_once_with( - DATABASE, user, plugins=["pgaudit"], client_relations=[relation] - ) - _update_endpoints.assert_called_once() - _update_unit_status.assert_called_once() - assert not isinstance(harness.model.unit.status, BlockedStatus) - - # Assert that the correct calls were made when the database name is not provided. - postgresql_mock.create_user.reset_mock() - postgresql_mock.create_database.reset_mock() - postgresql_mock.get_postgresql_version.reset_mock() - _update_endpoints.reset_mock() - _update_unit_status.reset_mock() - with harness.hooks_disabled(): - harness.update_relation_data( - rel_id, - "application/0", - {"database": ""}, - ) - assert harness.charm.legacy_db_relation.set_up_relation(relation) - postgresql_mock.create_user.assert_called_once_with(user, "test-password", False) - postgresql_mock.create_database.assert_called_once_with( - "test_database", user, plugins=["pgaudit"], client_relations=[relation] - ) - _update_endpoints.assert_called_once() - _update_unit_status.assert_called_once() - assert not isinstance(harness.model.unit.status, BlockedStatus) - - # BlockedStatus due to a PostgreSQLCreateUserError. - postgresql_mock.create_database.reset_mock() - postgresql_mock.get_postgresql_version.reset_mock() - _update_endpoints.reset_mock() - _update_unit_status.reset_mock() - assert not harness.charm.legacy_db_relation.set_up_relation(relation) - postgresql_mock.create_database.assert_not_called() - _update_endpoints.assert_not_called() - _update_unit_status.assert_not_called() - assert isinstance(harness.model.unit.status, BlockedStatus) - - # BlockedStatus due to a PostgreSQLCreateDatabaseError. - harness.charm.unit.status = ActiveStatus() - assert not harness.charm.legacy_db_relation.set_up_relation(relation) - _update_endpoints.assert_not_called() - _update_unit_status.assert_not_called() - assert isinstance(harness.model.unit.status, BlockedStatus) - - -def test_update_unit_status(harness): - with ( - patch( - "relations.db.DbProvides._check_for_blocking_relations" - ) as _check_for_blocking_relations, - patch( - "charm.PostgresqlOperatorCharm.is_blocked", new_callable=PropertyMock - ) as _is_blocked, - ): - # Test when the charm is not blocked. - rel_id = harness.model.get_relation(RELATION_NAME).id - relation = harness.model.get_relation(RELATION_NAME, rel_id) - _is_blocked.return_value = False - harness.charm.legacy_db_relation._update_unit_status(relation) - _check_for_blocking_relations.assert_not_called() - assert not isinstance(harness.charm.unit.status, ActiveStatus) - - # Test when the charm is blocked but not due to extensions request. - _is_blocked.return_value = True - harness.charm.unit.status = BlockedStatus("fake message") - harness.charm.legacy_db_relation._update_unit_status(relation) - _check_for_blocking_relations.assert_not_called() - assert not isinstance(harness.charm.unit.status, ActiveStatus) - - # Test when there are relations causing the blocked status. - harness.charm.unit.status = BlockedStatus( - "extensions requested through relation, enable them through config options" - ) - _check_for_blocking_relations.return_value = True - harness.charm.legacy_db_relation._update_unit_status(relation) - _check_for_blocking_relations.assert_called_once_with(relation.id) - assert not isinstance(harness.charm.unit.status, ActiveStatus) - - # Test when there are no relations causing the blocked status anymore. - _check_for_blocking_relations.reset_mock() - _check_for_blocking_relations.return_value = False - harness.charm.legacy_db_relation._update_unit_status(relation) - _check_for_blocking_relations.assert_called_once_with(relation.id) - assert isinstance(harness.charm.unit.status, ActiveStatus) - - -def test_on_relation_broken_extensions_unblock(harness): - with ( - patch.object(PostgresqlOperatorCharm, "postgresql", Mock()) as postgresql_mock, - patch( - "charm.PostgresqlOperatorCharm.primary_endpoint", - new_callable=PropertyMock, - ) as _primary_endpoint, - patch("charm.PostgresqlOperatorCharm.is_blocked", new_callable=PropertyMock) as is_blocked, - patch("charm.Patroni.member_started", new_callable=PropertyMock) as _member_started, - patch("charm.DbProvides._on_relation_departed") as _on_relation_departed, - ): - # Set some side effects to test multiple situations. - rel_id = harness.model.get_relation(RELATION_NAME).id - is_blocked.return_value = True - _member_started.return_value = True - _primary_endpoint.return_value = {"1.1.1.1"} - postgresql_mock.delete_user = PropertyMock(return_value=None) - harness.model.unit.status = BlockedStatus( - "extensions requested through relation, enable them through config options" - ) - with harness.hooks_disabled(): - harness.update_relation_data( - rel_id, - "application", - {"database": DATABASE, "extensions": "test"}, - ) - - # Break the relation that blocked the charm. - harness.remove_relation(rel_id) - assert isinstance(harness.model.unit.status, ActiveStatus) - - -def test_on_relation_broken_extensions_keep_block(harness): - with ( - patch.object(PostgresqlOperatorCharm, "postgresql", Mock()) as postgresql_mock, - patch("charm.DbProvides._on_relation_departed") as _on_relation_departed, - patch("charm.Patroni.member_started", new_callable=PropertyMock) as _member_started, - patch( - "charm.PostgresqlOperatorCharm.primary_endpoint", - new_callable=PropertyMock, - ) as _primary_endpoint, - patch("charm.PostgresqlOperatorCharm.is_blocked", new_callable=PropertyMock) as is_blocked, - ): - # Set some side effects to test multiple situations. - is_blocked.return_value = True - _member_started.return_value = True - _primary_endpoint.return_value = {"1.1.1.1"} - postgresql_mock.delete_user = PropertyMock(return_value=None) - harness.model.unit.status = BlockedStatus( - "extensions requested through relation, enable them through config options" - ) - with harness.hooks_disabled(): - first_rel_id = harness.add_relation(RELATION_NAME, "application1") - harness.update_relation_data( - first_rel_id, - "application1", - {"database": DATABASE, "extensions": "test"}, - ) - second_rel_id = harness.add_relation(RELATION_NAME, "application2") - harness.update_relation_data( - second_rel_id, - "application2", - {"database": DATABASE, "extensions": "test"}, - ) - - event = Mock() - event.relation.id = first_rel_id - # Break one of the relations that block the charm. - harness.charm.legacy_db_relation._on_relation_broken(event) - assert isinstance(harness.model.unit.status, BlockedStatus) - - -def test_update_endpoints_with_relation(harness): - with ( - patch.object(PostgresqlOperatorCharm, "postgresql", Mock()) as postgresql_mock, - patch("charm.Patroni.get_primary") as _get_primary, - patch( - "relations.db.logger", - ) as _logger, - patch( - "charm.PostgresqlOperatorCharm.members_ips", - new_callable=PropertyMock, - ) as _members_ips, - patch( - "charm.PostgresqlOperatorCharm.primary_endpoint", - new_callable=PropertyMock(return_value="1.1.1.1"), - ) as _primary_endpoint, - patch( - "charm.DbProvides._get_state", - side_effect="postgresql/0", - ) as _get_state, - ): - # Set some side effects to test multiple situations. - postgresql_mock.get_postgresql_version = PropertyMock( - side_effect=[ - POSTGRESQL_VERSION, - POSTGRESQL_VERSION, - PostgreSQLGetPostgreSQLVersionError, - ] - ) - - # Mock the members_ips list to simulate different scenarios - # (with and without a replica). - _members_ips.side_effect = [ - {"1.1.1.1", "2.2.2.2"}, - {"1.1.1.1", "2.2.2.2"}, - {"1.1.1.1"}, - {"1.1.1.1"}, - ] - - # Add two different relations. - rel_id = harness.add_relation(RELATION_NAME, "application") - another_rel_id = harness.add_relation(RELATION_NAME, "application") - - # Get the relation to be used in the subsequent update endpoints calls. - relation = harness.model.get_relation(RELATION_NAME, rel_id) - - # Set some data to be used and compared in the relations. - password = "test-password" - master = f"dbname={DATABASE} host=1.1.1.1 password={password} port={DATABASE_PORT} user=" - standbys = f"dbname={DATABASE} host=2.2.2.2 password={password} port={DATABASE_PORT} user=" - - # Set some required data before update_endpoints is called. - for rel in [rel_id, another_rel_id]: - user = f"relation-{rel}" - harness.charm.set_secret("app", user, password) - harness.charm.set_secret("app", f"{user}-database", DATABASE) - - # Test with both a primary and a replica. - # Update the endpoints with the event and check that it updated only - # the right relation databags (the app and unit databags from the event). - harness.charm.legacy_db_relation.update_endpoints(relation) - for rel in [rel_id, another_rel_id]: - # Set the expected username based on the relation id. - user = f"relation-{rel}" - - # Check that the unit relation databag contains (or not) the endpoints. - unit_relation_data = harness.get_relation_data(rel, harness.charm.unit.name) - if rel == rel_id: - assert ( - "master" in unit_relation_data - and master + user == unit_relation_data["master"] - ) - assert ( - "standbys" in unit_relation_data - and standbys + user == unit_relation_data["standbys"] - ) - else: - assert not ( - "master" in unit_relation_data - and master + user == unit_relation_data["master"] - ) - assert not ( - "standbys" in unit_relation_data - and standbys + user == unit_relation_data["standbys"] - ) - - # Also test with only a primary instance. - harness.charm.legacy_db_relation.update_endpoints(relation) - for rel in [rel_id, another_rel_id]: - # Set the expected username based on the relation id. - user = f"relation-{rel}" - - # Check that the unit relation databag contains the endpoints. - unit_relation_data = harness.get_relation_data(rel, harness.charm.unit.name) - if rel == rel_id: - assert ( - "master" in unit_relation_data - and master + user == unit_relation_data["master"] - ) - assert ( - "standbys" in unit_relation_data - and standbys + user == unit_relation_data["standbys"] - ) - else: - assert not ( - "master" in unit_relation_data - and master + user == unit_relation_data["master"] - ) - assert not ( - "standbys" in unit_relation_data - and standbys + user == unit_relation_data["standbys"] - ) - - # version is not updated due to a PostgreSQLGetPostgreSQLVersionError. - harness.charm.legacy_db_relation.update_endpoints() - _logger.exception.assert_called_once_with( - "Failed to retrieve the PostgreSQL version to initialise/update db relation" - ) - - -def test_update_endpoints_without_relation(harness): - with ( - patch.object(PostgresqlOperatorCharm, "postgresql", Mock()) as postgresql_mock, - patch("charm.Patroni.get_primary") as _get_primary, - patch( - "relations.db.logger", - ) as _logger, - patch( - "charm.PostgresqlOperatorCharm.members_ips", - new_callable=PropertyMock, - ) as _members_ips, - patch( - "charm.PostgresqlOperatorCharm.primary_endpoint", - new_callable=PropertyMock(return_value="1.1.1.1"), - ) as _primary_endpoint, - patch( - "charm.DbProvides._get_state", - side_effect="postgresql/0", - ) as _get_state, - ): - # Set some side effects to test multiple situations. - postgresql_mock.get_postgresql_version = PropertyMock( - side_effect=[ - POSTGRESQL_VERSION, - POSTGRESQL_VERSION, - PostgreSQLGetPostgreSQLVersionError, - ] - ) - _get_primary.return_value = harness.charm.unit.name - # Mock the members_ips list to simulate different scenarios - # (with and without a replica). - _members_ips.side_effect = [ - {"1.1.1.1", "2.2.2.2"}, - {"1.1.1.1", "2.2.2.2"}, - {"1.1.1.1"}, - {"1.1.1.1"}, - ] - - # Add two different relations. - rel_id = harness.add_relation(RELATION_NAME, "application") - another_rel_id = harness.add_relation(RELATION_NAME, "application") - - # Set some data to be used and compared in the relations. - password = "test-password" - master = f"dbname={DATABASE} host=1.1.1.1 password={password} port={DATABASE_PORT} user=" - standbys = f"dbname={DATABASE} host=2.2.2.2 password={password} port={DATABASE_PORT} user=" - - # Set some required data before update_endpoints is called. - for rel in [rel_id, another_rel_id]: - user = f"relation-{rel}" - harness.charm.set_secret("app", user, password) - harness.charm.set_secret("app", f"{user}-database", DATABASE) - - # Test with both a primary and a replica. - # Update the endpoints and check that all relations' databags are updated. - harness.charm.legacy_db_relation.update_endpoints() - for rel in [rel_id, another_rel_id]: - # Set the expected username based on the relation id. - user = f"relation-{rel}" - - # Check that the unit relation databag contains the endpoints. - unit_relation_data = harness.get_relation_data(rel, harness.charm.unit.name) - assert "master" in unit_relation_data and master + user == unit_relation_data["master"] - assert ( - "standbys" in unit_relation_data - and standbys + user == unit_relation_data["standbys"] - ) - - # Also test with only a primary instance. - harness.charm.legacy_db_relation.update_endpoints() - for rel in [rel_id, another_rel_id]: - # Set the expected username based on the relation id. - user = f"relation-{rel}" - - # Check that the unit relation databag contains the endpoints. - unit_relation_data = harness.get_relation_data(rel, harness.charm.unit.name) - assert "master" in unit_relation_data and master + user == unit_relation_data["master"] - assert ( - "standbys" in unit_relation_data - and standbys + user == unit_relation_data["standbys"] - ) - - # version is not updated due to a PostgreSQLGetPostgreSQLVersionError. - harness.charm.legacy_db_relation.update_endpoints() - _logger.exception.assert_called_once_with( - "Failed to retrieve the PostgreSQL version to initialise/update db relation" - ) - - -def test_get_allowed_units(harness): - # No allowed units from the current database application. - peer_rel_id = harness.model.get_relation(PEER).id - rel_id = harness.model.get_relation(RELATION_NAME).id - peer_relation = harness.model.get_relation(PEER, peer_rel_id) - assert harness.charm.legacy_db_relation._get_allowed_units(peer_relation) == "" - - # List of space separated allowed units from the other application. - harness.add_relation_unit(rel_id, "application/1") - db_relation = harness.model.get_relation(RELATION_NAME, rel_id) - assert ( - harness.charm.legacy_db_relation._get_allowed_units(db_relation) - == "application/0 application/1" - ) From 70db58a30f8bbfda6585f1cb66026f85c1b6da8f Mon Sep 17 00:00:00 2001 From: Dragomir Penev Date: Thu, 13 Mar 2025 14:43:55 +0200 Subject: [PATCH 66/74] Restore pydantic rule --- .github/renovate.json5 | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/.github/renovate.json5 b/.github/renovate.json5 index 3932a46a8d..cd60ef68a5 100644 --- a/.github/renovate.json5 +++ b/.github/renovate.json5 @@ -8,6 +8,12 @@ ], "baseBranches": ["main", "/^*\\/edge$/"], packageRules: [ + { + matchPackageNames: [ + 'pydantic', + ], + allowedVersions: '<2.0.0', + }, ], customManagers: [ ], From 7cddad1878d2858386d078438330f811f229ed28 Mon Sep 17 00:00:00 2001 From: Dragomir Penev Date: Thu, 13 Mar 2025 14:58:24 +0200 Subject: [PATCH 67/74] Remove legacy rels metadata --- metadata.yaml | 4 ---- 1 file changed, 4 deletions(-) diff --git a/metadata.yaml b/metadata.yaml index f44eb9e1f1..4ce46fe9c0 100644 --- a/metadata.yaml +++ b/metadata.yaml @@ -32,10 +32,6 @@ provides: optional: true database: interface: postgresql_client - db: - interface: pgsql - db-admin: - interface: pgsql cos-agent: interface: cos_agent limit: 1 From 70012ce0e1af8441325fe4e4744dc7d685845f28 Mon Sep 17 00:00:00 2001 From: Dragomir Penev Date: Thu, 13 Mar 2025 16:20:53 +0200 Subject: [PATCH 68/74] Remove manual dispatch --- .github/workflows/release.yaml | 2 -- 1 file changed, 2 deletions(-) diff --git a/.github/workflows/release.yaml b/.github/workflows/release.yaml index 5907046788..c194536590 100644 --- a/.github/workflows/release.yaml +++ b/.github/workflows/release.yaml @@ -14,8 +14,6 @@ on: - '.github/workflows/ci.yaml' - '.github/workflows/lib-check.yaml' - '.github/workflows/sync_docs.yaml' - # for testing purposes: - workflow_dispatch: jobs: ci-tests: From 04e89dea0d00eeea8d1e93a3daebafa97ef12abf Mon Sep 17 00:00:00 2001 From: Dragomir Penev Date: Thu, 13 Mar 2025 20:34:46 +0200 Subject: [PATCH 69/74] Create schema to test admin user privileges --- src/charm.py | 2 -- tests/integration/new_relations/test_new_relations_1.py | 2 +- 2 files changed, 1 insertion(+), 3 deletions(-) diff --git a/src/charm.py b/src/charm.py index 891cf5bbfc..a814a710de 100755 --- a/src/charm.py +++ b/src/charm.py @@ -303,7 +303,6 @@ def get_secret(self, scope: Scopes, key: str) -> str | None: if not (peers := self.model.get_relation(PEER)): return None secret_key = self._translate_field_to_secret_key(key) - return self.peer_relation_data(scope).get_secret(peers.id, secret_key) def set_secret(self, scope: Scopes, key: str, value: str | None) -> str | None: @@ -327,7 +326,6 @@ def remove_secret(self, scope: Scopes, key: str) -> None: if not (peers := self.model.get_relation(PEER)): return None secret_key = self._translate_field_to_secret_key(key) - self.peer_relation_data(scope).delete_relation_data(peers.id, [secret_key]) @property diff --git a/tests/integration/new_relations/test_new_relations_1.py b/tests/integration/new_relations/test_new_relations_1.py index 4566286df6..f8d6c0d611 100644 --- a/tests/integration/new_relations/test_new_relations_1.py +++ b/tests/integration/new_relations/test_new_relations_1.py @@ -519,7 +519,7 @@ async def test_admin_role(ops_test: OpsTest): f"test_{''.join(secrets.choice(string.ascii_lowercase) for _ in range(10))}" ) should_fail = database == DATABASE_DEFAULT_NAME - cursor.execute(f"CREATE TABLE {random_name}(data TEXT);") + cursor.execute(f"CREATE SCHEMA test; CREATE TABLE test.{random_name}(data TEXT);") if should_fail: assert False, ( f"failed to run a statement in the following database: {database}" From 74513d2fe66b6f7f70ab3662760cfca5ee4406ae Mon Sep 17 00:00:00 2001 From: Dragomir Penev Date: Fri, 14 Mar 2025 01:12:09 +0200 Subject: [PATCH 70/74] Cleanup markers --- pyproject.toml | 2 +- tests/integration/ha_tests/test_async_replication.py | 12 +----------- tests/integration/ha_tests/test_scaling.py | 6 ------ .../integration/ha_tests/test_scaling_three_units.py | 3 --- .../ha_tests/test_scaling_three_units_async.py | 3 --- tests/integration/markers.py | 1 - 6 files changed, 2 insertions(+), 25 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index 82e0129586..3dc81824de 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -86,7 +86,7 @@ exclude_lines = [ minversion = "6.0" log_cli_level = "INFO" asyncio_mode = "auto" -markers = ["juju2", "juju3", "juju_secrets"] +markers = ["juju3", "juju_secrets"] # Formatting tools configuration [tool.black] diff --git a/tests/integration/ha_tests/test_async_replication.py b/tests/integration/ha_tests/test_async_replication.py index 7ac7ec4dbe..b8ed2b30c8 100644 --- a/tests/integration/ha_tests/test_async_replication.py +++ b/tests/integration/ha_tests/test_async_replication.py @@ -12,7 +12,7 @@ from pytest_operator.plugin import OpsTest from tenacity import Retrying, stop_after_delay, wait_fixed -from .. import architecture, markers +from .. import architecture from ..helpers import ( APPLICATION_NAME, DATABASE_APP_NAME, @@ -99,7 +99,6 @@ async def second_model_continuous_writes(second_model) -> None: assert action.results["result"] == "True", "Unable to clear up continuous_writes table" -@markers.juju3 @pytest.mark.abort_on_fail async def test_deploy_async_replication_setup( ops_test: OpsTest, first_model: Model, second_model: Model, charm @@ -143,7 +142,6 @@ async def test_deploy_async_replication_setup( ) -@markers.juju3 @pytest.mark.abort_on_fail async def test_async_replication( ops_test: OpsTest, @@ -220,7 +218,6 @@ async def test_async_replication( await check_writes(ops_test, extra_model=second_model) -@markers.juju3 @pytest.mark.abort_on_fail async def test_get_data_integrator_credentials( ops_test: OpsTest, @@ -232,7 +229,6 @@ async def test_get_data_integrator_credentials( data_integrator_credentials = result.results -@markers.juju3 @pytest.mark.abort_on_fail async def test_switchover( ops_test: OpsTest, @@ -286,7 +282,6 @@ async def test_switchover( await are_writes_increasing(ops_test, extra_model=second_model) -@markers.juju3 @pytest.mark.abort_on_fail async def test_data_integrator_creds_keep_on_working( ops_test: OpsTest, @@ -308,7 +303,6 @@ async def test_data_integrator_creds_keep_on_working( connection.close() -@markers.juju3 @pytest.mark.abort_on_fail async def test_promote_standby( ops_test: OpsTest, @@ -385,7 +379,6 @@ async def test_promote_standby( await are_writes_increasing(ops_test) -@markers.juju3 @pytest.mark.abort_on_fail async def test_reestablish_relation( ops_test: OpsTest, first_model: Model, second_model: Model, continuous_writes @@ -442,7 +435,6 @@ async def test_reestablish_relation( await check_writes(ops_test, extra_model=second_model) -@markers.juju3 @pytest.mark.abort_on_fail async def test_async_replication_failover_in_main_cluster( ops_test: OpsTest, first_model: Model, second_model: Model, continuous_writes @@ -487,7 +479,6 @@ async def test_async_replication_failover_in_main_cluster( await check_writes(ops_test, extra_model=second_model) -@markers.juju3 @pytest.mark.abort_on_fail async def test_async_replication_failover_in_secondary_cluster( ops_test: OpsTest, first_model: Model, second_model: Model, continuous_writes @@ -523,7 +514,6 @@ async def test_async_replication_failover_in_secondary_cluster( await check_writes(ops_test, extra_model=second_model) -@markers.juju3 @pytest.mark.abort_on_fail async def test_scaling( ops_test: OpsTest, first_model: Model, second_model: Model, continuous_writes diff --git a/tests/integration/ha_tests/test_scaling.py b/tests/integration/ha_tests/test_scaling.py index 8c85dd931e..0f48af1ffb 100644 --- a/tests/integration/ha_tests/test_scaling.py +++ b/tests/integration/ha_tests/test_scaling.py @@ -7,7 +7,6 @@ import pytest from pytest_operator.plugin import OpsTest -from .. import markers from ..helpers import ( CHARM_BASE, DATABASE_APP_NAME, @@ -27,7 +26,6 @@ charm = None -@markers.juju3 @pytest.mark.abort_on_fail async def test_build_and_deploy(ops_test: OpsTest, charm) -> None: """Build and deploy two PostgreSQL clusters.""" @@ -53,7 +51,6 @@ async def test_build_and_deploy(ops_test: OpsTest, charm) -> None: await ops_test.model.wait_for_idle(status="active", timeout=1500) -@markers.juju3 @pytest.mark.abort_on_fail async def test_removing_stereo_primary(ops_test: OpsTest, continuous_writes) -> None: # Start an application that continuously writes data to the database. @@ -102,7 +99,6 @@ async def test_removing_stereo_primary(ops_test: OpsTest, continuous_writes) -> await check_writes(ops_test) -@markers.juju3 @pytest.mark.abort_on_fail async def test_removing_stereo_sync_standby(ops_test: OpsTest, continuous_writes) -> None: # Start an application that continuously writes data to the database. @@ -136,14 +132,12 @@ async def test_removing_stereo_sync_standby(ops_test: OpsTest, continuous_writes await check_writes(ops_test) -@markers.juju3 @pytest.mark.abort_on_fail async def test_scale_to_five_units(ops_test: OpsTest) -> None: await ops_test.model.applications[DATABASE_APP_NAME].add_unit(count=3) await ops_test.model.wait_for_idle(status="active", timeout=1500) -@markers.juju3 @pytest.mark.abort_on_fail async def test_removing_raft_majority(ops_test: OpsTest, continuous_writes) -> None: # Start an application that continuously writes data to the database. diff --git a/tests/integration/ha_tests/test_scaling_three_units.py b/tests/integration/ha_tests/test_scaling_three_units.py index 7a44d0f5c2..a5b95bb9d1 100644 --- a/tests/integration/ha_tests/test_scaling_three_units.py +++ b/tests/integration/ha_tests/test_scaling_three_units.py @@ -8,7 +8,6 @@ import pytest from pytest_operator.plugin import OpsTest -from .. import markers from ..helpers import ( CHARM_BASE, DATABASE_APP_NAME, @@ -29,7 +28,6 @@ charm = None -@markers.juju3 @pytest.mark.abort_on_fail async def test_build_and_deploy(ops_test: OpsTest, charm) -> None: """Build and deploy two PostgreSQL clusters.""" @@ -55,7 +53,6 @@ async def test_build_and_deploy(ops_test: OpsTest, charm) -> None: await ops_test.model.wait_for_idle(status="active", timeout=1500) -@markers.juju3 @pytest.mark.parametrize( "roles", [ diff --git a/tests/integration/ha_tests/test_scaling_three_units_async.py b/tests/integration/ha_tests/test_scaling_three_units_async.py index 41fc03451d..96cecaa14f 100644 --- a/tests/integration/ha_tests/test_scaling_three_units_async.py +++ b/tests/integration/ha_tests/test_scaling_three_units_async.py @@ -8,7 +8,6 @@ import pytest from pytest_operator.plugin import OpsTest -from .. import markers from ..helpers import ( CHARM_BASE, DATABASE_APP_NAME, @@ -29,7 +28,6 @@ charm = None -@markers.juju3 @pytest.mark.abort_on_fail async def test_build_and_deploy(ops_test: OpsTest, charm) -> None: """Build and deploy two PostgreSQL clusters.""" @@ -55,7 +53,6 @@ async def test_build_and_deploy(ops_test: OpsTest, charm) -> None: await ops_test.model.wait_for_idle(status="active", timeout=1500) -@markers.juju3 @pytest.mark.parametrize( "roles", [ diff --git a/tests/integration/markers.py b/tests/integration/markers.py index 2cfeab1c4f..2f6cdd315c 100644 --- a/tests/integration/markers.py +++ b/tests/integration/markers.py @@ -7,7 +7,6 @@ from . import architecture from .juju_ import juju_major_version -juju2 = pytest.mark.skipif(juju_major_version != 2, reason="Requires juju 2") juju3 = pytest.mark.skipif(juju_major_version != 3, reason="Requires juju 3") juju_secrets = pytest.mark.skipif(juju_major_version < 3, reason="Requires juju secrets") amd64_only = pytest.mark.skipif( From 082285f9fafd3a279ccd45361f01c56ddb85722c Mon Sep 17 00:00:00 2001 From: Dragomir Penev Date: Fri, 14 Mar 2025 02:02:49 +0200 Subject: [PATCH 71/74] Workaround for cluster restore test --- tests/integration/ha_tests/helpers.py | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/tests/integration/ha_tests/helpers.py b/tests/integration/ha_tests/helpers.py index 951659ee45..d9ea25543d 100644 --- a/tests/integration/ha_tests/helpers.py +++ b/tests/integration/ha_tests/helpers.py @@ -954,21 +954,21 @@ async def add_unit_with_storage(ops_test, app, storage): Note: this function exists as a temporary solution until this issue is resolved: https://github.com/juju/python-libjuju/issues/695 """ - expected_units = len(ops_test.model.applications[app].units) + 1 - prev_units = [unit.name for unit in ops_test.model.applications[app].units] + original_units = {unit.name for unit in ops_test.model.applications[app].units} model_name = ops_test.model.info.name add_unit_cmd = f"add-unit {app} --model={model_name} --attach-storage={storage}".split() return_code, _, _ = await ops_test.juju(*add_unit_cmd) assert return_code == 0, "Failed to add unit with storage" async with ops_test.fast_forward(): await ops_test.model.wait_for_idle(apps=[app], status="active", timeout=2000) - assert len(ops_test.model.applications[app].units) == expected_units, ( - "New unit not added to model" - ) + + # When removing all units sometimes the last unit remain in the list + current_units = {unit.name for unit in ops_test.model.applications[app].units} + original_units.intersection_update(current_units) + assert original_units.issubset(current_units), "New unit not added to model" # verify storage attached - curr_units = [unit.name for unit in ops_test.model.applications[app].units] - new_unit = next(unit for unit in set(curr_units) - set(prev_units)) + new_unit = (current_units - original_units).pop() assert storage_id(ops_test, new_unit) == storage, "unit added with incorrect storage" # return a reference to newly added unit From fa1cf706e63e8ca3993af9cab48c6acf0d5f6043 Mon Sep 17 00:00:00 2001 From: Dragomir Penev Date: Mon, 17 Mar 2025 22:02:27 +0200 Subject: [PATCH 72/74] Promote permadiff --- .github/workflows/promote.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/promote.yaml b/.github/workflows/promote.yaml index 7f0de3c67e..6b2832b4ec 100644 --- a/.github/workflows/promote.yaml +++ b/.github/workflows/promote.yaml @@ -27,7 +27,7 @@ jobs: name: Promote charm uses: canonical/data-platform-workflows/.github/workflows/_promote_charm.yaml@v30.2.0 with: - track: '14' + track: '16' from-risk: ${{ inputs.from-risk }} to-risk: ${{ inputs.to-risk }} secrets: From 494e695940ee28d34500b7d87bce0b70b4610c25 Mon Sep 17 00:00:00 2001 From: Dragomir Penev <6687393+dragomirp@users.noreply.github.com> Date: Wed, 19 Mar 2025 16:16:28 +0200 Subject: [PATCH 73/74] Apply suggestions from code review Co-authored-by: Marcelo Henrique Neppel --- tests/integration/test_backups_pitr_aws.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/integration/test_backups_pitr_aws.py b/tests/integration/test_backups_pitr_aws.py index 3938912d06..5a671b9623 100644 --- a/tests/integration/test_backups_pitr_aws.py +++ b/tests/integration/test_backups_pitr_aws.py @@ -320,10 +320,10 @@ async def pitr_backup_operations( @pytest.mark.abort_on_fail async def test_pitr_backup_aws( - ops_test: OpsTest, gcp_cloud_configs: tuple[dict, dict], charm + ops_test: OpsTest, aws_cloud_configs: tuple[dict, dict], charm ) -> None: """Build, deploy two units of PostgreSQL and do backup in AWS. Then, write new data into DB, switch WAL file and test point-in-time-recovery restore action.""" - config, credentials = gcp_cloud_configs + config, credentials = aws_cloud_configs await pitr_backup_operations( ops_test, From 8fff98fc9acd3ddc7351c45a5af9b95aa6c416c8 Mon Sep 17 00:00:00 2001 From: Dragomir Penev Date: Wed, 19 Mar 2025 16:17:45 +0200 Subject: [PATCH 74/74] Bump snaps --- src/constants.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/constants.py b/src/constants.py index e0ffb6e111..9eb1ac152c 100644 --- a/src/constants.py +++ b/src/constants.py @@ -32,7 +32,7 @@ SNAP_PACKAGES = [ ( POSTGRESQL_SNAP_NAME, - {"revision": {"aarch64": "158", "x86_64": "159"}}, + {"revision": {"aarch64": "160", "x86_64": "161"}}, ) ]