1package handler
2
3import (
4 "encoding/json"
5 "fmt"
6 "net/http"
7 "regexp"
8
9 "github.com/bnema/gordon/internal/common"
10 "github.com/bnema/gordon/internal/server"
11 "github.com/bnema/gordon/internal/templating/cmdparams"
12 "github.com/bnema/gordon/pkg/docker"
13 "github.com/bnema/gordon/pkg/store"
14 "github.com/labstack/echo/v4"
15)
16
17type InfoResponse struct {
18 Uptime string `json:"uptime"`
19 Version string `json:"version"`
20}
21
22type DeployResponse = common.DeployResponse
23type PushResponse = common.PushResponse
24
25func sendJSONResponse(c echo.Context, statusCode int, response interface{}) error {
26 return c.JSON(statusCode, response)
27}
28
29func (info *InfoResponse) Populate(a *server.App) {
30 info.Uptime = a.GetUptime()
31 info.Version = a.GetVersionstring()
32}
33
34// Handle GET on /api/ping endpoint
35func GetInfos(c echo.Context, a *server.App) error {
36 payload := new(common.RequestPayload)
37 if err := c.Bind(payload); err != nil {
38 return c.JSON(http.StatusBadRequest, "Invalid payload: "+err.Error())
39 }
40
41 if payload.Type != "ping" {
42 return c.JSON(http.StatusBadRequest, "Invalid payload type")
43 }
44
45 pingPayload, ok := payload.Payload.(common.PingPayload)
46 if !ok {
47 return c.JSON(http.StatusBadRequest, "Invalid payload structure")
48 }
49
50 if pingPayload.Message != "ping" {
51 return c.JSON(http.StatusBadRequest, "Invalid ping message")
52 }
53
54 // Prepare and populate the information
55 info := &InfoResponse{}
56 info.Populate(a)
57
58 return c.JSON(http.StatusOK, info)
59}
60
61func PostPush(c echo.Context, a *server.App) error {
62 // Initialize pushPayload object
63 payload := &common.PushPayload{
64 ImageName: c.Request().Header.Get("X-Image-Name"),
65 }
66
67 if payload.ImageName == "" {
68 return c.JSON(http.StatusBadRequest, "Invalid image name")
69 }
70
71 imageReader := c.Request().Body
72 defer imageReader.Close()
73
74 // Rename the image to a valid name so that it can be saved (remove user/, :tag and add .tar)
75 imageFileName := payload.ImageName
76 imageFileName = regexp.MustCompile(`^([a-zA-Z0-9\-_.]+\/)?`).ReplaceAllString(imageFileName, "")
77 imageFileName = regexp.MustCompile(`(:[a-zA-Z0-9\-_.]+)?$`).ReplaceAllString(imageFileName, "")
78 imageFileName = imageFileName + ".tar"
79
80 // Save the image tar in the storage
81 imagePath, err := store.SaveImageToStorage(&a.Config, imageFileName, imageReader)
82 if err != nil {
83 return sendJSONResponse(c, http.StatusInternalServerError, PushResponse{
84 Success: false,
85 Message: fmt.Sprintf("Failed to save image: %v", err),
86 })
87 }
88
89 // Debug
90 fmt.Printf("Image saved successfully to: %s\n", imagePath)
91
92 // Import the tar in docker
93 imageID, err := docker.ImportImageToEngine(imagePath)
94 if err != nil {
95 store.RemoveFromStorage(imagePath) // Clean up the saved image if import fails
96 return sendJSONResponse(c, http.StatusInternalServerError, PushResponse{
97 Success: false,
98 Message: fmt.Sprintf("Failed to import image: %v", err),
99 })
100 }
101
102 if imageID == "" {
103 return sendJSONResponse(c, http.StatusInternalServerError, PushResponse{
104 Success: false,
105 Message: "Imported image ID is empty",
106 })
107 }
108
109 // Remove the image from the storage
110 err = store.RemoveFromStorage(imagePath)
111 if err != nil {
112 return sendJSONResponse(c, http.StatusInternalServerError, PushResponse{
113 Success: false,
114 Message: fmt.Sprintf("Failed to remove temporary image file: %v", err),
115 })
116 }
117
118 fmt.Printf("Image imported successfully: %s\n", payload.ImageName)
119
120 // Update the payload with the image ID
121 payload.ImageID = imageID
122
123 // If we arrive here, send back a success response with the target domain
124 return sendJSONResponse(c, http.StatusOK, common.PushResponse{
125 Success: true,
126 Message: "Deployment successful",
127 })
128}
129
130func PostDeploy(c echo.Context, a *server.App) error {
131 // Initialize pushPayload object
132 payload := &common.DeployPayload{
133 Port: c.Request().Header.Get("X-Ports"),
134 ImageName: c.Request().Header.Get("X-Image-Name"),
135 TargetDomain: c.Request().Header.Get("X-Target-Domain"),
136 }
137
138 if payload.ImageName == "" {
139 return c.JSON(http.StatusBadRequest, "Invalid image name")
140 }
141
142 imageReader := c.Request().Body
143 defer imageReader.Close()
144
145 // Rename the image to a valid name so that it can be saved (remove user/, :tag and add .tar)
146 imageFileName := payload.ImageName
147 imageFileName = regexp.MustCompile(`^([a-zA-Z0-9\-_.]+\/)?`).ReplaceAllString(imageFileName, "")
148 imageFileName = regexp.MustCompile(`(:[a-zA-Z0-9\-_.]+)?$`).ReplaceAllString(imageFileName, "")
149 imageFileName = imageFileName + ".tar"
150
151 // Check the ports struct, if there is no /tcp, or /udp, add /tcp
152 if !regexp.MustCompile(`\/(tcp|udp)$`).MatchString(payload.Port) {
153 payload.Port = payload.Port + "/tcp"
154 }
155
156 // Check the target domain, if there is no https:// or http://, add https://
157 if !regexp.MustCompile(`^https?:\/\/`).MatchString(payload.TargetDomain) {
158 payload.TargetDomain = "https://" + payload.TargetDomain
159 }
160
161 // Save the image tar in the storage
162 imagePath, err := store.SaveImageToStorage(&a.Config, imageFileName, imageReader)
163 if err != nil {
164 return sendJSONResponse(c, http.StatusInternalServerError, DeployResponse{
165 Success: false,
166 Message: fmt.Sprintf("Failed to save image: %v", err),
167 })
168 }
169
170 // Debug
171 fmt.Printf("Image saved successfully to: %s\n", imagePath)
172
173 // Import the tar in docker
174 imageID, err := docker.ImportImageToEngine(imagePath)
175 if err != nil {
176 store.RemoveFromStorage(imagePath) // Clean up the saved image if import fails
177 return sendJSONResponse(c, http.StatusInternalServerError, DeployResponse{
178 Success: false,
179 Message: fmt.Sprintf("Failed to import image: %v", err),
180 })
181 }
182
183 if imageID == "" {
184 return sendJSONResponse(c, http.StatusInternalServerError, DeployResponse{
185 Success: false,
186 Message: "Imported image ID is empty",
187 })
188 }
189
190 // Remove the image from the storage
191 err = store.RemoveFromStorage(imagePath)
192 if err != nil {
193 return sendJSONResponse(c, http.StatusInternalServerError, DeployResponse{
194 Success: false,
195 Message: fmt.Sprintf("Failed to remove temporary image file: %v", err),
196 })
197 }
198
199 fmt.Printf("Image imported successfully: %s\n", payload.ImageName)
200
201 // Update the payload with the image ID
202 payload.ImageID = imageID
203
204 // Create the container using cmdparams.FromPayloadStructToCmdParams
205 params, err := cmdparams.FromPayloadStructToCmdParams(payload, a, imageID)
206 if err != nil {
207 return sendJSONResponse(c, http.StatusInternalServerError, DeployResponse{
208 Success: false,
209 Message: fmt.Sprintf("Failed to create command parameters: %v", err),
210 })
211 }
212 // Create the container
213 containerID, err := docker.CreateContainer(params)
214 if err != nil {
215 return sendJSONResponse(c, http.StatusInternalServerError, DeployResponse{
216 Success: false,
217 Message: fmt.Sprintf("Failed to create container: %v", err),
218 })
219 }
220
221 // Start the container
222 err = docker.StartContainer(containerID)
223 if err != nil {
224 docker.RemoveContainer(containerID) // Clean up if start fails
225 return sendJSONResponse(c, http.StatusInternalServerError, DeployResponse{
226 Success: false,
227 Message: fmt.Sprintf("Failed to start container: %v", err),
228 })
229 }
230
231 // If we arrive here, send back a success response with the target domain
232 response := DeployResponse{
233 Success: true,
234 Message: "Deployment successful",
235 Domain: payload.TargetDomain,
236 }
237
238 // Log the response before sending
239 responseJSON, _ := json.Marshal(response)
240 fmt.Printf("Server response: %s\n", string(responseJSON))
241
242 return c.JSON(http.StatusOK, response)
243}