diff options
author | Felipe Ruhland <felipe.ruhland@gmail.com> | 2021-03-24 17:59:47 +0100 |
---|---|---|
committer | Felipe Ruhland <felipe.ruhland@gmail.com> | 2021-03-24 18:03:54 +0100 |
commit | d4310b2db0e5fe5cb987a454f7e97c6e388a470e (patch) | |
tree | 44a5a5ce95180258ed2c3b5694bc05b9b4ae568a | |
parent | 31775a1532a66cf8a4c183a99bb5c73623147295 (diff) | |
download | docker-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.py | 1 | ||||
-rw-r--r-- | tests/unit/fake_api.py | 10 | ||||
-rw-r--r-- | tests/unit/fake_api_client.py | 1 | ||||
-rw-r--r-- | tests/unit/models_secrets_test.py | 11 |
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) |