Flow to Update Cloudflare DNS records

For those who use Cloudflare for managing their DNS records, this flow attempts to update their DNS A records for a given set of domain names.
It uses http://ipinfo.io/ip to determine the current external IP address. Before running the flow you might like to run
curl ipinfo.io/ip
to check that the correct IP address is returned.

The flow expects an array of domain names in msg.payload. Fill in the subflow properties with your Cloudflare Zone ID, Cloudflare logon email and Global API key and whether the DNS records should be marked as proxied or not. Currently it does not allow different settings for Proxied for different domains. If you needed this you could use two instances of the subflow, one for proxied domains and one for non-proxied.

The subflow gets the current external IP address, then fetches the Cloudflare records for the complete Zone. For each of the domains given it compares the current value in the Cloudflare record with that optained from ipinfo.io, and updates only those that need to be updated.

When complete, output 1 sends, in msg.payload, an array of strings indicating, for each domain, whether it was already correct or whether it was updated. If there is any output from output 2 this indicates an error of some sort. Feed the message into a debug node showing the Complete Message to see what the problem was. There may be further information In msg.payload.

For my use I configure the Inject node to run ten seconds after startup, then every 5 minutes. I use output 2 to send me a notification that there is a problem.

For any problems with this flow please ask on the Node Red forum.

