Перейти к основному содержимому

CI/CD для Go проектов

Базовая структура проекта

my-go-app/
├── .github/workflows/
│ └── ci.yml
├── cmd/
│ └── main.go
├── internal/
│ └── ...
├── pkg/
│ └── ...
├── config/
│ └── config.go
├── Dockerfile
├── docker-compose.yml
├── Makefile
├── go.mod
└── go.sum

Makefile

.PHONY: all build test lint clean run docker-build docker-run help

# Параметры
BINARY_NAME=myapp
VERSION=$(shell git describe --tags --always --dirty 2>/dev/null || echo "dev")
LDFLAGS=-X main.version=$(VERSION) -s -w

# Сборка
build:
go build -ldflags "$(LDFLAGS)" -o $(BINARY_NAME) ./cmd/main.go

# Тестирование
test:
go test -v -race -cover -coverprofile=coverage.out ./...

# Покрытие
test-coverage: test
go tool cover -func=coverage.out

# Линтинг
lint:
golangci-lint run ./...

# Очистка
clean:
rm -f $(BINARY_NAME) coverage.out

# Запуск
run: build
./$(BINARY_NAME)

# Docker
docker-build:
docker build -t $(BINARY_NAME):$(VERSION) .

docker-run:
docker run -p 8080:8080 $(BINARY_NAME):$(VERSION)

# Генерация
generate:
go generate ./...

# Форматирование
fmt:
go fmt ./...

# Обновление зависимостей
deps:
go mod tidy
go mod download

# Проверка лицензий (треrd-party)
license:
@which $@ > /dev/null || go install github.com/google/addlicense@latest
addlicense -y .

# Help
help:
@echo "Доступные команды:"
@echo " build - собрать бинарник"
@echo " test - запустить тесты"
@echo " lint - запустить линтер"
@echo " clean - очистить артефакты"
@echo " run - собрать и запустить"
@echo " docker-build - собрать Docker образ"
@echo " docker-run - запустить в Docker"

GitHub Actions CI

name: CI

on:
push:
branches: [main, develop]
pull_request:
branches: [main]

env:
GO_VERSION: '1.21'
DOCKER_BUILDKIT: 1

jobs:
lint:
name: Lint
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4

- name: Set up Go
uses: actions/setup-go@v5
with:
go-version: ${{ env.GO_VERSION }}

- name: Cache Go modules
uses: actions/cache@v3
with:
path: |
~/.cache/go-build
~/go/pkg/mod
key: ${{ runner.os }}-go-${{ hashFiles('**/go.sum') }}
restore-keys: |
${{ runner.os }}-go-

- name: Install golangci-lint
run: go install github.com/golangci/golangci-lint/cmd/golangci-lint@latest

- name: Run linter
run: golangci-lint run ./...

test:
name: Test
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4

- name: Set up Go
uses: actions/setup-go@v5
with:
go-version: ${{ env.GO_VERSION }}

- name: Cache Go modules
uses: actions/cache@v3
with:
path: |
~/.cache/go-build
~/go/pkg/mod
key: ${{ runner.os }}-go-${{ hashFiles('**/go.sum') }}
restore-keys: |
${{ runner.os }}-go-

- name: Run tests
run: go test -race -cover -coverprofile=coverage.out ./...

- name: Upload coverage
uses: codecov/codecov-action@v3
with:
files: ./coverage.out

build:
name: Build
runs-on: ubuntu-latest
needs: [lint, test]
steps:
- uses: actions/checkout@v4

- name: Set up Go
uses: actions/setup-go@v5
with:
go-version: ${{ env.GO_VERSION }}

- name: Build binary
run: |
VERSION=$(git describe --tags --always --dirty 2>/dev/null || echo "dev")
go build -ldflags "-X main.version=${VERSION}" -o myapp ./cmd/main.go

- name: Upload binary
uses: actions/upload-artifact@v3
with:
name: binary
path: myapp

docker:
name: Docker
runs-on: ubuntu-latest
needs: [lint, test]
if: github.event_name == 'push' && github.ref == 'refs/heads/main'
steps:
- uses: actions/checkout@v4

- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3

- name: Login to registry
uses: docker/login-action@v3
with:
registry: ${{ secrets.DOCKER_REGISTRY }}
username: ${{ secrets.DOCKER_USER }}
password: ${{ secrets.DOCKER_PASS }}

- name: Build and push
uses: docker/build-push-action@v5
with:
context: .
push: true
tags: ${{ secrets.DOCKER_REGISTRY }}/myapp:${{ github.sha }}
cache-from: type=gha
cache-to: type=gha,mode=max

deploy:
name: Deploy
runs-on: ubuntu-latest
needs: [build, docker]
if: github.ref == 'refs/heads/main'
steps:
- name: Deploy to server
uses: appleboy/ssh-action@v1
with:
host: ${{ secrets.SERVER_HOST }}
username: ${{ secrets.SERVER_USER }}
key: ${{ secrets.SERVER_SSH_KEY }}
script: |
docker pull ${{ secrets.DOCKER_REGISTRY }}/myapp:${{ github.sha }}
docker-compose down
docker-compose up -d

GitLab CI

stages:
- lint
- test
- build
- security
- deploy

variables:
GO_VERSION: "1.21"
DOCKER_DRIVER: overlay2

lint:
stage: lint
image: golang:${GO_VERSION}-alpine
script:
- apk add --no-cache git
- go install github.com/golangci/golangci-lint/cmd/golangci-lint@latest
- golangci-lint run ./...
only:
- merge_requests
- main

test:
stage: test
image: golang:${GO_VERSION}-alpine
script:
- go test -race -cover -coverprofile=coverage.out ./...
coverage: '/total:\s+\(\d+\.\d+%\)/'
artifacts:
reports:
coverage_report:
coverage_format: cobertura
path: coverage.out
only:
- merge_requests
- main

build:
stage: build
image: golang:${GO_VERSION}-alpine
script:
- VERSION=$(git describe --tags --always --dirty 2>/dev/null || echo "dev")
- go build -ldflags "-X main.version=${VERSION}" -o myapp ./cmd/main
artifacts:
paths:
- myapp
only:
- main

security:
stage: security
image:
name: aquasec/trivy:latest
entrypoint: [""]
script:
- trivy fs --exit-code 2 --severity HIGH,CRITICAL .
allow_failure: true
only:
- main

deploy:
stage: deploy
image: alpine:latest
script:
- apk add --no-cache openssh-client
- echo "$SSH_PRIVATE_KEY" > deploy_key
- chmod 600 deploy_key
- ssh -o StrictHostKeyChecking=no -i deploy_key $SERVER_USER@$SERVER_HOST "
docker pull $CI_REGISTRY_IMAGE:$CI_COMMIT_SHA &&
docker-compose down &&
docker-compose up -d
"
environment:
name: production
url: https://example.com
only:
- main

SemVer и версионирование

package main

import (
"fmt"
"os"
"strings"
)

var (
version = "dev"
commit = "unknown"
date = "unknown"
)

func main() {
fmt.Printf("Version: %s\n", version)
fmt.Printf("Commit: %s\n", commit)
fmt.Printf("Date: %s\n", date)

// SemVer проверка
if version != "dev" && !isValidSemVer(version) {
fmt.Println("Invalid version format")
os.Exit(1)
}
}

func isValidSemVer(v string) bool {
parts := strings.Split(v, ".")
if len(parts) != 3 {
return false
}
for _, p := range parts {
for _, c := range p {
if c < '0' || c > '9' {
return false
}
}
}
return true
}

Changelog генерация

# Changelog

## [1.0.0] - $(date +%Y-%m-%d)

### Added
- Initial release
- REST API endpoints
- Docker support

### Changed
- Improved performance
- Fixed memory leak

### Deprecated
- Old endpoint /v1/users (use /v2/users)

### Removed
- Legacy authentication

### Fixed
- Bug in user creation

Итоги

КомпонентИнструмент
Линтингgolangci-lint
Тестированиеgo test
CI/CDGitHub Actions, GitLab CI
DockerDockerfile, docker-compose
ВерсионированиеSemVer, git tags
СборкаMakefile