Skip to content
This repository was archived by the owner on Nov 22, 2024. It is now read-only.

Commit d400c56

Browse files
committed
support for dns_records/import and file upload via library and cli4 command
1 parent 412ced6 commit d400c56

5 files changed

Lines changed: 109 additions & 20 deletions

File tree

CloudFlare/api_v4.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -107,6 +107,9 @@ def zones(self):
107107
self._add_with_auth(base, "zones", "subscription"))
108108
setattr(branch, "subscriptions",
109109
self._add_with_auth(base, "zones", "subscriptions"))
110+
branch = getattr(getattr(self, "zones"), "dns_records")
111+
setattr(branch, "import",
112+
self._add_with_auth(base, "zones", "dns_records/import"))
110113

111114
def zones_settings(self):
112115
""" API core commands for Cloudflare API"""

CloudFlare/cloudflare.py

Lines changed: 18 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -56,7 +56,7 @@ def call_with_auth(self, method,
5656
api_call_part2=None,
5757
api_call_part3=None,
5858
identifier1=None, identifier2=None, identifier3=None,
59-
params=None, data=None):
59+
params=None, data=None, files=None):
6060
""" Cloudflare v4 API"""
6161

6262
if self.email is '' or self.token is '':
@@ -65,12 +65,17 @@ def call_with_auth(self, method,
6565
'User-Agent': self.user_agent,
6666
'X-Auth-Email': self.email,
6767
'X-Auth-Key': self.token,
68-
'Content-Type': 'application/json'
68+
'Content-Type': 'multipart/form-data'
6969
}
70+
if files:
71+
# overwrite Content-Type as we are uploading data
72+
headers['Content-Type'] = 'multipart/form-data'
73+
# however something isn't right and this works ... look at again later!
74+
del headers['Content-Type']
7075
return self._call(method, headers,
7176
api_call_part1, api_call_part2, api_call_part3,
7277
identifier1, identifier2, identifier3,
73-
params, data)
78+
params, data, files)
7479

7580
def call_with_certauth(self, method,
7681
api_call_part1,
@@ -84,8 +89,8 @@ def call_with_certauth(self, method,
8489
raise CloudFlareAPIError(0, 'no cert token defined')
8590
headers = {
8691
'User-Agent': self.user_agent,
87-
'X-Auth-User-Service-Key': self.certtoken,
88-
'Content-Type': 'application/json'
92+
'X-Auth-User-Service-Key': self.certtoken,
93+
'Content-Type': 'application/json'
8994
}
9095
return self._call(method, headers,
9196
api_call_part1, api_call_part2, api_call_part3,
@@ -95,7 +100,7 @@ def call_with_certauth(self, method,
95100
def _raw(self, method, headers,
96101
api_call_part1, api_call_part2=None, api_call_part3=None,
97102
identifier1=None, identifier2=None, identifier3=None,
98-
params=None, data=None):
103+
params=None, data=None, files=None):
99104
""" Cloudflare v4 API"""
100105

101106
if self.logger:
@@ -107,6 +112,8 @@ def _raw(self, method, headers,
107112
str(identifier3)))
108113
self.logger.debug('Call: optional params and data %s %s' % (str(params),
109114
str(data)))
115+
if files:
116+
self.logger.debug('Call: upload file %r' % (files))
110117

111118
if (method is None) or (api_call_part1 is None):
112119
# should never happen
@@ -152,7 +159,7 @@ def _raw(self, method, headers,
152159
if method == 'GET':
153160
response = requests.get(url, headers=headers, params=params, data=data)
154161
elif method == 'POST':
155-
response = requests.post(url, headers=headers, params=params, json=data)
162+
response = requests.post(url, headers=headers, params=params, json=data, files=files)
156163
elif method == 'PUT':
157164
response = requests.put(url, headers=headers, params=params, json=data)
158165
elif method == 'DELETE':
@@ -188,13 +195,13 @@ def _call(self, method, headers,
188195
api_call_part2=None,
189196
api_call_part3=None,
190197
identifier1=None, identifier2=None, identifier3=None,
191-
params=None, data=None):
198+
params=None, data=None, files=None):
192199
""" Cloudflare v4 API"""
193200

194201
response_data = self._raw(method, headers,
195202
api_call_part1, api_call_part2, api_call_part3,
196203
identifier1, identifier2, identifier3,
197-
params, data)
204+
params, data, files)
198205

199206
# Sanatize the returned results - just in case API is messed up
200207
if 'success' not in response_data:
@@ -363,15 +370,15 @@ def patch(self, identifier1=None, identifier2=None, identifier3=None, params=Non
363370
identifier1, identifier2, identifier3,
364371
params, data)
365372

366-
def post(self, identifier1=None, identifier2=None, identifier3=None, params=None, data=None):
373+
def post(self, identifier1=None, identifier2=None, identifier3=None, params=None, data=None, files=None):
367374
""" Cloudflare v4 API"""
368375

369376
return self._base.call_with_auth('POST',
370377
self.api_call_part1,
371378
self.api_call_part2,
372379
self.api_call_part3,
373380
identifier1, identifier2, identifier3,
374-
params, data)
381+
params, data, files)
375382

376383
def put(self, identifier1=None, identifier2=None, identifier3=None, params=None, data=None):
377384
""" Cloudflare v4 API"""

README.md

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -461,6 +461,8 @@ For example:
461461
cli4 --put ="00000000000000000000000000000000" /user/load_balancers/maps/:00000000000000000000000000000000/region/:WNAM
462462
```
463463

464+
Data can also be uploaded from file contents. Using the ```item=@filename``` format will open the file and the contents uploaded in the POST.
465+
464466
### CLI output
465467

466468
The output from the CLI command is in JSON or YAML format (and human readable). This is controled by the **--yaml** or **--json** flags (JSON is the default).
@@ -620,6 +622,34 @@ $ cli4 /zones/:example.com/dnssec
620622
$
621623
```
622624