[{"id":"b2605c103b3c10bd","type":"subflow","name":"Update Cloudflare DNS","info":"","category":"","in":[{"x":20,"y":160,"wires":[{"id":"56c00aa5bbf32b95"}]}],"out":[{"x":960,"y":740,"wires":[{"id":"2ba9a52f9f444496","port":0},{"id":"f4e2275fe98ba7c4","port":0}]},{"x":920,"y":1040,"wires":[{"id":"320213502d7cd81b","port":0},{"id":"631528af6996430d","port":0}]}],"env":[{"name":"ZoneID","type":"str","value":"","ui":{"label":{"en-US":"Zone ID"},"type":"input","opts":{"types":["str","env"]}}},{"name":"Email","type":"str","value":"","ui":{"type":"input","opts":{"types":["str","env"]}}},{"name":"Key","type":"cred","ui":{"label":{"en-US":"API key"},"type":"input","opts":{"types":["cred"]}}},{"name":"Proxied","type":"bool","value":"false","ui":{"type":"input","opts":{"types":["bool","env"]}}}],"meta":{},"credentials":{"Key":""},"color":"#DDAA99","status":{"x":520,"y":1040,"wires":[{"id":"e7d67646cd91b5de","port":0},{"id":"5f0ca8668bb282e0","port":0}]}},{"id":"d155437807140393","type":"comment","z":"b2605c103b3c10bd","name":"Given an array of domain names, updates cloudflare dns records","info":"","x":330,"y":40,"wires":[]},{"id":"56c00aa5bbf32b95","type":"change","z":"b2605c103b3c10bd","g":"eeb017401050907c","name":"Move domains to msg.domains","rules":[{"t":"move","p":"payload","pt":"msg","to":"domains","tot":"msg"}],"action":"","property":"","from":"","to":"","reg":false,"x":210,"y":160,"wires":[["27a26faaeaddf108","5ca6f91adb8a1873"]]},{"id":"27a26faaeaddf108","type":"http request","z":"b2605c103b3c10bd","g":"eeb017401050907c","name":"Get our IP","method":"GET","ret":"txt","paytoqs":"ignore","url":"http://ipinfo.io/ip","tls":"","persist":false,"proxy":"","insecureHTTPParser":false,"authType":"","senderr":false,"headers":[],"x":430,"y":160,"wires":[["337770b779737664"]]},{"id":"320213502d7cd81b","type":"catch","z":"b2605c103b3c10bd","name":"","scope":null,"uncaught":false,"x":720,"y":1040,"wires":[[]]},{"id":"337770b779737664","type":"switch","z":"b2605c103b3c10bd","g":"eeb017401050907c","name":"status 200?","property":"statusCode","propertyType":"msg","rules":[{"t":"eq","v":"200","vt":"num"},{"t":"else"}],"checkall":"true","repair":false,"outputs":2,"x":590,"y":160,"wires":[["b133ab72bfb0f0bc"],["41341f10167041c5","d4a77c1dff11fdd4"]]},{"id":"41341f10167041c5","type":"link out","z":"b2605c103b3c10bd","g":"eeb017401050907c","name":"link out 1","mode":"link","links":["631528af6996430d"],"x":615,"y":200,"wires":[]},{"id":"631528af6996430d","type":"link in","z":"b2605c103b3c10bd","name":"link in 1","links":["41341f10167041c5","315f6996631ee864","97a03b41453e681a","5af9ab018c92680d"],"x":835,"y":1000,"wires":[[]]},{"id":"b133ab72bfb0f0bc","type":"change","z":"b2605c103b3c10bd","g":"eeb017401050907c","name":"IP to msg.ip","rules":[{"t":"move","p":"payload","pt":"msg","to":"ip","tot":"msg"}],"action":"","property":"","from":"","to":"","reg":false,"x":810,"y":160,"wires":[["8fb0166cc4a85bf2"]]},{"id":"8fb0166cc4a85bf2","type":"function","z":"b2605c103b3c10bd","g":"0d186e43bfe6c8d1","name":"Prepare request","func":"msg.url = `https://api.cloudflare.com/client/v4/zones/${env.get(\"ZoneID\")}/dns_records/`\nmsg.headers = { \n    \"X-Auth-Email\": env.get(\"Email\"),\n    \"X-Auth-Key\": env.get(\"Key\"),\n    \"Content-Type\": \"application/json\"\n}\nnode.status({text: \"Fetching DNS records from cloudflare\"})\nreturn msg;\n\n","outputs":1,"noerr":0,"initialize":"","finalize":"","libs":[],"x":120,"y":360,"wires":[["d002a11e550e568f"]]},{"id":"d002a11e550e568f","type":"http request","z":"b2605c103b3c10bd","g":"0d186e43bfe6c8d1","name":"","method":"GET","ret":"obj","paytoqs":"ignore","url":"","tls":"","persist":false,"proxy":"","insecureHTTPParser":false,"authType":"","senderr":false,"headers":[],"x":290,"y":360,"wires":[["b7281df6ef9ea0c4"]]},{"id":"b7281df6ef9ea0c4","type":"switch","z":"b2605c103b3c10bd","g":"0d186e43bfe6c8d1","name":"status 200?","property":"statusCode","propertyType":"msg","rules":[{"t":"eq","v":"200","vt":"num"},{"t":"else"}],"checkall":"true","repair":false,"outputs":2,"x":450,"y":360,"wires":[["b2c3b7ed632da273"],["315f6996631ee864","35e5bcc0290e2bf2"]]},{"id":"315f6996631ee864","type":"link out","z":"b2605c103b3c10bd","g":"0d186e43bfe6c8d1","name":"link out 2","mode":"link","links":["631528af6996430d"],"x":755,"y":360,"wires":[]},{"id":"b2c3b7ed632da273","type":"change","z":"b2605c103b3c10bd","g":"0d186e43bfe6c8d1","name":"dns records to msg.dnsRecords","rules":[{"t":"move","p":"payload.result","pt":"msg","to":"dnsRecords","tot":"msg"}],"action":"","property":"","from":"","to":"","reg":false,"x":290,"y":440,"wires":[["84f21f90c7fb7ad9"]]},{"id":"185c0de8add48677","type":"split","z":"b2605c103b3c10bd","g":"66cb617f2a1e06bb","name":"split domains","splt":"\\n","spltType":"str","arraySplt":1,"arraySpltType":"len","stream":false,"addname":"","x":370,"y":580,"wires":[["093ef87d1423fd0b"]]},{"id":"84f21f90c7fb7ad9","type":"change","z":"b2605c103b3c10bd","g":"66cb617f2a1e06bb","name":"Set payload to domains","rules":[{"t":"set","p":"payload","pt":"msg","to":"domains","tot":"msg"}],"action":"","property":"","from":"","to":"","reg":false,"x":150,"y":580,"wires":[["185c0de8add48677"]]},{"id":"30a90ff9536913ba","type":"function","z":"b2605c103b3c10bd","g":"66cb617f2a1e06bb","name":"Check current registered IP, \\n Prepare request","func":"// save current domain for use later in flow\nmsg.domain = msg.payload\n// find existing record for the domain\nconst record = msg.dnsRecords.find(element => element.name == msg.payload);\nif (record) {\n    if (msg.ip != record.content) {\n        msg.url = `https://api.cloudflare.com/client/v4/zones/${env.get(\"ZoneID\")}/dns_records/${record.id}`\n        msg.headers = {\n            \"X-Auth-Email\": env.get(\"Email\"),\n            \"X-Auth-Key\": env.get(\"Key\"),\n            \"Content-Type\": \"application/json\"\n        }\n        node.status({ fill: \"green\", text: `Updating ${msg.payload}` });\n        msg.payload = { \"type\": \"A\", \"name\": msg.payload, \"content\": msg.ip, \"ttl\": 1, \"proxied\": env.get(\"Proxied\") }\n    } else {\n        // IP does not need changing\n        node.status({ fill: \"green\",text: `${msg.payload} Unchanged` });\n        let msg2 = RED.util.cloneMessage(msg);\n        msg2.payload = `${msg.payload} Unchanged`\n        node.send([null, msg2, null])\n        msg = null\n    }\n} else {\n    let msg2 = RED.util.cloneMessage(msg);\n    msg2.payload = `Record for ${msg.payload} not found`\n    node.send([null, null, msg2])\n    node.status({ fill: \"red\", text: `Record for ${msg.payload} not found` });\n\n    msg = null\n}\nreturn msg;","outputs":3,"noerr":0,"initialize":"","finalize":"","libs":[],"x":420,"y":640,"wires":[["60441a47a703c104"],["2ba9a52f9f444496"],["97a03b41453e681a"]]},{"id":"97a03b41453e681a","type":"link out","z":"b2605c103b3c10bd","g":"66cb617f2a1e06bb","name":"link out 3","mode":"link","links":["631528af6996430d"],"x":425,"y":700,"wires":[]},{"id":"60441a47a703c104","type":"http request","z":"b2605c103b3c10bd","g":"66cb617f2a1e06bb","name":"Update record","method":"PUT","ret":"obj","paytoqs":"ignore","url":"","tls":"","persist":false,"proxy":"","insecureHTTPParser":false,"authType":"","senderr":false,"headers":[],"x":180,"y":760,"wires":[["6c7e843c8161757e"]]},{"id":"6c7e843c8161757e","type":"switch","z":"b2605c103b3c10bd","g":"66cb617f2a1e06bb","name":"status 200?","property":"statusCode","propertyType":"msg","rules":[{"t":"eq","v":"200","vt":"num"},{"t":"else"}],"checkall":"true","repair":false,"outputs":2,"x":390,"y":760,"wires":[["9483a955d6f12538"],["5af9ab018c92680d","05a88e7ff38ea77c"]]},{"id":"5af9ab018c92680d","type":"link out","z":"b2605c103b3c10bd","g":"66cb617f2a1e06bb","name":"link out 4","mode":"link","links":["631528af6996430d"],"x":565,"y":840,"wires":[]},{"id":"093ef87d1423fd0b","type":"delay","z":"b2605c103b3c10bd","g":"66cb617f2a1e06bb","name":"One every two seconds","pauseType":"rate","timeout":"5","timeoutUnits":"seconds","rate":"1","nbRateUnits":"2","rateUnits":"second","randomFirst":"1","randomLast":"5","randomUnits":"seconds","drop":false,"allowrate":false,"outputs":1,"x":150,"y":640,"wires":[["30a90ff9536913ba"]]},{"id":"e7d67646cd91b5de","type":"status","z":"b2605c103b3c10bd","name":"","scope":["8fb0166cc4a85bf2","30a90ff9536913ba","9483a955d6f12538"],"x":280,"y":1040,"wires":[[]]},{"id":"2ba9a52f9f444496","type":"join","z":"b2605c103b3c10bd","g":"66cb617f2a1e06bb","name":"","mode":"auto","build":"object","property":"payload","propertyType":"msg","key":"topic","joiner":"\\n","joinerType":"str","accumulate":"false","timeout":"","count":"","reduceRight":false,"x":810,"y":740,"wires":[[]]},{"id":"4651b729ca6d1a38","type":"link out","z":"b2605c103b3c10bd","g":"eeb017401050907c","name":"link out 7","mode":"link","links":["f4e2275fe98ba7c4"],"x":535,"y":200,"wires":[]},{"id":"9483a955d6f12538","type":"function","z":"b2605c103b3c10bd","g":"66cb617f2a1e06bb","name":"IP updated","func":"msg.payload = `${msg.domain} Updated`\nnode.status({ fill: \"green\", text: `${msg.domain} updated` });\nreturn msg;","outputs":1,"noerr":0,"initialize":"","finalize":"","libs":[],"x":590,"y":740,"wires":[["2ba9a52f9f444496"]]},{"id":"d4a77c1dff11fdd4","type":"change","z":"b2605c103b3c10bd","g":"eeb017401050907c","name":"Error fetching IP address","rules":[{"t":"set","p":"payload","pt":"msg","to":"[\"Error fetching IP address from http://ipinfo.io/ip\"]","tot":"json"}],"action":"","property":"","from":"","to":"","reg":false,"x":330,"y":200,"wires":[["4651b729ca6d1a38"]]},{"id":"f4e2275fe98ba7c4","type":"link in","z":"b2605c103b3c10bd","name":"link in 3","links":["4651b729ca6d1a38","da6bc08f85b2b276"],"x":945,"y":660,"wires":[[]]},{"id":"35e5bcc0290e2bf2","type":"change","z":"b2605c103b3c10bd","g":"0d186e43bfe6c8d1","name":"Error fetching DNS records","rules":[{"t":"set","p":"payload","pt":"msg","to":"[\"Error fetching DNS records from cloudflare\"]","tot":"json"}],"action":"","property":"","from":"","to":"","reg":false,"x":680,"y":400,"wires":[["da6bc08f85b2b276","0f445f09c61a7be5"]]},{"id":"da6bc08f85b2b276","type":"link out","z":"b2605c103b3c10bd","g":"0d186e43bfe6c8d1","name":"link out 8","mode":"link","links":["f4e2275fe98ba7c4"],"x":905,"y":400,"wires":[]},{"id":"05a88e7ff38ea77c","type":"function","z":"b2605c103b3c10bd","g":"66cb617f2a1e06bb","name":"Error updating domain","func":"msg.payload = `${msg.domain}  Error during update`\nreturn msg;","outputs":1,"noerr":0,"initialize":"","finalize":"","libs":[],"x":500,"y":900,"wires":[["2ba9a52f9f444496"]]},{"id":"5ca6f91adb8a1873","type":"change","z":"b2605c103b3c10bd","g":"eeb017401050907c","name":"Status: fetching IP","rules":[{"t":"set","p":"status.text","pt":"msg","to":"Fetching current IP address","tot":"str"}],"action":"","property":"","from":"","to":"","reg":false,"x":450,"y":120,"wires":[["1ce90f9f100db058"]]},{"id":"1ce90f9f100db058","type":"link out","z":"b2605c103b3c10bd","g":"eeb017401050907c","name":"link out 10","mode":"link","links":["5f0ca8668bb282e0"],"x":575,"y":120,"wires":[]},{"id":"5f0ca8668bb282e0","type":"link in","z":"b2605c103b3c10bd","name":"link in 4","links":["1ce90f9f100db058","d9d6bf93b01ade66"],"x":425,"y":1000,"wires":[[]]},{"id":"d9d6bf93b01ade66","type":"link out","z":"b2605c103b3c10bd","g":"0d186e43bfe6c8d1","name":"link out 11","mode":"link","links":["5f0ca8668bb282e0"],"x":915,"y":460,"wires":[]},{"id":"0f445f09c61a7be5","type":"change","z":"b2605c103b3c10bd","g":"0d186e43bfe6c8d1","name":"Status: Error fetching DNS records","rules":[{"t":"delete","p":"payload","pt":"msg"},{"t":"set","p":"status.text","pt":"msg","to":"Error fetching DNS records","tot":"str"}],"action":"","property":"","from":"","to":"","reg":false,"x":720,"y":460,"wires":[["d9d6bf93b01ade66"]]},{"id":"eeb017401050907c","type":"group","z":"b2605c103b3c10bd","name":"Get Current IP Address","style":{"label":true},"nodes":["56c00aa5bbf32b95","27a26faaeaddf108","337770b779737664","41341f10167041c5","b133ab72bfb0f0bc","4651b729ca6d1a38","d4a77c1dff11fdd4","5ca6f91adb8a1873","1ce90f9f100db058"],"x":54,"y":79,"w":852,"h":162},{"id":"0d186e43bfe6c8d1","type":"group","z":"b2605c103b3c10bd","name":"Get DNS records from Cloudflare \\n ","style":{"label":true,"label-position":"n"},"nodes":["8fb0166cc4a85bf2","d002a11e550e568f","b7281df6ef9ea0c4","315f6996631ee864","b2c3b7ed632da273","35e5bcc0290e2bf2","da6bc08f85b2b276","d9d6bf93b01ade66","0f445f09c61a7be5"],"x":14,"y":303,"w":942,"h":198},{"id":"66cb617f2a1e06bb","type":"group","z":"b2605c103b3c10bd","name":"Check/Update records \\n ","style":{"label":true,"label-position":"n"},"nodes":["84f21f90c7fb7ad9","185c0de8add48677","30a90ff9536913ba","97a03b41453e681a","60441a47a703c104","6c7e843c8161757e","5af9ab018c92680d","093ef87d1423fd0b","2ba9a52f9f444496","9483a955d6f12538","05a88e7ff38ea77c"],"x":14,"y":523,"w":872,"h":418},{"id":"e7c7eef22ae43dae","type":"inject","z":"dd2ba972fa7bbf9b","name":"","props":[{"p":"payload"}],"repeat":"","crontab":"","once":false,"onceDelay":"10","topic":"","payload":"[\"test.mydomain.com\",\"grafana.mydomain.com\",\"nodered.mydomain.com\"]","payloadType":"json","x":150,"y":400,"wires":[["c6834533eccd6189"]]},{"id":"c6834533eccd6189","type":"subflow:b2605c103b3c10bd","z":"dd2ba972fa7bbf9b","name":"","env":[{"name":"ZoneID","value":"ebbee93a8964cb9b852","type":"str"},{"name":"Email","value":"[email protected]","type":"str"},{"name":"Key","type":"cred"}],"x":350,"y":400,"wires":[["62d43b7f7603534f"],["90e7e841f7a5c5f6"]]},{"id":"62d43b7f7603534f","type":"debug","z":"dd2ba972fa7bbf9b","name":"OP","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"payload","targetType":"msg","statusVal":"","statusType":"auto","x":570,"y":380,"wires":[]},{"id":"90e7e841f7a5c5f6","type":"debug","z":"dd2ba972fa7bbf9b","name":"ERROR","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"true","targetType":"full","statusVal":"","statusType":"auto","x":580,"y":440,"wires":[]}]

Flow Info

Created 1 year, 7 months ago
Updated 1 year, 3 months ago
Rating: 5 1

Owner

Actions

Rate:

Node Types

Core
  • catch (x1)
  • change (x8)
  • comment (x1)
  • debug (x2)
  • delay (x1)
  • function (x4)
  • http request (x3)
  • inject (x1)
  • join (x1)
  • link in (x3)
  • link out (x8)
  • split (x1)
  • status (x1)
  • switch (x3)
Other
  • group (x3)
  • subflow (x1)
  • subflow:b2605c103b3c10bd (x1)

Tags

  • Cloudflare
  • DNS
Copy this flow JSON to your clipboard and then import into Node-RED using the Import From > Clipboard (Ctrl-I) menu option