summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorFelipe Ruhland <felipe.ruhland@gmail.com>2021-03-24 17:59:47 +0100
committerFelipe Ruhland <felipe.ruhland@gmail.com>2021-03-24 18:03:54 +0100
commitd4310b2db0e5fe5cb987a454f7e97c6e388a470e (patch)
tree44a5a5ce95180258ed2c3b5694bc05b9b4ae568a
parent31775a1532a66cf8a4c183a99bb5c73623147295 (diff)
downloaddocker-py-d4310b2db0e5fe5cb987a454f7e97c6e388a470e.tar.gz
Fix `KeyError` when creating a new secret
How to reproduce the issue: ```py >>> import docker >>> cli = docker.from_env() >>> cli.secrets.create(name="any_name", data="1") Traceback (most recent call last): File "<stdin>", line 1, in <module> File "/home/docker-py/docker/models/secrets.py", line 10, in __repr__ return "<%s: '%s'>" % (self.__class__.__name__, self.name) File "/home/docker-py/docker/models/secrets.py", line 14, in name return self.attrs['Spec']['Name'] KeyError: 'Spec' ``` The exception raises because create secrets API `/secrets/create` only return the `id` attribute: https://docs.docker.com/engine/api/v1.41/#operation/SecretCreate The secret model is created using just the `id` attribute and fails when looking for Spec.Name attribute. ```py def __repr__(self): return "<%s: '%s'>" % (self.__class__.__name__, self.name) ``` ```py @property def name(self): return self.attrs['Spec']['Name'] ``` I came up with a ugly solution but will prevent the problem to happen again: ```py def create(self, **kwargs): obj = self.client.api.create_secret(**kwargs) + obj.setdefault("Spec", {})["Name"] = kwargs.get("name") return self.prepare_model(obj) ``` After the API call, I added the name attribute to the right place to be used on the property name. ```py >>> import docker >>> cli = docker.from_env() >>> cli.secrets.create(name="any_name", data="1") <Secret: 'any_name'> ``` It isn't the most elegant solution, but it will do the trick. I had a previous PR #2517 when I propose using the `id` attribute instead of `name` on the `__repr__` method, but I think this one will be better. That fixes #2025 Signed-off-by: Felipe Ruhland <felipe.ruhland@gmail.com>
-rw-r--r--docker/models/secrets.py1
-rw-r--r--tests/unit/fake_api.py10
-rw-r--r--tests/unit/fake_api_client.py1
-rw-r--r--tests/unit/models_secrets_test.py11
4 files changed, 23 insertions, 0 deletions
diff --git a/docker/models/secrets.py b/docker/models/secrets.py
index ca11ede..e2ee88a 100644
--- a/docker/models/secrets.py
+++ b/docker/models/secrets.py
@@ -30,6 +30,7 @@ class SecretCollection(Collection):
def create(self, **kwargs):
obj = self.client.api.create_secret(**kwargs)
+ obj.setdefault("Spec", {})["Name"] = kwargs.get("name")
return self.prepare_model(obj)
create.__doc__ = APIClient.create_secret.__doc__
diff --git a/tests/unit/fake_api.py b/tests/unit/fake_api.py
index 27e463d..4fd4d11 100644
--- a/tests/unit/fake_api.py
+++ b/tests/unit/fake_api.py
@@ -17,6 +17,8 @@ FAKE_URL = 'myurl'
FAKE_PATH = '/path'
FAKE_VOLUME_NAME = 'perfectcherryblossom'
FAKE_NODE_ID = '24ifsmvkjbyhk'
+FAKE_SECRET_ID = 'epdyrw4tsi03xy3deu8g8ly6o'
+FAKE_SECRET_NAME = 'super_secret'
# Each method is prefixed with HTTP method (get, post...)
# for clarity and readability
@@ -512,6 +514,12 @@ def post_fake_network_disconnect():
return 200, None
+def post_fake_secret():
+ status_code = 200
+ response = {'ID': FAKE_SECRET_ID}
+ return status_code, response
+
+
# Maps real api url to fake response callback
prefix = 'http+docker://localhost'
if constants.IS_WINDOWS_PLATFORM:
@@ -643,4 +651,6 @@ fake_responses = {
CURRENT_VERSION, prefix, FAKE_NETWORK_ID
), 'POST'):
post_fake_network_disconnect,
+ '{1}/{0}/secrets/create'.format(CURRENT_VERSION, prefix):
+ post_fake_secret,
}
diff --git a/tests/unit/fake_api_client.py b/tests/unit/fake_api_client.py
index e85001d..5825b6e 100644
--- a/tests/unit/fake_api_client.py
+++ b/tests/unit/fake_api_client.py
@@ -40,6 +40,7 @@ def make_fake_api_client(overrides=None):
fake_api.post_fake_create_container()[1],
'create_host_config.side_effect': api_client.create_host_config,
'create_network.return_value': fake_api.post_fake_network()[1],
+ 'create_secret.return_value': fake_api.post_fake_secret()[1],
'exec_create.return_value': fake_api.post_fake_exec_create()[1],
'exec_start.return_value': fake_api.post_fake_exec_start()[1],
'images.return_value': fake_api.get_fake_images()[1],
diff --git a/tests/unit/models_secrets_test.py b/tests/unit/models_secrets_test.py
new file mode 100644
index 0000000..4ccf4c6
--- /dev/null
+++ b/tests/unit/models_secrets_test.py
@@ -0,0 +1,11 @@
+import unittest
+
+from .fake_api_client import make_fake_client
+from .fake_api import FAKE_SECRET_NAME
+
+
+class CreateServiceTest(unittest.TestCase):
+ def test_secrets_repr(self):
+ client = make_fake_client()
+ secret = client.secrets.create(name="super_secret", data="secret")
+ assert secret.__repr__() == "<Secret: '{}'>".format(FAKE_SECRET_NAME)