"""
This is a collection of useful utility functions when working with docker on different environments.
In order to use these functions, install fabric on your local machine with::
    pip install fabric
Please note: Fabric is a remote code execution tool, NOT a remote configuration tool. While you can copy files
from here to there, it is not a good replacement for salt or ansible in this regard.
There is a function called `production` where you need to fill in the details about your production machine(s).
You can then run::
    fab production status
to get the status of your stack
To list all available commands, run::
    fab -l
"""


import time
from fabric.api import *  # noqa
from fabric.api import cd
from fabric.api import env
from fabric.api import lcd
from fabric.colors import blue
from fabric.operations import local as lrun, run, put


def local():
    """
    Work on the local environment
    """
    env.compose_file = "docker-compose.yml"
    env.project_dir = "."
    env.run = lrun
    env.cd = lcd


def production():
    """
    Work on the production environment
    """
    env.hosts = [
        "159.203.191.135"
    ]  # list the ip addresses or domain names of your production boxes here
    env.port = 56565  # ssh port
    env.user = "root"  # remote user, see `env.run` if you don't log in as root

    env.compose_file = "docker-compose.prod.yml"
    env.project_dir = "/code/djangopackages"  # this is the project dir where your code lives on this machine

    # if you don't use key authentication, add your password here
    # env.password = "foobar"
    # if your machine has no bash installed, fall back to sh
    # env.shell = "/bin/sh -c"

    env.run = run  # if you don't log in as root, replace with 'env.run = sudo'
    env.cd = cd


def copy_secrets():
    """
    Copies secrets from local to remote.
    :return:
    """
    secrets = [
        ".env-production",
    ]

    for secret in secrets:
        remote_path = "/".join([env.project_dir, secret])
        print(
            blue(
                "Copying {secret} to {remote_path} on {host}".format(
                    secret=secret, remote_path=remote_path, host=env.host
                )
            )
        )
        put(secret, remote_path)


def rollback(commit="HEAD~1"):
    """
    Rollback to a previous commit and build the stack
    :param commit: Commit you want to roll back to. Default is the previous commit
    """
    with env.cd(env.project_dir):
        env.run(f"git checkout {commit}")

    deploy()


def backup():
    with env.cd(env.project_dir):
        # Manage Backups
        docker_compose("run django-a python manage.py clearsessions")
        docker_compose("run postgres backup")
        env.run("gzip /data/djangopackages/backups/*.sql")


def deploy():
    """
    Pulls the latest changes from main, rebuilt and restarts the stack
    """

    # lrun("git push origin main")
    # copy_secrets()
    with env.cd(env.project_dir):
        # Manage Backups
        # docker_compose("run django-a python manage.py clearsessions")

        # docker_compose("run postgres backup")
        # env.run("gzip /data/djangopackages/backups/*.sql")

        # Pull the latest code
        env.run("git pull origin main")

        # turn maintenance mode on
        # maintenance_mode_on("django-a")

        # Build our primary Docker image
        build_and_restart("django-a")
        time.sleep(10)

        # just to make sure they are on
        # docker_compose("start postgres")
        docker_compose("start redis")
        time.sleep(10)

        # Build our secondary Docker image
        build_and_restart("django-b")

        # collectstatic
        collectstatic("django-a")

        # turn maintenance mode off
        # maintenance_mode_off("django-a")


def build_and_restart(service):
    docker_compose(f"build {service}")
    docker_compose(f"create {service}")
    docker_compose(f"stop {service}")
    docker_compose(f"start {service}")


def collectstatic(service):
    docker_compose(f"exec {service} python manage.py collectstatic --no-input -v 1")


def maintenance_mode_on(service):
    docker_compose(f"exec {service} python manage.py maintenance_mode on")


def maintenance_mode_off(service):
    docker_compose(f"exec {service} python manage.py maintenance_mode off")


def docker_compose(command):
    """
    Run a docker-compose command
    :param command: Command you want to run
    """
    with env.cd(env.project_dir):
        return env.run(f"docker-compose -f {env.compose_file} {command}")

fabfile.py
"""
This is a collection of useful utility functions when working with docker on different environments.
In order to use these functions, install fabric on your local machine with::
    pip install fabric
Please note: Fabric is a remote code execution tool, NOT a remote configuration tool. While you can copy files
from here to there, it is not a good replacement for salt or ansible in this regard.
There is a function called `production` where you need to fill in the details about your production machine(s).
You can then run::
    fab production status
to get the status of your stack
To list all available commands, run::
    fab -l
"""


import time
from fabric.api import *  # noqa
from fabric.api import cd
from fabric.api import env
from fabric.api import lcd
from fabric.colors import blue
from fabric.operations import local as lrun, run, put


def local():
    """
    Work on the local environment
    """
    env.compose_file = "docker-compose.yml"
    env.project_dir = "."
    env.run = lrun
    env.cd = lcd


