Skip to content

Commit

Permalink
Merge pull request #32 from NathanVaughn/feature/proper-args
Browse files Browse the repository at this point in the history
Fix argument parsing and 403 errors
  • Loading branch information
NathanVaughn authored Jul 7, 2022
2 parents 831dd21 + 8e4deb6 commit 2ce9f53
Show file tree
Hide file tree
Showing 5 changed files with 512 additions and 110 deletions.
4 changes: 2 additions & 2 deletions .github/workflows/tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ jobs:
tests:

runs-on: ubuntu-latest

steps:
- name: Checkout Code
uses: actions/checkout@v2
Expand All @@ -19,7 +19,7 @@ jobs:
uses: actions/setup-python@v2
with:
python-version: "3.9"

- name: Install pytest
run: python -m pip install pytest

Expand Down
77 changes: 44 additions & 33 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,9 @@ This action uses Cloudflare's API to purge their

## Inputs

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.
You can mix and match the various inputs however you want
(other than the zone and auth key). If you don't provide
`files` or `tags` or `hosts` or `prefixes`, then all files will be purged.

### `cf_zone` or `CLOUDFLARE_ZONE` environment variable

Expand All @@ -27,36 +27,38 @@ The Cloudflare API key you've generated for your zone. Example:
c2547eb745079dac9320b638f5e225cf483cc5cfdda41
```

### `urls` (optional)
### `files` (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
```yml
files: https://nathanv.me/assets/images/profile.png https://nathanv.me/assets/images/favicons/apple-touch-icon.png
```
The key `urls` is also accepted for backwards compatibility.

### `tags` (optional)

A space seperated list of tags to purge. Example:

```text
some-tag another-tag
```yml
tags: some-tag another-tag
```

### `hosts` (optional)

A space seperated list of hosts to purge. Example:

```text
nathanv.me blog.nathanv.me
```yml
hosts: nathanv.me blog.nathanv.me
```

### `prefixes` (optional)

A space seperated list of prefixes to purge. Example:

```text
nathanv.me/assets/ blog.nathanv.me/assets
```yml
prefixes: nathanv.me/assets/ blog.nathanv.me/assets
```

