Repository: docker/docker-py
Issue: exec_start broken v.2.3.0

Below code block works on v2.2.1 but gives exception on v2.3.0

```
exe = llclient.exec_create(container=containerId, cmd=cmd)['Id']
exe_start= llclient.exec_start(exec_id=exe, detach=False, tty=False, stream=True, socket=False)
```

Exception on v2.3.0:

```
Exception in thread Thread-1:
Traceback (most recent call last):
  File "/System/Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/threading.py", line 810, in __bootstrap_inner
    self.run()
  File "__init__.py", line 67, in run
    exe_start= llclient.exec_start(exec_id=exe, detach=False, tty=False, stream=True, socket=False)
  File "/Library/Python/2.7/site-packages/docker/utils/decorators.py", line 35, in wrapper
    return f(self, *args, **kwargs)
  File "/Library/Python/2.7/site-packages/docker/utils/decorators.py", line 19, in wrapped
    'Resource ID was not provided'
NullResource: Resource ID was not provided
```

Changed files:
docker/api/container.py, docker/api/network.py, docker/api/secret.py, docker/api/client.py, docker/api/swarm.py, docker/utils/decorators.py, docker/api/plugin.py, docker/api/exec_api.py, docker/api/service.py, docker/types/services.py, docker/api/image.py

diff --git a/docker/api/client.py b/docker/api/client.py
index 54ec6abb..6e567b16 100644
--- a/docker/api/client.py
+++ b/docker/api/client.py
@@ -248,7 +248,7 @@ class APIClient(
             'stream': 1
         }
 
-    @check_resource
+    @check_resource('container')
     def _attach_websocket(self, container, params=None):
         url = self._url("/containers/{0}/attach/ws", container)
         req = requests.Request("POST", url, params=self._attach_params(params))
diff --git a/docker/api/container.py b/docker/api/container.py
index 97b54059..f7ff971b 100644
--- a/docker/api/container.py
+++ b/docker/api/container.py
@@ -10,7 +10,7 @@ from ..types import (
 
 
 class ContainerApiMixin(object):
-    @utils.check_resource
+    @utils.check_resource('container')
     def attach(self, container, stdout=True, stderr=True,
                stream=False, logs=False):
         """
@@ -54,7 +54,7 @@ class ContainerApiMixin(object):
 
         return self._read_from_socket(response, stream)
 
-    @utils.check_resource
+    @utils.check_resource('container')
     def attach_socket(self, container, params=None, ws=False):
         """
         Like ``attach``, but returns the underlying socket-like object for the
@@ -93,7 +93,7 @@ class ContainerApiMixin(object):
             )
         )
 
-    @utils.check_resource
+    @utils.check_resource('container')
     def commit(self, container, repository=None, tag=None, message=None,
                author=None, changes=None, conf=None):
         """
@@ -195,7 +195,7 @@ class ContainerApiMixin(object):
                 x['Id'] = x['Id'][:12]
         return res
 
-    @utils.check_resource
+    @utils.check_resource('container')
     def copy(self, container, resource):
         """
         Identical to the ``docker cp`` command. Get files/folders from the
@@ -659,7 +659,7 @@ class ContainerApiMixin(object):
         """
         return EndpointConfig(self._version, *args, **kwargs)
 
-    @utils.check_resource
+    @utils.check_resource('container')
     def diff(self, container):
         """
         Inspect changes on a container's filesystem.
@@ -678,7 +678,7 @@ class ContainerApiMixin(object):
             self._get(self._url("/containers/{0}/changes", container)), True
         )
 
-    @utils.check_resource
+    @utils.check_resource('container')
     def export(self, container):
         """
         Export the contents of a filesystem as a tar archive.
@@ -699,7 +699,7 @@ class ContainerApiMixin(object):
         self._raise_for_status(res)
         return res.raw
 
-    @utils.check_resource
+    @utils.check_resource('container')
     @utils.minimum_version('1.20')
     def get_archive(self, container, path):
         """
@@ -730,7 +730,7 @@ class ContainerApiMixin(object):
             utils.decode_json_header(encoded_stat) if encoded_stat else None
         )
 
