Skip to content

Commit

Permalink
Introduce mix build task to update/replace an old...
Browse files Browse the repository at this point in the history
...the release.sh script.

We have added a few more checks: repo is clean, tests are passing. We
prompt user with a few reminders (instead of simple "warning"-like text
output). Furthermore, we re-wrote the `build.sh` and made it cleaner (we
hope so, at least).
  • Loading branch information
cr0t committed Nov 30, 2024
1 parent 873d9ca commit e46c2e0
Show file tree
Hide file tree
Showing 4 changed files with 126 additions and 86 deletions.
7 changes: 4 additions & 3 deletions .docker/Dockerfile.build
Original file line number Diff line number Diff line change
Expand Up @@ -20,9 +20,10 @@
# * exec: docker container exec -it lexin sh
# * logs: docker container logs --follow --tail 100 lexin

ARG ELIXIR_VERSION
ARG ERLANG_VERSION
ARG ALPINE_VERSION
# these defaults shall be overwritten by the --build-arg params that are taken from the .env file
ARG ELIXIR_VERSION=1.17.3
ARG ERLANG_VERSION=27.1.2
ARG ALPINE_VERSION=3.20.3

ARG BUILDER_IMAGE="hexpm/elixir:${ELIXIR_VERSION}-erlang-${ERLANG_VERSION}-alpine-${ALPINE_VERSION}"
ARG RUNNER_IMAGE="alpine:${ALPINE_VERSION}"
Expand Down
29 changes: 26 additions & 3 deletions mix.exs
Original file line number Diff line number Diff line change
@@ -1,10 +1,13 @@
defmodule Lexin.MixProject do
use Mix.Project

@app :lexin
@version "0.17.7"

def project do
[
app: :lexin,
version: "0.17.7",
app: @app,
version: @version,
elixir: "~> 1.16",
elixirc_paths: elixirc_paths(Mix.env()),
start_permanent: Mix.env() == :prod,
Expand Down Expand Up @@ -73,7 +76,27 @@ defmodule Lexin.MixProject do
"gettext.update": ["gettext.extract --merge --no-fuzzy"],
test: ["esbuild default", "test"],
"sitemap.gen": ["run --no-start scripts/sitemap_generator.exs"],
"sitemap.check": ["run --no-start scripts/sitemap_tester.exs"]
"sitemap.check": ["run --no-start scripts/sitemap_tester.exs"],
build: ["build.check", &build_confirmed?/1, "build.run"],
"build.check": [
"cmd git status --porcelain | grep . && echo \"'error: Working directory is dirty'\" && exit 1 || exit 0",
"cmd mix test --warnings-as-errors"
],
"build.run": "cmd ./scripts/build.sh ghcr.io/cr0t/#{@app}:#{@version}"
]
end

defp build_confirmed?(_) do
with true <- Mix.shell().yes?("Have you generated sitemaps and put them to the server?"),
true <- Mix.shell().yes?("Have you ran `gzip -k *` in the sitemaps directory?"),
true <- Mix.shell().yes?("Have you bumped Lexin's version?") do
:ok
else
_ ->
IO.puts("error: Cancelling build...")

System.halt(1)
Process.sleep(:infinity)
end
end
end
155 changes: 96 additions & 59 deletions scripts/build.sh
Original file line number Diff line number Diff line change
@@ -1,71 +1,108 @@
#!/bin/bash
#
# Defines Docker build process for production ready image of our Elixir app.
#
# We want to define Elixir/Erlang versions in minimum places to improve maintainability,
# so we use `.env` file and parse it to convert to build arguments list.
#
# Example:
#
# $ ./scripts/build.sh lexin 0.3.0
# $ ./scripts/build.sh lexin 0.3.0 --force
# $ ./scripts/build.sh lexin 0.3.0 --force --no-cache
#
# Note: `--no-cache` should always be used only with `--force` going first; `--no-cache` tells
# Docker to skip local cache.
#!/usr/bin/env bash

if ! docker version > /dev/null 2>&1; then
echo "Docker is not running. We cannot build release without Docker!"
# Strict mode for better error handling
set -euo pipefail

usage() {
echo "Usage: $0 IMAGE_TAG [OPTIONS]"
echo ""
echo "Build and push a Docker image for an Elixir application"
echo ""
echo "Arguments:"
echo ""
echo " IMAGE_TAG Full image tag (e.g., ghcr.io/cr0t/lexin:0.3.0)"
echo ""
echo "Options:"
echo ""
echo " --force Force rebuild, removing existing image"
echo " --no-cache Disable Docker build cache"
echo ""
echo "Examples:"
echo ""
echo " $0 ghcr.io/cr0t/lexin:0.3.0"
echo " $0 ghcr.io/cr0t/lexin:0.3.0 --force"
echo " $0 ghcr.io/cr0t/lexin:0.3.0 --force --no-cache"
exit 1
fi
}

