diff --git a/fastly/__init__.py b/fastly/__init__.py index 032b693..08e2175 100755 --- a/fastly/__init__.py +++ b/fastly/__init__.py @@ -3,40 +3,43 @@ # Author: Chris Zacharias (chris@imgix.com) # Copyright (c) 2012, Zebrafish Labs Inc. # All rights reserved. -# +# # Redistribution and use in source and binary forms, with or without # modification, are permitted provided that the following conditions are met: -# +# # Redistributions of source code must retain the above copyright notice, # this list of conditions and the following disclaimer. -# +# # Redistributions in binary form must reproduce the above copyright notice, -# this list of conditions and the following disclaimer in the documentation +# this list of conditions and the following disclaimer in the documentation # and/or other materials provided with the distribution. -# +# # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" # AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE -# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE -# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR -# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF -# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS -# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN -# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) -# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE +# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE # POSSIBILITY OF SUCH DAMAGE. +from datetime import datetime import httplib2 -import urllib -import re import json -from datetime import datetime +import re +import urllib + +from version import __version__ FASTLY_SCHEME = "https" FASTLY_HOST = "api.fastly.com" FASTLY_SESSION_REGEX = re.compile("(fastly\.session=[^;]+);") + class FastlyRoles(object): USER = "user" BILLING = "billing" @@ -51,52 +54,52 @@ class FastlyCacheSettingsAction(object): class FastlyConditionType(object): - RESPONSE="response" - CACHE="cache" - REQUEST="request" - FETCH="fetch" + RESPONSE = "response" + CACHE = "cache" + REQUEST = "request" + FETCH = "fetch" class FastlyHeaderAction(object): - SET="set" - APPEND="append" - DELETE="delete" - REGEX="regex" - REGEX_ALL="regex_repeat" + SET = "set" + APPEND = "append" + DELETE = "delete" + REGEX = "regex" + REGEX_ALL = "regex_repeat" class FastlyHeaderType(object): - RESPONSE="response" - FETCH="fetch" - CACHE="cache" - REQUEST="request" + RESPONSE = "response" + FETCH = "fetch" + CACHE = "cache" + REQUEST = "request" class FastlyRequestSettingAction(object): - LOOKUP="lookup" - PASS="pass" + LOOKUP = "lookup" + PASS = "pass" class FastlyForwardedForAction(object): - CLEAR="clear" - LEAVE="leave" - APPEND="append" - APPEND_ALL="append_all" - OVERWRITE="overwrite" + CLEAR = "clear" + LEAVE = "leave" + APPEND = "append" + APPEND_ALL = "append_all" + OVERWRITE = "overwrite" class FastlyStatsType(object): - ALL="all" - DAILY="daily" - HOURLY="hourly" - MINUTELY="minutely" + ALL = "all" + DAILY = "daily" + HOURLY = "hourly" + MINUTELY = "minutely" class FastlyDirectorType(object): - RANDOM=1 - ROUNDROBIN=2 - HASH=3 - CLIENT=4 + RANDOM = 1 + ROUNDROBIN = 2 + HASH = 3 + CLIENT = 4 class FastlyConnection(object): @@ -109,7 +112,6 @@ def __init__(self, api_key): def fully_authed(self): return self._fully_authed - def login(self, user, password): body = self._formdata({ "user": user, @@ -119,18 +121,16 @@ def login(self, user, password): self._fully_authed = True return FastlySession(self, content) - def list_backends(self, service_id, version_number): """List all backends for a particular service and version.""" - content = self._fetch("/service/%s/version/%d/backend" % (service_id, version_number)) return map(lambda x: FastlyBackend(self, x), content) - - def create_backend(self, + def create_backend( + self, service_id, - version_number, - name, + version_number, + name, address, use_ssl=False, port=80, @@ -144,7 +144,11 @@ def create_backend(self, shield=None, request_condition=None, healthcheck=None, - comment=None): + comment=None, + ssl_cert_hostname=None, + ssl_sni_hostname=None, + min_tls_version=None, + max_tls_version=None,): """Create a backend for a particular service and version.""" body = self._formdata({ "name": name, @@ -162,46 +166,45 @@ def create_backend(self, "request_condition": request_condition, "healthcheck": healthcheck, "comment": comment, + "ssl_cert_hostname": ssl_cert_hostname, + "ssl_sni_hostname": ssl_sni_hostname, + "min_tls_version": min_tls_version, + "max_tls_version": max_tls_version, }, FastlyBackend.FIELDS) content = self._fetch("/service/%s/version/%d/backend" % (service_id, version_number), method="POST", body=body) return FastlyBackend(self, content) - def get_backend(self, service_id, version_number, name): """Get the backend for a particular service and version.""" - content = self._fetch("/service/%s/version/%d/backend/%s" % (service_id, version_number, name)) + content = self._fetch("/service/%s/version/%d/backend/%s" % (service_id, version_number, urllib.quote(name, safe=''))) return FastlyBackend(self, content) - def update_backend(self, service_id, version_number, name_key, **kwargs): """Update the backend for a particular service and version.""" body = self._formdata(kwargs, FastlyBackend.FIELDS) - content = self._fetch("/service/%s/version/%d/backend/%s" % (service_id, version_number, name_key), method="PUT", body=body) + content = self._fetch("/service/%s/version/%d/backend/%s" % (service_id, version_number, urllib.quote(name_key, safe='')), method="PUT", body=body) return FastlyBackend(self, content) - def delete_backend(self, service_id, version_number, name): """Delete the backend for a particular service and version.""" - content = self._fetch("/service/%s/version/%d/backend/%s" % (service_id, version_number, name), method="DELETE") + content = self._fetch("/service/%s/version/%d/backend/%s" % (service_id, version_number, urllib.quote(name, safe='')), method="DELETE") return self._status(content) - def check_backends(self, service_id, version_number): """Performs a health check against each backend in version. If the backend has a specific type of healthcheck, that one is performed, otherwise a HEAD request to / is performed. The first item is the details on the Backend itself. The second item is details of the specific HTTP request performed as a health check. The third item is the response details.""" content = self._fetch("/service/%s/version/%d/backend/check_all" % (service_id, version_number)) # TODO: Use a strong-typed class for output? return content - def list_cache_settings(self, service_id, version_number): """Get a list of all cache settings for a particular service and version.""" content = self._fetch("/service/%s/version/%d/cache_settings" % (service_id, version_number)) return map(lambda x: FastlyCacheSettings(self, x), content) - - def create_cache_settings(self, - service_id, - version_number, + def create_cache_settings( + self, + service_id, + version_number, name, action, ttl=None, @@ -218,39 +221,35 @@ def create_cache_settings(self, content = self._fetch("/service/%s/version/%d/cache_settings" % (service_id, version_number), method="POST", body=body) return FastlyCacheSettings(self, content) - def get_cache_settings(self, service_id, version_number, name): """Get a specific cache settings object.""" - content = self._fetch("/service/%s/version/%d/cache_settings/%s" % (service_id, version_number, name)) + content = self._fetch("/service/%s/version/%d/cache_settings/%s" % (service_id, version_number, urllib.quote(name, safe=''))) return FastlyCacheSettings(self, content) - def update_cache_settings(self, service_id, version_number, name_key, **kwargs): """Update a specific cache settings object.""" body = self._formdata(kwargs, FastlyCacheSettings.FIELDS) - content = self._fetch("/service/%s/version/%d/cache_settings/%s" % (service_id, version_number, name_key), method="PUT", body=body) + content = self._fetch("/service/%s/version/%d/cache_settings/%s" % (service_id, version_number, urllib.quote(name_key, safe='')), method="PUT", body=body) return FastlyCacheSettings(self, content) - def delete_cache_settings(self, service_id, version_number, name): """Delete a specific cache settings object.""" - content = self._fetch("/service/%s/version/%d/cache_settings/%s" % (service_id, version_number, name), method="DELETE") + content = self._fetch("/service/%s/version/%d/cache_settings/%s" % (service_id, version_number, urllib.quote(name, safe='')), method="DELETE") return self._status(content) - def list_conditions(self, service_id, version_number): """Gets all conditions for a particular service and version.""" content = self._fetch("/service/%s/version/%d/condition" % (service_id, version_number)) return map(lambda x: FastlyCondition(self, x), content) - - def create_condition(self, - service_id, + def create_condition( + self, + service_id, version_number, name, _type, statement, - priority="10", + priority="10", comment=None): """Creates a new condition.""" body = self._formdata({ @@ -263,26 +262,24 @@ def create_condition(self, content = self._fetch("/service/%s/version/%d/condition" % (service_id, version_number), method="POST", body=body) return FastlyCondition(self, content) - def get_condition(self, service_id, version_number, name): """Gets a specified condition.""" - content = self._fetch("/service/%s/version/%d/condition/%s" % (service_id, version_number, name)) + content = self._fetch("/service/%s/version/%d/condition/%s" % (service_id, version_number, urllib.quote(name, safe=''))) return FastlyCondition(self, content) - def update_condition(self, service_id, version_number, name_key, **kwargs): """Updates the specified condition.""" + if '_type' in kwargs: + kwargs['type'] = kwargs['_type'] body = self._formdata(kwargs, FastlyCondition.FIELDS) - content = self._fetch("/service/%s/version/%d/condition/%s" % (service_id, version_number, name_key), method="PUT", body=body) + content = self._fetch("/service/%s/version/%d/condition/%s" % (service_id, version_number, urllib.quote(name_key, safe='')), method="PUT", body=body) return FastlyCondition(self, content) - def delete_condition(self, service_id, version_number, name): """Deletes the specified condition.""" - content = self._fetch("/service/%s/version/%d/condition/%s" % (service_id, version_number, name), method="DELETE") + content = self._fetch("/service/%s/version/%d/condition/%s" % (service_id, version_number, urllib.quote(name, safe='')), method="DELETE") return self._status(content) - def content_edge_check(self, url): """Retrieve headers and MD5 hash of the content for a particular url from each Fastly edge server.""" prefixes = ["http://", "https://"] @@ -293,52 +290,47 @@ def content_edge_check(self, url): content = self._fetch("/content/edge_check/%s" % url) return content - def get_current_customer(self): """Get the logged in customer.""" content = self._fetch("/current_customer") return FastlyCustomer(self, content) - def get_customer(self, customer_id): """Get a specific customer.""" content = self._fetch("/customer/%s" % customer_id) return FastlyCustomer(self, content) - def get_customer_details(self, customer_id): """Get a specific customer, owner, and billing contact.""" content = self._fetch("/customer/details/%s" % customer_id) return content - def list_customer_users(self, customer_id): """List all users from a specified customer id.""" content = self._fetch("/customer/users/%s" % customer_id) return map(lambda x: FastlyUser(self, x), content) - def update_customer(self, customer_id, **kwargs): """Update a customer.""" body = self._formdata(kwargs, FastlyCustomer.FIELDS) content = self._fetch("/customer/%s" % customer_id, method="PUT", body=body) return FastlyCustomer(self, content) - def delete_customer(self, customer_id): """Delete a customer.""" content = self._fetch("/customer/%s" % customer_id, method="DELETE") return self._status(content) - def list_directors(self, service_id, version_number): """List the directors for a particular service and version.""" content = self._fetch("/service/%s/version/%d/director" % (service_id, version_number)) return map(lambda x: FastlyDirector(self, x), content) - - def create_director(self, service_id, version_number, - name, + def create_director( + self, + service_id, + version_number, + name, quorum=75, _type=FastlyDirectorType.RANDOM, retries=5, @@ -355,54 +347,49 @@ def create_director(self, service_id, version_number, content = self._fetch("/service/%s/version/%d/director" % (service_id, version_number), method="POST", body=body) return FastlyDirector(self, content) - def get_director(self, service_id, version_number, name): """Get the director for a particular service and version.""" - content = self._fetch("/service/%s/version/%d/director/%s" % (service_id, version_number, name)) + content = self._fetch("/service/%s/version/%d/director/%s" % (service_id, version_number, urllib.quote(name, safe=''))) return FastlyDirector(self, content) - def update_director(self, service_id, version_number, name_key, **kwargs): """Update the director for a particular service and version.""" + if '_type' in kwargs: + kwargs['type'] = kwargs['_type'] body = self._formdata(kwargs, FastlyDirector.FIELDS) - content = self._fetch("/service/%s/version/%d/director/%s" % (service_id, version_number, name_key), method="PUT", body=body) + content = self._fetch("/service/%s/version/%d/director/%s" % (service_id, version_number, urllib.quote(name_key, safe='')), method="PUT", body=body) return FastlyDirector(self, content) - def delete_director(self, service_id, version_number, name): """Delete the director for a particular service and version.""" - content = self._fetch("/service/%s/version/%d/director/%s" % (service_id, version_number, name), method="DELETE") + content = self._fetch("/service/%s/version/%d/director/%s" % (service_id, version_number, urllib.quote(name, safe='')), method="DELETE") return self._status(content) - def get_director_backend(self, service_id, version_number, director_name, backend_name): """Returns the relationship between a Backend and a Director. If the Backend has been associated with the Director, it returns a simple record indicating this. Otherwise, returns a 404.""" - content = self._fetch("/service/%s/version/%d/director/%s/backend/%s" % (service_id, version_number, director_name, backend_name), method="GET") + content = self._fetch("/service/%s/version/%d/director/%s/backend/%s" % (service_id, version_number, director_name, urllib.quote(backend_name, safe='')), method="GET") return FastlyDirectorBackend(self, content) - def create_director_backend(self, service_id, version_number, director_name, backend_name): """Establishes a relationship between a Backend and a Director. The Backend is then considered a member of the Director and can be used to balance traffic onto.""" - content = self._fetch("/service/%s/version/%d/director/%s/backend/%s" % (service_id, version_number, director_name, backend_name), method="POST") + content = self._fetch("/service/%s/version/%d/director/%s/backend/%s" % (service_id, version_number, director_name, urllib.quote(backend_name, safe='')), method="POST") return FastlyDirectorBackend(self, content) - def delete_director_backend(self, service_id, version_number, director_name, backend_name): """Deletes the relationship between a Backend and a Director. The Backend is no longer considered a member of the Director and thus will not have traffic balanced onto it from this Director.""" - content = self._fetch("/service/%s/version/%d/director/%s/backend/%s" % (service_id, version_number, director_name, backend_name), method="DELETE") + content = self._fetch("/service/%s/version/%d/director/%s/backend/%s" % (service_id, version_number, director_name, urllib.quote(backend_name, safe='')), method="DELETE") return self._status(content) - def list_domains(self, service_id, version_number): """List the domains for a particular service and version.""" content = self._fetch("/service/%s/version/%d/domain" % (service_id, version_number)) return map(lambda x: FastlyDomain(self, x), content) - - def create_domain(self, - service_id, - version_number, - name, + def create_domain( + self, + service_id, + version_number, + name, comment=None): """Create a domain for a particular service and version.""" body = self._formdata({ @@ -413,55 +400,79 @@ def create_domain(self, content = self._fetch("/service/%s/version/%d/domain" % (service_id, version_number), method="POST", body=body) return FastlyDomain(self, content) - def get_domain(self, service_id, version_number, name): """Get the domain for a particular service and version.""" - content = self._fetch("/service/%s/version/%d/domain/%s" % (service_id, version_number, name)) + content = self._fetch("/service/%s/version/%d/domain/%s" % (service_id, version_number, urllib.quote(name, safe=''))) return FastlyDomain(self, content) - def update_domain(self, service_id, version_number, name_key, **kwargs): """Update the domain for a particular service and version.""" body = self._formdata(kwargs, FastlyDomain.FIELDS) - content = self._fetch("/service/%s/version/%d/domain/%s" % (service_id, version_number, name_key), method="PUT", body=body) + content = self._fetch("/service/%s/version/%d/domain/%s" % (service_id, version_number, urllib.quote(name_key, safe='')), method="PUT", body=body) return FastlyDomain(self, content) - def delete_domain(self, service_id, version_number, name): """Delete the domain for a particular service and version.""" - content = self._fetch("/service/%s/version/%d/domain/%s" % (service_id, version_number, name), method="DELETE") - return self._status(self, content) - + content = self._fetch("/service/%s/version/%d/domain/%s" % (service_id, version_number, urllib.quote(name, safe='')), method="DELETE") + return self._status(content) def check_domain(self, service_id, version_number, name): """Checks the status of a domain's DNS record. Returns an array of 3 items. The first is the details for the domain. The second is the current CNAME of the domain. The third is a boolean indicating whether or not it has been properly setup to use Fastly.""" - content = self._fetch("/service/%s/version/%d/domain/%s/check" % (service_id, version_number, name)) + content = self._fetch("/service/%s/version/%d/domain/%s/check" % (service_id, version_number, urllib.quote(name, safe=''))) return FastlyDomainCheck(self, content) - def check_domains(self, service_id, version_number): """Checks the status of all domain DNS records for a Service Version. Returns an array items in the same format as the single domain /check.""" content = self._fetch("/service/%s/version/%d/domain/check_all" % (service_id, version_number)) return map(lambda x: FastlyDomainCheck(self, x), content) - def get_event_log(self, object_id): """Get the specified event log.""" content = self._fetch("/event_log/%s" % object_id, method="GET") return FastlyEventLog(self, content) + def list_gzip(self, service_id, version_number): + """List all gzip configurations for a particular service and version""" + content = self._fetch("/service/%s/version/%d/gzip" % (service_id, version_number)) + return map(lambda x: FastlyGzip(self, x), content) + + def create_gzip(self, service_id, version_number, name, cache_condition=None, content_types=None, extensions=None): + body = self._formdata({ + "name": name, + "cache_condition": cache_condition, + "content_types": content_types, + "extensions": extensions + }, FastlyGzip.FIELDS) + """Creates a new Gzip object.""" + content = self._fetch("/service/%s/version/%d/gzip" % (service_id, version_number), method="POST", body=body) + return FastlyGzip(self, content) + + def get_gzip(self, service_id, version_number, name): + """Retrieves a Header object by name.""" + content = self._fetch("/service/%s/version/%d/gzip/%s" % (service_id, version_number, urllib.quote(name, safe=''))) + return FastlyGzip(self, content) + + def update_gzip(self, service_id, version_number, name_key, **kwargs): + """Modifies an existing Gzip object by name.""" + body = self._formdata(kwargs, FastlyGzip.FIELDS) + content = self._fetch("/service/%s/version/%d/gzip/%s" % (service_id, version_number, urllib.quote(name_key, safe='')), method="PUT", body=body) + return FastlyGzip(self, content) + + def delete_gzip(self, service_id, version_number, name): + """Deletes a Gzip object by name.""" + content = self._fetch("/service/%s/version/%d/gzip/%s" % (service_id, version_number, urllib.quote(name, safe='')), method="DELETE") + return self._status(content) def list_headers(self, service_id, version_number): """Retrieves all Header objects for a particular Version of a Service.""" content = self._fetch("/service/%s/version/%d/header" % (service_id, version_number)) return map(lambda x: FastlyHeader(self, x), content) - - def create_header(self, service_id, version_number, name, destination, source, _type=FastlyHeaderType.RESPONSE, action=FastlyHeaderAction.SET, regex=None, substitution=None, ignore_if_set=None, priority=10, response_condition=None, cache_condition=None, request_condition=None): + def create_header(self, service_id, version_number, name, dst, src, _type=FastlyHeaderType.RESPONSE, action=FastlyHeaderAction.SET, regex=None, substitution=None, ignore_if_set=None, priority=10, response_condition=None, cache_condition=None, request_condition=None): body = self._formdata({ "name": name, - "dst": destination, - "src": source, + "dst": dst, + "src": src, "type": _type, "action": action, "regex": regex, @@ -476,34 +487,32 @@ def create_header(self, service_id, version_number, name, destination, source, _ content = self._fetch("/service/%s/version/%d/header" % (service_id, version_number), method="POST", body=body) return FastlyHeader(self, content) - def get_header(self, service_id, version_number, name): """Retrieves a Header object by name.""" - content = self._fetch("/service/%s/version/%d/header/%s" % (service_id, version_number, name)) + content = self._fetch("/service/%s/version/%d/header/%s" % (service_id, version_number, urllib.quote(name, safe=''))) return FastlyHeader(self, content) - def update_header(self, service_id, version_number, name_key, **kwargs): """Modifies an existing Header object by name.""" + if '_type' in kwargs: + kwargs['type'] = kwargs['_type'] body = self._formdata(kwargs, FastlyHeader.FIELDS) - content = self._fetch("/service/%s/version/%d/header/%s" % (service_id, version_number, name_key), method="PUT", body=body) + content = self._fetch("/service/%s/version/%d/header/%s" % (service_id, version_number, urllib.quote(name_key, safe='')), method="PUT", body=body) return FastlyHeader(self, content) - def delete_header(self, service_id, version_number, name): """Deletes a Header object by name.""" - content = self._fetch("/service/%s/version/%d/header/%s" % (service_id, version_number, name), method="DELETE") + content = self._fetch("/service/%s/version/%d/header/%s" % (service_id, version_number, urllib.quote(name, safe='')), method="DELETE") return self._status(content) - def list_healthchecks(self, service_id, version_number): """List all of the healthchecks for a particular service and version.""" content = self._fetch("/service/%s/version/%d/healthcheck" % (service_id, version_number)) return map(lambda x: FastlyHealthCheck(self, x), content) - - def create_healthcheck(self, - service_id, + def create_healthcheck( + self, + service_id, version_number, name, host, @@ -533,45 +542,39 @@ def create_healthcheck(self, content = self._fetch("/service/%s/version/%d/healthcheck" % (service_id, version_number), method="POST", body=body) return FastlyHealthCheck(self, content) - def get_healthcheck(self, service_id, version_number, name): """Get the healthcheck for a particular service and version.""" - content = self._fetch("/service/%s/version/%d/healthcheck/%s" % (service_id, version_number, name)) + content = self._fetch("/service/%s/version/%d/healthcheck/%s" % (service_id, version_number, urllib.quote(name, safe=''))) return FastlyHealthCheck(self, content) - def update_healthcheck(self, service_id, version_number, name_key, **kwargs): """Update the healthcheck for a particular service and version.""" body = self._formdata(kwargs, FastlyHealthCheck.FIELDS) - content = self._fetch("/service/%s/version/%d/healthcheck/%s" % (service_id, version_number, name_key), method="PUT", body=body) + content = self._fetch("/service/%s/version/%d/healthcheck/%s" % (service_id, version_number, urllib.quote(name_key, safe='')), method="PUT", body=body) return FastlyHealthCheck(self, content) - def delete_healthcheck(self, service_id, version_number, name): """Delete the healthcheck for a particular service and version.""" - content = self._fetch("/service/%s/version/%d/healthcheck/%s" % (service_id, version_number, name), method="DELETE") + content = self._fetch("/service/%s/version/%d/healthcheck/%s" % (service_id, version_number, urllib.quote(name, safe='')), method="DELETE") return self._status(content) - - def purge_url(self, host, path): + def purge_url(self, host, path): """Purge an individual URL.""" - content = self._fetch(path, method="PURGE", headers={ "Host": host }) + content = self._fetch(path, method="PURGE", headers={"Host": host}) return FastlyPurge(self, content) - def check_purge_status(self, purge_id): """Get the status and times of a recently completed purge.""" content = self._fetch("/purge?id=%s" % purge_id) return map(lambda x: FastlyPurgeStatus(self, x), content) - def list_request_settings(self, service_id, version_number): """Returns a list of all Request Settings objects for the given service and version.""" content = self._fetch("/service/%s/version/%d/request_settings" % (service_id, version_number)) return map(lambda x: FastlyRequestSetting(self, x), content) - - def create_request_setting(self, + def create_request_setting( + self, service_id, version_number, name, @@ -604,32 +607,27 @@ def create_request_setting(self, content = self._fetch("/service/%s/version/%d/request_settings" % (service_id, version_number), method="POST", body=body) return FastlyRequestSetting(self, content) - def get_request_setting(self, service_id, version_number, name): """Gets the specified Request Settings object.""" - content = self._fetch("/service/%s/version/%d/request_settings/%s" % (service_id, version_number, name)) + content = self._fetch("/service/%s/version/%d/request_settings/%s" % (service_id, version_number, urllib.quote(name, safe=''))) return FastlyRequestSetting(self, content) - def update_request_setting(self, service_id, version_number, name_key, **kwargs): """Updates the specified Request Settings object.""" body = self._formdata(kwargs, FastlyHealthCheck.FIELDS) - content = self._fetch("/service/%s/version/%d/request_settings/%s" % (service_id, version_number, name_key), method="PUT", body=body) + content = self._fetch("/service/%s/version/%d/request_settings/%s" % (service_id, version_number, urllib.quote(name_key, safe='')), method="PUT", body=body) return FastlyRequestSetting(self, content) - def delete_request_setting(self, service_id, version_number, name): """Removes the specfied Request Settings object.""" - content = self._fetch("/service/%s/version/%d/request_settings/%s" % (service_id, version_number, name), method="DELETE") + content = self._fetch("/service/%s/version/%d/request_settings/%s" % (service_id, version_number, urllib.quote(name, safe='')), method="DELETE") return self._status(content) - def list_response_objects(self, service_id, version_number): """Returns all Response Objects for the specified service and version.""" content = self._fetch("/service/%s/version/%d/response_object" % (service_id, version_number)) return map(lambda x: FastlyResponseObject(self, x), content) - def create_response_object(self, service_id, version_number, name, status="200", response="OK", content="", request_condition=None, cache_condition=None): """Creates a new Response Object.""" body = self._formdata({ @@ -643,26 +641,22 @@ def create_response_object(self, service_id, version_number, name, status="200", content = self._fetch("/service/%s/version/%d/response_object" % (service_id, version_number), method="POST", body=body) return FastlyResponseObject(self, content) - def get_response_object(self, service_id, version_number, name): """Gets the specified Response Object.""" - content = self._fetch("/service/%s/version/%d/response_object/%s" % (service_id, version_number, name)) + content = self._fetch("/service/%s/version/%d/response_object/%s" % (service_id, version_number, urllib.quote(name, safe=''))) return FastlyResponseObject(self, content) - def update_response_object(self, service_id, version_number, name_key, **kwargs): """Updates the specified Response Object.""" body = self._formdata(kwargs, FastlyResponseObject.FIELDS) - content = self._fetch("/service/%s/version/%d/response_object/%s" % (service_id, version_number, name_key), method="PUT", body=body) + content = self._fetch("/service/%s/version/%d/response_object/%s" % (service_id, version_number, urllib.quote(name_key, safe='')), method="PUT", body=body) return FastlyResponseObject(self, content) - def delete_response_object(self, service_id, version_number, name): """Deletes the specified Response Object.""" - content = self._fetch("/service/%s/version/%d/response_object/%s" % (service_id, version_number, name), method="DELETE") + content = self._fetch("/service/%s/version/%d/response_object/%s" % (service_id, version_number, urllib.quote(name, safe='')), method="DELETE") return self._status(content) - def create_service(self, customer_id, name, publish_key=None, comment=None): """Create a service.""" body = self._formdata({ @@ -674,88 +668,75 @@ def create_service(self, customer_id, name, publish_key=None, comment=None): content = self._fetch("/service", method="POST", body=body) return FastlyService(self, content) - def list_services(self): """List Services.""" content = self._fetch("/service") return map(lambda x: FastlyService(self, x), content) - def get_service(self, service_id): """Get a specific service by id.""" content = self._fetch("/service/%s" % service_id) return FastlyService(self, content) - def get_service_details(self, service_id): """List detailed information on a specified service.""" content = self._fetch("/service/%s/details" % service_id) return FastlyService(self, content) - def get_service_by_name(self, service_name): """Get a specific service by name.""" - content = self._fetch("/service/search?name=%s" % service_name) + content = self._fetch("/service/search?name=%s" % urllib.quote(service_name, safe='')) return FastlyService(self, content) - def update_service(self, service_id, **kwargs): """Update a service.""" body = self._formdata(kwargs, FastlyService.FIELDS) content = self._fetch("/service/%s" % service_id, method="PUT", body=body) return FastlyService(self, content) - def delete_service(self, service_id): """Delete a service.""" content = self._fetch("/service/%s" % service_id, method="DELETE") return self._status(content) - def list_domains_by_service(self, service_id): """List the domains within a service.""" content = self._fetch("/service/%s/domain" % service_id, method="GET") return map(lambda x: FastlyDomain(self, x), content) - def purge_service(self, service_id): """Purge everything from a service.""" content = self._fetch("/service/%s/purge_all" % service_id, method="POST") return self._status(content) - def purge_service_by_key(self, service_id, key): """Purge a particular service by a key.""" content = self._fetch("/service/%s/purge/%s" % (service_id, key), method="POST") return self._status(content) - def get_settings(self, service_id, version_number): """Get the settings for a particular service and version.""" content = self._fetch("/service/%s/version/%d/settings" % (service_id, version_number)) return FastlySettings(self, content) - def update_settings(self, service_id, version_number, settings={}): """Update the settings for a particular service and version.""" body = urllib.urlencode(settings) content = self._fetch("/service/%s/version/%d/settings" % (service_id, version_number), method="PUT", body=body) return FastlySettings(self, content) - def get_stats(self, service_id, stat_type=FastlyStatsType.ALL): """Get the stats from a service.""" content = self._fetch("/service/%s/stats/%s" % (service_id, stat_type)) return content - def list_syslogs(self, service_id, version_number): """List all of the Syslogs for a particular service and version.""" content = self._fetch("/service/%s/version/%d/syslog" % (service_id, version_number)) return map(lambda x: FastlySyslog(self, x), content) - - def create_syslog(self, + def create_syslog( + self, service_id, version_number, name, @@ -780,26 +761,22 @@ def create_syslog(self, content = self._fetch("/service/%s/version/%d/syslog" % (service_id, version_number), method="POST", body=body) return FastlySyslog(self, content) - def get_syslog(self, service_id, version_number, name): """Get the Syslog for a particular service and version.""" - content = self._fetch("/service/%s/version/%d/syslog/%s" % (service_id, version_number, name)) + content = self._fetch("/service/%s/version/%d/syslog/%s" % (service_id, version_number, urllib.quote(name, safe=''))) return FastlySyslog(self, content) - def update_syslog(self, service_id, version_number, name_key, **kwargs): """Update the Syslog for a particular service and version.""" body = self._formdata(kwargs, FastlySyslog.FIELDS) - content = self._fetch("/service/%s/version/%d/syslog/%s" % (service_id, version_number, name_key), method="PUT", body=body) + content = self._fetch("/service/%s/version/%d/syslog/%s" % (service_id, version_number, urllib.quote(name_key, safe='')), method="PUT", body=body) return FastlySyslog(self, content) - def delete_syslog(self, service_id, version_number, name): """Delete the Syslog for a particular service and version.""" - content = self._fetch("/service/%s/version/%d/syslog/%s" % (service_id, version_number, name), method="DELETE") + content = self._fetch("/service/%s/version/%d/syslog/%s" % (service_id, version_number, urllib.quote(name, safe='')), method="DELETE") return self._status(content) - def change_password(self, old_password, new_password): """Update the user's password to a new one.""" body = self._formdata({ @@ -809,19 +786,16 @@ def change_password(self, old_password, new_password): content = self._fetch("/current_user/password", method="POST", body=body) return FastlyUser(self, content) - def get_current_user(self): """Get the logged in user.""" content = self._fetch("/current_user") return FastlyUser(self, content) - def get_user(self, user_id): """Get a specific user.""" content = self._fetch("/user/%s" % user_id) return FastlyUser(self, content) - def create_user(self, customer_id, name, login, password, role=FastlyRoles.USER, require_new_password=True): """Create a user.""" body = self._formdata({ @@ -835,32 +809,27 @@ def create_user(self, customer_id, name, login, password, role=FastlyRoles.USER, content = self._fetch("/user", method="POST", body=body) return FastlyUser(self, content) - def update_user(self, user_id, **kwargs): """Update a user.""" body = self._formdata(kwargs, FastlyUser.FIELDS) content = self._fetch("/user/%s" % user_id, method="PUT", body=body) return FastlyUser(self, content) - def delete_user(self, user_id): """Delete a user.""" content = self._fetch("/user/%s" % user_id, method="DELETE") return self._status(content) - def request_password_reset(self, user_id): """Requests a password reset for the specified user.""" content = self._fetch("/user/%s/password/request_reset" % (user_id), method="POST") return FastlyUser(self, content) - def list_vcls(self, service_id, version_number): """List the uploaded VCLs for a particular service and version.""" content = self._fetch("/service/%s/version/%d/vcl" % (service_id, version_number)) return map(lambda x: FastlyVCL(self, x), content) - def upload_vcl(self, service_id, version_number, name, content, main=None, comment=None): """Upload a VCL for a particular service and version.""" body = self._formdata({ @@ -872,56 +841,47 @@ def upload_vcl(self, service_id, version_number, name, content, main=None, comme content = self._fetch("/service/%s/version/%d/vcl" % (service_id, version_number), method="POST", body=body) return FastlyVCL(self, content) - def download_vcl(self, service_id, version_number, name): """Download the specified VCL.""" # TODO: Not sure what to do here, the documentation shows invalid response. Will have to test. raise Exception("Not implemented") - def get_vcl(self, service_id, version_number, name, include_content=True): """Get the uploaded VCL for a particular service and version.""" - content = self._fetch("/service/%s/version/%d/vcl/%s?include_content=%d" % (service_id, version_number, name, int(include_content))) + content = self._fetch("/service/%s/version/%d/vcl/%s?include_content=%d" % (service_id, version_number, urllib.quote(name, safe=''), int(include_content))) return FastlyVCL(self, content) - def get_vcl_html(self, service_id, version_number, name): """Get the uploaded VCL for a particular service and version with HTML syntax highlighting.""" - content = self._fetch("/service/%s/version/%d/vcl/%s/content" % (service_id, version_number, name)) + content = self._fetch("/service/%s/version/%d/vcl/%s/content" % (service_id, version_number, urllib.quote(name, safe=''))) return content.get("content", None) - def get_generated_vcl(self, service_id, version_number): """Display the generated VCL for a particular service and version.""" content = self._fetch("/service/%s/version/%d/generated_vcl" % (service_id, version_number)) return FastlyVCL(self, content) - def get_generated_vcl_html(self, service_id, version_number): """Display the content of generated VCL with HTML syntax highlighting.""" content = self._fetch("/service/%s/version/%d/generated_vcl/content" % (service_id, version_number)) return content.get("content", None) - def set_main_vcl(self, service_id, version_number, name): """Set the specified VCL as the main.""" - content = self._fetch("/service/%s/version/%d/vcl/%s/main" % (service_id, version_number, name), method="PUT") + content = self._fetch("/service/%s/version/%d/vcl/%s/main" % (service_id, version_number, urllib.quote(name, safe='')), method="PUT") return FastlyVCL(self, content) - def update_vcl(self, service_id, version_number, name_key, **kwargs): """Update the uploaded VCL for a particular service and version.""" body = self._formdata(kwargs, FastlyVCL.FIELDS) - content = self._fetch("/service/%s/version/%d/vcl/%s" % (service_id, version_number, name_key), method="PUT", body=body) + content = self._fetch("/service/%s/version/%d/vcl/%s" % (service_id, version_number, urllib.quote(name_key, safe='')), method="PUT", body=body) return FastlyVCL(self, content) - def delete_vcl(self, service_id, version_number, name): """Delete the uploaded VCL for a particular service and version.""" - content = self._fetch("/service/%s/version/%d/vcl/%s" % (service_id, version_number, name), method="DELETE") + content = self._fetch("/service/%s/version/%d/vcl/%s" % (service_id, version_number, urllib.quote(name, safe='')), method="DELETE") return self._status(content) - def create_version(self, service_id, inherit_service_id=None, comment=None): """Create a version for a particular service.""" body = self._formdata({ @@ -932,12 +892,10 @@ def create_version(self, service_id, inherit_service_id=None, comment=None): content = self._fetch("/service/%s/version" % service_id, method="POST", body=body) return FastlyVersion(self, content) - def list_versions(self, service_id): - content = self._fetch("/service/%s/version"% service_id) + content = self._fetch("/service/%s/version" % service_id) return map(lambda x: FastlyVersion(self, x), content) - def get_version(self, service_id, version_number): """Get the version for a particular service.""" content = self._fetch("/service/%s/version/%d" % (service_id, version_number)) @@ -949,44 +907,38 @@ def update_version(self, service_id, version_number, **kwargs): content = self._fetch("/service/%s/version/%d/" % (service_id, version_number), method="PUT", body=body) return FastlyVersion(self, content) - def clone_version(self, service_id, version_number): """Clone the current configuration into a new version.""" content = self._fetch("/service/%s/version/%d/clone" % (service_id, version_number), method="PUT") return FastlyVersion(self, content) - def activate_version(self, service_id, version_number): """Activate the current version.""" content = self._fetch("/service/%s/version/%d/activate" % (service_id, version_number), method="PUT") return FastlyVersion(self, content) - def deactivate_version(self, service_id, version_number): """Deactivate the current version.""" content = self._fetch("/service/%s/version/%d/deactivate" % (service_id, version_number), method="PUT") return FastlyVersion(self, content) - def validate_version(self, service_id, version_number): """Validate the version for a particular service and version.""" content = self._fetch("/service/%s/version/%d/validate" % (service_id, version_number)) return self._status(content) - def lock_version(self, service_id, version_number): """Locks the specified version.""" content = self._fetch("/service/%s/version/%d/lock" % (service_id, version_number)) return self._status(content) - def list_wordpressess(self, service_id, version_number): """Get all of the wordpresses for a specified service and version.""" content = self._fetch("/service/%s/version/%d/wordpress" % (service_id, version_number)) return map(lambda x: FastlyWordpress(self, x), content) - - def create_wordpress(self, + def create_wordpress( + self, service_id, version_number, name, @@ -1001,42 +953,36 @@ def create_wordpress(self, content = self._fetch("/service/%s/version/%d/wordpress" % (service_id, version_number), method="POST", body=body) return FastlyWordpress(self, content) - def get_wordpress(self, service_id, version_number, name): """Get information on a specific wordpress.""" - content = self._fetch("/service/%s/version/%d/wordpress/%s" % (service_id, version_number, name)) + content = self._fetch("/service/%s/version/%d/wordpress/%s" % (service_id, version_number, urllib.quote(name, safe=''))) return FastlyWordpress(self, content) - def update_wordpress(self, service_id, version_number, name_key, **kwargs): """Update a specified wordpress.""" body = self._formdata(kwargs, FastlyWordpress.FIELDS) - content = self._fetch("/service/%s/version/%d/wordpress/%s" % (service_id, version_number, name_key), method="PUT", body=body) + content = self._fetch("/service/%s/version/%d/wordpress/%s" % (service_id, version_number, urllib.quote(name_key, safe='')), method="PUT", body=body) return FastlyWordpress(self, content) - def delete_wordpress(self, service_id, version_number, name): """Delete a specified wordpress.""" - content = self._fetch("/service/%s/version/%d/wordpress/%s" % (service_id, version_number, name), method="DELETE") + content = self._fetch("/service/%s/version/%d/wordpress/%s" % (service_id, version_number, urllib.quote(name, safe='')), method="DELETE") return self._status(content) - # TODO: Is this broken? def delete_version(self, service_id, version_number): content = self._fetch("/service/%s/version/%d" % (service_id, version_number), method="DELETE") return self._status(content) - def _status(self, status): if not isinstance(status, FastlyStatus): status = FastlyStatus(self, status) if status.status != "ok": - raise FastlyError("FastlyError: %s" % status.msg) + raise FastlyError("FastlyError: %s" % status.msg) return True - def _formdata(self, fields, valid=[]): data = {} for key in fields.keys(): @@ -1046,7 +992,6 @@ def _formdata(self, fields, valid=[]): data[key] = str(int(data[key])) return urllib.urlencode(data) - def _fetch(self, url, method="GET", body=None, headers={}): hdrs = {} hdrs.update(headers) @@ -1057,29 +1002,30 @@ def _fetch(self, url, method="GET", body=None, headers={}): if self._fully_authed: hdrs["Cookie"] = self._session else: - hdrs["X-Fastly-Key"] = self._api_key + hdrs["Fastly-Key"] = self._api_key hdrs["Content-Accept"] = "application/json" - if not hdrs.has_key("Content-Type") and method in ["POST", "PUT"]: + hdrs["User-Agent"] = "fastly-python-v%s" % __version__ + if "Content-Type" not in hdrs and method in ["POST", "PUT"]: hdrs["Content-Type"] = "application/x-www-form-urlencoded" - conn = httplib2.Http(disable_ssl_certificate_validation=True) + conn = httplib2.Http(disable_ssl_certificate_validation=False, timeout=10) endpoint = "%s://%s%s" % (FASTLY_SCHEME, FASTLY_HOST, url) return self._check(*conn.request(endpoint, method, body=body, headers=hdrs)) - def _check(self, resp, content): status = resp.status payload = None if content: try: payload = json.loads(content) - except ValueError: # Could not decode, usually HTML + # Could not decode, usually HTML + except ValueError: payload = content if status == 200: # Keep track of the session. Only really set during /login - if resp.has_key("set-cookie"): + if "set-cookie" in resp: set_cookie = resp["set-cookie"] match = FASTLY_SESSION_REGEX.search(set_cookie) if match is not None: @@ -1175,7 +1121,6 @@ class FastlySession(FastlyObject): def customer(self): return FastlyCustomer(self._conn, self._data["customer"]) - @property def user(self): return FastlyUser(self._conn, self._data["user"]) @@ -1201,11 +1146,17 @@ class FastlyBackend(FastlyObject, IServiceVersionObject): "request_condition", "healthcheck", "comment", + "ssl_cert_hostname", + "ssl_sni_hostname", + "min_tls_version", + "max_tls_version", ] @property def healthcheck(self): - return self._conn.get_healthcheck(self.service_id, self.version, self.healthcheck) + if not self.__getattr__('healthcheck'): + return None + return self._conn.get_healthcheck(self.service_id, self.version, self.__getattr__("healthcheck")) class FastlyCacheSettings(FastlyObject, IServiceVersionObject): @@ -1232,6 +1183,7 @@ class FastlyCondition(FastlyObject, IServiceVersionObject): "type", "statement", "priority", + "comment", ] @@ -1274,6 +1226,7 @@ class FastlyDirector(FastlyObject, IServiceVersionObject, IDateStampedObject): "deleted", "capacity", "comment", + "backends", ] @@ -1328,6 +1281,18 @@ class FastlyEventLog(FastlyObject): ] +class FastlyGzip(FastlyObject, IServiceVersionObject): + """Gzip configuration allows you to choose resources to automatically compress.""" + FIELDS = [ + "cache_condition", + "content_types", + "extensions", + "name", + "service_id", + "version" + ] + + class FastlyHeader(FastlyObject, IServiceVersionObject): """Header objects are used to add, modify, or delete headers from requests and responses. The header content can be simple strings or be derived from variables inside Varnish. Regular expressions can be used to customize the headers even further.""" FIELDS = [ @@ -1347,6 +1312,14 @@ class FastlyHeader(FastlyObject, IServiceVersionObject): "cache_condition", ] + @property + def destination(self): + return self.dst + + @property + def source(self): + return self.src + class FastlyHealthCheck(FastlyObject, IServiceVersionObject): """Healthchecks are used to customize the way Fastly checks on your Backends. Only Backends that have successful Healthchecks will be sent traffic, thus assuring that the failure of one server does not affect visitors.""" @@ -1426,7 +1399,7 @@ class FastlyService(FastlyObject): "customer_id", "publish_key", "active_version", - "versions" + "versions", "comment", ] @@ -1449,19 +1422,25 @@ class FastlySettings(FastlyObject, IServiceVersionObject): class FastlySyslog(FastlyObject, IServiceVersionObject, IDateStampedObject): """Fastly will stream log messages to the location, and in the format, specified in the Syslog object.""" FIELDS = [ - "name", - "service_id", - "version", "address", + "created_at", + "deleted_at", + "format", + "format_version", + "hostname", + "ipv4", + "message_type", + "name", + "placement", "port", - "use_tls", + "response_condition", + "service_id", "tls_ca_cert", + "tls_hostname", "token", - "format", - "response_condition", - "created", - "updated", - "deleted", + "updated_at", + "use_tls", + "version", ] @@ -1524,31 +1503,31 @@ def settings(self): @property def backends(self): - return dict([ (b.name, b) for b in self._conn.list_backends(self.service_id, int(self.number))]) + return dict([(b.name, b) for b in self._conn.list_backends(self.service_id, int(self.number))]) @property def healthchecks(self): - return dict([ (h.name, h) for h in self._conn.list_healthchecks(self.service_id, int(self.number))]) + return dict([(h.name, h) for h in self._conn.list_healthchecks(self.service_id, int(self.number))]) @property def domains(self): - return dict([ (d.name, d) for d in self._conn.list_domains(self.service_id, int(self.number))]) + return dict([(d.name, d) for d in self._conn.list_domains(self.service_id, int(self.number))]) @property def directors(self): - return dict([ (d.name, d) for d in self._conn.list_directors(self.service_id, int(self.number))]) + return dict([(d.name, d) for d in self._conn.list_directors(self.service_id, int(self.number))]) @property def origins(self): - return dict([ (o.name, o) for o in self._conn.list_origins(self.service_id, int(self.number))]) + return dict([(o.name, o) for o in self._conn.list_origins(self.service_id, int(self.number))]) @property def syslogs(self): - return dict([ (s.name, s) for s in self._conn.list_syslogs(self.service_id, int(self.number))]) + return dict([(s.name, s) for s in self._conn.list_syslogs(self.service_id, int(self.number))]) @property def vcls(self): - return dict([ (v.name, v) for v in self._conn.list_vcls(self.service_id, int(self.number))]) + return dict([(v.name, v) for v in self._conn.list_vcls(self.service_id, int(self.number))]) class FastlyWordpress(FastlyObject, IServiceVersionObject): diff --git a/fastly/version.py b/fastly/version.py new file mode 100644 index 0000000..8a81504 --- /dev/null +++ b/fastly/version.py @@ -0,0 +1 @@ +__version__ = '1.0.4' diff --git a/setup.py b/setup.py index da15c1a..9ad4941 100644 --- a/setup.py +++ b/setup.py @@ -7,19 +7,21 @@ long_description = f.read().decode('utf-8') setup( - name = "fastly-python", - version = "1.0.4", - author = "Chris Zacharias", - author_email = "chris@imgix.com", - description = ("A Python client libary for the Fastly API."), - license = "BSD", - keywords = "fastly", - url = "https://github.com/zebrafishlabs/fastly-python", + name="fastly-python", + version="1.0.4", + author="Chris Zacharias", + author_email="chris@imgix.com", + description=("A Python client libary for the Fastly API."), + license="BSD", + keywords="fastly", + url="https://github.com/zebrafishlabs/fastly-python", packages=['fastly', 'tests'], + install_requires=[ + 'httplib2', + ], scripts=['bin/fastly_upload_vcl.py', 'bin/fastly_purge_url.py'], long_description=long_description, - install_requires=['httplib2'], - classifiers=[ + classifiers=[ "Development Status :: 3 - Alpha", "Topic :: Software Development :: Libraries :: Python Modules", "License :: OSI Approved :: BSD License",