v3.3.1 31f1eec

config.go RawHistoryFolder

1package services
2
3import (
4	"fmt"
5	"strings"
6	"time"
7
8	"github.com/bnema/gordon/internal/domain"
9)
10
11type Config struct {
12	Name      string            `mapstructure:"name"`
13	Image     string            `mapstructure:"image"`
14	Enabled   bool              `mapstructure:"enabled"`
15	Env       []string          `mapstructure:"env"`
16	EnvFile   string            `mapstructure:"env_file"`
17	Secrets   []SecretRefConfig `mapstructure:"secrets"`
18	Readiness ReadinessConfig   `mapstructure:"readiness"`
19	Cleanup   CleanupConfig     `mapstructure:"cleanup"`
20	Ports     []PortConfig      `mapstructure:"ports"`
21	Volumes   []VolumeConfig    `mapstructure:"volumes"`
22}
23
24type PortConfig struct {
25	Name         string                 `mapstructure:"name"`
26	Container    int                    `mapstructure:"container"`
27	Protocol     domain.NetworkProtocol `mapstructure:"protocol"`
28	Publish      string                 `mapstructure:"publish"`
29	Private      bool                   `mapstructure:"private"`
30	Public       bool                   `mapstructure:"public"`
31	TrustedCIDRs []string               `mapstructure:"trusted_cidrs"`
32}
33
34type VolumeConfig struct {
35	Source   string `mapstructure:"source"`
36	Target   string `mapstructure:"target"`
37	ReadOnly bool   `mapstructure:"read_only"`
38}
39
40type ResolvedVolumeMount struct {
41	Source   string
42	Target   string
43	ReadOnly bool
44	Managed  bool
45}
46
47type SecretRefConfig struct {
48	Name string `mapstructure:"name"`
49	Key  string `mapstructure:"key"`
50}
51
52type ReadinessConfig struct {
53	Type     string `mapstructure:"type"`
54	Path     string `mapstructure:"path"`
55	Contains string `mapstructure:"contains"`
56	Timeout  string `mapstructure:"timeout"`
57}
58
59type CleanupConfig struct {
60	PreserveVolumes *bool `mapstructure:"preserve_volumes"`
61	RemoveContainer *bool `mapstructure:"remove_container"`
62}
63
64func ToDomain(configs []Config) ([]domain.StandaloneService, error) {
65	services := make([]domain.StandaloneService, 0, len(configs))
66	seenNames := make(map[string]struct{}, len(configs))
67	seenRuntimeNames := make(map[string]string, len(configs))
68	for i, cfg := range configs {
69		svc, err := cfg.ToDomain()
70		if err != nil {
71			return nil, fmt.Errorf("service config %d: %w", i, err)
72		}
73		name := strings.TrimSpace(svc.Name)
74		if _, ok := seenNames[name]; ok {
75			return nil, fmt.Errorf("service config %d: duplicate service name %q", i, name)
76		}
77		runtimeName := serviceRuntimeIdentifier(name)
78		if previous, ok := seenRuntimeNames[runtimeName]; ok {
79			return nil, fmt.Errorf("service config %d: duplicate service name %q normalizes to same runtime identifier as %q", i, name, previous)
80		}
81		seenNames[name] = struct{}{}
82		seenRuntimeNames[runtimeName] = name
83		services = append(services, svc)
84	}
85	return services, nil
86}
87
88func (c Config) ToDomain() (domain.StandaloneService, error) {
89	if err := c.Cleanup.validate(); err != nil {
90		return domain.StandaloneService{}, err
91	}
92	readiness, err := c.readinessToDomain()
93	if err != nil {
94		return domain.StandaloneService{}, err
95	}
96	svc := domain.StandaloneService{
97		Name:      c.Name,
98		Image:     c.Image,
99		Enabled:   c.Enabled,
100		Env:       append([]string(nil), c.Env...),
101		EnvFile:   c.EnvFile,
102		Secrets:   secretRefsToDomain(c.Secrets),
103		Readiness: readiness,
104		Cleanup:   c.Cleanup.toDomain(),
105		Ports:     portsToDomain(c.Ports),
106		Volumes:   volumesToDomain(c.Volumes),
107	}
108	if err := svc.Validate(); err != nil {
109		return domain.StandaloneService{}, err
110	}
111	return svc, nil
112}
113
114func (c Config) readinessToDomain() (domain.StandaloneServiceReadiness, error) {
115	readinessType := c.Readiness.Type
116	if readinessType == "" {
117		readinessType = domain.StandaloneServiceReadinessNone
118	}
119	readiness := domain.StandaloneServiceReadiness{Type: readinessType, Path: c.Readiness.Path, Contains: c.Readiness.Contains}
120	if c.Readiness.Timeout != "" {
121		timeout, err := time.ParseDuration(c.Readiness.Timeout)
122		if err != nil {
123			return domain.StandaloneServiceReadiness{}, fmt.Errorf("readiness timeout %q is invalid: %w", c.Readiness.Timeout, err)
124		}
125		if timeout <= 0 {
126			return domain.StandaloneServiceReadiness{}, fmt.Errorf("readiness timeout must be positive when set")
127		}
128		readiness.Timeout = timeout
129		readiness.TimeoutSet = true
130	}
131	return readiness, nil
132}
133
134func (c CleanupConfig) toDomain() domain.StandaloneServiceCleanup {
135	cleanup := domain.StandaloneServiceCleanup{PreserveVolumes: true, RemoveContainer: true}
136	if c.PreserveVolumes != nil {
137		cleanup.PreserveVolumes = *c.PreserveVolumes
138	}
139	if c.RemoveContainer != nil {
140		cleanup.RemoveContainer = *c.RemoveContainer
141	}
142	return cleanup
143}
144
145func (c CleanupConfig) validate() error {
146	if c.PreserveVolumes == nil || c.RemoveContainer == nil {
147		return nil
148	}
149	if !*c.PreserveVolumes && !*c.RemoveContainer {
150		return fmt.Errorf("cleanup cannot set preserve_volumes=false with remove_container=false")
151	}
152	return nil
153}
154
155func portsToDomain(configs []PortConfig) []domain.StandaloneServicePort {
156	ports := make([]domain.StandaloneServicePort, 0, len(configs))
157	for _, cfg := range configs {
158		ports = append(ports, domain.StandaloneServicePort{
159			Name:         cfg.Name,
160			Container:    cfg.Container,
161			Protocol:     cfg.Protocol,
162			Publish:      cfg.Publish,
163			Private:      cfg.Private,
164			Public:       cfg.Public,
165			TrustedCIDRs: append([]string(nil), cfg.TrustedCIDRs...),
166		})
167	}
168	return ports
169}
170
171func volumesToDomain(configs []VolumeConfig) []domain.StandaloneServiceVolume {
172	volumes := make([]domain.StandaloneServiceVolume, 0, len(configs))
173	for _, cfg := range configs {
174		volumes = append(volumes, domain.StandaloneServiceVolume{Source: cfg.Source, Target: cfg.Target, ReadOnly: cfg.ReadOnly})
175	}
176	return volumes
177}
178
179func secretRefsToDomain(configs []SecretRefConfig) []domain.StandaloneServiceSecretRef {
180	secrets := make([]domain.StandaloneServiceSecretRef, 0, len(configs))
181	for _, cfg := range configs {
182		secrets = append(secrets, domain.StandaloneServiceSecretRef{Name: cfg.Name, Key: cfg.Key})
183	}
184	return secrets
185}
186
187func ResolveVolumeMounts(prefix, serviceName string, explicitVolumes []domain.StandaloneServiceVolume, imageVolumePaths []string) []ResolvedVolumeMount {
188	if len(explicitVolumes) > 0 {
189		mounts := make([]ResolvedVolumeMount, 0, len(explicitVolumes))
190		for _, volume := range explicitVolumes {
191			mounts = append(mounts, ResolvedVolumeMount{Source: volume.Source, Target: volume.Target, ReadOnly: volume.ReadOnly})
192		}
193		return mounts
194	}
195
196	mounts := make([]ResolvedVolumeMount, 0, len(imageVolumePaths))
197	for _, path := range imageVolumePaths {
198		mounts = append(mounts, ResolvedVolumeMount{Source: ManagedServiceVolumeName(prefix, serviceName, path), Target: path, Managed: true})
199	}
200	return mounts
201}
202
203func ManagedServiceVolumeName(prefix, serviceName, volumePath string) string {
204	return fmt.Sprintf("%s-%s-%s",
205		prefix,
206		strings.ReplaceAll(serviceName, ".", "-"),
207		strings.ReplaceAll(strings.Trim(volumePath, "/"), "/", "-"))
208}
209
210func serviceRuntimeIdentifier(name string) string {
211	return strings.NewReplacer(".", "-", "_", "-", "/", "-").Replace(name)
212}

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