Skip to content

Commit

Permalink
support for dns_records/import and file upload via library and cli4 c…
Browse files Browse the repository at this point in the history
…ommand
  • Loading branch information
mahtin committed Aug 27, 2017
1 parent 412ced6 commit d400c56
Show file tree
Hide file tree
Showing 5 changed files with 109 additions and 20 deletions.
3 changes: 3 additions & 0 deletions CloudFlare/api_v4.py
Original file line number Diff line number Diff line change
Expand Up @@ -107,6 +107,9 @@ def zones(self):
self._add_with_auth(base, "zones", "subscription"))
setattr(branch, "subscriptions",
self._add_with_auth(base, "zones", "subscriptions"))
branch = getattr(getattr(self, "zones"), "dns_records")
setattr(branch, "import",
self._add_with_auth(base, "zones", "dns_records/import"))

def zones_settings(self):
""" API core commands for Cloudflare API"""
Expand Down
29 changes: 18 additions & 11 deletions CloudFlare/cloudflare.py
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@ def call_with_auth(self, method,
api_call_part2=None,
api_call_part3=None,
identifier1=None, identifier2=None, identifier3=None,
params=None, data=None):
params=None, data=None, files=None):
""" Cloudflare v4 API"""

if self.email is '' or self.token is '':
Expand All @@ -65,12 +65,17 @@ def call_with_auth(self, method,
'User-Agent': self.user_agent,
'X-Auth-Email': self.email,
'X-Auth-Key': self.token,
'Content-Type': 'application/json'
'Content-Type': 'multipart/form-data'
}
if files:
# overwrite Content-Type as we are uploading data
headers['Content-Type'] = 'multipart/form-data'
# however something isn't right and this works ... look at again later!
del headers['Content-Type']
return self._call(method, headers,
api_call_part1, api_call_part2, api_call_part3,
identifier1, identifier2, identifier3,
params, data)
params, data, files)

