diff --git a/Dockerfile b/Dockerfile index c6bb37d..fc3de5d 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,8 +1,8 @@ # Use a golang alpine as the base image -FROM public.ecr.aws/docker/library/golang:1.23.3-alpine3.20 as go_builder +FROM public.ecr.aws/docker/library/golang:1.25.1-alpine3.22 AS go_builder RUN apk update RUN apk add make cmake git alpine-sdk - +ENV CGO_ENABLED=1 GOOS=linux # Setup # Read arguments @@ -11,59 +11,39 @@ ARG VERSION_TAG ARG GIT_COMMIT ARG COMMIT_DATE ARG BUILD_DATE - +ARG DIRTY # Set env variables ENV COMMIT_DATE=$COMMIT_DATE ENV SERVICE=$SERVICE ENV GIT_COMMIT=$GIT_COMMIT ENV VERSION_TAG=$VERSION_TAG ENV BUILD_DATE=$BUILD_DATE +ENV DIRTY=$DIRTY RUN echo "building service: ${SERVICE}" RUN echo "version: ${VERSION_TAG}" RUN echo "git commit: ${GIT_COMMIT}" RUN echo "commit date: ${COMMIT_DATE}" RUN echo "compilation date: ${BUILD_DATE}" +RUN echo "dirty build: ${DIRTY}" # Set the working directory WORKDIR / COPY . . # Build -RUN make gobuild - -# Create linux svcuser -RUN mkdir /build/etc && \ - echo "svcuser:x:1010:1010::/sbin/nologin:/bin/false" > /build/etc/passwd && \ - echo "macuser:x:501:20::/sbin/nologin:/bin/false" >> /build/etc/passwd && \ - echo "linuxuser:x:1000:1000::/sbin/nologin:/bin/false" >> /build/etc/passwd && \ - echo "root:x:0:0:root:/sbin/nologin:/bin/false" >> /build/etc/passwd && \ - echo "svcgroup:x:1010:svcuser" > /build/etc/group && \ - echo "macgroup:x:20:macuser" >> /build/etc/group && \ - echo "linuxgroup:x:1000:linuxuser" >> /build/etc/group && \ - echo "root:x:0:root" >> /build/etc/group && \ - mkdir /build/config && \ - chown -R 1010:1010 /build/config - +RUN make build ############################################################################################################ -#SSL certs -FROM alpine as certs -RUN apk add --no-cache ca-certificates - -############################################################################################################ - - -# Copy binary to a scratch container. Let's keep our images nice and small! -FROM scratch -COPY --from=go_builder /build . -COPY --from=certs /etc/ssl/certs/ca-certificates.crt /etc/ssl/certs/ +# Copy binary to a fresh alpine container. Let's keep our images nice and small! +FROM alpine:3.22 +RUN adduser -D -H -u 1010 svcuser && apk add --no-cache ca-certificates +COPY --from=go_builder /build/checkout /checkout COPY LICENSE ./ # Set User USER svcuser -# Expose the port your application will run on -EXPOSE 8000 - +# Expose the default application port +EXPOSE 8080 # Run the binary ENTRYPOINT ["/checkout"] diff --git a/Makefile b/Makefile index 8852b36..bbc5847 100644 --- a/Makefile +++ b/Makefile @@ -2,21 +2,34 @@ # Build folder BUILD_FOLDER = build +COVERAGE_BUILD_FOLDER ?= $(BUILD_FOLDER)/coverage +UNIT_COVERAGE_OUT ?= $(COVERAGE_BUILD_FOLDER)/ut_cov.out +BIN ?= $(BUILD_FOLDER)/checkout + +# Packages +PKG ?= github.com/ATMackay/checkout +CONSTANTS_PKG ?= $(PKG)/constants -# Test coverage variables -COVERAGE_BUILD_FOLDER = $(BUILD_FOLDER)/coverage -UNIT_COVERAGE_OUT = $(COVERAGE_BUILD_FOLDER)/ut_cov.out # Git based version -VERSION_TAG ?= $(shell git describe --tags) -GIT_COMMIT ?= $(shell git rev-parse HEAD) -BUILD_DATE ?= $(shell date -u +'%Y-%m-%d %H:%M:%S') -COMMIT_DATE ?= $(shell git show -s --format="%ci" $(shell git rev-parse HEAD)) +VERSION_TAG ?= $(shell git describe --tags) +GIT_COMMIT ?= $(shell git rev-parse HEAD) +BUILD_DATE ?= $(shell date -u +'%Y-%m-%d %H:%M:%S') +COMMIT_DATE ?= $(shell git show -s --format="%ci" $(shell git rev-parse HEAD)) +DIRTY ?= false + +LDFLAGS := -s -w \ + -X '$(CONSTANTS_PKG).Version=$(VERSION_TAG)' \ + -X '$(CONSTANTS_PKG).CommitDate=$(COMMIT_DATE)' \ + -X '$(CONSTANTS_PKG).GitCommit=$(GIT_COMMIT)' \ + -X '$(CONSTANTS_PKG).BuildDate=$(BUILD_DATE)' \ + -X '$(CONSTANTS_PKG).Dirty=$(DIRTY)' build: - @GO111MODULE=on go build -o $(BUILD_FOLDER)/checkout \ - -ldflags=" -X 'github.com/ATMackay/checkout/constants.Version=$(VERSION_TAG)' -X 'github.com/ATMackay/checkout/constants.CommitDate=$(COMMIT_DATE)' -X 'github.com/ATMackay/checkout/constants.BuildDate=$(BUILD_DATE)' -X 'github.com/ATMackay/checkout/constants.GitCommit=$(GIT_COMMIT)'" - @echo "Checkout server successfully built. To run the application execute './$(BUILD_FOLDER)/checkout run'" + @mkdir -p build + @echo ">> building $(BIN) (version=$(VERSION_TAG) commit=$(GIT_COMMIT) dirty=$(DIRTY))" + GO111MODULE=on go build -ldflags "$(LDFLAGS)" -o $(BIN) + @echo "Checkout server successfully built. To run the application execute './$(BIN) run'" run: build @./$(BUILD_FOLDER)/checkout run --memory-db diff --git a/build-docker.sh b/build-docker.sh index c35dbfd..d1ec336 100755 --- a/build-docker.sh +++ b/build-docker.sh @@ -1,22 +1,64 @@ #!/usr/bin/env bash -set -e - -commit_hash=$(git rev-parse HEAD) -commit_hash_short=$(git rev-parse --short HEAD) -commit_timestamp=$(git show -s --format="%ci" ${commit_hash}) -version_tag=$(git describe --tags) -build_date=$(date -u +'%Y-%m-%d %H:%M:%S') - -docker build \ - --build-arg SERVICE=checkout \ - --build-arg GIT_COMMIT="$commit_hash" \ - --build-arg COMMIT_DATE="$commit_timestamp" \ - --build-arg VERSION_TAG="$version_tag" \ - --build-arg BUILD_DATE="$build_date" \ - -t checkout:latest \ - -t checkout:"$commit_hash_short" \ - -f Dockerfile . +#!/usr/bin/env bash +set -Eeuo pipefail + +IMAGE_NAME=${IMAGE_NAME:-checkout} +SERVICE=${SERVICE:-checkout} + +# Git info +commit_hash="$(git rev-parse HEAD)" +commit_hash_short="$(git rev-parse --short=12 HEAD)" +commit_timestamp="$(git show -s --format=%cI "${commit_hash}")" +build_date="$(date -u +%Y-%m-%dT%H:%M:%SZ)" + +# Base tag from Git +base_tag="$(git describe --tags --abbrev=0 --match 'v*' 2>/dev/null)" + +# Dirty detection: non-empty output -> dirty +if [[ -n "$(git status --porcelain)" ]]; then + dirty="true" + version_tag="${base_tag}-dirty-${commit_hash_short}-$(date -u +%Y%m%d%H%M)" + tags=( "${IMAGE_NAME}:${version_tag}" ) # no :latest for dirty images +else + dirty="false" + version_tag="${base_tag}" + tags=( "${IMAGE_NAME}:${version_tag}" "${IMAGE_NAME}:${commit_hash_short}" "${IMAGE_NAME}:latest" ) +fi + +echo ">> version_tag=${version_tag}" +echo ">> commit=${commit_hash}" +echo ">> dirty=${dirty}" +echo ">> build_date=${build_date}" + +# Build args (propagated into your Makefile's -ldflags via Dockerfile) +build_args="\ + --build-arg SERVICE=${SERVICE} \ + --build-arg GIT_COMMIT=${commit_hash} \ + --build-arg COMMIT_DATE=${commit_timestamp} \ + --build-arg VERSION_TAG=${version_tag} \ + --build-arg BUILD_DATE=${build_date} \ + --build-arg DIRTY=${dirty} \ +" + +# Primary tag = first in the list +set -- $tags +primary_tag="$1" + +# Build image +echo ">> docker build -t $primary_tag $build_args -f Dockerfile ." +docker build -t "$primary_tag" $build_args -f Dockerfile . + +# Apply the remaining tags +shift +for t in "$@"; do + docker tag "$primary_tag" "$t" +done + +echo ">> Built tags:" +for t in $tags; do + echo " $t" +done # Remove intermediate Docker layers docker image prune -f \ No newline at end of file diff --git a/cmd/cmd_test.go b/cmd/cmd_test.go index ed96832..7183c32 100644 --- a/cmd/cmd_test.go +++ b/cmd/cmd_test.go @@ -10,3 +10,7 @@ func Test_CheckoutCMD(t *testing.T) { c := NewCheckoutCmd() require.Len(t, c.Commands(), 2) } + +func Test_BuildDirty(t *testing.T) { + require.False(t, buildDirty()) +} diff --git a/cmd/logging_test.go b/cmd/logging_test.go index 7a1706b..9827926 100644 --- a/cmd/logging_test.go +++ b/cmd/logging_test.go @@ -23,8 +23,7 @@ func Test_Logger(t *testing.T) { for _, format := range format { for _, tc := range tests { t.Run(tc.name+format, func(t *testing.T) { - err := initLogging(tc.levelStr, format) - if (err != nil) != tc.expectErr { + if err := initLogging(tc.levelStr, format); (err != nil) != tc.expectErr { t.Errorf("unexpected error %v", err) } }) diff --git a/cmd/run.go b/cmd/run.go index 659b005..91a21f4 100644 --- a/cmd/run.go +++ b/cmd/run.go @@ -6,6 +6,7 @@ import ( "os" "os/signal" "path/filepath" + "strings" "github.com/ATMackay/checkout/constants" "github.com/ATMackay/checkout/database" @@ -67,6 +68,10 @@ func NewRunCmd() *cobra.Command { "commit", constants.GitCommit, "version", constants.Version, ) + if buildDirty() { + // Warn if the build contains uncommitted changes + slog.Warn("running a DIRTY build (uncommitted changes present) — do not run in production") + } svc.Start() sigChan := make(chan os.Signal, 1) @@ -127,3 +132,5 @@ func NewRunCmd() *cobra.Command { viper.AutomaticEnv() // Automatically read environment variables return cmd } + +func buildDirty() bool { return strings.EqualFold(constants.Dirty, "true") } diff --git a/cmd/version.go b/cmd/version.go index d683c88..952fd36 100644 --- a/cmd/version.go +++ b/cmd/version.go @@ -16,6 +16,9 @@ func VersionCmd() *cobra.Command { fmt.Println("git commit sha:", constants.GitCommit) fmt.Println("commit timestamp:", constants.CommitDate) fmt.Println("compilation date:", constants.BuildDate) + if buildDirty() { + fmt.Println("git tree DIRTY (uncommitted changes in build).") + } return nil }, } diff --git a/constants/version.go b/constants/version.go index 56bfbb8..08e32d4 100644 --- a/constants/version.go +++ b/constants/version.go @@ -8,4 +8,5 @@ var ( CommitDate = "" // overwritten by -ldflag "-X 'github.com/ATMackay/checkout/constants.CommitDate=$commit_date'" GitCommit = "" // overwritten by -ldflag "-X 'github.com/ATMackay/checkout/constants.GitCommit=$commit_hash'" BuildDate = "" // overwritten by -ldflag "-X 'github.com/ATMackay/checkout/constants.BuildDate=$build_date'" + Dirty = "false" // overwritten by -ldflag "-X 'github.com/ATMackay/checkout/constants.Dirty=$dirty'" )