refactor: templates api logic
This commit is contained in:
@@ -87,7 +87,8 @@ func (p *ReverseProxy) handleRequest(c echo.Context) error {
|
|||||||
return echo.NewHTTPError(http.StatusNotFound)
|
return echo.NewHTTPError(http.StatusNotFound)
|
||||||
}
|
}
|
||||||
|
|
||||||
first := strings.Split(subdomain, ".")[0]
|
ps := strings.Split(subdomain, ".")
|
||||||
|
first := ps[len(ps)-1]
|
||||||
proxy, ok := p.httpProxies[first]
|
proxy, ok := p.httpProxies[first]
|
||||||
if !ok {
|
if !ok {
|
||||||
return echo.NewHTTPError(http.StatusNotFound)
|
return echo.NewHTTPError(http.StatusNotFound)
|
||||||
|
@@ -1,113 +0,0 @@
|
|||||||
package template
|
|
||||||
|
|
||||||
import (
|
|
||||||
"archive/tar"
|
|
||||||
"bytes"
|
|
||||||
"context"
|
|
||||||
"errors"
|
|
||||||
"github.com/docker/docker/api/types"
|
|
||||||
"github.com/docker/docker/client"
|
|
||||||
"github.com/google/uuid"
|
|
||||||
"github.com/uptrace/bun"
|
|
||||||
"io"
|
|
||||||
"time"
|
|
||||||
)
|
|
||||||
|
|
||||||
type createTemplateOptions struct {
|
|
||||||
name string
|
|
||||||
description string
|
|
||||||
}
|
|
||||||
|
|
||||||
type templateBuildOptions struct {
|
|
||||||
imageTag string
|
|
||||||
buildArgs map[string]*string
|
|
||||||
}
|
|
||||||
|
|
||||||
func createDockerTemplate(ctx context.Context, tx bun.Tx, opts createTemplateOptions) (*template, error) {
|
|
||||||
id, err := uuid.NewV7()
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
now := time.Now().Format(time.RFC3339)
|
|
||||||
|
|
||||||
t := template{
|
|
||||||
ID: id,
|
|
||||||
Name: opts.name,
|
|
||||||
Description: opts.description,
|
|
||||||
CreatedOn: now,
|
|
||||||
LastModifiedOn: now,
|
|
||||||
IsBuilt: false,
|
|
||||||
}
|
|
||||||
dockerfile := templateFile{
|
|
||||||
TemplateID: id,
|
|
||||||
FilePath: "Dockerfile",
|
|
||||||
Content: make([]byte, 0),
|
|
||||||
}
|
|
||||||
readme := templateFile{
|
|
||||||
TemplateID: id,
|
|
||||||
FilePath: "README.md",
|
|
||||||
Content: make([]byte, 0),
|
|
||||||
}
|
|
||||||
files := []*templateFile{&dockerfile, &readme}
|
|
||||||
|
|
||||||
if err = tx.NewInsert().Model(&t).Returning("*").Scan(ctx); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
if err = tx.NewInsert().Model(&files).Scan(ctx); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
t.Files = files
|
|
||||||
|
|
||||||
return &t, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func buildDockerTemplate(ctx context.Context, docker *client.Client, tmpl *template, opts templateBuildOptions) (io.ReadCloser, error) {
|
|
||||||
if len(tmpl.Files) == 0 {
|
|
||||||
return nil, errors.New("cannot build docker template: no files in template")
|
|
||||||
}
|
|
||||||
|
|
||||||
buf := new(bytes.Buffer)
|
|
||||||
tw := tar.NewWriter(buf)
|
|
||||||
defer tw.Close()
|
|
||||||
|
|
||||||
var dockerfile []byte
|
|
||||||
for _, file := range tmpl.Files {
|
|
||||||
if file.FilePath == "Dockerfile" {
|
|
||||||
dockerfile = file.Content
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if len(dockerfile) == 0 {
|
|
||||||
return nil, errors.New("cannot build docker template: template does not contain Dockerfile")
|
|
||||||
}
|
|
||||||
|
|
||||||
h := tar.Header{
|
|
||||||
Name: "Dockerfile",
|
|
||||||
Size: int64(len(dockerfile)),
|
|
||||||
}
|
|
||||||
err := tw.WriteHeader(&h)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
_, err = tw.Write(dockerfile)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
r := bytes.NewReader(buf.Bytes())
|
|
||||||
|
|
||||||
res, err := docker.ImageBuild(ctx, r, types.ImageBuildOptions{
|
|
||||||
Context: r,
|
|
||||||
Tags: []string{opts.imageTag},
|
|
||||||
BuildArgs: opts.buildArgs,
|
|
||||||
})
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return res.Body, nil
|
|
||||||
}
|
|
@@ -1,15 +1,12 @@
|
|||||||
package template
|
package template
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bufio"
|
|
||||||
"database/sql"
|
"database/sql"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
|
||||||
"github.com/labstack/echo/v4"
|
"github.com/labstack/echo/v4"
|
||||||
"io"
|
"io"
|
||||||
"net/http"
|
"net/http"
|
||||||
"strings"
|
|
||||||
"tesseract/internal/service"
|
"tesseract/internal/service"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -19,7 +16,7 @@ type createTemplateRequestBody struct {
|
|||||||
Documentation string `json:"documentation"`
|
Documentation string `json:"documentation"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type updateTemplateRequestBody struct {
|
type postTemplateRequestBody struct {
|
||||||
Description *string `json:"description"`
|
Description *string `json:"description"`
|
||||||
Files []templateFile `json:"files"`
|
Files []templateFile `json:"files"`
|
||||||
|
|
||||||
@@ -27,92 +24,52 @@ type updateTemplateRequestBody struct {
|
|||||||
BuildArgs map[string]*string `json:"buildArgs"`
|
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 {
|
func fetchAllTemplates(c echo.Context) error {
|
||||||
db := service.Database(c)
|
mgr := templateManagerFrom(c)
|
||||||
|
templates, err := mgr.findAllTemplates(c.Request().Context())
|
||||||
var templates []template
|
|
||||||
err := db.NewSelect().Model(&templates).Scan(c.Request().Context())
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if errors.Is(err, sql.ErrNoRows) {
|
|
||||||
return c.JSON(http.StatusOK, make([]template, 0))
|
|
||||||
}
|
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(templates) == 0 {
|
|
||||||
return c.JSON(http.StatusOK, make([]template, 0))
|
|
||||||
}
|
|
||||||
return c.JSON(http.StatusOK, templates)
|
return c.JSON(http.StatusOK, templates)
|
||||||
}
|
}
|
||||||
|
|
||||||
func fetchTemplate(c echo.Context) error {
|
func fetchTemplate(c echo.Context) error {
|
||||||
db := service.Database(c)
|
mgr := templateManagerFrom(c)
|
||||||
|
template, err := mgr.findTemplate(c.Request().Context(), c.Param("templateName"))
|
||||||
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 err != nil {
|
||||||
if errors.Is(err, sql.ErrNoRows) {
|
if errors.Is(err, errTemplateNotFound) {
|
||||||
return echo.NewHTTPError(http.StatusNotFound)
|
return echo.NewHTTPError(http.StatusNotFound)
|
||||||
}
|
}
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
return c.JSON(http.StatusOK, template)
|
||||||
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 {
|
func createOrUpdateTemplate(c echo.Context) error {
|
||||||
db := service.Database(c)
|
mgr := templateManagerFrom(c)
|
||||||
|
exists, err := mgr.hasTemplate(c.Request().Context(), c.Param("templateName"))
|
||||||
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 err != nil {
|
||||||
if errors.Is(err, sql.ErrNoRows) {
|
|
||||||
return createTemplate(c)
|
|
||||||
}
|
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
if !exists {
|
if !exists {
|
||||||
return createTemplate(c)
|
return createTemplate(c)
|
||||||
}
|
}
|
||||||
|
|
||||||
return updateTemplate(c)
|
var body postTemplateRequestBody
|
||||||
|
err = json.NewDecoder(c.Request().Body).Decode(&body)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if body.ImageTag != nil || body.BuildArgs != nil {
|
||||||
|
return buildTemplate(c, body)
|
||||||
|
}
|
||||||
|
|
||||||
|
return updateTemplate(c, body)
|
||||||
}
|
}
|
||||||
|
|
||||||
func createTemplate(c echo.Context) error {
|
func createTemplate(c echo.Context) error {
|
||||||
db := service.Database(c)
|
mgr := templateManagerFrom(c)
|
||||||
name := c.Param("templateName")
|
name := c.Param("templateName")
|
||||||
|
|
||||||
var body createTemplateRequestBody
|
var body createTemplateRequestBody
|
||||||
@@ -121,86 +78,49 @@ func createTemplate(c echo.Context) error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
tx, err := db.BeginTx(c.Request().Context(), nil)
|
createdTemplate, err := mgr.createTemplate(c.Request().Context(), createTemplateOptions{
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
createdTemplate, err := createDockerTemplate(c.Request().Context(), tx, createTemplateOptions{
|
|
||||||
name: name,
|
name: name,
|
||||||
description: body.Description,
|
description: body.Description,
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
_ = tx.Rollback()
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
if err = tx.Commit(); err != nil {
|
|
||||||
_ = tx.Rollback()
|
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
return c.JSON(http.StatusOK, createdTemplate)
|
return c.JSON(http.StatusOK, createdTemplate)
|
||||||
}
|
}
|
||||||
|
|
||||||
func updateTemplate(c echo.Context) error {
|
func updateTemplate(c echo.Context, body postTemplateRequestBody) error {
|
||||||
db := service.Database(c)
|
|
||||||
name := c.Param("templateName")
|
name := c.Param("templateName")
|
||||||
|
mgr := templateManagerFrom(c)
|
||||||
|
ctx := c.Request().Context()
|
||||||
|
|
||||||
var body updateTemplateRequestBody
|
updatedTemplate, err := mgr.updateTemplate(ctx, name, updateTemplateOptions{
|
||||||
err := json.NewDecoder(c.Request().Body).Decode(&body)
|
description: *body.Description,
|
||||||
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
if errors.Is(err, errTemplateNotFound) {
|
||||||
}
|
|
||||||
|
|
||||||
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 echo.NewHTTPError(http.StatusNotFound)
|
||||||
}
|
}
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
if body.Description != nil {
|
return c.JSON(http.StatusOK, &updatedTemplate)
|
||||||
tmpl.Description = *body.Description
|
}
|
||||||
_, err = tx.NewUpdate().Model(&tmpl).
|
|
||||||
Column("description").
|
func buildTemplate(c echo.Context, body postTemplateRequestBody) error {
|
||||||
WherePK().
|
mgr := templateManagerFrom(c)
|
||||||
Exec(c.Request().Context())
|
name := c.Param("templateName")
|
||||||
|
ctx := c.Request().Context()
|
||||||
|
|
||||||
|
template, err := mgr.findTemplate(ctx, name)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
_ = tx.Rollback()
|
if errors.Is(err, errTemplateNotFound) {
|
||||||
|
return echo.NewHTTPError(http.StatusNotFound)
|
||||||
|
}
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
if err = tx.Commit(); err != nil {
|
outputChan, err := mgr.buildTemplate(ctx, template, buildTemplateOptions{
|
||||||
_ = 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,
|
imageTag: *body.ImageTag,
|
||||||
buildArgs: body.BuildArgs,
|
buildArgs: body.BuildArgs,
|
||||||
})
|
})
|
||||||
@@ -213,116 +133,30 @@ func updateTemplate(c echo.Context) error {
|
|||||||
w.Header().Set("Cache-Control", "no-cache")
|
w.Header().Set("Cache-Control", "no-cache")
|
||||||
w.Header().Set("Connection", "keep-alive")
|
w.Header().Set("Connection", "keep-alive")
|
||||||
|
|
||||||
scanner := bufio.NewScanner(log)
|
for o := range outputChan {
|
||||||
|
switch o := o.(type) {
|
||||||
var imageID string
|
case error:
|
||||||
|
|
||||||
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
|
return err
|
||||||
}
|
case string:
|
||||||
|
if _, err = w.Write([]byte(o)); err != nil {
|
||||||
if stream, ok := msg["stream"].(string); ok {
|
|
||||||
if _, err = w.Write([]byte(stream)); err != nil {
|
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
w.Flush()
|
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
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
if err = tx.Commit(); err != nil {
|
|
||||||
_ = tx.Rollback()
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
return c.JSON(http.StatusOK, &tmpl)
|
|
||||||
}
|
|
||||||
|
|
||||||
func deleteTemplate(c echo.Context) error {
|
func deleteTemplate(c echo.Context) error {
|
||||||
templateName := c.Param("templateName")
|
mgr := templateManagerFrom(c)
|
||||||
if templateName == "" {
|
name := c.Param("templateName")
|
||||||
|
|
||||||
|
err := mgr.deleteTemplate(c.Request().Context(), name)
|
||||||
|
if err != nil {
|
||||||
|
if errors.Is(err, errTemplateNotFound) {
|
||||||
return echo.NewHTTPError(http.StatusNotFound)
|
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 err
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -330,37 +164,13 @@ func deleteTemplate(c echo.Context) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func fetchTemplateFile(c echo.Context) error {
|
func fetchTemplateFile(c echo.Context) error {
|
||||||
|
mgr := templateManagerFrom(c)
|
||||||
templateName := c.Param("templateName")
|
templateName := c.Param("templateName")
|
||||||
if templateName == "" {
|
|
||||||
return echo.NewHTTPError(http.StatusNotFound)
|
|
||||||
}
|
|
||||||
|
|
||||||
filePath := c.Param("filePath")
|
filePath := c.Param("filePath")
|
||||||
if filePath == "" {
|
|
||||||
return echo.NewHTTPError(http.StatusNotFound)
|
|
||||||
}
|
|
||||||
|
|
||||||
db := service.Database(c)
|
file, err := mgr.findTemplateFile(c.Request().Context(), templateName, filePath)
|
||||||
|
|
||||||
var tmpl template
|
|
||||||
err := db.NewSelect().Model(&tmpl).
|
|
||||||
Column("id").
|
|
||||||
Where("name = ?", templateName).
|
|
||||||
Scan(c.Request().Context())
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if errors.Is(err, sql.ErrNoRows) {
|
if errors.Is(err, errTemplateNotFound) || errors.Is(err, errTemplateFileNotFound) {
|
||||||
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 echo.NewHTTPError(http.StatusNotFound)
|
||||||
}
|
}
|
||||||
return err
|
return err
|
||||||
@@ -370,74 +180,40 @@ func fetchTemplateFile(c echo.Context) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func updateTemplateFile(c echo.Context) error {
|
func updateTemplateFile(c echo.Context) error {
|
||||||
|
mgr := templateManagerFrom(c)
|
||||||
templateName := c.Param("templateName")
|
templateName := c.Param("templateName")
|
||||||
if templateName == "" {
|
|
||||||
return echo.NewHTTPError(http.StatusNotFound)
|
|
||||||
}
|
|
||||||
|
|
||||||
filePath := c.Param("filePath")
|
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)
|
newContent, err := io.ReadAll(c.Request().Body)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
_, err = tx.NewUpdate().Table("template_files").
|
err = mgr.updateTemplateFile(c.Request().Context(), templateName, filePath, newContent)
|
||||||
Set("content = ?", newContent).
|
|
||||||
Where("template_id = ?", tmpl.ID).
|
|
||||||
Where("file_path = ?", filePath).
|
|
||||||
Exec(c.Request().Context())
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if errors.Is(err, sql.ErrNoRows) {
|
if errors.Is(err, errTemplateNotFound) || errors.Is(err, errTemplateFileNotFound) {
|
||||||
return echo.NewHTTPError(http.StatusNotFound)
|
return echo.NewHTTPError(http.StatusNotFound)
|
||||||
}
|
}
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
if err = tx.Commit(); err != nil {
|
|
||||||
_ = tx.Rollback()
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
return c.NoContent(http.StatusOK)
|
return c.NoContent(http.StatusOK)
|
||||||
}
|
}
|
||||||
|
|
||||||
func fetchAllTemplateImages(c echo.Context) error {
|
func fetchAllTemplateImages(c echo.Context) error {
|
||||||
db := service.Database(c)
|
db := service.Database(c)
|
||||||
|
|
||||||
var images []TemplateImage
|
var images []Image
|
||||||
err := db.NewSelect().Model(&images).Scan(c.Request().Context())
|
err := db.NewSelect().Model(&images).Scan(c.Request().Context())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if errors.Is(err, sql.ErrNoRows) {
|
if errors.Is(err, sql.ErrNoRows) {
|
||||||
return c.JSON(http.StatusOK, make([]TemplateImage, 0))
|
return c.JSON(http.StatusOK, make([]Image, 0))
|
||||||
}
|
}
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(images) == 0 {
|
if len(images) == 0 {
|
||||||
return c.JSON(http.StatusOK, make([]TemplateImage, 0))
|
return c.JSON(http.StatusOK, make([]Image, 0))
|
||||||
}
|
}
|
||||||
|
|
||||||
return c.JSON(http.StatusOK, images)
|
return c.JSON(http.StatusOK, images)
|
||||||
|
45
internal/template/middleware.go
Normal file
45
internal/template/middleware.go
Normal file
@@ -0,0 +1,45 @@
|
|||||||
|
package template
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net/http"
|
||||||
|
"tesseract/internal/service"
|
||||||
|
|
||||||
|
"github.com/labstack/echo/v4"
|
||||||
|
)
|
||||||
|
|
||||||
|
func newTemplateManagerMiddleware(service service.Services) echo.MiddlewareFunc {
|
||||||
|
mgr := templateManager{
|
||||||
|
db: service.Database,
|
||||||
|
dockerClient: service.DockerClient,
|
||||||
|
}
|
||||||
|
return func(next echo.HandlerFunc) echo.HandlerFunc {
|
||||||
|
return func(c echo.Context) error {
|
||||||
|
c.Set("templateManager", &mgr)
|
||||||
|
return next(c)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func validateTemplateName(next echo.HandlerFunc) echo.HandlerFunc {
|
||||||
|
return func(c echo.Context) error {
|
||||||
|
templateName := c.Param("templateName")
|
||||||
|
if templateName == "" || !templateNameRegex.MatchString(templateName) {
|
||||||
|
return echo.NewHTTPError(http.StatusNotFound)
|
||||||
|
}
|
||||||
|
return next(c)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func validateTemplateFilePath(next echo.HandlerFunc) echo.HandlerFunc {
|
||||||
|
return func(c echo.Context) error {
|
||||||
|
filePath := c.Param("filePath")
|
||||||
|
if filePath == "" {
|
||||||
|
return echo.NewHTTPError(http.StatusNotFound)
|
||||||
|
}
|
||||||
|
return next(c)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func templateManagerFrom(c echo.Context) *templateManager {
|
||||||
|
return c.Get("templateManager").(*templateManager)
|
||||||
|
}
|
@@ -2,14 +2,16 @@ package template
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"github.com/labstack/echo/v4"
|
"github.com/labstack/echo/v4"
|
||||||
|
"tesseract/internal/service"
|
||||||
)
|
)
|
||||||
|
|
||||||
func DefineRoutes(g *echo.Group) {
|
func DefineRoutes(g *echo.Group, services service.Services) {
|
||||||
|
g.Use(newTemplateManagerMiddleware(services))
|
||||||
g.GET("/templates", fetchAllTemplates)
|
g.GET("/templates", fetchAllTemplates)
|
||||||
g.GET("/templates/:templateName", fetchTemplate)
|
g.GET("/templates/:templateName", fetchTemplate, validateTemplateName)
|
||||||
g.POST("/templates/:templateName", createOrUpdateTemplate)
|
g.POST("/templates/:templateName", createOrUpdateTemplate, validateTemplateName)
|
||||||
g.DELETE("/templates/:templateName", deleteTemplate)
|
g.DELETE("/templates/:templateName", deleteTemplate, validateTemplateName)
|
||||||
g.GET("/templates/:templateName/:filePath", fetchTemplateFile)
|
g.GET("/templates/:templateName/:filePath", fetchTemplateFile, validateTemplateName, validateTemplateFilePath)
|
||||||
g.POST("/templates/:templateName/:filePath", updateTemplateFile)
|
g.POST("/templates/:templateName/:filePath", updateTemplateFile, validateTemplateName, validateTemplateFilePath)
|
||||||
g.GET("/template-images", fetchAllTemplateImages)
|
g.GET("/template-images", fetchAllTemplateImages)
|
||||||
}
|
}
|
||||||
|
@@ -3,8 +3,12 @@ package template
|
|||||||
import (
|
import (
|
||||||
"github.com/google/uuid"
|
"github.com/google/uuid"
|
||||||
"github.com/uptrace/bun"
|
"github.com/uptrace/bun"
|
||||||
|
"regexp"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// templateNameRegex is a regex to test whether a given template name is valid
|
||||||
|
var templateNameRegex = regexp.MustCompile("^[\\w-]+$")
|
||||||
|
|
||||||
type template struct {
|
type template struct {
|
||||||
bun.BaseModel `bun:"table:templates,alias:template"`
|
bun.BaseModel `bun:"table:templates,alias:template"`
|
||||||
|
|
||||||
@@ -27,7 +31,7 @@ type templateFile struct {
|
|||||||
Content []byte `bun:"type:blob" json:"content"`
|
Content []byte `bun:"type:blob" json:"content"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type TemplateImage struct {
|
type Image struct {
|
||||||
bun.BaseModel `bun:"table:template_images,alias:template_images"`
|
bun.BaseModel `bun:"table:template_images,alias:template_images"`
|
||||||
|
|
||||||
TemplateID uuid.UUID `bun:"type:uuid" json:"-"`
|
TemplateID uuid.UUID `bun:"type:uuid" json:"-"`
|
||||||
|
417
internal/template/template_manager.go
Normal file
417
internal/template/template_manager.go
Normal file
@@ -0,0 +1,417 @@
|
|||||||
|
package template
|
||||||
|
|
||||||
|
import (
|
||||||
|
"archive/tar"
|
||||||
|
"bufio"
|
||||||
|
"bytes"
|
||||||
|
"context"
|
||||||
|
"database/sql"
|
||||||
|
"encoding/json"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"github.com/docker/docker/api/types"
|
||||||
|
"github.com/docker/docker/client"
|
||||||
|
"github.com/google/uuid"
|
||||||
|
"github.com/uptrace/bun"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
type templateManager struct {
|
||||||
|
db *bun.DB
|
||||||
|
dockerClient *client.Client
|
||||||
|
}
|
||||||
|
|
||||||
|
type createTemplateOptions struct {
|
||||||
|
name string
|
||||||
|
description string
|
||||||
|
}
|
||||||
|
|
||||||
|
type updateTemplateOptions struct {
|
||||||
|
tx *bun.Tx
|
||||||
|
description string
|
||||||
|
}
|
||||||
|
|
||||||
|
type buildTemplateOptions struct {
|
||||||
|
tx *bun.Tx
|
||||||
|
imageTag string
|
||||||
|
buildArgs map[string]*string
|
||||||
|
}
|
||||||
|
|
||||||
|
var errTemplateNotFound = errors.New("template not found")
|
||||||
|
var errTemplateFileNotFound = errors.New("template file not found")
|
||||||
|
|
||||||
|
func (mgr *templateManager) beginTx(ctx context.Context) (bun.Tx, error) {
|
||||||
|
tx, err := mgr.db.BeginTx(ctx, nil)
|
||||||
|
if err != nil {
|
||||||
|
return bun.Tx{}, err
|
||||||
|
}
|
||||||
|
return tx, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (mgr *templateManager) findAllTemplates(ctx context.Context) ([]template, error) {
|
||||||
|
var templates []template
|
||||||
|
err := mgr.db.NewSelect().Model(&templates).Scan(ctx)
|
||||||
|
if err != nil {
|
||||||
|
if errors.Is(err, sql.ErrNoRows) {
|
||||||
|
return make([]template, 0), nil
|
||||||
|
}
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(templates) == 0 {
|
||||||
|
return make([]template, 0), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return templates, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (mgr *templateManager) findTemplate(ctx context.Context, name string) (*template, error) {
|
||||||
|
var template template
|
||||||
|
err := mgr.db.NewSelect().Model(&template).
|
||||||
|
Relation("Files").
|
||||||
|
Where("name = ?", name).
|
||||||
|
Scan(ctx)
|
||||||
|
if err != nil {
|
||||||
|
if errors.Is(err, sql.ErrNoRows) {
|
||||||
|
return nil, errTemplateNotFound
|
||||||
|
}
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(template.Files) > 0 {
|
||||||
|
template.FileMap = make(map[string]*templateFile)
|
||||||
|
}
|
||||||
|
for _, f := range template.Files {
|
||||||
|
template.FileMap[f.FilePath] = f
|
||||||
|
}
|
||||||
|
|
||||||
|
return &template, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (mgr *templateManager) hasTemplate(ctx context.Context, name string) (bool, error) {
|
||||||
|
exists, err := mgr.db.NewSelect().
|
||||||
|
Table("templates").
|
||||||
|
Where("name = ?", name).
|
||||||
|
Exists(ctx)
|
||||||
|
if err != nil {
|
||||||
|
if errors.Is(err, sql.ErrNoRows) {
|
||||||
|
return false, nil
|
||||||
|
}
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
|
return exists, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (mgr *templateManager) createTemplate(ctx context.Context, opts createTemplateOptions) (*template, error) {
|
||||||
|
tx, err := mgr.db.BeginTx(ctx, nil)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
id, err := uuid.NewV7()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
now := time.Now().Format(time.RFC3339)
|
||||||
|
|
||||||
|
t := template{
|
||||||
|
ID: id,
|
||||||
|
Name: opts.name,
|
||||||
|
Description: opts.description,
|
||||||
|
CreatedOn: now,
|
||||||
|
LastModifiedOn: now,
|
||||||
|
IsBuilt: false,
|
||||||
|
}
|
||||||
|
dockerfile := templateFile{
|
||||||
|
TemplateID: id,
|
||||||
|
FilePath: "Dockerfile",
|
||||||
|
Content: make([]byte, 0),
|
||||||
|
}
|
||||||
|
readme := templateFile{
|
||||||
|
TemplateID: id,
|
||||||
|
FilePath: "README.md",
|
||||||
|
Content: make([]byte, 0),
|
||||||
|
}
|
||||||
|
files := []*templateFile{&dockerfile, &readme}
|
||||||
|
|
||||||
|
if err = tx.NewInsert().Model(&t).Returning("*").Scan(ctx); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if err = tx.NewInsert().Model(&files).Scan(ctx); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
t.Files = files
|
||||||
|
|
||||||
|
if err = tx.Commit(); err != nil {
|
||||||
|
_ = tx.Rollback()
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return &t, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (mgr *templateManager) updateTemplate(ctx context.Context, name string, opts updateTemplateOptions) (*template, error) {
|
||||||
|
tx := opts.tx
|
||||||
|
autoCommit := false
|
||||||
|
if tx == nil {
|
||||||
|
_tx, err := mgr.db.BeginTx(ctx, nil)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
autoCommit = true
|
||||||
|
tx = &_tx
|
||||||
|
}
|
||||||
|
|
||||||
|
var template template
|
||||||
|
err := tx.NewUpdate().Model(&template).
|
||||||
|
Where("name = ?", name).
|
||||||
|
Set("description = ?", opts.description).
|
||||||
|
Returning("*").
|
||||||
|
Scan(ctx)
|
||||||
|
if err != nil {
|
||||||
|
if errors.Is(err, sql.ErrNoRows) {
|
||||||
|
return nil, errTemplateNotFound
|
||||||
|
}
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if autoCommit {
|
||||||
|
if err = tx.Commit(); err != nil {
|
||||||
|
_ = tx.Rollback()
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return &template, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (mgr *templateManager) buildTemplate(ctx context.Context, template *template, opts buildTemplateOptions) (<-chan any, error) {
|
||||||
|
tx := opts.tx
|
||||||
|
autoCommit := false
|
||||||
|
if tx == nil {
|
||||||
|
_tx, err := mgr.db.BeginTx(ctx, nil)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
autoCommit = true
|
||||||
|
tx = &_tx
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(template.Files) == 0 {
|
||||||
|
return nil, errors.New("cannot build docker template: no files in template")
|
||||||
|
}
|
||||||
|
|
||||||
|
buf := new(bytes.Buffer)
|
||||||
|
tw := tar.NewWriter(buf)
|
||||||
|
defer tw.Close()
|
||||||
|
|
||||||
|
var dockerfile []byte
|
||||||
|
for _, file := range template.Files {
|
||||||
|
if file.FilePath == "Dockerfile" {
|
||||||
|
dockerfile = file.Content
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if len(dockerfile) == 0 {
|
||||||
|
return nil, errors.New("cannot build docker template: template does not contain Dockerfile")
|
||||||
|
}
|
||||||
|
|
||||||
|
h := tar.Header{
|
||||||
|
Name: "Dockerfile",
|
||||||
|
Size: int64(len(dockerfile)),
|
||||||
|
}
|
||||||
|
err := tw.WriteHeader(&h)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err = tw.Write(dockerfile)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
r := bytes.NewReader(buf.Bytes())
|
||||||
|
|
||||||
|
res, err := mgr.dockerClient.ImageBuild(ctx, r, types.ImageBuildOptions{
|
||||||
|
Context: r,
|
||||||
|
Tags: []string{opts.imageTag},
|
||||||
|
BuildArgs: opts.buildArgs,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
outputChan := make(chan any)
|
||||||
|
|
||||||
|
go func() {
|
||||||
|
scanner := bufio.NewScanner(res.Body)
|
||||||
|
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 {
|
||||||
|
outputChan <- err
|
||||||
|
}
|
||||||
|
|
||||||
|
if stream, ok := msg["stream"].(string); ok {
|
||||||
|
outputChan <- stream
|
||||||
|
} else if errmsg, ok := msg["error"].(string); ok {
|
||||||
|
outputChan <- errmsg + "\n"
|
||||||
|
} 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"
|
||||||
|
}
|
||||||
|
outputChan <- text
|
||||||
|
} else if aux, ok := msg["aux"].(map[string]any); ok {
|
||||||
|
if id, ok := aux["ID"].(string); ok {
|
||||||
|
imageID = id
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var img *Image
|
||||||
|
|
||||||
|
if imageID != "" {
|
||||||
|
img = &Image{
|
||||||
|
TemplateID: template.ID,
|
||||||
|
ImageTag: opts.imageTag,
|
||||||
|
ImageID: imageID,
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err = tx.NewInsert().Model(img).Exec(ctx)
|
||||||
|
if err != nil {
|
||||||
|
_ = tx.Rollback()
|
||||||
|
outputChan <- err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if autoCommit {
|
||||||
|
if err = tx.Commit(); err != nil {
|
||||||
|
_ = tx.Rollback()
|
||||||
|
outputChan <- err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if img != nil {
|
||||||
|
outputChan <- img
|
||||||
|
}
|
||||||
|
|
||||||
|
close(outputChan)
|
||||||
|
}()
|
||||||
|
|
||||||
|
return outputChan, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (mgr *templateManager) deleteTemplate(ctx context.Context, name string) error {
|
||||||
|
tx, err := mgr.db.BeginTx(ctx, nil)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
res, err := tx.NewDelete().Table("templates").
|
||||||
|
Where("name = ?", name).
|
||||||
|
Exec(ctx)
|
||||||
|
if err != nil {
|
||||||
|
if errors.Is(err, sql.ErrNoRows) {
|
||||||
|
return errTemplateNotFound
|
||||||
|
}
|
||||||
|
_ = tx.Rollback()
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
count, err := res.RowsAffected()
|
||||||
|
if err != nil {
|
||||||
|
_ = tx.Rollback()
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if count != 1 {
|
||||||
|
_ = tx.Rollback()
|
||||||
|
return errors.New("unexpected number of templates deleted")
|
||||||
|
}
|
||||||
|
|
||||||
|
if err = tx.Commit(); err != nil {
|
||||||
|
_ = tx.Rollback()
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (mgr *templateManager) findTemplateFile(ctx context.Context, templateName, filePath string) (*templateFile, error) {
|
||||||
|
var tmpl template
|
||||||
|
err := mgr.db.NewSelect().Model(&tmpl).
|
||||||
|
Column("id").
|
||||||
|
Where("name = ?", templateName).
|
||||||
|
Scan(ctx)
|
||||||
|
if err != nil {
|
||||||
|
if errors.Is(err, sql.ErrNoRows) {
|
||||||
|
return nil, errTemplateNotFound
|
||||||
|
}
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
var file templateFile
|
||||||
|
err = mgr.db.NewSelect().Model(&file).
|
||||||
|
Where("template_id = ?", tmpl.ID).
|
||||||
|
Where("file_path = ?", filePath).
|
||||||
|
Scan(ctx)
|
||||||
|
if err != nil {
|
||||||
|
if errors.Is(err, sql.ErrNoRows) {
|
||||||
|
return nil, errTemplateFileNotFound
|
||||||
|
}
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return &file, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (mgr *templateManager) updateTemplateFile(ctx context.Context, templateName, filePath string, content []byte) error {
|
||||||
|
tx, err := mgr.db.BeginTx(ctx, nil)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
var template template
|
||||||
|
err = tx.NewSelect().Model(&template).
|
||||||
|
Column("id").
|
||||||
|
Where("name = ?", templateName).
|
||||||
|
Scan(ctx)
|
||||||
|
if err != nil {
|
||||||
|
if errors.Is(err, sql.ErrNoRows) {
|
||||||
|
return errTemplateNotFound
|
||||||
|
}
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err = tx.NewUpdate().Table("template_files").
|
||||||
|
Set("content = ?", content).
|
||||||
|
Where("template_id = ?", template.ID).
|
||||||
|
Where("file_path = ?", filePath).
|
||||||
|
Exec(ctx)
|
||||||
|
if err != nil {
|
||||||
|
_ = tx.Rollback()
|
||||||
|
if errors.Is(err, sql.ErrNoRows) {
|
||||||
|
return errTemplateFileNotFound
|
||||||
|
}
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if err = tx.Commit(); err != nil {
|
||||||
|
_ = tx.Rollback()
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
@@ -1,8 +1,9 @@
|
|||||||
package workspace
|
package workspace
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"github.com/labstack/echo/v4"
|
|
||||||
"tesseract/internal/service"
|
"tesseract/internal/service"
|
||||||
|
|
||||||
|
"github.com/labstack/echo/v4"
|
||||||
)
|
)
|
||||||
|
|
||||||
func newWorkspaceManagerMiddleware(services service.Services) echo.MiddlewareFunc {
|
func newWorkspaceManagerMiddleware(services service.Services) echo.MiddlewareFunc {
|
||||||
|
@@ -131,7 +131,7 @@ func (mgr workspaceManager) createWorkspace(ctx context.Context, opts createWork
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
var img template.TemplateImage
|
var img template.Image
|
||||||
err = tx.NewSelect().Model(&img).
|
err = tx.NewSelect().Model(&img).
|
||||||
Where("image_id = ?", opts.imageID).
|
Where("image_id = ?", opts.imageID).
|
||||||
Scan(ctx)
|
Scan(ctx)
|
||||||
|
2
main.go
2
main.go
@@ -80,7 +80,7 @@ func main() {
|
|||||||
|
|
||||||
g := apiServer.Group("/api")
|
g := apiServer.Group("/api")
|
||||||
workspace.DefineRoutes(g, services)
|
workspace.DefineRoutes(g, services)
|
||||||
template.DefineRoutes(g)
|
template.DefineRoutes(g, services)
|
||||||
|
|
||||||
apiServer.HTTPErrorHandler = func(err error, c echo.Context) {
|
apiServer.HTTPErrorHandler = func(err error, c echo.Context) {
|
||||||
var he *echo.HTTPError
|
var he *echo.HTTPError
|
||||||
|
Reference in New Issue
Block a user