wip: 🔕 temporary commit

This commit is contained in:
Paramtamtam 2024-06-29 14:54:47 +04:00
parent b7228c3933
commit 14815b4c73
No known key found for this signature in database
GPG Key ID: 366371698FAD0A2B
24 changed files with 723 additions and 3827 deletions

View File

@ -1,4 +1,5 @@
# Docs: <https://docs.github.com/en/free-pro-team@latest/github/administering-a-repository/customizing-dependency-updates>
# yaml-language-server: $schema=https://json.schemastore.org/dependabot-2.0.json
# docs: https://docs.github.com/en/free-pro-team@latest/github/administering-a-repository/customizing-dependency-updates
version: 2

13
.github/release.yml vendored Normal file
View File

@ -0,0 +1,13 @@
# yaml-language-server: $schema=https://json.schemastore.org/github-release-config.json
# docs: https://docs.github.com/en/repositories/releasing-projects-on-github/automatically-generated-release-notes
changelog:
categories:
- title: 🛠 Fixes
labels: [type:fix, type:bug]
- title: 🚀 Features
labels: [type:feature, type:feature_request]
- title: 📦 Dependency updates
labels: [dependencies]
- title: Other Changes
labels: ['*']

View File

@ -1,4 +1,7 @@
name: dependabot
# yaml-language-server: $schema=https://json.schemastore.org/github-workflow.json
# docs: https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions
name: 🤖 Dependabot
on:
pull_request: {}
@ -9,6 +12,7 @@ permissions:
jobs:
dependabot: # https://tinyurl.com/e69djmen
name: Enable auto-merge for Dependabot PRs
runs-on: ubuntu-latest
if: ${{ github.actor == 'dependabot[bot]' }}
steps:
@ -16,10 +20,8 @@ jobs:
id: metadata
with: {github-token: "${{ secrets.GITHUB_TOKEN }}"}
- name: Enable auto-merge for Dependabot PRs
if: ${{ contains(fromJSON('["version-update:semver-minor", "version-update:semver-patch"]'), steps.metadata.outputs.update-type) }}
- if: ${{ contains(fromJSON('["version-update:semver-minor", "version-update:semver-patch"]'), steps.metadata.outputs.update-type) }}
run: gh pr merge --auto --merge "$PR_URL"
continue-on-error: true
env:
PR_URL: ${{ github.event.pull_request.html_url }}
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}

View File

@ -1,4 +1,7 @@
name: documentation
# yaml-language-server: $schema=https://json.schemastore.org/github-workflow.json
# docs: https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions
name: 📚 Documentation
on:
push:
@ -12,7 +15,7 @@ jobs:
steps:
- uses: actions/checkout@v4
- uses: peter-evans/dockerhub-description@v4 # Action page: <https://github.com/peter-evans/dockerhub-description>
- uses: peter-evans/dockerhub-description@v4
with:
username: ${{ secrets.DOCKER_LOGIN }}
password: ${{ secrets.DOCKER_USER_PASSWORD }}

View File

