0.1.5 b849f6c

cli-endpoints.go RawHistoryFolder

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}

Keyboard shortcuts

Single-key shortcuts are on. ctrl k and esc work whatever this says.

ctrl k :
go to a section, branch, tag, file or repository
?
this list
j k
move the row cursor down / up
enter
open the row under the cursor; on Files, a directory opens or closes in place
↑ ↓ ← → home end
in the file tree (focus on a row): previous / next row, close / open a directory or go to its parent / first entry, first / last row. They never open a file
l h
on Files: open a directory or a file / close a directory or go up to its parent, then to the parent page. Elsewhere: into the side panel (on a merge request, its files) / back to the content; there j k move between its controls
g g G
first / last row
/
filter the list; on Files, find a file anywhere in the repository (esc clears, enter opens the best match)
b
switch branch or tag
g s g f g c g b g m g i g r g a
tabs: summary, files, commits, branches & tags, merge requests, issues, releases, actions (when available)
g h
home
w
wide mode: use the whole width of the screen, or not (remembered)
y
copy a permalink pinned to the commit
d u x
diff: split / unified / collapse or expand all
e E
diff: the whole file in view / every file, with the lines the hunks hide
n
merge request: next note in the diff
esc
close a dialog, leave a field

Branches 7

Tags 106

↑↓ moveenter openesc close