From 826d2f82a5b443fdd63b50f83ccb4f8eae798c45 Mon Sep 17 00:00:00 2001 From: Ben Hoyt Date: Wed, 30 Aug 2023 11:10:41 +1200 Subject: [PATCH] fix(model): Ensure Secret.get_content returns a copy of the dict This avoids the caller modifying the Secret instance's dict. Fixes #999 --- ops/model.py | 5 ++++- test/test_model.py | 12 ++++++++++++ 2 files changed, 16 insertions(+), 1 deletion(-) diff --git a/ops/model.py b/ops/model.py index 24f40f513..906b46374 100644 --- a/ops/model.py +++ b/ops/model.py @@ -1181,6 +1181,9 @@ def _on_secret_changed(self, event): def get_content(self, *, refresh: bool = False) -> Dict[str, str]: """Get the secret's content. + Returns: + A copy of the secret's content dictionary. + Args: refresh: If true, fetch the latest revision's content and tell Juju to update to tracking that revision. The default is to @@ -1190,7 +1193,7 @@ def get_content(self, *, refresh: bool = False) -> Dict[str, str]: if refresh or self._content is None: self._content = self._backend.secret_get( id=self.id, label=self.label, refresh=refresh) - return self._content + return self._content.copy() def peek_content(self) -> Dict[str, str]: """Get the content of the latest revision of this secret. diff --git a/test/test_model.py b/test/test_model.py index b310e0f6f..d74dd1725 100755 --- a/test/test_model.py +++ b/test/test_model.py @@ -3104,6 +3104,18 @@ def test_get_content_uncached(self): self.assertEqual(fake_script_calls(self, clear=True), [['secret-get', 'secret:z', '--format=json']]) + def test_get_content_copies_dict(self): + fake_script(self, 'secret-get', """echo '{"foo": "bar"}'""") + + secret = self.make_secret(id='z') + content = secret.get_content() + self.assertEqual(content, {'foo': 'bar'}) + content['new'] = 'value' + self.assertEqual(secret.get_content(), {'foo': 'bar'}) + + self.assertEqual(fake_script_calls(self, clear=True), + [['secret-get', 'secret:z', '--format=json']]) + def test_peek_content(self): fake_script(self, 'secret-get', """echo '{"foo": "peeked"}'""")