def production():
    """
    Work on the production environment
    """
    env.hosts = [
        "159.203.191.135"
    ]  # list the ip addresses or domain names of your production boxes here
    env.port = 56565  # ssh port
    env.user = "root"  # remote user, see `env.run` if you don't log in as root

    env.compose_file = "docker-compose.prod.yml"
    env.project_dir = "/code/djangopackages"  # this is the project dir where your code lives on this machine

    # if you don't use key authentication, add your password here
    # env.password = "foobar"
    # if your machine has no bash installed, fall back to sh
    # env.shell = "/bin/sh -c"

    env.run = run  # if you don't log in as root, replace with 'env.run = sudo'
    env.cd = cd


def copy_secrets():
    """
    Copies secrets from local to remote.
    :return:
    """
    secrets = [
        ".env-production",
    ]

    for secret in secrets:
        remote_path = "/".join([env.project_dir, secret])
        print(
            blue(
                "Copying {secret} to {remote_path} on {host}".format(
                    secret=secret, remote_path=remote_path, host=env.host
                )
            )
        )
        put(secret, remote_path)


def rollback(commit="HEAD~1"):
    """
    Rollback to a previous commit and build the stack
    :param commit: Commit you want to roll back to. Default is the previous commit
    """
    with env.cd(env.project_dir):
        env.run(f"git checkout {commit}")

    deploy()


def backup():
    with env.cd(env.project_dir):
        # Manage Backups
        docker_compose("run django-a python manage.py clearsessions")
        docker_compose("run postgres backup")
        env.run("gzip /data/djangopackages/backups/*.sql")


def deploy():
    """
    Pulls the latest changes from main, rebuilt and restarts the stack
    """

    # lrun("git push origin main")
    # copy_secrets()
    with env.cd(env.project_dir):
        # Manage Backups
        # docker_compose("run django-a python manage.py clearsessions")

        # docker_compose("run postgres backup")
        # env.run("gzip /data/djangopackages/backups/*.sql")

        # Pull the latest code
        env.run("git pull origin main")

        # turn maintenance mode on
        # maintenance_mode_on("django-a")

        # Build our primary Docker image
        build_and_restart("django-a")
        time.sleep(10)

        # just to make sure they are on
        # docker_compose("start postgres")
        docker_compose("start redis")
        time.sleep(10)

        # Build our secondary Docker image
        build_and_restart("django-b")

        # collectstatic
        collectstatic("django-a")

        # turn maintenance mode off
        # maintenance_mode_off("django-a")


def build_and_restart(service):
    docker_compose(f"build {service}")
    docker_compose(f"create {service}")
    docker_compose(f"stop {service}")
    docker_compose(f"start {service}")


def collectstatic(service):
    docker_compose(f"exec {service} python manage.py collectstatic --no-input -v 1")


def maintenance_mode_on(service):
    docker_compose(f"exec {service} python manage.py maintenance_mode on")


def maintenance_mode_off(service):
    docker_compose(f"exec {service} python manage.py maintenance_mode off")


def purge_cache(service):
    docker_compose(f"exec {service} cli4 --delete purge_everything=true /zones/:djangopackages.org/purge_cache")


def docker_compose(command):
    """
    Run a docker-compose command
    :param command: Command you want to run
    """
    with env.cd(env.project_dir):
        return env.run(f"docker-compose -f {env.compose_file} {command}")

fabfile.py
diff --git a/.env.local.example b/.env.local.example
index 351e5fa8c..4f67eb100 100644
--- a/.env.local.example
+++ b/.env.local.example
@@ -1,4 +1,5 @@
 ADMIN_URL_BASE=admin/
+CLOUDFLARE_API_TOKEN=
 DATABASE_URL=postgres://djangopackages:djangopackages@postgres:5432/djangopackages
 DEBUG=
 DJANGO_DEBUG=
diff --git a/fabfile.py b/fabfile.py
index ac8191845..1097dc82e 100644
--- a/fabfile.py
+++ b/fabfile.py
@@ -152,6 +152,10 @@ def maintenance_mode_off(service):
     docker_compose(f"exec {service} python manage.py maintenance_mode off")
 
 
