Skip to content

Commit

Permalink
Move assets from wheel to S3
Browse files Browse the repository at this point in the history
This moves static assets from inside the Python wheel/source
distribution to S3, proxied by Cloudflare as a CDN.
  • Loading branch information
jackrosenthal committed Feb 19, 2024
1 parent cf0c57e commit 98ec2fe
Show file tree
Hide file tree
Showing 8 changed files with 103 additions and 22 deletions.
5 changes: 4 additions & 1 deletion .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -79,13 +79,16 @@ jobs:
build-dist:
name: "Build Wheel"
runs-on: ubuntu-22.04
if: github.event_name == 'push'
steps:
- uses: actions/checkout@v4
- uses: actions/setup-python@v4
with:
python-version: 3.11
- name: "Install build dependencies"
run: pip install --upgrade build wheel setuptools
run: pip install --upgrade build wheel setuptools s3fs
- name: "Upload assets to CDN"
run: ./cdnify.py
- name: "Build"
run: python -m build
- name: "Upload Artifacts"
Expand Down
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -21,3 +21,4 @@ development.ini
production.ini
gearbox.pid
client_secrets.json
/algobowl/lib/algocdn.py
2 changes: 0 additions & 2 deletions MANIFEST.in
Original file line number Diff line number Diff line change
@@ -1,7 +1,5 @@
include README.md
include VERSION
recursive-include algobowl/public *
include algobowl/public/favicon.ico
recursive-include algobowl/i18n *
recursive-include algobowl/templates *
global-exclude *.pyc
Expand Down
13 changes: 13 additions & 0 deletions algobowl/lib/helpers.py
Original file line number Diff line number Diff line change
Expand Up @@ -39,3 +39,16 @@ def ftime(dt, duration=None):
raise ValueError("Duration cannot be before the specified time")
result += f" to {ftime(duration)}"
return result


