1package handler
2
3import (
4 "bytes"
5 "fmt"
6 "io"
7 "net/http"
8 "regexp"
9
10 "github.com/bnema/gordon/internal/common"
11 "github.com/bnema/gordon/internal/server"
12 "github.com/bnema/gordon/internal/templating/cmdparams"
13 "github.com/bnema/gordon/pkg/docker"
14 "github.com/bnema/gordon/pkg/store"
15 "github.com/labstack/echo/v4"
16)
17
18type InfoResponse struct {
19 Uptime string `json:"uptime"`
20 Version string `json:"version"`
21}
22
23func (info *InfoResponse) Populate(a *server.App) {
24 info.Uptime = a.GetUptime()
25 info.Version = a.GetVersionstring()
26}
27
28// Handle GET on /api/ping endpoint
29func GetInfos(c echo.Context, a *server.App) error {
30 body, _ := io.ReadAll(c.Request().Body)
31 c.Request().Body = io.NopCloser(bytes.NewBuffer(body)) // Reset the body
32
33 payload := new(common.RequestPayload)
34 if err := c.Bind(payload); err != nil {
35 fmt.Println("Bind Error:", err)
36 return c.JSON(http.StatusBadRequest, err.Error())
37 }
38
39 if payload.Type != "ping" {
40 return c.JSON(http.StatusBadRequest, "Invalid payload type")
41 }
42
43 pingPayload, ok := payload.Payload.(common.PingPayload)
44 if !ok {
45 return c.JSON(http.StatusBadRequest, "Invalid payload type")
46 }
47 if pingPayload.Message != "ping" {
48 return c.JSON(http.StatusBadRequest, "Invalid payload data")
49 }
50
51 // Prepare and populate the information
52 info := &InfoResponse{}
53 info.Populate(a)
54
55 return c.JSON(http.StatusOK, info)
56
57}
58
59func PostDeploy(c echo.Context, a *server.App) error {
60
61 // Initialize pushPayload object
62 payload := &common.DeployPayload{
63 Ports: c.Request().Header.Get("X-Ports"),
64 ImageName: c.Request().Header.Get("X-Image-Name"),
65 TargetDomain: c.Request().Header.Get("X-Target-Domain"),
66 }
67
68 if payload.ImageName == "" {
69 return c.JSON(http.StatusBadRequest, "Invalid image name")
70 }
71
72 imageReader := c.Request().Body
73 defer imageReader.Close()
74
75 // Rename the image to a valid name so that it can be saved (remove user/, :tag and add .tar)
76 imageFileName := payload.ImageName
77 imageFileName = regexp.MustCompile(`^([a-zA-Z0-9\-_.]+\/)?`).ReplaceAllString(imageFileName, "")
78 imageFileName = regexp.MustCompile(`(:[a-zA-Z0-9\-_.]+)?$`).ReplaceAllString(imageFileName, "")
79 imageFileName = imageFileName + ".tar"
80
81 // Check the ports struct, if there is no /tcp, or /udp, add /tcp
82 if !regexp.MustCompile(`\/(tcp|udp)$`).MatchString(payload.Ports) {
83 payload.Ports = payload.Ports + "/tcp"
84 }
85
86 // Check the target domain, if there is no https:// or http://, add https://
87 if !regexp.MustCompile(`^https?:\/\/`).MatchString(payload.TargetDomain) {
88 payload.TargetDomain = "https://" + payload.TargetDomain
89 }
90
91 // Save the image tar in the storage
92 imageFilePath, err := store.SaveImageToStorage(&a.Config, imageFileName, imageReader)
93 if err != nil {
94 return sendJsonError(c, err)
95 }
96
97 // Import the tar in docker
98 err = docker.ImportImageToEngine(imageFilePath)
99 if err != nil {
100 return sendJsonError(c, err)
101 }
102
103 // Remove the image from the storage
104 err = store.RemoveFromStorage()
105 if err != nil {
106 return sendJsonError(c, err)
107 }
108
109 // append localhost to the image name since it's a manually imported image and not from a registry
110 payload.ImageName = "localhost/" + payload.ImageName
111
112 // Get the image ID
113 imageID, err := docker.GetImageID(payload.ImageName)
114 if err != nil {
115 return sendJsonError(c, err)
116 }
117
118 // Update the payload with the image ID
119 payload.ImageID = imageID
120
121 // Create the container using cmdparams.FromPayloadStructToCmdParams
122 params, err := cmdparams.FromPayloadStructToCmdParams(payload, a)
123 if err != nil {
124 return sendJsonError(c, err)
125 }
126
127 // Create the container
128 containerID, err := docker.CreateContainer(params)
129 if err != nil {
130 return sendJsonError(c, err)
131 }
132
133 // Start the container
134 err = docker.StartContainer(containerID)
135 if err != nil {
136 return sendJsonError(c, err)
137 }
138
139 // Remove the container if it fails to start
140 defer func() {
141 if err != nil {
142 docker.RemoveContainer(containerID)
143 c.JSON(http.StatusInternalServerError, sendJsonError(c, err))
144 return
145 }
146 }()
147
148 // If we arrive here, send back payload.TargetDomain so the client can test it
149 return c.JSON(http.StatusOK, payload.TargetDomain)
150}