From 0f874e98caf60193373754f85e00598496af3ebc Mon Sep 17 00:00:00 2001 From: Nathan Vaughn Date: Tue, 29 Jun 2021 18:12:29 -0500 Subject: [PATCH 1/7] Rewrite with Python --- Dockerfile | 8 +++--- README.md | 75 ++++++++++++++++++++++++++++++++++++++++++++------- action.yml | 26 ++++++++++++++++++ entrypoint.sh | 32 ---------------------- main.py | 70 +++++++++++++++++++++++++++++++++++++++++++++++ 5 files changed, 165 insertions(+), 46 deletions(-) delete mode 100755 entrypoint.sh create mode 100644 main.py diff --git a/Dockerfile b/Dockerfile index fb6efb3..34e8caa 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,6 +1,4 @@ -FROM alpine:3.14.0 +FROM python:3.9-alpine -RUN apk add --no-cache bash curl - -COPY entrypoint.sh /entrypoint.sh -ENTRYPOINT ["/entrypoint.sh"] \ No newline at end of file +COPY main.py /app/main.py +ENTRYPOINT ["python", "/app/main.py"] \ No newline at end of file diff --git a/README.md b/README.md index 7a1714f..7436393 100644 --- a/README.md +++ b/README.md @@ -2,45 +2,102 @@ [![Codacy Badge](https://api.codacy.com/project/badge/Grade/816b3dfe8bb34c9eb922a638ff6fa3bb)](https://www.codacy.com/manual/NathanVaughn/actions-cloudflare-purge?utm_source=github.com&utm_medium=referral&utm_content=NathanVaughn/actions-cloudflare-purge&utm_campaign=Badge_Grade) This action uses Cloudflare's API to purge their -[entire cache](https://api.cloudflare.com/#zone-purge-all-files) of your site. +[cache](https://api.cloudflare.com/#zone-purge-all-files) of your site. ## Inputs -All inputs are pulled from environment variables for security and are required. -You can set these in the [secrets](https://help.github.com/en/articles/virtual-environments-for-github-actions#creating-and-using-secrets-encrypted-variables) -section of your repository information. +You can mix and match the various inputs however you want +(other than the zone and auth key). If you don't provide +`urls` or `tags` or `hosts` or `prefixes`, then all files will be purged. -### `CLOUDFLARE_ZONE` +### `cf_zone` or `CLOUDFLARE_ZONE` environment variable The zone ID of your Cloudflare site. Example: -```bash +```text 023e105f4ecef8ad9ca31a8372d0c353 ``` -### `CLOUDFLARE_AUTH_KEY` +### `cf_auth` or `CLOUDFLARE_AUTH_KEY` environment variable The Cloudflare API key you've generated for your zone. Example: -```bash +```text c2547eb745079dac9320b638f5e225cf483cc5cfdda41 ``` +### `urls` (optional) + +A space seperated list of URLs to purge. Example: + +```text +"https://nathanv.me/assets/images/profile.png https://nathanv.me/assets/images/favicons/apple-touch-icon.png" +``` + +### `tags` (optional) + +A space seperated list of tags to purge. Example: + +```text +"some-tag another-tag" +``` + +### `hosts` (optional) + +A space seperated list of hosts to purge. Example: + +```text +"nathanv.me blog.nathanv.me" +``` + +### `prefixes` (optional) + +A space seperated list of prefixes to purge. Example: + +```text +"nathanv.me/assets/ blog.nathanv.me/assets" +``` + + ## Outputs None -## Example Usage +## Example Usages + +```yml +- name: Purge cache + uses: nathanvaughn/actions-cloudflare-purge@master + if: success() + # preferred + with: + cf_zone: ${{ secrets.CLOUDFLARE_ZONE }} + cf_auth: ${{ secrets.CLOUDFLARE_AUTH_KEY }} +``` ```yml - name: Purge cache uses: nathanvaughn/actions-cloudflare-purge@master if: success() + # legacy env: CLOUDFLARE_ZONE: ${{ secrets.CLOUDFLARE_ZONE }} CLOUDFLARE_AUTH_KEY: ${{ secrets.CLOUDFLARE_AUTH_KEY }} ``` +```yml +- name: Purge cache + uses: nathanvaughn/actions-cloudflare-purge@master + if: success() + with: + cf_zone: ${{ secrets.CLOUDFLARE_ZONE }} + cf_auth: ${{ secrets.CLOUDFLARE_AUTH_KEY }} + urls: "https://nathanv.me/assets/images/profile.png https://nathanv.me/assets/images/favicons/apple-touch-icon.png" + tags: "some-tag another-tag" + hosts: "nathanv.me blog.nathanv.me" + prefixes: "nathanv.me/assets/ blog.nathanv.me/assets" +``` + ## Getting Cloudflare Info 1. First, go to the [API tokens page](https://dash.cloudflare.com/profile/api-tokens) diff --git a/action.yml b/action.yml index 8d184e4..457b339 100644 --- a/action.yml +++ b/action.yml @@ -1,9 +1,35 @@ name: 'Cloudflare Cache Purge Action' description: 'A GitHub Action to purge Cloudflare''s entire cache of your site' author: 'Nathan Vaughn' +inputs: + cf_zone: + description: 'Cloudflare Zone' + required: false + cf_auth: + description: 'Cloudflare Authentication Key' + required: false + urls: + description: 'URLs to purge' + required: true + tags: + description: 'Cache-Tags to purge' + required: true + hosts: + description: 'Hosts to purge' + required: true + prefixes: + description: 'Prefixes to purge' + required: true runs: using: 'docker' image: 'Dockerfile' + args: + - '--cf-zone ${{ inputs.cf_zone }}' + - '--cf-auth ${{ inputs.cf_auth }}' + - '--urls ${{ inputs.urls }}' + - '--tags ${{ inputs.tags }}' + - '--hosts ${{ inputs.hosts }}' + - '--prefixes ${{ inputs.prefixes }}' branding: icon: 'cloud' color: 'orange' \ No newline at end of file diff --git a/entrypoint.sh b/entrypoint.sh deleted file mode 100755 index b303805..0000000 --- a/entrypoint.sh +++ /dev/null @@ -1,32 +0,0 @@ -#!/bin/bash - -set -e - -function print_error() { - echo -e "\e[31mERROR: ${1}\e[m" -} - -if [ -z "${CLOUDFLARE_ZONE}" ]; then - print_error "Cloudflare zone not found" - exit 1 -fi - -if [ -z "${CLOUDFLARE_AUTH_KEY}" ]; then - print_error "Cloudflare auth key not found" - exit 1 -fi - -response=$(curl -X POST "https://api.cloudflare.com/client/v4/zones/${CLOUDFLARE_ZONE}/purge_cache" -H "Authorization: Bearer ${CLOUDFLARE_AUTH_KEY}" -H "Content-Type: application/json" --data '{"purge_everything":true}') - -echo "Response data: $response" - -# remove whitespace from string -response=$(echo "$response" | tr -d ' ') - -if [[ $response =~ '"success":true' ]]; then - echo "Cloudflare cache cleared successfully" - exit 0 -else - print_error "Cloudflare API call failed" - exit 1 -fi diff --git a/main.py b/main.py new file mode 100644 index 0000000..35369b7 --- /dev/null +++ b/main.py @@ -0,0 +1,70 @@ +import argparse +import json +import pprint +import os +from urllib import request, parse + + +def main(): + # parse the arguments + parser = argparse.ArgumentParser() + + # load values from environment first, by default + parser.add_argument("--cf-zone", nargs="?", type=str, default=os.getenv("CLOUDFLARE_ZONE")) + parser.add_argument("--cf-auth", nargs="?", type=str, default=os.getenv("CLOUDFLARE_AUTH_KEY")) + + # caching objects + parser.add_argument("--urls", nargs="*", type=str) + parser.add_argument("--tags", nargs="*", type=str) + parser.add_argument("--hosts", nargs="*", type=str) + parser.add_argument("--prefixes", nargs="*", type=str) + + args = parser.parse_args() + + if not args.cf_zone: + parser.error("Cloudflare Zone required") + + if not args.cf_auth: + parser.error("Cloudflare Auth required") + + # prepare the request data + + data = {} + + if args.urls: + data["files"] = args.urls + + if args.tags: + data["tags"] = args.tags + + if args.hosts: + data["hosts"] = args.hosts + + if args.prefixes: + data["prefixes"] = args.prefixes + + if not args.urls and not args.tags and not args.hosts and not args.prefixes: + data["purge_everything"] = True + + # create the request + url = f"https://api.cloudflare.com/client/v4/zones/args.cf_zone/purge_cache" + headers = {"Authorization": f"Bearer {args.cf_auth}"} + encoded_data = parse.urlencode(data).encode() + + # send it + print(f"Making POST request to {url} with {data}") + + req = request.Request(url, data=encoded_data, headers=headers) + resp = request.urlopen(req) + + # process response + resp_data = json.loads(resp.read()) + + print(pprint.pprint(resp_data)) + + if resp_data["success"] != True: + raise Exception("Success NOT True") + + +if __name__ == "__main__": + main() \ No newline at end of file From 6d5954f66cf188857e547b833319dafa6ddf023f Mon Sep 17 00:00:00 2001 From: Nathan Vaughn Date: Tue, 29 Jun 2021 18:28:37 -0500 Subject: [PATCH 2/7] Try and fix args --- action.yml | 7 +------ main.py | 15 ++++++++++++--- 2 files changed, 13 insertions(+), 9 deletions(-) diff --git a/action.yml b/action.yml index 457b339..fc24a6b 100644 --- a/action.yml +++ b/action.yml @@ -24,12 +24,7 @@ runs: using: 'docker' image: 'Dockerfile' args: - - '--cf-zone ${{ inputs.cf_zone }}' - - '--cf-auth ${{ inputs.cf_auth }}' - - '--urls ${{ inputs.urls }}' - - '--tags ${{ inputs.tags }}' - - '--hosts ${{ inputs.hosts }}' - - '--prefixes ${{ inputs.prefixes }}' + - '--cf-zone ${{ inputs.cf_zone }} --cf-auth ${{ inputs.cf_auth }} --urls ${{ inputs.urls }} --tags ${{ inputs.tags }} --hosts ${{ inputs.hosts }} --prefixes ${{ inputs.prefixes }}' branding: icon: 'cloud' color: 'orange' \ No newline at end of file diff --git a/main.py b/main.py index 35369b7..5701045 100644 --- a/main.py +++ b/main.py @@ -2,6 +2,7 @@ import json import pprint import os +import sys from urllib import request, parse @@ -10,8 +11,8 @@ def main(): parser = argparse.ArgumentParser() # load values from environment first, by default - parser.add_argument("--cf-zone", nargs="?", type=str, default=os.getenv("CLOUDFLARE_ZONE")) - parser.add_argument("--cf-auth", nargs="?", type=str, default=os.getenv("CLOUDFLARE_AUTH_KEY")) + parser.add_argument("--cf-zone", nargs="?", type=str) + parser.add_argument("--cf-auth", nargs="?", type=str) # caching objects parser.add_argument("--urls", nargs="*", type=str) @@ -19,8 +20,16 @@ def main(): parser.add_argument("--hosts", nargs="*", type=str) parser.add_argument("--prefixes", nargs="*", type=str) - args = parser.parse_args() + args = parser.parse_args(sys.argv[1]) + # if no argument given, pull from environment + if not args.cf_zone: + args.cf_zone = os.getenv("CLOUDFLARE_ZONE") + + if not args.cf_auth: + args.cf_auth = os.getenv("CLOUDFLARE_AUTH_KEY") + + # see if anything was set if not args.cf_zone: parser.error("Cloudflare Zone required") From c0bb059ea7b3f2ec84023cbd2950e0de58257c4d Mon Sep 17 00:00:00 2001 From: Nathan Vaughn Date: Tue, 29 Jun 2021 18:32:25 -0500 Subject: [PATCH 3/7] Missed a split --- main.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/main.py b/main.py index 5701045..02ff964 100644 --- a/main.py +++ b/main.py @@ -20,7 +20,7 @@ def main(): parser.add_argument("--hosts", nargs="*", type=str) parser.add_argument("--prefixes", nargs="*", type=str) - args = parser.parse_args(sys.argv[1]) + args = parser.parse_args(sys.argv[1].split()) # if no argument given, pull from environment if not args.cf_zone: From e4a4993f07899cf6e8605edee550a219a991a356 Mon Sep 17 00:00:00 2001 From: Nathan Vaughn Date: Tue, 29 Jun 2021 18:33:58 -0500 Subject: [PATCH 4/7] missed format string --- main.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/main.py b/main.py index 02ff964..2e02cfc 100644 --- a/main.py +++ b/main.py @@ -56,7 +56,7 @@ def main(): data["purge_everything"] = True # create the request - url = f"https://api.cloudflare.com/client/v4/zones/args.cf_zone/purge_cache" + url = f"https://api.cloudflare.com/client/v4/zones/{args.cf_zone}/purge_cache" headers = {"Authorization": f"Bearer {args.cf_auth}"} encoded_data = parse.urlencode(data).encode() From 09ca54e9da5819f97e8e2cbf551233a020d2ce3c Mon Sep 17 00:00:00 2001 From: Nathan Vaughn Date: Tue, 29 Jun 2021 18:41:41 -0500 Subject: [PATCH 5/7] Try and fix data format --- main.py | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/main.py b/main.py index 2e02cfc..75a6be2 100644 --- a/main.py +++ b/main.py @@ -3,7 +3,7 @@ import pprint import os import sys -from urllib import request, parse +from urllib import request def main(): @@ -25,7 +25,7 @@ def main(): # if no argument given, pull from environment if not args.cf_zone: args.cf_zone = os.getenv("CLOUDFLARE_ZONE") - + if not args.cf_auth: args.cf_auth = os.getenv("CLOUDFLARE_AUTH_KEY") @@ -57,8 +57,12 @@ def main(): # create the request url = f"https://api.cloudflare.com/client/v4/zones/{args.cf_zone}/purge_cache" - headers = {"Authorization": f"Bearer {args.cf_auth}"} - encoded_data = parse.urlencode(data).encode() + encoded_data = json.dumps(data).encode("utf-8") + headers = { + "Authorization": f"Bearer {args.cf_auth}", + "Content-Type": "application/json", + "Content-Length": len(encoded_data), + } # send it print(f"Making POST request to {url} with {data}") From b4750b4ce3855b49fa8a53f8f4966158ffb9d376 Mon Sep 17 00:00:00 2001 From: Nathan Vaughn Date: Tue, 29 Jun 2021 18:44:12 -0500 Subject: [PATCH 6/7] Add one extra print --- main.py | 1 + 1 file changed, 1 insertion(+) diff --git a/main.py b/main.py index 75a6be2..cb13429 100644 --- a/main.py +++ b/main.py @@ -73,6 +73,7 @@ def main(): # process response resp_data = json.loads(resp.read()) + print("Response:") print(pprint.pprint(resp_data)) if resp_data["success"] != True: From befedd39c6ebcbe381382d37dcf697616f6a47d3 Mon Sep 17 00:00:00 2001 From: Nathan Vaughn Date: Tue, 29 Jun 2021 18:47:55 -0500 Subject: [PATCH 7/7] Add badges --- README.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 7436393..516cb60 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,6 @@ # Cloudflare Cache Purge Action + +[![Code style: black](https://img.shields.io/badge/code%20style-black-000000.svg)](https://github.com/psf/black) [![Codacy Badge](https://api.codacy.com/project/badge/Grade/816b3dfe8bb34c9eb922a638ff6fa3bb)](https://www.codacy.com/manual/NathanVaughn/actions-cloudflare-purge?utm_source=github.com&utm_medium=referral&utm_content=NathanVaughn/actions-cloudflare-purge&utm_campaign=Badge_Grade) This action uses Cloudflare's API to purge their @@ -58,7 +60,6 @@ A space seperated list of prefixes to purge. Example: "nathanv.me/assets/ blog.nathanv.me/assets" ``` - ## Outputs None