def url(path: str) -> str:
try:
from algobowl.lib import algocdn
except ImportError:
pass
else:
try:
return algocdn.url_map[path]
except KeyError:
pass
return tg.url(path)
2 changes: 1 addition & 1 deletion algobowl/templates/group/evaluation.xhtml
Original file line number Diff line number Diff line change
Expand Up @@ -47,5 +47,5 @@ pb_color = cycle(
Submit Evaluations
</button>
</form>
<script type="text/javascript" src="${tg.url('/assets/js/evaluation.js')}"></script>
<script type="text/javascript" src="${h.url('/assets/js/evaluation.js')}"></script>
</div>
2 changes: 1 addition & 1 deletion algobowl/templates/group/resolution.xhtml
Original file line number Diff line number Diff line change
Expand Up @@ -156,5 +156,5 @@
</div>
</div>
</div>
<script type="text/javascript" src="${tg.url('/assets/js/resolution.js')}"></script>
<script type="text/javascript" src="${h.url('/assets/js/resolution.js')}"></script>
</div>
34 changes: 17 additions & 17 deletions algobowl/templates/master.xhtml
Original file line number Diff line number Diff line change
Expand Up @@ -8,29 +8,29 @@
${tg.config['site.branding.name']}: <py:block name="title">Competition</py:block>
</title>

<link rel="stylesheet" type="text/css" media="screen" href="${tg.url('/assets/css/bootstrap.min.css')}" />
<link rel="stylesheet" type="text/css" media="screen" href="${tg.url('/assets/fontawesome/css/all.min.css')}" />
<link rel="stylesheet" type="text/css" media="screen" href="${tg.url('/assets/css/style.css')}" />
<link rel="stylesheet" type="text/css" media="screen" href="${h.url('/assets/css/bootstrap.min.css')}" />
<link rel="stylesheet" type="text/css" media="screen" href="${h.url('/assets/fontawesome/css/all.min.css')}" />
<link rel="stylesheet" type="text/css" media="screen" href="${h.url('/assets/css/style.css')}" />

<!-- Favicons -->
<link rel="apple-touch-icon-precomposed" sizes="57x57" href="${tg.url('/assets/img/apple-touch-icon-57x57.png')}" />
<link rel="apple-touch-icon-precomposed" sizes="114x114" href="${tg.url('/assets/img/apple-touch-icon-114x114.png')}" />
<link rel="apple-touch-icon-precomposed" sizes="72x72" href="${tg.url('/assets/img/apple-touch-icon-72x72.png')}" />
<link rel="apple-touch-icon-precomposed" sizes="144x144" href="${tg.url('/assets/img/apple-touch-icon-144x144.png')}" />
<link rel="apple-touch-icon-precomposed" sizes="120x120" href="${tg.url('/assets/img/apple-touch-icon-120x120.png')}" />
<link rel="apple-touch-icon-precomposed" sizes="152x152" href="${tg.url('/assets/img/apple-touch-icon-152x152.png')}" />
<link rel="icon" type="image/png" href="${tg.url('/assets/img/favicon-32x32.png')}" sizes="32x32" />
<link rel="icon" type="image/png" href="${tg.url('/assets/img/favicon-16x16.png')}" sizes="16x16" />
<link rel="apple-touch-icon-precomposed" sizes="57x57" href="${h.url('/assets/img/apple-touch-icon-57x57.png')}" />
<link rel="apple-touch-icon-precomposed" sizes="114x114" href="${h.url('/assets/img/apple-touch-icon-114x114.png')}" />
<link rel="apple-touch-icon-precomposed" sizes="72x72" href="${h.url('/assets/img/apple-touch-icon-72x72.png')}" />
<link rel="apple-touch-icon-precomposed" sizes="144x144" href="${h.url('/assets/img/apple-touch-icon-144x144.png')}" />
<link rel="apple-touch-icon-precomposed" sizes="120x120" href="${h.url('/assets/img/apple-touch-icon-120x120.png')}" />
<link rel="apple-touch-icon-precomposed" sizes="152x152" href="${h.url('/assets/img/apple-touch-icon-152x152.png')}" />
<link rel="icon" type="image/png" href="${h.url('/assets/img/favicon-32x32.png')}" sizes="32x32" />
<link rel="icon" type="image/png" href="${h.url('/assets/img/favicon-16x16.png')}" sizes="16x16" />
<meta name="application-name" content="${tg.config['site.branding.name']}" />
<meta name="msapplication-TileColor" content="#FFFFFF" />
<meta name="msapplication-TileImage" content="${tg.url('/assets/img/mstile-144x144.png')}" />
<link rel="shortcut icon" href="${tg.url('/assets/img/favicon.ico')}" />
<meta name="msapplication-TileImage" content="${h.url('/assets/img/mstile-144x144.png')}" />
<link rel="shortcut icon" href="${h.url('/assets/img/favicon.ico')}" />
<!-- End Favicons -->

<script src="${tg.url('/assets/js/jquery-3.3.1.min.js')}"></script>
<script src="${tg.url('/assets/js/popper.min.js')}"></script>
<script src="${tg.url('/assets/js/bootstrap.min.js')}"></script>
<script src="${tg.url('/assets/js/algobowl.js')}"></script>
<script src="${h.url('/assets/js/jquery-3.3.1.min.js')}"></script>
<script src="${h.url('/assets/js/popper.min.js')}"></script>
<script src="${h.url('/assets/js/bootstrap.min.js')}"></script>
<script src="${h.url('/assets/js/algobowl.js')}"></script>

<py:block name="head"></py:block>
</head>
Expand Down
66 changes: 66 additions & 0 deletions cdnify.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
#!/usr/bin/env python3

"""Upload assets to S3 and produce algobowl/lib/algocdn.py.
This gets called prior to building a wheel, that way we can remove assets from
the wheel and source distributions on PyPI.
"""

from pathlib import Path
import subprocess

import s3fs
import click

HERE = Path(__file__).resolve().parent


def write_py_out(f, url_map):
print("# Auto-generated by cdnify.py", file=f)
print(f"url_map = {url_map!r}", file=f)


@click.command()
@click.option("--access-key", envvar="S3_ACCESS_KEY", required=True)
@click.option("--secret-key", envvar="S3_SECRET_KEY", required=True)
@click.option("--bucket", envvar="S3_BUCKET", default="algocdn")
@click.option(
"--endpoint-url",
envvar="S3_ENDPOINT_URL",
default="https://s3.us-west-004.backblazeb2.com",
)
@click.option(
"--public-url-prefix",
default="https://assets.algobowl.org/file/algocdn",
)
@click.option("--public-dir", type=Path, default=HERE / "algobowl" / "public")
@click.option("--py-out", type=Path, default=HERE / "algobowl" / "lib" / "algocdn.py")
def main(
access_key, secret_key, bucket, endpoint_url, public_dir, public_url_prefix, py_out
):
s3 = s3fs.S3FileSystem(endpoint_url=endpoint_url, key=access_key, secret=secret_key)
url_map = {}
assets_hash = subprocess.run(
["git", "log", "-n1", "--format=%H", "--", public_dir],
stdout=subprocess.PIPE,
encoding="utf-8",
check=True,
).stdout.strip()

for path in public_dir.glob("**/*"):
if not path.is_file():
continue
relative_url = f"/{path.relative_to(public_dir)}"
remote_path = f"{assets_hash}{relative_url}"
s3_uri = f"s3://{bucket}/{remote_path}"
if not s3.exists(s3_uri):
click.echo(f"Upload {path} to {s3_uri}", err=True)
s3.put_file(path, s3_uri)
url_map[relative_url] = f"{public_url_prefix}/{remote_path}"

with open(py_out, "w", encoding="utf-8") as f:
write_py_out(f, url_map)


if __name__ == "__main__":
main()

0 comments on commit 98ec2fe

Please sign in to comment.