-    @utils.check_resource
+    @utils.check_resource('container')
     def inspect_container(self, container):
         """
         Identical to the `docker inspect` command, but only for containers.
@@ -750,7 +750,7 @@ class ContainerApiMixin(object):
             self._get(self._url("/containers/{0}/json", container)), True
         )
 
-    @utils.check_resource
+    @utils.check_resource('container')
     def kill(self, container, signal=None):
         """
         Kill a container or send a signal to a container.
@@ -773,7 +773,7 @@ class ContainerApiMixin(object):
 
         self._raise_for_status(res)
 
-    @utils.check_resource
+    @utils.check_resource('container')
     def logs(self, container, stdout=True, stderr=True, stream=False,
              timestamps=False, tail='all', since=None, follow=None):
         """
@@ -836,7 +836,7 @@ class ContainerApiMixin(object):
             logs=True
         )
 
-    @utils.check_resource
+    @utils.check_resource('container')
     def pause(self, container):
         """
         Pauses all processes within a container.
@@ -852,7 +852,7 @@ class ContainerApiMixin(object):
         res = self._post(url)
         self._raise_for_status(res)
 
-    @utils.check_resource
+    @utils.check_resource('container')
     def port(self, container, private_port):
         """
         Lookup the public-facing port that is NAT-ed to ``private_port``.
@@ -901,7 +901,7 @@ class ContainerApiMixin(object):
 
         return h_ports
 
-    @utils.check_resource
+    @utils.check_resource('container')
     @utils.minimum_version('1.20')
     def put_archive(self, container, path, data):
         """
@@ -949,7 +949,7 @@ class ContainerApiMixin(object):
         url = self._url('/containers/prune')
         return self._result(self._post(url, params=params), True)
 
-    @utils.check_resource
+    @utils.check_resource('container')
     def remove_container(self, container, v=False, link=False, force=False):
         """
         Remove a container. Similar to the ``docker rm`` command.
@@ -973,7 +973,7 @@ class ContainerApiMixin(object):
         self._raise_for_status(res)
 
     @utils.minimum_version('1.17')
-    @utils.check_resource
+    @utils.check_resource('container')
     def rename(self, container, name):
         """
         Rename a container. Similar to the ``docker rename`` command.
@@ -991,7 +991,7 @@ class ContainerApiMixin(object):
         res = self._post(url, params=params)
         self._raise_for_status(res)
 
-    @utils.check_resource
+    @utils.check_resource('container')
     def resize(self, container, height, width):
         """
         Resize the tty session.
@@ -1010,7 +1010,7 @@ class ContainerApiMixin(object):
         res = self._post(url, params=params)
         self._raise_for_status(res)
 
-    @utils.check_resource
+    @utils.check_resource('container')
     def restart(self, container, timeout=10):
         """
         Restart a container. Similar to the ``docker restart`` command.
@@ -1031,7 +1031,7 @@ class ContainerApiMixin(object):
         res = self._post(url, params=params)
         self._raise_for_status(res)
 
-    @utils.check_resource
+    @utils.check_resource('container')
     def start(self, container, *args, **kwargs):
         """
         Start a container. Similar to the ``docker start`` command, but
@@ -1070,7 +1070,7 @@ class ContainerApiMixin(object):
         self._raise_for_status(res)
 
     @utils.minimum_version('1.17')
-    @utils.check_resource
+    @utils.check_resource('container')
     def stats(self, container, decode=None, stream=True):
         """
         Stream statistics for a specific container. Similar to the
@@ -1096,7 +1096,7 @@ class ContainerApiMixin(object):
             return self._result(self._get(url, params={'stream': False}),
                                 json=True)
 
-    @utils.check_resource
+    @utils.check_resource('container')
     def stop(self, container, timeout=10):
         """
         Stops a container. Similar to the ``docker stop`` command.
@@ -1117,7 +1117,7 @@ class ContainerApiMixin(object):
                          timeout=(timeout + (self.timeout or 0)))
         self._raise_for_status(res)
 
-    @utils.check_resource
+    @utils.check_resource('container')
     def top(self, container, ps_args=None):
         """
         Display the running processes of a container.
