Skip to content

Commit 1862858

Browse files
authored
Merge pull request #453 from mike391/Add-support-configure-docker-containers-yaml
Feat: Added support for configuring Docker containers through glance.yaml instead of labels
2 parents 18436e9 + 306c28f commit 1862858

File tree

3 files changed

+117
-29
lines changed

3 files changed

+117
-29
lines changed

docs/configuration.md

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1834,6 +1834,19 @@ Configuration of the containers is done via labels applied to each container:
18341834
glance.description: Movies & shows
18351835
```
18361836

1837+
Alternatively, you can also define the values within your `glance.yml` via the `containers` property, where the key is the container name and each value is the same as the labels but without the "glance." prefix:
1838+
1839+
```yaml
1840+
- type: docker-containers
1841+
containers:
1842+
container_name_1:
1843+
title: Container Name
1844+
description: Description of the container
1845+
url: https://container.domain.com
1846+
icon: si:container-icon
1847+
hide: false
1848+
```
1849+
18371850
For services with multiple containers you can specify a `glance.id` on the "main" container and `glance.parent` on each "child" container:
18381851

18391852
<details>
@@ -1885,13 +1898,17 @@ If any of the child containers are down, their status will propagate up to the p
18851898
| Name | Type | Required | Default |
18861899
| ---- | ---- | -------- | ------- |
18871900
| hide-by-default | boolean | no | false |
1901+
| format-container-names | boolean | no | false |
18881902
| sock-path | string | no | /var/run/docker.sock |
18891903
| category | string | no | |
18901904
| running-only | boolean | no | false |
18911905

18921906
##### `hide-by-default`
18931907
Whether to hide the containers by default. If set to `true` you'll have to manually add a `glance.hide: false` label to each container you want to display. By default all containers will be shown and if you want to hide a specific container you can add a `glance.hide: true` label.
18941908

1909+
##### `format-container-names`
1910+
When set to `true`, automatically converts container names such as `container_name_1` into `Container Name 1`.
1911+
18951912
##### `sock-path`
18961913
The path to the Docker socket. This can also be a [remote socket](https://docs.docker.com/engine/daemon/remote-access/) or proxied socket using something like [docker-socket-proxy](https://github.com/Tecnativa/docker-socket-proxy).
18971914

internal/glance/templates/docker-containers.html

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@
1414
{{- range .Children }}
1515
<li class="flex gap-7 items-center">
1616
<div class="margin-bottom-3">{{ template "state-icon" .StateIcon }}</div>
17-
<div class="color-highlight">{{ .Title }} <span class="size-h5 color-base">{{ .StateText }}</span></div>
17+
<div class="color-highlight">{{ .Name }} <span class="size-h5 color-base">{{ .StateText }}</span></div>
1818
</li>
1919
{{- end }}
2020
</ul>
@@ -24,9 +24,9 @@
2424

2525
<div class="min-width-0 grow">
2626
{{- if .URL }}
27-
<a href="{{ .URL | safeURL }}" class="color-highlight size-title-dynamic block text-truncate" {{ if not .SameTab }}target="_blank"{{ end }} rel="noreferrer">{{ .Title }}</a>
27+
<a href="{{ .URL | safeURL }}" class="color-highlight size-title-dynamic block text-truncate" {{ if not .SameTab }}target="_blank"{{ end }} rel="noreferrer">{{ .Name }}</a>
2828
{{- else }}
29-
<div class="color-highlight text-truncate size-title-dynamic">{{ .Title }}</div>
29+
<div class="color-highlight text-truncate size-title-dynamic">{{ .Name }}</div>
3030
{{- end }}
3131
{{- if .Description }}
3232
<div class="text-truncate">{{ .Description }}</div>

internal/glance/widget-docker-containers.go

Lines changed: 97 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -16,12 +16,14 @@ import (
1616
var dockerContainersWidgetTemplate = mustParseTemplate("docker-containers.html", "widget-base.html")
1717

1818
type dockerContainersWidget struct {
19-
widgetBase `yaml:",inline"`
20-
HideByDefault bool `yaml:"hide-by-default"`
21-
RunningOnly bool `yaml:"running-only"`
22-
Category string `yaml:"category"`
23-
SockPath string `yaml:"sock-path"`
24-
Containers dockerContainerList `yaml:"-"`
19+
widgetBase `yaml:",inline"`
20+
HideByDefault bool `yaml:"hide-by-default"`
21+
RunningOnly bool `yaml:"running-only"`
22+
Category string `yaml:"category"`
23+
SockPath string `yaml:"sock-path"`
24+
FormatContainerNames bool `yaml:"format-container-names"`
25+
Containers dockerContainerList `yaml:"-"`
26+
LabelOverrides map[string]map[string]string `yaml:"containers"`
2527
}
2628

2729
func (widget *dockerContainersWidget) initialize() error {
@@ -35,7 +37,14 @@ func (widget *dockerContainersWidget) initialize() error {
3537
}
3638

3739
func (widget *dockerContainersWidget) update(ctx context.Context) {
38-
containers, err := fetchDockerContainers(widget.SockPath, widget.HideByDefault, widget.Category, widget.RunningOnly)
40+
containers, err := fetchDockerContainers(
41+
widget.SockPath,
42+
widget.HideByDefault,
43+
widget.Category,
44+
widget.RunningOnly,
45+
widget.FormatContainerNames,
46+
widget.LabelOverrides,
47+
)
3948
if !widget.canContinueUpdateAfterHandlingErr(err) {
4049
return
4150
}
@@ -102,7 +111,7 @@ func (l *dockerContainerLabels) getOrDefault(label, def string) string {
102111
}
103112

104113
type dockerContainer struct {
105-
Title string
114+
Name string
106115
URL string
107116
SameTab bool
108117
Image string
@@ -124,7 +133,7 @@ func (containers dockerContainerList) sortByStateIconThenTitle() {
124133
return (*p)[containers[a].StateIcon] < (*p)[containers[b].StateIcon]
125134
}
126135

127-
return strings.ToLower(containers[a].Title) < strings.ToLower(containers[b].Title)
136+
return strings.ToLower(containers[a].Name) < strings.ToLower(containers[b].Name)
128137
})
129138
}
130139

@@ -141,8 +150,15 @@ func dockerContainerStateToStateIcon(state string) string {
141150
}
142151
}
143152

144-
func fetchDockerContainers(socketPath string, hideByDefault bool, category string, runningOnly bool) (dockerContainerList, error) {
145-
containers, err := fetchDockerContainersFromSource(socketPath, category, runningOnly)
153+
func fetchDockerContainers(
154+
socketPath string,
155+
hideByDefault bool,
156+
category string,
157+
runningOnly bool,
158+
formatNames bool,
159+
labelOverrides map[string]map[string]string,
160+
) (dockerContainerList, error) {
161+
containers, err := fetchDockerContainersFromSource(socketPath, category, runningOnly, labelOverrides)
146162
if err != nil {
147163
return nil, fmt.Errorf("fetching containers: %w", err)
148164
}
@@ -154,7 +170,7 @@ func fetchDockerContainers(socketPath string, hideByDefault bool, category strin
154170
container := &containers[i]
155171

156172
dc := dockerContainer{
157-
Title: deriveDockerContainerTitle(container),
173+
Name: deriveDockerContainerName(container, formatNames),
158174
URL: container.Labels.getOrDefault(dockerContainerLabelURL, ""),
159175
Description: container.Labels.getOrDefault(dockerContainerLabelDescription, ""),
160176
SameTab: stringToBool(container.Labels.getOrDefault(dockerContainerLabelSameTab, "false")),
@@ -169,7 +185,7 @@ func fetchDockerContainers(socketPath string, hideByDefault bool, category strin
169185
for i := range children {
170186
child := &children[i]
171187
dc.Children = append(dc.Children, dockerContainer{
172-
Title: deriveDockerContainerTitle(child),
188+
Name: deriveDockerContainerName(child, formatNames),
173189
StateText: child.Status,
174190
StateIcon: dockerContainerStateToStateIcon(strings.ToLower(child.State)),
175191
})
@@ -197,12 +213,31 @@ func fetchDockerContainers(socketPath string, hideByDefault bool, category strin
197213
return dockerContainers, nil
198214
}
199215

200-
func deriveDockerContainerTitle(container *dockerContainerJsonResponse) string {
216+
func deriveDockerContainerName(container *dockerContainerJsonResponse, formatNames bool) string {
201217
if v := container.Labels.getOrDefault(dockerContainerLabelName, ""); v != "" {
202218
return v
203219
}
204220

205-
return strings.TrimLeft(itemAtIndexOrDefault(container.Names, 0, "n/a"), "/")
221+
if len(container.Names) == 0 || container.Names[0] == "" {
222+
return "n/a"
223+
}
224+
225+
name := strings.TrimLeft(container.Names[0], "/")
226+
227+
if formatNames {
228+
name = strings.ReplaceAll(name, "_", " ")
229+
name = strings.ReplaceAll(name, "-", " ")
230+
231+
words := strings.Split(name, " ")
232+
for i := range words {
233+
if len(words[i]) > 0 {
234+
words[i] = strings.ToUpper(words[i][:1]) + words[i][1:]
235+
}
236+
}
237+
name = strings.Join(words, " ")
238+
}
239+
240+
return name
206241
}
207242

208243
func groupDockerContainerChildren(
@@ -243,7 +278,13 @@ func isDockerContainerHidden(container *dockerContainerJsonResponse, hideByDefau
243278
return hideByDefault
244279
}
245280

246-
func fetchDockerContainersFromSource(source string, category string, runningOnly bool) ([]dockerContainerJsonResponse, error) {
281+
282+
func fetchDockerContainersFromSource(
283+
source string,
284+
category string,
285+
runningOnly bool,
286+
labelOverrides map[string]map[string]string,
287+
) ([]dockerContainerJsonResponse, error) {
247288
var hostname string
248289

249290
var client *http.Client
@@ -271,20 +312,12 @@ func fetchDockerContainersFromSource(source string, category string, runningOnly
271312
}
272313
}
273314

274-
query := url.Values{}
275-
query.Set("all", ternary(runningOnly, "false", "true"))
276-
277-
if category != "" {
278-
query.Set(
279-
"filters",
280-
fmt.Sprintf(`{"label": ["%s=%s"]}`, dockerContainerLabelCategory, category),
281-
)
282-
}
283315

316+
fetchAll := ternary(runningOnly, "false", "true")
284317
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
285318
defer cancel()
286319

287-
request, err := http.NewRequestWithContext(ctx, "GET", "http://"+hostname+"/containers/json?"+query.Encode(), nil)
320+
request, err := http.NewRequestWithContext(ctx, "GET", "http://"+hostname+"/containers/json?all="+fetchAll, nil)
288321
if err != nil {
289322
return nil, fmt.Errorf("creating request: %w", err)
290323
}
@@ -304,5 +337,43 @@ func fetchDockerContainersFromSource(source string, category string, runningOnly
304337
return nil, fmt.Errorf("decoding response: %w", err)
305338
}
306339

340+
for i := range containers {
341+
container := &containers[i]
342+
name := strings.TrimLeft(itemAtIndexOrDefault(container.Names, 0, ""), "/")
343+
344+
if name == "" {
345+
continue
346+
}
347+
348+
overrides, ok := labelOverrides[name]
349+
if !ok {
350+
continue
351+
}
352+
353+
if container.Labels == nil {
354+
container.Labels = make(dockerContainerLabels)
355+
}
356+
357+
for label, value := range overrides {
358+
container.Labels["glance."+label] = value
359+
}
360+
}
361+
362+
// We have to filter here instead of using the `filters` parameter of Docker's API
363+
// because the user may define a category override within their config
364+
if category != "" {
365+
filtered := make([]dockerContainerJsonResponse, 0, len(containers))
366+
367+
for i := range containers {
368+
container := &containers[i]
369+
370+
if container.Labels.getOrDefault(dockerContainerLabelCategory, "") == category {
371+
filtered = append(filtered, *container)
372+
}
373+
}
374+
375+
containers = filtered
376+
}
377+
307378
return containers, nil
308379
}

0 commit comments

Comments
 (0)