<pre style='margin:0'>
Rainer Müller (raimue) pushed a commit to branch master
in repository macports-infrastructure.

</pre>
<p><a href="https://github.com/macports/macports-infrastructure/commit/02f5a137ebdbe62583e76814865b3c52f9090b81">https://github.com/macports/macports-infrastructure/commit/02f5a137ebdbe62583e76814865b3c52f9090b81</a></p>
<pre style="white-space: pre; background: #F8F8F8">The following commit(s) were added to refs/heads/master by this push:
<span style='display:block; white-space:pre;color:#404040;'>     new 02f5a13  buildbot: Automatically purge CDN after deploying
</span>02f5a13 is described below

<span style='display:block; white-space:pre;color:#808000;'>commit 02f5a137ebdbe62583e76814865b3c52f9090b81
</span>Author: Rainer Müller <raimue@macports.org>
AuthorDate: Thu Oct 19 19:39:15 2017 +0200

<span style='display:block; white-space:pre;color:#404040;'>    buildbot: Automatically purge CDN after deploying
</span><span style='display:block; white-space:pre;color:#404040;'>    
</span><span style='display:block; white-space:pre;color:#404040;'>    If credentials are configured in config.json, CDNPurgeStep will run
</span><span style='display:block; white-space:pre;color:#404040;'>    a shell command to purge the caches of the modified zone on MaxCDN. The
</span><span style='display:block; white-space:pre;color:#404040;'>    shell command is executed directly on the master as it does not need any
</span><span style='display:block; white-space:pre;color:#404040;'>    build artifacts and otherwise the script and its modules would need to
</span><span style='display:block; white-space:pre;color:#404040;'>    be transferred to the buildslave first.
</span><span style='display:block; white-space:pre;color:#404040;'>    
</span><span style='display:block; white-space:pre;color:#404040;'>    maxcdn-purge.py as a little helper script which uses the MaxCDN Python
</span><span style='display:block; white-space:pre;color:#404040;'>    API module to purge the cache of a zone given by its zone ID.
</span>---
 buildbot/config.json.sample                        |  10 +-
 buildbot/master.cfg                                |  20 +++
 buildbot/maxcdn-purge/.gitignore                   |   1 +
 buildbot/maxcdn-purge/maxcdn                       |   1 +
 buildbot/maxcdn-purge/maxcdn-0.0.7/PKG-INFO        |  11 ++
 .../maxcdn-0.0.7/maxcdn.egg-info/PKG-INFO          |  11 ++
 .../maxcdn-0.0.7/maxcdn.egg-info/SOURCES.txt       |   9 ++
 .../maxcdn.egg-info/dependency_links.txt           |   1 +
 .../maxcdn-0.0.7/maxcdn.egg-info/requires.txt      |   3 +
 .../maxcdn-0.0.7/maxcdn.egg-info/top_level.txt     |   1 +
 .../maxcdn-purge/maxcdn-0.0.7/maxcdn/__init__.py   |   1 +
 .../maxcdn-purge/maxcdn-0.0.7/maxcdn/maxcdn.py     |  90 +++++++++++++
 buildbot/maxcdn-purge/maxcdn-0.0.7/setup.cfg       |   5 +
 buildbot/maxcdn-purge/maxcdn-0.0.7/setup.py        |  35 +++++
 buildbot/maxcdn-purge/maxcdn-0.0.7/test/test.py    | 145 +++++++++++++++++++++
 buildbot/maxcdn-purge/maxcdn-purge.py              |  36 +++++
 16 files changed, 378 insertions(+), 2 deletions(-)

