Skip to content

Commit

Permalink
feat(docker-nginx): Replace Environment Variables within index.html (
Browse files Browse the repository at this point in the history
  • Loading branch information
shrink authored Jul 4, 2022
1 parent 2bdd891 commit 995e32e
Show file tree
Hide file tree
Showing 13 changed files with 362 additions and 0 deletions.
7 changes: 7 additions & 0 deletions .editorconfig
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
root = true

[*]
charset = utf-8
end_of_line = lf
insert_final_newline = true
trim_trailing_whitespace = true
92 changes: 92 additions & 0 deletions .github/workflows/docker-nginx.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
name: "docker-nginx runtime"

on:
workflow_dispatch:
push:
paths:
- "docker-nginx/**"
- ".github/**"

jobs:
test-docker-nginx:
uses: ./.github/workflows/test_with_docker_compose.yaml
needs:
- publish-container-image
with:
image: "docker-nginx"
publish-container-image:
runs-on: ubuntu-latest
needs:
- meta
steps:
- name: Log in to GitHub Container Registry as actor
uses: docker/login-action@v1
with:
registry: "${{ needs.meta.outputs.registry }}"
username: "${{ needs.meta.outputs.username }}"
password: "${{ github.token }}"
- uses: docker/setup-buildx-action@v1
- uses: docker/setup-qemu-action@v1
- uses: actions/checkout@v2
- name: Build Docker Image
uses: docker/build-push-action@v2
with:
outputs: "type=oci,dest=${{ needs.meta.outputs.archive }}"
platforms: "linux/amd64"
labels: "${{ needs.meta.outputs.labels }}"
context: "docker-nginx"
- name: Upload application's Docker Image as pipeline artifact
uses: actions/upload-artifact@v2
with:
path: "${{ needs.meta.outputs.archive }}"
name: "${{ needs.meta.outputs.archive }}"
- name: Push OCI archive (image) to registry for commit
uses: pr-mpt/actions-push-oci-archive-to-registry@v1
with:
archive: "${{ needs.meta.outputs.archive }}"
image: "${{ needs.meta.outputs.image_name }}"
tag: "${{ needs.meta.outputs.tags-commit }}"
- name: Tag image in registry with branch name
uses: shrink/actions-docker-registry-tag@v2
with:
registry: "${{ needs.meta.outputs.registry }}"
repository: "${{ needs.meta.outputs.image_repository }}"
target: "${{ needs.meta.outputs.tags-commit }}"
tags: "${{ needs.meta.outputs.tags-branch }}"
- name: Tag image as latest if build is on main branch
if: "${{ needs.meta.outputs.branch == 'main' }}"
uses: shrink/actions-docker-registry-tag@v2
with:
registry: "${{ needs.meta.outputs.registry }}"
repository: "${{ needs.meta.outputs.image_repository }}"
target: "${{ needs.meta.outputs.tags-commit }}"
tags: "latest"
meta:
runs-on: ubuntu-latest
outputs:
registry: "ghcr.io"
username: "${{ github.actor }}"
image_repository: "${{ github.repository }}-docker-nginx"
image_name: "ghcr.io/${{ github.repository }}-docker-nginx"
archive: "docker-nginx-${{ steps.commit.outputs.short }}.tar"
labels: "${{ steps.image.outputs.labels }}"
branch: "${{ steps.branch.outputs.sanitized-branch-name }}"
tags-commit: "${{ steps.commit.outputs.short }}"
tags-branch: "branches-${{ steps.branch.outputs.sanitized-branch-name }}"
steps:
- name: Get commit hash
id: commit
uses: pr-mpt/actions-commit-hash@v1
with:
prefix: "sha-"
- name: Generate branch Docker Image attributes
id: image
uses: docker/metadata-action@v3
with:
images: "ghcr.io/${{ github.repository }}-docker-nginx"
- id: branches
uses: tj-actions/branch-names@v5
- id: branch
uses: yeouchien/sanitize-branch-name-action@v1
with:
branch-name: "${{ steps.branches.outputs.current_branch }}"
12 changes: 12 additions & 0 deletions .github/workflows/fixtures/greeting/expected.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
<!DOCTYPE html>
<html lang="en">

<body>
<script type="text/javascript">
window.config = {
greeting: "Welcome" || "Hello",
};
</script>
</body>

</html>
12 changes: 12 additions & 0 deletions .github/workflows/fixtures/greeting/index.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
<!DOCTYPE html>
<html lang="en">

<body>
<script type="text/javascript">
window.config = {
greeting: "$GREETING" || "Hello",
};
</script>
</body>

</html>
52 changes: 52 additions & 0 deletions .github/workflows/test_with_docker_compose.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
name: "Test HTTP Service Satisfies Specification"

on:
workflow_call:
inputs:
image:
description: "Name (which is also the path) of the Image to test"
required: true
type: string

jobs:
http-fixtures:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- name: Log in to GitHub Container Registry as actor
uses: docker/login-action@v1
with:
registry: "ghcr.io"
username: "${{ github.actor }}"
password: "${{ github.token }}"
- name: Get commit hash
id: commit
uses: pr-mpt/actions-commit-hash@v1
with:
prefix: "sha-"
- name: Launch test service(s)
env:
COMPOSE_FILE: "./${{ inputs.image }}/docker-compose.tests.yaml"
SPARSE_IMAGE: "ghcr.io/${{ github.repository }}-${{ inputs.image }}:${{ steps.commit.outputs.short }}"
run: |
docker compose pull --ignore-pull-failures
docker compose up -d --wait
##################################################
# The greeting fixture is available on port 8080 #
##################################################
- name: Perform index page request to greeting
id: greeting-index-response
uses: CamiloGarciaLaRotta/watermelon-http-client@v1
with:
url: "http://localhost:8080"
- name: Load greeting fixture
id: greeting-index-expected
uses: juliangruber/read-file-action@v1
with:
path: ./.github/workflows/fixtures/greeting/expected.html
- name: Test greeting index page matches fixture
uses: pr-mpt/actions-assert@v3
with:
assertion: npm://@assertions/equivalent-html:v1
actual: "${{ fromJSON(steps.greeting-index-response.outputs.response) }}"
expected: "${{ steps.greeting-index-expected.outputs.content }}"
21 changes: 21 additions & 0 deletions LICENSE
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
The MIT License (MIT)

Copyright (c) 2022 > LIMITED and contributors

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
68 changes: 68 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
# Sparse

SPA (Single-page application) runtimes for portable builds.

## How It Works

A Sparse runtime replaces Environment Variable references in the SPA entrypoint
HTML with their values from the Environment.

### Example

1. Clone this repository OR create an `index.html` entrypoint

```html
<!DOCTYPE html>
<html lang="en">
<body>
<script type="text/javascript">
window.config = {
API: "$API_URL",
};
</script>
</body>
</html>
```

2. Launch the sparse runtime, e.g: [docker-nginx](/docker-nginx)

```console
dev:~$ docker pull ghcr.io/pr-mpt/sparse-docker-nginx
dev:~$ docker run -v $(PWD):/srv -p 8080:8080 -e API_URL="https://api.example.com" ghcr.io/pr-mpt/sparse-docker-nginx
```

3. Observe Environment Variable replacement in page source

```console
dev:~$ open http://localhost:8080
```

```html
<!DOCTYPE html>
<html lang="en">
<body>
<script type="text/javascript">
window.config = {
API: "https://api.example.com",
};
</script>
</body>
</html>
```

## Specification

- Environment Variables are strings
- Default to empty string value
- Replacement must be performed at startup, may be performed on each request
- `index.html` is the application Entrypoint where variables are replaced
- Passthrough all other paths to the filesystem
- Allow port specification, default to 8080

## Runtimes

- [Docker](/docker-nginx)
- Netlify
- Fly
- Cloudflare Functions
- Binary
11 changes: 11 additions & 0 deletions docker-nginx/Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
ARG NGINX=1.22

FROM nginx:$NGINX-alpine

WORKDIR /srv

COPY insert-environment-variables.sh /docker-entrypoint.d/99-insert-environment-variables.sh

COPY default.conf /etc/nginx/conf.d/default.conf

RUN chmod +x /docker-entrypoint.d/99-insert-environment-variables.sh
31 changes: 31 additions & 0 deletions docker-nginx/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
# Docker: alpine + nginx

A simple alpine linux + nginx container using `envsubst` to replace Environment
Variables on start.

:safety_pin: Please consider [digest pinning][docker/digest-pinning] when
utilising a third-party Docker image, the Container Registry page for
[`sparse-docker-nginx`][packages/docker-nginx] provides a full list of tags and
their respective digests.

```Dockerfile
FROM ghcr.io/pr-mpt/sparse-docker-nginx

COPY index.html ./
```

## Configure nginx

You may provide a custom nginx configuration by writing your configuration to
`default.conf`, e.g:

```Dockerfile
FROM ghcr.io/pr-mpt/sparse-docker-nginx

COPY nginx.conf /etc/nginx/conf.d/default.conf

COPY index.html ./
```

[docker/digest-pinning]: https://docs.docker.com/engine/reference/commandline/pull/#pull-an-image-by-digest-immutable-identifier
[packages/docker-nginx]: https://github.com/pr-mpt/sparse/pkgs/container/sparse-docker-nginx
19 changes: 19 additions & 0 deletions docker-nginx/default.conf
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
server {
listen 8080 default_server;

gzip on;
gzip_min_length 1000;
gzip_types text/plain text/xml application/javascript text/css;

root /srv;

location / {
add_header Cache-Control "no-store";
try_files $uri /entrypoint.html;
}

location ~ \.(?!html) {
add_header Cache-Control "public, max-age=2678400";
try_files $uri =404;
}
}
10 changes: 10 additions & 0 deletions docker-nginx/docker-compose.tests.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
services:
greeting:
image: ${SPARSE_IMAGE:-pr-mpt/sparse-docker-nginx}
build: .
ports:
- 8080:8080
environment:
GREETING: "Welcome"
volumes:
- ../.github/workflows/fixtures/greeting/index.html:/srv/index.html
8 changes: 8 additions & 0 deletions docker-nginx/insert-environment-variables.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
cd /srv

if [[ ! -f index.html ]]; then
echo "Sparse cannot start: an index.html file is required at /srv/index.html"
exit 1
fi

envsubst < index.html > entrypoint.html
19 changes: 19 additions & 0 deletions index.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
<!DOCTYPE html>
<html lang="en">

<body>
<script type="text/javascript">
window.config = {
API: "$API_URL" || "http://default.localhost",
};

window.onload = () => {
document.getElementById("api").innerHTML = window.config.API;
};
</script>
</body>
<p>
API endpoint provided at runtime: <span id="api">loading</span>
</p>

</html>

0 comments on commit 995e32e

Please sign in to comment.