2.0.1 dcf7d6b

handlers.go RawHistoryFolder

1package events
2
3import (
4	"context"
5	"fmt"
6	"strings"
7
8	"github.com/rs/zerolog/log"
9	"gordon/internal/config"
10	"gordon/internal/container"
11	"gordon/pkg/runtime"
12)
13
14type ContainerEventHandler struct {
15	manager *container.Manager
16	config  *config.Config
17}
18
19func NewContainerEventHandler(manager *container.Manager, config *config.Config) *ContainerEventHandler {
20	return &ContainerEventHandler{
21		manager: manager,
22		config:  config,
23	}
24}
25
26func (h *ContainerEventHandler) CanHandle(eventType EventType) bool {
27	switch eventType {
28	case ImagePushed, ConfigReload, ContainerStop, ContainerStart:
29		return true
30	default:
31		return false
32	}
33}
34
35func (h *ContainerEventHandler) Handle(event Event) error {
36	switch event.Type {
37	case ImagePushed:
38		return h.handleImagePushed(event)
39	case ConfigReload:
40		return h.handleConfigReload(event)
41	case ContainerStop:
42		return h.handleContainerStop(event)
43	case ContainerStart:
44		return h.handleContainerStart(event)
45	default:
46		return fmt.Errorf("unsupported event type: %s", event.Type)
47	}
48}
49
50func (h *ContainerEventHandler) handleImagePushed(event Event) error {
51	imageName := event.ImageName
52	tag := event.Tag
53	
54	if imageName == "" {
55		return fmt.Errorf("image name is required for ImagePushed event")
56	}
57	
58	if tag == "" {
59		tag = "latest"
60	}
61	
62	fullImageName := fmt.Sprintf("%s:%s", imageName, tag)
63	
64	log.Info().
65		Str("image", fullImageName).
66		Str("event_id", event.ID).
67		Msg("Processing image push event")
68	
69	routes := h.findRoutesForImage(fullImageName)
70	if len(routes) == 0 {
71		log.Debug().
72			Str("image", fullImageName).
73			Msg("No routes configured for pushed image")
74		return nil
75	}
76	
77	for _, route := range routes {
78		if err := h.deployContainerForRoute(route, fullImageName); err != nil {
79			log.Error().
80				Err(err).
81				Str("route", route).
82				Str("image", fullImageName).
83				Msg("Failed to deploy container for route")
84		}
85	}
86	
87	return nil
88}
89
90func (h *ContainerEventHandler) handleConfigReload(event Event) error {
91	log.Info().
92		Str("event_id", event.ID).
93		Msg("Processing configuration reload event")
94	
95	ctx := context.Background()
96	
97	// Get current containers managed by Gordon
98	currentContainers := h.manager.ListContainers()
99	
100	// Track which routes are currently active
101	activeRoutes := make(map[string]*runtime.Container)
102	for _, container := range currentContainers {
103		if route, exists := container.Labels["gordon.route"]; exists {
104			activeRoutes[route] = container
105		}
106	}
107	
108	// Process each configured route
109	for route, imageName := range h.config.Routes {
110		if container, exists := activeRoutes[route]; exists {
111			// Route exists - check if image has changed
112			currentImage := container.Labels["gordon.image"]
113			if currentImage != imageName {
114				log.Info().
115					Str("route", route).
116					Str("old_image", currentImage).
117					Str("new_image", imageName).
118					Msg("Image changed for route, redeploying")
119				
120				if err := h.deployContainerForRoute(route, imageName); err != nil {
121					log.Error().
122						Err(err).
123						Str("route", route).
124						Msg("Failed to redeploy container for route")
125				}
126			}
127			// Remove from active routes so we know it's been processed
128			delete(activeRoutes, route)
129		} else {
130			// New route - deploy container
131			log.Info().
132				Str("route", route).
133				Str("image", imageName).
134				Msg("New route detected, deploying container")
135			
136			if err := h.deployContainerForRoute(route, imageName); err != nil {
137				log.Error().
138					Err(err).
139					Str("route", route).
140					Msg("Failed to deploy container for new route")
141			}
142		}
143	}
144	
145	// Stop containers for routes that are no longer configured
146	for route, container := range activeRoutes {
147		log.Info().
148			Str("route", route).
149			Str("container_id", container.ID).
150			Msg("Route no longer configured, stopping container")
151		
152		if err := h.manager.StopContainer(ctx, container.ID); err != nil {
153			log.Error().
154				Err(err).
155				Str("container_id", container.ID).
156				Msg("Failed to stop container for removed route")
157		}
158		
159		if err := h.manager.RemoveContainer(ctx, container.ID, true); err != nil {
160			log.Error().
161				Err(err).
162				Str("container_id", container.ID).
163				Msg("Failed to remove container for removed route")
164		}
165	}
166	
167	return nil
168}
169
170func (h *ContainerEventHandler) handleContainerStop(event Event) error {
171	containerID := event.ContainerID
172	if containerID == "" {
173		return fmt.Errorf("container ID is required for ContainerStop event")
174	}
175	
176	log.Info().
177		Str("container_id", containerID).
178		Str("event_id", event.ID).
179		Msg("Processing container stop event")
180	
181	ctx := context.Background()
182	if err := h.manager.StopContainer(ctx, containerID); err != nil {
183		return fmt.Errorf("failed to stop container %s: %w", containerID, err)
184	}
185	
186	return nil
187}
188
189func (h *ContainerEventHandler) handleContainerStart(event Event) error {
190	containerID := event.ContainerID
191	if containerID == "" {
192		return fmt.Errorf("container ID is required for ContainerStart event")
193	}
194	
195	log.Info().
196		Str("container_id", containerID).
197		Str("event_id", event.ID).
198		Msg("Processing container start event")
199	
200	// StartContainer method doesn't exist on Manager - containers are started via DeployContainer
201	// For manual start, we'd need to add this method or use the runtime directly
202	return fmt.Errorf("container start not implemented - use automatic deployment instead")
203}
204
205func (h *ContainerEventHandler) findRoutesForImage(imageName string) []string {
206	var routes []string
207	
208	for route, configuredImage := range h.config.Routes {
209		if strings.EqualFold(configuredImage, imageName) {
210			routes = append(routes, route)
211		}
212	}
213	
214	return routes
215}
216
217func (h *ContainerEventHandler) deployContainerForRoute(route, imageName string) error {
218	ctx := context.Background()
219	
220	containers := h.manager.ListContainers()
221	
222	containerName := fmt.Sprintf("gordon-%s", strings.ReplaceAll(route, ".", "-"))
223	
224	// Stop and remove existing container if it exists
225	for _, container := range containers {
226		if container.Name == containerName {
227			log.Info().
228				Str("container_name", containerName).
229				Str("old_container_id", container.ID).
230				Msg("Stopping existing container for route")
231			
232			if err := h.manager.StopContainer(ctx, container.ID); err != nil {
233				log.Warn().
234					Err(err).
235					Str("container_id", container.ID).
236					Msg("Failed to stop existing container")
237			}
238			
239			if err := h.manager.RemoveContainer(ctx, container.ID, true); err != nil {
240				log.Warn().
241					Err(err).
242					Str("container_id", container.ID).
243					Msg("Failed to remove existing container")
244			}
245		}
246	}
247	
248	// Use DeployContainer which handles creation and starting
249	routeConfig := config.Route{
250		Domain: route,
251		Image:  imageName,
252		HTTPS:  true,
253	}
254	
255	container, err := h.manager.DeployContainer(ctx, routeConfig)
256	if err != nil {
257		return fmt.Errorf("failed to deploy container for route %s: %w", route, err)
258	}
259	
260	log.Info().
261		Str("route", route).
262		Str("image", imageName).
263		Str("container_id", container.ID).
264		Str("container_name", containerName).
265		Msg("Successfully deployed container for route")
266	
267	return nil
268}

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