@ -1,113 +1,122 @@
name: release
# yaml-language-server: $schema=https://json.schemastore.org/github-workflow.json
# docs: https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions
name: 🚀 Release
on:
release: # Docs: <https://git.io/JeBz1#release-event-release>
types: [published]
jobs:
purge-cdn-cache:
name: Purge jsDelivr CDN cache
wip:
runs-on: ubuntu-latest
steps:
- uses: gacts/purge-jsdelivr-cache@v1 # Action page: <https://github.com/gacts/purge-jsdelivr-cache>
with:
url: |
https://cdn.jsdelivr.net/gh/tarampampam/error-pages@2/l10n/l10n.js
https://cdn.jsdelivr.net/gh/tarampampam/error-pages@2/l10n/l10n.min.js
https://cdn.jsdelivr.net/gh/tarampampam/error-pages@latest/l10n/l10n.js
https://cdn.jsdelivr.net/gh/tarampampam/error-pages@latest/l10n/l10n.min.js
- run: echo WIP
build:
name: Build for ${{ matrix.os }} (${{ matrix.arch }})
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
os: [linux, darwin] # linux, freebsd, darwin, windows
arch: [amd64] # amd64, 386
steps:
- uses: actions/checkout@v4
- uses: gacts/setup-go-with-cache@v1
with: {go-version-file: go.mod}
- {uses: gacts/github-slug@v1, id: slug}
- name: Generate builder values
id: values
run: echo "binary-name=error-pages-${{ matrix.os }}-${{ matrix.arch }}" >> $GITHUB_OUTPUT
- name: Build application
env:
GOOS: ${{ matrix.os }}
GOARCH: ${{ matrix.arch }}
CGO_ENABLED: 0
LDFLAGS: -s -w -X gh.tarampamp.am/error-pages/internal/version.version=${{ steps.slug.outputs.version }}
run: go build -trimpath -ldflags "$LDFLAGS" -o "./${{ steps.values.outputs.binary-name }}" ./cmd/error-pages/
- name: Upload binary file to release
uses: svenstaro/upload-release-action@v2
with:
repo_token: ${{ secrets.GITHUB_TOKEN }}
file: ${{ steps.values.outputs.binary-name }}
asset_name: ${{ steps.values.outputs.binary-name }}
tag: ${{ github.ref }}
docker-image:
name: Build docker image
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- {uses: gacts/github-slug@v1, id: slug}
- uses: docker/setup-qemu-action@v3 # Action page: <https://github.com/docker/setup-qemu-action>
- uses: docker/setup-buildx-action@v3 # Action page: <https://github.com/docker/setup-buildx-action>
- uses: docker/login-action@v3 # Action page: <https://github.com/docker/login-action>
with:
username: ${{ secrets.DOCKER_LOGIN }}
password: ${{ secrets.DOCKER_PASSWORD }}
- uses: docker/login-action@v3 # Action page: <https://github.com/docker/login-action>
with:
registry: ghcr.io
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- uses: docker/build-push-action@v5 # Action page: <https://github.com/docker/build-push-action>
with:
context: .
file: Dockerfile
push: true
platforms: linux/amd64,linux/arm64,linux/arm/v7,linux/arm64/v8
build-args: "APP_VERSION=${{ steps.slug.outputs.version }}"
tags: |
tarampampam/error-pages:${{ steps.slug.outputs.version }}
tarampampam/error-pages:${{ steps.slug.outputs.version-major }}.${{ steps.slug.outputs.version-minor }}
tarampampam/error-pages:${{ steps.slug.outputs.version-major }}
tarampampam/error-pages:latest
ghcr.io/${{ github.actor }}/${{ github.event.repository.name }}:${{ steps.slug.outputs.version }}
ghcr.io/${{ github.actor }}/${{ github.event.repository.name }}:${{ steps.slug.outputs.version-major }}.${{ steps.slug.outputs.version-minor }}
ghcr.io/${{ github.actor }}/${{ github.event.repository.name }}:${{ steps.slug.outputs.version-major }}
ghcr.io/${{ github.actor }}/${{ github.event.repository.name }}:latest
demo:
name: Update the demonstration
runs-on: ubuntu-latest
needs: [docker-image]
steps:
- {uses: gacts/github-slug@v1, id: slug}
- name: Take rendered templates from the built docker image
run: |
docker create --name img ghcr.io/${{ github.actor }}/${{ github.event.repository.name }}:${{ steps.slug.outputs.version }}
docker cp img:/opt/html ./out
docker rm -f img
- name: Deploy to GitHub Pages
uses: peaceiris/actions-gh-pages@v4
with:
github_token: ${{ secrets.GITHUB_TOKEN }}
publish_dir: ./out
#jobs:
# purge-cdn-cache:
# name: Purge jsDelivr CDN cache
# runs-on: ubuntu-latest
# steps:
# - uses: gacts/purge-jsdelivr-cache@v1 # Action page: <https://github.com/gacts/purge-jsdelivr-cache>
# with:
# url: |
# https://cdn.jsdelivr.net/gh/tarampampam/error-pages@2/l10n/l10n.js
# https://cdn.jsdelivr.net/gh/tarampampam/error-pages@2/l10n/l10n.min.js
# https://cdn.jsdelivr.net/gh/tarampampam/error-pages@latest/l10n/l10n.js
# https://cdn.jsdelivr.net/gh/tarampampam/error-pages@latest/l10n/l10n.min.js
#
# build:
# name: Build for ${{ matrix.os }} (${{ matrix.arch }})
# runs-on: ubuntu-latest
# strategy:
# fail-fast: false
# matrix:
# os: [linux, darwin] # linux, freebsd, darwin, windows
# arch: [amd64] # amd64, 386
# steps:
# - uses: actions/checkout@v4
#
# - uses: gacts/setup-go-with-cache@v1
# with: {go-version-file: go.mod}
#
# - {uses: gacts/github-slug@v1, id: slug}
#
# - name: Generate builder values
# id: values
# run: echo "binary-name=error-pages-${{ matrix.os }}-${{ matrix.arch }}" >> $GITHUB_OUTPUT
#
# - name: Build application
# env:
# GOOS: ${{ matrix.os }}
# GOARCH: ${{ matrix.arch }}
# CGO_ENABLED: 0
# LDFLAGS: -s -w -X gh.tarampamp.am/error-pages/internal/version.version=${{ steps.slug.outputs.version }}
# run: go build -trimpath -ldflags "$LDFLAGS" -o "./${{ steps.values.outputs.binary-name }}" ./cmd/error-pages/
#
# - name: Upload binary file to release
# uses: svenstaro/upload-release-action@v2
# with:
# repo_token: ${{ secrets.GITHUB_TOKEN }}
# file: ${{ steps.values.outputs.binary-name }}
# asset_name: ${{ steps.values.outputs.binary-name }}
# tag: ${{ github.ref }}
#
# docker-image:
# name: Build docker image
# runs-on: ubuntu-latest
# steps:
# - uses: actions/checkout@v4
#
# - {uses: gacts/github-slug@v1, id: slug}
#
# - uses: docker/setup-qemu-action@v3 # Action page: <https://github.com/docker/setup-qemu-action>
#
# - uses: docker/setup-buildx-action@v3 # Action page: <https://github.com/docker/setup-buildx-action>
#
# - uses: docker/login-action@v3 # Action page: <https://github.com/docker/login-action>
# with:
# username: ${{ secrets.DOCKER_LOGIN }}
# password: ${{ secrets.DOCKER_PASSWORD }}
#
# - uses: docker/login-action@v3 # Action page: <https://github.com/docker/login-action>
# with:
# registry: ghcr.io
# username: ${{ github.actor }}
# password: ${{ secrets.GITHUB_TOKEN }}
#
# - uses: docker/build-push-action@v5 # Action page: <https://github.com/docker/build-push-action>
# with:
# context: .
# file: Dockerfile
# push: true
# platforms: linux/amd64,linux/arm64,linux/arm/v7,linux/arm64/v8
# build-args: "APP_VERSION=${{ steps.slug.outputs.version }}"
# tags: |
# tarampampam/error-pages:${{ steps.slug.outputs.version }}
# tarampampam/error-pages:${{ steps.slug.outputs.version-major }}.${{ steps.slug.outputs.version-minor }}
# tarampampam/error-pages:${{ steps.slug.outputs.version-major }}
# tarampampam/error-pages:latest
# ghcr.io/${{ github.actor }}/${{ github.event.repository.name }}:${{ steps.slug.outputs.version }}
# ghcr.io/${{ github.actor }}/${{ github.event.repository.name }}:${{ steps.slug.outputs.version-major }}.${{ steps.slug.outputs.version-minor }}
# ghcr.io/${{ github.actor }}/${{ github.event.repository.name }}:${{ steps.slug.outputs.version-major }}
# ghcr.io/${{ github.actor }}/${{ github.event.repository.name }}:latest
#
# demo:
# name: Update the demonstration
# runs-on: ubuntu-latest
# needs: [docker-image]
# steps:
# - {uses: gacts/github-slug@v1, id: slug}
#
# - name: Take rendered templates from the built docker image
# run: |
# docker create --name img ghcr.io/${{ github.actor }}/${{ github.event.repository.name }}:${{ steps.slug.outputs.version }}
# docker cp img:/opt/html ./out
# docker rm -f img
#
# - name: Deploy to GitHub Pages
# uses: peaceiris/actions-gh-pages@v4
# with:
# github_token: ${{ secrets.GITHUB_TOKEN }}
# publish_dir: ./out

View File

