Task: Message when Docker Hub has new images for containers

n8n workflow JSON:
{"id":"7","name":"Notify when a Docker image update is available","nodes":[{"name":"On clicking 'execute'","type":"n8n-nodes-base.manualTrigger","position":[90,230],"parameters":{},"typeVersion":1},{"name":"HTTP Request","type":"n8n-nodes-base.httpRequest","notes":"Get latest images from DockerHub","position":[660,120],"parameters":{"url":"=https://hub.docker.com/v2/repositories/{{$json[\"repo\"]}}/tags/{{$json[\"tag\"]}}/","options":{},"queryParametersUi":{"parameter":[{"name":"ordering","value":"last_updated"},{"name":"page_size","value":"25"},{"name":"page","value":"1"}]},"headerParametersUi":{"parameter":[]}},"notesInFlow":true,"typeVersion":1},{"name":"Execute Command","type":"n8n-nodes-base.executeCommand","notes":"kubectl get pods","position":[290,230],"parameters":{"command":"/home/node/.n8n/bin/kubectl get pod --namespace default -o json"},"notesInFlow":true,"typeVersion":1},{"name":"Function","type":"n8n-nodes-base.function","notes":"Parse kube images","position":[480,230],"parameters":{"functionCode":"var newitems = [];\njsonObject = JSON.parse(items[0].json.stdout);\n\nfor (i = 0; i < jsonObject.items.length; i++) {\n  for (s = 0; s < jsonObject.items[i].status.containerStatuses.length; s++) {\n    name = jsonObject.items[i].status.containerStatuses[s].name;\n    image = jsonObject.items[i].status.containerStatuses[s].image;\n    combined = jsonObject.items[i].status.containerStatuses[s].imageID;\n    \n    if (!image.match(/\\//)) {\n      repo = 'library/' + image.match(/([^\\:]+)/)[1];\n    } else {\n      if (image.match(/([^\\:]+)/)) {\n        repo = image.match(/([^\\:]+)/)[1];\n      }\n    }\n      tagCapture = /[^\\:]+\\:(.*)/\n      if (image.match(tagCapture)) {\n        tag = image.match(tagCapture)[1];\n      } else {\n        tag = 'latest';\n      }\n\n      digestCapture = /@(.*)/\n      digest = combined.match(digestCapture)[1];\n\n      newitems.push({ \"json\": { \"name\": name, \"repo\": repo, \"tag\": tag, \"digest\": digest } });\n  }\n}\n\nnewitems = newitems.filter((thing, index, self) =>\n  index === self.findIndex((t) => (\n    t.json.image === thing.json.image && t.json.digest === thing.json.digest && !t.json.repo.match(/[^\\/]+\\..*\\//)\n  ))\n)\n\nreturn newitems;"},"notesInFlow":true,"typeVersion":1},{"name":"Function1","type":"n8n-nodes-base.function","notes":"Get out of date images for each repo/image:tag","position":[1000,210],"parameters":{"functionCode":"returnItems = [];\n\nfor (item = 0; item < items.length; item++) {\n  currentItemJson = items[item].json;\n  for (imageIndex = 0; imageIndex < currentItemJson.images.length; imageIndex++) {\n    if (currentItemJson.images[imageIndex].architecture == \"amd64\") {\n      break;\n    }\n  }\n  latestDigest = currentItemJson.images[imageIndex].digest;\n  if (currentItemJson.digest != latestDigest) {\n    returnItems.push({\"json\": { \n                        \"name\": currentItemJson.name,\n                        \"out_of_date_image\": currentItemJson.repo, \n                        \"tag\": currentItemJson.tag, \n                        \"out_of_date_digest\": currentItemJson.digest,\n                        \"dockerhub_digest\": latestDigest\n                    }});\n  }\n}\n\nreturn returnItems;\n"},"notesInFlow":true,"typeVersion":1},{"name":"Discord","type":"n8n-nodes-base.discord","notes":"Send notification","position":[650,900],"parameters":{"text":"=```\n{{$json[\"name\"]}}\n```\nNew image available for **{{$json[\"out_of_date_image\"]}}:{{$json[\"tag\"]}}**\nCluster digest:`{{$json[\"out_of_date_digest\"]}}`\nDocker Hub digest: `{{$json[\"dockerhub_digest\"]}}`","webhookUri":"CHANGEME"},"notesInFlow":true,"typeVersion":1},{"name":"Cron","type":"n8n-nodes-base.cron","position":[90,90],"parameters":{"triggerTimes":{"item":[{"mode":"custom","cronExpression":"24 */6 * * *"}]}},"typeVersion":1},{"name":"Merge","type":"n8n-nodes-base.merge","position":[830,210],"parameters":{"mode":"mergeByIndex"},"typeVersion":1},{"name":"IF","type":"n8n-nodes-base.if","notes":"Check if the digest from Docker Hub is the same as what's on disk","position":[650,490],"parameters":{"conditions":{"string":[{"value1":"={{$json[\"stdout\"]}}","value2":"={{$json[\"dockerhub_digest\"]}}"}]}},"notesInFlow":true,"typeVersion":1},{"name":"NoOp","type":"n8n-nodes-base.noOp","position":[840,420],"parameters":{},"typeVersion":1},{"name":"Execute Command1","type":"n8n-nodes-base.executeCommand","notes":"Read previously saved digests","position":[290,510],"parameters":{"command":"=cat '/home/node/.n8n/storage/checkDockerHubForNewImages/{{$node[\"Function1\"].json[\"out_of_date_image\"]}}/{{$node[\"Function1\"].json[\"tag\"]}}.txt'","executeOnce":false},"notesInFlow":true,"typeVersion":1},{"name":"Execute Command2","type":"n8n-nodes-base.executeCommand","notes":"Save Docker Hub digest so we don't alert next time","position":[650,750],"parameters":{"command":"=mkdir -p '/home/node/.n8n/storage/checkDockerHubForNewImages/{{$json[\"out_of_date_image\"]}}'\necho '{{$json[\"dockerhub_digest\"]}}' >  '/home/node/.n8n/storage/checkDockerHubForNewImages/{{$json[\"out_of_date_image\"]}}/{{$json[\"tag\"]}}.txt'","executeOnce":false},"notesInFlow":true,"typeVersion":1,"continueOnFail":true},{"name":"Execute Command3","type":"n8n-nodes-base.executeCommand","notes":"Reset notifications","position":[380,50],"parameters":{"command":"=rm -rf /home/node/.n8n/storage/checkDockerHubForNewImages/*"},"notesInFlow":true,"typeVersion":1},{"name":"Merge1","type":"n8n-nodes-base.merge","position":[480,490],"parameters":{"mode":"mergeByIndex"},"typeVersion":1},{"name":"IF1","type":"n8n-nodes-base.if","notes":"Check if the digest from Docker Hub is the same as what's on disk","position":[480,800],"parameters":{"conditions":{"string":[{"value1":"={{$json[\"out_of_date_digest\"]}}","value2":"={{$json[\"docker-content-digest\"]}}"}]}},"notesInFlow":true,"typeVersion":1},{"name":"HTTP Request1","type":"n8n-nodes-base.httpRequest","notes":"Get a Dockerhub token","position":[840,580],"parameters":{"url":"=https://auth.docker.io/token?service=registry.docker.io&scope=repository:{{$json[\"out_of_date_image\"]}}:pull","options":{}},"executeOnce":false,"notesInFlow":true,"typeVersion":1},{"name":"HTTP Request2","type":"n8n-nodes-base.httpRequest","notes":"Get Manifests","position":[1170,530],"parameters":{"url":"=https://registry-1.docker.io/v2/{{$json[\"out_of_date_image\"]}}/manifests/{{$json[\"tag\"]}}","options":{},"requestMethod":"HEAD","headerParametersUi":{"parameter":[{"name":"Authorization","value":"=Bearer {{$json[\"token\"]}}"}]}},"notesInFlow":true,"typeVersion":1},{"name":"Merge2","type":"n8n-nodes-base.merge","position":[290,800],"parameters":{"mode":"mergeByIndex"},"typeVersion":1},{"name":"Merge3","type":"n8n-nodes-base.merge","position":[1000,530],"parameters":{"mode":"mergeByIndex"},"typeVersion":1}],"active":true,"settings":{},"connections":{"IF":{"main":[[{"node":"NoOp","type":"main","index":0}],[{"node":"HTTP Request1","type":"main","index":0},{"node":"Merge2","type":"main","index":0},{"node":"Merge3","type":"main","index":0}]]},"IF1":{"main":[[{"node":"Execute Command2","type":"main","index":0}],[{"node":"Execute Command2","type":"main","index":0},{"node":"Discord","type":"main","index":0}]]},"Cron":{"main":[[{"node":"Execute Command","type":"main","index":0}]]},"Merge":{"main":[[{"node":"Function1","type":"main","index":0}]]},"Merge1":{"main":[[{"node":"IF","type":"main","index":0}]]},"Merge2":{"main":[[{"node":"IF1","type":"main","index":0}]]},"Merge3":{"main":[[{"node":"HTTP Request2","type":"main","index":0}]]},"Function":{"main":[[{"node":"HTTP Request","type":"main","index":0},{"node":"Merge","type":"main","index":1}]]},"Function1":{"main":[[{"node":"Execute Command1","type":"main","index":0},{"node":"Merge1","type":"main","index":0}]]},"HTTP Request":{"main":[[{"node":"Merge","type":"main","index":0}]]},"HTTP Request1":{"main":[[{"node":"Merge3","type":"main","index":1}]]},"HTTP Request2":{"main":[[{"node":"Merge2","type":"main","index":1}]]},"Execute Command":{"main":[[{"node":"Function","type":"main","index":0}]]},"Execute Command1":{"main":[[{"node":"Merge1","type":"main","index":1}]]},"On clicking 'execute'":{"main":[[{"node":"Execute Command","type":"main","index":0}]]}}}