625+
### Zone file upload CLI examples
626+
627+
Refer to [Import DNS records](https://api.cloudflare.com/#dns-records-for-a-zone-import-dns-records) on API documentation for this feature.
628+
629+
```bash
630+
$ cat zone.txt
631+
example.com. IN SOA somewhere.example.com. someone.example.com. (
632+
2017010101
633+
3H
634+
15
635+
1w
636+
3h
637+
)
638+
639+
record1.example.com. IN A 10.0.0.1
640+
record2.example.com. IN AAAA 2001:d8b::2
641+
record3.example.com. IN CNAME record1.example.com.
642+
record4.example.com. IN TXT "some text"
643+
$
644+
645+
$ cli4 --post file=@zone.txt /zones/:example.txt/dns_records/import
646+
{
647+
"recs_added": 4,
648+
"total_records_parsed": 4
649+
}
650+
$
651+
```
652+
623653
## Implemented API calls
624654

625655
The **--dump** argument to cli4 will produce a list of all the call implemented within the library.

README.rst

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -514,6 +514,10 @@ For example:
514514

515515
cli4 --put ="00000000000000000000000000000000" /user/load_balancers/maps/:00000000000000000000000000000000/region/:WNAM
516516

517+
Data can also be uploaded from file contents. Using the
518+
``item=@filename`` format will open the file and the contents uploaded
519+
in the POST.
520+
517521
CLI output
518522
~~~~~~~~~~
519523

@@ -681,6 +685,37 @@ DNSSEC CLI examples
681685
}
682686
$
683687
688+
Zone file upload CLI examples
689+
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
690+
691+
Refer to `Import DNS
692+
records <https://api.cloudflare.com/#dns-records-for-a-zone-import-dns-records>`__
693+
on API documentation for this feature.
694+
695+
.. code:: bash
696+
697+
$ cat zone.txt
698+
example.com. IN SOA somewhere.example.com. someone.example.com. (
699+
2017010101
700+
3H
701+
15
702+
1w
703+
3h
704+
)
705+
706+
record1.example.com. IN A 10.0.0.1
707+
record2.example.com. IN AAAA 2001:d8b::2
708+
record3.example.com. IN CNAME record1.example.com.
709+
record4.example.com. IN TXT "some text"
710+
$
711+
712+
$ cli4 --post file=@zone.txt /zones/:example.txt/dns_records/import
713+
{
714+
"recs_added": 4,
715+
"total_records_parsed": 4
716+
}
717+
$
718+
684719
Implemented API calls
685720
---------------------
686721

cli4/cli4.py

Lines changed: 23 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -78,18 +78,24 @@ def cli4(args):
7878
elif opt in ('-D', '--delete'):
7979
method = 'DELETE'
8080

81+
if dump:
82+
cf = CloudFlare.CloudFlare()
83+
dump_commands(cf)
84+
exit(0)
85+
8186
digits_only = re.compile('^-?[0-9]+$')
8287
floats_only = re.compile('^-?[0-9.]+$')
8388

8489
# next grab the params. These are in the form of tag=value
8590
params = None
91+
files = None
8692
while len(args) > 0 and '=' in args[0]:
8793
tag_string, value_string = args.pop(0).split('=', 1)
88-
if value_string == 'true':
94+
if value_string.lower() == 'true':
8995
value = True
90-
elif value_string == 'false':
96+
elif value_string.lower() == 'false':
9197
value = False
92-
elif value_string == '':
98+
elif value_string == '' or value_string.lower() == 'none':
9399
value = None
94100
elif value_string[0] is '=' and value_string[1:] == '':
95101
exit('cli4: %s== - no number value passed' % (tag_string))
@@ -108,8 +114,21 @@ def cli4(args):
108114
value = yaml.safe_load(value_string)
109115
except ValueError:
110116
exit('cli4: %s="%s" - can\'t parse json value' % (tag_string, value_string))
117+
elif value_string[0] is '@':
118+
filename = value_string[1:]
119+
# a file to be uploaded - used in dns_records/import - only via POST
120+
if method != 'POST':
121+
exit('cli4: %s=%s - file upload only with POST' % (tag_string, filename))
122+
files = {}
123+
try:
124+
files[tag_string] = open(filename, 'rb')
125+
except:
126+
exit('cli4: %s=%s - file open failure' % (tag_string, filename))
127+
# no need for param code below
128+
continue
111129
else:
112130
value = value_string
131+
113132
if tag_string == '':
114133
# There's no tag; it's just an unnamed list
115134
if params is None:
@@ -129,11 +148,6 @@ def cli4(args):
129148
exit('cli4: %s=%s - param error. Can\'t mix unnamed and named list' %
130149
(tag_string, value_string))
131150

132-
if dump:
133-
cf = CloudFlare.CloudFlare()
134-
dump_commands(cf)
135-
exit(0)
136-
137151
# what's left is the command itself
138152
if len(args) != 1:
139153
exit(usage)
@@ -240,7 +254,7 @@ def cli4(args):
240254
r = m.post(identifier1=identifier1,
241255
identifier2=i2,
242256
identifier3=identifier3,
243-
data=params)
257+
data=params, files=files)
244258
elif method is 'PUT':
245259
r = m.put(identifier1=identifier1,
246260
identifier2=i2,

0 commit comments

Comments
 (0)