├── assets
└── screen.png
├── .gitignore
├── .dockerignore
├── Dockerfile
├── html.go
├── .github
└── workflows
│ ├── pr.yaml
│ └── docker.yaml
├── compose.sample.yaml
├── go.mod
├── main.go
├── docker.go
├── widget.gohtml
├── README.md
└── go.sum
/assets/screen.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/DVDAndroid/glance-docker-container-ext/HEAD/assets/screen.png
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | *.exe
2 | *.exe~
3 | *.dll
4 | *.so
5 | *.dylib
6 | *.test
7 | *.out
8 | go.work
9 |
10 | .idea
11 | *.iml
12 | glance-docker-container-ext*
13 |
--------------------------------------------------------------------------------
/.dockerignore:
--------------------------------------------------------------------------------
1 | *.exe
2 | *.exe~
3 | *.dll
4 | *.so
5 | *.dylib
6 | *.test
7 | *.out
8 | go.work
9 |
10 | .idea
11 | *.iml
12 | glance-docker-container-ext*
13 |
14 | .git
15 | .gitignore
16 | assets
17 | README.md
18 | compose.sample.yaml
19 |
--------------------------------------------------------------------------------
/Dockerfile:
--------------------------------------------------------------------------------
1 | FROM golang:1.22-alpine AS builder
2 |
3 | WORKDIR /build
4 | COPY . .
5 |
6 | RUN go mod download
7 | RUN CGO_ENABLED=0 go build --trimpath -o glance-docker-container-ext .
8 |
9 | FROM alpine:latest
10 |
11 | WORKDIR /app
12 |
13 | COPY --from=builder /build/glance-docker-container-ext .
14 | COPY widget.gohtml /app/widget.gohtml
15 |
16 | CMD ["./glance-docker-container-ext"]
17 |
18 |
--------------------------------------------------------------------------------
/html.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "html/template"
5 | "log/slog"
6 | "net/http"
7 | "os"
8 | )
9 |
10 | func BuildHtml(w http.ResponseWriter, containers []DockerContainer) {
11 | content, err := os.ReadFile("widget.gohtml")
12 | if err != nil {
13 | slog.Error("error reading widget template", "err", err)
14 | return
15 | }
16 |
17 | tmpl := template.Must(template.New("webpage").Parse(string(content)))
18 | tmpl.Execute(w, map[string]interface{}{
19 | "Containers": containers,
20 | })
21 | }
22 |
--------------------------------------------------------------------------------
/.github/workflows/pr.yaml:
--------------------------------------------------------------------------------
1 | name: Build
2 |
3 | on:
4 | push:
5 | paths:
6 | - '*go*'
7 | - 'Dockerfile'
8 | pull_request:
9 | paths:
10 | - '*go*'
11 | - 'Dockerfile'
12 |
13 | jobs:
14 | build:
15 | runs-on: ubuntu-latest
16 |
17 | steps:
18 | - name: Checkout repository
19 | uses: actions/checkout@v4
20 |
21 | - name: Set up Go
22 | uses: actions/setup-go@v4
23 | with:
24 | go-version: '1.22'
25 |
26 | - name: Build Docker image
27 | run: |
28 | docker build -f Dockerfile .
29 |
--------------------------------------------------------------------------------
/compose.sample.yaml:
--------------------------------------------------------------------------------
1 | services:
2 | glance:
3 | image: ghcr.io/dvdandroid/glance:2024-08-05-5da1768
4 | container_name: glance
5 | restart: unless-stopped
6 | volumes:
7 | - ./glance/glance.yml:/app/glance.yml
8 | - /etc/timezone:/etc/timezone:ro
9 | - /etc/localtime:/etc/localtime:ro
10 | networks:
11 | - ....
12 | - glance-network
13 |
14 | glance-docker-container-ext:
15 | image: ghcr.io/dvdandroid/glance-docker-container-ext
16 | container_name: glance-docker-container-ext
17 | restart: unless-stopped
18 | environment:
19 | - DOCKER_HOST=unix:///var/run/docker.sock
20 | volumes:
21 | - /var/run/docker.sock:/var/run/docker.sock
22 | networks:
23 | - glance-network
24 |
25 | networks:
26 | glance-network:
27 | name: glance-network
28 | driver_opts:
29 | com.docker.network.bridge.name: br-glance
30 |
--------------------------------------------------------------------------------
/go.mod:
--------------------------------------------------------------------------------
1 | module glance-docker-container-ext
2 |
3 | go 1.22
4 |
5 | require (
6 | github.com/fsouza/go-dockerclient v1.11.1
7 | github.com/gorilla/schema v1.4.1
8 | )
9 |
10 | require (
11 | github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1 // indirect
12 | github.com/Microsoft/go-winio v0.6.2 // indirect
13 | github.com/containerd/containerd v1.6.26 // indirect
14 | github.com/containerd/log v0.1.0 // indirect
15 | github.com/docker/docker v27.1.1+incompatible // indirect
16 | github.com/docker/go-connections v0.4.0 // indirect
17 | github.com/docker/go-units v0.5.0 // indirect
18 | github.com/gogo/protobuf v1.3.2 // indirect
19 | github.com/klauspost/compress v1.15.9 // indirect
20 | github.com/moby/docker-image-spec v1.3.1 // indirect
21 | github.com/moby/patternmatcher v0.6.0 // indirect
22 | github.com/moby/sys/sequential v0.5.0 // indirect
23 | github.com/moby/sys/user v0.1.0 // indirect
24 | github.com/moby/term v0.0.0-20210619224110-3f7ff695adc6 // indirect
25 | github.com/morikuni/aec v1.0.0 // indirect
26 | github.com/opencontainers/go-digest v1.0.0 // indirect
27 | github.com/opencontainers/image-spec v1.1.0-rc2.0.20221005185240-3a7f492d3f1b // indirect
28 | github.com/pkg/errors v0.9.1 // indirect
29 | github.com/sirupsen/logrus v1.9.3 // indirect
30 | golang.org/x/sys v0.22.0 // indirect
31 | )
32 |
--------------------------------------------------------------------------------
/main.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "fmt"
5 | docker "github.com/fsouza/go-dockerclient"
6 | "github.com/gorilla/schema"
7 | "log/slog"
8 | "net/http"
9 | "os"
10 | )
11 |
12 | type params struct {
13 | WidgetTitle string `schema:"title,default:Docker Containers"`
14 | Group string `schema:"group"`
15 | AllContainers bool `schema:"all,default:true"`
16 | Order string `schema:"order,default:name"`
17 | SameTab bool `schema:"same-tab,default:false"`
18 | IgnoreStatus bool `schema:"ignore-status,default:false"`
19 | }
20 |
21 | func main() {
22 | host := os.Getenv("HOST")
23 | port := os.Getenv("PORT")
24 | if port == "" {
25 | port = "8081"
26 | }
27 |
28 | dockerClient, err := docker.NewClientFromEnv()
29 | if err != nil {
30 | slog.Error("error creating docker client", "err", err)
31 | panic(err)
32 | }
33 |
34 | var decoder = schema.NewDecoder()
35 | decoder.IgnoreUnknownKeys(true)
36 |
37 | http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
38 | var p params
39 | err := decoder.Decode(&p, r.URL.Query())
40 | if err != nil {
41 | slog.Error("error decoding params", "err", err)
42 | w.WriteHeader(http.StatusInternalServerError)
43 | return
44 | }
45 |
46 | w.Header().Set("Widget-Title", p.WidgetTitle)
47 | w.Header().Set("Widget-Content-Type", "html")
48 | w.Header().Set("Content-Type", "text/html")
49 |
50 | containers, err := LoadContainers(dockerClient, p)
51 | if err != nil {
52 | slog.Error("cannot connect to docker engine", "err", err)
53 | w.WriteHeader(http.StatusInternalServerError)
54 | w.Write([]byte(`
58 | Cannot connect to Docker Engine
`))
59 | return
60 | }
61 |
62 | if len(containers) == 0 {
63 | w.Write([]byte(`No containers found
`))
64 | return
65 | }
66 |
67 | BuildHtml(w, containers)
68 | })
69 |
70 | slog.Info("starting webserver", "host", host, "port", port)
71 | err = http.ListenAndServe(fmt.Sprintf("%s:%s", host, port), nil)
72 | if err != nil {
73 | slog.Error("error starting webserver", "err", err)
74 | return
75 | }
76 | }
77 |
--------------------------------------------------------------------------------
/.github/workflows/docker.yaml:
--------------------------------------------------------------------------------
1 | name: Create and publish a Docker image
2 |
3 | on:
4 | release:
5 | types: [ published ]
6 |
7 | env:
8 | REGISTRY: ghcr.io
9 | IMAGE_NAME: ${{ github.repository }}
10 |
11 | jobs:
12 | build-and-push-image:
13 | runs-on: ubuntu-latest
14 | permissions:
15 | contents: read
16 | packages: write
17 | attestations: write
18 | id-token: write
19 |
20 | steps:
21 | - name: Checkout repository
22 | uses: actions/checkout@v4
23 |
24 | - name: Set up Go
25 | uses: actions/setup-go@v4
26 | with:
27 | go-version: '1.22'
28 |
29 | - name: Log in to the Container registry
30 | uses: docker/login-action@v3
31 | with:
32 | registry: ${{ env.REGISTRY }}
33 | username: ${{ github.actor }}
34 | password: ${{ secrets.GITHUB_TOKEN }}
35 |
36 | - name: Log in to the Container registry
37 | uses: docker/login-action@v3
38 | with:
39 | username: dvdandroid
40 | password: ${{ secrets.DOCKERHUB_TOKEN }}
41 |
42 | - name: Build Docker image
43 | run: |
44 | GITHUB_SHA=$(echo ${{ github.sha }} | cut -c1-7)
45 | VERSION=$(echo ${{ github.ref }} | sed 's/refs\/tags\///')
46 | IMAGE_TAG=${VERSION}-${GITHUB_SHA}
47 | docker build -t img -f Dockerfile .
48 | # make IMAGE_NAME lowercase
49 | IMAGE_NAME=$(echo ${{ env.IMAGE_NAME }} | tr '[:upper:]' '[:lower:]')
50 |
51 | if [[ ${{ github.event.release.prerelease }} == "false" ]]; then
52 | major=$(echo $VERSION | cut -d. -f1)
53 | minor=$(echo $VERSION | cut -d. -f2)
54 |
55 | tag_major="$major"
56 | tag_minor="$major.$minor"
57 |
58 | docker tag img ${{ env.REGISTRY }}/${IMAGE_NAME}:${tag_major}
59 | docker tag img ${{ env.REGISTRY }}/${IMAGE_NAME}:${tag_minor}
60 | docker tag img ${{ env.REGISTRY }}/${IMAGE_NAME}:latest
61 |
62 | docker tag img ${IMAGE_NAME}:${tag_major}
63 | docker tag img ${IMAGE_NAME}:${tag_minor}
64 | docker tag img ${IMAGE_NAME}:latest
65 | fi
66 |
67 | docker tag img ${{ env.REGISTRY }}/${IMAGE_NAME}:${VERSION}
68 | docker tag img ${{ env.REGISTRY }}/${IMAGE_NAME}:${IMAGE_TAG}
69 | docker tag img ${{ env.REGISTRY }}/${IMAGE_NAME}:${GITHUB_SHA}
70 |
71 | docker tag img ${IMAGE_NAME}:${VERSION}
72 | docker tag img ${IMAGE_NAME}:${IMAGE_TAG}
73 | docker tag img ${IMAGE_NAME}:${GITHUB_SHA}
74 |
75 | - name: Push Docker image
76 | run: |
77 | # make IMAGE_NAME lowercase
78 | IMAGE_NAME=$(echo ${{ env.IMAGE_NAME }} | tr '[:upper:]' '[:lower:]')
79 | docker push -a ${{ env.REGISTRY }}/${IMAGE_NAME}
80 | docker push -a ${IMAGE_NAME}
81 |
--------------------------------------------------------------------------------
/docker.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | docker "github.com/fsouza/go-dockerclient"
5 | "log/slog"
6 | "sort"
7 | "strconv"
8 | "strings"
9 | )
10 |
11 | type DockerContainer struct {
12 | Name string
13 | Description string
14 | State string // created|restarting|running|removing|paused|exited|dead
15 | Status string
16 | Icon string
17 | IsSvgIcon bool
18 | URL string
19 | SameTab bool
20 | }
21 |
22 | type GlanceLabel struct {
23 | Enable bool
24 | Name string
25 | Description string
26 | Url string
27 | Icon string
28 | Group string
29 | SameTab bool
30 | }
31 |
32 | func LoadContainers(dockerClient *docker.Client, p params) ([]DockerContainer, error) {
33 | containerList, err := dockerClient.ListContainers(docker.ListContainersOptions{
34 | All: p.AllContainers,
35 | })
36 |
37 | if err != nil {
38 | return nil, err
39 | }
40 |
41 | var containers []DockerContainer
42 | for _, container := range containerList {
43 | glanceLabels := make(map[int]GlanceLabel)
44 |
45 | for label, value := range container.Labels {
46 | if !strings.HasPrefix(label, "glance.") {
47 | continue
48 | }
49 |
50 | parts := strings.Split(label, ".")
51 | if len(parts) != 3 {
52 | if len(parts) == 2 {
53 | slog.Warn("found deprecated label. use new format instead glance.."+parts[1], "label", label, "container", container.Names[0][1:])
54 | }
55 | continue
56 | }
57 |
58 | index, err := strconv.Atoi(parts[1])
59 | if err != nil {
60 | continue
61 | }
62 |
63 | gl, exists := glanceLabels[index]
64 | if !exists {
65 | glanceLabels[index] = GlanceLabel{}
66 | gl = glanceLabels[index]
67 | }
68 | switch parts[2] {
69 | case "enable":
70 | gl.Enable = value == "true"
71 | case "name":
72 | gl.Name = value
73 | case "description":
74 | gl.Description = value
75 | case "group":
76 | gl.Group = value
77 | case "icon":
78 | gl.Icon = value
79 | if strings.HasPrefix(value, "si:") {
80 | gl.Icon = strings.TrimPrefix(value, "si:")
81 | gl.Icon = "https://cdnjs.cloudflare.com/ajax/libs/simple-icons/11.14.0/" + gl.Icon + ".svg"
82 | }
83 | case "url":
84 | gl.Url = value
85 | case "same-tab":
86 | gl.SameTab = value == "true"
87 | }
88 | glanceLabels[index] = gl
89 | }
90 |
91 | for _, gl := range glanceLabels {
92 | if !gl.Enable {
93 | continue
94 | }
95 | if gl.Group != p.Group {
96 | continue
97 | }
98 |
99 | state := container.State
100 | if p.IgnoreStatus {
101 | state = ""
102 | }
103 |
104 | if gl.Name == "" {
105 | gl.Name = container.Names[0][1:]
106 | }
107 |
108 | containers = append(containers, DockerContainer{
109 | Name: gl.Name,
110 | Status: container.Status,
111 | State: state,
112 | Description: gl.Description,
113 | Icon: gl.Icon,
114 | IsSvgIcon: strings.Contains(gl.Icon, "/simple-icons/") || strings.HasSuffix(gl.Icon, ".svg"),
115 | URL: gl.Url,
116 | SameTab: p.SameTab || gl.SameTab,
117 | })
118 | }
119 | }
120 |
121 | sortContainers(containers, strings.Split(p.Order, ","))
122 |
123 | return containers, nil
124 | }
125 |
126 | func sortContainers(containers []DockerContainer, order []string) {
127 | sort.Slice(containers, func(i, j int) bool {
128 | for _, field := range order {
129 | switch field {
130 | case "name":
131 | name1 := strings.ToLower(containers[i].Name)
132 | name2 := strings.ToLower(containers[j].Name)
133 | if name1 != name2 {
134 | return name1 < name2
135 | }
136 | description1 := strings.ToLower(containers[i].Description)
137 | description2 := strings.ToLower(containers[j].Description)
138 | if description1 != description2 {
139 | return description1 < description2
140 | }
141 | case "status":
142 | if containers[i].State != containers[j].State {
143 | return containers[i].State < containers[j].State
144 | }
145 | }
146 | }
147 | return false
148 | })
149 | }
150 |
--------------------------------------------------------------------------------
/widget.gohtml:
--------------------------------------------------------------------------------
1 |
8 |
9 | {{ define "container" }}
10 | {{- /*gotype: glance-docker-container-ext.DockerContainer*/ -}}
11 | {{ if .Icon }}
12 | {{ if .URL }}
13 |
14 | {{ end }}
15 |
16 | {{ if .URL }}
17 |
18 | {{ end }}
19 | {{ end }}
20 |
21 | {{ if .URL }}
22 |
{{ .Name }}
24 | {{ else }}
25 |
{{ .Name }}
26 | {{ end }}
27 | {{ if .Description}}
28 |
{{ .Description }}
29 | {{ end }}
30 |
31 | {{ if ne .State "" }}
32 |
33 | {{ if eq .State "created" }}
34 |
38 | {{ end }}
39 | {{ if eq .State "running" }}
40 |
45 | {{ end }}
46 | {{ if eq .State "restarting" }}
47 |
52 | {{ end }}
53 | {{ if eq .State "paused" }}
54 |
59 | {{ end }}
60 | {{ if eq .State "removing" }}
61 |
66 | {{ end }}
67 | {{ if eq .State "exited" }}
68 |
74 | {{ end }}
75 | {{ if eq .State "dead" }}
76 |
81 | {{ end }}
82 |
83 | {{ end }}
84 | {{ end }}
85 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | glance-docker-container-ext
2 | ===
3 |
4 | 
5 | 
6 |
7 | [Glance](https://github.com/glanceapp/glance) extension that creates a widget that displays the running Docker containers.
8 |
9 | 
10 |
11 | ## Installation
12 |
13 | Assuming you are using Docker compose, add the following to your `docker-compose.yml` file containing Glance container:
14 |
15 | ```yaml
16 | glance-docker-container-ext:
17 | image: dvdandroid/glance-docker-container-ext
18 | container_name: glance-docker-container-ext
19 | restart: unless-stopped
20 | environment:
21 | - DOCKER_HOST=unix:///var/run/docker.sock
22 | - PORT=8081 # Optional, default is 8081
23 | volumes:
24 | - /var/run/docker.sock:/var/run/docker.sock
25 | ```
26 |
27 | then in your `glance.yml` config file, add the following:
28 |
29 | ```yaml
30 | - type: extension
31 | allow-potentially-dangerous-html: true
32 | url: http://glance-docker-container-ext:8081
33 | cache: 5m
34 | parameters:
35 | title: Docker Containers
36 | all: true
37 | order: name,status
38 | ```
39 |
40 | ### Parameters
41 |
42 | | Parameter | Description | Default |
43 | |-----------------|--------------------------------------------------------------------------------------------------------------------------|---------------------|
44 | | `title` | Title of the widget | "Docker Containers" |
45 | | `all` | Show all containers or only running ones | `true` |
46 | | `order` | Order of the containers, comma separated **string** of `name`, `status`
(`name`,`status`,`name,status`,`status,name`) | `name` |
47 | | `group` | Identifier for the group of containers. If set, only containers with the same group will be displayed. | |
48 | | `same-tab` | Open the URL in the same tab. Value customizable per container | `false` |
49 | | `ignore-status` | Status of the containers will not be displayed | `false` |
50 |
51 | ## Configuration
52 |
53 | Then, for every container you want to monitor, add the following labels to its compose file:
54 |
55 | ```yaml
56 | labels:
57 | glance.0.enable: true
58 | glance.0.name: Sonarr
59 | glance.0.description: TV show search
60 | glance.0.group: media
61 | glance.0.url: http://sonarr.lan
62 | glance.0.icon: ./assets/imgs/television-classic.svg
63 | ```
64 |
65 | :warning: Multiple labels can be added to the same container. Read below
66 |
67 | | Label | Description | Default |
68 | |------------------------|------------------------------------------------------------------------------------------------------------|----------------|
69 | | `glance.X.enable` | Enable monitoring for this container | |
70 | | `glance.X.name` | Name of the container | container name |
71 | | `glance.X.description` | Description of the container | |
72 | | `glance.X.group` | Identifier for the group of containers, used in combination with parameter `group` in glance configuration | |
73 | | `glance.X.url` | URL to open when clicking on the container | |
74 | | `glance.X.icon` | Icon to display, pointing to assets or Simple Icon (`si:` prefix) | |
75 | | `glance.X.same-tab` | Open the URL in the same tab | `false` |
76 |
77 | Value of `X` must be replaced with a number starting from 0: this allows to add multiple widget referring to the same container.
78 |
79 | For example, if you want to define two widgets for the same container, but with different labels, you can do it like this (in the compose file):
80 |
81 | ```yaml
82 | labels:
83 | glance.0.enable: true
84 | glance.0.name: Container (ADMIN)
85 | glance.0.url: http://website.lan/admin
86 | glance.1.enable: true
87 | glance.1.group: User
88 | glance.1.name: Container (USER)
89 | glance.1.description: User access
90 | glance.1.url: http://website.lan/user
91 | ```
92 |
--------------------------------------------------------------------------------
/go.sum:
--------------------------------------------------------------------------------
1 | github.com/AdaLogics/go-fuzz-headers v0.0.0-20210715213245-6c3934b029d8 h1:V8krnnfGj4pV65YLUm3C0/8bl7V5Nry2Pwvy3ru/wLc=
2 | github.com/AdaLogics/go-fuzz-headers v0.0.0-20210715213245-6c3934b029d8/go.mod h1:CzsSbkDixRphAF5hS6wbMKq0eI6ccJRb7/A0M6JBnwg=
3 | github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1 h1:UQHMgLO+TxOElx5B5HZ4hJQsoJ/PvUvKRhJHDQXO8P8=
4 | github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1/go.mod h1:xomTg63KZ2rFqZQzSB4Vz2SUXa1BpHTVz9L5PTmPC4E=
5 | github.com/Microsoft/go-winio v0.6.2 h1:F2VQgta7ecxGYO8k3ZZz3RS8fVIXVxONVUPlNERoyfY=
6 | github.com/Microsoft/go-winio v0.6.2/go.mod h1:yd8OoFMLzJbo9gZq8j5qaps8bJ9aShtEA8Ipt1oGCvU=
7 | github.com/containerd/containerd v1.6.26 h1:VVfrE6ZpyisvB1fzoY8Vkiq4sy+i5oF4uk7zu03RaHs=
8 | github.com/containerd/containerd v1.6.26/go.mod h1:I4TRdsdoo5MlKob5khDJS2EPT1l1oMNaE2MBm6FrwxM=
9 | github.com/containerd/log v0.1.0 h1:TCJt7ioM2cr/tfR8GPbGf9/VRAX8D2B4PjzCpfX540I=
10 | github.com/containerd/log v0.1.0/go.mod h1:VRRf09a7mHDIRezVKTRCrOq78v577GXq3bSa3EhrzVo=
11 | github.com/creack/pty v1.1.11 h1:07n33Z8lZxZ2qwegKbObQohDhXDQxiMMz1NOUGYlesw=
12 | github.com/creack/pty v1.1.11/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
13 | github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
14 | github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
15 | github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
16 | github.com/docker/docker v27.1.1+incompatible h1:hO/M4MtV36kzKldqnA37IWhebRA+LnqqcqDja6kVaKY=
17 | github.com/docker/docker v27.1.1+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk=
18 | github.com/docker/go-connections v0.4.0 h1:El9xVISelRB7BuFusrZozjnkIM5YnzCViNKohAFqRJQ=
19 | github.com/docker/go-connections v0.4.0/go.mod h1:Gbd7IOopHjR8Iph03tsViu4nIes5XhDvyHbTtUxmeec=
20 | github.com/docker/go-units v0.5.0 h1:69rxXcBk27SvSaaxTtLh/8llcHD8vYHT7WSdRZ/jvr4=
21 | github.com/docker/go-units v0.5.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk=
22 | github.com/fsouza/go-dockerclient v1.11.1 h1:i5Vk9riDxW2uP9pVS5FYkpquMTFT5lsx2pt7oErRTjI=
23 | github.com/fsouza/go-dockerclient v1.11.1/go.mod h1:UfjOOaspAq+RGh7GX1aZ0HeWWGHQWWsh+H5BgEWB3Pk=
24 | github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q=
25 | github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q=
26 | github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
27 | github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
28 | github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
29 | github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
30 | github.com/gorilla/schema v1.4.1 h1:jUg5hUjCSDZpNGLuXQOgIWGdlgrIdYvgQ0wZtdK1M3E=
31 | github.com/gorilla/schema v1.4.1/go.mod h1:Dg5SSm5PV60mhF2NFaTV1xuYYj8tV8NOPRo4FggUMnM=
32 | github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8=
33 | github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
34 | github.com/klauspost/compress v1.15.9 h1:wKRjX6JRtDdrE9qwa4b/Cip7ACOshUI4smpCQanqjSY=
35 | github.com/klauspost/compress v1.15.9/go.mod h1:PhcZ0MbTNciWF3rruxRgKxI5NkcHHrHUDtV4Yw2GlzU=
36 | github.com/moby/docker-image-spec v1.3.1 h1:jMKff3w6PgbfSa69GfNg+zN/XLhfXJGnEx3Nl2EsFP0=
37 | github.com/moby/docker-image-spec v1.3.1/go.mod h1:eKmb5VW8vQEh/BAr2yvVNvuiJuY6UIocYsFu/DxxRpo=
38 | github.com/moby/patternmatcher v0.6.0 h1:GmP9lR19aU5GqSSFko+5pRqHi+Ohk1O69aFiKkVGiPk=
39 | github.com/moby/patternmatcher v0.6.0/go.mod h1:hDPoyOpDY7OrrMDLaYoY3hf52gNCR/YOUYxkhApJIxc=
40 | github.com/moby/sys/sequential v0.5.0 h1:OPvI35Lzn9K04PBbCLW0g4LcFAJgHsvXsRyewg5lXtc=
41 | github.com/moby/sys/sequential v0.5.0/go.mod h1:tH2cOOs5V9MlPiXcQzRC+eEyab644PWKGRYaaV5ZZlo=
42 | github.com/moby/sys/user v0.1.0 h1:WmZ93f5Ux6het5iituh9x2zAG7NFY9Aqi49jjE1PaQg=
43 | github.com/moby/sys/user v0.1.0/go.mod h1:fKJhFOnsCN6xZ5gSfbM6zaHGgDJMrqt9/reuj4T7MmU=
44 | github.com/moby/term v0.0.0-20210619224110-3f7ff695adc6 h1:dcztxKSvZ4Id8iPpHERQBbIJfabdt4wUm5qy3wOL2Zc=
45 | github.com/moby/term v0.0.0-20210619224110-3f7ff695adc6/go.mod h1:E2VnQOmVuvZB6UYnnDB0qG5Nq/1tD9acaOpo6xmt0Kw=
46 | github.com/morikuni/aec v1.0.0 h1:nP9CBfwrvYnBRgY6qfDQkygYDmYwOilePFkwzv4dU8A=
47 | github.com/morikuni/aec v1.0.0/go.mod h1:BbKIizmSmc5MMPqRYbxO4ZU0S0+P200+tUnFx7PXmsc=
48 | github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8Oi/yOhh5U=
49 | github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM=
50 | github.com/opencontainers/image-spec v1.1.0-rc2.0.20221005185240-3a7f492d3f1b h1:YWuSjZCQAPM8UUBLkYUk1e+rZcvWHJmFb6i6rM44Xs8=
51 | github.com/opencontainers/image-spec v1.1.0-rc2.0.20221005185240-3a7f492d3f1b/go.mod h1:3OVijpioIKYWTqjiG0zfF6wvoJ4fAXGbjdZuI2NgsRQ=
52 | github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
53 | github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
54 | github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
55 | github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
56 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
57 | github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ=
58 | github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ=
59 | github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4=
60 | github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
61 | github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
62 | github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk=
63 | github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
64 | github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
65 | github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
66 | golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
67 | golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
68 | golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
69 | golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
70 | golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
71 | golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
72 | golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
73 | golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
74 | golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
75 | golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
76 | golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
77 | golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
78 | golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
79 | golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
80 | golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
81 | golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
82 | golang.org/x/sys v0.0.0-20210616094352-59db8d763f22/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
83 | golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
84 | golang.org/x/sys v0.22.0 h1:RI27ohtqKCnwULzJLqkv897zojh5/DwS/ENaMzUOaWI=
85 | golang.org/x/sys v0.22.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
86 | golang.org/x/term v0.22.0 h1:BbsgPEJULsl2fV/AT3v15Mjva5yXKQDyKf+TbDz7QJk=
87 | golang.org/x/term v0.22.0/go.mod h1:F3qCibpT5AMpCRfhfT53vVJwhLtIVHhB9XDjfFvnMI4=
88 | golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
89 | golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
90 | golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
91 | golang.org/x/tools v0.0.0-20190624222133-a101b041ded4/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
92 | golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
93 | golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
94 | golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
95 | golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
96 | golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
97 | golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
98 | golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
99 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
100 | gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
101 | gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
102 | gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
103 | gotest.tools/v3 v3.0.2/go.mod h1:3SzNCllyD9/Y+b5r9JIKQ474KzkZyqLqEfYqMsX94Bk=
104 | gotest.tools/v3 v3.5.0 h1:Ljk6PdHdOhAb5aDMWXjDLMMhph+BpztA4v1QdqEW2eY=
105 | gotest.tools/v3 v3.5.0/go.mod h1:isy3WKz7GK6uNw/sbHzfKBLvlvXwUyV06n6brMxxopU=
106 |
--------------------------------------------------------------------------------