v2.31.0-dev4 f8fb41e

volumes.go RawHistoryFolder

1package preview
2
3import (
4	"context"
5	"fmt"
6	"strings"
7
8	"github.com/bnema/gordon/internal/domain"
9)
10
11// VolumeCloner abstracts container runtime operations needed for volume cloning.
12type VolumeCloner interface {
13	CreateVolume(ctx context.Context, name string) error
14	RemoveVolume(ctx context.Context, name string, force bool) error
15	CreateContainer(ctx context.Context, config *domain.ContainerConfig) (*domain.Container, error)
16	StartContainer(ctx context.Context, containerID string) error
17	WaitForContainer(ctx context.Context, containerID string) error
18	RemoveContainer(ctx context.Context, containerID string, force bool) error
19	ListVolumes(ctx context.Context) ([]*domain.VolumeInfo, error)
20}
21
22// VolumeNamer is a function that maps a source volume name to a destination volume name.
23type VolumeNamer func(sourceVolName string) string
24
25// DefaultNamer returns a VolumeNamer that uses BuildCloneVolumeName with the given preview name.
26func DefaultNamer(previewName string) VolumeNamer {
27	return func(sourceVolName string) string {
28		return BuildCloneVolumeName(previewName, sourceVolName)
29	}
30}
31
32// BuildCloneVolumeName generates a preview volume name.
33func BuildCloneVolumeName(previewName, volumeName string) string {
34	return "preview-" + previewName + "-" + volumeName
35}
36
37// BuildCloneContainerName generates a preview attachment container name from an image reference.
38func BuildCloneContainerName(previewName, image string) string {
39	name := image
40	if i := strings.LastIndex(name, "/"); i >= 0 {
41		name = name[i+1:]
42	}
43	if i := strings.Index(name, ":"); i >= 0 {
44		name = name[:i]
45	}
46	return "preview-" + previewName + "-" + name
47}
48
49// CloneVolumes copies volumes from source using a read-only helper container.
50// sourceVolumes is a list of actual Docker volume names to clone.
51func CloneVolumes(ctx context.Context, cloner VolumeCloner, namer VolumeNamer, sourceVolumes []string) ([]string, error) {
52	var clonedNames []string
53	for _, sourceVolName := range sourceVolumes {
54		destName := namer(sourceVolName)
55
56		if err := cloner.CreateVolume(ctx, destName); err != nil {
57			cleanupVolumes(ctx, cloner, clonedNames)
58			return nil, fmt.Errorf("create volume %s: %w", destName, err)
59		}
60
61		helperConfig := &domain.ContainerConfig{
62			Image: "busybox:1.37",
63			Name:  "gordon-vol-copy-" + destName,
64			Cmd:   []string{"cp", "-a", "/src/.", "/dst/"},
65			Volumes: map[string]string{
66				"/dst": destName,
67			},
68			ReadOnlyVolumes: map[string]string{
69				"/src": sourceVolName,
70			},
71		}
72
73		created, err := cloner.CreateContainer(ctx, helperConfig)
74		if err != nil {
75			_ = cloner.RemoveVolume(ctx, destName, true)
76			cleanupVolumes(ctx, cloner, clonedNames)
77			return nil, fmt.Errorf("create copy helper for %s: %w", destName, err)
78		}
79
80		if err := cloner.StartContainer(ctx, created.ID); err != nil {
81			_ = cloner.RemoveContainer(ctx, created.ID, true)
82			_ = cloner.RemoveVolume(ctx, destName, true)
83			cleanupVolumes(ctx, cloner, clonedNames)
84			return nil, fmt.Errorf("start copy helper for %s: %w", destName, err)
85		}
86
87		if err := cloner.WaitForContainer(ctx, created.ID); err != nil {
88			_ = cloner.RemoveContainer(ctx, created.ID, true)
89			_ = cloner.RemoveVolume(ctx, destName, true)
90			cleanupVolumes(ctx, cloner, clonedNames)
91			return nil, fmt.Errorf("wait for copy helper %s: %w", destName, err)
92		}
93
94		_ = cloner.RemoveContainer(ctx, created.ID, true)
95		clonedNames = append(clonedNames, destName)
96	}
97	return clonedNames, nil
98}
99
100func cleanupVolumes(ctx context.Context, cloner VolumeCloner, names []string) {
101	for _, name := range names {
102		_ = cloner.RemoveVolume(ctx, name, true)
103	}
104}

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