Script 'mail_helper' called by obssrc
Hello community,
here is the log from the commit of package python-cloudflare for
openSUSE:Factory checked in at 2022-01-23 18:38:44
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Comparing /work/SRC/openSUSE:Factory/python-cloudflare (Old)
and /work/SRC/openSUSE:Factory/.python-cloudflare.new.1938 (New)
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Package is "python-cloudflare"
Sun Jan 23 18:38:44 2022 rev:8 rq:948246 version:2.8.15
Changes:
--------
--- /work/SRC/openSUSE:Factory/python-cloudflare/python-cloudflare.changes
2020-07-14 08:00:59.122119080 +0200
+++
/work/SRC/openSUSE:Factory/.python-cloudflare.new.1938/python-cloudflare.changes
2022-01-23 18:38:47.989901928 +0100
@@ -1,0 +2,37 @@
+Sun Jan 23 16:44:10 UTC 2022 - Dirk M??ller <[email protected]>
+
+- update to 2.8.15:
+ * added cursor example
+ * zones/rulesets added
+ * Removing excess trailing parenthesis
+ * first pass at adding travis CI
+ * zones/waiting_rooms, accounts/diagnostics, and more
+ * configparser added it haste, but not needed - oops!
+ * /zones/:id/access/...
+ * configparser missing - oops!
+ * cleaner and easier way to find missing api calls - this changes
--dump/--api for the better
+ * /zones/:id/access/apps/policies - fixed along with a bunch more similar
typos
+ * added more profile info
+ * revoke-tokens -> revoke_tokens
+ * updated and included AMP RealURL/Signed Exchange API
+ * now with curl style debug - i.e. matches api information page
+ * now with curl style debug - i.e. matches api information page
+ * now with access_requests as underscore
+ * make sure verbose, etc is always used
+ * dashes vs underscores - finally tamed!
+ * /zones/:id/access/apps/:id/revoke-tokens - added
+ * improve deprecated code - add dates, check expire, improve parse of api
webpage
+ * added support for dashes/underscores in commands and python calls - kinda
overdue
+ * cleanup of logic around uuid match. added support for dashes/underscores
in commands
+ * /zones/:id/access/apps/revoke-tokens - removed as depricated
+ * removed deprecated /organizations and /user/virtual_dns api
+ * Added base_url to config and env variables
+ * accounts/:id/rules/lists/bulk_operations/:operation_id - syntax fixed
+ * Added working GraphQL examples
+ * improved debug for jSON based data/params
+ * rules,access/logs,access/apps,etc added
+ * api decode and Makefile now consistent - no leading slash
+ * moved network functions - but forgot one call - now fixed
+ * moved network functions into their own file - part of splitting up a large
file
+
+-------------------------------------------------------------------
Old:
----
cloudflare-2.8.3.tar.gz
New:
----
cloudflare-2.8.15.tar.gz
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Other differences:
------------------
++++++ python-cloudflare.spec ++++++
--- /var/tmp/diff_new_pack.TjGy11/_old 2022-01-23 18:38:49.277893262 +0100
+++ /var/tmp/diff_new_pack.TjGy11/_new 2022-01-23 18:38:49.281893235 +0100
@@ -1,7 +1,7 @@
#
# spec file for package python-cloudflare
#
-# Copyright (c) 2020 SUSE LLC
+# Copyright (c) 2022 SUSE LLC
#
# All modifications and additions to the file contributed by third parties
# remain the property of their copyright owners, unless otherwise agreed
@@ -18,7 +18,7 @@
%{?!python_module:%define python_module() python-%{**} python3-%{**}}
Name: python-cloudflare
-Version: 2.8.3
+Version: 2.8.15
Release: 0
Summary: Python wrapper for the Cloudflare v4 API
License: MIT
@@ -39,7 +39,7 @@
Requires: python-jsonlines
Requires: python-requests >= 2.4.2
Requires(post): update-alternatives
-Requires(postun): update-alternatives
+Requires(postun):update-alternatives
BuildArch: noarch
%python_subpackages
++++++ cloudflare-2.8.3.tar.gz -> cloudflare-2.8.15.tar.gz ++++++
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/cloudflare-2.8.3/CloudFlare/__init__.py
new/cloudflare-2.8.15/CloudFlare/__init__.py
--- old/cloudflare-2.8.3/CloudFlare/__init__.py 2020-06-23 03:44:49.000000000
+0200
+++ new/cloudflare-2.8.15/CloudFlare/__init__.py 2021-01-01
01:17:57.000000000 +0100
@@ -1,7 +1,7 @@
""" Cloudflare v4 API"""
from __future__ import absolute_import
-__version__ = '2.8.3'
+__version__ = '2.8.15'
from .cloudflare import CloudFlare
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/cloudflare-2.8.3/CloudFlare/api_decode_from_web.py
new/cloudflare-2.8.15/CloudFlare/api_decode_from_web.py
--- old/cloudflare-2.8.3/CloudFlare/api_decode_from_web.py 2020-06-19
23:59:12.000000000 +0200
+++ new/cloudflare-2.8.15/CloudFlare/api_decode_from_web.py 2020-08-04
23:06:19.000000000 +0200
@@ -1,5 +1,7 @@
""" API extras for Cloudflare API"""
+import datetime
+
from bs4 import BeautifulSoup, Comment
API_TYPES = ['GET', 'POST', 'PATCH', 'PUT', 'DELETE']
@@ -10,9 +12,31 @@
cmds = []
# look for deprecated first in section
deprecated = False
+ deprecated_date = ''
+ deprecated_already = False
for tag2 in section.find_all('h3'):
+ # <h3 class="text-warning" data-reactid="13490">Deprecation
Warning</h3>
if 'Deprecation Warning' in str(tag2):
deprecated = True
+ break
+ for tag2 in section.find_all('p'):
+ # <p class="deprecation-date" data-reactid="13491">End of life Date:
November 2, 2020</p>
+ if 'End of life Date:' in str(tag2):
+ for child in tag2.children:
+ deprecated_date = str(child).replace('End of life
Date:','').strip()
+ try:
+ # clean up date
+ d = datetime.datetime.strptime(deprecated_date, '%B %d,
%Y')
+ if d <= datetime.datetime.now():
+ # already done!
+ deprecated_already = True
+ deprecated_date = d.strftime('%Y-%m-%d')
+ except ValueError:
+ # Lets not worry about all the date formats that could
show-up. Leave as a string
+ pass
+ break
+ if deprecated_date != '':
+ break
# look for all API calls in section
for tag2 in section.find_all('pre'):
cmd = []
@@ -24,10 +48,12 @@
if len(cmd) == 0:
continue
action = cmd[0]
- cmd = '/' + ''.join(cmd[1:])
if action == '' or action not in API_TYPES:
continue
- v = {'deprecated': deprecated, 'action': action, 'cmd': cmd}
+ cmd = ''.join(cmd[1:])
+ if cmd[0] != '/':
+ cmd = '/' + cmd
+ v = {'action': action, 'cmd': cmd, 'deprecated': deprecated,
'deprecated_date': deprecated_date, 'deprecated_already': deprecated_already}
cmds.append(v)
return cmds
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/cloudflare-2.8.3/CloudFlare/api_v4.py
new/cloudflare-2.8.15/CloudFlare/api_v4.py
--- old/cloudflare-2.8.3/CloudFlare/api_v4.py 2020-06-17 21:27:12.000000000
+0200
+++ new/cloudflare-2.8.15/CloudFlare/api_v4.py 2020-12-31 23:16:37.000000000
+0100
@@ -9,11 +9,11 @@
user_load_balancers(self)
user_load_balancing_analytics(self)
user_tokens_verify(self)
- user_virtual_dns(self)
user_workers(self)
# The API commands for /zones/
zones(self)
+ zones_access(self)
zones_amp(self)
zones_analytics(self)
zones_argo(self)
@@ -30,16 +30,12 @@
zones_settings(self)
zones_spectrum(self)
zones_ssl(self)
+ zones_waiting_rooms(self)
zones_workers(self)
# The API commands for /railguns/
railguns(self)
- # The API commands for /organizations/
- organizations(self)
- organizations_audit_logs(self)
- organizations_virtual_dns(self)
-
# The API commands for /certificates/
certificates(self)
@@ -48,8 +44,10 @@
# The API commands for /accounts/
accounts(self)
+ accounts_access(self)
accounts_addressing(self)
accounts_audit_logs(self)
+ accounts_diagnostics(self)
accounts_firewall(self)
accounts_load_balancers(self)
accounts_secondary_dns(self)
@@ -82,11 +80,6 @@
""" API core commands for Cloudflare API"""
self.add('AUTH', "zones")
- self.add('VOID', "zones", "access")
- self.add('AUTH', "zones", "access/apps")
- self.add('AUTH', "zones", "access/apps/policies")
- self.add('AUTH', "zones", "access/apps/revoke-tokens")
- self.add('AUTH', "zones", "access/certificates")
self.add('AUTH', "zones", "activation_check")
self.add('AUTH', "zones", "available_plans")
self.add('AUTH', "zones", "available_rate_plans")
@@ -107,6 +100,8 @@
self.add('AUTH', "zones", "purge_cache")
self.add('AUTH', "zones", "railguns")
self.add('AUTH', "zones", "railguns", "diagnose")
+ self.add('AUTH', 'zones', 'rulesets')
+ self.add('AUTH', 'zones', 'rulesets', 'versions')
self.add('VOID', "zones", "security")
self.add('AUTH', "zones", "security/events")
self.add('AUTH', "zones", "subscription")
@@ -200,7 +195,7 @@
""" API core commands for Cloudflare API"""
self.add('VOID', "zones", "amp")
- self.add('AUTH', "zones", "amp/viewer")
+ self.add('AUTH', "zones", "amp/sxg")
def zones_logpush(self):
""" API core commands for Cloudflare API"""
@@ -234,23 +229,6 @@
self.add('AUTH', "railguns")
self.add('AUTH', "railguns", "zones")
-def organizations(self):
- """ API core commands for Cloudflare API"""
-
- self.add('AUTH', "organizations")
- self.add('AUTH', "organizations", "members")
- self.add('AUTH', "organizations", "invites")
- self.add('AUTH', "organizations", "railguns")
- self.add('AUTH', "organizations", "railguns", "zones")
- self.add('AUTH', "organizations", "roles")
- self.add('VOID', "organizations", "firewall")
- self.add('VOID', "organizations", "firewall/access_rules")
- self.add('AUTH', "organizations", "firewall/access_rules/rules")
- self.add('VOID', "organizations", "load_balancers")
- self.add('AUTH', "organizations", "load_balancers/monitors")
- self.add('AUTH', "organizations", "load_balancers/pools")
- self.add('AUTH', "organizations", "load_balancers/pools", "health")
-
def certificates(self):
""" API core commands for Cloudflare API"""
@@ -331,18 +309,12 @@
self.add('VOID', "user/load_balancers")
self.add('AUTH', "user/load_balancers/monitors")
self.add('AUTH', "user/load_balancers/monitors", "preview")
+ self.add('AUTH', 'user/load_balancers/monitors', 'references')
self.add('AUTH', "user/load_balancers/preview")
self.add('AUTH', "user/load_balancers/pools")
self.add('AUTH', "user/load_balancers/pools", "health")
self.add('AUTH', "user/load_balancers/pools", "preview")
-
-def user_virtual_dns(self):
- """ API core commands for Cloudflare API"""
-
- self.add('AUTH', "user/virtual_dns")
- self.add('VOID', "user/virtual_dns", "dns_analytics")
- self.add('AUTH', "user/virtual_dns", "dns_analytics/report")
- self.add('AUTH', "user/virtual_dns", "dns_analytics/report/bytime")
+ self.add('AUTH', 'user/load_balancers/pools', 'references')
def user_workers(self):
""" API core commands for Cloudflare API"""
@@ -350,14 +322,6 @@
self.add('VOID', "user/workers")
self.add('AUTH', "user/workers/scripts")
-def organizations_virtual_dns(self):
- """ API core commands for Cloudflare API"""
-
- self.add('AUTH', "organizations", "virtual_dns")
- self.add('VOID', "organizations", "virtual_dns", "dns_analytics")
- self.add('AUTH', "organizations", "virtual_dns", "dns_analytics/report")
- self.add('AUTH', "organizations", "virtual_dns",
"dns_analytics/report/bytime")
-
def user_audit_logs(self):
""" API core commands for Cloudflare API"""
@@ -366,9 +330,8 @@
def user_load_balancing_analytics(self):
""" API core commands for Cloudflare API"""
- self.add('VOID', "user", "load_balancing_analytics")
- self.add('AUTH', "user", "load_balancing_analytics/events")
- self.add('AUTH', "user", "load_balancing_analytics/entities")
+ self.add('VOID', "user/load_balancing_analytics")
+ self.add('AUTH', "user/load_balancing_analytics/events")
def user_tokens_verify(self):
""" API core commands for Cloudflare API"""
@@ -378,30 +341,26 @@
self.add('AUTH', "user/tokens/verify")
self.add('AUTH', "user/tokens", "value")
-def organizations_audit_logs(self):
- """ API core commands for Cloudflare API"""
-
- self.add('AUTH', "organizations", "audit_logs")
-
def accounts(self):
""" API core commands for Cloudflare API"""
self.add('AUTH', "accounts")
- self.add('VOID', "accounts", "access")
- self.add('AUTH', "accounts", "access/groups")
- self.add('AUTH', "accounts", "access/identity_providers")
- self.add('AUTH', "accounts", "access/organizations")
- self.add('AUTH', "accounts", "access/organizations/revoke_user")
- self.add('AUTH', "accounts", "access/service_tokens")
self.add('VOID', "accounts", "billing")
self.add('AUTH', "accounts", "billing/profile")
self.add('AUTH', "accounts", "custom_pages")
self.add('AUTH', "accounts", "members")
self.add('AUTH', "accounts", "railguns")
- self.add('AUTH', "accounts", "railguns/connections")
+ self.add('AUTH', "accounts", "railguns", "connections")
self.add('VOID', "accounts", "registrar")
self.add('AUTH', "accounts", "registrar/domains")
self.add('AUTH', "accounts", "roles")
+ self.add('VOID', 'accounts', 'rules')
+ self.add('AUTH', 'accounts', 'rules/lists')
+ self.add('AUTH', 'accounts', 'rules/lists', 'items')
+ self.add('AUTH', 'accounts', 'rules/lists/bulk_operations')
+ self.add('AUTH', 'accounts', 'rulesets')
+ self.add('AUTH', 'accounts', 'rulesets', 'versions')
+ self.add('AUTH', 'accounts', 'rulesets/import')
self.add('VOID', "accounts", "storage")
self.add('AUTH', "accounts", "storage/analytics")
self.add('AUTH', "accounts", "storage/analytics/stored")
@@ -411,12 +370,15 @@
self.add('AUTH', "accounts", "storage/kv/namespaces", "keys")
self.add('AUTH', "accounts", "storage/kv/namespaces", "values")
self.add('AUTH', "accounts", "subscriptions")
+ self.add('AUTH', 'accounts', 'tunnels')
+ self.add('AUTH', 'accounts', 'tunnels', 'connections')
self.add('AUTH', "accounts", "virtual_dns")
- self.add('VOID', "accounts", "virtual_dns/dns_analytics")
- self.add('AUTH', "accounts", "virtual_dns/dns_analytics/report")
- self.add('AUTH', "accounts", "virtual_dns/dns_analytics/report/bytime")
+ self.add('VOID', "accounts", "virtual_dns", "dns_analytics")
+ self.add('AUTH', "accounts", "virtual_dns", "dns_analytics/report")
+ self.add('AUTH', "accounts", "virtual_dns", "dns_analytics/report/bytime")
self.add('VOID', "accounts", "workers")
self.add('AUTH', "accounts", "workers/scripts")
+ self.add('AUTH', 'accounts', 'workers/scripts', 'schedules')
def accounts_addressing(self):
""" API core commands for Cloudflare API"""
@@ -439,9 +401,12 @@
self.add('AUTH', 'accounts', 'load_balancers/preview')
self.add('AUTH', "accounts", "load_balancers/monitors")
self.add('AUTH', 'accounts', 'load_balancers/monitors', 'preview')
+ self.add('AUTH', 'accounts', 'load_balancers/monitors', 'references')
self.add('AUTH', "accounts", "load_balancers/pools")
self.add('AUTH', "accounts", "load_balancers/pools", "health")
self.add('AUTH', 'accounts', 'load_balancers/pools', 'preview')
+ self.add('AUTH', 'accounts', 'load_balancers/pools', 'references')
+ self.add('AUTH', 'accounts', 'load_balancers/regions')
self.add('AUTH', 'accounts', 'load_balancers/search')
def accounts_firewall(self):
@@ -456,18 +421,21 @@
self.add('VOID', "accounts", "secondary_dns")
self.add('AUTH', "accounts", "secondary_dns/masters")
+ self.add('AUTH', 'accounts', 'secondary_dns/primaries')
self.add('AUTH', "accounts", "secondary_dns/tsigs")
def accounts_stream(self):
""" API core commands for Cloudflare API"""
self.add('AUTH', "accounts", "stream")
+ self.add('AUTH', "accounts", "stream", "captions")
self.add('AUTH', "accounts", "stream/copy")
self.add('AUTH', "accounts", "stream/direct_upload")
- self.add('AUTH', "accounts", "stream/embed")
+ self.add('AUTH', "accounts", "stream", "embed")
self.add('AUTH', "accounts", "stream/keys")
self.add('AUTH', "accounts", "stream/preview")
- self.add('AUTH', "accounts", "stream", "captions")
+ self.add('AUTH', "accounts", "stream/watermarks")
+ self.add('AUTH', "accounts", "stream/webhook")
def zones_media(self):
""" API core commands for Cloudflare API"""
@@ -485,3 +453,50 @@
""" API core commands for Cloudflare API"""
self.add('AUTH', "graphql")
+
+def zones_access(self):
+ """ API core commands for Cloudflare API"""
+
+ self.add('VOID', "zones", "access")
+ self.add('AUTH', "zones", "access/apps")
+ self.add('AUTH', "zones", "access/apps", "policies")
+ self.add('AUTH', "zones", "access/apps", "revoke_tokens")
+ self.add('AUTH', "zones", "access/certificates")
+ #self.add('AUTH', "zones", "access/apps/ca")
+ self.add('AUTH', "zones", "access/apps", "ca")
+ self.add('AUTH', "zones", "access/groups")
+ self.add('AUTH', "zones", "access/identity_providers")
+ self.add('AUTH', "zones", "access/organizations")
+ self.add('AUTH', "zones", "access/organizations/revoke_user")
+ self.add('AUTH', "zones", "access/service_tokens")
+
+def accounts_access(self):
+ """ API core commands for Cloudflare API"""
+
+ self.add('VOID', "accounts", "access")
+ self.add('AUTH', 'accounts', 'access/certificates')
+ self.add('AUTH', "accounts", "access/groups")
+ self.add('AUTH', "accounts", "access/identity_providers")
+ self.add('AUTH', "accounts", "access/organizations")
+ self.add('AUTH', "accounts", "access/organizations/revoke_user")
+ self.add('AUTH', "accounts", "access/service_tokens")
+ self.add('VOID', "accounts", "access/logs")
+ self.add('AUTH', 'accounts', 'access/logs/access_requests')
+ self.add('AUTH', 'accounts', 'access/apps')
+ #self.add('AUTH', 'accounts', 'access/apps/ca')
+ self.add('AUTH', 'accounts', 'access/apps', 'ca')
+ self.add('AUTH', 'accounts', 'access/apps', 'policies')
+ self.add('AUTH', 'accounts', 'access/apps', 'revoke_tokens')
+
+def accounts_diagnostics(self):
+ """ API core commands for Cloudflare API"""
+
+ self.add('VOID', 'accounts', 'diagnostics')
+ self.add('AUTH', 'accounts', 'diagnostics/traceroute')
+
+def zones_waiting_rooms(self):
+ """ API core commands for Cloudflare API"""
+
+ self.add('AUTH', 'zones', 'waiting_rooms')
+ self.add('AUTH', 'zones', 'waiting_rooms', 'status')
+ self.add('AUTH', 'zones', 'waiting_rooms/preview')
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/cloudflare-2.8.3/CloudFlare/cloudflare.py
new/cloudflare-2.8.15/CloudFlare/cloudflare.py
--- old/cloudflare-2.8.3/CloudFlare/cloudflare.py 2020-06-23
01:09:21.000000000 +0200
+++ new/cloudflare-2.8.15/CloudFlare/cloudflare.py 2020-08-04
22:21:31.000000000 +0200
@@ -4,8 +4,9 @@
import json
import requests
+from .network import CFnetwork
from .logging_helper import CFlogger
-from .utils import user_agent, sanitize_secrets
+from .utils import user_agent, build_curl
from .read_configs import read_configs
from .api_v4 import api_v4
from .api_extras import api_extras
@@ -36,12 +37,15 @@
self.certtoken = config['certtoken']
else:
self.certtoken = None
-
- self.base_url = config['base_url']
+ if 'base_url' in config:
+ self.base_url = config['base_url']
+ else:
+ # We must have a base_url value
+ self.base_url = BASE_URL
self.raw = config['raw']
self.use_sessions = config['use_sessions']
self.profile = config['profile']
- self.session = None
+ self.network = CFnetwork(use_sessions=self.use_sessions)
self.user_agent = user_agent()
if 'debug' in config and config['debug']:
@@ -155,71 +159,11 @@
identifier1, identifier2, identifier3,
params, data, files)
- def _connection(self, method, url, headers=None, params=None,
data=None, files=None):
- """ Cloudflare v4 API"""
-
- if self.use_sessions:
- if self.session is None:
- self.session = requests.Session()
- else:
- self.session = requests
-
- method = method.upper()
-
- if method == 'GET':
- return self.session.get(url,
- headers=headers, params=params,
data=data)
- if method == 'POST':
- if isinstance(data, str):
- return self.session.post(url,
- headers=headers, params=params,
data=data, files=files)
- else:
- return self.session.post(url,
- headers=headers, params=params,
json=data, files=files)
- if method == 'PUT':
- if isinstance(data, str):
- return self.session.put(url,
- headers=headers, params=params,
data=data)
- else:
- return self.session.put(url,
- headers=headers, params=params,
json=data)
- if method == 'DELETE':
- if isinstance(data, str):
- return self.session.delete(url,
- headers=headers, params=params,
data=data)
- else:
- return self.session.delete(url,
- headers=headers, params=params,
json=data)
- if method == 'PATCH':
- if isinstance(data, str):
- return self.session.request('PATCH', url,
- headers=headers,
params=params, data=data)
- else:
- return self.session.request('PATCH', url,
- headers=headers,
params=params, json=data)
-
- # should never happen
- raise CloudFlareAPIError(0, 'method not supported')
-
- def _network(self, method, headers, parts,
+ def _call_network(self, method, headers, parts,
identifier1=None, identifier2=None, identifier3=None,
params=None, data=None, files=None):
""" Cloudflare v4 API"""
- if self.logger:
- self.logger.debug('Call: %s,%s,%s,%s,%s,%s',
- str(parts[0]),
- str(identifier1),
- str(parts[1]),
- str(identifier2),
- str(parts[2]),
- str(identifier3))
- self.logger.debug('Call: optional params and data %s %s',
- str(params),
- str(data))
- if files:
- self.logger.debug('Call: upload file %r', files)
-
if (method is None) or (parts[0] is None):
# should never happen
raise CloudFlareInternalError(0, 'You must specify a method
and endpoint')
@@ -252,23 +196,16 @@
url += '/' + identifier3
if self.logger:
- self.logger.debug('Call: method and url %s %s', str(method),
str(url))
- self.logger.debug('Call: headers %s',
str(sanitize_secrets(headers)))
+ msg = build_curl(method, url, headers, params, data, files)
+ self.logger.debug('Call: emulated curl command ...\n' + msg)
try:
- if self.logger:
- self.logger.debug('Call: doit!')
- response = self._connection(method, url, headers, params,
data, files)
- if self.logger:
- self.logger.debug('Call: done!')
+ response = self.network(method, url, headers, params, data,
files)
except Exception as e:
if self.logger:
self.logger.debug('Call: exception! "%s"' % (e))
raise CloudFlareAPIError(0, 'connection failed.')
- if self.logger:
- self.logger.debug('Response: url %s', response.url)
-
# Create response_{type|code|data}
try:
response_type = response.headers['Content-Type']
@@ -335,13 +272,13 @@
params=None, data=None, files=None):
""" Cloudflare v4 API"""
- [response_type, response_code, response_data] =
self._network(method,
-
headers, parts,
-
identifier1,
-
identifier2,
-
identifier3,
-
params, data, files)
-
+ [response_type, response_code, response_data] =
self._call_network(method,
+
headers, parts,
+
identifier1,
+
identifier2,
+
identifier3,
+
params, data, files)
+
if response_type == 'application/json':
# API says it's JSON; so it better be parsable as JSON
# NDJSON is returned by Enterprise Log Share i.e.
/zones/:id/logs/received
@@ -607,7 +544,7 @@
try:
if self.logger:
self.logger.debug('Call: doit!')
- response = self._connection("GET", url)
+ response = self.network('GET', url)
if self.logger:
self.logger.debug('Call: done!')
except Exception as e:
@@ -888,14 +825,20 @@
branch = self
for element in a[0:-1]:
try:
- branch = getattr(branch, element)
+ if '-' in element:
+ branch = getattr(element, element.replace('-','_'))
+ else:
+ branch = getattr(branch, element)
except:
# should never happen
raise CloudFlareAPIError(0, 'api load name failed')
name = a[-1]
try:
- f = getattr(branch, name)
+ if '-' in name:
+ f = getattr(element, name.replace('-','_'))
+ else:
+ f = getattr(branch, name)
# already exists - don't let it overwrite
raise CloudFlareAPIError(0, 'api duplicate name found: %s/**%s**'
% ('/'.join(a[0:-1]), name))
except AttributeError:
@@ -916,7 +859,11 @@
# should never happen
raise CloudFlareAPIError(0, 'api load type mismatch')
- setattr(branch, name, f)
+ if '-' in name:
+ # dashes (vs underscores) cause issues in Python and other
languages
+ setattr(branch, name.replace('-','_'), f)
+ else:
+ setattr(branch, name, f)
def api_list(self, m=None, s=''):
"""recursive walk of the api tree returning a list of api calls"""
@@ -937,7 +884,9 @@
if 'delete' in d or 'get' in d or 'patch' in d or 'post' in d
or 'put' in d:
# only show the result if a call exists for this part
if '_parts' in d:
- w.append(s + '/' + n)
+ # handle underscores by returning the actual API call
vs the method name
+ w.append(str(a)[1:-1])
+ ## w.append(str(a)[1:-1].replace('/:id/','/'))
w = w + self.api_list(a, s + '/' + n)
return w
@@ -946,15 +895,13 @@
return api_decode_from_web(self._base._api_from_web())
- def __init__(self, email=None, token=None, certtoken=None, debug=False,
raw=False, use_sessions=True, profile=None):
+ def __init__(self, email=None, token=None, certtoken=None, debug=False,
raw=False, use_sessions=True, profile=None, base_url=None):
""" Cloudflare v4 API"""
- base_url = BASE_URL
-
try:
config = read_configs(profile)
- except:
- raise CloudFlareAPIError(0, 'profile/configuration read error')
+ except Exception as e:
+ raise CloudFlareAPIError(0, str(e))
# class creation values override all configuration values
if email is not None:
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/cloudflare-2.8.3/CloudFlare/network.py
new/cloudflare-2.8.15/CloudFlare/network.py
--- old/cloudflare-2.8.3/CloudFlare/network.py 1970-01-01 01:00:00.000000000
+0100
+++ new/cloudflare-2.8.15/CloudFlare/network.py 2020-06-24 02:38:21.000000000
+0200
@@ -0,0 +1,68 @@
+""" Network for Cloudflare API"""
+from __future__ import absolute_import
+
+import requests
+
+from .exceptions import CloudFlareAPIError
+
+class CFnetwork(object):
+ """ Network for Cloudflare API"""
+
+ def __init__(self, use_sessions=True):
+ """ Network for Cloudflare API"""
+
+ self.use_sessions = use_sessions
+ self.session = None
+
+ def __call__(self, method, url, headers=None, params=None, data=None,
files=None):
+ """ Network for Cloudflare API"""
+
+ if self.use_sessions:
+ if self.session is None:
+ self.session = requests.Session()
+ else:
+ self.session = requests
+
+ method = method.upper()
+
+ if method == 'GET':
+ return self.session.get(url,
+ headers=headers, params=params, data=data)
+ if method == 'POST':
+ if isinstance(data, str):
+ return self.session.post(url,
+ headers=headers, params=params,
data=data, files=files)
+ else:
+ return self.session.post(url,
+ headers=headers, params=params,
json=data, files=files)
+ if method == 'PUT':
+ if isinstance(data, str):
+ return self.session.put(url,
+ headers=headers, params=params,
data=data)
+ else:
+ return self.session.put(url,
+ headers=headers, params=params,
json=data)
+ if method == 'DELETE':
+ if isinstance(data, str):
+ return self.session.delete(url,
+ headers=headers, params=params,
data=data)
+ else:
+ return self.session.delete(url,
+ headers=headers, params=params,
json=data)
+ if method == 'PATCH':
+ if isinstance(data, str):
+ return self.session.request('PATCH', url,
+ headers=headers, params=params,
data=data)
+ else:
+ return self.session.request('PATCH', url,
+ headers=headers, params=params,
json=data)
+
+ # should never happen
+ raise CloudFlareAPIError(0, 'method not supported')
+
+ def __del__(self):
+ """ Network for Cloudflare API"""
+
+ if self.use_sessions and self.session:
+ self.session.close()
+ self.session = None
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/cloudflare-2.8.3/CloudFlare/read_configs.py
new/cloudflare-2.8.15/CloudFlare/read_configs.py
--- old/cloudflare-2.8.3/CloudFlare/read_configs.py 2020-02-05
01:18:44.000000000 +0100
+++ new/cloudflare-2.8.15/CloudFlare/read_configs.py 2020-07-18
01:24:02.000000000 +0200
@@ -11,13 +11,14 @@
""" reading the config file for Cloudflare API"""
# We return all these values
- config = {'email': None, 'token': None, 'certtoken': None, 'extras': None,
'profile': None}
+ config = {'email': None, 'token': None, 'certtoken': None, 'extras': None,
'base_url': None, 'profile': None}
# envioronment variables override config files - so setup first
config['email'] = os.getenv('CF_API_EMAIL')
config['token'] = os.getenv('CF_API_KEY')
config['certtoken'] = os.getenv('CF_API_CERTKEY')
config['extras'] = os.getenv('CF_API_EXTRAS')
+ config['base_url'] = os.getenv('CF_API_URL')
if profile is None:
profile = 'CloudFlare'
config['profile'] = profile
@@ -42,7 +43,7 @@
# however section name is missing - this is an error
raise Exception("%s: configuration section missing" % (profile))
- for option in ['email', 'token', 'certtoken', 'extras']:
+ for option in ['email', 'token', 'certtoken', 'extras', 'base_url']:
if option not in config or config[option] is None:
try:
if option == 'extras':
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/cloudflare-2.8.3/CloudFlare/utils.py
new/cloudflare-2.8.15/CloudFlare/utils.py
--- old/cloudflare-2.8.3/CloudFlare/utils.py 2020-01-08 23:41:32.000000000
+0100
+++ new/cloudflare-2.8.15/CloudFlare/utils.py 2020-07-22 02:18:18.000000000
+0200
@@ -33,3 +33,38 @@
secrets_copy['Authorization'] = redacted_phrase
return secrets_copy
+
+def build_curl(method, url, headers, params, data, files):
+ """ misc utilities for Cloudflare API"""
+
+ msg = []
+ # url
+ url_full = url
+ if params is not None:
+ for k in params:
+ if k is None:
+ continue
+ url_full += '&%s=%s' % (k, params[k])
+ url_full = url_full.replace('&', '?', 1)
+ msg.append(' curl -X %s "%s" \\' % (str(method), str(url_full)))
+ # headers
+ h = sanitize_secrets(headers)
+ for k in h:
+ if k is None:
+ continue
+ msg.append(' -H "%s: %s" \\' % (k, h[k]))
+ # data
+ if data is not None:
+ try:
+ str_data = json.dumps(data)
+ except:
+ str_data = str(data)
+ msg.append(' --data \'%s\' \\' % (str_data.replace('\n', '
')))
+ # files
+ if files is not None:
+ msg.append(' --form "file=@%s" \\' % (files))
+
+ # remove the last \ from the last line.
+ msg[-1] = msg[-1][:-1]
+
+ return '\n'.join(msg)
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/cloudflare-2.8.3/PKG-INFO
new/cloudflare-2.8.15/PKG-INFO
--- old/cloudflare-2.8.3/PKG-INFO 2020-06-23 03:46:31.000000000 +0200
+++ new/cloudflare-2.8.15/PKG-INFO 2021-01-01 01:20:46.000000000 +0100
@@ -1,6 +1,6 @@
Metadata-Version: 1.1
Name: cloudflare
-Version: 2.8.3
+Version: 2.8.15
Summary: Python wrapper for the Cloudflare v4 API
Home-page: https://github.com/cloudflare/python-cloudflare
Author: Martin J. Levy
@@ -208,6 +208,7 @@
- Account email (only if an API Key is being used)
- Optional Origin-CA Certificate Token
- Optional Debug flag (True/False)
+ - Optional Profile name (the default is ``Cloudflare``)
.. code:: python
@@ -217,7 +218,7 @@
cf = CloudFlare.CloudFlare()
# A minimal call with debug enabled
- cf = CloudFlare.CloudFlare(debug=True))
+ cf = CloudFlare.CloudFlare(debug=True)
# An authenticated call using an API Token (note the missing
email)
cf =
CloudFlare.CloudFlare(token='00000000000000000000000000000000')
@@ -832,6 +833,85 @@
COUNT=1 PAGE=7 PER_PAGE=5 TOTAL_COUNT=31 TOTAL_PAGES=7 --
varius.example vehicula.example velit.example velit.example vitae.example
COUNT=5 PAGE=6 PER_PAGE=5 TOTAL_COUNT=31 TOTAL_PAGES=7 --
vivamus.example
+ Paging thru lists (using cursors)
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+ Some API calls use cursors to read beyond the initally returned values.
+ See the API page in order to see which API calls do this.
+
+ ::
+
+ $ ACCOUNT_ID="00000000000000000000000000000000"
+ $ LIST_ID="00000000000000000000000000000000"
+ $
+ $ cli4 --raw
/accounts/::${ACCOUNT_ID}/rules/lists/::${LIST_ID}/items > /tmp/page1.json
+ $ after=`jq -r '.result_info.cursors.after' < /tmp/page1.json`
+ $ echo "after=$after"
+ after=Mxm4GVmKjYbFjy2VxMPipnJigm1M_s6lCS9ABR9wx-RM2A
+ $
+
+ Once we have the ``after`` value, we can pass it along in order to read
+ the next hunk of values. We finish when ``after`` returns as null (or
+ isn't present).
+
+ ::
+
+ $ cli4 --raw cursor="$after"
/accounts/::${ACCOUNT_ID}/rules/lists/::${LIST_ID}/items > /tmp/page2.json
+ $ after=`jq -r '.result_info.cursors.after' < /tmp/page2.json`
+ $ echo "after=$after"
+ after=null
+ $
+
+ We can see the results now in two files.
+
+ ::
+
+ $ jq -c '.result[]' < /tmp/page1.json | wc -l
+ 25
+ $
+
+ $ jq -c '.result[]' < /tmp/page2.json | wc -l
+ 5
+ $
+
+ $ for f in /tmp/page?.json ; do jq -r '.result[]|.id,.ip,.comment'
< $f | paste - - - ; done | column -s' ' -t
+ 0fe44928258549feb47126a966fbf4a0 0.0.0.0 all zero
+ 2e1e02120f5e466f8c0e26375e4cf4c8 1.0.0.1 Cloudflare DNS
a
+ 9ca5fd0ac6f54fdbb9dedd3fb72ce2da 1.1.1.1 Cloudflare DNS
b
+ b3654987446743738c782f36ebe074f5 10.0.0.0/8 RFC1918 space
+ 90bec8ce37d242faa2e27d1e78c1d8e2 103.21.244.0/22 Cloudflare IP
+ 970a3c810cda41af9bef2c36a1892f7e 103.22.200.0/22 Cloudflare IP
+ 3ec8516158bf4f3cac18210f611ee541 103.31.4.0/22 Cloudflare IP
+ ee9d268367204e6bb8e5e4c907f22de8 104.16.0.0/12 Cloudflare IP
+ 93ae02eda9774c45840af367a02fe529 108.162.192.0/18 Cloudflare IP
+ 62891ebf6db44aa494d79a6401af185e 131.0.72.0/22 Cloudflare IP
+ cac40cd940cc470582b8c912a8a12bea 141.101.64.0/18 Cloudflare IP
+ f6d5eacd81a2407f8e0d81caee21e7f8 162.158.0.0/15 Cloudflare IP
+ 3d538dfc38ab471d9d3fe78332acfa4e 172.16.0.0/12 RFC1918 space
+ f353cb8f98424837ad35382a22b9debe 172.64.0.0/13 Cloudflare IP
+ 78f3e1a0bafc41f88d4d40ad49a642e0 173.245.48.0/20 Cloudflare IP
+ c23a545475c54c32a7681c6b508d3e80 188.114.96.0/20 Cloudflare IP
+ f693237c9e294fe481221cbc2d7c20ef 190.93.240.0/20 Cloudflare IP
+ 6d465ab3a0994c07827ebdcf8f34d977 192.168.0.0/16 RFC1918 space
+ 1ad1e634b3664bac939086185c62faf7 197.234.240.0/22 Cloudflare IP
+ 5d2968e7b3114d8e869a379d71c8ba86 198.41.128.0/17 Cloudflare IP
+ 6a69de60b31448fa864f0a3ac5abe8d0 224.0.0.0/24 Multicast
+ 30749cce89af4ab3a80e308294f46a46 240.0.0.0/4 Class E
+ 2b32c67ea4d044628abe39f28662d8f0 255.255.255.255 all ones
+ cc7cd828b2fb4bcfb9391c2d3ef8d068 2400:cb00::/32 Cloudflare IP
+ b30d4cbd7dcd48729e8ebeda552e48a8 2405:8100::/32 Cloudflare IP
+ 49db60758c8344959c338a74afc9748a 2405:b500::/32 Cloudflare IP
+ 96e9eca1923c40d5a84865145f5a5d6a 2606:4700::/32 Cloudflare IP
+ 21bc52a26e10405d89b7180ddcf49302 2803:f800::/32 Cloudflare IP
+ ff78f842188e4b869eb5389ae9ab8f41 2a06:98c0::/29 Cloudflare IP
+ 0880cdfc40b14f6fa0639522a728859d 2c0f:f248::/32 Cloudflare IP
+ $
+
+ The ``result_info.cursors`` area also contains a ``before`` value for
+ reverse scrolling.
+
+ As with ``per_page`` scrolling, raw mode is used.
+
DNSSEC CLI examples
~~~~~~~~~~~~~~~~~~~
@@ -1069,6 +1149,64 @@
Refer to the Cloudflare Workers API documentation for more information.
+ Cloudflare GraphQL
+ ------------------
+
+ The GraphQL interface can be accessed via the command line or via
+ Python.
+
+ ::
+
+ query="""
+ query {
+ viewer {
+ zones(filter: {zoneTag: "%s"} ) {
+ httpRequests1dGroups(limit:40, filter:{date_lt: "%s",
date_gt: "%s"}) {
+ sum { countryMap { bytes, requests,
clientCountryName } }
+ dimensions { date }
+ }
+ }
+ }
+ }
+ """ % (zone_id, date_before[0:10], date_after[0:10])
+
+ r = cf.graphql.post(data={'query':query})
+
+ httpRequests1dGroups = zone_info =
r['data']['viewer']['zones'][0]['httpRequests1dGroups']
+
+ See the `examples/example\_graphql.sh <examples/example_graphql.sh>`__
+ and `examples/example\_graphql.py <examples/example_graphql.py>`__
files
+ for working examples. Here is the working example of the shell version:
+
+ ::
+
+ $ examples/example_graphql.sh example.com
+ 2020-07-14T02:00:00Z 34880
+ 2020-07-14T03:00:00Z 18953
+ 2020-07-14T04:00:00Z 28700
+ 2020-07-14T05:00:00Z 2358
+ 2020-07-14T06:00:00Z 34905
+ 2020-07-14T07:00:00Z 779
+ 2020-07-14T08:00:00Z 35450
+ 2020-07-14T10:00:00Z 17803
+ 2020-07-14T11:00:00Z 32678
+ 2020-07-14T12:00:00Z 19947
+ 2020-07-14T13:00:00Z 4956
+ 2020-07-14T14:00:00Z 34585
+ 2020-07-14T15:00:00Z 3022
+ 2020-07-14T16:00:00Z 5224
+ 2020-07-14T18:00:00Z 79482
+ 2020-07-14T21:00:00Z 10609
+ 2020-07-14T22:00:00Z 5740
+ 2020-07-14T23:00:00Z 2545
+ 2020-07-15T01:00:00Z 10777
+ $
+
+ For more information on how to use GraphQL at Cloudflare, refer to the
+ `Cloudflare GraphQL Analytics
+ API <https://developers.cloudflare.com/analytics/graphql-api>`__. It
+ contains a full overview of Cloudflare's GraphQL features and keywords.
+
Implemented API calls
---------------------
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/cloudflare-2.8.3/README.rst
new/cloudflare-2.8.15/README.rst
--- old/cloudflare-2.8.3/README.rst 2020-06-20 07:53:59.000000000 +0200
+++ new/cloudflare-2.8.15/README.rst 2020-12-31 23:06:35.000000000 +0100
@@ -200,6 +200,7 @@
- Account email (only if an API Key is being used)
- Optional Origin-CA Certificate Token
- Optional Debug flag (True/False)
+- Optional Profile name (the default is ``Cloudflare``)
.. code:: python
@@ -209,7 +210,7 @@
cf = CloudFlare.CloudFlare()
# A minimal call with debug enabled
- cf = CloudFlare.CloudFlare(debug=True))
+ cf = CloudFlare.CloudFlare(debug=True)
# An authenticated call using an API Token (note the missing email)
cf = CloudFlare.CloudFlare(token='00000000000000000000000000000000')
@@ -824,6 +825,85 @@
COUNT=1 PAGE=7 PER_PAGE=5 TOTAL_COUNT=31 TOTAL_PAGES=7 -- varius.example
vehicula.example velit.example velit.example vitae.example
COUNT=5 PAGE=6 PER_PAGE=5 TOTAL_COUNT=31 TOTAL_PAGES=7 -- vivamus.example
+Paging thru lists (using cursors)
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+Some API calls use cursors to read beyond the initally returned values.
+See the API page in order to see which API calls do this.
+
+::
+
+ $ ACCOUNT_ID="00000000000000000000000000000000"
+ $ LIST_ID="00000000000000000000000000000000"
+ $
+ $ cli4 --raw /accounts/::${ACCOUNT_ID}/rules/lists/::${LIST_ID}/items >
/tmp/page1.json
+ $ after=`jq -r '.result_info.cursors.after' < /tmp/page1.json`
+ $ echo "after=$after"
+ after=Mxm4GVmKjYbFjy2VxMPipnJigm1M_s6lCS9ABR9wx-RM2A
+ $
+
+Once we have the ``after`` value, we can pass it along in order to read
+the next hunk of values. We finish when ``after`` returns as null (or
+isn't present).
+
+::
+
+ $ cli4 --raw cursor="$after"
/accounts/::${ACCOUNT_ID}/rules/lists/::${LIST_ID}/items > /tmp/page2.json
+ $ after=`jq -r '.result_info.cursors.after' < /tmp/page2.json`
+ $ echo "after=$after"
+ after=null
+ $
+
+We can see the results now in two files.
+
+::
+
+ $ jq -c '.result[]' < /tmp/page1.json | wc -l
+ 25
+ $
+
+ $ jq -c '.result[]' < /tmp/page2.json | wc -l
+ 5
+ $
+
+ $ for f in /tmp/page?.json ; do jq -r '.result[]|.id,.ip,.comment' < $f |
paste - - - ; done | column -s' ' -t
+ 0fe44928258549feb47126a966fbf4a0 0.0.0.0 all zero
+ 2e1e02120f5e466f8c0e26375e4cf4c8 1.0.0.1 Cloudflare DNS a
+ 9ca5fd0ac6f54fdbb9dedd3fb72ce2da 1.1.1.1 Cloudflare DNS b
+ b3654987446743738c782f36ebe074f5 10.0.0.0/8 RFC1918 space
+ 90bec8ce37d242faa2e27d1e78c1d8e2 103.21.244.0/22 Cloudflare IP
+ 970a3c810cda41af9bef2c36a1892f7e 103.22.200.0/22 Cloudflare IP
+ 3ec8516158bf4f3cac18210f611ee541 103.31.4.0/22 Cloudflare IP
+ ee9d268367204e6bb8e5e4c907f22de8 104.16.0.0/12 Cloudflare IP
+ 93ae02eda9774c45840af367a02fe529 108.162.192.0/18 Cloudflare IP
+ 62891ebf6db44aa494d79a6401af185e 131.0.72.0/22 Cloudflare IP
+ cac40cd940cc470582b8c912a8a12bea 141.101.64.0/18 Cloudflare IP
+ f6d5eacd81a2407f8e0d81caee21e7f8 162.158.0.0/15 Cloudflare IP
+ 3d538dfc38ab471d9d3fe78332acfa4e 172.16.0.0/12 RFC1918 space
+ f353cb8f98424837ad35382a22b9debe 172.64.0.0/13 Cloudflare IP
+ 78f3e1a0bafc41f88d4d40ad49a642e0 173.245.48.0/20 Cloudflare IP
+ c23a545475c54c32a7681c6b508d3e80 188.114.96.0/20 Cloudflare IP
+ f693237c9e294fe481221cbc2d7c20ef 190.93.240.0/20 Cloudflare IP
+ 6d465ab3a0994c07827ebdcf8f34d977 192.168.0.0/16 RFC1918 space
+ 1ad1e634b3664bac939086185c62faf7 197.234.240.0/22 Cloudflare IP
+ 5d2968e7b3114d8e869a379d71c8ba86 198.41.128.0/17 Cloudflare IP
+ 6a69de60b31448fa864f0a3ac5abe8d0 224.0.0.0/24 Multicast
+ 30749cce89af4ab3a80e308294f46a46 240.0.0.0/4 Class E
+ 2b32c67ea4d044628abe39f28662d8f0 255.255.255.255 all ones
+ cc7cd828b2fb4bcfb9391c2d3ef8d068 2400:cb00::/32 Cloudflare IP
+ b30d4cbd7dcd48729e8ebeda552e48a8 2405:8100::/32 Cloudflare IP
+ 49db60758c8344959c338a74afc9748a 2405:b500::/32 Cloudflare IP
+ 96e9eca1923c40d5a84865145f5a5d6a 2606:4700::/32 Cloudflare IP
+ 21bc52a26e10405d89b7180ddcf49302 2803:f800::/32 Cloudflare IP
+ ff78f842188e4b869eb5389ae9ab8f41 2a06:98c0::/29 Cloudflare IP
+ 0880cdfc40b14f6fa0639522a728859d 2c0f:f248::/32 Cloudflare IP
+ $
+
+The ``result_info.cursors`` area also contains a ``before`` value for
+reverse scrolling.
+
+As with ``per_page`` scrolling, raw mode is used.
+
DNSSEC CLI examples
~~~~~~~~~~~~~~~~~~~
@@ -1061,6 +1141,64 @@
Refer to the Cloudflare Workers API documentation for more information.
+Cloudflare GraphQL
+------------------
+
+The GraphQL interface can be accessed via the command line or via
+Python.
+
+::
+
+ query="""
+ query {
+ viewer {
+ zones(filter: {zoneTag: "%s"} ) {
+ httpRequests1dGroups(limit:40, filter:{date_lt: "%s", date_gt:
"%s"}) {
+ sum { countryMap { bytes, requests, clientCountryName } }
+ dimensions { date }
+ }
+ }
+ }
+ }
+ """ % (zone_id, date_before[0:10], date_after[0:10])
+
+ r = cf.graphql.post(data={'query':query})
+
+ httpRequests1dGroups = zone_info =
r['data']['viewer']['zones'][0]['httpRequests1dGroups']
+
+See the `examples/example\_graphql.sh <examples/example_graphql.sh>`__
+and `examples/example\_graphql.py <examples/example_graphql.py>`__ files
+for working examples. Here is the working example of the shell version:
+
+::
+
+ $ examples/example_graphql.sh example.com
+ 2020-07-14T02:00:00Z 34880
+ 2020-07-14T03:00:00Z 18953
+ 2020-07-14T04:00:00Z 28700
+ 2020-07-14T05:00:00Z 2358
+ 2020-07-14T06:00:00Z 34905
+ 2020-07-14T07:00:00Z 779
+ 2020-07-14T08:00:00Z 35450
+ 2020-07-14T10:00:00Z 17803
+ 2020-07-14T11:00:00Z 32678
+ 2020-07-14T12:00:00Z 19947
+ 2020-07-14T13:00:00Z 4956
+ 2020-07-14T14:00:00Z 34585
+ 2020-07-14T15:00:00Z 3022
+ 2020-07-14T16:00:00Z 5224
+ 2020-07-14T18:00:00Z 79482
+ 2020-07-14T21:00:00Z 10609
+ 2020-07-14T22:00:00Z 5740
+ 2020-07-14T23:00:00Z 2545
+ 2020-07-15T01:00:00Z 10777
+ $
+
+For more information on how to use GraphQL at Cloudflare, refer to the
+`Cloudflare GraphQL Analytics
+API <https://developers.cloudflare.com/analytics/graphql-api>`__. It
+contains a full overview of Cloudflare's GraphQL features and keywords.
+
Implemented API calls
---------------------
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/cloudflare-2.8.3/cli4/cli4.py
new/cloudflare-2.8.15/cli4/cli4.py
--- old/cloudflare-2.8.3/cli4/cli4.py 2020-06-23 03:23:50.000000000 +0200
+++ new/cloudflare-2.8.15/cli4/cli4.py 2020-07-24 20:07:15.000000000 +0200
@@ -17,20 +17,22 @@
import CloudFlare
from . import converters
-def dump_commands():
+def dump_commands(cf):
"""dump a tree of all the known API commands"""
- cf = CloudFlare.CloudFlare()
w = cf.api_list()
sys.stdout.write('\n'.join(w) + '\n')
-def dump_commands_from_web():
+def dump_commands_from_web(cf):
"""dump a tree of all the known API commands - from web"""
- cf = CloudFlare.CloudFlare()
w = cf.api_from_web()
for r in w:
if r['deprecated']:
- continue
- sys.stdout.write('%-6s %s\n' % (r['action'], r['cmd']))
+ if r['deprecated_already']:
+ sys.stdout.write('%-6s %s ; deprecated %s - expired!\n' %
(r['action'], r['cmd'], r['deprecated_date']))
+ else:
+ sys.stdout.write('%-6s %s ; deprecated %s\n' % (r['action'],
r['cmd'], r['deprecated_date']))
+ else:
+ sys.stdout.write('%-6s %s\n' % (r['action'], r['cmd']))
def run_command(cf, method, command, params=None, content=None, files=None):
"""run the command line"""
@@ -62,7 +64,7 @@
if len(element) in [32, 40, 48] and hex_only.match(element):
# raw identifier - lets just use it as-is
identifier1 = element
- if len(element) == 36 and uuid_value.match(element):
+ elif len(element) == 36 and uuid_value.match(element):
# uuid identifier - lets just use it as-is
identifier1 = element
elif element[0] == ':':
@@ -97,7 +99,7 @@
if len(element) in [32, 40, 48] and hex_only.match(element):
# raw identifier - lets just use it as-is
identifier2 = element
- if len(element) == 36 and uuid_value.match(element):
+ elif len(element) == 36 and uuid_value.match(element):
# uuid identifier - lets just use it as-is
identifier2 = element
elif element[0] == ':':
@@ -128,11 +130,14 @@
if len(element) in [32, 40, 48] and hex_only.match(element):
# raw identifier - lets just use it as-is
identifier3 = element
- if len(element) == 36 and uuid_value.match(element):
+ elif len(element) == 36 and uuid_value.match(element):
# uuid identifier - lets just use it as-is
identifier3 = element
elif waf_rules.match(element):
identifier3 = element
+ elif element[0] == ':':
+ # raw string - used for workers script_names
+ identifier3 = element[1:]
else:
if len(cmd) >= 6 and cmd[0] == 'accounts' and cmd[2] ==
'storage' and cmd[3] == 'kv' and cmd[4] == 'namespaces' and cmd[6] == 'values':
identifier3 = element
@@ -141,7 +146,11 @@
raise e
else:
try:
- m = getattr(m, element)
+ # dashes (vs underscores) cause issues in Python and other
languages
+ if '-' in element:
+ m = getattr(m, element.replace('-','_'))
+ else:
+ m = getattr(m, element)
cmd.append(element)
except AttributeError:
# the verb/element was not found
@@ -329,11 +338,13 @@
method = 'DELETE'
if dump:
- dump_commands()
+ cf = CloudFlare.CloudFlare(debug=verbose, raw=raw, profile=profile)
+ dump_commands(cf)
sys.exit(0)
if dump_from_web:
- dump_commands_from_web()
+ cf = CloudFlare.CloudFlare(debug=verbose, raw=raw, profile=profile)
+ dump_commands_from_web(cf)
sys.exit(0)
digits_only = re.compile('^-?[0-9]+$')
@@ -380,7 +391,10 @@
#value = json.loads(value) - changed to yaml code to remove
unicode string issues
if yaml is None:
sys.exit('cli4: install yaml support')
- value = yaml.safe_load(value_string)
+ try:
+ value = yaml.safe_load(value_string)
+ except yaml.parser.ParserError as e:
+ raise ValueError
except ValueError:
sys.exit('cli4: %s="%s" - can\'t parse json value' %
(tag_string, value_string))
elif value_string[0] == '@':
@@ -404,10 +418,8 @@
if tag_string == '':
# There's no tag; it's just an unnamed list
if params is None:
- params = []
- try:
- params.append(value)
- except AttributeError:
+ params = value
+ else:
sys.exit('cli4: %s=%s - param error. Can\'t mix unnamed and
named list' %
(tag_string, value_string))
else:
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/cloudflare-2.8.3/cloudflare.egg-info/PKG-INFO
new/cloudflare-2.8.15/cloudflare.egg-info/PKG-INFO
--- old/cloudflare-2.8.3/cloudflare.egg-info/PKG-INFO 2020-06-23
03:46:31.000000000 +0200
+++ new/cloudflare-2.8.15/cloudflare.egg-info/PKG-INFO 2021-01-01
01:20:46.000000000 +0100
@@ -1,6 +1,6 @@
Metadata-Version: 1.1
Name: cloudflare
-Version: 2.8.3
+Version: 2.8.15
Summary: Python wrapper for the Cloudflare v4 API
Home-page: https://github.com/cloudflare/python-cloudflare
Author: Martin J. Levy
@@ -208,6 +208,7 @@
- Account email (only if an API Key is being used)
- Optional Origin-CA Certificate Token
- Optional Debug flag (True/False)
+ - Optional Profile name (the default is ``Cloudflare``)
.. code:: python
@@ -217,7 +218,7 @@
cf = CloudFlare.CloudFlare()
# A minimal call with debug enabled
- cf = CloudFlare.CloudFlare(debug=True))
+ cf = CloudFlare.CloudFlare(debug=True)
# An authenticated call using an API Token (note the missing
email)
cf =
CloudFlare.CloudFlare(token='00000000000000000000000000000000')
@@ -832,6 +833,85 @@
COUNT=1 PAGE=7 PER_PAGE=5 TOTAL_COUNT=31 TOTAL_PAGES=7 --
varius.example vehicula.example velit.example velit.example vitae.example
COUNT=5 PAGE=6 PER_PAGE=5 TOTAL_COUNT=31 TOTAL_PAGES=7 --
vivamus.example
+ Paging thru lists (using cursors)
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+ Some API calls use cursors to read beyond the initally returned values.
+ See the API page in order to see which API calls do this.
+
+ ::
+
+ $ ACCOUNT_ID="00000000000000000000000000000000"
+ $ LIST_ID="00000000000000000000000000000000"
+ $
+ $ cli4 --raw
/accounts/::${ACCOUNT_ID}/rules/lists/::${LIST_ID}/items > /tmp/page1.json
+ $ after=`jq -r '.result_info.cursors.after' < /tmp/page1.json`
+ $ echo "after=$after"
+ after=Mxm4GVmKjYbFjy2VxMPipnJigm1M_s6lCS9ABR9wx-RM2A
+ $
+
+ Once we have the ``after`` value, we can pass it along in order to read
+ the next hunk of values. We finish when ``after`` returns as null (or
+ isn't present).
+
+ ::
+
+ $ cli4 --raw cursor="$after"
/accounts/::${ACCOUNT_ID}/rules/lists/::${LIST_ID}/items > /tmp/page2.json
+ $ after=`jq -r '.result_info.cursors.after' < /tmp/page2.json`
+ $ echo "after=$after"
+ after=null
+ $
+
+ We can see the results now in two files.
+
+ ::
+
+ $ jq -c '.result[]' < /tmp/page1.json | wc -l
+ 25
+ $
+
+ $ jq -c '.result[]' < /tmp/page2.json | wc -l
+ 5
+ $
+
+ $ for f in /tmp/page?.json ; do jq -r '.result[]|.id,.ip,.comment'
< $f | paste - - - ; done | column -s' ' -t
+ 0fe44928258549feb47126a966fbf4a0 0.0.0.0 all zero
+ 2e1e02120f5e466f8c0e26375e4cf4c8 1.0.0.1 Cloudflare DNS
a
+ 9ca5fd0ac6f54fdbb9dedd3fb72ce2da 1.1.1.1 Cloudflare DNS
b
+ b3654987446743738c782f36ebe074f5 10.0.0.0/8 RFC1918 space
+ 90bec8ce37d242faa2e27d1e78c1d8e2 103.21.244.0/22 Cloudflare IP
+ 970a3c810cda41af9bef2c36a1892f7e 103.22.200.0/22 Cloudflare IP
+ 3ec8516158bf4f3cac18210f611ee541 103.31.4.0/22 Cloudflare IP
+ ee9d268367204e6bb8e5e4c907f22de8 104.16.0.0/12 Cloudflare IP
+ 93ae02eda9774c45840af367a02fe529 108.162.192.0/18 Cloudflare IP
+ 62891ebf6db44aa494d79a6401af185e 131.0.72.0/22 Cloudflare IP
+ cac40cd940cc470582b8c912a8a12bea 141.101.64.0/18 Cloudflare IP
+ f6d5eacd81a2407f8e0d81caee21e7f8 162.158.0.0/15 Cloudflare IP
+ 3d538dfc38ab471d9d3fe78332acfa4e 172.16.0.0/12 RFC1918 space
+ f353cb8f98424837ad35382a22b9debe 172.64.0.0/13 Cloudflare IP
+ 78f3e1a0bafc41f88d4d40ad49a642e0 173.245.48.0/20 Cloudflare IP
+ c23a545475c54c32a7681c6b508d3e80 188.114.96.0/20 Cloudflare IP
+ f693237c9e294fe481221cbc2d7c20ef 190.93.240.0/20 Cloudflare IP
+ 6d465ab3a0994c07827ebdcf8f34d977 192.168.0.0/16 RFC1918 space
+ 1ad1e634b3664bac939086185c62faf7 197.234.240.0/22 Cloudflare IP
+ 5d2968e7b3114d8e869a379d71c8ba86 198.41.128.0/17 Cloudflare IP
+ 6a69de60b31448fa864f0a3ac5abe8d0 224.0.0.0/24 Multicast
+ 30749cce89af4ab3a80e308294f46a46 240.0.0.0/4 Class E
+ 2b32c67ea4d044628abe39f28662d8f0 255.255.255.255 all ones
+ cc7cd828b2fb4bcfb9391c2d3ef8d068 2400:cb00::/32 Cloudflare IP
+ b30d4cbd7dcd48729e8ebeda552e48a8 2405:8100::/32 Cloudflare IP
+ 49db60758c8344959c338a74afc9748a 2405:b500::/32 Cloudflare IP
+ 96e9eca1923c40d5a84865145f5a5d6a 2606:4700::/32 Cloudflare IP
+ 21bc52a26e10405d89b7180ddcf49302 2803:f800::/32 Cloudflare IP
+ ff78f842188e4b869eb5389ae9ab8f41 2a06:98c0::/29 Cloudflare IP
+ 0880cdfc40b14f6fa0639522a728859d 2c0f:f248::/32 Cloudflare IP
+ $
+
+ The ``result_info.cursors`` area also contains a ``before`` value for
+ reverse scrolling.
+
+ As with ``per_page`` scrolling, raw mode is used.
+
DNSSEC CLI examples
~~~~~~~~~~~~~~~~~~~
@@ -1069,6 +1149,64 @@
Refer to the Cloudflare Workers API documentation for more information.
+ Cloudflare GraphQL
+ ------------------
+
+ The GraphQL interface can be accessed via the command line or via
+ Python.
+
+ ::
+
+ query="""
+ query {
+ viewer {
+ zones(filter: {zoneTag: "%s"} ) {
+ httpRequests1dGroups(limit:40, filter:{date_lt: "%s",
date_gt: "%s"}) {
+ sum { countryMap { bytes, requests,
clientCountryName } }
+ dimensions { date }
+ }
+ }
+ }
+ }
+ """ % (zone_id, date_before[0:10], date_after[0:10])
+
+ r = cf.graphql.post(data={'query':query})
+
+ httpRequests1dGroups = zone_info =
r['data']['viewer']['zones'][0]['httpRequests1dGroups']
+
+ See the `examples/example\_graphql.sh <examples/example_graphql.sh>`__
+ and `examples/example\_graphql.py <examples/example_graphql.py>`__
files
+ for working examples. Here is the working example of the shell version:
+
+ ::
+
+ $ examples/example_graphql.sh example.com
+ 2020-07-14T02:00:00Z 34880
+ 2020-07-14T03:00:00Z 18953
+ 2020-07-14T04:00:00Z 28700
+ 2020-07-14T05:00:00Z 2358
+ 2020-07-14T06:00:00Z 34905
+ 2020-07-14T07:00:00Z 779
+ 2020-07-14T08:00:00Z 35450
+ 2020-07-14T10:00:00Z 17803
+ 2020-07-14T11:00:00Z 32678
+ 2020-07-14T12:00:00Z 19947
+ 2020-07-14T13:00:00Z 4956
+ 2020-07-14T14:00:00Z 34585
+ 2020-07-14T15:00:00Z 3022
+ 2020-07-14T16:00:00Z 5224
+ 2020-07-14T18:00:00Z 79482
+ 2020-07-14T21:00:00Z 10609
+ 2020-07-14T22:00:00Z 5740
+ 2020-07-14T23:00:00Z 2545
+ 2020-07-15T01:00:00Z 10777
+ $
+
+ For more information on how to use GraphQL at Cloudflare, refer to the
+ `Cloudflare GraphQL Analytics
+ API <https://developers.cloudflare.com/analytics/graphql-api>`__. It
+ contains a full overview of Cloudflare's GraphQL features and keywords.
+
Implemented API calls
---------------------
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/cloudflare-2.8.3/cloudflare.egg-info/SOURCES.txt
new/cloudflare-2.8.15/cloudflare.egg-info/SOURCES.txt
--- old/cloudflare-2.8.3/cloudflare.egg-info/SOURCES.txt 2020-06-23
03:46:31.000000000 +0200
+++ new/cloudflare-2.8.15/cloudflare.egg-info/SOURCES.txt 2021-01-01
01:20:46.000000000 +0100
@@ -10,6 +10,7 @@
CloudFlare/cloudflare.py
CloudFlare/exceptions.py
CloudFlare/logging_helper.py
+CloudFlare/network.py
CloudFlare/read_configs.py
CloudFlare/utils.py
cli4/__init__.py
@@ -33,6 +34,8 @@
examples/example_delete_zone_entry.py
examples/example_dns_export.py
examples/example_dnssec_settings.py
+examples/example_graphql.py
+examples/example_graphql.sh
examples/example_ips.py
examples/example_list_api_from_web.py
examples/example_page_rules.sh
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/cloudflare-2.8.3/examples/example_graphql.py
new/cloudflare-2.8.15/examples/example_graphql.py
--- old/cloudflare-2.8.3/examples/example_graphql.py 1970-01-01
01:00:00.000000000 +0100
+++ new/cloudflare-2.8.15/examples/example_graphql.py 2020-07-15
03:45:23.000000000 +0200
@@ -0,0 +1,78 @@
+#!/usr/bin/env python
+"""Cloudflare API code - example"""
+
+import os
+import sys
+import time
+import datetime
+import pytz
+
+sys.path.insert(0, os.path.abspath('..'))
+import CloudFlare
+
+def now_iso8601_time(h_delta):
+ """Cloudflare API code - example"""
+
+ t = time.time() - (h_delta * 3600)
+ r = datetime.datetime.fromtimestamp(int(t),
tz=pytz.timezone("UTC")).strftime('%Y-%m-%dT%H:%M:%SZ')
+ return r
+
+def main():
+ """Cloudflare API code - example"""
+
+ # Grab the zone name
+ try:
+ zone_name = sys.argv[1]
+ params = {'name':zone_name, 'per_page':1}
+ except IndexError:
+ exit('usage: example_graphql zone')
+
+ cf = CloudFlare.CloudFlare()
+
+ # grab the zone identifier
+ try:
+ zones = cf.zones.get(params=params)
+ except CloudFlare.exceptions.CloudFlareAPIError as e:
+ exit('/zones.get %d %s - api call failed' % (e, e))
+ except Exception as e:
+ exit('/zones - %s - api call failed' % (e))
+
+ date_before = now_iso8601_time(0) # now
+ date_after = now_iso8601_time(7 * 24) # 7 days worth
+
+ zone_id = zones[0]['id']
+ query="""
+ query {
+ viewer {
+ zones(filter: {zoneTag: "%s"} ) {
+ httpRequests1dGroups(limit:40, filter:{date_lt: "%s", date_gt:
"%s"}) {
+ sum { countryMap { bytes, requests, clientCountryName } }
+ dimensions { date }
+ }
+ }
+ }
+ }
+ """ % (zone_id, date_before[0:10], date_after[0:10]) # only use yyyy-mm-dd
part for httpRequests1dGroups
+
+ # query - always a post
+ try:
+ r = cf.graphql.post(data={'query':query})
+ except CloudFlare.exceptions.CloudFlareAPIError as e:
+ exit('/graphql.post %d %s - api call failed' % (e, e))
+
+ ## only one zone, so use zero'th element!
+ zone_info = r['data']['viewer']['zones'][0]
+
+ httpRequests1dGroups = zone_info['httpRequests1dGroups']
+
+ for h in sorted(httpRequests1dGroups, key=lambda v:
v['dimensions']['date']):
+ result_date = h['dimensions']['date']
+ result_info = h['sum']['countryMap']
+ print(result_date)
+ for element in sorted(result_info, key=lambda v: -v['bytes']):
+ print(" %7d %7d %2s" % (element['bytes'], element['requests'],
element['clientCountryName']))
+
+if __name__ == '__main__':
+ main()
+ exit(0)
+
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/cloudflare-2.8.3/examples/example_graphql.sh
new/cloudflare-2.8.15/examples/example_graphql.sh
--- old/cloudflare-2.8.3/examples/example_graphql.sh 1970-01-01
01:00:00.000000000 +0100
+++ new/cloudflare-2.8.15/examples/example_graphql.sh 2020-07-15
03:43:04.000000000 +0200
@@ -0,0 +1,34 @@
+:
+
+#
+# Show usage of GraphQL - see
https://developers.cloudflare.com/analytics/graphql-api for all info
+#
+
+# pass one argument - the zone
+ZONEID=`cli4 name="$1" /zones | jq -r '.[].id'`
+if [ "${ZONEID}" = "" ]
+then
+ echo "$1: zone not found" 1>&2
+ exit 1
+fi
+
+# Just query the last 24 hours
+DATE_BEFORE=`date -u +%Y-%m-%dT%H:%M:%SZ`
+DATE_AFTER=`date -u -v -24H +%Y-%m-%dT%H:%M:%SZ`
+
+# build the GraphQL query - this is just a simple example
+QUERY='
+ query {
+ viewer {
+ zones(filter: {zoneTag: "'${ZONEID}'"} ) {
+ httpRequests1hGroups(limit:100, orderBy:[datetime_ASC],
filter:{datetime_gt:"'${DATE_AFTER}'", datetime_lt:"'${DATE_BEFORE}'"}) {
+ dimensions { datetime }
+ sum { bytes }
+ }
+ }
+ }
+ }
+'
+
+# this not only does the query; but also drills down into the results to print
the final data
+cli4 --post query="${QUERY}" /graphql | jq -cr
'.data.viewer.zones[]|.httpRequests1hGroups[]|.dimensions.datetime,.sum.bytes'
| paste - -