@ -1,4 +1,7 @@
name: tests
# yaml-language-server: $schema=https://json.schemastore.org/github-workflow.json
# docs: https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions
name: 🧪 Tests
on:
push:
@ -12,224 +15,177 @@ concurrency:
group: ${{ github.ref }}
cancel-in-progress: true
jobs: # Docs: <https://git.io/JvxXE>
jobs:
gitleaks:
name: Gitleaks
name: Check for GitLeaks
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
with: {fetch-depth: 0}
- name: Check for GitLeaks
uses: gacts/gitleaks@v1 # Action page: <https://github.com/gacts/gitleaks>
- {uses: actions/checkout@v4, with: {fetch-depth: 0}}
- uses: gacts/gitleaks@v1
golangci-lint:
name: Golang-CI (lint)
name: Run golangci-lint
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: gacts/setup-go-with-cache@v1
with: {go-version-file: go.mod}
- {uses: actions/setup-go@v5, with: {go-version-file: go.mod}}
- uses: golangci/golangci-lint-action@v6
with: {skip-pkg-cache: true, skip-build-cache: true}
validate-config-file:
name: Validate config file
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- {uses: actions/setup-node@v4, with: {node-version: 16}}
- name: Install linter
run: npm install -g ajv-cli # Package page: <https://www.npmjs.com/package/ajv-cli>
- name: Run linter
run: ajv validate --all-errors --verbose -s ./schemas/config/1.0.schema.json -d ./error-pages.y*ml
lint-l10n:
name: Lint l10n file(s)
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- {uses: actions/setup-node@v4, with: {node-version: 16}}
- name: Install eslint
run: npm install -g eslint@v8 # Package page: <https://www.npmjs.com/package/eslint>
- name: Run linter
working-directory: l10n
run: eslint ./*.js
go-test:
name: Unit tests
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
with: {fetch-depth: 2} # Fixes codecov error 'Issue detecting commit SHA'
- {uses: actions/setup-go@v5, with: {go-version-file: go.mod}}
- run: go test -race ./...
- uses: gacts/setup-go-with-cache@v1
with: {go-version-file: go.mod}
# build:
# name: Build for ${{ matrix.os }} (${{ matrix.arch }})
# runs-on: ubuntu-latest
# strategy:
# fail-fast: false
# matrix:
# os: [linux, darwin] # linux, freebsd, darwin, windows
# arch: [amd64] # amd64, 386
# needs: [golangci-lint, go-test, validate-config-file]
# steps:
# - uses: actions/checkout@v4
#
# - uses: gacts/setup-go-with-cache@v1
# with: {go-version-file: go.mod}
#
# - {uses: gacts/github-slug@v1, id: slug}
#
# - name: Build application
# env:
# GOOS: ${{ matrix.os }}
# GOARCH: ${{ matrix.arch }}
# CGO_ENABLED: 0
# LDFLAGS: -s -w -X gh.tarampamp.am/error-pages/internal/version.version=${{ steps.slug.outputs.branch-name-slug }}@${{ steps.slug.outputs.commit-hash-short }}
# run: go build -trimpath -ldflags "$LDFLAGS" -o ./error-pages ./cmd/error-pages/
#
# - name: Try to execute
# if: matrix.os == 'linux'
# run: ./error-pages --version && ./error-pages -h
#
# - uses: actions/upload-artifact@v4
# with:
# name: error-pages-${{ matrix.os }}-${{ matrix.arch }}
# path: error-pages
# if-no-files-found: error
# retention-days: 1
- name: Run Unit tests
run: go test -race -covermode=atomic -coverprofile /tmp/coverage.txt ./...
- uses: codecov/codecov-action@v4 # https://github.com/codecov/codecov-action
continue-on-error: true
with:
file: /tmp/coverage.txt
token: ${{ secrets.CODECOV_TOKEN }}
build:
name: Build for ${{ matrix.os }} (${{ matrix.arch }})
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
os: [linux, darwin] # linux, freebsd, darwin, windows
arch: [amd64] # amd64, 386
needs: [golangci-lint, go-test, validate-config-file]
steps:
- uses: actions/checkout@v4
- uses: gacts/setup-go-with-cache@v1
with: {go-version-file: go.mod}
- {uses: gacts/github-slug@v1, id: slug}
- name: Build application
env:
GOOS: ${{ matrix.os }}
GOARCH: ${{ matrix.arch }}
CGO_ENABLED: 0
LDFLAGS: -s -w -X gh.tarampamp.am/error-pages/internal/version.version=${{ steps.slug.outputs.branch-name-slug }}@${{ steps.slug.outputs.commit-hash-short }}
run: go build -trimpath -ldflags "$LDFLAGS" -o ./error-pages ./cmd/error-pages/
- name: Try to execute
if: matrix.os == 'linux'
run: ./error-pages --version && ./error-pages -h
- uses: actions/upload-artifact@v4
with:
name: error-pages-${{ matrix.os }}-${{ matrix.arch }}
path: error-pages
if-no-files-found: error
retention-days: 1
generate:
name: Run templates generator
runs-on: ubuntu-latest
needs: [build]
steps:
- uses: actions/checkout@v4
- uses: actions/download-artifact@v4
with:
name: error-pages-linux-amd64
path: .artifact
- name: Prepare binary file to run
working-directory: .artifact
run: mv ./error-pages ./../error-pages && chmod +x ./../error-pages
- name: Run generator
run: ./error-pages --verbose build --index ./out
- name: Test files creation
run: |
test -f ./out/index.html
test -f ./out/ghost/404.html
test -f ./out/l7-dark/404.html
test -f ./out/l7-light/404.html
test -f ./out/shuffle/404.html
test -f ./out/noise/404.html
test -f ./out/hacker-terminal/404.html
test -f ./out/cats/404.html
test -f ./out/lost-in-space/404.html
test -f ./out/app-down/404.html
test -f ./out/connection/404.html
test -f ./out/matrix/404.html
test -f ./out/orient/404.html
docker-image:
name: Build docker image
runs-on: ubuntu-latest
needs: [golangci-lint, go-test, validate-config-file]
steps:
- uses: actions/checkout@v4
- {uses: gacts/github-slug@v1, id: slug}
- uses: docker/build-push-action@v5 # Action page: <https://github.com/docker/build-push-action>
with:
context: .
file: Dockerfile
push: false
build-args: "APP_VERSION=${{ steps.slug.outputs.branch-name-slug }}@${{ steps.slug.outputs.commit-hash-short }}"
tags: app:ci
- run: docker save app:ci > ./docker-image.tar
- uses: actions/upload-artifact@v4
with:
name: docker-image
path: ./docker-image.tar
retention-days: 1
scan-docker-image:
name: Scan the docker image
runs-on: ubuntu-latest
needs: [docker-image]
steps:
- uses: actions/checkout@v4 # is needed for `upload-sarif` action
- uses: actions/download-artifact@v4
with:
name: docker-image
path: .artifact
- uses: aquasecurity/trivy-action@0.21.0 # action page: <https://github.com/aquasecurity/trivy-action>
with:
input: .artifact/docker-image.tar
format: sarif
severity: MEDIUM,HIGH,CRITICAL
exit-code: 1
output: trivy-results.sarif
- uses: github/codeql-action/upload-sarif@v3
if: always()
continue-on-error: true
with: {sarif_file: trivy-results.sarif}
poke-docker-image:
name: Run the docker image
runs-on: ubuntu-latest
needs: [docker-image]
timeout-minutes: 2
steps:
- uses: actions/checkout@v4
- uses: actions/download-artifact@v4
with:
name: docker-image
path: .artifact
- working-directory: .artifact
run: docker load < docker-image.tar
- uses: gacts/install-hurl@v1
- name: Run container with the app
run: docker run --rm -d -p "8080:8080/tcp" -e "SHOW_DETAILS=true" -e "PROXY_HTTP_HEADERS=X-Foo,Bar,Baz_blah" --name app app:ci
- name: Wait for container "healthy" state
run: until [[ "`docker inspect -f {{.State.Health.Status}} app`" == "healthy" ]]; do echo "wait 1 sec.."; sleep 1; done
- run: hurl --color --test --fail-at-end --variable host=127.0.0.1 --variable port=8080 ./test/hurl/*.hurl
- name: Stop the container
if: always()
run: docker kill app
# generate:
# name: Run templates generator
# runs-on: ubuntu-latest
# needs: [build]
# steps:
# - uses: actions/checkout@v4
#
# - uses: actions/download-artifact@v4
# with:
# name: error-pages-linux-amd64
# path: .artifact
#
# - name: Prepare binary file to run
# working-directory: .artifact
# run: mv ./error-pages ./../error-pages && chmod +x ./../error-pages
#
# - name: Run generator
# run: ./error-pages --verbose build --index ./out
#
# - name: Test files creation
# run: |
# test -f ./out/index.html
# test -f ./out/ghost/404.html
# test -f ./out/l7-dark/404.html
# test -f ./out/l7-light/404.html
# test -f ./out/shuffle/404.html
# test -f ./out/noise/404.html
# test -f ./out/hacker-terminal/404.html
# test -f ./out/cats/404.html
# test -f ./out/lost-in-space/404.html
# test -f ./out/app-down/404.html
# test -f ./out/connection/404.html
# test -f ./out/matrix/404.html
# test -f ./out/orient/404.html
#
# docker-image:
# name: Build docker image
# runs-on: ubuntu-latest
# needs: [golangci-lint, go-test, validate-config-file]
# steps:
# - uses: actions/checkout@v4
#
# - {uses: gacts/github-slug@v1, id: slug}
#
# - uses: docker/build-push-action@v5 # Action page: <https://github.com/docker/build-push-action>
# with:
# context: .
# file: Dockerfile
# push: false
# build-args: "APP_VERSION=${{ steps.slug.outputs.branch-name-slug }}@${{ steps.slug.outputs.commit-hash-short }}"
# tags: app:ci
#
# - run: docker save app:ci > ./docker-image.tar
#
# - uses: actions/upload-artifact@v4
# with:
# name: docker-image
# path: ./docker-image.tar
# retention-days: 1
#
# scan-docker-image:
# name: Scan the docker image
# runs-on: ubuntu-latest
# needs: [docker-image]
# steps:
# - uses: actions/checkout@v4 # is needed for `upload-sarif` action
#
# - uses: actions/download-artifact@v4
# with:
# name: docker-image
# path: .artifact
#
# - uses: aquasecurity/trivy-action@0.21.0 # action page: <https://github.com/aquasecurity/trivy-action>
# with:
# input: .artifact/docker-image.tar
# format: sarif
# severity: MEDIUM,HIGH,CRITICAL
# exit-code: 1
# output: trivy-results.sarif
#
# - uses: github/codeql-action/upload-sarif@v3
# if: always()
# continue-on-error: true
# with: {sarif_file: trivy-results.sarif}
#
# poke-docker-image:
# name: Run the docker image
# runs-on: ubuntu-latest
# needs: [docker-image]
# timeout-minutes: 2
# steps:
# - uses: actions/checkout@v4
#
# - uses: actions/download-artifact@v4
# with:
# name: docker-image
# path: .artifact
#
# - working-directory: .artifact
# run: docker load < docker-image.tar
#
# - uses: gacts/install-hurl@v1
#
# - name: Run container with the app
# run: docker run --rm -d -p "8080:8080/tcp" -e "SHOW_DETAILS=true" -e "PROXY_HTTP_HEADERS=X-Foo,Bar,Baz_blah" --name app app:ci
#
# - name: Wait for container "healthy" state
# run: until [[ "`docker inspect -f {{.State.Health.Status}} app`" == "healthy" ]]; do echo "wait 1 sec.."; sleep 1; done
#
# - run: hurl --color --test --fail-at-end --variable host=127.0.0.1 --variable port=8080 ./test/hurl/*.hurl
#
# - name: Stop the container
# if: always()
# run: docker kill app

View File

@ -110,10 +110,6 @@ issues:
- tmp
- temp
- testdata
- cmd-old # TODO: remove
- internal-old # TODO: remove
- templates-old # TODO: remove
- test-old # TODO: remove
exclude-rules:
- {path: flags\.go, linters: [gochecknoglobals, lll, mnd, dupl]}
- {path: env\.go, linters: [lll, gosec]}

View File

@ -7,8 +7,6 @@ FROM docker.io/library/golang:1.22-bookworm AS develop
ENV GOPATH="/var/tmp/go"
RUN set -x \
&& GOBIN=/bin go install gotest.tools/gotestsum@latest \
&& go clean -cache -modcache \
# renovate: source=github-releases name=golangci/golangci-lint
&& GOLANGCI_LINT_VERSION="1.59.1" \
&& wget -O- -nv "https://cdn.jsdelivr.net/gh/golangci/golangci-lint@v${GOLANGCI_LINT_VERSION}/install.sh" \

View File

@ -10,10 +10,10 @@ $ error-pages [GLOBAL FLAGS] [COMMAND] [COMMAND FLAGS] [ARGUMENTS...]
Global flags:
| Name | Description | Default value | Environment variables |
|--------------------|---------------------------------------------|:-------------:|:---------------------:|
| `--log-level="…"` | logging level (debug/info/warn/error/fatal) | `info` | `LOG_LEVEL` |
| `--log-format="…"` | logging format (console/json) | `console` | `LOG_FORMAT` |
| Name | Description | Default value | Environment variables |
|--------------------|---------------------------------------|:-------------:|:---------------------:|
| `--log-level="…"` | logging level (debug/info/warn/error) | `info` | `LOG_LEVEL` |
| `--log-format="…"` | logging format (console/json) | `console` | `LOG_FORMAT` |
### `serve` command (aliases: `s`, `server`, `http`)
@ -27,21 +27,23 @@ $ error-pages [GLOBAL FLAGS] serve [COMMAND FLAGS] [ARGUMENTS...]
The following flags are supported:
| Name | Description | Default value | Environment variables |
|------------------------------|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|:-----------------------------------------:|:-------------------------:|
| `--listen="…"` (`-l`) | IP (v4 or v6) address to listen on | `0.0.0.0` | `LISTEN_ADDR` |
| `--port="…"` (`-p`) | TCP port number | `8080` | `LISTEN_PORT` |
| `--add-template="…"` | to add a new template, provide the path to the file using this flag (the filename without the extension will be used as the template name) | `[]` | *none* |
| `--add-http-code="…"` | to add a new HTTP status code, provide the code and its message/description using this flag (the format should be '%code%=%message%/%description%'; the code may contain a wildcard '*' to cover multiple codes at once, for example, '4**' will cover all 4xx codes, unless a more specific code was described previously) | `map[]` | *none* |
| `--json-format="…"` | override the default error page response in JSON format (Go templates are supported) | | `RESPONSE_JSON_FORMAT` |
| `--xml-format="…"` | override the default error page response in XML format (Go templates are supported) | | `RESPONSE_XML_FORMAT` |
| `--template-name="…"` (`-t`) | name of the template to use for rendering error pages | `template-1` | `TEMPLATE_NAME` |
| `--disable-l10n` | disable localization of error pages (if the template supports localization) | `false` | `DISABLE_L10N` |
| `--default-error-page="…"` | the code of the default (index page, when a code is not specified) error page to render | `404` | `DEFAULT_ERROR_PAGE` |
| `--send-same-http-code` | the HTTP response should have the same status code as the requested error page (by default, every response with an error page will have a status code of 200) | `false` | `SEND_SAME_HTTP_CODE` |
| `--show-details` | show request details in the error page response (if supported by the template) | `false` | `SHOW_DETAILS` |
| `--proxy-headers="…"` | listed here HTTP headers will be proxied from the original request to the error page response (comma-separated list) | `X-Request-Id,X-Trace-Id,X-Amzn-Trace-Id` | `PROXY_HTTP_HEADERS` |
| `--rotation-mode="…"` | templates automatic rotation mode (disabled/random-on-startup/random-on-each-request/random-daily/random-hourly) | `disabled` | `TEMPLATES_ROTATION_MODE` |
| Name | Description | Default value | Environment variables |
|------------------------------|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|:-----------------------------------------:|:---------------------------:|
| `--listen="…"` (`-l`) | IP (v4 or v6) address to listen on | `0.0.0.0` | `LISTEN_ADDR` |
| `--port="…"` (`-p`) | TCP port number | `8080` | `LISTEN_PORT` |
| `--add-template="…"` | to add a new template, provide the path to the file using this flag (the filename without the extension will be used as the template name) | `[]` | *none* |
| `--disable-template="…"` | disable the specified template by its name | `[]` | *none* |
| `--add-http-code="…"` | to add a new HTTP status code, provide the code and its message/description using this flag (the format should be '%code%=%message%/%description%'; the code may contain a wildcard '*' to cover multiple codes at once, for example, '4**' will cover all 4xx codes, unless a more specific code was described previously) | `map[]` | *none* |
| `--json-format="…"` | override the default error page response in JSON format (Go templates are supported) | | `RESPONSE_JSON_FORMAT` |
| `--xml-format="…"` | override the default error page response in XML format (Go templates are supported) | | `RESPONSE_XML_FORMAT` |
| `--plaintext-format="…"` | override the default error page response in plain text format (Go templates are supported) | | `RESPONSE_PLAINTEXT_FORMAT` |
| `--template-name="…"` (`-t`) | name of the template to use for rendering error pages | `app-down` | `TEMPLATE_NAME` |
| `--disable-l10n` | disable localization of error pages (if the template supports localization) | `false` | `DISABLE_L10N` |
| `--default-error-page="…"` | the code of the default (index page, when a code is not specified) error page to render | `404` | `DEFAULT_ERROR_PAGE` |
| `--send-same-http-code` | the HTTP response should have the same status code as the requested error page (by default, every response with an error page will have a status code of 200) | `false` | `SEND_SAME_HTTP_CODE` |
| `--show-details` | show request details in the error page response (if supported by the template) | `false` | `SHOW_DETAILS` |
| `--proxy-headers="…"` | listed here HTTP headers will be proxied from the original request to the error page response (comma-separated list) | `X-Request-Id,X-Trace-Id,X-Amzn-Trace-Id` | `PROXY_HTTP_HEADERS` |
| `--rotation-mode="…"` | templates automatic rotation mode (disabled/random-on-startup/random-on-each-request/random-hourly/random-daily) | `disabled` | `TEMPLATES_ROTATION_MODE` |
### `healthcheck` command (aliases: `chk`, `health`, `check`)

View File

@ -1,140 +0,0 @@
templates:
# - name: {string} Template name (optional, if path is defined)
# path: {string} Path to the template file
# content: {string} Template content, if path is not defined
- path: ./templates/ghost.html
name: ghost # name is optional, if path is defined
content: ${GHOST_TEMPLATE_CONTENT}
- path: ./templates/l7-light.html
- path: ./templates/l7-dark.html
- path: ./templates/shuffle.html
- path: ./templates/noise.html
- path: ./templates/hacker-terminal.html
- path: ./templates/cats.html
- path: ./templates/lost-in-space.html
- path: ./templates/app-down.html
- path: ./templates/connection.html
- path: ./templates/matrix.html
- path: ./templates/orient.html
formats:
json:
content: |
{
"error": true,
"code": {{ code | json }},
"message": {{ message | json }},
"description": {{ description | json }}{{ if show_details }},
"details": {
"host": {{ host | json }},
"original_uri": {{ original_uri | json }},
"forwarded_for": {{ forwarded_for | json }},
"namespace": {{ namespace | json }},
"ingress_name": {{ ingress_name | json }},
"service_name": {{ service_name | json }},
"service_port": {{ service_port | json }},
"request_id": {{ request_id | json }},
"timestamp": {{ now.Unix }}
}{{ end }}
}
xml:
content: |
<?xml version="1.0" encoding="utf-8"?>
<error>
<code>{{ code }}</code>
<message>{{ message }}</message>
<description>{{ description }}</description>{{ if show_details }}
<details>
<host>{{ host }}</host>
<originalURI>{{ original_uri }}</originalURI>
<forwardedFor>{{ forwarded_for }}</forwardedFor>
<namespace>{{ namespace }}</namespace>
<ingressName>{{ ingress_name }}</ingressName>
<serviceName>{{ service_name }}</serviceName>
<servicePort>{{ service_port }}</servicePort>
<requestID>{{ request_id }}</requestID>
<timestamp>{{ now.Unix }}</timestamp>
</details>{{ end }}
</error>
pages:
400:
message: Bad Request
description: The server did not understand the request
401:
message: Unauthorized
description: The requested page needs a username and a password
403:
message: Forbidden
description: Access is forbidden to the requested page
404:
message: Not Found
description: The server can not find the requested page
405:
message: Method Not Allowed
description: The method specified in the request is not allowed
407:
message: Proxy Authentication Required
description: You must authenticate with a proxy server before this request can be served
408:
message: Request Timeout
description: The request took longer than the server was prepared to wait
409:
message: Conflict
description: The request could not be completed because of a conflict
410:
message: Gone
description: The requested page is no longer available
411:
message: Length Required
description: The "Content-Length" is not defined. The server will not accept the request without it
412:
message: Precondition Failed
description: The pre condition given in the request evaluated to false by the server
413:
message: Payload Too Large
description: The server will not accept the request, because the request entity is too large
416:
message: Requested Range Not Satisfiable
description: The requested byte range is not available and is out of bounds
418:
message: I'm a teapot
description: Attempt to brew coffee with a teapot is not supported
429:
message: Too Many Requests
description: Too many requests in a given amount of time
500:
message: Internal Server Error
description: The server met an unexpected condition
502:
message: Bad Gateway
description: The server received an invalid response from the upstream server
503:
message: Service Unavailable
description: The server is temporarily overloading or down
504:
message: Gateway Timeout
description: The gateway has timed out
505:
message: HTTP Version Not Supported
description: The server does not support the "http protocol" version

29
go.mod
View File

@ -3,41 +3,20 @@ module gh.tarampamp.am/error-pages
go 1.22
require (
github.com/a8m/envsubst v1.4.2
github.com/fasthttp/router v1.5.1
github.com/fatih/color v1.17.0
github.com/kami-zh/go-capturer v0.0.0-20171211120116-e492ea43421d
github.com/pkg/errors v0.9.1
github.com/prometheus/client_golang v1.19.1
github.com/prometheus/client_model v0.6.1
github.com/stretchr/testify v1.9.0
github.com/urfave/cli-docs/v3 v3.0.0-alpha5
github.com/urfave/cli/v2 v2.27.2
github.com/urfave/cli/v3 v3.0.0-alpha9
github.com/valyala/fasthttp v1.54.0
go.uber.org/automaxprocs v1.5.3
go.uber.org/goleak v1.3.0
go.uber.org/zap v1.27.0
gopkg.in/yaml.v3 v3.0.1
)
require (
github.com/andybalholm/brotli v1.1.0 // indirect
github.com/beorn7/perks v1.0.1 // indirect
github.com/cespare/xxhash/v2 v2.2.0 // indirect
github.com/cpuguy83/go-md2man/v2 v2.0.4 // indirect
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/klauspost/compress v1.17.7 // indirect
github.com/mattn/go-colorable v0.1.13 // indirect
github.com/mattn/go-isatty v0.0.20 // indirect
github.com/kr/pretty v0.3.1 // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect
github.com/prometheus/common v0.50.0 // indirect
github.com/prometheus/procfs v0.13.0 // indirect
github.com/rogpeppe/go-internal v1.12.0 // indirect
github.com/russross/blackfriday/v2 v2.1.0 // indirect
github.com/savsgio/gotils v0.0.0-20240303185622-093b76447511 // indirect
github.com/valyala/bytebufferpool v1.0.0 // indirect
github.com/xrash/smetrics v0.0.0-20240521201337-686a1a2994c1 // indirect
go.uber.org/multierr v1.11.0 // indirect
golang.org/x/sys v0.18.0 // indirect
google.golang.org/protobuf v1.33.0 // indirect
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
)

63
go.sum
View File

@ -1,82 +1,35 @@
github.com/a8m/envsubst v1.4.2 h1:4yWIHXOLEJHQEFd4UjrWDrYeYlV7ncFWJOCBRLOZHQg=
github.com/a8m/envsubst v1.4.2/go.mod h1:MVUTQNGQ3tsjOOtKCNd+fl8RzhsXcDvvAEzkhGtlsbY=
github.com/andybalholm/brotli v1.1.0 h1:eLKJA0d02Lf0mVpIDgYnqXcUn0GqVmEFny3VuID1U3M=
github.com/andybalholm/brotli v1.1.0/go.mod h1:sms7XGricyQI9K10gOSf56VKKWS4oLer58Q+mhRPtnY=
github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM=
github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw=
github.com/cespare/xxhash/v2 v2.2.0 h1:DC2CZ1Ep5Y4k3ZQ899DldepgrayRUGE6BBZ/cd9Cj44=
github.com/cespare/xxhash/v2 v2.2.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
github.com/cpuguy83/go-md2man/v2 v2.0.4 h1:wfIWP927BUkWJb2NmU/kNDYIBTh/ziUX91+lVfRxZq4=
github.com/cpuguy83/go-md2man/v2 v2.0.4/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/fasthttp/router v1.5.1 h1:uViy8UYYhm5npJSKEZ4b/ozM//NGzVCfJbh6VJ0VKr8=
github.com/fasthttp/router v1.5.1/go.mod h1:WrmsLo3mrerZP2VEXRV1E8nL8ymJFYCDTr4HmnB8+Zs=
github.com/fatih/color v1.17.0 h1:GlRw1BRJxkpqUCBKzKOw098ed57fEsKeNjpTe3cSjK4=
github.com/fatih/color v1.17.0/go.mod h1:YZ7TlrGPkiz6ku9fK3TLD/pl3CpsiFyu8N92HLgmosI=
github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
github.com/kami-zh/go-capturer v0.0.0-20171211120116-e492ea43421d h1:cVtBfNW5XTHiKQe7jDaDBSh/EVM4XLPutLAGboIXuM0=
github.com/kami-zh/go-capturer v0.0.0-20171211120116-e492ea43421d/go.mod h1:P2viExyCEfeWGU259JnaQ34Inuec4R38JCyBx2edgD0=
github.com/klauspost/compress v1.17.7 h1:ehO88t2UGzQK66LMdE8tibEd1ErmzZjNEqWkjLAKQQg=
github.com/klauspost/compress v1.17.7/go.mod h1:Di0epgTjJY877eYKx5yC51cX2A2Vl2ibi7bDH9ttBbw=
github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA=
github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg=
github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/prashantv/gostub v1.1.0 h1:BTyx3RfQjRHnUWaGF9oQos79AlQ5k8WNktv7VGvVH4g=
github.com/prashantv/gostub v1.1.0/go.mod h1:A5zLQHz7ieHGG7is6LLXLz7I8+3LZzsrV0P1IAHhP5U=
github.com/prometheus/client_golang v1.19.1 h1:wZWJDwK+NameRJuPGDhlnFgx8e8HN3XHQeLaYJFJBOE=
github.com/prometheus/client_golang v1.19.1/go.mod h1:mP78NwGzrVks5S2H6ab8+ZZGJLZUq1hoULYBAYBw1Ho=
github.com/prometheus/client_model v0.6.1 h1:ZKSh/rekM+n3CeS952MLRAdFwIKqeY8b62p8ais2e9E=
github.com/prometheus/client_model v0.6.1/go.mod h1:OrxVMOVHjw3lKMa8+x6HeMGkHMQyHDk9E3jmP2AmGiY=
github.com/prometheus/common v0.50.0 h1:YSZE6aa9+luNa2da6/Tik0q0A5AbR+U003TItK57CPQ=
github.com/prometheus/common v0.50.0/go.mod h1:wHFBCEVWVmHMUpg7pYcOm2QUR/ocQdYSJVQJKnHc3xQ=
github.com/prometheus/procfs v0.13.0 h1:GqzLlQyfsPbaEHaQkO7tbDlriv/4o5Hudv6OXHGKX7o=
github.com/prometheus/procfs v0.13.0/go.mod h1:cd4PFCR54QLnGKPaKGA6l+cfuNXtht43ZKY6tow0Y1g=
github.com/rogpeppe/go-internal v1.10.0 h1:TMyTOH3F/DB16zRVcYyreMH6GnZZrwQVAoYjRBZyWFQ=
github.com/rogpeppe/go-internal v1.10.0/go.mod h1:UQnix2H7Ngw/k4C5ijL5+65zddjncjaFoBhdsK/akog=
github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs=
github.com/rogpeppe/go-internal v1.12.0 h1:exVL4IDcn6na9z1rAb56Vxr+CgyK3nn3O+epU5NdKM8=
github.com/rogpeppe/go-internal v1.12.0/go.mod h1:E+RYuTGaKKdloAfM02xzb0FW3Paa99yedzYV+kq4uf4=
github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk=
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
github.com/savsgio/gotils v0.0.0-20240303185622-093b76447511 h1:KanIMPX0QdEdB4R3CiimCAbxFrhB3j7h0/OvpYGVQa8=
github.com/savsgio/gotils v0.0.0-20240303185622-093b76447511/go.mod h1:sM7Mt7uEoCeFSCBM+qBrqvEo+/9vdmj19wzp3yzUhmg=
github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg=
github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
github.com/urfave/cli-docs/v3 v3.0.0-alpha5 h1:H1oWnR2/GN0dNm2PVylws+GxSOD6YOwW/jI5l78YfPk=
github.com/urfave/cli-docs/v3 v3.0.0-alpha5/go.mod h1:AIqom6Q60U4tiqHp41i7+/AB2XHgi1WvQ7jOFlccmZ4=
github.com/urfave/cli/v2 v2.27.2 h1:6e0H+AkS+zDckwPCUrZkKX38mRaau4nL2uipkJpbkcI=
github.com/urfave/cli/v2 v2.27.2/go.mod h1:g0+79LmHHATl7DAcHO99smiR/T7uGLw84w8Y42x+4eM=
github.com/urfave/cli/v3 v3.0.0-alpha9 h1:P0RMy5fQm1AslQS+XCmy9UknDXctOmG/q/FZkUFnJSo=
github.com/urfave/cli/v3 v3.0.0-alpha9/go.mod h1:0kK/RUFHyh+yIKSfWxwheGndfnrvYSmYFVeKCh03ZUc=
github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw=
github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc=
github.com/valyala/fasthttp v1.54.0 h1:cCL+ZZR3z3HPLMVfEYVUMtJqVaui0+gu7Lx63unHwS0=
github.com/valyala/fasthttp v1.54.0/go.mod h1:6dt4/8olwq9QARP/TDuPmWyWcl4byhpvTJ4AAtcz+QM=
github.com/xrash/smetrics v0.0.0-20240521201337-686a1a2994c1 h1:gEOO8jv9F4OT7lGCjxCBTO/36wtF6j2nSip77qHd4x4=
github.com/xrash/smetrics v0.0.0-20240521201337-686a1a2994c1/go.mod h1:Ohn+xnUBiLI6FVj/9LpzZWtj1/D6lUovWYBkxHVV3aM=
go.uber.org/automaxprocs v1.5.3 h1:kWazyxZUrS3Gs4qUpbwo5kEIMGe/DAvi5Z4tl2NW4j8=
go.uber.org/automaxprocs v1.5.3/go.mod h1:eRbA25aqJrxAbsLO0xy5jVwPt7FQnRgjW+efnwa1WM0=
go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto=
go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE=
go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0=
go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y=
go.uber.org/zap v1.27.0 h1:aJMhYGrd5QSmlpLMr2MftRKl7t8J8PTZPA732ud/XR8=
go.uber.org/zap v1.27.0/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E=
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.18.0 h1:DBdB3niSjOA/O0blCZBqDefyWNYveAYMNF1Wum0DYQ4=
golang.org/x/sys v0.18.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
google.golang.org/protobuf v1.33.0 h1:uNO2rsAINq/JlFpSdYEKIZ0uKD/R9cpdv0T+yoGwGmI=
google.golang.org/protobuf v1.33.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=

View File

@ -3,6 +3,8 @@ package appmeta
import "testing"
func TestVersion(t *testing.T) {
t.Parallel()
for give, want := range map[string]string{
// without changes
"vvv": "vvv",

View File

@ -4,7 +4,6 @@ import (
"context"
"testing"
"github.com/kami-zh/go-capturer"
"github.com/stretchr/testify/assert"
"gh.tarampamp.am/error-pages/internal/cli"
@ -15,11 +14,5 @@ func TestNewApp(t *testing.T) {
app := cli.NewApp("appName")
assert.NotEmpty(t, app.Flags)
output := capturer.CaptureStdout(func() {
assert.NoError(t, app.Run(context.Background(), []string{""}))
})
assert.NotEmpty(t, output)
assert.NoError(t, app.Run(context.Background(), []string{""}))
}

View File

@ -73,7 +73,7 @@ func NewCommand(log *logger.Logger) *cli.Command { //nolint:funlen,gocognit
logger.Uint64("failed", failed.Load()),
logger.Duration("duration", time.Since(startedAt)),
logger.Float64("RPS", float64(success.Load()+failed.Load())/time.Since(startedAt).Seconds()),
logger.Float64("errors rate", float64(failed.Load())/float64(success.Load()+failed.Load())*100),
logger.Float64("errors rate", float64(failed.Load())/float64(success.Load()+failed.Load())*100), //nolint:mnd
)
}()
@ -99,7 +99,7 @@ func NewCommand(log *logger.Logger) *cli.Command { //nolint:funlen,gocognit
var req, rErr = makeRequest(perfCtx, uint16(c.Uint(portFlag.Name)))
if rErr != nil {
log.Error("failed to create a new request", logger.Error(rErr))
log.Error("Failed to create a new request", logger.Error(rErr))
return
}
@ -110,11 +110,11 @@ func NewCommand(log *logger.Logger) *cli.Command { //nolint:funlen,gocognit
var resp, respErr = httpClient.Do(req)
if resp != nil {
if _, err := io.Copy(io.Discard, resp.Body); err != nil && !errIsDone(err) {
log.Error("failed to read response body", logger.Error(err))
log.Error("Failed to read response body", logger.Error(err))
}
if err := resp.Body.Close(); err != nil && !errIsDone(err) {
log.Error("failed to close response body", logger.Error(err))
log.Error("Failed to close response body", logger.Error(err))
}
}
@ -123,7 +123,7 @@ func NewCommand(log *logger.Logger) *cli.Command { //nolint:funlen,gocognit
return
}
log.Error("request failed", logger.Error(respErr))
log.Error("Request failed", logger.Error(respErr))
failed.Add(1)
continue

View File

@ -110,7 +110,7 @@ func NewCommand(log *logger.Logger) *cli.Command { //nolint:funlen,gocognit,gocy
OnlyOnce: true,
}
proxyHeadersListFlag = cli.StringFlag{
Name: "proxy-headers", // TODO: add support for the "*" wildcard
Name: "proxy-headers",
Usage: "listed here HTTP headers will be proxied from the original request to the error page response " +
"(comma-separated list)",
Value: strings.Join(cfg.ProxyHeaders, ","),

View File

@ -0,0 +1,101 @@
package serve_test
import (
"context"
"fmt"
"net"
"strconv"
"testing"
"time"
"github.com/stretchr/testify/require"
"gh.tarampamp.am/error-pages/internal/cli/serve"
"gh.tarampamp.am/error-pages/internal/logger"
)
func TestCommand_Run(t *testing.T) {
t.Parallel()
var (
port = getFreeTcpPort(t)
cmd = serve.NewCommand(logger.NewNop())
)
var ctx, cancel = context.WithTimeout(context.Background(), 10*time.Second)
defer cancel()
var ch = make(chan error, 1)
go func() {
defer close(ch)
ch <- cmd.Run(ctx, []string{
"serve",
"--port", strconv.Itoa(int(port)),
"--add-template", "./testdata/foo-template.html",
"--disable-template", "ghost",
"--disable-template", "<unknown>",
"--add-http-code", "200=Code/Description",
"--json-format", "json format",
"--xml-format", "xml format",
"--plaintext-format", "plaintext format",
"--template-name", "foo-template",
"--disable-l10n",
"--default-error-page", "503",
"--send-same-http-code",
"--show-details",
"--proxy-headers", "X-Forwarded-For,X-Forwarded-Proto",
"--rotation-mode", "random-on-each-request",
})
}()
var connected bool
for {
conn, err := net.DialTimeout("tcp", fmt.Sprintf("127.0.0.1:%d", port), time.Second)
if err == nil {
connected = true
require.NoError(t, conn.Close())
break
} else {
t.Log(err)
}
select {
case <-ctx.Done():
t.Fatal("timeout")
case chErr := <-ch:
require.NoError(t, chErr)
case <-time.After(10 * time.Millisecond):
}
}
require.True(t, connected, "server is not running")
}
// getFreeTcpPort is a helper function to get a free TCP port number.
func getFreeTcpPort(t *testing.T) uint16 {
t.Helper()
l, lErr := net.Listen("tcp", "127.0.0.1:0")
require.NoError(t, lErr)
port := l.Addr().(*net.TCPAddr).Port
require.NoError(t, l.Close())
// make sure port is closed
for {
conn, err := net.Dial("tcp", fmt.Sprintf("127.0.0.1:%d", port))
if err != nil {
break
}
require.NoError(t, conn.Close())
<-time.After(5 * time.Millisecond)
}
return uint16(port)
}

View File

@ -0,0 +1,10 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
</body>
</html>

View File

@ -0,0 +1,226 @@
package error_page_test
import (
"net/http"
"net/http/httptest"
"testing"
"github.com/stretchr/testify/assert"
"gh.tarampamp.am/error-pages/internal/config"
"gh.tarampamp.am/error-pages/internal/http/handlers/error_page"
"gh.tarampamp.am/error-pages/internal/logger"
)
func TestHandler(t *testing.T) {
t.Parallel()
for name, tt := range map[string]struct {
giveConfig func() *config.Config
giveUrl string
giveHeaders map[string]string
wantStatusCode int
wantHeaders map[string]string
wantBodyIncludes []string
}{
"common, plain text": {
giveConfig: func() *config.Config { cfg := config.New(); return &cfg },
giveUrl: "/",
giveHeaders: map[string]string{"Content-Type": "text/plain"},
wantStatusCode: http.StatusOK,
wantHeaders: map[string]string{"Content-Type": "text/plain; charset=utf-8"},
wantBodyIncludes: []string{"Error 404", "Not Found"},
},
"common, html": {
giveConfig: func() *config.Config {
cfg := config.New()
cfg.TemplateName = "ghost"
return &cfg
},
giveUrl: "/",
giveHeaders: map[string]string{"X-Format": "text/html", "X-Code": "407"},
wantStatusCode: http.StatusOK,
wantHeaders: map[string]string{"Content-Type": "text/html; charset=utf-8"},
wantBodyIncludes: []string{
"<!DOCTYPE html>",
"<title>407: Proxy Authentication Required",
"Proxy Authentication Required",
},
},
"common, json": {
giveConfig: func() *config.Config {
cfg := config.New()
cfg.RespondWithSameHTTPCode = true
return &cfg
},
giveUrl: "/503.html",
giveHeaders: map[string]string{"Accept": "application/json", "X-FooBar": "baz"},
wantStatusCode: http.StatusServiceUnavailable,
wantHeaders: map[string]string{
"Content-Type": "application/json; charset=utf-8",
"X-FooBar": "", // is not in the list of proxy headers
},
wantBodyIncludes: []string{"503", "Service Unavailable"},
},
"common, xml": {
giveConfig: func() *config.Config {
cfg := config.New()
cfg.ProxyHeaders = append(cfg.ProxyHeaders, "X-FooBar")
return &cfg
},
giveUrl: "/500",
giveHeaders: map[string]string{"Accept": "application/xml", "X-FooBar": "baz"},
wantStatusCode: http.StatusOK,
wantHeaders: map[string]string{
"Content-Type": "application/xml; charset=utf-8",
"X-FooBar": "baz",
},
wantBodyIncludes: []string{"500", "Internal Server Error"},
},
"show details": {
giveConfig: func() *config.Config {
cfg := config.New()
cfg.ShowDetails = true
return &cfg
},
giveUrl: "/503",
giveHeaders: map[string]string{
"Accept": "application/json",
"X-Original-URI": "/foo/bar",
"X-Namespace": "some-Namespace",
"X-Ingress-Name": "ingress-name",
"X-Service-Name": "service-name",
"X-Service-Port": "666",
"X-Request-ID": "req-id-777",
"X-Forwarded-For": "123.123.123.123:12312",
"Host": "example.com",
},
wantStatusCode: http.StatusOK,
wantHeaders: map[string]string{"Content-Type": "application/json; charset=utf-8"},
wantBodyIncludes: []string{
"503",
"Service Unavailable",
"details",
"/foo/bar",
"some-Namespace",
"ingress-name",
"service-name",
"666",
"req-id-777",
"123.123.123.123:12312",
"example.com",
},
},
"fallback to StatusText if code is not found": {
giveConfig: func() *config.Config {
cfg := config.New()
cfg.Codes = config.Codes{}
return &cfg
},
giveUrl: "/100",
giveHeaders: map[string]string{"Accept": "application/json"},
wantStatusCode: http.StatusOK,
wantHeaders: map[string]string{"Content-Type": "application/json; charset=utf-8"},
wantBodyIncludes: []string{"100", "Continue"},
},
"unknown code": {
giveConfig: func() *config.Config {
cfg := config.New()
cfg.Codes = config.Codes{}
return &cfg
},
giveUrl: "/1",
giveHeaders: map[string]string{"Accept": "application/json"},
wantStatusCode: http.StatusOK,
wantHeaders: map[string]string{"Content-Type": "application/json; charset=utf-8"},
wantBodyIncludes: []string{"1", "Unknown Status Code"},
},
} {
t.Run(name, func(t *testing.T) {
t.Parallel()
var (
req = httptest.NewRequest(http.MethodGet, tt.giveUrl, http.NoBody)
handler = error_page.New(tt.giveConfig(), logger.NewNop())
rr = httptest.NewRecorder()
)
for k, v := range tt.giveHeaders {
req.Header.Set(k, v)
}
handler.ServeHTTP(rr, req)
assert.Equal(t, rr.Code, tt.wantStatusCode)
for hName, hWant := range tt.wantHeaders {
for hGot := range rr.Header() {
if hGot == hName {
assert.Contains(t, hWant, rr.Header().Get(hGot))
}
}
}
for _, wantBodyInclude := range tt.wantBodyIncludes {
assert.Contains(t, rr.Body.String(), wantBodyInclude)
}
})
}
}
func TestRotationModeOnEachRequest(t *testing.T) {
t.Parallel()
var cfg = config.New()
cfg.RotationMode = config.RotationModeRandomOnEachRequest
cfg.Templates = map[string]string{
"foo": "foo",
"bar": "bar",
}
var (
lastResponseBody string
changedTimes int
handler = error_page.New(&cfg, logger.NewNop())
)
for range 300 {
var (
req = httptest.NewRequest(http.MethodGet, "/", http.NoBody)
rr = httptest.NewRecorder()
)
req.Header.Set("Accept", "text/html")
handler.ServeHTTP(rr, req)
if lastResponseBody != rr.Body.String() {
changedTimes++
lastResponseBody = rr.Body.String()
}
}
assert.True(t, changedTimes > 30, "the template should be changed at least 30 times")
}

View File

@ -0,0 +1,44 @@
package logreq_test
import (
"bytes"
"net/http"
"net/http/httptest"
"testing"
"github.com/stretchr/testify/assert"
"gh.tarampamp.am/error-pages/internal/http/middleware/logreq"
"gh.tarampamp.am/error-pages/internal/logger"
)
func TestNew(t *testing.T) {
t.Parallel()
var (
buf bytes.Buffer
log, _ = logger.New(logger.DebugLevel, logger.JSONFormat, &buf)
mw = logreq.New(log, nil)
rr = httptest.NewRecorder()
req = httptest.NewRequest(http.MethodPut, "/foo/bar", http.NoBody)
)
req.Header.Set("User-Agent", "test")
req.Header.Set("Referer", "https://example.com")
req.Header.Set("Content-Type", "application/json")
mw(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(http.StatusOK)
})).ServeHTTP(rr, req)
var logRecord = buf.String()
assert.Contains(t, logRecord, `"level":"info"`)
assert.Contains(t, logRecord, `"msg":"HTTP request processed"`)
assert.Contains(t, logRecord, `"useragent":"test"`)
assert.Contains(t, logRecord, `"method":"PUT"`)
assert.Contains(t, logRecord, `"url":"/foo/bar"`)
assert.Contains(t, logRecord, `"referer":"https://example.com"`)
assert.Contains(t, logRecord, `application/json`)
}

File diff suppressed because it is too large Load Diff

Before

Width:  |  Height:  |  Size: 169 KiB

View File

@ -1,108 +0,0 @@
{
"$schema": "http://json-schema.org/draft-07/schema#",
"title": "Error-Pages config file schema",
"description": "Error-Pages config file schema.",
"type": "object",
"properties": {
"templates": {
"type": "array",
"description": "Templates list",
"items": {
"type": "object",
"description": "Template properties",
"properties": {
"path": {
"type": "string",
"description": "Path to the template file",
"examples": [
"./templates/ghost.html",
"/opt/tpl/ghost.htm"
]
},
"name": {
"type": "string",
"description": "Template name (optional, if path is defined)",
"examples": [
"ghost"
]
},
"content": {
"type": "string",
"description": "Template content, if path is not defined",
"examples": [
"<html><body>{{ code }}: {{ message }}</body></html>"
]
}
},
"additionalProperties": false
}
},
"formats": {
"type": "object",
"description": "Responses, based on requested content-type format",
"properties": {
"json": {
"type": "object",
"description": "JSON format",
"properties": {
"content": {
"type": "string",
"description": "JSON response body (template tags are allowed here)",
"examples": [
"{\"error\": true, \"code\": {{ code | json }}, \"message\": {{ message | json }}}"
]
}
},
"additionalProperties": false
},
"xml": {
"type": "object",
"description": "XML format",
"properties": {
"content": {
"type": "string",
"description": "XML response body (template tags are allowed here)",
"examples": [
"<?xml version=\"1.0\" encoding=\"utf-8\"?><error><code>{{ code }}</code><message>{{ message }}</message></error>"
]
}
},
"additionalProperties": false
}
},
"additionalProperties": false
},
"pages": {
"type": "object",
"description": "Error pages (codes)",
"patternProperties": {
"^[a-zA-Z0-9_-]+$": {
"type": "object",
"description": "Error page (code)",
"properties": {
"message": {
"type": "string",
"description": "Error page message (title)",
"examples": [
"Bad Request"
]
},
"description": {
"type": "string",
"description": "Error page description",
"examples": [
"The server did not understand the request"
]
}
},
"additionalProperties": false
}
},
"additionalProperties": false
}
},
"additionalProperties": false,
"required": [
"templates"
]
}

View File

@ -1,13 +0,0 @@
# Config file schemas
These schemas describe Error Pages configuration file and used by:
- <https://github.com/SchemaStore/schemastore>
Schemas naming agreement: `<version_major>.<version_minor>.schema.json`.
## Contributing guide
If you want to modify the existing schema - your changes **MUST** be backward compatible. If your changes break backward compatibility - you **MUST** create a new schema file with a fresh version and "register" it in a [schemas catalog][schemas_catalog].
[schemas_catalog]:https://github.com/SchemaStore/schemastore/blob/master/src/api/json/catalog.json

View File

@ -1,15 +0,0 @@
# Schemas
This directory contains public schemas for the most important parts of application.
**Do not rename or remove this directory or any file or directory inside.**
- You can validate existing config file using the following command:
```bash
$ docker run --rm -v "$(pwd):/src" -w "/src" node:16-alpine sh -c \
"npm install -g ajv-cli && \
ajv validate --all-errors --verbose \
-s ./schemas/config/1.0.schema.json \
-d ./error-pages.y*ml"
```