/
Anabol1ks
/
test_hack
Обзор
Документация
Войти
/
Anabol1ks
/
test_hack
Код
Запросы
0
Задачи
Вики
Пакеты
0
Релизы
0
CI/CD
Аналитика
Безопасность
master
internal/gen/gitlab.go
253 строки
7 KB
Anabol1ks
feat: добавлена поддержка установки make для alpine в CI/CD конфигурации
10 ноя 2025, 19:17
10 ноя 2025, 19:17
951e8b1
Код
Авторство
О чём код?
package gen import ( "bytes" "fmt" "strings" "text/template" "ciwzd/internal/core" ) var gitlabT = template.Must(template.New("gitlab").Parse(` stages: [build, test] default: image: {{.Image}} {{- if .UseDocker }} services: - docker:dind variables: DOCKER_DRIVER: overlay2 DOCKER_TLS_CERTDIR: "/certs" {{- else }} before_script: {{- if .NeedsMake }} - apk add --no-cache make {{- end}} - {{.Setup}} {{- end}} {{- if .CIVariables }} # Переменные окружения, необходимые для CI/CD # Установите их в Settings > CI/CD > Variables variables: {{- range .CIVariables }} {{.Name}}: ${{.Name}}{{if .Description}} # {{.Description}}{{end}} {{- end}} {{- end}} build: stage: build script: {{- range .Build }} - {{ . }} {{- end}} rules: - if: '$CI_COMMIT_BRANCH' test: stage: test {{- if not .UseDocker }} script: {{- range .Test }} - {{ . }} {{- end}} {{- else }} script: - echo "Tests should run inside Docker container" {{- end}} `)) type GitLabData struct { Image string Setup string Build []string Test []string UseDocker bool NeedsMake bool // нужна ли установка make для alpine CIVariables []core.EnvVariable // переменные, необходимые в CI/CD } type SimpleRenderer struct { validator *ImageValidator } func NewSimpleRenderer() *SimpleRenderer { return &SimpleRenderer{ validator: NewImageValidator(), } } func pickImage(res core.AnalysisResult) string { // Приоритет 1: Если есть Dockerfile, используем docker:latest для multi-stage builds if res.Dockerfile { return "docker:latest" } // Приоритет 2: Определяем по фреймворкам (наиболее точный контекст) frameworks := strings.Join(res.Frameworks, ",") // Backend фреймворки Go if strings.Contains(frameworks, "gin") || strings.Contains(frameworks, "fiber") || strings.Contains(frameworks, "echo") || strings.Contains(frameworks, "grpc") { if ver, ok := res.Versions["go"]; ok && ver != "" { return "golang:" + ver } return "golang:1.23" } // Backend фреймворки Python if strings.Contains(frameworks, "fastapi") || strings.Contains(frameworks, "django") || strings.Contains(frameworks, "flask") { return pickPythonImage(res.Versions) } // Frontend фреймворки (Node.js) if strings.Contains(frameworks, "react") || strings.Contains(frameworks, "vue") || strings.Contains(frameworks, "next") || strings.Contains(frameworks, "express") { return pickNodeImage(res.Versions) } // Приоритет 3: Fallback на языки с учетом их количества и контекста langs := strings.Join(res.Languages, ",") hasGo := strings.Contains(langs, "go") hasPython := strings.Contains(langs, "python") hasJS := strings.Contains(langs, "javascript") switch { case hasGo: if ver, ok := res.Versions["go"]; ok && ver != "" { return "golang:" + ver } return "golang:1.23" case hasPython: return pickPythonImage(res.Versions) case hasJS: return pickNodeImage(res.Versions) default: return "alpine:3.20" } } // pickPythonImage извлекает образ Python с версией func pickPythonImage(versions map[string]string) string { if ver, ok := versions["python"]; ok && ver != "" { // Извлекаем major.minor (например, из "3.11.5" или "3.10,<4.0" берём "3.10") ver = strings.TrimLeft(ver, "^~>=") parts := strings.Split(ver, ",") if len(parts) > 0 { ver = strings.TrimSpace(parts[0]) dotParts := strings.Split(ver, ".") if len(dotParts) >= 2 { return "python:" + dotParts[0] + "." + dotParts[1] } } } return "python:3.12" } // pickNodeImage извлекает образ Node с версией func pickNodeImage(versions map[string]string) string { if ver, ok := versions["node"]; ok && ver != "" { // Извлекаем major версию (например, из ">=18.0.0" берём "18") parts := strings.Split(ver, ".") if len(parts) > 0 { major := strings.TrimLeft(parts[0], ">=<^~") major = strings.TrimSpace(major) return "node:" + major } } return "node:20" } func pickSetup(pm []string) string { // Приоритизируем пакетные менеджеры pmStr := strings.Join(pm, ",") switch { case strings.Contains(pmStr, "go"): return "go env" case strings.Contains(pmStr, "poetry"): return "pip install poetry" case strings.Contains(pmStr, "uv"): return "pip install uv" case strings.Contains(pmStr, "pip"), strings.Contains(pmStr, "pipenv"): return "python --version" case strings.Contains(pmStr, "pnpm"): return "corepack enable && pnpm -v" case strings.Contains(pmStr, "yarn"): return "corepack enable && yarn -v" case strings.Contains(pmStr, "npm"): return "npm -v" default: return "echo ok" } } func (r *SimpleRenderer) RenderGitLab(res core.AnalysisResult, _ map[string]any) ([]byte, error) { image := pickImage(res) // Валидируем образ и получаем существующий вариант validatedImage := r.validator.ValidateImage(image) useDocker := validatedImage == "docker:latest" // Проверяем, нужна ли установка make (если alpine и есть команды make) needsMake := false if strings.Contains(validatedImage, "alpine") { for _, cmd := range append(res.Build, res.Test...) { if strings.HasPrefix(cmd, "make ") { needsMake = true break } } } // Фильтруем только переменные, необходимые для CI/CD var ciVariables []core.EnvVariable for _, v := range res.EnvVariables { if v.IsCINeeded { ciVariables = append(ciVariables, v) } } data := GitLabData{ Image: validatedImage, Setup: pickSetup(res.PackageManagers), Build: res.Build, Test: res.Test, UseDocker: useDocker, NeedsMake: needsMake, CIVariables: ciVariables, } // Если используем Docker, настраиваем build команды if useDocker && len(data.Build) == 0 { data.Build = []string{ "docker build -t $CI_REGISTRY_IMAGE:$CI_COMMIT_SHORT_SHA .", "docker tag $CI_REGISTRY_IMAGE:$CI_COMMIT_SHORT_SHA $CI_REGISTRY_IMAGE:latest", } } // sane defaults если анализ ничего не нашёл if !useDocker { if len(data.Build) == 0 && strings.Contains(strings.Join(res.Languages, ","), "go") { data.Build = []string{"go build ./..."} } if len(data.Test) == 0 && strings.Contains(strings.Join(res.Languages, ","), "go") { data.Test = []string{"go test ./..."} } } var buf bytes.Buffer if err := gitlabT.Execute(&buf, data); err != nil { return nil, fmt.Errorf("template execution failed: %w", err) } return buf.Bytes(), nil }