Compare commits
22 Commits
Author | SHA1 | Date | |
---|---|---|---|
9d82d42680 | |||
9da418648c | |||
7a33e815af | |||
bf643393ad | |||
c4a3ea4d98 | |||
dd3de165f7 | |||
7c3d490685 | |||
aff61e32fc | |||
1a0207017a | |||
638dc104f8 | |||
8b420c500f | |||
078c32b929 | |||
e4a2fccef4 | |||
bde6e734b3 | |||
e018e30975 | |||
bf25c33b3f | |||
c1e0f6e04f | |||
381eefa47b | |||
ee68d72fcd | |||
c5e580c627 | |||
3734143548 | |||
7858f1c55b |
59
.gitea/workflows/build-builder.yaml
Normal file
59
.gitea/workflows/build-builder.yaml
Normal file
@@ -0,0 +1,59 @@
|
|||||||
|
name: Build Builder Docker Image
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
tags:
|
||||||
|
- builder-*
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
create-builder-docker-image:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
container:
|
||||||
|
image: docker:28.3.2-dind
|
||||||
|
steps:
|
||||||
|
- name: Checkout repository
|
||||||
|
run: |
|
||||||
|
# Install git
|
||||||
|
apk add --no-cache git
|
||||||
|
|
||||||
|
echo "=== GitHub Variables ==="
|
||||||
|
echo "github.ref = ${{ github.ref }}"
|
||||||
|
echo "github.ref_name = ${{ github.ref_name }}"
|
||||||
|
echo "github.sha = ${{ github.sha }}"
|
||||||
|
echo "github.repository = ${{ github.repository }}"
|
||||||
|
echo "========================"
|
||||||
|
echo "Cloning..."
|
||||||
|
git clone https://oauth2:${{ secrets.GITEATOKEN }}@direct-dev.ru/gitea/GiteaAdmin/hello_gitea.git hello_gitea
|
||||||
|
cd hello_gitea
|
||||||
|
echo "Checkout to ${{ github.ref }} ..."
|
||||||
|
git checkout ${{ github.ref }}
|
||||||
|
|
||||||
|
- name: Setup Docker Buildx
|
||||||
|
run: |
|
||||||
|
# Docker is already installed in docker:dind image
|
||||||
|
echo "look at docker version"
|
||||||
|
docker --version
|
||||||
|
# Setup Docker Buildx for multi-platform builds
|
||||||
|
echo "setup buildx"
|
||||||
|
docker buildx create --name builder-builx --use
|
||||||
|
docker buildx inspect --bootstrap
|
||||||
|
|
||||||
|
- name: Login to Docker Hub
|
||||||
|
run: |
|
||||||
|
echo "login to docker hub ..."
|
||||||
|
echo ${{ secrets.DOCKERHUB_TOKEN }} | docker login -u ${{ secrets.DOCKERHUB_USERNAME }} --password-stdin
|
||||||
|
|
||||||
|
- name: Build multi-platform Docker images
|
||||||
|
run: |
|
||||||
|
cd hello_gitea
|
||||||
|
echo "Build multi-platform images using buildx ..."
|
||||||
|
docker buildx build \
|
||||||
|
--platform linux/amd64,linux/arm64 \
|
||||||
|
--tag ${{ secrets.DOCKERHUB_USERNAME }}/my-build-golang-runner:${{ github.ref_name }} \
|
||||||
|
--tag ${{ secrets.DOCKERHUB_USERNAME }}/my-build-golang-runner:latest \
|
||||||
|
--push \
|
||||||
|
-f Dockerfile.builder \
|
||||||
|
.
|
||||||
|
|
||||||
|
- name: Remove buildx
|
||||||
|
run: |
|
||||||
|
docker buildx rm builder-builx
|
@@ -5,27 +5,55 @@ on:
|
|||||||
- v*
|
- v*
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
release:
|
create-release:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
container:
|
container:
|
||||||
image: golang:1.21-alpine
|
# image: golang:1.21
|
||||||
|
image: ${{ secrets.DOCKERHUB_USERNAME }}/my-build-golang-runner:latest
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout repository
|
- name: Checkout repository
|
||||||
run: |
|
run: |
|
||||||
git clone https://oauth2:${{ secrets.GITEATOKEN }}@direct-dev.ru/gitea/GiteaAdmin/hello_gitea.git hello_gitea
|
git clone https://oauth2:${{ secrets.GITEATOKEN }}@direct-dev.ru/gitea/GiteaAdmin/hello_gitea.git hello_gitea
|
||||||
cd hello_gitea
|
cd hello_gitea
|
||||||
git checkout ${{ github.ref }}
|
git checkout ${{ github.ref }}
|
||||||
|
docker pull ${{ secrets.DOCKERHUB_USERNAME }}/my-build-golang-runner:latest
|
||||||
|
|
||||||
- name: Setup Go
|
- name: Setup Go
|
||||||
run: |
|
run: |
|
||||||
|
# Install jq for JSON parsing
|
||||||
|
# apt-get update && apt-get install -y jq
|
||||||
|
git --version
|
||||||
go version
|
go version
|
||||||
|
jq --version
|
||||||
|
|
||||||
- name: Build
|
- name: Build all binaries
|
||||||
run: |
|
run: |
|
||||||
cd hello_gitea
|
cd hello_gitea
|
||||||
mkdir -p bin
|
mkdir -p bin
|
||||||
go build -o bin/hello-api-${{ github.ref_name }} main.go
|
|
||||||
ls -la bin/
|
# Build for all platforms
|
||||||
|
quick-build linux amd64
|
||||||
|
quick-build linux arm64
|
||||||
|
quick-build windows amd64
|
||||||
|
quick-build darwin amd64
|
||||||
|
quick-build darwin arm64
|
||||||
|
|
||||||
|
# Build for all platforms
|
||||||
|
GOOS=linux GOARCH=amd64 go build -o bin/hello-api-linux-amd64 main.go
|
||||||
|
GOOS=linux GOARCH=arm64 go build -o bin/hello-api-linux-arm64 main.go
|
||||||
|
GOOS=windows GOARCH=amd64 go build -o bin/hello-api-windows-amd64.exe main.go
|
||||||
|
GOOS=darwin GOARCH=amd64 go build -o bin/hello-api-darwin-amd64 main.go
|
||||||
|
GOOS=darwin GOARCH=arm64 go build -o bin/hello-api-darwin-arm64 main.go
|
||||||
|
|
||||||
|
# Create archives
|
||||||
|
cd bin
|
||||||
|
tar -czf hello-api-linux-amd64.tar.gz hello-api-linux-amd64
|
||||||
|
tar -czf hello-api-linux-arm64.tar.gz hello-api-linux-arm64
|
||||||
|
tar -czf hello-api-windows-amd64.tar.gz hello-api-windows-amd64.exe
|
||||||
|
tar -czf hello-api-darwin-amd64.tar.gz hello-api-darwin-amd64
|
||||||
|
tar -czf hello-api-darwin-arm64.tar.gz hello-api-darwin-arm64
|
||||||
|
|
||||||
|
ls -la
|
||||||
|
|
||||||
- name: Create Release
|
- name: Create Release
|
||||||
run: |
|
run: |
|
||||||
@@ -37,19 +65,118 @@ jobs:
|
|||||||
-d '{
|
-d '{
|
||||||
"tag_name": "${{ github.ref_name }}",
|
"tag_name": "${{ github.ref_name }}",
|
||||||
"name": "Release ${{ github.ref_name }}",
|
"name": "Release ${{ github.ref_name }}",
|
||||||
"body": "Automated release",
|
"body": "Automated release with multi-platform binaries and Docker image",
|
||||||
"draft": false,
|
"draft": false,
|
||||||
"prerelease": false
|
"prerelease": false
|
||||||
}' \
|
}' \
|
||||||
"https://direct-dev.ru/gitea/api/v1/repos/GiteaAdmin/hello_gitea/releases"
|
"https://direct-dev.ru/gitea/api/v1/repos/GiteaAdmin/hello_gitea/releases"
|
||||||
|
|
||||||
# Upload asset
|
# Upload assets
|
||||||
RELEASE_ID=$(curl -s -H "Authorization: token ${{ secrets.GITEATOKEN }}" \
|
RELEASE_ID=$(curl -s -H "Authorization: token ${{ secrets.GITEATOKEN }}" \
|
||||||
"https://direct-dev.ru/gitea/api/v1/repos/GiteaAdmin/hello_gitea/releases/tags/${{ github.ref_name }}" | \
|
"https://direct-dev.ru/gitea/api/v1/repos/GiteaAdmin/hello_gitea/releases/tags/${{ github.ref_name }}" | \
|
||||||
jq -r '.id')
|
jq -r '.id')
|
||||||
|
|
||||||
|
# Upload all binaries
|
||||||
|
for file in bin/*.tar.gz; do
|
||||||
|
echo "Uploading $file..."
|
||||||
curl -X POST \
|
curl -X POST \
|
||||||
-H "Authorization: token ${{ secrets.GITEATOKEN }}" \
|
-H "Authorization: token ${{ secrets.GITEATOKEN }}" \
|
||||||
-H "Content-Type: application/octet-stream" \
|
-H "Content-Type: application/octet-stream" \
|
||||||
--data-binary @bin/hello-api-${{ github.ref_name }} \
|
--data-binary @$file \
|
||||||
"https://direct-dev.ru/gitea/api/v1/repos/GiteaAdmin/hello_gitea/releases/$RELEASE_ID/assets?name=hello-api-${{ github.ref_name }}"
|
"https://direct-dev.ru/gitea/api/v1/repos/GiteaAdmin/hello_gitea/releases/$RELEASE_ID/assets?name=$(basename $file)"
|
||||||
|
done
|
||||||
|
|
||||||
|
create-docker-image:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
container:
|
||||||
|
image: docker:28.3.2-dind
|
||||||
|
needs: create-release
|
||||||
|
steps:
|
||||||
|
- name: Checkout repository
|
||||||
|
run: |
|
||||||
|
# Install git
|
||||||
|
apk add --no-cache git
|
||||||
|
|
||||||
|
echo "=== GitHub Variables ==="
|
||||||
|
echo "github.ref = ${{ github.ref }}"
|
||||||
|
echo "github.ref_name = ${{ github.ref_name }}"
|
||||||
|
echo "github.sha = ${{ github.sha }}"
|
||||||
|
echo "github.repository = ${{ github.repository }}"
|
||||||
|
echo "========================"
|
||||||
|
git clone https://oauth2:${{ secrets.GITEATOKEN }}@direct-dev.ru/gitea/GiteaAdmin/hello_gitea.git hello_gitea
|
||||||
|
cd hello_gitea
|
||||||
|
git checkout ${{ github.ref }}
|
||||||
|
|
||||||
|
- name: Setup Docker Buildx
|
||||||
|
run: |
|
||||||
|
# Docker is already installed in docker:dind image
|
||||||
|
docker --version
|
||||||
|
|
||||||
|
# Setup Docker Buildx for multi-platform builds
|
||||||
|
docker buildx create --name go-buildx --use
|
||||||
|
docker buildx inspect --bootstrap
|
||||||
|
|
||||||
|
- name: Login to Docker Hub
|
||||||
|
run: |
|
||||||
|
echo ${{ secrets.DOCKERHUB_TOKEN }} | docker login -u ${{ secrets.DOCKERHUB_USERNAME }} --password-stdin
|
||||||
|
|
||||||
|
- name: Build multi-platform Docker images
|
||||||
|
run: |
|
||||||
|
cd hello_gitea
|
||||||
|
# Build multi-platform images using buildx
|
||||||
|
docker buildx build \
|
||||||
|
--platform linux/amd64,linux/arm64 \
|
||||||
|
--tag ${{ secrets.DOCKERHUB_USERNAME }}/hello-api:${{ github.ref_name }} \
|
||||||
|
--tag ${{ secrets.DOCKERHUB_USERNAME }}/hello-api:latest \
|
||||||
|
--push \
|
||||||
|
.
|
||||||
|
- name: Remove buildx
|
||||||
|
run: |
|
||||||
|
docker buildx rm go-buildx
|
||||||
|
|
||||||
|
update-to-release-branch:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
container:
|
||||||
|
image: docker:28.3.2-dind
|
||||||
|
needs: create-docker-image
|
||||||
|
steps:
|
||||||
|
- name: Create Release Branch
|
||||||
|
run: |
|
||||||
|
echo "=== GitHub Variables ==="
|
||||||
|
echo "github.ref = ${{ github.ref }}"
|
||||||
|
echo "github.ref_name = ${{ github.ref_name }}"
|
||||||
|
echo "github.sha = ${{ github.sha }}"
|
||||||
|
echo "github.repository = ${{ github.repository }}"
|
||||||
|
echo "DOCKERHUB_USERNAME = ${{ secrets.DOCKERHUB_USERNAME }}"
|
||||||
|
echo "DOCKERHUB_TOKEN = ${{ secrets.DOCKERHUB_TOKEN }}"
|
||||||
|
echo "GITEATOKEN = ${{ secrets.GITEATOKEN }}"
|
||||||
|
echo "========================"
|
||||||
|
# Clone repository
|
||||||
|
echo "Cloning repository..."
|
||||||
|
git clone https://oauth2:${{ secrets.GITEATOKEN }}@direct-dev.ru/gitea/GiteaAdmin/hello_gitea.git hello_gitea
|
||||||
|
cd hello_gitea
|
||||||
|
|
||||||
|
# Configure git
|
||||||
|
echo "Configuring git..."
|
||||||
|
git config user.email "info@direct-dev.ru"
|
||||||
|
git config user.name "Direct-Dev-Robot"
|
||||||
|
|
||||||
|
# Check if release branch exists
|
||||||
|
echo "Checking if release branch exists..."
|
||||||
|
if git ls-remote --heads origin release | grep -q release; then
|
||||||
|
echo "Release branch exists, checking out..."
|
||||||
|
git checkout release
|
||||||
|
echo "release branch exists - pulling release branch..."
|
||||||
|
git pull origin release
|
||||||
|
else
|
||||||
|
echo "release branch does not exist - creating new release branch..."
|
||||||
|
git checkout -b release
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Reset to the tag commit
|
||||||
|
echo "Resetting to the tag commit ${{ github.ref_name }} ..."
|
||||||
|
git reset --hard ${{ github.ref_name }}
|
||||||
|
|
||||||
|
# Push changes to release branch
|
||||||
|
echo "Pushing changes to release branch..."
|
||||||
|
git push origin release --force
|
1
.gitignore
vendored
1
.gitignore
vendored
@@ -25,3 +25,4 @@ go.work.sum
|
|||||||
# env file
|
# env file
|
||||||
.env
|
.env
|
||||||
bin/
|
bin/
|
||||||
|
dockerhub_token
|
51
Dockerfile
Normal file
51
Dockerfile
Normal file
@@ -0,0 +1,51 @@
|
|||||||
|
# Build stage
|
||||||
|
FROM --platform=$BUILDPLATFORM golang:1.21-alpine AS builder
|
||||||
|
|
||||||
|
# Install git and ca-certificates
|
||||||
|
RUN apk --no-cache add git ca-certificates
|
||||||
|
|
||||||
|
WORKDIR /app
|
||||||
|
|
||||||
|
# Copy go mod files
|
||||||
|
COPY go.mod go.sum ./
|
||||||
|
|
||||||
|
# Download dependencies
|
||||||
|
RUN go mod download
|
||||||
|
|
||||||
|
# Copy source code
|
||||||
|
COPY . .
|
||||||
|
|
||||||
|
# Build the application for target platform
|
||||||
|
ARG TARGETPLATFORM
|
||||||
|
ARG BUILDPLATFORM
|
||||||
|
ARG TARGETOS
|
||||||
|
ARG TARGETARCH
|
||||||
|
|
||||||
|
RUN CGO_ENABLED=0 GOOS=${TARGETOS} GOARCH=${TARGETARCH} go build -a -installsuffix cgo -o hello-api main.go
|
||||||
|
|
||||||
|
# Final stage
|
||||||
|
FROM alpine:latest
|
||||||
|
|
||||||
|
# Install jq and ca-certificates
|
||||||
|
RUN apk --no-cache add ca-certificates jq
|
||||||
|
|
||||||
|
# Create non-root user
|
||||||
|
RUN addgroup -g 1001 -S appgroup && \
|
||||||
|
adduser -u 1001 -S appuser -G appgroup
|
||||||
|
|
||||||
|
WORKDIR /app
|
||||||
|
|
||||||
|
# Copy binary from builder stage
|
||||||
|
COPY --from=builder /app/hello-api .
|
||||||
|
|
||||||
|
# Change ownership to non-root user
|
||||||
|
RUN chown appuser:appgroup hello-api
|
||||||
|
|
||||||
|
# Switch to non-root user
|
||||||
|
USER appuser
|
||||||
|
|
||||||
|
# Expose port (if your app uses one)
|
||||||
|
EXPOSE 8080
|
||||||
|
|
||||||
|
# Run the application
|
||||||
|
CMD ["./hello-api"]
|
32
Dockerfile.builder
Normal file
32
Dockerfile.builder
Normal file
@@ -0,0 +1,32 @@
|
|||||||
|
# Используем образ Go с поддержкой мультиплатформенности
|
||||||
|
FROM golang:1.24
|
||||||
|
|
||||||
|
# Устанавливаем пакеты (одинаково работают на amd64/arm64)
|
||||||
|
RUN apt-get update && \
|
||||||
|
apt-get install -y --no-install-recommends \
|
||||||
|
git \
|
||||||
|
ca-certificates \
|
||||||
|
jq && \
|
||||||
|
rm -rf /var/lib/apt/lists/*
|
||||||
|
|
||||||
|
# Создаем рабочую директорию
|
||||||
|
WORKDIR /app
|
||||||
|
|
||||||
|
# Копируем файлы зависимостей
|
||||||
|
COPY go.mod go.sum ./
|
||||||
|
|
||||||
|
# Предварительно загружаем все зависимости
|
||||||
|
RUN go mod download && go mod verify
|
||||||
|
|
||||||
|
# Создаем скрипт для быстрой сборки
|
||||||
|
COPY scripts/quick-build.sh /usr/local/bin/quick-build
|
||||||
|
|
||||||
|
# делаем скрипт исполняемым
|
||||||
|
RUN chmod +x /usr/local/bin/quick-build
|
||||||
|
|
||||||
|
# Устанавливаем переменные окружения
|
||||||
|
ENV GOPATH=/go
|
||||||
|
ENV PATH=$PATH:/go/bin:/usr/local/bin
|
||||||
|
|
||||||
|
# (Опционально) Можно добавить команду по умолчанию
|
||||||
|
CMD ["bash"]
|
381
README.md
381
README.md
@@ -1,3 +1,380 @@
|
|||||||
# hello_gitea
|
# Hello Gitea API
|
||||||
|
|
||||||
test repo to try with actions
|
Современный REST API сервер, построенный на Go с использованием Gin framework и структурированного логирования.
|
||||||
|
|
||||||
|
## 🚀 Возможности
|
||||||
|
|
||||||
|
- ✅ REST API с JSON ответами
|
||||||
|
- ✅ Health check endpoint
|
||||||
|
- ✅ CORS поддержка
|
||||||
|
- ✅ **Современное структурированное логирование** (Go 1.24+)
|
||||||
|
- ✅ **Настраиваемые уровни и форматы логирования**
|
||||||
|
- ✅ **Системная информация и мониторинг**
|
||||||
|
- ✅ Мультиплатформенная сборка
|
||||||
|
- ✅ Docker образы для Linux AMD64/ARM64
|
||||||
|
- ✅ Автоматические релизы через Gitea Actions
|
||||||
|
|
||||||
|
## 📦 Установка
|
||||||
|
|
||||||
|
### Из бинарного файла
|
||||||
|
|
||||||
|
1. Скачайте бинарный файл для вашей платформы из [релизов](https://direct-dev.ru/gitea/GiteaAdmin/hello_gitea/releases)
|
||||||
|
2. Распакуйте архив
|
||||||
|
3. Запустите: `./hello-api-<platform>`
|
||||||
|
|
||||||
|
### Из Docker образа
|
||||||
|
|
||||||
|
```bash
|
||||||
|
docker pull <username>/hello-api:latest
|
||||||
|
docker run -p 8080:8080 <username>/hello-api:latest
|
||||||
|
```
|
||||||
|
|
||||||
|
### Из исходного кода
|
||||||
|
|
||||||
|
```bash
|
||||||
|
git clone https://direct-dev.ru/gitea/GiteaAdmin/hello_gitea.git
|
||||||
|
cd hello_gitea
|
||||||
|
go mod download
|
||||||
|
go run main.go
|
||||||
|
```
|
||||||
|
|
||||||
|
## 🔧 Конфигурация
|
||||||
|
|
||||||
|
### Основные настройки
|
||||||
|
|
||||||
|
Сервер запускается на порту 8080 по умолчанию. Можно изменить через переменную окружения:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
export PORT=3000
|
||||||
|
./hello-api
|
||||||
|
```
|
||||||
|
|
||||||
|
### Настройка логирования
|
||||||
|
|
||||||
|
Приложение поддерживает гибкую настройку логирования через переменные окружения:
|
||||||
|
|
||||||
|
#### Уровни логирования (`LOG_LEVEL`)
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Доступные уровни (по возрастанию важности):
|
||||||
|
export LOG_LEVEL=DEBUG # Все сообщения
|
||||||
|
export LOG_LEVEL=INFO # Информационные и выше
|
||||||
|
export LOG_LEVEL=WARN # Предупреждения и выше
|
||||||
|
export LOG_LEVEL=ERROR # Только ошибки
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Форматы логирования (`LOG_FORMAT`)
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# JSON формат (по умолчанию) - для production
|
||||||
|
export LOG_FORMAT=json
|
||||||
|
|
||||||
|
# Текстовый формат - для разработки
|
||||||
|
export LOG_FORMAT=text
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Примеры конфигурации
|
||||||
|
|
||||||
|
**Для разработки:**
|
||||||
|
|
||||||
|
```bash
|
||||||
|
export LOG_LEVEL=DEBUG
|
||||||
|
export LOG_FORMAT=text
|
||||||
|
export PORT=8080
|
||||||
|
go run main.go
|
||||||
|
```
|
||||||
|
|
||||||
|
**Для production:**
|
||||||
|
|
||||||
|
```bash
|
||||||
|
export LOG_LEVEL=INFO
|
||||||
|
export LOG_FORMAT=json
|
||||||
|
export PORT=8080
|
||||||
|
./hello-api
|
||||||
|
```
|
||||||
|
|
||||||
|
## 📡 API Endpoints
|
||||||
|
|
||||||
|
### GET /
|
||||||
|
|
||||||
|
Основной endpoint
|
||||||
|
|
||||||
|
**Ответ:**
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"message": "Hello, World!",
|
||||||
|
"version": "1.0.33"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### GET /healthz
|
||||||
|
|
||||||
|
Health check endpoint
|
||||||
|
|
||||||
|
**Ответ:**
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"status": "ok",
|
||||||
|
"version": "1.0.33"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### GET /api/v1/info
|
||||||
|
|
||||||
|
Информация о сервисе
|
||||||
|
|
||||||
|
**Ответ:**
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"service": "hello-api",
|
||||||
|
"status": "running",
|
||||||
|
"version": "1.0.33"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### POST /api/v1/echo
|
||||||
|
|
||||||
|
Echo endpoint - возвращает отправленное сообщение
|
||||||
|
|
||||||
|
**Запрос:**
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"message": "Hello from client!"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**Ответ:**
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"echo": "Hello from client!",
|
||||||
|
"version": "1.0.33"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### GET /api/v1/config
|
||||||
|
|
||||||
|
**Новый endpoint** - системная информация и конфигурация
|
||||||
|
|
||||||
|
**Ответ:**
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"version": "1.0.33",
|
||||||
|
"system_info": {
|
||||||
|
"go_version": "go1.24.0",
|
||||||
|
"os": "linux",
|
||||||
|
"architecture": "amd64",
|
||||||
|
"num_cpu": 8,
|
||||||
|
"start_time": "2024-01-15T10:30:45Z"
|
||||||
|
},
|
||||||
|
"environment": {
|
||||||
|
"PATH": "/usr/local/bin:/usr/bin:/bin",
|
||||||
|
"PORT": "8080",
|
||||||
|
"LOG_LEVEL": "INFO"
|
||||||
|
},
|
||||||
|
"uptime": "2h30m15s"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### POST /api/v1/log-test
|
||||||
|
|
||||||
|
**Новый endpoint** - тестирование уровней логирования
|
||||||
|
|
||||||
|
**Запрос:**
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"level": "debug",
|
||||||
|
"message": "Test log message"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**Ответ:**
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"status": "logged",
|
||||||
|
"level": "debug",
|
||||||
|
"message": "Test log message"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## 📊 Логирование
|
||||||
|
|
||||||
|
### Структурированное логирование
|
||||||
|
|
||||||
|
Приложение использует современную систему логирования Go 1.24+ (`log/slog`) с поддержкой:
|
||||||
|
|
||||||
|
- **Структурированных логов** в JSON и текстовом формате
|
||||||
|
- **Уровней логирования** (DEBUG, INFO, WARN, ERROR)
|
||||||
|
- **Контекстной информации** (IP клиента, User-Agent, время запроса)
|
||||||
|
- **Автоматического форматирования** времени
|
||||||
|
- **Фильтрации чувствительных данных**
|
||||||
|
|
||||||
|
### Примеры логов
|
||||||
|
|
||||||
|
#### JSON формат (production)
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"time": "2024-01-15T10:30:45.123Z",
|
||||||
|
"level": "INFO",
|
||||||
|
"msg": "HTTP Request",
|
||||||
|
"method": "GET",
|
||||||
|
"path": "/api/v1/config",
|
||||||
|
"status": 200,
|
||||||
|
"latency": "1.234ms",
|
||||||
|
"client_ip": "192.168.1.100",
|
||||||
|
"user_agent": "curl/7.68.0",
|
||||||
|
"timestamp": "2024-01-15T10:30:45.123Z"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Текстовый формат (development)
|
||||||
|
|
||||||
|
``` text
|
||||||
|
2024-01-15T10:30:45.123Z INFO HTTP Request method=GET path=/api/v1/config status=200 latency=1.234ms client_ip=192.168.1.100 user_agent="curl/7.68.0"
|
||||||
|
```
|
||||||
|
|
||||||
|
## Основные изменения в README
|
||||||
|
|
||||||
|
### 🆕 **Новые разделы:**
|
||||||
|
|
||||||
|
1. **Конфигурация логирования** - подробное описание переменных окружения
|
||||||
|
2. **Форматы логирования** - примеры JSON и текстового формата
|
||||||
|
3. **Новые API endpoints** - `/api/v1/config` и `/api/v1/log-test`
|
||||||
|
4. **Безопасность логирования** - фильтрация чувствительных данных
|
||||||
|
|
||||||
|
### 📊 **Детальное описание логирования:**
|
||||||
|
|
||||||
|
- **Уровни логирования** (DEBUG, INFO, WARN, ERROR)
|
||||||
|
- **Форматы** (JSON для production, text для разработки)
|
||||||
|
- **Примеры логов** с реальными данными
|
||||||
|
- **Контекстная информация** в логах
|
||||||
|
|
||||||
|
### 🔧 **Практические примеры:**
|
||||||
|
|
||||||
|
- Настройка для разработки и production
|
||||||
|
- Команды для тестирования
|
||||||
|
- Отладочные приемы
|
||||||
|
|
||||||
|
### 📦 **Улучшенная структура:**
|
||||||
|
|
||||||
|
- Четкое разделение возможностей
|
||||||
|
- Пошаговые инструкции
|
||||||
|
- Примеры конфигурации
|
||||||
|
- Команды для тестирования
|
||||||
|
|
||||||
|
Теперь README полностью отражает современные возможности приложения с детальным описанием системы логирования! 🎉
|
||||||
|
|
||||||
|
### Логируемые события
|
||||||
|
|
||||||
|
- **HTTP запросы** с детальной информацией
|
||||||
|
- **Ошибки приложения** с контекстом
|
||||||
|
- **Системные события** (запуск, остановка)
|
||||||
|
- **API вызовы** с параметрами
|
||||||
|
- **Конфигурационные изменения**
|
||||||
|
|
||||||
|
### Безопасность
|
||||||
|
|
||||||
|
- **Автоматическая фильтрация** чувствительных переменных окружения
|
||||||
|
- **Безопасное логирование** без утечки секретов
|
||||||
|
- **Контролируемые уровни** для разных сред
|
||||||
|
|
||||||
|
## Разработка
|
||||||
|
|
||||||
|
### Зависимости
|
||||||
|
|
||||||
|
- Go 1.24+
|
||||||
|
- Gin framework
|
||||||
|
|
||||||
|
### Сборка
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Для текущей платформы
|
||||||
|
go build -o hello-api main.go
|
||||||
|
|
||||||
|
# Для Linux AMD64
|
||||||
|
GOOS=linux GOARCH=amd64 go build -o hello-api-linux-amd64 main.go
|
||||||
|
|
||||||
|
# Для Linux ARM64
|
||||||
|
GOOS=linux GOARCH=arm64 go build -o hello-api-linux-arm64 main.go
|
||||||
|
|
||||||
|
# Для Windows
|
||||||
|
GOOS=windows GOARCH=amd64 go build -o hello-api-windows-amd64.exe main.go
|
||||||
|
|
||||||
|
# Для macOS
|
||||||
|
GOOS=darwin GOARCH=amd64 go build -o hello-api-darwin-amd64 main.go
|
||||||
|
GOOS=darwin GOARCH=arm64 go build -o hello-api-darwin-arm64 main.go
|
||||||
|
```
|
||||||
|
|
||||||
|
### Тестирование
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Запуск сервера с отладочным логированием
|
||||||
|
export LOG_LEVEL=DEBUG
|
||||||
|
export LOG_FORMAT=text
|
||||||
|
go run main.go
|
||||||
|
|
||||||
|
# Тестирование API
|
||||||
|
curl http://localhost:8080/
|
||||||
|
curl http://localhost:8080/healthz
|
||||||
|
curl http://localhost:8080/api/v1/info
|
||||||
|
curl http://localhost:8080/api/v1/config
|
||||||
|
|
||||||
|
# Тестирование echo
|
||||||
|
curl -X POST http://localhost:8080/api/v1/echo \
|
||||||
|
-H "Content-Type: application/json" \
|
||||||
|
-d '{"message":"Hello!"}'
|
||||||
|
|
||||||
|
# Тестирование логирования
|
||||||
|
curl -X POST http://localhost:8080/api/v1/log-test \
|
||||||
|
-H "Content-Type: application/json" \
|
||||||
|
-d '{"level": "debug", "message": "Test log message"}'
|
||||||
|
```
|
||||||
|
|
||||||
|
### Отладка
|
||||||
|
|
||||||
|
Для детального анализа работы приложения:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Включить отладочное логирование
|
||||||
|
export LOG_LEVEL=DEBUG
|
||||||
|
export LOG_FORMAT=text
|
||||||
|
|
||||||
|
# Запустить приложение
|
||||||
|
go run main.go
|
||||||
|
|
||||||
|
# В другом терминале - мониторинг логов
|
||||||
|
tail -f /var/log/application.log # если логи в файл
|
||||||
|
```
|
||||||
|
|
||||||
|
## 🚀 CI/CD
|
||||||
|
|
||||||
|
При создании тега (например, `v1.1.20`) автоматически:
|
||||||
|
|
||||||
|
1. Собираются бинарники для всех платформ
|
||||||
|
2. Создается Docker образ для Linux AMD64/ARM64
|
||||||
|
3. Образ публикуется в Docker Hub
|
||||||
|
4. Создается релиз в Gitea с бинарниками
|
||||||
|
|
||||||
|
Дополнительно `.gitea/workflows/build_build.yaml` предназначен для автоматизации процесса сборки и публикации Docker-образов билдера - то есть образа который будет использоваться в основном процессе сборки и релиза. Этот workflow запускается (триггерится) автоматически при пуше тега, начинающегося с `builder-` (например, `builder-v1.2.3`), в репозиторий на сервере Gitea.
|
||||||
|
|
||||||
|
Когда такой тег появляется, workflow выполняет следующие задачи:
|
||||||
|
|
||||||
|
- Клонирует репозиторий и переключается на соответствующую версию кода.
|
||||||
|
- Настраивает окружение для сборки Docker-образов с поддержкой мультиплатформенности (amd64 и arm64).
|
||||||
|
- Выполняет аутентификацию в Docker Hub.
|
||||||
|
- Собирает и публикует Docker-образы для разных архитектур (tag DOCKERHUB_USERNAME/my-build-golang-runnerr:builder-v1.2.3 и tag DOCKERHUB_USERNAME/my-build-golang-runner:latest.
|
||||||
|
- Для сборки используется специальный Dockerfile (`Dockerfile.builder`) для создания образа билдера.
|
||||||
|
|
||||||
|
Таким образом, данный файл обеспечивает автоматическую сборку и публикацию артефактов проекта при выпуске новых версий, что упрощает процесс релиза и гарантирует наличие актуальных образов и бинарников для пользователей.
|
||||||
|
|
||||||
|
## 📄 Лицензия
|
||||||
|
|
||||||
|
MIT Licens
|
||||||
|
Binary file not shown.
8
build-builder.sh
Normal file
8
build-builder.sh
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
docker buildx build \
|
||||||
|
--platform linux/amd64,linux/arm64 \
|
||||||
|
--tag "${DOCKERHUB_USERNAME:-kuznetcovay}"/my-build-golang-runner:latest \
|
||||||
|
--push \
|
||||||
|
-f Dockerfile_for_runner_image \
|
||||||
|
.
|
1839
docs/gitea-actions-guide.md
Normal file
1839
docs/gitea-actions-guide.md
Normal file
File diff suppressed because it is too large
Load Diff
33
go.mod
33
go.mod
@@ -1,3 +1,34 @@
|
|||||||
module direct-dev-ru/hello_gitea
|
module direct-dev-ru/hello_gitea
|
||||||
|
|
||||||
go 1.21
|
go 1.24
|
||||||
|
|
||||||
|
require github.com/gin-gonic/gin v1.10.1
|
||||||
|
|
||||||
|
require (
|
||||||
|
github.com/bytedance/sonic v1.11.6 // indirect
|
||||||
|
github.com/bytedance/sonic/loader v0.1.1 // indirect
|
||||||
|
github.com/cloudwego/base64x v0.1.4 // indirect
|
||||||
|
github.com/cloudwego/iasm v0.2.0 // indirect
|
||||||
|
github.com/gabriel-vasile/mimetype v1.4.3 // indirect
|
||||||
|
github.com/gin-contrib/sse v0.1.0 // indirect
|
||||||
|
github.com/go-playground/locales v0.14.1 // indirect
|
||||||
|
github.com/go-playground/universal-translator v0.18.1 // indirect
|
||||||
|
github.com/go-playground/validator/v10 v10.20.0 // indirect
|
||||||
|
github.com/goccy/go-json v0.10.2 // indirect
|
||||||
|
github.com/json-iterator/go v1.1.12 // indirect
|
||||||
|
github.com/klauspost/cpuid/v2 v2.2.7 // indirect
|
||||||
|
github.com/leodido/go-urn v1.4.0 // indirect
|
||||||
|
github.com/mattn/go-isatty v0.0.20 // indirect
|
||||||
|
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
|
||||||
|
github.com/modern-go/reflect2 v1.0.2 // indirect
|
||||||
|
github.com/pelletier/go-toml/v2 v2.2.2 // indirect
|
||||||
|
github.com/twitchyliquid64/golang-asm v0.15.1 // indirect
|
||||||
|
github.com/ugorji/go/codec v1.2.12 // indirect
|
||||||
|
golang.org/x/arch v0.8.0 // indirect
|
||||||
|
golang.org/x/crypto v0.23.0 // indirect
|
||||||
|
golang.org/x/net v0.25.0 // indirect
|
||||||
|
golang.org/x/sys v0.20.0 // indirect
|
||||||
|
golang.org/x/text v0.15.0 // indirect
|
||||||
|
google.golang.org/protobuf v1.34.1 // indirect
|
||||||
|
gopkg.in/yaml.v3 v3.0.1 // indirect
|
||||||
|
)
|
||||||
|
89
go.sum
Normal file
89
go.sum
Normal file
@@ -0,0 +1,89 @@
|
|||||||
|
github.com/bytedance/sonic v1.11.6 h1:oUp34TzMlL+OY1OUWxHqsdkgC/Zfc85zGqw9siXjrc0=
|
||||||
|
github.com/bytedance/sonic v1.11.6/go.mod h1:LysEHSvpvDySVdC2f87zGWf6CIKJcAvqab1ZaiQtds4=
|
||||||
|
github.com/bytedance/sonic/loader v0.1.1 h1:c+e5Pt1k/cy5wMveRDyk2X4B9hF4g7an8N3zCYjJFNM=
|
||||||
|
github.com/bytedance/sonic/loader v0.1.1/go.mod h1:ncP89zfokxS5LZrJxl5z0UJcsk4M4yY2JpfqGeCtNLU=
|
||||||
|
github.com/cloudwego/base64x v0.1.4 h1:jwCgWpFanWmN8xoIUHa2rtzmkd5J2plF/dnLS6Xd/0Y=
|
||||||
|
github.com/cloudwego/base64x v0.1.4/go.mod h1:0zlkT4Wn5C6NdauXdJRhSKRlJvmclQ1hhJgA0rcu/8w=
|
||||||
|
github.com/cloudwego/iasm v0.2.0 h1:1KNIy1I1H9hNNFEEH3DVnI4UujN+1zjpuk6gwHLTssg=
|
||||||
|
github.com/cloudwego/iasm v0.2.0/go.mod h1:8rXZaNYT2n95jn+zTI1sDr+IgcD2GVs0nlbbQPiEFhY=
|
||||||
|
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||||
|
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||||
|
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||||
|
github.com/gabriel-vasile/mimetype v1.4.3 h1:in2uUcidCuFcDKtdcBxlR0rJ1+fsokWf+uqxgUFjbI0=
|
||||||
|
github.com/gabriel-vasile/mimetype v1.4.3/go.mod h1:d8uq/6HKRL6CGdk+aubisF/M5GcPfT7nKyLpA0lbSSk=
|
||||||
|
github.com/gin-contrib/sse v0.1.0 h1:Y/yl/+YNO8GZSjAhjMsSuLt29uWRFHdHYUb5lYOV9qE=
|
||||||
|
github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI=
|
||||||
|
github.com/gin-gonic/gin v1.10.1 h1:T0ujvqyCSqRopADpgPgiTT63DUQVSfojyME59Ei63pQ=
|
||||||
|
github.com/gin-gonic/gin v1.10.1/go.mod h1:4PMNQiOhvDRa013RKVbsiNwoyezlm2rm0uX/T7kzp5Y=
|
||||||
|
github.com/go-playground/assert/v2 v2.2.0 h1:JvknZsQTYeFEAhQwI4qEt9cyV5ONwRHC+lYKSsYSR8s=
|
||||||
|
github.com/go-playground/assert/v2 v2.2.0/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4=
|
||||||
|
github.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/oXslEjJA=
|
||||||
|
github.com/go-playground/locales v0.14.1/go.mod h1:hxrqLVvrK65+Rwrd5Fc6F2O76J/NuW9t0sjnWqG1slY=
|
||||||
|
github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJnYK9S473LQFuzCbDbfSFY=
|
||||||
|
github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY=
|
||||||
|
github.com/go-playground/validator/v10 v10.20.0 h1:K9ISHbSaI0lyB2eWMPJo+kOS/FBExVwjEviJTixqxL8=
|
||||||
|
github.com/go-playground/validator/v10 v10.20.0/go.mod h1:dbuPbCMFw/DrkbEynArYaCwl3amGuJotoKCe95atGMM=
|
||||||
|
github.com/goccy/go-json v0.10.2 h1:CrxCmQqYDkv1z7lO7Wbh2HN93uovUHgrECaO5ZrCXAU=
|
||||||
|
github.com/goccy/go-json v0.10.2/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I=
|
||||||
|
github.com/google/go-cmp v0.5.5 h1:Khx7svrCpmxxtHBq5j2mp/xVjsi8hQMfNLvJFAlrGgU=
|
||||||
|
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||||
|
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
|
||||||
|
github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM=
|
||||||
|
github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo=
|
||||||
|
github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg=
|
||||||
|
github.com/klauspost/cpuid/v2 v2.2.7 h1:ZWSB3igEs+d0qvnxR/ZBzXVmxkgt8DdzP6m9pfuVLDM=
|
||||||
|
github.com/klauspost/cpuid/v2 v2.2.7/go.mod h1:Lcz8mBdAVJIBVzewtcLocK12l3Y+JytZYpaMropDUws=
|
||||||
|
github.com/knz/go-libedit v1.10.1/go.mod h1:MZTVkCWyz0oBc7JOWP3wNAzd002ZbM/5hgShxwh4x8M=
|
||||||
|
github.com/leodido/go-urn v1.4.0 h1:WT9HwE9SGECu3lg4d/dIA+jxlljEa1/ffXKmRjqdmIQ=
|
||||||
|
github.com/leodido/go-urn v1.4.0/go.mod h1:bvxc+MVxLKB4z00jd1z+Dvzr47oO32F/QSNjSBOlFxI=
|
||||||
|
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
|
||||||
|
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
|
||||||
|
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
|
||||||
|
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg=
|
||||||
|
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
|
||||||
|
github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M=
|
||||||
|
github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
|
||||||
|
github.com/pelletier/go-toml/v2 v2.2.2 h1:aYUidT7k73Pcl9nb2gScu7NSrKCSHIDE89b3+6Wq+LM=
|
||||||
|
github.com/pelletier/go-toml/v2 v2.2.2/go.mod h1:1t835xjRzz80PqgE6HHgN2JOsmgYu/h4qDAS4n929Rs=
|
||||||
|
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/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
|
||||||
|
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
|
||||||
|
github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA=
|
||||||
|
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
|
||||||
|
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||||
|
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||||
|
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
|
||||||
|
github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
|
||||||
|
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
|
||||||
|
github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg=
|
||||||
|
github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
|
||||||
|
github.com/twitchyliquid64/golang-asm v0.15.1 h1:SU5vSMR7hnwNxj24w34ZyCi/FmDZTkS4MhqMhdFk5YI=
|
||||||
|
github.com/twitchyliquid64/golang-asm v0.15.1/go.mod h1:a1lVb/DtPvCB8fslRZhAngC2+aY1QWCk3Cedj/Gdt08=
|
||||||
|
github.com/ugorji/go/codec v1.2.12 h1:9LC83zGrHhuUA9l16C9AHXAqEV/2wBQ4nkvumAE65EE=
|
||||||
|
github.com/ugorji/go/codec v1.2.12/go.mod h1:UNopzCgEMSXjBc6AOMqYvWC1ktqTAfzJZUZgYf6w6lg=
|
||||||
|
golang.org/x/arch v0.0.0-20210923205945-b76863e36670/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8=
|
||||||
|
golang.org/x/arch v0.8.0 h1:3wRIsP3pM4yUptoR96otTUOXI367OS0+c9eeRi9doIc=
|
||||||
|
golang.org/x/arch v0.8.0/go.mod h1:FEVrYAQjsQXMVJ1nsMoVVXPZg6p2JE2mx8psSWTDQys=
|
||||||
|
golang.org/x/crypto v0.23.0 h1:dIJU/v2J8Mdglj/8rJ6UUOM3Zc9zLZxVZwwxMooUSAI=
|
||||||
|
golang.org/x/crypto v0.23.0/go.mod h1:CKFgDieR+mRhux2Lsu27y0fO304Db0wZe70UKqHu0v8=
|
||||||
|
golang.org/x/net v0.25.0 h1:d/OCCoBEUq33pjydKrGQhw7IlUPI2Oylr+8qLx49kac=
|
||||||
|
golang.org/x/net v0.25.0/go.mod h1:JkAGAh7GEvH74S6FOH42FLoXpXbE/aqXSrIQjXgsiwM=
|
||||||
|
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
|
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
|
golang.org/x/sys v0.20.0 h1:Od9JTbYCk261bKm4M/mw7AklTlFYIa0bIp9BgSm1S8Y=
|
||||||
|
golang.org/x/sys v0.20.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||||
|
golang.org/x/text v0.15.0 h1:h1V/4gjBv8v9cjcR6+AR5+/cIYK5N/WAgiv4xlsEtAk=
|
||||||
|
golang.org/x/text v0.15.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
|
||||||
|
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4=
|
||||||
|
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||||
|
google.golang.org/protobuf v1.34.1 h1:9ddQBjfCyZPOHPUiPxpYESBLc+T8P3E+Vo4IbKZgFWg=
|
||||||
|
google.golang.org/protobuf v1.34.1/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos=
|
||||||
|
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.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||||
|
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||||
|
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||||
|
nullprogram.com/x/optparse v1.0.0/go.mod h1:KdyPE+Igbe0jQUrVfMqDMeJQIJZEuyV7pjYmp6pbG50=
|
||||||
|
rsc.io/pdf v0.1.1/go.mod h1:n8OzWcQ6Sp37PL01nO98y4iUCRdTGarVfzxY20ICaU4=
|
307
main.go
307
main.go
@@ -1,23 +1,318 @@
|
|||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"context"
|
||||||
|
"log/slog"
|
||||||
"net/http"
|
"net/http"
|
||||||
"os"
|
"os"
|
||||||
|
"runtime"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/gin-gonic/gin"
|
||||||
)
|
)
|
||||||
|
|
||||||
const version = "1.0.0"
|
const version = "1.0.32"
|
||||||
|
|
||||||
|
// SystemInfo holds system information
|
||||||
|
type SystemInfo struct {
|
||||||
|
GoVersion string `json:"go_version"`
|
||||||
|
OS string `json:"os"`
|
||||||
|
Architecture string `json:"architecture"`
|
||||||
|
NumCPU int `json:"num_cpu"`
|
||||||
|
StartTime string `json:"start_time"`
|
||||||
|
}
|
||||||
|
|
||||||
|
var (
|
||||||
|
startTime = time.Now()
|
||||||
|
logger *slog.Logger
|
||||||
|
)
|
||||||
|
|
||||||
|
// setupLogger configures structured logging with different levels for dev/prod
|
||||||
|
func setupLogger() {
|
||||||
|
var logLevel slog.Level
|
||||||
|
var logFormat string
|
||||||
|
|
||||||
|
// Determine log level from environment
|
||||||
|
switch os.Getenv("LOG_LEVEL") {
|
||||||
|
case "DEBUG":
|
||||||
|
logLevel = slog.LevelDebug
|
||||||
|
case "INFO":
|
||||||
|
logLevel = slog.LevelInfo
|
||||||
|
case "WARN":
|
||||||
|
logLevel = slog.LevelWarn
|
||||||
|
case "ERROR":
|
||||||
|
logLevel = slog.LevelError
|
||||||
|
default:
|
||||||
|
logLevel = slog.LevelInfo
|
||||||
|
}
|
||||||
|
|
||||||
|
// Determine log format from environment
|
||||||
|
logFormat = os.Getenv("LOG_FORMAT")
|
||||||
|
if logFormat == "" {
|
||||||
|
logFormat = "json" // Default to JSON for production
|
||||||
|
}
|
||||||
|
|
||||||
|
// Configure logger based on format
|
||||||
|
var handler slog.Handler
|
||||||
|
opts := &slog.HandlerOptions{
|
||||||
|
Level: logLevel,
|
||||||
|
AddSource: true,
|
||||||
|
ReplaceAttr: func(groups []string, a slog.Attr) slog.Attr {
|
||||||
|
// Customize timestamp format
|
||||||
|
if a.Key == slog.TimeKey {
|
||||||
|
return slog.Attr{
|
||||||
|
Key: slog.TimeKey,
|
||||||
|
Value: slog.StringValue(a.Value.Time().Format("2006-01-02T15:04:05.000Z07:00")),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return a
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
if logFormat == "text" {
|
||||||
|
handler = slog.NewTextHandler(os.Stdout, opts)
|
||||||
|
} else {
|
||||||
|
handler = slog.NewJSONHandler(os.Stdout, opts)
|
||||||
|
}
|
||||||
|
|
||||||
|
logger = slog.New(handler)
|
||||||
|
slog.SetDefault(logger)
|
||||||
|
}
|
||||||
|
|
||||||
|
// logMiddleware creates a structured logging middleware for Gin
|
||||||
|
func logMiddleware() gin.HandlerFunc {
|
||||||
|
return gin.LoggerWithFormatter(func(param gin.LogFormatterParams) string {
|
||||||
|
// Create structured log entry
|
||||||
|
logger.Info("HTTP Request",
|
||||||
|
"method", param.Method,
|
||||||
|
"path", param.Path,
|
||||||
|
"status", param.StatusCode,
|
||||||
|
"latency", param.Latency,
|
||||||
|
"client_ip", param.ClientIP,
|
||||||
|
"user_agent", param.Request.UserAgent(),
|
||||||
|
"timestamp", param.TimeStamp.Format("2006-01-02T15:04:05.000Z07:00"),
|
||||||
|
)
|
||||||
|
return "" // Return empty string as we handle logging ourselves
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// errorLogger logs errors with structured context
|
||||||
|
func ErrorLogger(err error, context ...any) {
|
||||||
|
logger.Error("Application Error",
|
||||||
|
append([]any{"error", err.Error()}, context...)...,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
|
// Setup structured logging
|
||||||
fmt.Fprintf(w, "Hello, World! (Version %s)\n", version)
|
setupLogger()
|
||||||
|
|
||||||
|
logger.Info("Starting application",
|
||||||
|
"version", version,
|
||||||
|
"go_version", runtime.Version(),
|
||||||
|
"os", runtime.GOOS,
|
||||||
|
"architecture", runtime.GOARCH,
|
||||||
|
"num_cpu", runtime.NumCPU(),
|
||||||
|
)
|
||||||
|
|
||||||
|
// Set Gin mode
|
||||||
|
gin.SetMode(gin.ReleaseMode)
|
||||||
|
|
||||||
|
// Create router with custom logger
|
||||||
|
r := gin.New()
|
||||||
|
r.Use(logMiddleware())
|
||||||
|
r.Use(gin.Recovery())
|
||||||
|
|
||||||
|
// Add middleware for CORS
|
||||||
|
r.Use(func(c *gin.Context) {
|
||||||
|
c.Header("Access-Control-Allow-Origin", "*")
|
||||||
|
c.Header("Access-Control-Allow-Methods", "GET, POST, PUT, DELETE, OPTIONS")
|
||||||
|
c.Header("Access-Control-Allow-Headers", "Origin, Content-Type, Content-Length, Accept-Encoding, X-CSRF-Token, Authorization")
|
||||||
|
|
||||||
|
if c.Request.Method == "OPTIONS" {
|
||||||
|
c.AbortWithStatus(http.StatusNoContent)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
c.Next()
|
||||||
})
|
})
|
||||||
|
|
||||||
|
// Health check endpoint
|
||||||
|
r.GET("/healthz", func(c *gin.Context) {
|
||||||
|
logger.Debug("Health check requested", "client_ip", c.ClientIP())
|
||||||
|
c.JSON(http.StatusOK, gin.H{
|
||||||
|
"status": "ok",
|
||||||
|
"version": version,
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
// Main endpoint
|
||||||
|
r.GET("/", func(c *gin.Context) {
|
||||||
|
logger.Info("Root endpoint accessed",
|
||||||
|
"client_ip", c.ClientIP(),
|
||||||
|
"user_agent", c.Request.UserAgent(),
|
||||||
|
)
|
||||||
|
c.JSON(http.StatusOK, gin.H{
|
||||||
|
"message": "Hello, World!",
|
||||||
|
"version": version,
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
// API endpoints
|
||||||
|
api := r.Group("/api/v1")
|
||||||
|
{
|
||||||
|
api.GET("/info", func(c *gin.Context) {
|
||||||
|
logger.Debug("API info requested", "client_ip", c.ClientIP())
|
||||||
|
c.JSON(http.StatusOK, gin.H{
|
||||||
|
"service": "hello-api",
|
||||||
|
"version": version,
|
||||||
|
"status": "running",
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
api.POST("/echo", func(c *gin.Context) {
|
||||||
|
var request struct {
|
||||||
|
Message string `json:"message"`
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := c.ShouldBindJSON(&request); err != nil {
|
||||||
|
logger.Warn("Invalid JSON in echo request",
|
||||||
|
"client_ip", c.ClientIP(),
|
||||||
|
"error", err.Error(),
|
||||||
|
)
|
||||||
|
c.JSON(http.StatusBadRequest, gin.H{
|
||||||
|
"error": "Invalid JSON",
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
logger.Info("Echo request processed",
|
||||||
|
"client_ip", c.ClientIP(),
|
||||||
|
"message_length", len(request.Message),
|
||||||
|
)
|
||||||
|
|
||||||
|
c.JSON(http.StatusOK, gin.H{
|
||||||
|
"echo": request.Message,
|
||||||
|
"version": version,
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
// Configuration endpoint with detailed logging
|
||||||
|
api.GET("/config", func(c *gin.Context) {
|
||||||
|
logger.Debug("Configuration requested", "client_ip", c.ClientIP())
|
||||||
|
|
||||||
|
systemInfo := SystemInfo{
|
||||||
|
GoVersion: runtime.Version(),
|
||||||
|
OS: runtime.GOOS,
|
||||||
|
Architecture: runtime.GOARCH,
|
||||||
|
NumCPU: runtime.NumCPU(),
|
||||||
|
StartTime: startTime.Format(time.RFC3339),
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get environment variables (excluding sensitive ones)
|
||||||
|
envVars := make(map[string]string)
|
||||||
|
sensitiveCount := 0
|
||||||
|
for _, env := range os.Environ() {
|
||||||
|
// Skip sensitive environment variables
|
||||||
|
if !isSensitiveEnvVar(env) {
|
||||||
|
envVars[env] = os.Getenv(env)
|
||||||
|
} else {
|
||||||
|
sensitiveCount++
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
logger.Info("Configuration accessed",
|
||||||
|
"client_ip", c.ClientIP(),
|
||||||
|
"env_vars_count", len(envVars),
|
||||||
|
"sensitive_vars_filtered", sensitiveCount,
|
||||||
|
"uptime", time.Since(startTime).String(),
|
||||||
|
)
|
||||||
|
|
||||||
|
c.JSON(http.StatusOK, gin.H{
|
||||||
|
"version": version,
|
||||||
|
"system_info": systemInfo,
|
||||||
|
"environment": envVars,
|
||||||
|
"uptime": time.Since(startTime).String(),
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
// New logging endpoint to test log levels
|
||||||
|
api.POST("/log-test", func(c *gin.Context) {
|
||||||
|
var request struct {
|
||||||
|
Level string `json:"level"`
|
||||||
|
Message string `json:"message"`
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := c.ShouldBindJSON(&request); err != nil {
|
||||||
|
logger.Warn("Invalid JSON in log-test request",
|
||||||
|
"client_ip", c.ClientIP(),
|
||||||
|
"error", err.Error(),
|
||||||
|
)
|
||||||
|
c.JSON(http.StatusBadRequest, gin.H{
|
||||||
|
"error": "Invalid JSON",
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Log based on requested level
|
||||||
|
switch request.Level {
|
||||||
|
case "debug":
|
||||||
|
logger.Debug(request.Message, "client_ip", c.ClientIP())
|
||||||
|
case "info":
|
||||||
|
logger.Info(request.Message, "client_ip", c.ClientIP())
|
||||||
|
case "warn":
|
||||||
|
logger.Warn(request.Message, "client_ip", c.ClientIP())
|
||||||
|
case "error":
|
||||||
|
logger.Error(request.Message, "client_ip", c.ClientIP())
|
||||||
|
default:
|
||||||
|
logger.Info(request.Message, "client_ip", c.ClientIP(), "level", request.Level)
|
||||||
|
}
|
||||||
|
|
||||||
|
c.JSON(http.StatusOK, gin.H{
|
||||||
|
"status": "logged",
|
||||||
|
"level": request.Level,
|
||||||
|
"message": request.Message,
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get port from environment or use default
|
||||||
port := os.Getenv("PORT")
|
port := os.Getenv("PORT")
|
||||||
if port == "" {
|
if port == "" {
|
||||||
port = "8080"
|
port = "8080"
|
||||||
}
|
}
|
||||||
|
|
||||||
fmt.Printf("Server starting on port %s...\n", port)
|
// Start server with structured logging
|
||||||
http.ListenAndServe(":"+port, nil)
|
logger.Info("Server starting",
|
||||||
|
"port", port,
|
||||||
|
"mode", gin.Mode(),
|
||||||
|
)
|
||||||
|
|
||||||
|
ctx := context.Background()
|
||||||
|
go func() {
|
||||||
|
// Graceful shutdown handling
|
||||||
|
<-ctx.Done()
|
||||||
|
logger.Info("Shutdown signal received, stopping server...")
|
||||||
|
}()
|
||||||
|
|
||||||
|
err := r.Run(":" + port)
|
||||||
|
if err != nil {
|
||||||
|
logger.Error("Server failed to start", "error", err.Error())
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// isSensitiveEnvVar checks if an environment variable contains sensitive information
|
||||||
|
func isSensitiveEnvVar(env string) bool {
|
||||||
|
sensitivePrefixes := []string{
|
||||||
|
"PASSWORD", "SECRET", "KEY", "TOKEN", "CREDENTIAL",
|
||||||
|
"AWS_", "GITHUB_", "DOCKER_", "KUBERNETES_",
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, prefix := range sensitivePrefixes {
|
||||||
|
if len(env) >= len(prefix) && env[:len(prefix)] == prefix {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
}
|
}
|
17
makefile
17
makefile
@@ -1,8 +1,8 @@
|
|||||||
.PHONY: build clean test
|
.PHONY: build clean test release
|
||||||
|
|
||||||
BIN_DIR=bin
|
BIN_DIR=bin
|
||||||
APP_NAME=hello-api
|
APP_NAME=hello-api
|
||||||
VERSION=1.0.0
|
VERSION=1.0.32
|
||||||
|
|
||||||
build:
|
build:
|
||||||
mkdir -p $(BIN_DIR)
|
mkdir -p $(BIN_DIR)
|
||||||
@@ -13,3 +13,16 @@ clean:
|
|||||||
|
|
||||||
test:
|
test:
|
||||||
go test -v ./...
|
go test -v ./...
|
||||||
|
|
||||||
|
# Задача для создания релиза
|
||||||
|
# Использование: make release VERSION=1.0.25
|
||||||
|
release:
|
||||||
|
@if [ -z "$(VERSION)" ]; then \
|
||||||
|
echo "Ошибка: Необходимо указать версию в формате v1.0.25"; \
|
||||||
|
echo "Использование: make release VERSION=1.0.25"; \
|
||||||
|
exit 1; \
|
||||||
|
fi
|
||||||
|
@./scripts/release-interactive.sh $(VERSION)
|
||||||
|
|
||||||
|
release-interactive:
|
||||||
|
@./scripts/release-interactive.sh
|
19
scripts/quick-build.sh
Normal file
19
scripts/quick-build.sh
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
if [ ! -f go.mod ]; then
|
||||||
|
echo "go.mod not found in current directory"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Проверяем, изменились ли зависимости
|
||||||
|
if [ ! -f go.sum ] || ! go mod verify >/dev/null 2>&1; then
|
||||||
|
echo "Dependencies changed, downloading..."
|
||||||
|
go mod download
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Собираем для указанной платформы
|
||||||
|
if [ -n "$1" ] && [ -n "$2" ]; then
|
||||||
|
GOOS=$1 GOARCH=$2 go build -o "hello-api-$1-$2" main.go
|
||||||
|
else
|
||||||
|
go build -o hello-api main.go
|
||||||
|
fi
|
133
scripts/release-interactive.sh
Executable file
133
scripts/release-interactive.sh
Executable file
@@ -0,0 +1,133 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
# Интерактивный скрипт для автоматизации релиза
|
||||||
|
# Использование: ./scripts/release-interactive.sh [version]
|
||||||
|
# Если версия не указана, скрипт запросит её интерактивно
|
||||||
|
|
||||||
|
set -e # Остановить выполнение при ошибке
|
||||||
|
|
||||||
|
# Функция для получения версии интерактивно
|
||||||
|
get_version_interactive() {
|
||||||
|
echo "🚀 Создание нового релиза"
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
# Показываем текущую версию
|
||||||
|
CURRENT_VERSION=$(grep 'const version = "' main.go | sed 's/const version = "\([^"]*\)"/\1/')
|
||||||
|
echo "📋 Текущая версия: $CURRENT_VERSION"
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
# Запрашиваем новую версию
|
||||||
|
read -r -p "Введите новую версию (формат X.Y.Z): " VERSION
|
||||||
|
|
||||||
|
# Проверяем, что версия не пустая
|
||||||
|
if [ -z "$VERSION" ]; then
|
||||||
|
echo "❌ Версия не может быть пустой"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Проверяем формат версии
|
||||||
|
if [[ ! $VERSION =~ ^[0-9]+\.[0-9]+\.[0-9]+$ ]]; then
|
||||||
|
echo "❌ Неверный формат версии. Используйте формат X.Y.Z (например, 1.0.25)"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Подтверждение
|
||||||
|
echo ""
|
||||||
|
echo "📝 Подтверждение:"
|
||||||
|
echo " Текущая версия: $CURRENT_VERSION"
|
||||||
|
echo " Новая версия: $VERSION"
|
||||||
|
echo ""
|
||||||
|
read -r -p "Продолжить? (y/N): " CONFIRM
|
||||||
|
|
||||||
|
if [[ ! $CONFIRM =~ ^[Yy]$ ]]; then
|
||||||
|
echo "❌ Релиз отменен"
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
# Проверяем, передана ли версия как аргумент
|
||||||
|
if [ $# -eq 0 ]; then
|
||||||
|
# Версия не указана, запрашиваем интерактивно
|
||||||
|
get_version_interactive
|
||||||
|
else
|
||||||
|
# Версия указана как аргумент
|
||||||
|
VERSION=$1
|
||||||
|
|
||||||
|
# Проверка формата версии
|
||||||
|
if [[ ! $VERSION =~ ^[0-9]+\.[0-9]+\.[0-9]+$ ]]; then
|
||||||
|
echo "Ошибка: Неверный формат версии. Используйте формат X.Y.Z (например, 1.0.25)"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "🚀 Начинаем релиз версии v$VERSION..."
|
||||||
|
|
||||||
|
# Проверяем, что мы в git репозитории
|
||||||
|
if ! git rev-parse --git-dir > /dev/null 2>&1; then
|
||||||
|
echo "Ошибка: Не найден git репозиторий"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Проверяем, что нет незакоммиченных изменений
|
||||||
|
# if ! git diff-index --quiet HEAD --; then
|
||||||
|
# echo "Ошибка: Есть незакоммиченные изменения. Сначала закоммитьте их."
|
||||||
|
# exit 1
|
||||||
|
# fi
|
||||||
|
|
||||||
|
# Обновляем версию в main.go
|
||||||
|
echo "📝 Обновляем версию в main.go..."
|
||||||
|
if [[ "$OSTYPE" == "darwin"* ]]; then
|
||||||
|
# macOS
|
||||||
|
sed -i '' "s/const version = \"[^\"]*\"/const version = \"$VERSION\"/" main.go
|
||||||
|
else
|
||||||
|
# Linux
|
||||||
|
sed -i "s/const version = \"[^\"]*\"/const version = \"$VERSION\"/" main.go
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Проверяем, что изменение применилось
|
||||||
|
if ! grep -q "const version = \"$VERSION\"" main.go; then
|
||||||
|
echo "Ошибка: Не удалось обновить версию в main.go"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "✅ Версия обновлена в main.go"
|
||||||
|
|
||||||
|
# Обновляем версию в makefile
|
||||||
|
echo "📝 Обновляем версию в makefile..."
|
||||||
|
if [[ "$OSTYPE" == "darwin"* ]]; then
|
||||||
|
# macOS
|
||||||
|
sed -i '' "s/^VERSION=.*/VERSION=$VERSION/" makefile
|
||||||
|
else
|
||||||
|
# Linux
|
||||||
|
sed -i "s/^VERSION=.*/VERSION=$VERSION/" makefile
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Проверяем, что изменение применилось
|
||||||
|
if ! grep -q "^VERSION=$VERSION" makefile; then
|
||||||
|
echo "Ошибка: Не удалось обновить версию в makefile"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "✅ Версия обновлена в makefile"
|
||||||
|
|
||||||
|
# Выполняем git команды
|
||||||
|
echo "📦 Добавляем изменения в git..."
|
||||||
|
git add .
|
||||||
|
|
||||||
|
echo "💾 Создаем коммит..."
|
||||||
|
git commit -m "Release v$VERSION"
|
||||||
|
|
||||||
|
echo "🏷️ Создаем тег..."
|
||||||
|
git tag -a "v$VERSION" -m "Release v$VERSION"
|
||||||
|
|
||||||
|
echo "🚀 Отправляем изменения и теги..."
|
||||||
|
git push
|
||||||
|
git push --tags
|
||||||
|
|
||||||
|
echo "🎉 Релиз v$VERSION успешно завершен!"
|
||||||
|
echo "📋 Выполненные действия:"
|
||||||
|
echo " - Обновлена версия в main.go"
|
||||||
|
echo " - Обновлена версия в makefile"
|
||||||
|
echo " - Создан коммит с сообщением 'Release v$VERSION'"
|
||||||
|
echo " - Создан тег v$VERSION"
|
||||||
|
echo " - Изменения отправлены в удаленный репозиторий"
|
Reference in New Issue
Block a user