-
-
Save jaytaylor/6273175 to your computer and use it in GitHub Desktop.
| #!/usr/bin/env bash | |
| ## | |
| # @author Jay Taylor [@jtaylor] | |
| # @date 2013-08-15 | |
| # | |
| # @description CloudFlare management script. | |
| # | |
| # Path ENV VAR override. | |
| if test -z "${CF_PATH:-}"; then | |
| CF_PATH="${HOME}/.cloudflare" | |
| fi | |
| # Ensure base path exists. | |
| set -e | |
| mkdir -p "${CF_PATH}" || ( echo "error: failed to create CF_PATH=${CF_PATH}" 1>&2 ; exit 1 ) | |
| set +e | |
| ## | |
| # Begin Configuration | |
| # | |
| # NB: Hard-code these configuration values or place them in the corresponding file. | |
| tkn=$(test -r "${CF_PATH}/token" && cat "${CF_PATH}/token" || echo '<YOUR_API_TOKEN_HERE>') | |
| email=$(test -r "${CF_PATH}/email" && cat "${CF_PATH}/email" || echo '<YOUR_EMAIL_ADDRESS_HERE>') | |
| zone=$(test -r "${CF_PATH}/zone" && cat "${CF_PATH}/zone" || echo '<YOUR_ZONE_HERE>') | |
| ttl=$(test -r "${CF_PATH}/ttl" && cat "${CF_PATH}/ttl" || echo '<DEFAULT_TTL_SECONDS_HERE>') | |
| # "service_mode" [applies to A/AAAA/CNAME] | |
| # Status of CloudFlare Proxy, 1 = orange cloud, 0 = grey cloud. | |
| cfProxy=0 | |
| # | |
| # End Configuration | |
| ## | |
| zid=$(test -r "${HOME}/.cloudflare/zid" && cat "${HOME}/.cloudflare/zid" || echo '') | |
| # Attempt zone-id lookup if it is absent. | |
| if test -z "${zid}"; then | |
| response=$( | |
| curl \ | |
| --fail \ | |
| --silent \ | |
| --show-error \ | |
| --compressed \ | |
| -H "X-Auth-Email: ${email}" \ | |
| -H "X-Auth-Key: ${tkn}" \ | |
| "https://api.cloudflare.com/client/v4/zones?name=${zone}" | |
| ) | |
| rc=$? | |
| if test $rc -ne 0; then | |
| echo "error: failed to lookup zone-id for domain name \"${zone}\", curl exit code=${rc}" 1>&2 | |
| exit $rc | |
| else | |
| zid=$( | |
| echo "${response}" | python -c 'import json, sys | |
| data = json.loads(sys.argv[1] if len(sys.argv)>1 else sys.stdin.read()) | |
| print(data.get("result", [{}])[0].get("id", "") if len(data.get("result", [])) > 0 else "")' | |
| ) | |
| if test -z "${zid}"; then | |
| echo "error: no zone-id found matching domain name \"${zone}\"" 1>&2 | |
| exit 1 | |
| else | |
| echo "${zid}" > "${HOME}/.cloudflare/zid" | |
| fi | |
| fi | |
| fi | |
| if test -z "$1" || test "$1" = '-h' || test "$1" = '--help'; then | |
| echo "usage: $0 [ACTION] [additionalParameters?].. | |
| ACTION - one of \"create\", \"read\", \"update\", \"delete\", or \"id\"" 1>&2 | |
| exit 1 | |
| fi | |
| action=$1 | |
| # Action aliases. | |
| if test "${action}" = 'add' || test "${action}" = '+'; then action='create'; fi | |
| if test "${action}" = 'edit' || test "${action}" = 'modify' || test "${action}" = '~'; then action='update'; fi | |
| if test "${action}" = 'remove' || test "${action}" = 'rm' || test "${action}" = 'erase' || test "${action}" = '-'; then action='delete'; fi | |
| if test "${action}" = 'list'; then action='read'; fi | |
| # Validate action. | |
| test "${action}" != 'create' && \ | |
| test "${action}" != 'read' && \ | |
| test "${action}" != 'update' && \ | |
| test "${action}" != 'delete' && \ | |
| test "${action}" != 'id' && \ | |
| echo "error: unrecognized action \"${action}\" (see -h or --help), operation aborted" 1>&2 && exit 1 || true | |
| # Translate requested action to CloudFlare's name for the action. | |
| if test "${action}" = 'create'; then a='rec_new'; fi | |
| if test "${action}" = 'read' || test "${action}" = 'id'; then a='rec_load_all'; fi | |
| if test "${action}" = 'update'; then a='rec_edit'; fi | |
| if test "${action}" = 'delete'; then a='rec_delete'; fi | |
| if test "${action}" = 'id'; then | |
| test -z "$2" && echo 'error: missing required parameter: search query' 1>&2 && exit 1 || true | |
| results='' | |
| page=1 | |
| while true; do | |
| searchQuery=$2 | |
| response=$( | |
| curl \ | |
| --fail \ | |
| --silent \ | |
| --show-error \ | |
| --compressed \ | |
| -H "X-Auth-Email: ${email}" \ | |
| -H "X-Auth-Key: ${tkn}" \ | |
| -H 'Content-Type: application/json' \ | |
| "https://api.cloudflare.com/client/v4/zones/${zid}/dns_records?page=${page}&per_page=100" | |
| ) | |
| rc=$? | |
| if test $rc -ne 0; then | |
| echo "error: cloudflare dns record query failed on page=${page}, curl exit code=${rc}" 1>&2 | |
| exit $rc | |
| fi | |
| result=$( | |
| echo "${response}" | python -c 'import json, sys | |
| data = json.loads(sys.argv[1] if len(sys.argv)>1 else sys.stdin.read()) | |
| #sys.stderr.write("%s" % data) | |
| for record in data.get("result", []): | |
| if record["type"].upper() in ("CNAME", "A", "TXT"): | |
| print("{0} {1} {2}".format(record["id"], record["name"], record["content"]))' \ | |
| | grep "${searchQuery}" | |
| ) | |
| results=$(echo -e -n "${results}\n${result}" | grep -v '^$') | |
| numRecords=$( | |
| echo "${response}" | python -c 'import json, sys | |
| data = json.loads(sys.argv[1] if len(sys.argv)>1 else sys.stdin.read()) | |
| sys.stdout.write("%s" % (len(data.get("result", [])),))' | |
| ) | |
| if test ${numRecords} -lt 100; then | |
| break | |
| fi | |
| page=$(($page + 1)) | |
| done | |
| test -z "${results}" && echo "error: no results found for search query \"${searchQuery}\"" 1>&2 && exit 1 || true | |
| test $(echo "${results}" | wc -l) -gt 1 && echo -e "error: too many results found for search query \"${searchQuery}\":\n${results}" 1>&2 && exit 1 || true | |
| echo "${results}" | cut -d' ' -f1 | |
| exit 0 | |
| fi | |
| if test "${action}" = 'read'; then | |
| filter=$2 | |
| page=1 | |
| out='id\tname\tcontent | |
| --\t----\t------- | |
| ' | |
| while true; do | |
| response=$( | |
| curl \ | |
| --fail \ | |
| --silent \ | |
| --show-error \ | |
| --compressed \ | |
| -H "X-Auth-Email: ${email}" \ | |
| -H "X-Auth-Key: ${tkn}" \ | |
| -H 'Content-Type: application/json' \ | |
| "https://api.cloudflare.com/client/v4/zones/${zid}/dns_records?page=${page}&per_page=100" | |
| ) | |
| rc=$? | |
| if test $rc -ne 0; then | |
| echo "error: cloudflare dns record query failed on page=${page}, curl exit code=${rc}" 1>&2 | |
| exit $rc | |
| fi | |
| out="${out} | |
| $(echo "${response}" | python -c 'import json, sys | |
| data = json.loads(sys.argv[1] if len(sys.argv)>1 else sys.stdin.read()) | |
| for record in data.get("result", []): | |
| if record["type"].upper() in ("CNAME", "A", "TXT"): | |
| print("{0}\t{1}\t{2}".format(record["id"], record["name"], record["content"]))' \ | |
| | grep "$(test -n "${filter}" && echo "${filter}" || echo '.*')")" | |
| numRecords=$( | |
| echo "${response}" | python -c 'import json, sys | |
| data = json.loads(sys.argv[1] if len(sys.argv)>1 else sys.stdin.read()) | |
| sys.stdout.write("%s" % (len(data.get("result", [])),))' | |
| ) | |
| if test ${numRecords} -lt 100; then | |
| break | |
| fi | |
| page=$(($page + 1)) | |
| done | |
| column -t <<< "$(echo -e "${out}")" | |
| fi | |
| if test "${action}" = 'create'; then | |
| method='POST' | |
| elif test "${action}" = 'update'; then | |
| method='PUT' | |
| elif test "${action}" = 'delete'; then | |
| method='DELETE' | |
| fi | |
| apiUrl="https://api.cloudflare.com/client/v4/zones/${zid}/dns_records" | |
| httpJsonData='{' | |
| if test "${action}" = 'create' || test "${action}" = 'update'; then | |
| test -z "$2" && echo 'error: missing required parameter: subdomain name' 1>&2 && exit 1 || true | |
| httpJsonData="${httpJsonData}\"name\": \"$2\"" | |
| test -z "$3" && echo 'error: missing required parameter: ip or cname hostname' 1>&2 && exit 1 || true | |
| if test -z "$4"; then | |
| if test -n "$(echo "$3" | grep '^[0-9\.]\+$')"; then | |
| httpJsonData="${httpJsonData}, \"type\": \"A\"" | |
| else | |
| httpJsonData="${httpJsonData}, \"type\": \"CNAME\"" | |
| fi | |
| else | |
| httpJsonData="${httpJsonData}, \"type\": \"$4\"" | |
| fi | |
| httpJsonData="${httpJsonData}, \"content\": \"$3\", \"ttl\": ${ttl}" | |
| fi | |
| if test "${action}" = 'update'; then | |
| test -z "$2" && echo 'error: missing required parameter: subdomain name' && exit 1 || true | |
| test -z "$3" && echo 'error: missing required parameter: ip-address or target domain name' && exit 1 || true | |
| if test -n "$4"; then | |
| recordId="$4" | |
| else | |
| specifier=$2 | |
| echo "info: attempting to resolve record id for \"${specifier}\"" 1>&2 | |
| recordId=$("$0" id "${specifier}") | |
| rc=$? | |
| test $rc -ne 0 && echo "error: id resolution failed for specifier \"${specifier}\"" 1>&2 && exit 1 || true | |
| fi | |
| apiUrl="${apiUrl}/${recordId}" | |
| httpJsonData="${httpJsonData}, \"id\": \"${recordId}\"" | |
| fi | |
| httpJsonData="${httpJsonData}}" | |
| if test "${action}" = 'delete'; then | |
| test -z "$2" && echo 'error: missing required parameter: record specifier (id, or name to resolve to id)' 1>&2 && exit 1 || true | |
| # Test if specifier | |
| specifier=$2 | |
| if test -z "$(echo "${specifier}" | grep '^[0-9a-f]\+')"; then | |
| echo "info: attempting to resolve record id for \"${specifier}\"" | |
| recordId=$("$0" id "${specifier}") | |
| rc=$? | |
| test $rc -ne 0 && echo "error: id resolution failed for specifier \"${specifier}\"" 1>&2 && exit 1 || true | |
| fi | |
| apiUrl="${apiUrl}/${recordId}" | |
| httpJsonData='' | |
| fi | |
| if test "${action}" = 'create' || test "${action}" = 'update' || test "${action}" = 'delete'; then | |
| curl \ | |
| --silent \ | |
| --show-error \ | |
| --fail \ | |
| --compressed \ | |
| -X "${method}" \ | |
| -H 'Content-Type: application/json' \ | |
| -H "X-Auth-Email: ${email}" \ | |
| -H "X-Auth-Key: ${tkn}" \ | |
| --data "${httpJsonData}" \ | |
| "${apiUrl}" | |
| rc=$? | |
| echo '' | |
| if test $rc -ne 0; then | |
| echo "error: failed to ${action} record, curl return code=${rc}" 1>&2 | |
| exit $rc | |
| fi | |
| fi | |
| exit 0 |
What is the proper way to use delete?
./cf.sh delete demo
curl: (22) The requested URL returned error: 400 Bad Request
error: failed to delete record, curl return code=22
Just found out, you probably have a typo, change that to:
--- cf.sh.orig 2016-08-26 17:53:26.544435110 +0200
+++ cf.sh 2016-08-26 23:31:41.496494641 +0200
@@ -245,7 +245,7 @@
test -z "$2" && echo 'error: missing required parameter: record specifier (id, or name to resolve to id)' 1>&2 && exit 1 || true
# Test if specifier
specifier=$2
- if test -z "$(echo "${specifier}" | grep '^[0-9a-f]+')"; then
- if test -n "$(echo "${specifier}" | grep '^[0-9a-f]+')"; then
echo "info: attempting to resolve record id for "${specifier}""
recordId=$("$0" id "${specifier}")
rc=$?
Have you considered putting more detailed help output into this? Would be nice to have some examples; eg, in my use case I want to take ngrok output and assign the temporarily assigned domain to a CloudFlare name alias. It'd be neat to see this kind of example in a help.
the description is nothing https://gist.github.com/ylluminate @ylluminate @mitjax how to run simple add a cname against this like
demo.example.co cnam kkjbjlknllmlklmkbknl ? is it working script?
It now uses the V4 API. Thank you for your help @dgehri!