9

Goal:

I'm using a bash CURL script to connect to the Cloudflare APIv4. The goal is to update an A-record. My script:

   # Get current public IP
      current_ip=curl --silent ipecho.net/plain; echo

   # Update A record
      curl -X PUT "https://api.cloudflare.com/client/v4/zones/ZONEIDHERE/dns_records/DNSRECORDHERE" \
        -H "X-Auth-Email: EMAILHERE" \
        -H "X-Auth-Key: AUTHKEYHERE" \
        -H "Content-Type: application/json" \
        --data '{"id":"ZONEIDHERE","type":"A","name":"example.com","content":"'"${current_ip}"'","zone_name":"example.com"}'

Problem:

The current_ip variable is not printed when I call it in my script. The output will be "content" : "" and not "content" : "1.2.3.4".

I used other stackoverflow posts and I'm trying to follow their examples but I think I'm still doing something wrong, just can't figure out what. :(

Community
  • 1
  • 1
scre_www
  • 2,536
  • 4
  • 20
  • 30

2 Answers2

9

Using jq for this, as Charles Duffy's answer suggests, is a very good idea. However, if you can't or do not want to install jq here is what you can do with plain POSIX shell.

#!/bin/sh
set -e

current_ip="$(curl --silent --show-error --fail ipecho.net/plain)"
echo "IP: $current_ip"

# Update A record
curl -X PUT "https://api.cloudflare.com/client/v4/zones/ZONEIDHERE/dns_records/DNSRECORDHERE" \
    -H "X-Auth-Email: EMAILHERE" \
    -H "X-Auth-Key: AUTHKEYHERE" \
    -H "Content-Type: application/json" \
    --data @- <<END;
{
    "id": "ZONEIDHERE",
    "type": "A",
    "name": "example.com",
    "content": "$current_ip",
    "zone_name": "example.com"
}
END
nwk
  • 4,004
  • 1
  • 21
  • 22
  • The main place where this falls down is if you have content that can't be literally substituted into a JSON document without needing quoting or escaping, but that's not likely to happen for an IP address, so it's a reasonable place to compromise a bit. – Charles Duffy May 27 '16 at 20:38
  • ...I might put a `|| exit` on the end of the assignment from results of `curl`, just so we don't keep going in the event of a failure, or a `[[ $current_ip ]] || exit` (or, rather, since you're using `#!/bin/sh`, a `[ -n "$current_ip" ] || exit`) on a subsequent line; `--fail` makes sure that curl's exit status reflects a failure, but if the shell wasn't run with `-e` it isn't necessarily going to behave in accordance with that exit status. – Charles Duffy May 27 '16 at 20:40
  • 1
    Yes, exactly. You should be very careful when manipulating JSON as text and avoid it if possible. Even if your *nix machine doesn't have jq it probably has Perl or Python. When in doubt, use one of them to prepare your JSON. – nwk May 27 '16 at 20:45
  • I've added `set -e` before the first `curl` command. I intended to from the start but totally forgot. – nwk May 27 '16 at 20:46
  • 1
    (Also, I'm adopting your `--show-error` suggestion -- that *is* an improvement on using `--silent` alone. Thank you!) – Charles Duffy May 27 '16 at 20:59
  • @nwk thanks for your solution! I'm preferring an bash-only solution like you posted without python, perl or 3th party plugins. I tried your code but the part between @- < – scre_www May 27 '16 at 21:22
  • @scre_www That's because the content between < – nwk May 27 '16 at 21:24
  • Thank @nwk, I'm using your solution now on my NAS now so I rated this as accepted answer. <3 :) – scre_www May 27 '16 at 21:52
  • I have a problem that newlines are truncated at the end (but not in the middle). Any ideas? – Imaskar Aug 13 '19 at 11:15
8

The reliable way to edit JSON from shell scripts is to use jq:

# set shell variables with your contents
email="yourEmail"
authKey="yourAuthKey"
zoneid="yourZoneId"
dnsrecord="yourDnsRecord"

# make sure we show errors; --silent without --show-error can mask problems.
current_ip=$(curl --fail -sS ipecho.net/plain) || exit

# optional: template w/ JSON content that won't change
json_template='{"type": "A", "name": "example.com"}'

# build JSON with content that *can* change with jq
json_data=$(jq --arg zoneid "$zoneid" \
               --arg current_ip "$current_ip" \
               '.id=$zoneid | .content=$current_ip' \
               <<<"$json_template")

# ...and submit
curl -X PUT "https://api.cloudflare.com/client/v4/zones/$zoneid/dns_records/$dnsrecord" \
  -H "X-Auth-Email: $email" \
  -H "X-Auth-Key: $authKey" \
  -H "Content-Type: application/json" \
  --data "$json_data"
Andreas Jägle
  • 11,632
  • 3
  • 31
  • 31
Charles Duffy
  • 280,126
  • 43
  • 390
  • 441
  • I think using jq is a beautiful solution for the problem! The code is also very clean and straight forward, thank you for the code! However, I want to try to keep using plain bash without add-ons and even (if possible) without using python or perl. I didn't mentioned this in my post so I'm still searching for a non-jq option before accepting this as answer. +1 for solution – scre_www May 27 '16 at 21:11
  • 1
    I had some issues using the arg syntax with the `=` assignments. It did not replace / add the specified keys. It worked for me using `--arg zoneid "$zoneid"` (without the equals sign). – Andreas Jägle Jun 14 '19 at 16:47
  • 1
    @AndreasJägle, thank you for that fix! I don't know why this issue didn't get caught earlier -- maybe prior versions were more lenient for command line usage that didn't strictly match the manual? -- but that change is definitely the right thing. – Charles Duffy Jun 14 '19 at 17:31