commit bff6023b1a91bcf26dffa697858a96b2e3d295ca Author: Christopher Crone Date: Thu May 28 13:29:02 2020 +0200 Initial commit diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 0000000..36f971e --- /dev/null +++ b/.dockerignore @@ -0,0 +1 @@ +bin/* diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml new file mode 100644 index 0000000..321e765 --- /dev/null +++ b/.github/workflows/ci.yaml @@ -0,0 +1,21 @@ +name: Continuous Integration + +on: [push] + +jobs: + ci: + name: CI + runs-on: ubuntu-latest + env: + DOCKER_BUILDKIT: "1" + steps: + - name: Checkout code + uses: actions/checkout@v2 + - name: Run linter + run: make lint + - name: Run unit tests + run: make unit-test + - name: Build Linux binary + run: make PLATFORM=linux/amd64 + - name: Build Windows binary + run: make PLATFORM=windows/amd64 diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..e660fd9 --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +bin/ diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..c22710f --- /dev/null +++ b/Dockerfile @@ -0,0 +1,40 @@ +# syntax = docker/dockerfile:1-experimental + +FROM --platform=${BUILDPLATFORM} golang:1.14.3-alpine AS base +WORKDIR /src +ENV CGO_ENABLED=0 +COPY go.* . +RUN go mod download + +FROM base AS build +ARG TARGETOS +ARG TARGETARCH +RUN --mount=target=. \ + --mount=type=cache,target=/root/.cache/go-build \ + GOOS=${TARGETOS} GOARCH=${TARGETARCH} go build -o /out/example . + +FROM base AS unit-test +RUN --mount=target=. \ + --mount=type=cache,target=/root/.cache/go-build \ + go test -v . + +FROM golangci/golangci-lint:v1.27-alpine AS lint-base + +FROM base AS lint +RUN --mount=target=. \ + --mount=from=lint-base,src=/usr/bin/golangci-lint,target=/usr/bin/golangci-lint \ + --mount=type=cache,target=/root/.cache/go-build \ + --mount=type=cache,target=/root/.cache/golangci-lint \ + golangci-lint run --timeout 10m0s ./... + +FROM scratch AS bin-unix +COPY --from=build /out/example / + +FROM bin-unix AS bin-linux +FROM bin-unix AS bin-darwin + +FROM scratch AS bin-windows +COPY --from=build /out/example /example.exe + +FROM bin-${TARGETOS} as bin + diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..fdddb29 --- /dev/null +++ b/LICENSE @@ -0,0 +1,24 @@ +This is free and unencumbered software released into the public domain. + +Anyone is free to copy, modify, publish, use, compile, sell, or +distribute this software, either in source code form or as a compiled +binary, for any purpose, commercial or non-commercial, and by any +means. + +In jurisdictions that recognize copyright laws, the author or authors +of this software dedicate any and all copyright interest in the +software to the public domain. We make this dedication for the benefit +of the public at large and to the detriment of our heirs and +successors. We intend this dedication to be an overt act of +relinquishment in perpetuity of all present and future rights to this +software under copyright law. + +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 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. + +For more information, please refer to diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..9ea71d8 --- /dev/null +++ b/Makefile @@ -0,0 +1,18 @@ +all: bin/example +test: lint unit-test + +PLATFORM=local + +.PHONY: bin/example +bin/example: + @docker build . --target bin \ + --output bin/ \ + --platform ${PLATFORM} + +.PHONY: unit-test +unit-test: + @docker build . --target unit-test + +.PHONY: lint +lint: + @docker build . --target lint diff --git a/README.md b/README.md new file mode 100644 index 0000000..fba1879 --- /dev/null +++ b/README.md @@ -0,0 +1,106 @@ +![Continuous Integration](https://github.com/chris-crone/containerized-go-dev/workflows/Continuous%20Integration/badge.svg) + +Example Containerized Go Development Environment +------------------------------------------------ + +This repository contains an example Go project with a containerized development +environment. The example project is a simple CLI tool that echos back its +inputs. + +## Why should I containerize my development environment? + +There are several advantages to containerizing your development environment: +* You make explicit the tools and versions of tools required to develop your + project +* Your builds will be more deterministic and reproducible + +These will both make it easier for people to collaborate on your project, as +everyone will have the same environment, and make it easier to debug things like +CI failures. + +## Prerequisites + +The only requirements to build and use this project are Docker and `make`. The +latter can easily be substituted with your scripting tool of choice. + +You will also need to enable the BuildKit builder in the Docker CLI. This can be +done by setting `DOCKER_BUILDKIT=1` in your environment. + +### macOS + +* Install [Docker Desktop](https://www.docker.com/products/docker-desktop) +* Ensure that you have `make` (included with Xcode) +* Run `export DOCKER_BUILDKIT=1` in your terminal or add to your shell + initialization scripts + +### Windows + +* Install [Docker Desktop](https://www.docker.com/products/docker-desktop) +* Ensure that you have `make` +* If using PowerShell, run `$env:DOCKER_BUILDKIT=1` +* If using command prompt, run `set DOCKER_BUILDKIT=1` + +### Linux + +* Install [Docker](https://docs.docker.com/engine/install/) +* Ensure that you have `make` +* Run `export DOCKER_BUILDKIT=1` in your terminal or add to your shell + initialization scripts + +## Getting started + +Building the project will output a static binary in the bin/ folder. The +default platform is for macOS but this can be changed using the `PLATFORM` variable: +```console +$ make # build for your host OS +$ make PLATFORM=darwin/amd64 # build for macOS +$ make PLATFORM=windows/amd64 # build for Windows x86_64 +$ make PLATFORM=linux/amd64 # build for Linux x86_64 +$ make PLATFORM=linux/arm # build for Linux ARM +``` + +You can then run the binary, which is a simple echo binary, as follows: +```console +$ ./bin/example hello world! +hello world! +``` + +To run the unit tests run: +```console +$ make unit-test +``` + +To run the linter: +```console +$ make lint +``` + +There's then a helpful `test` alias for running both the linter and the unit +tests: +```console +$ make test +``` + +## Structure of project + +### Dockerfile + +The [Dockerfile](./Dockerfile) codifies all the tools needed for the project +and the commands that need to be run for building and testing it. + +### Makefile + +The [Makefile](./Makefile) is purely used to script the required `docker build` +commands as these can get quite long. You can replace this file with a scripting +language of your choice. + +### CI + +The CI is configured in the [ci.yaml file](./github/workflows/ci.yaml). By +containerizing the toolchain, the CI relies on the toolchain we defined in the +Dockerfile and doesn't require any custom setup. + +## Read more + +* [Docker build reference documentation](https://docs.docker.com/engine/reference/commandline/build/) +* [Experimental Dockerfile syntax](https://github.com/moby/buildkit/blob/master/frontend/dockerfile/docs/experimental.md) diff --git a/bin/.placeholder b/bin/.placeholder new file mode 100644 index 0000000..e69de29 diff --git a/go.mod b/go.mod new file mode 100644 index 0000000..4c05c79 --- /dev/null +++ b/go.mod @@ -0,0 +1,8 @@ +module github.com/chris-crone/containerized-go + +go 1.14 + +require ( + github.com/pkg/errors v0.9.1 + github.com/stretchr/testify v1.5.1 +) diff --git a/go.sum b/go.sum new file mode 100644 index 0000000..4516e6a --- /dev/null +++ b/go.sum @@ -0,0 +1,13 @@ +github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8= +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= +github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +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/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/testify v1.5.1 h1:nOGnQDM7FYENwehXlg/kFVnos3rEvtKTjRvOWSzb6H4= +github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/yaml.v2 v2.2.2 h1:ZCJp+EgiOT7lHqUV2J862kp8Qj64Jo6az82+3Td9dZw= +gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= diff --git a/main.go b/main.go new file mode 100644 index 0000000..47211e4 --- /dev/null +++ b/main.go @@ -0,0 +1,24 @@ +package main + +import ( + "fmt" + "os" + "strings" + + "github.com/pkg/errors" +) + +func echo(args []string) error { + if len(args) < 2 { + return errors.New("no message to echo") + } + _, err := fmt.Println(strings.Join(args[1:], " ")) + return err +} + +func main() { + if err := echo(os.Args); err != nil { + fmt.Fprintf(os.Stderr, "%+v\n", err) + os.Exit(1) + } +} diff --git a/main_test.go b/main_test.go new file mode 100644 index 0000000..1671c5f --- /dev/null +++ b/main_test.go @@ -0,0 +1,19 @@ +package main + +import ( + "testing" + + "github.com/stretchr/testify/require" +) + +func TestEcho(t *testing.T) { + // Test happy path + err := echo([]string{"bin-name", "hello", "world!"}) + require.NoError(t, err) +} + +func TestEchoErrorNoArgs(t *testing.T) { + // Test empty arguments + err := echo([]string{}) + require.Error(t, err) +}