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
|
||||
|
||||
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