445 lines
9.2 KiB
Go
445 lines
9.2 KiB
Go
package template
|
|
|
|
import (
|
|
"bufio"
|
|
"database/sql"
|
|
"encoding/json"
|
|
"errors"
|
|
"fmt"
|
|
"github.com/labstack/echo/v4"
|
|
"io"
|
|
"net/http"
|
|
"strings"
|
|
"tesseract/internal/service"
|
|
)
|
|
|
|
type createTemplateRequestBody struct {
|
|
Description string `json:"description"`
|
|
Content string `json:"content"`
|
|
Documentation string `json:"documentation"`
|
|
}
|
|
|
|
type updateTemplateRequestBody struct {
|
|
Description *string `json:"description"`
|
|
Files []templateFile `json:"files"`
|
|
|
|
ImageTag *string `json:"imageTag"`
|
|
BuildArgs map[string]*string `json:"buildArgs"`
|
|
}
|
|
|
|
type templateBuildLogEvent struct {
|
|
Type string `json:"type"`
|
|
LogContent string `json:"logContent"`
|
|
}
|
|
|
|
type templateBuildFinishedEvent struct {
|
|
Type string `json:"type"`
|
|
Template *template `json:"template"`
|
|
}
|
|
|
|
func fetchAllTemplates(c echo.Context) error {
|
|
db := service.Database(c)
|
|
|
|
var templates []template
|
|
err := db.NewSelect().Model(&templates).Scan(c.Request().Context())
|
|
if err != nil {
|
|
if errors.Is(err, sql.ErrNoRows) {
|
|
return c.JSON(http.StatusOK, make([]template, 0))
|
|
}
|
|
return err
|
|
}
|
|
|
|
if len(templates) == 0 {
|
|
return c.JSON(http.StatusOK, make([]template, 0))
|
|
}
|
|
return c.JSON(http.StatusOK, templates)
|
|
}
|
|
|
|
func fetchTemplate(c echo.Context) error {
|
|
db := service.Database(c)
|
|
|
|
name := c.Param("templateName")
|
|
if strings.TrimSpace(name) == "" {
|
|
return echo.NewHTTPError(http.StatusNotFound)
|
|
}
|
|
|
|
var tmpl template
|
|
err := db.NewSelect().Model(&tmpl).
|
|
Relation("Files").
|
|
Where("name = ?", name).
|
|
Scan(c.Request().Context())
|
|
if err != nil {
|
|
if errors.Is(err, sql.ErrNoRows) {
|
|
return echo.NewHTTPError(http.StatusNotFound)
|
|
}
|
|
return err
|
|
}
|
|
|
|
if len(tmpl.Files) > 0 {
|
|
tmpl.FileMap = make(map[string]*templateFile)
|
|
}
|
|
for _, f := range tmpl.Files {
|
|
tmpl.FileMap[f.FilePath] = f
|
|
}
|
|
|
|
return c.JSON(http.StatusOK, tmpl)
|
|
}
|
|
|
|
func createOrUpdateTemplate(c echo.Context) error {
|
|
db := service.Database(c)
|
|
|
|
name := c.Param("templateName")
|
|
if strings.TrimSpace(name) == "" {
|
|
return echo.NewHTTPError(http.StatusNotFound)
|
|
}
|
|
|
|
exists, err := db.NewSelect().
|
|
Table("templates").
|
|
Where("name = ?", name).
|
|
Exists(c.Request().Context())
|
|
if err != nil {
|
|
if errors.Is(err, sql.ErrNoRows) {
|
|
return createTemplate(c)
|
|
}
|
|
return err
|
|
}
|
|
|
|
if !exists {
|
|
return createTemplate(c)
|
|
}
|
|
|
|
return updateTemplate(c)
|
|
}
|
|
|
|
func createTemplate(c echo.Context) error {
|
|
db := service.Database(c)
|
|
name := c.Param("templateName")
|
|
|
|
var body createTemplateRequestBody
|
|
err := json.NewDecoder(c.Request().Body).Decode(&body)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
tx, err := db.BeginTx(c.Request().Context(), nil)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
createdTemplate, err := createDockerTemplate(c.Request().Context(), tx, createTemplateOptions{
|
|
name: name,
|
|
description: body.Description,
|
|
})
|
|
if err != nil {
|
|
_ = tx.Rollback()
|
|
return err
|
|
}
|
|
|
|
if err = tx.Commit(); err != nil {
|
|
_ = tx.Rollback()
|
|
return err
|
|
}
|
|
|
|
return c.JSON(http.StatusOK, createdTemplate)
|
|
}
|
|
|
|
func updateTemplate(c echo.Context) error {
|
|
db := service.Database(c)
|
|
name := c.Param("templateName")
|
|
|
|
var body updateTemplateRequestBody
|
|
err := json.NewDecoder(c.Request().Body).Decode(&body)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
if body.BuildArgs != nil && body.ImageTag == nil {
|
|
return echo.NewHTTPError(http.StatusBadRequest, "Image tag must be specified if buildArgs is passed")
|
|
}
|
|
|
|
tx, err := db.BeginTx(c.Request().Context(), nil)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
var tmpl template
|
|
err = tx.NewSelect().Model(&tmpl).
|
|
Where("name = ?", name).
|
|
Scan(c.Request().Context())
|
|
if err != nil {
|
|
if errors.Is(err, sql.ErrNoRows) {
|
|
return echo.NewHTTPError(http.StatusNotFound)
|
|
}
|
|
return err
|
|
}
|
|
|
|
if body.Description != nil {
|
|
tmpl.Description = *body.Description
|
|
_, err = tx.NewUpdate().Model(&tmpl).
|
|
Column("description").
|
|
WherePK().
|
|
Exec(c.Request().Context())
|
|
if err != nil {
|
|
_ = tx.Rollback()
|
|
return err
|
|
}
|
|
|
|
if err = tx.Commit(); err != nil {
|
|
_ = tx.Rollback()
|
|
return err
|
|
}
|
|
}
|
|
|
|
if body.ImageTag != nil {
|
|
err = tx.NewSelect().Model(&tmpl.Files).
|
|
Where("template_id = ?", tmpl.ID).
|
|
Scan(c.Request().Context())
|
|
if err != nil {
|
|
_ = tx.Rollback()
|
|
return err
|
|
}
|
|
|
|
docker := service.DockerClient(c)
|
|
log, err := buildDockerTemplate(c.Request().Context(), docker, &tmpl, templateBuildOptions{
|
|
imageTag: *body.ImageTag,
|
|
buildArgs: body.BuildArgs,
|
|
})
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
w := c.Response()
|
|
w.Header().Set("Content-Type", "text/event-stream")
|
|
w.Header().Set("Cache-Control", "no-cache")
|
|
w.Header().Set("Connection", "keep-alive")
|
|
|
|
scanner := bufio.NewScanner(log)
|
|
|
|
var imageID string
|
|
|
|
for scanner.Scan() {
|
|
t := scanner.Text()
|
|
|
|
fmt.Println("DOCKER LOG: ", t)
|
|
|
|
var msg map[string]any
|
|
err = json.Unmarshal([]byte(t), &msg)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
if stream, ok := msg["stream"].(string); ok {
|
|
if _, err = w.Write([]byte(stream)); err != nil {
|
|
return err
|
|
}
|
|
w.Flush()
|
|
} else if errmsg, ok := msg["error"].(string); ok {
|
|
if _, err = w.Write([]byte(errmsg + "\n")); err != nil {
|
|
return err
|
|
}
|
|
w.Flush()
|
|
} else if status, ok := msg["status"].(string); ok {
|
|
var text string
|
|
if progress, ok := msg["progress"].(string); ok {
|
|
text = fmt.Sprintf("%v: %v\n", status, progress)
|
|
} else {
|
|
text = status + "\n"
|
|
}
|
|
if _, err = w.Write([]byte(text)); err != nil {
|
|
return err
|
|
}
|
|
w.Flush()
|
|
} else if aux, ok := msg["aux"].(map[string]any); ok {
|
|
if id, ok := aux["ID"].(string); ok {
|
|
imageID = id
|
|
}
|
|
}
|
|
}
|
|
|
|
if imageID != "" {
|
|
img := TemplateImage{
|
|
TemplateID: tmpl.ID,
|
|
ImageTag: *body.ImageTag,
|
|
ImageID: imageID,
|
|
}
|
|
|
|
_, err = tx.NewInsert().Model(&img).Exec(c.Request().Context())
|
|
if err != nil {
|
|
_ = tx.Rollback()
|
|
return err
|
|
}
|
|
}
|
|
|
|
if err = tx.Commit(); err != nil {
|
|
_ = tx.Rollback()
|
|
return err
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
if err = tx.Commit(); err != nil {
|
|
_ = tx.Rollback()
|
|
return err
|
|
}
|
|
|
|
return c.JSON(http.StatusOK, &tmpl)
|
|
}
|
|
|
|
func deleteTemplate(c echo.Context) error {
|
|
templateName := c.Param("templateName")
|
|
if templateName == "" {
|
|
return echo.NewHTTPError(http.StatusNotFound)
|
|
}
|
|
|
|
db := service.Database(c)
|
|
|
|
tx, err := db.BeginTx(c.Request().Context(), nil)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
res, err := tx.NewDelete().Table("templates").
|
|
Where("name = ?", templateName).
|
|
Exec(c.Request().Context())
|
|
if err != nil {
|
|
if errors.Is(err, sql.ErrNoRows) {
|
|
return echo.NewHTTPError(http.StatusNotFound)
|
|
}
|
|
_ = tx.Rollback()
|
|
return err
|
|
}
|
|
|
|
count, err := res.RowsAffected()
|
|
if err != nil {
|
|
_ = tx.Rollback()
|
|
return err
|
|
}
|
|
|
|
if count != 1 {
|
|
_ = tx.Rollback()
|
|
return echo.NewHTTPError(http.StatusInternalServerError)
|
|
}
|
|
|
|
if err = tx.Commit(); err != nil {
|
|
_ = tx.Rollback()
|
|
return err
|
|
}
|
|
|
|
return c.NoContent(http.StatusOK)
|
|
}
|
|
|
|
func fetchTemplateFile(c echo.Context) error {
|
|
templateName := c.Param("templateName")
|
|
if templateName == "" {
|
|
return echo.NewHTTPError(http.StatusNotFound)
|
|
}
|
|
|
|
filePath := c.Param("filePath")
|
|
if filePath == "" {
|
|
return echo.NewHTTPError(http.StatusNotFound)
|
|
}
|
|
|
|
db := service.Database(c)
|
|
|
|
var tmpl template
|
|
err := db.NewSelect().Model(&tmpl).
|
|
Column("id").
|
|
Where("name = ?", templateName).
|
|
Scan(c.Request().Context())
|
|
if err != nil {
|
|
if errors.Is(err, sql.ErrNoRows) {
|
|
return echo.NewHTTPError(http.StatusNotFound)
|
|
}
|
|
return err
|
|
}
|
|
|
|
var file templateFile
|
|
err = db.NewSelect().Model(&file).
|
|
Where("template_id = ?", tmpl.ID).
|
|
Where("file_path = ?", filePath).
|
|
Scan(c.Request().Context())
|
|
if err != nil {
|
|
if errors.Is(err, sql.ErrNoRows) {
|
|
return echo.NewHTTPError(http.StatusNotFound)
|
|
}
|
|
return err
|
|
}
|
|
|
|
return c.Blob(http.StatusOK, "application/octet-stream", file.Content)
|
|
}
|
|
|
|
func updateTemplateFile(c echo.Context) error {
|
|
templateName := c.Param("templateName")
|
|
if templateName == "" {
|
|
return echo.NewHTTPError(http.StatusNotFound)
|
|
}
|
|
|
|
filePath := c.Param("filePath")
|
|
if filePath == "" {
|
|
return echo.NewHTTPError(http.StatusNotFound)
|
|
}
|
|
|
|
db := service.Database(c)
|
|
|
|
tx, err := db.BeginTx(c.Request().Context(), nil)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
var tmpl template
|
|
err = tx.NewSelect().Model(&tmpl).
|
|
Column("id").
|
|
Where("name = ?", templateName).
|
|
Scan(c.Request().Context())
|
|
if err != nil {
|
|
if errors.Is(err, sql.ErrNoRows) {
|
|
return echo.NewHTTPError(http.StatusNotFound)
|
|
}
|
|
return err
|
|
}
|
|
|
|
newContent, err := io.ReadAll(c.Request().Body)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
_, err = tx.NewUpdate().Table("template_files").
|
|
Set("content = ?", newContent).
|
|
Where("template_id = ?", tmpl.ID).
|
|
Where("file_path = ?", filePath).
|
|
Exec(c.Request().Context())
|
|
if err != nil {
|
|
if errors.Is(err, sql.ErrNoRows) {
|
|
return echo.NewHTTPError(http.StatusNotFound)
|
|
}
|
|
return err
|
|
}
|
|
|
|
if err = tx.Commit(); err != nil {
|
|
_ = tx.Rollback()
|
|
return err
|
|
}
|
|
|
|
return c.NoContent(http.StatusOK)
|
|
}
|
|
|
|
func fetchAllTemplateImages(c echo.Context) error {
|
|
db := service.Database(c)
|
|
|
|
var images []TemplateImage
|
|
err := db.NewSelect().Model(&images).Scan(c.Request().Context())
|
|
if err != nil {
|
|
if errors.Is(err, sql.ErrNoRows) {
|
|
return c.JSON(http.StatusOK, make([]TemplateImage, 0))
|
|
}
|
|
return err
|
|
}
|
|
|
|
if len(images) == 0 {
|
|
return c.JSON(http.StatusOK, make([]TemplateImage, 0))
|
|
}
|
|
|
|
return c.JSON(http.StatusOK, images)
|
|
}
|