@@ -1139,7 +1139,7 @@ class ContainerApiMixin(object):
             params['ps_args'] = ps_args
         return self._result(self._get(u, params=params), True)
 
-    @utils.check_resource
+    @utils.check_resource('container')
     def unpause(self, container):
         """
         Unpause all processes within a container.
@@ -1152,7 +1152,7 @@ class ContainerApiMixin(object):
         self._raise_for_status(res)
 
     @utils.minimum_version('1.22')
-    @utils.check_resource
+    @utils.check_resource('container')
     def update_container(
         self, container, blkio_weight=None, cpu_period=None, cpu_quota=None,
         cpu_shares=None, cpuset_cpus=None, cpuset_mems=None, mem_limit=None,
@@ -1217,7 +1217,7 @@ class ContainerApiMixin(object):
         res = self._post_json(url, data=data)
         return self._result(res, True)
 
-    @utils.check_resource
+    @utils.check_resource('container')
     def wait(self, container, timeout=None):
         """
         Block until a container stops, then return its exit code. Similar to
diff --git a/docker/api/exec_api.py b/docker/api/exec_api.py
index 3ff65256..2b407cef 100644
--- a/docker/api/exec_api.py
+++ b/docker/api/exec_api.py
@@ -6,7 +6,7 @@ from .. import utils
 
 class ExecApiMixin(object):
     @utils.minimum_version('1.15')
-    @utils.check_resource
+    @utils.check_resource('container')
     def exec_create(self, container, cmd, stdout=True, stderr=True,
                     stdin=False, tty=False, privileged=False, user='',
                     environment=None):
@@ -110,7 +110,7 @@ class ExecApiMixin(object):
         self._raise_for_status(res)
 
     @utils.minimum_version('1.15')
-    @utils.check_resource
+    @utils.check_resource('exec_id')
     def exec_start(self, exec_id, detach=False, tty=False, stream=False,
                    socket=False):
         """
diff --git a/docker/api/image.py b/docker/api/image.py
index 09eb086d..181c4a1e 100644
--- a/docker/api/image.py
+++ b/docker/api/image.py
@@ -12,7 +12,7 @@ log = logging.getLogger(__name__)
 
 class ImageApiMixin(object):
 
-    @utils.check_resource
+    @utils.check_resource('image')
     def get_image(self, image):
         """
         Get a tarball of an image. Similar to the ``docker save`` command.
@@ -39,7 +39,7 @@ class ImageApiMixin(object):
         self._raise_for_status(res)
         return res.raw
 
-    @utils.check_resource
+    @utils.check_resource('image')
     def history(self, image):
         """
         Show the history of an image.
@@ -228,7 +228,7 @@ class ImageApiMixin(object):
             image=image, repository=repository, tag=tag, changes=changes
         )
 
-    @utils.check_resource
+    @utils.check_resource('image')
     def insert(self, image, url, path):
         if utils.compare_version('1.12', self._version) >= 0:
             raise errors.DeprecatedMethod(
@@ -241,7 +241,7 @@ class ImageApiMixin(object):
         }
         return self._result(self._post(api_url, params=params))
 
-    @utils.check_resource
+    @utils.check_resource('image')
     def inspect_image(self, image):
         """
         Get detailed information about an image. Similar to the ``docker
@@ -443,7 +443,7 @@ class ImageApiMixin(object):
 
         return self._result(response)
 
-    @utils.check_resource
+    @utils.check_resource('image')
     def remove_image(self, image, force=False, noprune=False):
         """
         Remove an image. Similar to the ``docker rmi`` command.
@@ -477,7 +477,7 @@ class ImageApiMixin(object):
             True
         )
 
-    @utils.check_resource
+    @utils.check_resource('image')
     def tag(self, image, repository, tag=None, force=False):
         """
         Tag an image into a repository. Similar to the ``docker tag`` command.