<span style='display:block; white-space:pre;color:#808080;'>diff --git a/buildbot/config.json.sample b/buildbot/config.json.sample
</span><span style='display:block; white-space:pre;color:#808080;'>index 9907348..68ad0de 100644
</span><span style='display:block; white-space:pre;background:#e0e0ff;'>--- a/buildbot/config.json.sample
</span><span style='display:block; white-space:pre;background:#e0e0ff;'>+++ b/buildbot/config.json.sample
</span><span style='display:block; white-space:pre;background:#e0e0e0;'>@@ -5,12 +5,17 @@
</span>     "slaveprefix":  "/opt/local",
     "toolsprefix":  "/opt/mports",
     "deploy": {
<span style='display:block; white-space:pre;background:#e0ffe0;'>+        "maxcdn": {
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+            "key": "",
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+            "secret": ""
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+        },
</span>         "www": {
             "host": "",
             "user": "",
             "sshkeyfile": "",
             "sshknownhostsfile": "ssh_known_hosts",
<span style='display:block; white-space:pre;background:#ffe0e0;'>-            "destpath": ""
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+            "destpath": "",
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+            "maxcdnzoneid": 0
</span>         },
         "portindex": {
             "host": "",
<span style='display:block; white-space:pre;background:#e0e0e0;'>@@ -23,7 +28,8 @@
</span>             "user": "",
             "sshkeyfile": "",
             "sshknownhostsfile": "ssh_known_hosts",
<span style='display:block; white-space:pre;background:#ffe0e0;'>-            "destpath": ""
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+            "destpath": "",
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+            "maxcdnzoneid": 0
</span>         },
         "man": {
             "host": "",
<span style='display:block; white-space:pre;color:#808080;'>diff --git a/buildbot/master.cfg b/buildbot/master.cfg
</span><span style='display:block; white-space:pre;color:#808080;'>index 8f8847a..de7633b 100644
</span><span style='display:block; white-space:pre;background:#e0e0ff;'>--- a/buildbot/master.cfg
</span><span style='display:block; white-space:pre;background:#e0e0ff;'>+++ b/buildbot/master.cfg
</span><span style='display:block; white-space:pre;background:#e0e0e0;'>@@ -593,6 +593,22 @@ def make_ssh_deploy_step(sshkeyfile, sshknownhostsfile, step):
</span>             mode=0600),
         step)
 
<span style='display:block; white-space:pre;background:#e0ffe0;'>+
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+class CDNPurgeStep(steps.MasterShellCommand):
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+    # XXX: Ensure py*-requests-oauthlib is available
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+    def __init__(self, zoneid, **kwargs):
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+        super(CDNPurgeStep, self).__init__(
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+                name='cdn',
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+                description='purging',
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+                descriptionDone='purged',
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+                command="./maxcdn-purge/maxcdn-purge.py %s" % (zoneid,),
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+                env={'MAXCDN_KEY': config['deploy']['maxcdn']['key'],
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+                     'MAXCDN_SECRET': config['deploy']['maxcdn']['secret'],
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+                     'PATH': "${PATH}"},
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+                **kwargs)
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+
</span> if 'www' in config['deploy']:
     jobs_www_factory = util.BuildFactory()
     # TODO: incremental mode with cleanup?
<span style='display:block; white-space:pre;background:#e0e0e0;'>@@ -618,6 +634,8 @@ if 'www' in config['deploy']:
</span>                 user=config['deploy']['www']['user'],
                 srcpath='www/',
                 destpath=config['deploy']['www']['destpath'])))
<span style='display:block; white-space:pre;background:#e0ffe0;'>+    if 'maxcdn' in config['deploy'] and 'maxcdnzoneid' in config['deploy']['www']:
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+        jobs_www_factory.addStep(CDNPurgeStep(zoneid=config['deploy']['www']['maxcdnzoneid']))
</span> 
 if 'portindex' in config['deploy']:
     jobs_portindex_factory = util.BuildFactory()
<span style='display:block; white-space:pre;background:#e0e0e0;'>@@ -702,6 +720,8 @@ if 'guide' in config['deploy']:
</span>                 user=config['deploy']['guide']['user'],
                 srcpath='guide/guide/html/',
                 destpath=config['deploy']['guide']['destpath'])))
