feat: initialize golang project template with docker and ci/cd
This commit is contained in:
9
.dockerignore
Normal file
9
.dockerignore
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
.git
|
||||||
|
.github
|
||||||
|
bin/
|
||||||
|
build/
|
||||||
|
coverage.out
|
||||||
|
README.md
|
||||||
|
Makefile
|
||||||
|
var/
|
||||||
|
tmp/
|
||||||
62
.github/workflows/ci.yml
vendored
Normal file
62
.github/workflows/ci.yml
vendored
Normal file
@@ -0,0 +1,62 @@
|
|||||||
|
name: CI
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
branches: [ "main" ]
|
||||||
|
pull_request:
|
||||||
|
branches: [ "main" ]
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
lint:
|
||||||
|
name: Lint
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v4
|
||||||
|
- uses: actions/setup-go@v5
|
||||||
|
with:
|
||||||
|
go-version: '1.23'
|
||||||
|
cache: false
|
||||||
|
- name: golangci-lint
|
||||||
|
uses: golangci/golangci-lint-action@v6
|
||||||
|
with:
|
||||||
|
version: latest
|
||||||
|
|
||||||
|
test:
|
||||||
|
name: Test
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v4
|
||||||
|
- uses: actions/setup-go@v5
|
||||||
|
with:
|
||||||
|
go-version: '1.23'
|
||||||
|
- name: Test
|
||||||
|
run: go test -v -race -cover ./...
|
||||||
|
|
||||||
|
build:
|
||||||
|
name: Build
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
needs: [lint, test]
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v4
|
||||||
|
- uses: actions/setup-go@v5
|
||||||
|
with:
|
||||||
|
go-version: '1.23'
|
||||||
|
- name: Build
|
||||||
|
run: go build -v ./cmd/app
|
||||||
|
|
||||||
|
docker-build:
|
||||||
|
name: Docker Build
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
needs: [build]
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v4
|
||||||
|
- name: Set up Docker Buildx
|
||||||
|
uses: docker/setup-buildx-action@v3
|
||||||
|
- name: Build
|
||||||
|
uses: docker/build-push-action@v5
|
||||||
|
with:
|
||||||
|
context: .
|
||||||
|
push: false
|
||||||
|
tags: app:latest
|
||||||
|
cache-from: type=gha
|
||||||
|
cache-to: type=gha,mode=max
|
||||||
57
.github/workflows/release.yml
vendored
Normal file
57
.github/workflows/release.yml
vendored
Normal file
@@ -0,0 +1,57 @@
|
|||||||
|
name: Release
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
tags:
|
||||||
|
- '*'
|
||||||
|
|
||||||
|
permissions:
|
||||||
|
contents: write
|
||||||
|
packages: write
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
goreleaser:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v4
|
||||||
|
with:
|
||||||
|
fetch-depth: 0
|
||||||
|
- uses: actions/setup-go@v5
|
||||||
|
with:
|
||||||
|
go-version: '1.23'
|
||||||
|
- name: Run GoReleaser
|
||||||
|
uses: goreleaser/goreleaser-action@v5
|
||||||
|
with:
|
||||||
|
distribution: goreleaser
|
||||||
|
version: latest
|
||||||
|
args: release --clean
|
||||||
|
env:
|
||||||
|
GITEA_TOKEN: ${{ secrets.ACCESS_TOKEN_GITEA }}
|
||||||
|
|
||||||
|
docker-release:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
needs: goreleaser
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v4
|
||||||
|
- name: Set up Docker Buildx
|
||||||
|
uses: docker/setup-buildx-action@v3
|
||||||
|
- name: Login to GitHub Container Registry
|
||||||
|
uses: docker/login-action@v3
|
||||||
|
with:
|
||||||
|
registry: git.dcentral.systems
|
||||||
|
username: master
|
||||||
|
password: ${{ secrets.ACCESS_TOKEN_GITEA }}
|
||||||
|
- name: Extract metadata (tags, labels) for Docker
|
||||||
|
id: meta
|
||||||
|
uses: docker/metadata-action@v5
|
||||||
|
with:
|
||||||
|
images: git.dcentral.systems/templates/golang
|
||||||
|
- name: Build and push Docker image
|
||||||
|
uses: docker/build-push-action@v5
|
||||||
|
with:
|
||||||
|
context: .
|
||||||
|
push: true
|
||||||
|
tags: ${{ steps.meta.outputs.tags }}
|
||||||
|
labels: ${{ steps.meta.outputs.labels }}
|
||||||
|
cache-from: type=gha
|
||||||
|
cache-to: type=gha,mode=max
|
||||||
2
.gitignore
vendored
2
.gitignore
vendored
@@ -25,3 +25,5 @@ go.work.sum
|
|||||||
# env file
|
# env file
|
||||||
.env
|
.env
|
||||||
|
|
||||||
|
build/
|
||||||
|
|
||||||
|
|||||||
21
.golangci.yml
Normal file
21
.golangci.yml
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
# Options for analysis running.
|
||||||
|
version: 2
|
||||||
|
run:
|
||||||
|
timeout: 5m
|
||||||
|
|
||||||
|
linters:
|
||||||
|
enable:
|
||||||
|
- errcheck
|
||||||
|
- govet
|
||||||
|
- ineffassign
|
||||||
|
- staticcheck
|
||||||
|
- unused
|
||||||
|
- misspell
|
||||||
|
- unconvert
|
||||||
|
- unparam
|
||||||
|
|
||||||
|
linters-settings:
|
||||||
|
govet:
|
||||||
|
check-shadowing: true
|
||||||
|
gofmt:
|
||||||
|
simplify: true
|
||||||
45
AGENTS.md
Normal file
45
AGENTS.md
Normal file
@@ -0,0 +1,45 @@
|
|||||||
|
# Development Guidelines
|
||||||
|
|
||||||
|
This document outlines the best practices for Go development and Git workflow to be followed by the Gemini agent for this project.
|
||||||
|
|
||||||
|
## Go Development Guidelines
|
||||||
|
|
||||||
|
### Code Quality and Style
|
||||||
|
- **Formatting:** All Go code MUST be formatted with `gofmt` before committing.
|
||||||
|
- **Linting:** Use `golangci-lint` to enforce code quality and style.
|
||||||
|
- **Simplicity:** Write simple, clear, and concise code. Avoid unnecessary complexity.
|
||||||
|
- **Error Handling:** Handle all errors explicitly. Never ignore an error. Check for errors and return them from functions.
|
||||||
|
- **Naming:** Use meaningful and descriptive names for variables, functions, and packages. Follow Go's naming conventions (e.g., `camelCase` for private, `PascalCase` for public).
|
||||||
|
- **Comments:** Write comments to explain the *why* behind complex or non-obvious code, not the *what*. Document all public functions and packages.
|
||||||
|
- **Testing:**
|
||||||
|
- Write unit tests for all new code. Aim for high test coverage.
|
||||||
|
- Use table-driven tests for testing multiple scenarios.
|
||||||
|
- Mocks and interfaces should be used to isolate dependencies for testing.
|
||||||
|
- **Dependencies:** Use Go modules (`go mod`) to manage project dependencies. Keep the `go.mod` and `go.sum` files up-to-date.
|
||||||
|
|
||||||
|
### Best Practices
|
||||||
|
- **Concurrency:** When using goroutines, ensure proper synchronization to avoid race conditions. Use channels for communication between goroutines.
|
||||||
|
- **Interfaces:** Use interfaces to define behavior and decouple components.
|
||||||
|
- **Packages:** Structure your code into logical packages. Avoid circular dependencies.
|
||||||
|
- **Security:** Be mindful of security best practices. Sanitize inputs, avoid SQL injection, and handle credentials securely.
|
||||||
|
|
||||||
|
## Git Workflow Best Practices
|
||||||
|
|
||||||
|
### Branching
|
||||||
|
- **Main Branch:** The `main` branch is for production-ready code only. Direct commits to `main` are NOT allowed.
|
||||||
|
- **Feature Branches:** Create a new branch for every new feature or bug fix.
|
||||||
|
- Branch names should be descriptive and follow the pattern `feature/<short-description>` or `fix/<short-description>` (e.g., `feature/user-auth`, `fix/login-bug`).
|
||||||
|
- **Pull Requests (PRs):** All changes must be submitted through a Pull Request to the `main` branch.
|
||||||
|
|
||||||
|
### Committing
|
||||||
|
- **Atomic Commits:** Each commit should represent a single, logical change. Do not bundle unrelated changes into one commit.
|
||||||
|
- **Commit Messages:** Follow the [Conventional Commits](https://www.conventionalcommits.org/) specification. This helps in automating changelogs and versioning.
|
||||||
|
- **Format:** `<type>[optional scope]: <description>`
|
||||||
|
- **Example:** `feat: add user login endpoint`
|
||||||
|
- **Common types:** `feat`, `fix`, `docs`, `style`, `refactor`, `test`, `chore`.
|
||||||
|
|
||||||
|
### Pull Request (PR) Process
|
||||||
|
1. **Create a PR:** When your feature or fix is complete, create a PR against the `main` branch.
|
||||||
|
2. **CI Checks:** Ensure all automated checks (build, tests, linting) pass.
|
||||||
|
3. **Code Review:** The PR will be reviewed. Address any feedback by pushing new commits to your branch.
|
||||||
|
4. **Merge:** Once the PR is approved and all checks pass, it will be merged into `main`.
|
||||||
29
Dockerfile
Normal file
29
Dockerfile
Normal file
@@ -0,0 +1,29 @@
|
|||||||
|
# Build stage
|
||||||
|
FROM golang:1.23-alpine AS builder
|
||||||
|
|
||||||
|
WORKDIR /app
|
||||||
|
|
||||||
|
# Copy go.mod and go.sum first to leverage Docker cache
|
||||||
|
# We need to check if go.sum exists, but COPY fails if it doesn't.
|
||||||
|
# Since we just ran go mod init, go.sum might not exist yet if no deps.
|
||||||
|
# SAFE PATTERN: COPY go.mod and optional go.sum
|
||||||
|
COPY go.mod go.sum* ./
|
||||||
|
RUN go mod download
|
||||||
|
|
||||||
|
# Copy the rest of the source code
|
||||||
|
COPY . .
|
||||||
|
|
||||||
|
# Build the application
|
||||||
|
# -ldflags="-w -s" strips debug information for smaller binary
|
||||||
|
RUN CGO_ENABLED=0 GOOS=linux go build -ldflags="-w -s" -o /go/bin/app ./cmd/app
|
||||||
|
|
||||||
|
# Final stage
|
||||||
|
# Use distroless static image for security and minimal footprint
|
||||||
|
FROM gcr.io/distroless/static-debian12:nonroot
|
||||||
|
|
||||||
|
WORKDIR /
|
||||||
|
|
||||||
|
COPY --from=builder /go/bin/app /app
|
||||||
|
USER nonroot:nonroot
|
||||||
|
|
||||||
|
ENTRYPOINT ["/app"]
|
||||||
24
Makefile
Normal file
24
Makefile
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
.PHONY: build run test lint clean docker-build
|
||||||
|
|
||||||
|
APP_NAME := app
|
||||||
|
BUILD_DIR := build
|
||||||
|
CMD_PATH := ./cmd/app
|
||||||
|
|
||||||
|
build:
|
||||||
|
@mkdir -p $(BUILD_DIR)
|
||||||
|
go build -o $(BUILD_DIR)/$(APP_NAME) $(CMD_PATH)
|
||||||
|
|
||||||
|
run: build
|
||||||
|
$(BUILD_DIR)/$(APP_NAME)
|
||||||
|
|
||||||
|
test:
|
||||||
|
go test -v -race -cover ./...
|
||||||
|
|
||||||
|
lint:
|
||||||
|
golangci-lint run
|
||||||
|
|
||||||
|
clean:
|
||||||
|
rm -rf $(BUILD_DIR)
|
||||||
|
|
||||||
|
docker-build:
|
||||||
|
docker build -t $(APP_NAME):latest .
|
||||||
70
README.md
70
README.md
@@ -1,2 +1,70 @@
|
|||||||
# golang
|
# Golang Project Template
|
||||||
|
|
||||||
|
A production-ready Go project template with Docker builds, CI/CD pipelines, and best practices.
|
||||||
|
|
||||||
|
## Features
|
||||||
|
|
||||||
|
- **Standard Layout**: Follows Go project layout standards (`cmd`, `internal`).
|
||||||
|
- **Graceful Shutdown**: `main.go` implements signal handling and context cancellation.
|
||||||
|
- **Structured Logging**: Uses `log/slog`.
|
||||||
|
- **Docker**: Multi-stage `Dockerfile` using `distroless` for secure, small images.
|
||||||
|
- **CI/CD**: GitHub Actions for linting, testing, building, and releasing (with GoReleaser).
|
||||||
|
- **Tooling**: `Makefile` for common tasks and `golangci-lint` configuration.
|
||||||
|
|
||||||
|
## Prerequisites
|
||||||
|
|
||||||
|
- [Go](https://go.dev/) 1.23+
|
||||||
|
- [Docker](https://www.docker.com/)
|
||||||
|
- [Make](https://www.gnu.org/software/make/)
|
||||||
|
|
||||||
|
## Getting Started
|
||||||
|
|
||||||
|
### Build and Run
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Build binary
|
||||||
|
make build
|
||||||
|
|
||||||
|
# Run application
|
||||||
|
make run
|
||||||
|
```
|
||||||
|
|
||||||
|
### Testing
|
||||||
|
|
||||||
|
```bash
|
||||||
|
make test
|
||||||
|
```
|
||||||
|
|
||||||
|
### Linting
|
||||||
|
|
||||||
|
```bash
|
||||||
|
make lint
|
||||||
|
```
|
||||||
|
|
||||||
|
## Docker
|
||||||
|
|
||||||
|
Build the Docker image:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
make docker-build
|
||||||
|
```
|
||||||
|
|
||||||
|
Run the container:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
docker run --rm -p 8080:8080 app:latest
|
||||||
|
```
|
||||||
|
|
||||||
|
## CI/CD
|
||||||
|
|
||||||
|
The project includes GitHub Actions workflows:
|
||||||
|
|
||||||
|
- **CI (`ci.yml`)**: Runs on `main` and PRs. Validates formatting, linting, tests, and builds.
|
||||||
|
- **Release (`release.yml`)**: Runs on tags (`v*`). Creates a GitHub Release and pushes the Docker image to GHCR.
|
||||||
|
|
||||||
|
## Project Structure
|
||||||
|
|
||||||
|
- `cmd/app`: Main application entry point.
|
||||||
|
- `internal`: Private application code.
|
||||||
|
- `build`: Build artifacts.
|
||||||
|
- `.github`: CI/CD workflows.
|
||||||
|
|||||||
51
cmd/app/main.go
Normal file
51
cmd/app/main.go
Normal file
@@ -0,0 +1,51 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"log/slog"
|
||||||
|
"os"
|
||||||
|
"os/signal"
|
||||||
|
"syscall"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
logger := slog.New(slog.NewJSONHandler(os.Stdout, nil))
|
||||||
|
slog.SetDefault(logger)
|
||||||
|
|
||||||
|
ctx, cancel := context.WithCancel(context.Background())
|
||||||
|
defer cancel()
|
||||||
|
|
||||||
|
// Handle OS signals
|
||||||
|
go func() {
|
||||||
|
sigChan := make(chan os.Signal, 1)
|
||||||
|
signal.Notify(sigChan, syscall.SIGINT, syscall.SIGTERM)
|
||||||
|
<-sigChan
|
||||||
|
slog.Info("Received shutdown signal")
|
||||||
|
cancel()
|
||||||
|
}()
|
||||||
|
|
||||||
|
if err := run(ctx); err != nil {
|
||||||
|
slog.Error("Application error", "error", err)
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func run(ctx context.Context) error {
|
||||||
|
slog.Info("Starting application...")
|
||||||
|
|
||||||
|
// Simulate long running process or server start
|
||||||
|
// e.g. server.ListenAndServe()
|
||||||
|
|
||||||
|
<-ctx.Done()
|
||||||
|
slog.Info("Shutting down application...")
|
||||||
|
|
||||||
|
// Cleanup with timeout
|
||||||
|
_, shutdownCancel := context.WithTimeout(context.Background(), 5*time.Second)
|
||||||
|
defer shutdownCancel()
|
||||||
|
|
||||||
|
// Pretend to cleanup
|
||||||
|
time.Sleep(100 * time.Millisecond)
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user