From cb9a7ce01e817fdfb681872588484508a8b8899b Mon Sep 17 00:00:00 2001 From: Mohamad Nour Chawich Date: Wed, 13 Apr 2016 17:08:34 +0200 Subject: [PATCH 01/19] Update requirements.txt Old version of websocket-client has many bugs that were fixed. Some were causing issues in dependencies like this one docker/dockercloud-haproxy#15 --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index 72c0961..7540a8f 100644 --- a/requirements.txt +++ b/requirements.txt @@ -2,4 +2,4 @@ backports.ssl-match-hostname==3.4.0.2 future==0.15.0 requests==2.7.0 six==1.9.0 -websocket-client==0.32.0 +websocket-client==0.36.0 From af475584102596428f65d6ff1e558af270edc67e Mon Sep 17 00:00:00 2001 From: tifayuki Date: Thu, 14 Apr 2016 13:00:48 +0200 Subject: [PATCH 02/19] use abstract dependency instead of concrete ones --- requirements.txt | 1 - setup.py | 11 +++++++---- 2 files changed, 7 insertions(+), 5 deletions(-) diff --git a/requirements.txt b/requirements.txt index 7540a8f..ec72cca 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,4 +1,3 @@ -backports.ssl-match-hostname==3.4.0.2 future==0.15.0 requests==2.7.0 six==1.9.0 diff --git a/setup.py b/setup.py index a833d51..ed5b22e 100644 --- a/setup.py +++ b/setup.py @@ -4,6 +4,12 @@ import re from setuptools import setup, find_packages +requirements =[ + "future >= 0.15.0, < 1", + "requests >= 2.5.2, < 3", + "six >= 1.3.0, < 2", + "websocket-client >= 0.32.0, < 1" +] def read(*parts): path = os.path.join(os.path.dirname(__file__), *parts) @@ -20,14 +26,11 @@ def find_version(*file_paths): raise RuntimeError('Unable to find version string.') -with open('requirements.txt') as f: - install_requires = f.read().splitlines() - setup( name="python-dockercloud", version=find_version('dockercloud', '__init__.py'), packages=find_packages(), - install_requires=install_requires, + install_requires=requirements, provides=['docker'], include_package_data=True, author="Docker, Inc.", From be9d7151507fbe9de4e269d33801a4865bf5a44e Mon Sep 17 00:00:00 2001 From: tifayuki Date: Tue, 19 Apr 2016 17:44:28 +0200 Subject: [PATCH 03/19] change ws ping_interval to adapt new version of ws client --- dockercloud/api/base.py | 6 +++--- dockercloud/api/events.py | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/dockercloud/api/base.py b/dockercloud/api/base.py index b375ba4..bc904a5 100644 --- a/dockercloud/api/base.py +++ b/dockercloud/api/base.py @@ -308,7 +308,7 @@ class StreamingAPI(BasicObject): on_message=self._on_message, on_error=self._on_error, on_close=self._on_close) - ws.run_forever(ping_interval=5, ping_timeout=5, *args, **kwargs) + ws.run_forever(ping_interval=10, ping_timeout=5, *args, **kwargs) class StreamingLog(StreamingAPI): @@ -329,7 +329,7 @@ class StreamingLog(StreamingAPI): on_message=self._on_message, on_error=self._on_error, on_close=self._on_close) - ws.run_forever(ping_interval=5, ping_timeout=5, *args, **kwargs) + ws.run_forever(ping_interval=10, ping_timeout=5, *args, **kwargs) class Exec(StreamingAPI): @@ -348,4 +348,4 @@ class Exec(StreamingAPI): on_message=self._on_message, on_error=self._on_error, on_close=self._on_close) - ws.run_forever(ping_interval=5, ping_timeout=5, *args, **kwargs) + ws.run_forever(ping_interval=10, ping_timeout=5, *args, **kwargs) diff --git a/dockercloud/api/events.py b/dockercloud/api/events.py index 9a3fcfc..ea3bd83 100644 --- a/dockercloud/api/events.py +++ b/dockercloud/api/events.py @@ -39,4 +39,4 @@ class Events(StreamingAPI): on_message=self._on_message, on_error=self._on_error, on_close=self._on_close) - ws.run_forever(ping_interval=5, ping_timeout=5, *args, **kwargs) + ws.run_forever(ping_interval=10, ping_timeout=5, *args, **kwargs) From 91dfe343ed6902976f95e4048376c987a5e6b56f Mon Sep 17 00:00:00 2001 From: tifayuki Date: Tue, 19 Apr 2016 19:03:40 +0200 Subject: [PATCH 04/19] remove makefile and add test-requirements --- MANIFEST.in | 1 + Makefile | 13 ------------- setup.py | 5 +++++ test-requirements.txt | 3 +++ 4 files changed, 9 insertions(+), 13 deletions(-) delete mode 100644 Makefile create mode 100644 test-requirements.txt diff --git a/MANIFEST.in b/MANIFEST.in index 84c7124..f989a21 100644 --- a/MANIFEST.in +++ b/MANIFEST.in @@ -1,3 +1,4 @@ include LICENSE include requirements.txt +include test-requirements.txt include README.md diff --git a/Makefile b/Makefile deleted file mode 100644 index 0fd86d8..0000000 --- a/Makefile +++ /dev/null @@ -1,13 +0,0 @@ -test:prepare - venv/bin/python setup.py test - -clean: - rm -rf venv build dist *.egg-info - find . -name '*.pyc' -delete - -prepare:clean - set -ex - virtualenv venv - venv/bin/pip install mock - venv/bin/pip install -r requirements.txt - venv/bin/python setup.py install diff --git a/setup.py b/setup.py index ed5b22e..2ab4865 100644 --- a/setup.py +++ b/setup.py @@ -11,6 +11,7 @@ requirements =[ "websocket-client >= 0.32.0, < 1" ] + def read(*parts): path = os.path.join(os.path.dirname(__file__), *parts) with codecs.open(path, encoding='utf-8') as fobj: @@ -25,12 +26,16 @@ def find_version(*file_paths): return version_match.group(1) raise RuntimeError('Unable to find version string.') +with open('./test-requirements.txt') as test_reqs_txt: + test_requirements = [line for line in test_reqs_txt] + setup( name="python-dockercloud", version=find_version('dockercloud', '__init__.py'), packages=find_packages(), install_requires=requirements, + tests_require=test_requirements, provides=['docker'], include_package_data=True, author="Docker, Inc.", diff --git a/test-requirements.txt b/test-requirements.txt new file mode 100644 index 0000000..2e8cebf --- /dev/null +++ b/test-requirements.txt @@ -0,0 +1,3 @@ +mock==1.0.1 +coverage==4.0.3 +nose==1.3.7 From aef85ca5ec5bbbb2501e397d30e50737d7725c7e Mon Sep 17 00:00:00 2001 From: tifayuki Date: Tue, 19 Apr 2016 19:16:33 +0200 Subject: [PATCH 05/19] bump ver --- dockercloud/__init__.py | 2 +- requirements.txt | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/dockercloud/__init__.py b/dockercloud/__init__.py index 1607b62..f8dc072 100644 --- a/dockercloud/__init__.py +++ b/dockercloud/__init__.py @@ -25,7 +25,7 @@ from dockercloud.api.utils import Utils from dockercloud.api.events import Events from dockercloud.api.nodeaz import AZ -__version__ = '1.0.3' +__version__ = '1.0.4' dockercloud_auth = os.environ.get('DOCKERCLOUD_AUTH') basic_auth = auth.load_from_file("~/.docker/config.json") diff --git a/requirements.txt b/requirements.txt index ec72cca..fe172c4 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,4 +1,4 @@ future==0.15.0 requests==2.7.0 six==1.9.0 -websocket-client==0.36.0 +websocket-client==0.37.0 From ed92451219f6ea38ed01aea88b3b863d7d1eca4c Mon Sep 17 00:00:00 2001 From: tifayuki Date: Tue, 26 Apr 2016 12:51:36 +0200 Subject: [PATCH 06/19] add tests for CI --- Dockerfile | 6 ++++++ docker-compose.test.yml | 3 +++ hooks/push | 2 ++ 3 files changed, 11 insertions(+) create mode 100644 Dockerfile create mode 100644 docker-compose.test.yml create mode 100755 hooks/push diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..d20508e --- /dev/null +++ b/Dockerfile @@ -0,0 +1,6 @@ +FROM python:2.7.11-alpine + +ADD . /sdk +WORKDIR sdk +RUN python setup.py install + diff --git a/docker-compose.test.yml b/docker-compose.test.yml new file mode 100644 index 0000000..f4d914a --- /dev/null +++ b/docker-compose.test.yml @@ -0,0 +1,3 @@ +sut: + build: . + command: python setup.py test diff --git a/hooks/push b/hooks/push new file mode 100755 index 0000000..f966504 --- /dev/null +++ b/hooks/push @@ -0,0 +1,2 @@ +#!/bin/sh +echo "Skipping push the image" From 94a632aa4c0565affff9fbc6faa43cbfcb366e38 Mon Sep 17 00:00:00 2001 From: tifayuki Date: Tue, 19 Apr 2016 18:57:59 +0200 Subject: [PATCH 07/19] fix the auth error handler error in dockercloud events --- dockercloud/api/base.py | 2 +- dockercloud/api/events.py | 21 +++++++++++++++------ 2 files changed, 16 insertions(+), 7 deletions(-) diff --git a/dockercloud/api/base.py b/dockercloud/api/base.py index bc904a5..c6bf3d0 100644 --- a/dockercloud/api/base.py +++ b/dockercloud/api/base.py @@ -264,7 +264,7 @@ class StreamingAPI(BasicObject): header = {'User-Agent': user_agent} header.update(dockercloud.auth.get_auth_header()) self.header = [": ".join([key, value]) for key, value in header.items()] - logger.info("websocket: %s %s" % (self.url, self.header)) + logger.info("Websocket: %s %s" % (self.url, self.header)) self.open_handler = None self.message_handler = None self.error_handler = None diff --git a/dockercloud/api/events.py b/dockercloud/api/events.py index ea3bd83..f84e4aa 100644 --- a/dockercloud/api/events.py +++ b/dockercloud/api/events.py @@ -1,6 +1,7 @@ from __future__ import absolute_import import json +import logging import websocket @@ -8,32 +9,40 @@ import dockercloud from .base import StreamingAPI from .exceptions import AuthError +logger = logging.getLogger("python-dockercloud") + class Events(StreamingAPI): def __init__(self): endpoint = "events" url = "/".join([dockercloud.stream_host.rstrip("/"), "api", "audit", self._api_version, endpoint.lstrip("/")]) + self.invaid_auth_headers = set() + self.auth_error = "" super(self.__class__, self).__init__(url) def _on_message(self, ws, message): + logger.info("Websocket Message: %s" % message) try: event = json.loads(message) except ValueError: return - - if event.get("type") == "error" and event.get("data", {}).get("errorMessage") == "UNAUTHORIZED": - self.auth_error = True - raise AuthError("Not authorized") if event.get("type") == "auth": return if self.message_handler: self.message_handler(message) + def _on_error(self, ws, e): + if isinstance(e, websocket._exceptions.WebSocketBadStatusException) and getattr(e, "status_code") == 401: + self.auth_error = "Not Authorized" + self.invaid_auth_headers.add(str(dockercloud.auth.get_auth_header())) + + super(self.__class__, self)._on_error(ws, e) + def run_forever(self, *args, **kwargs): while True: - if self.auth_error: - raise AuthError("Not authorized") + if str(dockercloud.auth.get_auth_header()) in self.invaid_auth_headers: + raise AuthError(self.auth_error) ws = websocket.WebSocketApp(self.url, header=self.header, on_open=self._on_open, on_message=self._on_message, From 8494e4e04d432caaf6293f7e2954f3a4891d8e9a Mon Sep 17 00:00:00 2001 From: tifayuki Date: Tue, 17 May 2016 19:20:39 +0200 Subject: [PATCH 08/19] bump ver --- dockercloud/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dockercloud/__init__.py b/dockercloud/__init__.py index f8dc072..1fd6d63 100644 --- a/dockercloud/__init__.py +++ b/dockercloud/__init__.py @@ -25,7 +25,7 @@ from dockercloud.api.utils import Utils from dockercloud.api.events import Events from dockercloud.api.nodeaz import AZ -__version__ = '1.0.4' +__version__ = '1.0.5' dockercloud_auth = os.environ.get('DOCKERCLOUD_AUTH') basic_auth = auth.load_from_file("~/.docker/config.json") From d2ec0d96e75f7a007447f7389e251c85dd4521ba Mon Sep 17 00:00:00 2001 From: tifayuki Date: Thu, 9 Jun 2016 19:58:23 +0200 Subject: [PATCH 09/19] add the support for team and orgs --- dockercloud/__init__.py | 2 ++ dockercloud/api/base.py | 34 ++++++++++++++++++++++++++++------ dockercloud/api/events.py | 7 ++++++- 3 files changed, 36 insertions(+), 7 deletions(-) diff --git a/dockercloud/__init__.py b/dockercloud/__init__.py index 1fd6d63..e970478 100644 --- a/dockercloud/__init__.py +++ b/dockercloud/__init__.py @@ -38,6 +38,8 @@ if os.environ.get('DOCKERCLOUD_USER') and os.environ.get('DOCKERCLOUD_APIKEY'): rest_host = os.environ.get("DOCKERCLOUD_REST_HOST") or 'https://cloud.docker.com/' stream_host = os.environ.get("DOCKERCLOUD_STREAM_HOST") or 'wss://ws.cloud.docker.com/' +namespace = os.environ.get('DOCKERCLOUD_NAMESPACE') + user_agent = None logging.basicConfig() diff --git a/dockercloud/api/base.py b/dockercloud/api/base.py index c6bf3d0..afe32fd 100644 --- a/dockercloud/api/base.py +++ b/dockercloud/api/base.py @@ -58,7 +58,11 @@ class Restful(BasicObject): assert subsystem, "Subsystem not specified for %s" % self.__class__.__name__ for k, v in list(dict.items()): setattr(self, k, v) - self._detail_uri = "/".join(["api", subsystem, self._api_version, endpoint.strip("/"), self.pk]) + if dockercloud.namespace: + self._detail_uri = "/".join(["api", subsystem, self._api_version, dockercloud.namespace, + endpoint.strip("/"), self.pk]) + else: + self._detail_uri = "/".join(["api", subsystem, self._api_version, endpoint.strip("/"), self.pk]) self.__setchanges__([]) @property @@ -126,7 +130,10 @@ class Immutable(Restful): subsystem = getattr(cls, 'subsystem', None) assert endpoint, "Endpoint not specified for %s" % cls.__name__ assert subsystem, "Subsystem not specified for %s" % cls.__name__ - detail_uri = "/".join(["api", subsystem, cls._api_version, endpoint.strip("/"), pk]) + if dockercloud.namespace: + detail_uri = "/".join(["api", subsystem, cls._api_version, dockercloud.namespace, endpoint.strip("/"), pk]) + else: + detail_uri = "/".join(["api", subsystem, cls._api_version, endpoint.strip("/"), pk]) json = send_request('GET', detail_uri) if json: instance = cls() @@ -141,7 +148,10 @@ class Immutable(Restful): assert endpoint, "Endpoint not specified for %s" % cls.__name__ assert subsystem, "Subsystem not specified for %s" % cls.__name__ - detail_uri = "/".join(["api", subsystem, cls._api_version, endpoint.strip("/")]) + if dockercloud.namespace: + detail_uri = "/".join(["api", subsystem, cls._api_version, dockercloud.namespace, endpoint.strip("/")]) + else: + detail_uri = "/".join(["api", subsystem, cls._api_version, endpoint.strip("/")]) objects = [] while True: if limit and len(objects) >= limit: @@ -219,7 +229,10 @@ class Mutable(Immutable): # Figure out whether we should do a create or update if not self._detail_uri: action = "POST" - path = "/".join(["api", subsystem, self._api_version, endpoint.lstrip("/")]) + if dockercloud.namespace: + path = "/".join(["api", subsystem, self._api_version, dockercloud.namespace, endpoint.lstrip("/")]) + else: + path = "/".join(["api", subsystem, self._api_version, endpoint.lstrip("/")]) else: action = "PATCH" path = self._detail_uri @@ -316,7 +329,12 @@ class StreamingLog(StreamingAPI): endpoint = "%s/%s/logs/?follow=%s" % (resource, uuid, str(follow).lower()) if tail: endpoint = "%s&tail=%d" % (endpoint, tail) - url = "/".join([dockercloud.stream_host.rstrip("/"), "api", subsystem, self._api_version, endpoint.lstrip("/")]) + if dockercloud.namespace: + url = "/".join([dockercloud.stream_host.rstrip("/"), "api", subsystem, self._api_version, + dockercloud.namespace, endpoint.lstrip("/")]) + else: + url = "/".join([dockercloud.stream_host.rstrip("/"), "api", subsystem, self._api_version, + endpoint.lstrip("/")]) super(self.__class__, self).__init__(url) @staticmethod @@ -335,7 +353,11 @@ class StreamingLog(StreamingAPI): class Exec(StreamingAPI): def __init__(self, uuid, cmd='sh'): endpoint = "container/%s/exec/?command=%s" % (uuid, urllib.quote_plus(cmd)) - url = "/".join([dockercloud.stream_host.rstrip("/"), "api", "app", self._api_version, endpoint.lstrip("/")]) + if dockercloud.namespace: + url = "/".join([dockercloud.stream_host.rstrip("/"), "api", "app", self._api_version, + dockercloud.namespace, endpoint.lstrip("/")]) + else: + url = "/".join([dockercloud.stream_host.rstrip("/"), "api", "app", self._api_version, endpoint.lstrip("/")]) super(self.__class__, self).__init__(url) @staticmethod diff --git a/dockercloud/api/events.py b/dockercloud/api/events.py index f84e4aa..06f989a 100644 --- a/dockercloud/api/events.py +++ b/dockercloud/api/events.py @@ -15,7 +15,12 @@ logger = logging.getLogger("python-dockercloud") class Events(StreamingAPI): def __init__(self): endpoint = "events" - url = "/".join([dockercloud.stream_host.rstrip("/"), "api", "audit", self._api_version, endpoint.lstrip("/")]) + if dockercloud.namespace: + url = "/".join([dockercloud.stream_host.rstrip("/"), "api", "audit", self._api_version, + dockercloud.namespace, endpoint.lstrip("/")]) + else: + url = "/".join([dockercloud.stream_host.rstrip("/"), "api", "audit", self._api_version, + endpoint.lstrip("/")]) self.invaid_auth_headers = set() self.auth_error = "" super(self.__class__, self).__init__(url) From 655fcce56abd0d3f0da9b52e911636d931157443 Mon Sep 17 00:00:00 2001 From: tifayuki Date: Fri, 10 Jun 2016 11:22:07 +0200 Subject: [PATCH 10/19] bump version --- dockercloud/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dockercloud/__init__.py b/dockercloud/__init__.py index e970478..12bb373 100644 --- a/dockercloud/__init__.py +++ b/dockercloud/__init__.py @@ -25,7 +25,7 @@ from dockercloud.api.utils import Utils from dockercloud.api.events import Events from dockercloud.api.nodeaz import AZ -__version__ = '1.0.5' +__version__ = '1.0.6' dockercloud_auth = os.environ.get('DOCKERCLOUD_AUTH') basic_auth = auth.load_from_file("~/.docker/config.json") From 8b79e771d5dadccc093190d1ac839ca2343c663a Mon Sep 17 00:00:00 2001 From: tifayuki Date: Fri, 10 Jun 2016 16:51:14 +0200 Subject: [PATCH 11/19] Update Readme --- README.md | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/README.md b/README.md index 5a82ce2..aea721e 100644 --- a/README.md +++ b/README.md @@ -29,6 +29,19 @@ The authentication can be configured in the following ways: export DOCKERCLOUD_USER=username export DOCKERCLOUD_APIKEY=apikey +## Namespace + +To support teams and orgs, you can specify the namespace in the following ways: + +* Set it in the Python code: + + import dockercloud + dockercloud.namespace = "yourteam" + +* Set it in the environment variable: + + export DOCKERCLOUD_NAMESPACE=yourteam + ## Errors Errors in the HTTP API will be returned with status codes in the 4xx and 5xx ranges. From ef94b85a16eec02f6ff70a94d1d7d7f99e970a16 Mon Sep 17 00:00:00 2001 From: tifayuki Date: Fri, 10 Jun 2016 19:33:14 +0200 Subject: [PATCH 12/19] exclude az,nodetype,provider,region from namespace --- dockercloud/api/base.py | 9 +++++---- dockercloud/api/nodeaz.py | 1 + dockercloud/api/nodeprovider.py | 1 + dockercloud/api/noderegion.py | 1 + dockercloud/api/nodetype.py | 1 + 5 files changed, 9 insertions(+), 4 deletions(-) diff --git a/dockercloud/api/base.py b/dockercloud/api/base.py index afe32fd..e9feeed 100644 --- a/dockercloud/api/base.py +++ b/dockercloud/api/base.py @@ -22,6 +22,7 @@ class BasicObject(object): class Restful(BasicObject): _detail_uri = None + namespaced = True def __init__(self, **kwargs): """Simply reflect all the values in kwargs""" @@ -58,7 +59,7 @@ class Restful(BasicObject): assert subsystem, "Subsystem not specified for %s" % self.__class__.__name__ for k, v in list(dict.items()): setattr(self, k, v) - if dockercloud.namespace: + if self.namespaced and dockercloud.namespace: self._detail_uri = "/".join(["api", subsystem, self._api_version, dockercloud.namespace, endpoint.strip("/"), self.pk]) else: @@ -130,7 +131,7 @@ class Immutable(Restful): subsystem = getattr(cls, 'subsystem', None) assert endpoint, "Endpoint not specified for %s" % cls.__name__ assert subsystem, "Subsystem not specified for %s" % cls.__name__ - if dockercloud.namespace: + if cls.namespaced and dockercloud.namespace: detail_uri = "/".join(["api", subsystem, cls._api_version, dockercloud.namespace, endpoint.strip("/"), pk]) else: detail_uri = "/".join(["api", subsystem, cls._api_version, endpoint.strip("/"), pk]) @@ -148,7 +149,7 @@ class Immutable(Restful): assert endpoint, "Endpoint not specified for %s" % cls.__name__ assert subsystem, "Subsystem not specified for %s" % cls.__name__ - if dockercloud.namespace: + if cls.namespaced and dockercloud.namespace: detail_uri = "/".join(["api", subsystem, cls._api_version, dockercloud.namespace, endpoint.strip("/")]) else: detail_uri = "/".join(["api", subsystem, cls._api_version, endpoint.strip("/")]) @@ -229,7 +230,7 @@ class Mutable(Immutable): # Figure out whether we should do a create or update if not self._detail_uri: action = "POST" - if dockercloud.namespace: + if cls.namespaced and dockercloud.namespace: path = "/".join(["api", subsystem, self._api_version, dockercloud.namespace, endpoint.lstrip("/")]) else: path = "/".join(["api", subsystem, self._api_version, endpoint.lstrip("/")]) diff --git a/dockercloud/api/nodeaz.py b/dockercloud/api/nodeaz.py index abc4d5f..25a153b 100644 --- a/dockercloud/api/nodeaz.py +++ b/dockercloud/api/nodeaz.py @@ -6,6 +6,7 @@ from .base import Immutable class AZ(Immutable): subsystem = "infra" endpoint = "/az" + namespaced = False @classmethod def _pk_key(cls): diff --git a/dockercloud/api/nodeprovider.py b/dockercloud/api/nodeprovider.py index 3d31f06..168bc72 100644 --- a/dockercloud/api/nodeprovider.py +++ b/dockercloud/api/nodeprovider.py @@ -6,6 +6,7 @@ from .base import Immutable class Provider(Immutable): subsystem = "infra" endpoint = "/provider" + namespaced = False @classmethod def _pk_key(cls): diff --git a/dockercloud/api/noderegion.py b/dockercloud/api/noderegion.py index 4d10bed..9a9c97a 100644 --- a/dockercloud/api/noderegion.py +++ b/dockercloud/api/noderegion.py @@ -6,6 +6,7 @@ from .base import Immutable class Region(Immutable): subsystem = "infra" endpoint = "/region" + namespaced = False @classmethod def _pk_key(cls): diff --git a/dockercloud/api/nodetype.py b/dockercloud/api/nodetype.py index 5274aaf..7416d63 100644 --- a/dockercloud/api/nodetype.py +++ b/dockercloud/api/nodetype.py @@ -6,6 +6,7 @@ from .base import Immutable class NodeType(Immutable): subsystem = "infra" endpoint = "/nodetype" + namespaced = False @classmethod def _pk_key(cls): From 9f484a8a59a044cccd58d05a091cfb260c4d3ef3 Mon Sep 17 00:00:00 2001 From: tifayuki Date: Fri, 17 Jun 2016 17:58:30 +0200 Subject: [PATCH 13/19] remove namespace from action endpoints --- dockercloud/api/action.py | 1 + 1 file changed, 1 insertion(+) diff --git a/dockercloud/api/action.py b/dockercloud/api/action.py index 71a3ed8..214827f 100644 --- a/dockercloud/api/action.py +++ b/dockercloud/api/action.py @@ -6,6 +6,7 @@ from .base import Immutable, StreamingLog class Action(Immutable): subsystem = 'audit' endpoint = "/action" + namespaced = False @classmethod def _pk_key(cls): From afe9a7dad5e9f23ba55ffa531f303c64107a0e5b Mon Sep 17 00:00:00 2001 From: tifayuki Date: Fri, 17 Jun 2016 18:01:53 +0200 Subject: [PATCH 14/19] bump version --- dockercloud/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dockercloud/__init__.py b/dockercloud/__init__.py index 12bb373..087fa14 100644 --- a/dockercloud/__init__.py +++ b/dockercloud/__init__.py @@ -25,7 +25,7 @@ from dockercloud.api.utils import Utils from dockercloud.api.events import Events from dockercloud.api.nodeaz import AZ -__version__ = '1.0.6' +__version__ = '1.0.7' dockercloud_auth = os.environ.get('DOCKERCLOUD_AUTH') basic_auth = auth.load_from_file("~/.docker/config.json") From f5c85f96f140343db7c163da312b56e952022c37 Mon Sep 17 00:00:00 2001 From: tifayuki Date: Thu, 4 Aug 2016 15:42:11 +0200 Subject: [PATCH 15/19] fix test --- Dockerfile | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Dockerfile b/Dockerfile index d20508e..1794fc6 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,5 +1,7 @@ FROM python:2.7.11-alpine +RUN apk update && apk add ca-certificates + ADD . /sdk WORKDIR sdk RUN python setup.py install From bc67aa43cbb5134f76cdca8416ae3e2b3f61e620 Mon Sep 17 00:00:00 2001 From: tifayuki Date: Thu, 4 Aug 2016 13:58:29 +0200 Subject: [PATCH 16/19] add support for credential-helpers --- dockercloud/api/auth.py | 26 +++++++++++++++++++++++--- 1 file changed, 23 insertions(+), 3 deletions(-) diff --git a/dockercloud/api/auth.py b/dockercloud/api/auth.py index 5c44e9c..65d6668 100644 --- a/dockercloud/api/auth.py +++ b/dockercloud/api/auth.py @@ -3,12 +3,14 @@ from __future__ import absolute_import import base64 import json import os +import subprocess from requests.auth import HTTPBasicAuth import dockercloud from .http import send_request +HUB_INDEX = "https://index.docker.io/v1/" def authenticate(username, password): verify_credential(username, password) @@ -43,11 +45,29 @@ def load_from_file(f="~/.docker/config.json"): try: with open(os.path.expanduser(f)) as config_file: data = json.load(config_file) - - return data.get("auths", {}).get("https://index.docker.io/v1/", {}).get("auth", None) - except Exception: + except: return None + creds_store = data.get("credsStore", None) + if creds_store: + try: + cmd = "docker-credential-" + creds_store + p = subprocess.Popen([cmd, 'get'], stdout=subprocess.PIPE, stdin=subprocess.PIPE, stderr=subprocess.STDOUT) + out = p.communicate(input=HUB_INDEX)[0] + except: + raise dockercloud.AuthError('error getting credentials - err: exec: "%s": executable file not found in $PATH, out: ``' % cmd) + + try: + credential = json.loads(out) + username = credential.get("Username") + password = credential.get("Secret") + return base64.b64encode("%s:%s" % (username, password)) + except: + return None + + else: + return data.get("auths", {}).get(HUB_INDEX, {}).get("auth", None) + def get_auth_header(): try: From 06988c332b9fd4cf2413ad7cb90b5108fb7c5cde Mon Sep 17 00:00:00 2001 From: tifayuki Date: Fri, 5 Aug 2016 12:40:19 +0200 Subject: [PATCH 17/19] bump ver --- dockercloud/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dockercloud/__init__.py b/dockercloud/__init__.py index 087fa14..85a5a80 100644 --- a/dockercloud/__init__.py +++ b/dockercloud/__init__.py @@ -25,7 +25,7 @@ from dockercloud.api.utils import Utils from dockercloud.api.events import Events from dockercloud.api.nodeaz import AZ -__version__ = '1.0.7' +__version__ = '1.0.8' dockercloud_auth = os.environ.get('DOCKERCLOUD_AUTH') basic_auth = auth.load_from_file("~/.docker/config.json") From f78ac3a39909aec3c69419aadf82c1b528c4ee92 Mon Sep 17 00:00:00 2001 From: tifayuki Date: Tue, 29 Nov 2016 14:49:51 -0800 Subject: [PATCH 18/19] not permanently cache invalid auth header --- dockercloud/api/base.py | 4 ---- dockercloud/api/events.py | 11 +++++------ 2 files changed, 5 insertions(+), 10 deletions(-) diff --git a/dockercloud/api/base.py b/dockercloud/api/base.py index e9feeed..f889f69 100644 --- a/dockercloud/api/base.py +++ b/dockercloud/api/base.py @@ -267,11 +267,7 @@ class Triggerable(BasicObject): class StreamingAPI(BasicObject): def __init__(self, url): - self._ws_init(url) - - def _ws_init(self, url): self.url = url - user_agent = 'python-dockercloud/%s' % dockercloud.__version__ if dockercloud.user_agent: user_agent = "%s %s" % (dockercloud.user_agent, user_agent) diff --git a/dockercloud/api/events.py b/dockercloud/api/events.py index 06f989a..4440f72 100644 --- a/dockercloud/api/events.py +++ b/dockercloud/api/events.py @@ -21,8 +21,6 @@ class Events(StreamingAPI): else: url = "/".join([dockercloud.stream_host.rstrip("/"), "api", "audit", self._api_version, endpoint.lstrip("/")]) - self.invaid_auth_headers = set() - self.auth_error = "" super(self.__class__, self).__init__(url) def _on_message(self, ws, message): @@ -39,15 +37,16 @@ class Events(StreamingAPI): def _on_error(self, ws, e): if isinstance(e, websocket._exceptions.WebSocketBadStatusException) and getattr(e, "status_code") == 401: - self.auth_error = "Not Authorized" - self.invaid_auth_headers.add(str(dockercloud.auth.get_auth_header())) + self.auth_error = True super(self.__class__, self)._on_error(ws, e) def run_forever(self, *args, **kwargs): while True: - if str(dockercloud.auth.get_auth_header()) in self.invaid_auth_headers: - raise AuthError(self.auth_error) + if self.auth_error: + self.auth_error = False + raise AuthError("Not Authorized") + ws = websocket.WebSocketApp(self.url, header=self.header, on_open=self._on_open, on_message=self._on_message, From e9658aae73be9e8d8a23e2d88f2da551cb293d41 Mon Sep 17 00:00:00 2001 From: tifayuki Date: Thu, 1 Dec 2016 13:51:32 -0800 Subject: [PATCH 19/19] bump version --- dockercloud/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dockercloud/__init__.py b/dockercloud/__init__.py index 85a5a80..8bb6aa5 100644 --- a/dockercloud/__init__.py +++ b/dockercloud/__init__.py @@ -25,7 +25,7 @@ from dockercloud.api.utils import Utils from dockercloud.api.events import Events from dockercloud.api.nodeaz import AZ -__version__ = '1.0.8' +__version__ = '1.0.9' dockercloud_auth = os.environ.get('DOCKERCLOUD_AUTH') basic_auth = auth.load_from_file("~/.docker/config.json")