<span style='display:block; white-space:pre;background:#e0ffe0;'>+    if 'maxcdn' in config['deploy'] and 'maxcdnzoneid' in config['deploy']['guide']:
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+        jobs_guide_factory.addStep(CDNPurgeStep(zoneid=config['deploy']['guide']['maxcdnzoneid']))
</span> 
 if 'man' in config['deploy']:
     jobs_man_factory = util.BuildFactory()
<span style='display:block; white-space:pre;color:#808080;'>diff --git a/buildbot/maxcdn-purge/.gitignore b/buildbot/maxcdn-purge/.gitignore
</span>new file mode 100644
<span style='display:block; white-space:pre;color:#808080;'>index 0000000..0d20b64
</span><span style='display:block; white-space:pre;background:#ffe0e0;'>--- /dev/null
</span><span style='display:block; white-space:pre;background:#e0e0ff;'>+++ b/buildbot/maxcdn-purge/.gitignore
</span><span style='display:block; white-space:pre;background:#e0e0e0;'>@@ -0,0 +1 @@
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+*.pyc
</span><span style='display:block; white-space:pre;color:#808080;'>diff --git a/buildbot/maxcdn-purge/maxcdn b/buildbot/maxcdn-purge/maxcdn
</span>new file mode 120000
<span style='display:block; white-space:pre;color:#808080;'>index 0000000..b1214ca
</span><span style='display:block; white-space:pre;background:#ffe0e0;'>--- /dev/null
</span><span style='display:block; white-space:pre;background:#e0e0ff;'>+++ b/buildbot/maxcdn-purge/maxcdn
</span><span style='display:block; white-space:pre;background:#e0e0e0;'>@@ -0,0 +1 @@
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+maxcdn-0.0.7/maxcdn
</span>\ No newline at end of file
<span style='display:block; white-space:pre;color:#808080;'>diff --git a/buildbot/maxcdn-purge/maxcdn-0.0.7/PKG-INFO b/buildbot/maxcdn-purge/maxcdn-0.0.7/PKG-INFO
</span>new file mode 100644
<span style='display:block; white-space:pre;color:#808080;'>index 0000000..77ac22e
</span><span style='display:block; white-space:pre;background:#ffe0e0;'>--- /dev/null
</span><span style='display:block; white-space:pre;background:#e0e0ff;'>+++ b/buildbot/maxcdn-purge/maxcdn-0.0.7/PKG-INFO
</span><span style='display:block; white-space:pre;background:#e0e0e0;'>@@ -0,0 +1,11 @@
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+Metadata-Version: 1.0
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+Name: maxcdn
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+Version: 0.0.7
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+Summary: A Python REST Client for MaxCDN REST Web Services
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+Home-page: http://github.com/maxcdn/python-maxcdn
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+Author: Joshua P. Mervine
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+Author-email: joshua@mervine.net
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+License: MIT
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+Description: UNKNOWN
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+Keywords: MaxCDN CDN API REST
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+Platform: UNKNOWN
</span><span style='display:block; white-space:pre;color:#808080;'>diff --git a/buildbot/maxcdn-purge/maxcdn-0.0.7/maxcdn.egg-info/PKG-INFO b/buildbot/maxcdn-purge/maxcdn-0.0.7/maxcdn.egg-info/PKG-INFO
</span>new file mode 100644
<span style='display:block; white-space:pre;color:#808080;'>index 0000000..77ac22e
</span><span style='display:block; white-space:pre;background:#ffe0e0;'>--- /dev/null
</span><span style='display:block; white-space:pre;background:#e0e0ff;'>+++ b/buildbot/maxcdn-purge/maxcdn-0.0.7/maxcdn.egg-info/PKG-INFO
</span><span style='display:block; white-space:pre;background:#e0e0e0;'>@@ -0,0 +1,11 @@
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+Metadata-Version: 1.0
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+Name: maxcdn
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+Version: 0.0.7
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+Summary: A Python REST Client for MaxCDN REST Web Services
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+Home-page: http://github.com/maxcdn/python-maxcdn
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+Author: Joshua P. Mervine
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+Author-email: joshua@mervine.net
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+License: MIT
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+Description: UNKNOWN
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+Keywords: MaxCDN CDN API REST
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+Platform: UNKNOWN
</span><span style='display:block; white-space:pre;color:#808080;'>diff --git a/buildbot/maxcdn-purge/maxcdn-0.0.7/maxcdn.egg-info/SOURCES.txt b/buildbot/maxcdn-purge/maxcdn-0.0.7/maxcdn.egg-info/SOURCES.txt
</span>new file mode 100644
<span style='display:block; white-space:pre;color:#808080;'>index 0000000..9305979
</span><span style='display:block; white-space:pre;background:#ffe0e0;'>--- /dev/null
</span><span style='display:block; white-space:pre;background:#e0e0ff;'>+++ b/buildbot/maxcdn-purge/maxcdn-0.0.7/maxcdn.egg-info/SOURCES.txt
</span><span style='display:block; white-space:pre;background:#e0e0e0;'>@@ -0,0 +1,9 @@
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+setup.py
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+maxcdn/__init__.py
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+maxcdn/maxcdn.py
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+maxcdn.egg-info/PKG-INFO
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+maxcdn.egg-info/SOURCES.txt
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+maxcdn.egg-info/dependency_links.txt
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+maxcdn.egg-info/requires.txt
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+maxcdn.egg-info/top_level.txt
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+test/test.py
</span>\ No newline at end of file
<span style='display:block; white-space:pre;color:#808080;'>diff --git a/buildbot/maxcdn-purge/maxcdn-0.0.7/maxcdn.egg-info/dependency_links.txt b/buildbot/maxcdn-purge/maxcdn-0.0.7/maxcdn.egg-info/dependency_links.txt
</span>new file mode 100644
<span style='display:block; white-space:pre;color:#808080;'>index 0000000..8b13789
</span><span style='display:block; white-space:pre;background:#ffe0e0;'>--- /dev/null
</span><span style='display:block; white-space:pre;background:#e0e0ff;'>+++ b/buildbot/maxcdn-purge/maxcdn-0.0.7/maxcdn.egg-info/dependency_links.txt
</span><span style='display:block; white-space:pre;background:#e0e0e0;'>@@ -0,0 +1 @@
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+
</span><span style='display:block; white-space:pre;color:#808080;'>diff --git a/buildbot/maxcdn-purge/maxcdn-0.0.7/maxcdn.egg-info/requires.txt b/buildbot/maxcdn-purge/maxcdn-0.0.7/maxcdn.egg-info/requires.txt
</span>new file mode 100644
<span style='display:block; white-space:pre;color:#808080;'>index 0000000..5f8af11
</span><span style='display:block; white-space:pre;background:#ffe0e0;'>--- /dev/null
</span><span style='display:block; white-space:pre;background:#e0e0ff;'>+++ b/buildbot/maxcdn-purge/maxcdn-0.0.7/maxcdn.egg-info/requires.txt
</span><span style='display:block; white-space:pre;background:#e0e0e0;'>@@ -0,0 +1,3 @@
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+requests
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+requests_oauthlib
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+certifi
</span>\ No newline at end of file
<span style='display:block; white-space:pre;color:#808080;'>diff --git a/buildbot/maxcdn-purge/maxcdn-0.0.7/maxcdn.egg-info/top_level.txt b/buildbot/maxcdn-purge/maxcdn-0.0.7/maxcdn.egg-info/top_level.txt
</span>new file mode 100644
<span style='display:block; white-space:pre;color:#808080;'>index 0000000..2b0bda9
</span><span style='display:block; white-space:pre;background:#ffe0e0;'>--- /dev/null
</span><span style='display:block; white-space:pre;background:#e0e0ff;'>+++ b/buildbot/maxcdn-purge/maxcdn-0.0.7/maxcdn.egg-info/top_level.txt
</span><span style='display:block; white-space:pre;background:#e0e0e0;'>@@ -0,0 +1 @@
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+maxcdn
</span><span style='display:block; white-space:pre;color:#808080;'>diff --git a/buildbot/maxcdn-purge/maxcdn-0.0.7/maxcdn/__init__.py b/buildbot/maxcdn-purge/maxcdn-0.0.7/maxcdn/__init__.py
</span>new file mode 100644
<span style='display:block; white-space:pre;color:#808080;'>index 0000000..91632b4
</span><span style='display:block; white-space:pre;background:#ffe0e0;'>--- /dev/null
</span><span style='display:block; white-space:pre;background:#e0e0ff;'>+++ b/buildbot/maxcdn-purge/maxcdn-0.0.7/maxcdn/__init__.py
</span><span style='display:block; white-space:pre;background:#e0e0e0;'>@@ -0,0 +1 @@
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+from .maxcdn import MaxCDN
</span><span style='display:block; white-space:pre;color:#808080;'>diff --git a/buildbot/maxcdn-purge/maxcdn-0.0.7/maxcdn/maxcdn.py b/buildbot/maxcdn-purge/maxcdn-0.0.7/maxcdn/maxcdn.py
</span>new file mode 100644
<span style='display:block; white-space:pre;color:#808080;'>index 0000000..957f7c2
</span><span style='display:block; white-space:pre;background:#ffe0e0;'>--- /dev/null
</span><span style='display:block; white-space:pre;background:#e0e0ff;'>+++ b/buildbot/maxcdn-purge/maxcdn-0.0.7/maxcdn/maxcdn.py
</span><span style='display:block; white-space:pre;background:#e0e0e0;'>@@ -0,0 +1,90 @@
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+from requests_oauthlib import OAuth1Session as OAuth1
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+# handle python 3.x
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+try:
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+    import urlparse
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+except ImportError:
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+    from urllib import parse as urlparse
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+class MaxCDN(object):
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+    def __init__(self, alias, key, secret, server="rws.maxcdn.com", **kwargs):
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+        self.url = "https://%s/%s" % (server, alias)
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+        self.client = OAuth1(key, client_secret=secret, **kwargs)
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+    def _get_headers(self, json=True):
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+        headers = {"User-Agent": "Python MaxCDN API Client"}
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+        if json:
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+            headers["Content-Type"] = "application/json"
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+        return headers
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+    def _get_url(self, end_point):
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+        if not end_point.startswith("/"):
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+            return "{0}/{1}".format(self.url, end_point)
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+        else:
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+            return self.url + end_point
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+    def _parse_json(self, response):
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+        try:
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+            return response.json()
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+        except ValueError as e:
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+            raise self.ServerError(response, str(e))
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+    def _data_request(self, method, end_point, data, **kwargs):
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+        if data is None and "params" in kwargs:
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+            params = kwargs.pop("params")
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+            if type(params) is str:
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+                params = urlparse.parse_qs(params)
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+            data = params
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+        action = getattr(self.client, method)
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+        response = action(self._get_url(end_point), data=data,
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+                          headers=self._get_headers(json=True), **kwargs)
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+        if (response.status_code > 299):
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+            raise self.ServerError(response)
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+        return self._parse_json(response)
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+    def get(self, end_point, data=None, **kwargs):
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+        return self._data_request("get", end_point, data=data, **kwargs)
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+    def patch(self, end_point, data=None, **kwargs):
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+        return self._data_request("post", end_point, data=data, **kwargs)
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+    def post(self, end_point, data=None, **kwargs):
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+        return self._data_request("post", end_point, data=data, **kwargs)
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+    def put(self, end_point, data=None, **kwargs):
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+        return self._data_request("put", end_point, data=data, **kwargs)
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+    def delete(self, end_point, data=None, **kwargs):
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+        return self._data_request("delete", end_point, data=data, **kwargs)
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+    def purge(self, zoneid, file_or_files=None, **kwargs):
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+        path = "/zones/pull.json/%s/cache" % (zoneid)
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+        if file_or_files is not None:
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+            return self.delete(path, data={"files": file_or_files}, **kwargs)
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+        return self.delete(path, **kwargs)
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+    class ServerError(Exception):
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+        def __init__(self, response, message=None):
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+            try:
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+                resp = response.json()
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+                if message is None:
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+                    message = "{0}:: {1}".format(resp['error']['type'],
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+                                                 resp['error']['message'])
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+                self.reason = resp['error']['type']
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+            except ValueError:
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+                if message is None:
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+                    message = "{0} {1} from {2}".format(response.status_code,
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+                                                        response.reason,
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+                                                        response.url)
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+                self.reason = response.reason
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+            self.headers = response.headers
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+            self.code = response.status_code
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+            self.body = response._content
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+            self.url = response.url
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+            super(Exception, self).__init__(message)
</span><span style='display:block; white-space:pre;color:#808080;'>diff --git a/buildbot/maxcdn-purge/maxcdn-0.0.7/setup.cfg b/buildbot/maxcdn-purge/maxcdn-0.0.7/setup.cfg
</span>new file mode 100644
<span style='display:block; white-space:pre;color:#808080;'>index 0000000..861a9f5
</span><span style='display:block; white-space:pre;background:#ffe0e0;'>--- /dev/null
</span><span style='display:block; white-space:pre;background:#e0e0ff;'>+++ b/buildbot/maxcdn-purge/maxcdn-0.0.7/setup.cfg
</span><span style='display:block; white-space:pre;background:#e0e0e0;'>@@ -0,0 +1,5 @@
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+[egg_info]
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+tag_build = 
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+tag_date = 0
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+tag_svn_revision = 0
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+
</span><span style='display:block; white-space:pre;color:#808080;'>diff --git a/buildbot/maxcdn-purge/maxcdn-0.0.7/setup.py b/buildbot/maxcdn-purge/maxcdn-0.0.7/setup.py
</span>new file mode 100755
<span style='display:block; white-space:pre;color:#808080;'>index 0000000..f3eb6ed
</span><span style='display:block; white-space:pre;background:#ffe0e0;'>--- /dev/null
</span><span style='display:block; white-space:pre;background:#e0e0ff;'>+++ b/buildbot/maxcdn-purge/maxcdn-0.0.7/setup.py
</span><span style='display:block; white-space:pre;background:#e0e0e0;'>@@ -0,0 +1,35 @@
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+#!/usr/bin/env python
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+# -*- coding: utf-8 -*-
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+options = {
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+    "name":         "maxcdn",
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+    "version":      "0.0.7",
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+    "description":  "A Python REST Client for MaxCDN REST Web Services",
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+    "author":       "Joshua P. Mervine",
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+    "author_email": "joshua@mervine.net",
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+    "license":      "MIT",
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+    "keywords":     "MaxCDN CDN API REST",
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+    "packages":     ['maxcdn'],
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+    "url":          'http://github.com/maxcdn/python-maxcdn'
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+}
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+install_requires = [
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+    "requests",
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+    "requests_oauthlib",
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+    "certifi"
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+]
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+tests_require = [
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+    "nose",
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+    "mock"
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+]
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+include_package_data = True
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+try:
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+    from setuptools import setup
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+    options["install_requires"] = install_requires
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+    options["include_package_data"] = include_package_data
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+    options["tests_require"] = tests_require
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+    setup(**options)
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+except ImportError:
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+    print("ERROR: setuptools wasn't found, please install it")
</span><span style='display:block; white-space:pre;color:#808080;'>diff --git a/buildbot/maxcdn-purge/maxcdn-0.0.7/test/test.py b/buildbot/maxcdn-purge/maxcdn-0.0.7/test/test.py
</span>new file mode 100644
<span style='display:block; white-space:pre;color:#808080;'>index 0000000..7b4275a
</span><span style='display:block; white-space:pre;background:#ffe0e0;'>--- /dev/null
</span><span style='display:block; white-space:pre;background:#e0e0ff;'>+++ b/buildbot/maxcdn-purge/maxcdn-0.0.7/test/test.py
</span><span style='display:block; white-space:pre;background:#e0e0e0;'>@@ -0,0 +1,145 @@
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+import mock
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+import unittest
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+import requests
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+from maxcdn import MaxCDN
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+###############################################################################
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+# Mock as needed.
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+def mock_request(method, url, *args, **kwargs):
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+    pass
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+class Response(object):
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+    def __init__(self, method, code=200, data={"foo": "bar"}):
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+        self._json = {"code": code, "method": method, "data": data}
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+        self.status_code = code
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+        self.reason = "Success"
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+        self.headers = ""
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+        self.url = "http://www.example.com/foo/bar"
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+    def json(self):
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+        return self._json
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+class ErrorResponse(object):
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+    def __init__(self, code=200, badcontent="Some bad content."):
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+        self._content = badcontent
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+        self.status_code = code
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+        self.reason = "Error Response"
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+        self.headers = ""
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+        self.url = "http://www.example.com/foo/bar"
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+    def json(self):
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+        raise ValueError("No JSON object could be decoded")
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+def response(method, **kwargs):
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+    return Response(method, **kwargs)
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+def error_response(**kwargs):
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+    return ErrorResponse(**kwargs)
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+#
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+###############################################################################
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+class MaxCDNTests(unittest.TestCase):
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+    def setUp(self):
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+        self.alias = "test_alias"
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+        self.key = "test_key"
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+        self.secret = "test_secret"
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+        self.server = "rws.example.com"
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+        self.maxcdn = MaxCDN(self.alias, self.key,
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+                             self.secret, server=self.server)
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+    def test_init(self):
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+        self.assertTrue(self.maxcdn)
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+        self.assertEqual(self.maxcdn.url,
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+                         "https://rws.example.com/test_alias")
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+    def test_get_url(self):
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+        self.assertEqual(self.maxcdn._get_url("/foo.json"),
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+                         "https://rws.example.com/test_alias/foo.json")
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+        self.assertEqual(self.maxcdn._get_url("foo.json"),
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+                         "https://rws.example.com/test_alias/foo.json")
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+    def test_data_request(self):
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+        for meth in ["post", "put", "delete"]:
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+            requests.Session.request = mock.create_autospec(mock_request,
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+                                        return_value=response(meth))
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+            data = {"foo": "bar"}
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+            rsp = self.maxcdn._data_request(meth, meth + ".json", data=data)
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+            expected = {"code": 200, "method": meth, "data": {"foo": "bar"}}
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+            self.assertEqual(rsp, expected)
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+            requests.Session.request = mock.create_autospec(mock_request,
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+                    return_value=error_response())
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+        with self.assertRaises(MaxCDN.ServerError, None):
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+                self.maxcdn._data_request(meth, meth+".json", data={"foo":"bar"})
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+    def test_get(self):
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+        requests.Session.request = mock.create_autospec(mock_request,
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+                                        return_value=response("get"))
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+        expected = {"code": 200, "method": "get", "data": {"foo": "bar"}}
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+        self.assertEqual(self.maxcdn.get("/get.json"), expected)
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+        requests.Session.request = mock.create_autospec(mock_request,
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+                return_value=error_response())
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+        with self.assertRaises(MaxCDN.ServerError, None):
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+            self.maxcdn.get("/get.json")
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+    def test_post(self):
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+        requests.Session.request = mock.create_autospec(mock_request,
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+                                        return_value=response("post"))
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+        rsp = self.maxcdn.post("/post.json", data={"foo": "bar"})
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+        expected = {"code": 200, "method": "post", "data": {"foo": "bar"}}
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+        self.assertEqual(rsp, expected)
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+        rsp = self.maxcdn.post("/post.json", params={"foo": "bar"})
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+        self.assertEqual(rsp, expected)
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+        rsp = self.maxcdn.post("/post.json", params="foo=bar")
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+        self.assertEqual(rsp, expected)
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+    def test_put(self):
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+        requests.Session.request = mock.create_autospec(mock_request,
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+                                        return_value=response("put"))
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+        expected = {"code": 200, "method": "put", "data": {"foo": "bar"}}
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+        self.assertEqual(self.maxcdn.put("/put.json"), expected)
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+    def test_delete(self):
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+        requests.Session.request = mock.create_autospec(mock_request,
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+                                        return_value=response("delete"))
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+        expected = {"code": 200, "method": "delete", "data": {"foo": "bar"}}
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+        self.assertEqual(self.maxcdn.delete("/delete.json"), expected)
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+        rsp = self.maxcdn.patch("/delete.json", file_or_files="/foo.css")
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+        self.assertEqual(rsp, expected)
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+        files = ["/foo.css", "/bar.css"]
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+        expected = {"code": 200, "method": "delete", "data": {"foo": "bar"}}
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+        rsp = self.maxcdn.patch("/delete.json", file_or_files=files)
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+        self.assertEqual(rsp, expected)
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+    def test_purge(self):
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+        requests.Session.request = mock.create_autospec(mock_request,
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+                                        return_value=response("delete"))
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+        expected = {"code": 200, "method": "delete", "data": {"foo": "bar"}}
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+        self.assertEqual(self.maxcdn.purge(12345), expected)
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+        self.assertEqual(self.maxcdn.purge(12345, "/master.css"), expected)
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+        files = ["/master.css", "/other.css"]
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+        self.assertEqual(self.maxcdn.purge(12345, files), expected)
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+if __name__ == '__main__':
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+        unittest.main()
</span><span style='display:block; white-space:pre;color:#808080;'>diff --git a/buildbot/maxcdn-purge/maxcdn-purge.py b/buildbot/maxcdn-purge/maxcdn-purge.py
</span>new file mode 100755
<span style='display:block; white-space:pre;color:#808080;'>index 0000000..ac4befd
</span><span style='display:block; white-space:pre;background:#ffe0e0;'>--- /dev/null
</span><span style='display:block; white-space:pre;background:#e0e0ff;'>+++ b/buildbot/maxcdn-purge/maxcdn-purge.py
</span><span style='display:block; white-space:pre;background:#e0e0e0;'>@@ -0,0 +1,36 @@
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+#!/usr/bin/env python
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+from __future__ import print_function
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+import sys
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+import os
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+import pprint as pp
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+from maxcdn import MaxCDN
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+if not all(k in os.environ for k in ["MAXCDN_KEY", "MAXCDN_SECRET"]):
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+    print("Error: MAXCDN_KEY or MAXCDN_SECRET not set in environment!", file=sys.stderr)
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+    sys.exit(1)
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+if len(sys.argv) != 2:
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+    print("Usage: {} <zoneid>".format(sys.argv[0]), file=sys.stderr)
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+    sys.exit(1)
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+zoneid = sys.argv[1]
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+MAXCDN_ALIAS = "macports"
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+MAXCDN_KEY = os.environ["MAXCDN_KEY"]
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+MAXCDN_SECRET = os.environ["MAXCDN_SECRET"]
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+# Initialize MaxCDN API
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+maxcdn = MaxCDN(MAXCDN_ALIAS, MAXCDN_KEY, MAXCDN_SECRET)
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+# Purge requested zone
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+res = maxcdn.purge(zoneid)
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+if not 'code' in res:
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+    print("Error: Unexpected response:", file=sys.stderr)
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+    pp.pprint(res, file=sys.stderr)
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+    sys.exit(1)
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+elif res['code'] == 200:
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+    print("Zone {} purged.".format(zoneid))
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+else:
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+    print("Purging of zone {} failed with code: " + res['code'], file=sys.stderr)
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+    sys.exit(1)
</span></pre><pre style='margin:0'>

</pre>