diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index 844244d4..27793fbd 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -116,7 +116,9 @@ jobs: CI_PACKED_CHARMS: ${{ needs.build.outputs.charms }} LIBJUJU_VERSION_SPECIFIER: "==${{ matrix.juju-version.libjuju-version }}" - name: Print debug-log - run: juju switch testing; juju debug-log --replay --no-tail + run: | + lines=`juju switch testing; juju debug-log --replay --no-tail | grep SECRET` + echo "lines = ${lines}" - name: Dump logs uses: canonical/charm-logdump-action@main if: failure() diff --git a/lib/charms/data_platform_libs/v0/data_interfaces.py b/lib/charms/data_platform_libs/v0/data_interfaces.py index 5d1691d9..2ec01f09 100644 --- a/lib/charms/data_platform_libs/v0/data_interfaces.py +++ b/lib/charms/data_platform_libs/v0/data_interfaces.py @@ -501,8 +501,13 @@ def add_secret(self, content: Dict[str, str], relation: Relation) -> Secret: "Secret is already defined with uri %s", self._secret_uri ) + logger.info(f"[SECRET] Adding secret {self.label} {content}") secret = self.charm.app.add_secret(content, label=self.label) secret.grant(relation) + if self.meta: + logger.info( + f"[SECRET] Added secret {self.meta.id}, {self.meta.label}, {self.meta.__dict__}" + ) self._secret_uri = secret.id self._secret_meta = secret return self._secret_meta @@ -514,19 +519,44 @@ def meta(self) -> Optional[Secret]: if not (self._secret_uri or self.label): return try: + logger.info(f"[SECRET] Getting secret by label {self.label} ") self._secret_meta = self.charm.model.get_secret(label=self.label) + logger.info( + f"[SECRET] Received secret {self._secret_meta.id}, " + f"{self.label}, {self._secret_meta.get_info().revision}, {self._secret_meta.__dict__}" + ) except SecretNotFoundError: + logger.info(f"[SECRET] Couldn't get secret by label {self.label} {self.__dict__}") if self._secret_uri: self._secret_meta = self.charm.model.get_secret( id=self._secret_uri, label=self.label ) + logger.info( + f"[SECRET] Received secret {self._secret_meta.id}, " + f"{self.label}, {self._secret_meta.__dict__}" + ) return self._secret_meta def get_content(self) -> Dict[str, str]: """Getting cached secret content.""" if not self._secret_content: if self.meta: + logger.info("[SECRET] Getting secret contents without 'refresh=True'") self._secret_content = self.meta.get_content() + logger.info( + f"[SECRET] Got secret contents " + f"{self.meta.id}, {self.meta.label}, {self.meta.__dict__}" + ) + try: + logger.info("[SECRET] Peeking content contents") + self._secret_content = self.meta.peek_content() + logger.info( + f"[SECRET] Peeked secret contents " + f"{self.meta.id}, {self.meta.label}, {self.meta.__dict__}" + ) + except Exception: + pass + return self._secret_content def set_content(self, content: Dict[str, str]) -> None: @@ -536,13 +566,26 @@ def set_content(self, content: Dict[str, str]) -> None: if content: self.meta.set_content(content) + logger.info( + f"[SECRET] Setting secret" + f"{self.meta.id}, {self.meta.label}, {self.meta.__dict__}" + f"with content {content}" + ) self._secret_content = content else: self.meta.remove_all_revisions() + logger.info( + f"[SECRET] Deleting secret" + f"{self.meta.id}, {self.meta.label}, {self.meta.__dict__}" + ) def get_info(self) -> Optional[SecretInfo]: """Wrapper function to apply the corresponding call on the Secret object within CachedSecret if any.""" if self.meta: + logger.info( + f"[SECRET] Getting info about secret" + f"{self.meta.id}, {self.meta.label}, {self.meta.__dict__}" + ) return self.meta.get_info() diff --git a/tests/integration/test_charm.py b/tests/integration/test_charm.py index 6ba92005..b2c918f1 100644 --- a/tests/integration/test_charm.py +++ b/tests/integration/test_charm.py @@ -17,8 +17,6 @@ get_application_relation_data, get_juju_secret, get_leader_id, - get_non_leader_id, - list_juju_secrets, ) logger = logging.getLogger(__name__) @@ -431,14 +429,7 @@ async def test_provider_get_set_delete_fields(field, value, ops_test: OpsTest): "field,value,relation_field", [ ("new_field", "blah", "new_field"), - pytest.param( - "tls", - "True", - "secret-tls", - marks=pytest.mark.xfail( - reason="https://github.com/canonical/data-platform-libs/issues/108" - ), - ), + ("tls", "True", "secret-tls"), ], ) @pytest.mark.usefixtures("only_with_juju_secrets") @@ -461,6 +452,7 @@ async def test_provider_get_set_delete_fields_secrets( assert await get_application_relation_data( ops_test, APPLICATION_APP_NAME, SECOND_DATABASE_RELATION_NAME, relation_field ) + sleep(10) # Check all application units can read remote relation data for unit in ops_test.model.applications[APPLICATION_APP_NAME].units: @@ -486,13 +478,14 @@ async def test_provider_get_set_delete_fields_secrets( await action.wait() assert action.results.get("value") == value - # Delete normal field + # Delete field action = await ops_test.model.units.get(leader_name).run_action( "delete-relation-field", **{"relation_id": pytest.second_database_relation.id, "field": field}, ) await action.wait() + sleep(2) assert ( await get_application_relation_data( ops_test, APPLICATION_APP_NAME, SECOND_DATABASE_RELATION_NAME, relation_field @@ -527,276 +520,278 @@ async def test_provider_get_set_delete_fields_secrets( assert action.results["return-code"] == 0 -@pytest.mark.usefixtures("only_with_juju_secrets") -async def test_provider_deleted_secret_is_removed(ops_test: OpsTest): - """The 'tls' field, that was removed in the previous test has it's secret removed.""" - # Get TLS secret pointer - secret_uri = await get_application_relation_data( - ops_test, APPLICATION_APP_NAME, SECOND_DATABASE_RELATION_NAME, f"{SECRET_REF_PREFIX}tls" - ) - - # The 7 lines below can be removed once the test above is fully passing - leader_id = await get_leader_id(ops_test, DATABASE_APP_NAME) - leader_name = f"{DATABASE_APP_NAME}/{leader_id}" - action = await ops_test.model.units.get(leader_name).run_action( - "delete-relation-field", - **{"relation_id": pytest.second_database_relation.id, "field": "tls"}, - ) - await action.wait() - - assert ( - await get_application_relation_data( - ops_test, APPLICATION_APP_NAME, SECOND_DATABASE_RELATION_NAME, "secret-tls" - ) - is None - ) - - secrets = await list_juju_secrets(ops_test) - secret_xid = secret_uri.split("/")[-1] - assert secret_xid not in secrets - - -async def test_requires_get_set_delete_fields(ops_test: OpsTest): - # Add normal field - leader_id = await get_leader_id(ops_test, APPLICATION_APP_NAME) - leader_name = f"{APPLICATION_APP_NAME}/{leader_id}" - - action = await ops_test.model.units.get(leader_name).run_action( - "set-relation-field", - **{ - "relation_id": pytest.second_database_relation.id, - "field": "new_field", - "value": "blah", - }, - ) - await action.wait() - - assert ( - await get_application_relation_data( - ops_test, - DATABASE_APP_NAME, - SECOND_DATABASE_RELATION_NAME, - "new_field", - related_endpoint="second-database", - ) - == "blah" - ) - - # Check all application units can read remote relation data - for unit in ops_test.model.applications[DATABASE_APP_NAME].units: - action = await unit.run_action( - "get-relation-field", - **{ - "relation_id": pytest.second_database_relation.id, - "field": "new_field", - }, - ) - await action.wait() - assert action.results.get("value") == "blah" - - # Check if database can retrieve self-side relation data - action = await ops_test.model.units.get(leader_name).run_action( - "get-relation-self-side-field", - **{ - "relation_id": pytest.second_database_relation.id, - "field": "new_field", - "value": "blah", - }, - ) - await action.wait() - assert action.results.get("value") == "blah" - - # Delete normal field - action = await ops_test.model.units.get(leader_name).run_action( - "delete-relation-field", - **{"relation_id": pytest.second_database_relation.id, "field": "new_field"}, - ) - await action.wait() - - assert ( - await get_application_relation_data( - ops_test, - DATABASE_APP_NAME, - SECOND_DATABASE_RELATION_NAME, - "new_field", - related_endpoint="second-database", - ) - is None - ) - - -async def test_provider_set_delete_fields_leader_only(ops_test: OpsTest): - leader_id = await get_leader_id(ops_test, DATABASE_APP_NAME) - leader_name = f"{DATABASE_APP_NAME}/{leader_id}" - action = await ops_test.model.units.get(leader_name).run_action( - "set-relation-field", - **{ - "relation_id": pytest.second_database_relation.id, - "field": "new_field", - "value": "blah", - }, - ) - await action.wait() - - unit_id = await get_non_leader_id(ops_test, DATABASE_APP_NAME) - unit_name = f"{DATABASE_APP_NAME}/{unit_id}" - action = await ops_test.model.units.get(unit_name).run_action( - "set-relation-field", - **{ - "relation_id": pytest.second_database_relation.id, - "field": "new_field2", - "value": "blah2", - }, - ) - await action.wait() - - assert ( - await get_application_relation_data( - ops_test, APPLICATION_APP_NAME, SECOND_DATABASE_RELATION_NAME, "new_field2" - ) - is None - ) - - action = await ops_test.model.units.get(unit_name).run_action( - "delete-relation-field", - **{"relation_id": pytest.second_database_relation.id, "field": "new_field"}, - ) - await action.wait() - - assert ( - await get_application_relation_data( - ops_test, APPLICATION_APP_NAME, SECOND_DATABASE_RELATION_NAME, "new_field" - ) - == "blah" - ) - - -async def test_requires_set_delete_fields(ops_test: OpsTest): - # Add field - leader_id = await get_leader_id(ops_test, APPLICATION_APP_NAME) - leader_name = f"{APPLICATION_APP_NAME}/{leader_id}" - action = await ops_test.model.units.get(leader_name).run_action( - "set-relation-field", - **{ - "relation_id": pytest.second_database_relation.id, - "field": "new_field_req", - "value": "blah-req", - }, - ) - await action.wait() - - assert ( - await get_application_relation_data( - ops_test, - DATABASE_APP_NAME, - DATABASE_APP_NAME, - "new_field_req", - related_endpoint=SECOND_DATABASE_RELATION_NAME, - ) - == "blah-req" - ) - - # Delete field - action = await ops_test.model.units.get(leader_name).run_action( - "delete-relation-field", - **{"relation_id": pytest.second_database_relation.id, "field": "new_field_req"}, - ) - await action.wait() - - assert ( - await get_application_relation_data( - ops_test, - DATABASE_APP_NAME, - DATABASE_APP_NAME, - "new_field_req", - related_endpoint=SECOND_DATABASE_RELATION_NAME, - ) - is None - ) - - -async def test_requires_set_delete_fields_leader_only(ops_test: OpsTest): - leader_id = await get_leader_id(ops_test, APPLICATION_APP_NAME) - leader_name = f"{APPLICATION_APP_NAME}/{leader_id}" - action = await ops_test.model.units.get(leader_name).run_action( - "set-relation-field", - **{ - "relation_id": pytest.second_database_relation.id, - "field": "new_field-req", - "value": "blah-req", - }, - ) - await action.wait() - - unit_id = await get_non_leader_id(ops_test, APPLICATION_APP_NAME) - unit_name = f"{APPLICATION_APP_NAME}/{unit_id}" - action = await ops_test.model.units.get(unit_name).run_action( - "set-relation-field", - **{ - "relation_id": pytest.second_database_relation.id, - "field": "new_field2-req", - "value": "blah2-req", - }, - ) - await action.wait() - - assert ( - await get_application_relation_data( - ops_test, - DATABASE_APP_NAME, - DATABASE_APP_NAME, - "new_field2-req", - related_endpoint=SECOND_DATABASE_RELATION_NAME, - ) - is None - ) - - action = await ops_test.model.units.get(unit_name).run_action( - "delete-relation-field", - **{"relation_id": pytest.second_database_relation.id, "field": "new_field-req"}, - ) - await action.wait() - - assert ( - await get_application_relation_data( - ops_test, - DATABASE_APP_NAME, - DATABASE_APP_NAME, - "new_field-req", - related_endpoint=SECOND_DATABASE_RELATION_NAME, - ) - == "blah-req" - ) - - -async def test_scaling_requires_can_access_shared_secrest(ops_test): - """When scaling up the application, new units should have access to relation secrets.""" - await ops_test.model.applications[APPLICATION_APP_NAME].scale(3) - - await ops_test.model.wait_for_idle( - apps=[APPLICATION_APP_NAME], status="active", timeout=(15 * 60), wait_for_exact_units=3 - ) - - old_unit_name = f"{APPLICATION_APP_NAME}/1" - new_unit_name = f"{APPLICATION_APP_NAME}/2" - - action = await ops_test.model.units.get(old_unit_name).run_action( - "get-relation-field", - **{ - "relation_id": pytest.second_database_relation.id, - "field": "password", - }, - ) - await action.wait() - orig_password = action.results.get("value") - - action = await ops_test.model.units.get(new_unit_name).run_action( - "get-relation-field", - **{ - "relation_id": pytest.second_database_relation.id, - "field": "password", - }, - ) - await action.wait() - new_password = action.results.get("value") - assert new_password == orig_password +# +# +# @pytest.mark.usefixtures("only_with_juju_secrets") +# async def test_provider_deleted_secret_is_removed(ops_test: OpsTest): +# """The 'tls' field, that was removed in the previous test has it's secret removed.""" +# # Get TLS secret pointer +# secret_uri = await get_application_relation_data( +# ops_test, APPLICATION_APP_NAME, SECOND_DATABASE_RELATION_NAME, f"{SECRET_REF_PREFIX}tls" +# ) +# +# # The 7 lines below can be removed once the test above is fully passing +# leader_id = await get_leader_id(ops_test, DATABASE_APP_NAME) +# leader_name = f"{DATABASE_APP_NAME}/{leader_id}" +# action = await ops_test.model.units.get(leader_name).run_action( +# "delete-relation-field", +# **{"relation_id": pytest.second_database_relation.id, "field": "tls"}, +# ) +# await action.wait() +# +# assert ( +# await get_application_relation_data( +# ops_test, APPLICATION_APP_NAME, SECOND_DATABASE_RELATION_NAME, "secret-tls" +# ) +# is None +# ) +# +# secrets = await list_juju_secrets(ops_test) +# secret_xid = secret_uri.split("/")[-1] +# assert secret_xid not in secrets +# +# +# async def test_requires_get_set_delete_fields(ops_test: OpsTest): +# # Add normal field +# leader_id = await get_leader_id(ops_test, APPLICATION_APP_NAME) +# leader_name = f"{APPLICATION_APP_NAME}/{leader_id}" +# +# action = await ops_test.model.units.get(leader_name).run_action( +# "set-relation-field", +# **{ +# "relation_id": pytest.second_database_relation.id, +# "field": "new_field", +# "value": "blah", +# }, +# ) +# await action.wait() +# +# assert ( +# await get_application_relation_data( +# ops_test, +# DATABASE_APP_NAME, +# SECOND_DATABASE_RELATION_NAME, +# "new_field", +# related_endpoint="second-database", +# ) +# == "blah" +# ) +# +# # Check all application units can read remote relation data +# for unit in ops_test.model.applications[DATABASE_APP_NAME].units: +# action = await unit.run_action( +# "get-relation-field", +# **{ +# "relation_id": pytest.second_database_relation.id, +# "field": "new_field", +# }, +# ) +# await action.wait() +# assert action.results.get("value") == "blah" +# +# # Check if database can retrieve self-side relation data +# action = await ops_test.model.units.get(leader_name).run_action( +# "get-relation-self-side-field", +# **{ +# "relation_id": pytest.second_database_relation.id, +# "field": "new_field", +# "value": "blah", +# }, +# ) +# await action.wait() +# assert action.results.get("value") == "blah" +# +# # Delete normal field +# action = await ops_test.model.units.get(leader_name).run_action( +# "delete-relation-field", +# **{"relation_id": pytest.second_database_relation.id, "field": "new_field"}, +# ) +# await action.wait() +# +# assert ( +# await get_application_relation_data( +# ops_test, +# DATABASE_APP_NAME, +# SECOND_DATABASE_RELATION_NAME, +# "new_field", +# related_endpoint="second-database", +# ) +# is None +# ) +# +# +# async def test_provider_set_delete_fields_leader_only(ops_test: OpsTest): +# leader_id = await get_leader_id(ops_test, DATABASE_APP_NAME) +# leader_name = f"{DATABASE_APP_NAME}/{leader_id}" +# action = await ops_test.model.units.get(leader_name).run_action( +# "set-relation-field", +# **{ +# "relation_id": pytest.second_database_relation.id, +# "field": "new_field", +# "value": "blah", +# }, +# ) +# await action.wait() +# +# unit_id = await get_non_leader_id(ops_test, DATABASE_APP_NAME) +# unit_name = f"{DATABASE_APP_NAME}/{unit_id}" +# action = await ops_test.model.units.get(unit_name).run_action( +# "set-relation-field", +# **{ +# "relation_id": pytest.second_database_relation.id, +# "field": "new_field2", +# "value": "blah2", +# }, +# ) +# await action.wait() +# +# assert ( +# await get_application_relation_data( +# ops_test, APPLICATION_APP_NAME, SECOND_DATABASE_RELATION_NAME, "new_field2" +# ) +# is None +# ) +# +# action = await ops_test.model.units.get(unit_name).run_action( +# "delete-relation-field", +# **{"relation_id": pytest.second_database_relation.id, "field": "new_field"}, +# ) +# await action.wait() +# +# assert ( +# await get_application_relation_data( +# ops_test, APPLICATION_APP_NAME, SECOND_DATABASE_RELATION_NAME, "new_field" +# ) +# == "blah" +# ) +# +# +# async def test_requires_set_delete_fields(ops_test: OpsTest): +# # Add field +# leader_id = await get_leader_id(ops_test, APPLICATION_APP_NAME) +# leader_name = f"{APPLICATION_APP_NAME}/{leader_id}" +# action = await ops_test.model.units.get(leader_name).run_action( +# "set-relation-field", +# **{ +# "relation_id": pytest.second_database_relation.id, +# "field": "new_field_req", +# "value": "blah-req", +# }, +# ) +# await action.wait() +# +# assert ( +# await get_application_relation_data( +# ops_test, +# DATABASE_APP_NAME, +# DATABASE_APP_NAME, +# "new_field_req", +# related_endpoint=SECOND_DATABASE_RELATION_NAME, +# ) +# == "blah-req" +# ) +# +# # Delete field +# action = await ops_test.model.units.get(leader_name).run_action( +# "delete-relation-field", +# **{"relation_id": pytest.second_database_relation.id, "field": "new_field_req"}, +# ) +# await action.wait() +# +# assert ( +# await get_application_relation_data( +# ops_test, +# DATABASE_APP_NAME, +# DATABASE_APP_NAME, +# "new_field_req", +# related_endpoint=SECOND_DATABASE_RELATION_NAME, +# ) +# is None +# ) +# +# +# async def test_requires_set_delete_fields_leader_only(ops_test: OpsTest): +# leader_id = await get_leader_id(ops_test, APPLICATION_APP_NAME) +# leader_name = f"{APPLICATION_APP_NAME}/{leader_id}" +# action = await ops_test.model.units.get(leader_name).run_action( +# "set-relation-field", +# **{ +# "relation_id": pytest.second_database_relation.id, +# "field": "new_field-req", +# "value": "blah-req", +# }, +# ) +# await action.wait() +# +# unit_id = await get_non_leader_id(ops_test, APPLICATION_APP_NAME) +# unit_name = f"{APPLICATION_APP_NAME}/{unit_id}" +# action = await ops_test.model.units.get(unit_name).run_action( +# "set-relation-field", +# **{ +# "relation_id": pytest.second_database_relation.id, +# "field": "new_field2-req", +# "value": "blah2-req", +# }, +# ) +# await action.wait() +# +# assert ( +# await get_application_relation_data( +# ops_test, +# DATABASE_APP_NAME, +# DATABASE_APP_NAME, +# "new_field2-req", +# related_endpoint=SECOND_DATABASE_RELATION_NAME, +# ) +# is None +# ) +# +# action = await ops_test.model.units.get(unit_name).run_action( +# "delete-relation-field", +# **{"relation_id": pytest.second_database_relation.id, "field": "new_field-req"}, +# ) +# await action.wait() +# +# assert ( +# await get_application_relation_data( +# ops_test, +# DATABASE_APP_NAME, +# DATABASE_APP_NAME, +# "new_field-req", +# related_endpoint=SECOND_DATABASE_RELATION_NAME, +# ) +# == "blah-req" +# ) +# +# +# async def test_scaling_requires_can_access_shared_secrest(ops_test): +# """When scaling up the application, new units should have access to relation secrets.""" +# await ops_test.model.applications[APPLICATION_APP_NAME].scale(3) +# +# await ops_test.model.wait_for_idle( +# apps=[APPLICATION_APP_NAME], status="active", timeout=(15 * 60), wait_for_exact_units=3 +# ) +# +# old_unit_name = f"{APPLICATION_APP_NAME}/1" +# new_unit_name = f"{APPLICATION_APP_NAME}/2" +# +# action = await ops_test.model.units.get(old_unit_name).run_action( +# "get-relation-field", +# **{ +# "relation_id": pytest.second_database_relation.id, +# "field": "password", +# }, +# ) +# await action.wait() +# orig_password = action.results.get("value") +# +# action = await ops_test.model.units.get(new_unit_name).run_action( +# "get-relation-field", +# **{ +# "relation_id": pytest.second_database_relation.id, +# "field": "password", +# }, +# ) +# await action.wait() +# new_password = action.results.get("value") +# assert new_password == orig_password