# We expect at least two arguments: application name and version number
if [[ $# -lt 2 ]]; then
echo "Please, provide application name and version number, for example:"
echo "$0 my_app 1.1.0"
exit 2
fi
check_docker() {
if ! command -v docker &> /dev/null; then
echo "Error: Docker is not installed." >&2
exit 1
fi

# But in addition we can also accept --force and --no-cache as third and fourth arguments
if [ -n "$3" ] && [[ $3 == "--force" ]]; then
FORCE_FLAG=true
else
FORCE_FLAG=false
fi
if ! docker info &> /dev/null; then
echo "Error: Docker daemon is not running." >&2
exit 1
fi
}

if [ -n "$4" ] && [[ $4 == "--no-cache" ]]; then
NO_CACHE_FLAG=true
else
parse_arguments() {
FORCE_FLAG=false
NO_CACHE_FLAG=false
fi

# Some information we will need to name and tag the image we're building
APP_NAME=$1
APP_VERSION=$2
IMAGE_NAME="ghcr.io/cr0t/$APP_NAME:$APP_VERSION"
EXISTING_IMAGE_ID=$(docker images -q $IMAGE_NAME 2> /dev/null)
# Validate at least one argument
[[ $# -lt 1 ]] && usage

# Parse arguments
while [[ $# -gt 0 ]]; do
case "$1" in
--force)
FORCE_FLAG=true
shift
;;
--no-cache)
NO_CACHE_FLAG=true
shift
;;
-*)
echo "Error: Unknown option $1" >&2
usage
;;
*)
if [[ -z "${IMAGE_NAME:-}" ]]; then
IMAGE_NAME="$1"
shift
else
echo "Error: Too many arguments" >&2
usage
fi
;;
esac
done
}

build_docker_image() {
# Check if image exists and handle force rebuild
local existing_image_id
existing_image_id=$(docker images -q "$IMAGE_NAME" 2> /dev/null || true)

if [[ -n "$existing_image_id" ]]; then
if [[ "$FORCE_FLAG" == "true" ]]; then
echo "Removing existing image $IMAGE_NAME"
docker image rm "$existing_image_id"
else
echo "Image $IMAGE_NAME already exists. Use --force to rebuild."
exit 3
fi
fi

readonly BUILD_PLATFORM="linux/amd64"
readonly BUILD_ENV_ARGS=$(awk '!/^($|#)/ {printf "--build-arg %s ", $0}' .env)

if [ -n "$EXISTING_IMAGE_ID" ] && ! $FORCE_FLAG; then
echo "The image $IMAGE_NAME already exists: $EXISTING_IMAGE_ID!"
echo "You can use --force to delete and rebuild it forcefully, or bump the version in mix.exs."
echo "Note: add --no-cache to tell Docker skip local cache."
exit 3
fi
local build_flags="$BUILD_ENV_ARGS --platform $BUILD_PLATFORM --file .docker/Dockerfile.build"

BUILD_PLATFORM="linux/amd64"
BUILD_ENV_ARGS=$(for i in `cat .env | grep -v '#'`; do out+="--build-arg $i "; done; echo $out; out="")
BUILD_FLAGS="$BUILD_ENV_ARGS --platform $BUILD_PLATFORM --file .docker/Dockerfile.build "
[[ "$NO_CACHE_FLAG" == "true" ]] && build_flags="${build_flags} --no-cache"

if $FORCE_FLAG && [ -n "$EXISTING_IMAGE_ID" ]; then
docker image rm $EXISTING_IMAGE_ID
fi
# Execute build
docker build --debug $build_flags --tag "$IMAGE_NAME" .
}

# NOTE: add `--progress plain` to simplify debugging
if $NO_CACHE_FLAG; then
docker build $BUILD_FLAGS --no-cache --tag $IMAGE_NAME .
else
docker build $BUILD_FLAGS --tag $IMAGE_NAME .
fi
main() {
check_docker
parse_arguments "$@"
build_docker_image
# docker push "$IMAGE_NAME"
}

# push it to GitHub
docker push $IMAGE_NAME
main "$@"
21 changes: 0 additions & 21 deletions scripts/release.sh

This file was deleted.

0 comments on commit e46c2e0

Please sign in to comment.