latest cdbcd03

editcontainer.go RawHistoryFolder

1package handler
2
3import (
4	"fmt"
5	"log"
6	"strings"
7
8	"github.com/bnema/gordon/internal/server"
9	"github.com/bnema/gordon/internal/templating/cmdparams"
10	"github.com/bnema/gordon/internal/templating/render"
11	"github.com/bnema/gordon/pkg/docker"
12	"github.com/labstack/echo/v4"
13	"gopkg.in/yaml.v3"
14)
15
16func generateOrderedYAML(infoMap map[string]interface{}) (string, error) {
17	orderedKeys := []string{"Name", "Image", "Hostname", "Ports", "Environment", "Labels", "Network", "Volumes", "Restart"}
18
19	var yamlString string
20	for _, key := range orderedKeys {
21		value, exists := infoMap[key]
22		if !exists {
23			continue
24		}
25
26		yamlBytes, err := yaml.Marshal(map[string]interface{}{key: value})
27		if err != nil {
28			return "", err
29		}
30
31		yamlString += string(yamlBytes)
32	}
33
34	return yamlString, nil
35}
36
37// ContainerManagerEditGET displays the edit container view
38func ContainerManagerEditGET(c echo.Context, a *server.App) error {
39	containerID := c.Param("ID")
40
41	// Get the container info
42	containerInfo, err := docker.GetContainerInfo(containerID)
43	if err != nil {
44		return sendError(c, err)
45	}
46
47	// Trim the slash from the container name
48	containerInfo.Name = strings.TrimPrefix(containerInfo.Name, "/")
49	// Get the network names
50	networkNames := make([]string, 0, len(containerInfo.NetworkSettings.Networks))
51	for networkName := range containerInfo.NetworkSettings.Networks {
52		networkNames = append(networkNames, networkName)
53	}
54
55	// Prepare Ports
56	portMappings := make([]string, 0)
57	for port, bindings := range containerInfo.HostConfig.PortBindings {
58		for _, binding := range bindings {
59			portMappings = append(portMappings, fmt.Sprintf("%s:%s/%s", binding.HostPort, port.Port(), port.Proto()))
60		}
61	}
62
63	// Prepare Volumes (Mounts)
64	volumeMappings := make([]string, 0)
65	for _, mount := range containerInfo.Mounts {
66		volumeMappings = append(volumeMappings, fmt.Sprintf("%s:%s", mount.Source, mount.Destination))
67	}
68
69	// Initialize a map to store the information from the container
70	infoMap := make(map[string]interface{})
71
72	// Populate the map with container details
73	infoMap["Name"] = containerInfo.Name
74	infoMap["Image"] = containerInfo.Config.Image
75	infoMap["Hostname"] = containerInfo.Config.Hostname
76	infoMap["Ports"] = portMappings
77	infoMap["Volumes"] = volumeMappings
78	infoMap["Environment"] = containerInfo.Config.Env
79	infoMap["Labels"] = containerInfo.Config.Labels
80	infoMap["Network"] = networkNames
81	infoMap["Restart"] = containerInfo.HostConfig.RestartPolicy.Name
82
83	// Convert the infoMap to YAML format
84	yamlString, err := generateOrderedYAML(infoMap)
85	if err != nil {
86		return sendError(c, err)
87	}
88
89	// Pass the containerInfo to the data map
90	data := map[string]interface{}{
91		"Title":         "Edit container",
92		"ID":            containerID,
93		"ContainerInfo": yamlString,
94	}
95
96	rendererData, err := render.GetHTMLRenderer("html/fragments", "editcontainer.gohtml", a.TemplateFS, a)
97
98	if err != nil {
99		return sendError(c, err)
100	}
101
102	renderedHTML, err := rendererData.Render(data, a)
103
104	if err != nil {
105		return sendError(c, err)
106	}
107
108	return c.HTML(200, renderedHTML)
109}
110
111type TransactionStep func() error
112type RollbackStep func() error
113
114type TransactionQueue struct {
115	NewContainerID    string
116	NewContainerName  string
117	TempContainerName string
118	OldContainerID    string
119	OldContainerName  string
120	steps             []TransactionStep
121	rollbacks         []RollbackStep
122}
123
124func (tq *TransactionQueue) Add(step TransactionStep, rollback RollbackStep) {
125	tq.steps = append(tq.steps, step)
126	tq.rollbacks = append(tq.rollbacks, rollback)
127}
128
129func (tq *TransactionQueue) Execute() error {
130	var transactionErrors []error
131	var rollbackErrors []error
132	for i, step := range tq.steps {
133		err := step()
134		if err != nil {
135			log.Printf("Error occurred at step index %d: %v", i, err)
136			transactionErrors = append(transactionErrors, err)
137			for j := i - 1; j >= 0; j-- {
138				rollback := tq.rollbacks[j]
139				if rollback != nil {
140					if rollbackErr := rollback(); rollbackErr != nil {
141						log.Printf("Failed to execute rollback at index %d: %v", j, rollbackErr)
142						rollbackErrors = append(rollbackErrors, rollbackErr)
143					}
144				}
145			}
146			return fmt.Errorf("transaction failed: %v; rollback errors: %v", transactionErrors, rollbackErrors)
147		}
148	}
149	return nil
150}
151
152// ContainerManagerEditPOST handles the edit container form submission
153func ContainerManagerEditPOST(c echo.Context, a *server.App) error {
154	tq := &TransactionQueue{}
155	oldContainerID := c.Param("ID")
156
157	oldContainerInfo, err := docker.GetContainerInfo(oldContainerID)
158	if err != nil {
159		return sendError(c, fmt.Errorf("getting old container info failed: %w", err))
160	}
161
162	containerConfig := c.FormValue("container_config")
163	var containerParams cmdparams.YAMLContainerParams
164	err = yaml.Unmarshal([]byte(containerConfig), &containerParams)
165	if err != nil {
166		return sendError(c, fmt.Errorf("YAML unmarshal failed: %w", err))
167	}
168
169	// Use render.FromYAMLStructToCmdParams to convert YAMLContainerParams to ContainerCommandParams
170	cmdParams, err := cmdparams.FromYAMLStructToCmdParams(containerParams)
171	if err != nil {
172		return fmt.Errorf("failed to convert YAML: %w", err)
173	}
174
175	tq.OldContainerID = oldContainerID
176	tq.OldContainerName = strings.TrimPrefix(oldContainerInfo.Name, "/")
177	tq.NewContainerName = cmdParams.ContainerName
178	tq.TempContainerName = fmt.Sprintf("%s-temp", tq.NewContainerName)
179
180	cmdParams.ContainerName = tq.TempContainerName
181
182	// 1. Stop the old container
183	tq.Add(StopOldContainerStep(tq), StartOldContainerRollback(tq))
184
185	// 2. Create the new container
186	tq.Add(CreateNewContainerStep(tq, cmdParams), RemoveNewContainerRollback(tq))
187
188	// 3. Start the new container
189	tq.Add(StartNewContainerStep(tq), StopNewContainerRollback(tq))
190
191	// 4. Rename the new container to the original name
192	tq.Add(RemoveOldContainerStep(tq), nil)
193
194	// 5. Remove the old container
195	tq.Add(RenameNewContainerStep(tq), RenameNewContainerRollback(tq))
196
197	err = tq.Execute()
198	if err != nil {
199		return sendError(c, fmt.Errorf("transaction failed: %w", err))
200	}
201
202	return c.HTML(200, ActionSuccess(a))
203}
204
205// CreateNewContainerStep creates a new container with the given parameters
206func CreateNewContainerStep(tq *TransactionQueue, cmdParams docker.ContainerCommandParams) TransactionStep {
207	return func() error {
208		var err error
209		tq.NewContainerID, err = docker.CreateContainer(cmdParams)
210		if err != nil {
211			return fmt.Errorf("creating new container failed: %w", err)
212		}
213		return nil
214	}
215}
216
217// StartNewContainerStep starts the new container
218func StartNewContainerStep(tq *TransactionQueue) TransactionStep {
219	return func() error {
220		err := docker.StartContainer(tq.NewContainerID)
221		if err != nil {
222			return fmt.Errorf("starting new container failed: %w", err)
223		}
224		return nil
225	}
226}
227
228// StopOldContainerStep stops the old container
229func StopOldContainerStep(tq *TransactionQueue) TransactionStep {
230	return func() error {
231		err := docker.StopContainer(tq.OldContainerID)
232		if err != nil {
233			return fmt.Errorf("stopping old container failed: %w", err)
234		}
235		return nil
236	}
237}
238
239// RemoveOldContainerStep removes the old container
240func RemoveOldContainerStep(tq *TransactionQueue) TransactionStep {
241	return func() error {
242		err := docker.RemoveContainer(tq.OldContainerID)
243		if err != nil {
244			return fmt.Errorf("removing old container failed: %w", err)
245		}
246		return nil
247	}
248}
249
250// RemoveNewContainerRollback removes the new container if the transaction fails
251func RemoveNewContainerRollback(tq *TransactionQueue) RollbackStep {
252	return func() error {
253		err := docker.RemoveContainer(tq.NewContainerID)
254		if err != nil {
255			return fmt.Errorf("rollback: removing new container failed: %w", err)
256		}
257		return nil
258	}
259}
260
261// StopNewContainerRollback stops the new container if the transaction fails
262func StopNewContainerRollback(tq *TransactionQueue) RollbackStep {
263	return func() error {
264		err := docker.StopContainer(tq.NewContainerID)
265		if err != nil {
266			return fmt.Errorf("rollback: stopping new container failed: %w", err)
267		}
268		return nil
269	}
270}
271
272// StartOldContainerRollback starts the old container if the transaction fails
273func StartOldContainerRollback(tq *TransactionQueue) RollbackStep {
274	return func() error {
275		err := docker.StartContainer(tq.OldContainerID)
276		if err != nil {
277			return fmt.Errorf("rollback: starting old container failed: %w", err)
278		}
279		return nil
280	}
281}
282
283// RenameNewContainerStep renames the new temporary container to the original name
284func RenameNewContainerStep(tq *TransactionQueue) TransactionStep {
285	return func() error {
286		err := docker.RenameContainer(tq.NewContainerID, tq.NewContainerName)
287		if err != nil {
288			return fmt.Errorf("renaming new container failed: %w", err)
289		}
290		return nil
291	}
292}
293
294// RenameNewContainerRollback renames the new container back to the temporary name if it fails
295func RenameNewContainerRollback(tq *TransactionQueue) RollbackStep {
296	return func() error {
297		err := docker.RenameContainer(tq.NewContainerID, tq.TempContainerName)
298		if err != nil {
299			return fmt.Errorf("rollback: renaming new container back failed: %w", err)
300		}
301		return nil
302	}
303}

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