+def purge_cache(service):
+    docker_compose(f"exec {service} cli4 --delete purge_everything=true /zones/:djangopackages.org/purge_cache")
+
+
 def docker_compose(command):
     """
     Run a docker-compose command
diff --git a/requirements.in b/requirements.in
index 3b70140c5..e57c6d602 100644
--- a/requirements.in
+++ b/requirements.in
@@ -1,4 +1,5 @@
 bs4
+cloudflare
 Django<4.2
 # see requirements-to-watch.txt
 django-anymail
diff --git a/requirements.txt b/requirements.txt
index 6068f759d..806553624 100644
--- a/requirements.txt
+++ b/requirements.txt
@@ -19,7 +19,9 @@ async-timeout==4.0.2 \
 attrs==22.1.0 \
     --hash=sha256:29adc2665447e5191d0e7c568fde78b21f9672d344281d0c6e1ab085429b22b6 \
     --hash=sha256:86efa402f67bf2df34f51a335487cf46b1ec130d02b8d39fd248abfd30da551c
-    # via pytest
+    # via
+    #   jsonlines
+    #   pytest
 backports-zoneinfo==0.2.1 \
     --hash=sha256:17746bd546106fa389c51dbea67c8b7c8f0d14b5526a579ca6ccf5ed72c526cf \
     --hash=sha256:1b13e654a55cd45672cb54ed12148cd33628f672548f373963b0bff67b217328 \
@@ -41,7 +43,9 @@ backports-zoneinfo==0.2.1 \
 beautifulsoup4==4.11.1 \
     --hash=sha256:58d5c3d29f5a36ffeb94f02f0d786cd53014cf9b3b3951d42e0080d8a9498d30 \
     --hash=sha256:ad9aa55b65ef2808eb405f46cf74df7fcb7044d5cbc26487f96eb2ef2e436693
-    # via bs4
+    # via
+    #   bs4
+    #   cloudflare
 billiard==3.6.4.0 \
     --hash=sha256:299de5a8da28a783d51b197d496bef4f1595dd023a93a4f59dde1886ae905547 \
     --hash=sha256:87103ea78fa6ab4d5c751c4909bcff74617d985de7fa8b672cf8618afd5a875b
@@ -158,6 +162,9 @@ click-repl==0.2.0 \
     --hash=sha256:94b3fbbc9406a236f176e0506524b2937e4b23b6f4c0c0b2a0a83f8a64e9194b \
     --hash=sha256:cd12f68d745bf6151210790540b4cb064c7b13e571bc64b6957d98d120dacfd8
     # via celery
+cloudflare==2.10.2 \
+    --hash=sha256:9786549f732373eefadb13515a27f5f4cc636a758089562330065db43a3d9b7d
+    # via -r ./requirements.in
 colorama==0.4.5 \
     --hash=sha256:854bf444933e37f5824ae7bfc1e98d5bce2ebe4160d46b5edf346a89358e99da \
     --hash=sha256:e6c6b4334fc50988a639d9b98aa429a0b57da6e17b9a44f0451f930b6967b7a4
@@ -385,6 +392,10 @@ jinja2==3.1.2 \
     --hash=sha256:31351a702a408a9e7595a8fc6150fc3f43bb6bf7e319770cbc0db9df9437e852 \
     --hash=sha256:6088930bfe239f0e6710546ab9c19c9ef35e29792895fed6e6e31a023a182a61
     # via -r ./requirements.in
+jsonlines==3.1.0 \
+    --hash=sha256:2579cb488d96f815b0eb81629e3e6b0332da0962a18fa3532958f7ba14a5c37f \
+    --hash=sha256:632f5e38f93dfcb1ac8c4e09780b92af3a55f38f26e7c47ae85109d420b6ad39
+    # via cloudflare
 kombu==5.2.4 \
     --hash=sha256:37cee3ee725f94ea8bb173eaab7c1760203ea53bbebae226328600f9d2799610 \
     --hash=sha256:8b213b24293d3417bcf0d2f5537b7f756079e3ea232a8386dcc89a59fd2361a4
@@ -717,7 +728,9 @@ pyyaml==6.0 \
     --hash=sha256:dbad0e9d368bb989f4515da330b88a057617d16b6a8245084f1b05400f24609f \
     --hash=sha256:e61ceaab6f49fb8bdfaa0f92c4b57bcfbea54c09277b1b4f7ac376bfb7a7c174 \
     --hash=sha256:f84fbc98b019fef2ee9a1cb3ce93e3187a6df0b2538a651bfb890254ba9f90b5
-    # via vcrpy
+    # via
+    #   cloudflare
+    #   vcrpy
 redis==4.3.4 \
     --hash=sha256:a52d5694c9eb4292770084fa8c863f79367ca19884b329ab574d5cb2036b3e54 \
     --hash=sha256:ddf27071df4adf3821c4f2ca59d67525c3a82e5f268bed97b813cb4fabf87880
@@ -727,6 +740,7 @@ requests==2.28.1 \
     --hash=sha256:8fefa2a1a1365bf5520aac41836fbee479da67864514bdb821f31ce07ce65349
     # via
     #   -r ./requirements.in
+    #   cloudflare
     #   django-anymail
     #   emojificate
     #   github3-py

Invalidate Cloudflare cache when a static file changes
**Describe the bug**

On the #719 the [grid css](https://github.com/djangopackages/djangopackages/blob/930c7565ea216e42380694d32f1311b6d64e78b9/static/css/grid.css) was improved, but in production the old version is still served. 

I'm not sure if something is wrong with [`collectstatic`](https://github.com/djangopackages/djangopackages/blob/930c7565ea216e42380694d32f1311b6d64e78b9/compose/django/start.sh#L3), with some Caddy HTTP headers or a configuration on Cloudflare

**To Reproduce**
Steps to reproduce the behavior:
1. Open the https://djangopackages.org/static/css/grid.css and check if it contains the [new css ](https://github.com/djangopackages/djangopackages/blob/930c7565ea216e42380694d32f1311b6d64e78b9/static/css/grid.css)

**Expected behavior**

Whenever a new deployment occurs, if a static file has changed, Cloudflare must invalidate the previous version and serve the new version.

**Desktop (please complete the following information):**
 - OS: Ubuntu 20.04
 - Browser: Google Chrome v95.0.4638.54