diff --git a/docker/api/network.py b/docker/api/network.py
index 74f4cd2b..bd2959fd 100644
--- a/docker/api/network.py
+++ b/docker/api/network.py
@@ -156,6 +156,7 @@ class NetworkApiMixin(object):
         return self._result(self._post(url, params=params), True)
 
     @minimum_version('1.21')
+    @check_resource('net_id')
     def remove_network(self, net_id):
         """
         Remove a network. Similar to the ``docker network rm`` command.
@@ -168,6 +169,7 @@ class NetworkApiMixin(object):
         self._raise_for_status(res)
 
     @minimum_version('1.21')
+    @check_resource('net_id')
     def inspect_network(self, net_id, verbose=None):
         """
         Get detailed information about a network.
@@ -187,7 +189,7 @@ class NetworkApiMixin(object):
         res = self._get(url, params=params)
         return self._result(res, json=True)
 
-    @check_resource
+    @check_resource('image')
     @minimum_version('1.21')
     def connect_container_to_network(self, container, net_id,
                                      ipv4_address=None, ipv6_address=None,
@@ -224,7 +226,7 @@ class NetworkApiMixin(object):
         res = self._post_json(url, data=data)
         self._raise_for_status(res)
 
-    @check_resource
+    @check_resource('image')
     @minimum_version('1.21')
     def disconnect_container_from_network(self, container, net_id,
                                           force=False):
diff --git a/docker/api/plugin.py b/docker/api/plugin.py
index ba40c882..87520cce 100644
--- a/docker/api/plugin.py
+++ b/docker/api/plugin.py
@@ -5,7 +5,7 @@ from .. import auth, utils
 
 class PluginApiMixin(object):
     @utils.minimum_version('1.25')
-    @utils.check_resource
+    @utils.check_resource('name')
     def configure_plugin(self, name, options):
         """
             Configure a plugin.
@@ -171,7 +171,7 @@ class PluginApiMixin(object):
         return self._result(self._get(url, params=params), True)
 
     @utils.minimum_version('1.25')
-    @utils.check_resource
+    @utils.check_resource('name')
     def push_plugin(self, name):
         """
             Push a plugin to the registry.
@@ -195,7 +195,7 @@ class PluginApiMixin(object):
         return self._stream_helper(res, decode=True)
 
     @utils.minimum_version('1.25')
-    @utils.check_resource
+    @utils.check_resource('name')
     def remove_plugin(self, name, force=False):
         """
             Remove an installed plugin.
@@ -215,7 +215,7 @@ class PluginApiMixin(object):
         return True
 
     @utils.minimum_version('1.26')
-    @utils.check_resource
+    @utils.check_resource('name')
     def upgrade_plugin(self, name, remote, privileges):
         """
             Upgrade an installed plugin.
diff --git a/docker/api/secret.py b/docker/api/secret.py
index 03534a62..1760a394 100644
--- a/docker/api/secret.py
+++ b/docker/api/secret.py
@@ -36,7 +36,7 @@ class SecretApiMixin(object):
         )
 
     @utils.minimum_version('1.25')
-    @utils.check_resource
+    @utils.check_resource('id')
     def inspect_secret(self, id):
         """
             Retrieve secret metadata
@@ -54,7 +54,7 @@ class SecretApiMixin(object):
         return self._result(self._get(url), True)
 
     @utils.minimum_version('1.25')
-    @utils.check_resource
+    @utils.check_resource('id')
     def remove_secret(self, id):
         """
             Remove a secret
diff --git a/docker/api/service.py b/docker/api/service.py
index aea93cbf..0f14776d 100644
--- a/docker/api/service.py
+++ b/docker/api/service.py
@@ -113,7 +113,7 @@ class ServiceApiMixin(object):
         )
 
     @utils.minimum_version('1.24')
-    @utils.check_resource
+    @utils.check_resource('service')
     def inspect_service(self, service):
         """
         Return information about a service.
@@ -132,7 +132,7 @@ class ServiceApiMixin(object):
         return self._result(self._get(url), True)
 
     @utils.minimum_version('1.24')
-    @utils.check_resource
+    @utils.check_resource('task')
     def inspect_task(self, task):
         """
         Retrieve information about a task.
