Skip to content

Commit f266fb5

Browse files
committed
added code to query api documentation (via beautifulsoup4) to build a current api listing
1 parent f25bdfa commit f266fb5

6 files changed

Lines changed: 119 additions & 7 deletions

File tree

CloudFlare/api_decode_from_web.py

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
""" API extras for Cloudflare API"""
2+
3+
from bs4 import BeautifulSoup, Comment
4+
5+
API_TYPES = ['GET', 'POST', 'PATCH', 'PUT', 'DELETE']
6+
7+
def do_section(section):
8+
""" API extras for Cloudflare API"""
9+
10+
cmds = []
11+
# look for deprecated first in section
12+
deprecated = False
13+
for tag2 in section.find_all('h3'):
14+
if 'Deprecation Warning' in str(tag2):
15+
deprecated = True
16+
# look for all API calls in section
17+
for tag2 in section.find_all('pre'):
18+
cmd = []
19+
for child in tag2.children:
20+
if isinstance(child, Comment):
21+
# remove <!-- react-text ... -> parts
22+
continue
23+
cmd.append(child.strip())
24+
if len(cmd) == 0:
25+
continue
26+
action = cmd[0]
27+
cmd = '/' + ''.join(cmd[1:])
28+
if action == '' or action not in API_TYPES:
29+
continue
30+
v = {'deprecated': deprecated, 'action': action, 'cmd': cmd}
31+
cmds.append(v)
32+
return cmds
33+
34+
def api_decode_from_web(content):
35+
""" API extras for Cloudflare API"""
36+
37+
soup = BeautifulSoup(content, 'html.parser')
38+
39+
all_cmds = []
40+
for section in soup.find_all('section'):
41+
all_cmds += do_section(section)
42+
43+
return sorted(all_cmds, key=lambda v: v['cmd'])

CloudFlare/cloudflare.py

Lines changed: 28 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
from .read_configs import read_configs
1010
from .api_v4 import api_v4
1111
from .api_extras import api_extras
12+
from .api_decode_from_web import api_decode_from_web
1213
from .exceptions import CloudFlareError, CloudFlareAPIError, CloudFlareInternalError
1314

1415
BASE_URL = 'https://api.cloudflare.com/client/v4'
@@ -154,7 +155,7 @@ def call_with_certauth(self, method, parts,
154155
identifier1, identifier2, identifier3,
155156
params, data, files)
156157

157-
def _connection(self, method, url, headers, params, data, files):
158+
def _connection(self, method, url, headers=None, params=None, data=None, files=None):
158159
""" Cloudflare v4 API"""
159160