## Outputs
Expand All @@ -68,53 +70,62 @@ None
```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 }}
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 }}
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
cf_zone: ${{ secrets.CLOUDFLARE_ZONE }}
cf_auth: ${{ secrets.CLOUDFLARE_AUTH_KEY }}
files: |
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)
in your Cloudflare account.
1. First, go to the [API tokens page](https://dash.cloudflare.com/profile/api-tokens)
in your Cloudflare account.

![](images/api-tokens.jpg)

2. Click "Create Token", and fill out the form. Make sure to give the permission of
zone cache purge.
2. Click "Create Token", and fill out the form. Make sure to give the permission of
zone cache purge.

![](images/token-creation.jpg)

3. Click "Continue to summary", then "Confirm".
3. Click "Continue to summary", then "Confirm".

4. Copy the value of the token.

4. Copy the value of the token.
![](images/copy-token.jpg)

5. To find the zone ID for your site, go to your dashboard for the site, and look on the
right-hand panel.
5. To find the zone ID for your site, go to your dashboard for the site, and look on the
right-hand panel.

![](images/zone-id.jpg)

Follow GitHub's [documentation](https://help.github.com/en/articles/virtual-environments-for-github-actions#creating-and-using-secrets-encrypted-variables)
Expand Down
70 changes: 43 additions & 27 deletions action.yml
Original file line number Diff line number Diff line change
@@ -1,30 +1,46 @@
name: 'Cloudflare Cache Purge Action'
description: 'A GitHub Action to purge Cloudflare''s entire cache of your site'
author: 'Nathan Vaughn'
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
cf_zone:
description: "Cloudflare Zone"
required: false
cf_auth:
description: "Cloudflare Authentication Key"
required: false
urls:
description: "URLs to purge"
required: false
files:
description: "Files to purge"
required: false
tags:
description: "Cache-Tags to purge"
required: false
hosts:
description: "Hosts to purge"
required: false
prefixes:
description: "Prefixes to purge"
required: false
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 }}'
using: "docker"
image: "Dockerfile"
args:
- "--cf-zone"
- "${{ inputs.cf_zone }}"
- "--cf-auth"
- "${{ inputs.cf_auth }}"
- "--urls"
- "${{ inputs.urls }}"
- "--files"
- "${{ inputs.files }}"
- "--tags"
- "${{ inputs.tags }}"
- "--hosts"
- "${{ inputs.hosts }}"
- "--prefixes"
- "${{ inputs.prefixes }}"
branding:
icon: 'cloud'
color: 'orange'
icon: "cloud"
color: "orange"
102 changes: 80 additions & 22 deletions main.py
Original file line number Diff line number Diff line change
@@ -1,12 +1,44 @@
import argparse
import json
import pprint
import os
import sys
from typing import List
from urllib import request


def main():
def split_and_flatten_list(items: List[str]) -> List[str]:
"""
Take a list of strings and return a flat list of strings of the previous
elements split by spaces.
"""
# in case input is None, return an empty list
if items is None:
return []

new_list = []

# split each element by spaces
for item in items:
new_list.extend(item.split())

# filter out empty strings
return [i for i in new_list if i != ""]


def print_blue(text: str) -> None:
"""
Take text input and print each line in blue.
"""
# GitHub actions seems to reset the color after each line,
# hence we have to do this.
for line in text.split("\n"):
print(f"\033[0;34m{line}\033[0m")


def main() -> None:
if not os.getenv("NATHANVAUGHN_TESTING"):
print(f"::debug::{' '.join(sys.argv)}")

# parse the arguments
parser = argparse.ArgumentParser()

Expand All @@ -16,11 +48,26 @@ def main():

# caching objects
parser.add_argument("--urls", nargs="*", type=str)

parser.add_argument("--files", 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(sys.argv[1].split())
args = parser.parse_args()

# this is weird because while each argument accepts a list, GitHub Actions
# provides a single value for each argument. So, need to split each element
# by spaces to make it compatible with both with weird argument stuff
# and normal CLI usage.

# backwards compatibility
args.urls = split_and_flatten_list(args.urls)

args.files = split_and_flatten_list(args.files)
args.tags = split_and_flatten_list(args.tags)
args.hosts = split_and_flatten_list(args.hosts)
args.prefixes = split_and_flatten_list(args.prefixes)

# if no argument given, pull from environment
if not args.cf_zone:
Expand All @@ -37,55 +84,66 @@ def main():
parser.error("Cloudflare Auth required")

# prepare the request data
req_data = {}

data = {}
if args.files:
req_data["files"] = args.files

# backwards compatibility
if args.urls:
data["files"] = args.urls
req_data["files"] = req_data.get("files", []) + args.urls

if args.tags:
data["tags"] = args.tags
req_data["tags"] = args.tags

if args.hosts:
data["hosts"] = args.hosts
req_data["hosts"] = args.hosts

if args.prefixes:
data["prefixes"] = args.prefixes
req_data["prefixes"] = args.prefixes

if not args.urls and not args.tags and not args.hosts and not args.prefixes:
data["purge_everything"] = True
# if nothing was explicitly set, purge everything
if not req_data:
req_data["purge_everything"] = True

# create the request
url = f"https://api.cloudflare.com/client/v4/zones/{args.cf_zone}/purge_cache"
encoded_data = json.dumps(data).encode("utf-8")
headers = {
"Authorization": f"Bearer {args.cf_auth}",
"Content-Type": "application/json",
"Content-Length": len(encoded_data),
"User-Agent": "github.com/nathanvaughn/actions-cloudflare-purge",
}

# when testing, print the url, data, headers, and exit.
if os.getenv("NATHANVAUGHN_TESTING"):
# when testing, don't actually make a request
print(url)
print(json.dumps(headers))
print(json.dumps(data))
print(json.dumps(req_data))
sys.exit()

# send it
print(f"Making POST request to {url} with {data}")

req = request.Request(url, data=encoded_data, headers=headers)
else:
print("Request:")
print_blue(url)
print("Headers:")
print_blue(json.dumps(headers, indent=4))
print("Payload:")
print_blue(json.dumps(req_data, indent=4))

req = request.Request(
url, data=json.dumps(req_data).encode("utf-8"), headers=headers
)
resp = request.urlopen(req)

# process response
resp_data = json.loads(resp.read())

print("=========")
print("Response:")
print(pprint.pprint(resp_data))
print_blue(json.dumps(resp_data, indent=4))

if resp_data["success"] != True:
raise Exception("Success NOT True")
print("::error::Success NOT True")
sys.exit(1)


if __name__ == "__main__":
main()
main()
Loading

0 comments on commit 2ce9f53

Please sign in to comment.