v2.31.0-dev3 cbca441

handler.go RawHistoryFolder

1package preview
2
3import (
4	"context"
5	"fmt"
6	"strings"
7
8	"github.com/bnema/zerowrap"
9
10	"github.com/bnema/gordon/internal/boundaries/out"
11	"github.com/bnema/gordon/internal/domain"
12	"github.com/bnema/gordon/internal/usecase/auto"
13)
14
15// AutoPreviewHandler processes EventImagePushed for preview tag patterns.
16type AutoPreviewHandler struct {
17	serviceCtx     context.Context
18	config         auto.AutoConfigProvider
19	blobStorage    out.BlobStorage
20	previewService *Service
21}
22
23func NewAutoPreviewHandler(
24	serviceCtx context.Context,
25	config auto.AutoConfigProvider,
26	blobStorage out.BlobStorage,
27	previewService *Service,
28) *AutoPreviewHandler {
29	return &AutoPreviewHandler{
30		serviceCtx:     serviceCtx,
31		config:         config,
32		blobStorage:    blobStorage,
33		previewService: previewService,
34	}
35}
36
37func (h *AutoPreviewHandler) CanHandle(t domain.EventType) bool {
38	return t == domain.EventImagePushed
39}
40
41func (h *AutoPreviewHandler) Handle(ctx context.Context, event domain.Event) error {
42	ctx = zerowrap.CtxWithFields(ctx, map[string]any{
43		zerowrap.FieldLayer:   "usecase",
44		zerowrap.FieldHandler: "AutoPreviewHandler",
45		zerowrap.FieldEvent:   string(event.Type),
46		"event_id":            event.ID,
47	})
48	log := zerowrap.FromCtx(ctx)
49
50	payload, ok := event.Data.(domain.ImagePushedPayload)
51	if !ok {
52		log.Debug().Str("actual_type", fmt.Sprintf("%T", event.Data)).Msg("unexpected event data type, skipping")
53		return nil
54	}
55
56	// Extract labels from manifest
57	labels, err := auto.ExtractLabels(ctx, payload.Manifest, h.blobStorage)
58	if err != nil {
59		log.Debug().Err(err).Msg("failed to extract labels, skipping preview handler")
60		return nil
61	}
62
63	previewConfig := h.config.GetPreviewConfig()
64	previewName := ExtractPreviewName(payload.Reference, previewConfig.TagPatterns)
65
66	// Resolve base route: labels first, then route config fallback.
67	// When falling back to routes, skip preview domains (they contain the separator)
68	// to avoid using a preview as the base for another preview.
69	domains := collectDomains(labels)
70	if len(domains) == 0 {
71		routes := h.config.FindRoutesByImage(ctx, payload.Name)
72		for _, r := range routes {
73			if !strings.Contains(r.Domain, previewConfig.Separator) {
74				domains = append(domains, r.Domain)
75			}
76		}
77	}
78	if len(domains) == 0 {
79		log.Debug().Str("image", payload.Name).Msg("no domain found from labels or routes, skipping preview")
80		return nil
81	}
82	baseRouteDomain := domains[0]
83
84	// Validate allowlist
85	allowedDomains := h.config.GetAllowedDomains()
86	previewDomain, err := GeneratePreviewDomain(baseRouteDomain, previewName, previewConfig.Separator)
87	if err != nil {
88		log.Debug().Err(err).Str("base_domain", baseRouteDomain).Msg("failed to generate preview domain")
89		return nil
90	}
91	if !auto.MatchesDomainAllowlist(previewDomain, allowedDomains) {
92		log.Debug().Str("domain", previewDomain).Strs("patterns", allowedDomains).Msg("preview domain not in allowlist, skipping")
93		return nil
94	}
95
96	imageName := payload.Name + ":" + payload.Reference
97
98	// Launch async — volume cloning may exceed event bus timeout
99	go func() {
100		if err := h.previewService.CreatePreview(h.serviceCtx, CreatePreviewRequest{
101			Name:          previewName,
102			Domain:        previewDomain,
103			BaseRoute:     baseRouteDomain,
104			Image:         imageName,
105			HTTPS:         true,
106			PreviewConfig: previewConfig,
107		}); err != nil {
108			log.Error().Err(err).Str("preview", previewName).Msg("failed to create preview")
109		}
110	}()
111
112	return nil
113}
114
115func collectDomains(labels *domain.ImageLabels) []string {
116	var domains []string
117	if labels.Domain != "" {
118		domains = append(domains, labels.Domain)
119	}
120	domains = append(domains, labels.Domains...)
121	return domains
122}

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