160161
if self.use_sessions:
@@ -254,10 +255,9 @@ def _network(self, method, headers, parts,
254255
self.logger.debug('Call: method and url %s %s', str(method), str(url))
255256
self.logger.debug('Call: headers %s', str(sanitize_secrets(headers)))
256257

257-
if self.logger:
258-
self.logger.debug('Call: doit!')
259-
260258
try:
259+
if self.logger:
260+
self.logger.debug('Call: doit!')
261261
response = self._connection(method, url, headers, params, data, files)
262262
if self.logger:
263263
self.logger.debug('Call: done!')
@@ -595,6 +595,25 @@ def _call_unwrapped(self, method, headers, parts,
595595
result = response_data
596596
return result
597597

598+
def _api_from_web(self):
599+
""" Cloudflare v4 API"""
600+
601+
# base url isn't enough; we need less
602+
url = '/'.join(self.base_url.split('/')[0:3])
603+
604+
try:
605+
if self.logger:
606+
self.logger.debug('Call: doit!')
607+
response = self._connection("GET", url)
608+
if self.logger:
609+
self.logger.debug('Call: done!')
610+
except Exception as e:
611+
if self.logger:
612+
self.logger.debug('Call: exception! "%s"' % (e))
613+
raise CloudFlareAPIError(0, 'connection failed.')
614+
615+
return response.text
616+
598617
class _AddUnused(object):
599618
""" Cloudflare v4 API"""
600619

@@ -919,6 +938,11 @@ def api_list(self, m=None, s=''):
919938
w = w + self.api_list(a, s + '/' + n)
920939
return w
921940

941+
def api_from_web(self):
942+
""" Cloudflare v4 API"""
943+
944+
return api_decode_from_web(self._base._api_from_web())
945+
922946
def __init__(self, email=None, token=None, certtoken=None, debug=False, raw=False, use_sessions=True, profile=None):
923947
""" Cloudflare v4 API"""
924948

cli4/cli4.py

Lines changed: 19 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,15 @@ def dump_commands():
2323
w = cf.api_list()
2424
sys.stdout.write('\n'.join(w) + '\n')
2525

26+
def dump_commands_from_web():
27+
"""dump a tree of all the known API commands - from web"""
28+
cf = CloudFlare.CloudFlare()
29+
w = cf.api_from_web()
30+
for r in w:
31+
if r['deprecated']:
32+
continue
33+
sys.stdout.write('%-6s %s\n' % (r['action'], r['cmd']))
34+
2635
def run_command(cf, method, command, params=None, content=None, files=None):
2736
"""run the command line"""
2837
# remove leading and trailing /'s
@@ -239,6 +248,7 @@ def do_it(args):
239248
output = 'json'
240249
raw = False
241250
dump = False
251+
dump_from_web = False
242252
profile = None
243253
method = 'GET'
244254

@@ -247,19 +257,20 @@ def do_it(args):
247257
+ '[-j|--json] [-y|--yaml] [-n|ndjson] '
248258
+ '[-r|--raw] '
249259
+ '[-d|--dump] '
260+
+ '[-a|--api] '
250261
+ '[-p|--profile profile-name] '
251262
+ '[--get|--patch|--post|--put|--delete] '
252263
+ '[item=value|item=@filename|@filename ...] '
253264
+ '/command ...')
254265

255266
try:
256267
opts, args = getopt.getopt(args,
257-
'Vhvqjyrdp:GPOUD',
268+
'Vhvqjyrdap:GPOUD',
258269
[
259270
'version',
260271
'help', 'verbose', 'quiet', 'json', 'yaml', 'ndjson',
261272
'raw',
262-
'dump',
273+
'dump', 'api',
263274
'profile=',
264275
'get', 'patch', 'post', 'put', 'delete'
265276
])
@@ -290,6 +301,8 @@ def do_it(args):
290301
profile = arg
291302
elif opt in ('-d', '--dump'):
292303
dump = True
304+
elif opt in ('-a', '--api'):
305+
dump_from_web = True
293306
elif opt in ('-G', '--get'):
294307
method = 'GET'
295308
elif opt in ('-P', '--patch'):
@@ -305,6 +318,10 @@ def do_it(args):
305318
dump_commands()
306319
sys.exit(0)
307320

321+
if dump_from_web:
322+
dump_commands_from_web()
323+
sys.exit(0)
324+
308325
digits_only = re.compile('^-?[0-9]+$')
309326
floats_only = re.compile('^-?[0-9.]+$')
310327

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
#!/usr/bin/env python
2+
"""Cloudflare API code - example"""
3+
4+
from __future__ import print_function
5+
6+
import os
7+
import sys
8+
import json
9+
10+
sys.path.insert(0, os.path.abspath('..'))
11+
import CloudFlare
12+
13+
def main():
14+
"""Cloudflare API code - example"""
15+
16+
cf = CloudFlare.CloudFlare()
17+
try:
18+
r = cf.api_from_web()
19+
except Exception as e:
20+
exit('api_from_web: - %s - api call connection failed' % (e))
21+
22+
print(json.dumps(r))
23+
exit(0)
24+
25+
if __name__ == '__main__':
26+
main()
27+

requirements.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,3 +2,4 @@ requests>=2.4.2
22
future
33
pyyaml
44
jsonlines
5+
beautifulsoup4

setup.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@ def main():
3232
#package_data={'cloudflare-examples': ["examples/*"]},
3333
include_package_data=True,
3434
data_files = [('man/man1', ['cli4/cli4.man'])],
35-
install_requires=['requests', 'future', 'pyyaml', 'jsonlines'],
35+
install_requires=['requests', 'future', 'pyyaml', 'jsonlines', 'beautifulsoup4'],
3636
keywords='cloudflare',
3737
entry_points={
3838
'console_scripts': [

0 commit comments

Comments
 (0)