@@ -151,7 +151,7 @@ class ServiceApiMixin(object):
         return self._result(self._get(url), True)
 
     @utils.minimum_version('1.24')
-    @utils.check_resource
+    @utils.check_resource('service')
     def remove_service(self, service):
         """
         Stop and remove a service.
@@ -195,7 +195,7 @@ class ServiceApiMixin(object):
         return self._result(self._get(url, params=params), True)
 
     @utils.minimum_version('1.25')
-    @utils.check_resource
+    @utils.check_resource('service')
     def service_logs(self, service, details=False, follow=False, stdout=False,
                      stderr=False, since=0, timestamps=False, tail='all',
                      is_tty=None):
@@ -269,7 +269,7 @@ class ServiceApiMixin(object):
         return self._result(self._get(url, params=params), True)
 
     @utils.minimum_version('1.24')
-    @utils.check_resource
+    @utils.check_resource('service')
     def update_service(self, service, version, task_template=None, name=None,
                        labels=None, mode=None, update_config=None,
                        networks=None, endpoint_config=None,
diff --git a/docker/api/swarm.py b/docker/api/swarm.py
index 88770562..4fa0c4a1 100644
--- a/docker/api/swarm.py
+++ b/docker/api/swarm.py
@@ -117,7 +117,7 @@ class SwarmApiMixin(object):
         url = self._url('/swarm')
         return self._result(self._get(url), True)
 
-    @utils.check_resource
+    @utils.check_resource('node_id')
     @utils.minimum_version('1.24')
     def inspect_node(self, node_id):
         """
@@ -228,7 +228,7 @@ class SwarmApiMixin(object):
 
         return self._result(self._get(url, params=params), True)
 
-    @utils.check_resource
+    @utils.check_resource('node_id')
     @utils.minimum_version('1.24')
     def remove_node(self, node_id, force=False):
         """
diff --git a/docker/types/services.py b/docker/types/services.py
index 7456a42b..9cec34ef 100644
--- a/docker/types/services.py
+++ b/docker/types/services.py
@@ -443,7 +443,7 @@ class SecretReference(dict):
             gid (string): GID of the secret file's group. Default: 0
             mode (int): File access mode inside the container. Default: 0o444
     """
-    @check_resource
+    @check_resource('secret_id')
     def __init__(self, secret_id, secret_name, filename=None, uid=None,
                  gid=None, mode=0o444):
         self['SecretName'] = secret_name
diff --git a/docker/utils/decorators.py b/docker/utils/decorators.py
index 18cde412..5e195c0e 100644
--- a/docker/utils/decorators.py
+++ b/docker/utils/decorators.py
@@ -4,22 +4,21 @@ from .. import errors
 from . import utils
 
 
-def check_resource(f):
-    @functools.wraps(f)
-    def wrapped(self, resource_id=None, *args, **kwargs):
-        if resource_id is None:
-            if kwargs.get('container'):
-                resource_id = kwargs.pop('container')
-            elif kwargs.get('image'):
-                resource_id = kwargs.pop('image')
-        if isinstance(resource_id, dict):
-            resource_id = resource_id.get('Id', resource_id.get('ID'))
-        if not resource_id:
-            raise errors.NullResource(
-                'Resource ID was not provided'
-            )
-        return f(self, resource_id, *args, **kwargs)
-    return wrapped
+def check_resource(resource_name):
+    def decorator(f):
+        @functools.wraps(f)
+        def wrapped(self, resource_id=None, *args, **kwargs):
+            if resource_id is None and kwargs.get(resource_name):
+                resource_id = kwargs.pop(resource_name)
+            if isinstance(resource_id, dict):
+                resource_id = resource_id.get('Id', resource_id.get('ID'))
+            if not resource_id:
+                raise errors.NullResource(
+                    'Resource ID was not provided'
+                )
+            return f(self, resource_id, *args, **kwargs)
+        return wrapped
+    return decorator
 
 
 def minimum_version(version):