/
vadimp
/
cloud-worker
Обзор
Документация
Войти
/
vadimp
/
cloud-worker
Код
Запросы
0
Задачи
Вики
Пакеты
0
Релизы
0
Аналитика
Безопасность
master
main.go
272 строки
7 KB
Vadim Parinov
build
29 ноя 2024, 18:49
29 ноя 2024, 18:49
89ea98c
Код
Авторство
О чём код?
package main import ( "bytes" "fmt" "log" "os" "os/exec" "path/filepath" "strings" "time" "github.com/go-git/go-git/v5" "github.com/streadway/amqp" ) const ( rabbitMQURL = "amqps://wbcrxsyc:cBkb0c_B7zciCuexqaEf5DzJYc8ITdsv@gull.rmq.cloudamqp.com/wbcrxsyc" queueName = "docker-build-queue" baseDir = "/home/vadim/docker-builds" ) func main() { conn, err := amqp.Dial(rabbitMQURL) if err != nil { panic("Failed to connect to RabbitMQ") } defer conn.Close() ch, err := conn.Channel() if err != nil { panic("Failed to open a channel") } _, err = ch.QueueDeclare( queueName, // имя очереди true, // устойчивая false, // удаляется при отсутствии подписчиков false, // эксклюзивная false, // без ожидания nil, // аргументы ) if err != nil { panic("Failed to declare a queue") } //err = enqueueRepository(ch, "https://ghu_Yfo8yxlAh2Ayd4sizf5p58pslpzqmS3tbZal@github.com/parinov-vadim/test-next-js.git") if err != nil { log.Printf("Failed to enqueue repository: %v", err) } msgs, err := ch.Consume( queueName, // очередь "", // имя потребителя true, // автоматическое подтверждение false, // эксклюзивный false, // без локализации false, // без ожидания nil, // аргументы ) if err != nil { log.Fatalf("Failed to register a consumer: %v", err) } log.Println("Worker started. Waiting for messages...") forever := make(chan bool) go func() { for d := range msgs { repoURL := string(d.Body) log.Printf("Received message: %s", repoURL) err := processRepository(repoURL) if err != nil { log.Printf("Error processing repository: %v", err) } else { log.Printf("Successfully built Docker image from %s", repoURL) } } }() <-forever } func enqueueRepository(ch *amqp.Channel, repoURL string) error { err := ch.Publish( "", // exchange queueName, // routing key (имя очереди) false, // mandatory false, // immediate amqp.Publishing{ ContentType: "text/plain", Body: []byte(repoURL), }, ) if err != nil { return fmt.Errorf("failed to publish message: %v", err) } log.Printf("Repository enqueued: %s", repoURL) return nil } func createNextJsDockerfile(repoDir string) error { dockerfilePath := filepath.Join(repoDir, "Dockerfile") content := ` # Базовый образ для Node.js FROM node:20-alpine AS builder # Рабочая директория в контейнере WORKDIR /app # Копируем package.json и package-lock.json COPY package*.json ./ # Устанавливаем зависимости RUN npm install # Копируем весь проект COPY . . # Сборка проекта RUN npm run build # Production Stage FROM node:20-alpine AS runner WORKDIR /app # Копируем только необходимые файлы из builder COPY --from=builder /app/package*.json ./ COPY --from=builder /app/.next ./.next COPY --from=builder /app/public ./public COPY --from=builder /app/node_modules ./node_modules # Устанавливаем переменную окружения ENV NODE_ENV=production EXPOSE 3000 # Запускаем приложение CMD ["npm", "start"] ` err := os.WriteFile(dockerfilePath, []byte(content), 0644) if err != nil { return fmt.Errorf("failed to write Dockerfile: %v", err) } log.Printf("Dockerfile created at %s", dockerfilePath) return nil } func processRepository(repoURL string) error { // Извлечение имени репозитория из URL repoName := getRepoNameFromURL(repoURL) if repoName == "" { return fmt.Errorf("invalid repository URL: %s", repoURL) } // Создание уникальной временной директории для сборки tempDir := filepath.Join(baseDir, fmt.Sprintf("%s-%d", repoName, time.Now().UnixNano())) log.Printf("Cloning repository %s to %s", repoURL, tempDir) // Клонирование репозитория _, err := git.PlainClone(tempDir, false, &git.CloneOptions{ URL: repoURL, Progress: os.Stdout, }) if err != nil { return fmt.Errorf("failed to clone repository: %v", err) } // Создание Dockerfile для Next.js err = createNextJsDockerfile(tempDir) if err != nil { return fmt.Errorf("failed to create Dockerfile: %v", err) } // Построение Docker-образа imageTag := fmt.Sprintf("%s:%d", repoName, time.Now().Unix()) err = buildDockerImage(tempDir, imageTag) if err != nil { return fmt.Errorf("failed to build Docker image: %v", err) } log.Printf("Docker image %s built successfully", imageTag) // Формируем имя образа для реестра registry := "cloudapps.cr.cloud.ru" remoteImageTag := fmt.Sprintf("%s/%s:latest", registry, repoName) // Пушим образ в реестр err = pushDockerImage(imageTag, remoteImageTag) if err != nil { log.Printf("Error pushing Docker image: %v", err) return fmt.Errorf("failed to push Docker image: %v", err) } // Удаление временной директории после завершения сборки err = os.RemoveAll(tempDir) if err != nil { log.Printf("Failed to remove temporary directory: %v", err) } return nil } func buildDockerImage(contextDir, tag string) error { log.Printf("Starting Docker build for context: %s, image tag: %s", contextDir, tag) // Подготовка команды cmd := exec.Command("docker", "build", "-t", tag, contextDir) var out bytes.Buffer var stderr bytes.Buffer cmd.Stdout = &out cmd.Stderr = &stderr // Запуск команды err := cmd.Run() // Логирование результата if err != nil { log.Printf("Docker build failed with error: %s", err) log.Printf("Docker stderr: %s", stderr.String()) return fmt.Errorf("docker build error: %v", err) } log.Printf("Docker build output: %s", out.String()) log.Printf("Docker image %s built successfully.", tag) return nil } func pushDockerImage(localTag, remoteTag string) error { log.Printf("Tagging image %s as %s", localTag, remoteTag) // Тегируем локальный образ с именем для реестра cmd := exec.Command("docker", "tag", localTag, remoteTag) if err := cmd.Run(); err != nil { return fmt.Errorf("failed to tag Docker image: %v", err) } // Пушим образ в реестр log.Printf("Pushing Docker image %s", remoteTag) cmd = exec.Command("docker", "push", remoteTag) var stderr bytes.Buffer cmd.Stderr = &stderr err := cmd.Run() if err != nil { log.Printf("Docker push failed: %s", stderr.String()) return fmt.Errorf("docker push error: %v", err) } log.Printf("Docker image %s pushed successfully", remoteTag) return nil } // getRepoNameFromURL извлекает имя репозитория из URL func getRepoNameFromURL(repoURL string) string { // Разделить URL на части и взять последний элемент parts := strings.Split(repoURL, "github.com/") if len(parts) == 0 { return "" } parts = strings.Split(parts[len(parts)-1], "/") if len(parts) == 0 { return "" } str := parts[0] + "-" + parts[1] // Убираем суффикс .git, если он есть return strings.TrimSuffix(str, ".git") }