def call_with_certauth(self, method,
api_call_part1,
Expand All @@ -84,8 +89,8 @@ def call_with_certauth(self, method,
raise CloudFlareAPIError(0, 'no cert token defined')
headers = {
'User-Agent': self.user_agent,
'X-Auth-User-Service-Key': self.certtoken,
'Content-Type': 'application/json'
'X-Auth-User-Service-Key': self.certtoken,
'Content-Type': 'application/json'
}
return self._call(method, headers,
api_call_part1, api_call_part2, api_call_part3,
Expand All @@ -95,7 +100,7 @@ def call_with_certauth(self, method,
def _raw(self, method, headers,
api_call_part1, api_call_part2=None, api_call_part3=None,
identifier1=None, identifier2=None, identifier3=None,
params=None, data=None):
params=None, data=None, files=None):
""" Cloudflare v4 API"""

if self.logger:
Expand All @@ -107,6 +112,8 @@ def _raw(self, method, headers,
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 (api_call_part1 is None):
# should never happen
Expand Down Expand Up @@ -152,7 +159,7 @@ def _raw(self, method, headers,
if method == 'GET':
response = requests.get(url, headers=headers, params=params, data=data)
elif method == 'POST':
response = requests.post(url, headers=headers, params=params, json=data)
response = requests.post(url, headers=headers, params=params, json=data, files=files)
elif method == 'PUT':
response = requests.put(url, headers=headers, params=params, json=data)
elif method == 'DELETE':
Expand Down Expand Up @@ -188,13 +195,13 @@ def _call(self, method, headers,
api_call_part2=None,
api_call_part3=None,
identifier1=None, identifier2=None, identifier3=None,
params=None, data=None):
params=None, data=None, files=None):
""" Cloudflare v4 API"""

response_data = self._raw(method, headers,
api_call_part1, api_call_part2, api_call_part3,
identifier1, identifier2, identifier3,
params, data)
params, data, files)

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

def post(self, identifier1=None, identifier2=None, identifier3=None, params=None, data=None):
def post(self, identifier1=None, identifier2=None, identifier3=None, params=None, data=None, files=None):
""" Cloudflare v4 API"""

return self._base.call_with_auth('POST',
self.api_call_part1,
self.api_call_part2,
self.api_call_part3,
identifier1, identifier2, identifier3,
params, data)
params, data, files)

def put(self, identifier1=None, identifier2=None, identifier3=None, params=None, data=None):
""" Cloudflare v4 API"""
Expand Down
30 changes: 30 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -461,6 +461,8 @@ For example:
cli4 --put ="00000000000000000000000000000000" /user/load_balancers/maps/:00000000000000000000000000000000/region/:WNAM
```

Data can also be uploaded from file contents. Using the ```item=@filename``` format will open the file and the contents uploaded in the POST.

### CLI output

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).
Expand Down Expand Up @@ -620,6 +622,34 @@ $ cli4 /zones/:example.com/dnssec
$
```

### Zone file upload CLI examples

Refer to [Import DNS records](https://api.cloudflare.com/#dns-records-for-a-zone-import-dns-records) on API documentation for this feature.

```bash
$ cat zone.txt
example.com. IN SOA somewhere.example.com. someone.example.com. (
2017010101
3H
15
1w
3h
)

record1.example.com. IN A 10.0.0.1
record2.example.com. IN AAAA 2001:d8b::2
record3.example.com. IN CNAME record1.example.com.
record4.example.com. IN TXT "some text"
$

$ cli4 --post file=@zone.txt /zones/:example.txt/dns_records/import
{
"recs_added": 4,
"total_records_parsed": 4
}
$
```

## Implemented API calls

The **--dump** argument to cli4 will produce a list of all the call implemented within the library.
Expand Down
35 changes: 35 additions & 0 deletions README.rst
Original file line number Diff line number Diff line change
Expand Up @@ -514,6 +514,10 @@ For example:

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

Data can also be uploaded from file contents. Using the
``item=@filename`` format will open the file and the contents uploaded
in the POST.

CLI output
~~~~~~~~~~

Expand Down Expand Up @@ -681,6 +685,37 @@ DNSSEC CLI examples
}
$
Zone file upload CLI examples
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

Refer to `Import DNS
records <https://api.cloudflare.com/#dns-records-for-a-zone-import-dns-records>`__
on API documentation for this feature.

.. code:: bash
$ cat zone.txt
example.com. IN SOA somewhere.example.com. someone.example.com. (
2017010101
3H
15
1w
3h
)
record1.example.com. IN A 10.0.0.1
record2.example.com. IN AAAA 2001:d8b::2
record3.example.com. IN CNAME record1.example.com.
record4.example.com. IN TXT "some text"
$
$ cli4 --post file=@zone.txt /zones/:example.txt/dns_records/import
{
"recs_added": 4,
"total_records_parsed": 4
}
$
Implemented API calls
---------------------

Expand Down
32 changes: 23 additions & 9 deletions cli4/cli4.py
Original file line number Diff line number Diff line change
Expand Up @@ -78,18 +78,24 @@ def cli4(args):
elif opt in ('-D', '--delete'):
method = 'DELETE'

if dump:
cf = CloudFlare.CloudFlare()
dump_commands(cf)
exit(0)

digits_only = re.compile('^-?[0-9]+$')
floats_only = re.compile('^-?[0-9.]+$')

# next grab the params. These are in the form of tag=value
params = None
files = None
while len(args) > 0 and '=' in args[0]:
tag_string, value_string = args.pop(0).split('=', 1)
if value_string == 'true':
if value_string.lower() == 'true':
value = True
elif value_string == 'false':
elif value_string.lower() == 'false':
value = False
elif value_string == '':
elif value_string == '' or value_string.lower() == 'none':
value = None
elif value_string[0] is '=' and value_string[1:] == '':
exit('cli4: %s== - no number value passed' % (tag_string))
Expand All @@ -108,8 +114,21 @@ def cli4(args):
value = yaml.safe_load(value_string)
except ValueError:
exit('cli4: %s="%s" - can\'t parse json value' % (tag_string, value_string))
elif value_string[0] is '@':
filename = value_string[1:]
# a file to be uploaded - used in dns_records/import - only via POST
if method != 'POST':
exit('cli4: %s=%s - file upload only with POST' % (tag_string, filename))
files = {}
try:
files[tag_string] = open(filename, 'rb')
except:
exit('cli4: %s=%s - file open failure' % (tag_string, filename))
# no need for param code below
continue
else:
value = value_string

if tag_string == '':
# There's no tag; it's just an unnamed list
if params is None:
Expand All @@ -129,11 +148,6 @@ def cli4(args):
exit('cli4: %s=%s - param error. Can\'t mix unnamed and named list' %
(tag_string, value_string))

if dump:
cf = CloudFlare.CloudFlare()
dump_commands(cf)
exit(0)

# what's left is the command itself
if len(args) != 1:
exit(usage)
Expand Down Expand Up @@ -240,7 +254,7 @@ def cli4(args):
r = m.post(identifier1=identifier1,
identifier2=i2,
identifier3=identifier3,
data=params)
data=params, files=files)
elif method is 'PUT':
r = m.put(identifier1=identifier1,
identifier2=i2,
Expand Down

0 comments on commit d400c56

Please sign in to comment.