1
0
mirror of https://github.com/amir20/dozzle.git synced 2025-12-21 13:23:07 +01:00

feat: supports k8s cluster (#3599)

This commit is contained in:
Amir Raminfar
2025-02-10 09:29:39 -08:00
committed by GitHub
parent 209ce312d4
commit 13da2a4222
50 changed files with 1591 additions and 734 deletions

View File

@@ -12,6 +12,7 @@ declare module 'vue' {
'Carbon:circleSolid': typeof import('~icons/carbon/circle-solid')['default'] 'Carbon:circleSolid': typeof import('~icons/carbon/circle-solid')['default']
'Carbon:copyFile': typeof import('~icons/carbon/copy-file')['default'] 'Carbon:copyFile': typeof import('~icons/carbon/copy-file')['default']
'Carbon:information': typeof import('~icons/carbon/information')['default'] 'Carbon:information': typeof import('~icons/carbon/information')['default']
'Carbon:logoKubernetes': typeof import('~icons/carbon/logo-kubernetes')['default']
'Carbon:macShift': typeof import('~icons/carbon/mac-shift')['default'] 'Carbon:macShift': typeof import('~icons/carbon/mac-shift')['default']
'Carbon:play': typeof import('~icons/carbon/play')['default'] 'Carbon:play': typeof import('~icons/carbon/play')['default']
'Carbon:restart': typeof import('~icons/carbon/restart')['default'] 'Carbon:restart': typeof import('~icons/carbon/restart')['default']

View File

@@ -2,6 +2,7 @@
<mdi:satellite-variant v-if="type == 'agent'" /> <mdi:satellite-variant v-if="type == 'agent'" />
<ph:globe-simple v-else-if="type == 'remote'" /> <ph:globe-simple v-else-if="type == 'remote'" />
<mdi:hexagon-multiple v-else-if="type == 'swarm'" /> <mdi:hexagon-multiple v-else-if="type == 'swarm'" />
<carbon:logo-kubernetes v-else-if="type == 'k8s'" />
<ph:computer-tower v-else /> <ph:computer-tower v-else />
</template> </template>
<script setup lang="ts"> <script setup lang="ts">

View File

@@ -3,7 +3,7 @@ export type Host = {
name: string; name: string;
nCPU: number; nCPU: number;
memTotal: number; memTotal: number;
type: "agent" | "local" | "remote" | "swarm"; type: "agent" | "local" | "remote" | "swarm" | "k8s";
endpoint: string; endpoint: string;
available: boolean; available: boolean;
dockerVersion: string; dockerVersion: string;

18
examples/ingress.yml Normal file
View File

@@ -0,0 +1,18 @@
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: dozzle-ingress
annotations:
ingress.kubernetes.io/ssl-redirect: "false"
spec:
rules:
- host: dozzle.k3d.local
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: dozzle-service
port:
number: 8080

71
examples/k8s.dozzle.yml Normal file
View File

@@ -0,0 +1,71 @@
# rbac.yaml
apiVersion: v1
kind: ServiceAccount
metadata:
name: pod-viewer
---
# clusterrole.yaml
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
name: pod-viewer-role
rules:
- apiGroups: [""]
resources: ["pods", "pods/log", "nodes"]
verbs: ["get", "list", "watch"]
---
# clusterrolebinding.yaml
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
name: pod-viewer-binding
subjects:
- kind: ServiceAccount
name: pod-viewer
namespace: default
roleRef:
kind: ClusterRole
name: pod-viewer-role
apiGroup: rbac.authorization.k8s.io
---
# deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: dozzle
spec:
replicas: 1
selector:
matchLabels:
app: dozzle
template:
metadata:
labels:
app: dozzle
spec:
serviceAccountName: pod-viewer
containers:
- name: dozzle
image: amir20/dozzle:latest
imagePullPolicy: Never
ports:
- containerPort: 8080
env:
- name: DOZZLE_MODE
value: "k8s"
- name: DOZZLE_LEVEL
value: "debug"
---
# service.yaml
apiVersion: v1
kind: Service
metadata:
name: dozzle-service
spec:
type: ClusterIP
selector:
app: dozzle
ports:
- port: 8080
targetPort: 8080
protocol: TCP

38
go.mod
View File

@@ -36,6 +36,10 @@ require (
golang.org/x/sync v0.11.0 golang.org/x/sync v0.11.0
google.golang.org/grpc v1.70.0 google.golang.org/grpc v1.70.0
google.golang.org/protobuf v1.36.5 google.golang.org/protobuf v1.36.5
k8s.io/api v0.32.1
k8s.io/apimachinery v0.32.1
k8s.io/client-go v0.32.1
k8s.io/metrics v0.32.1
) )
require ( require (
@@ -45,14 +49,25 @@ require (
github.com/buger/jsonparser v1.1.1 // indirect github.com/buger/jsonparser v1.1.1 // indirect
github.com/cenkalti/backoff/v4 v4.3.0 // indirect github.com/cenkalti/backoff/v4 v4.3.0 // indirect
github.com/containerd/log v0.1.0 // indirect github.com/containerd/log v0.1.0 // indirect
github.com/davecgh/go-spew v1.1.1 // indirect github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect
github.com/decred/dcrd/dcrec/secp256k1/v4 v4.3.0 // indirect github.com/decred/dcrd/dcrec/secp256k1/v4 v4.3.0 // indirect
github.com/distribution/reference v0.6.0 // indirect github.com/distribution/reference v0.6.0 // indirect
github.com/emicklei/go-restful/v3 v3.11.0 // indirect
github.com/felixge/httpsnoop v1.0.4 // indirect github.com/felixge/httpsnoop v1.0.4 // indirect
github.com/fxamacker/cbor/v2 v2.7.0 // indirect
github.com/go-logr/logr v1.4.2 // indirect github.com/go-logr/logr v1.4.2 // indirect
github.com/go-logr/stdr v1.2.2 // indirect github.com/go-logr/stdr v1.2.2 // indirect
github.com/go-openapi/jsonpointer v0.21.0 // indirect
github.com/go-openapi/jsonreference v0.20.2 // indirect
github.com/go-openapi/swag v0.23.0 // indirect
github.com/goccy/go-json v0.10.3 // indirect github.com/goccy/go-json v0.10.3 // indirect
github.com/kr/pretty v0.2.1 // indirect github.com/golang/protobuf v1.5.4 // indirect
github.com/google/gnostic-models v0.6.8 // indirect
github.com/google/go-cmp v0.6.0 // indirect
github.com/google/gofuzz v1.2.0 // indirect
github.com/google/uuid v1.6.0 // indirect
github.com/josharian/intern v1.0.0 // indirect
github.com/json-iterator/go v1.1.12 // indirect
github.com/lestrrat-go/blackmagic v1.0.2 // indirect github.com/lestrrat-go/blackmagic v1.0.2 // indirect
github.com/lestrrat-go/httpcc v1.0.1 // indirect github.com/lestrrat-go/httpcc v1.0.1 // indirect
github.com/lestrrat-go/httprc v1.0.6 // indirect github.com/lestrrat-go/httprc v1.0.6 // indirect
@@ -63,18 +78,33 @@ require (
github.com/mattn/go-colorable v0.1.13 // indirect github.com/mattn/go-colorable v0.1.13 // indirect
github.com/mattn/go-isatty v0.0.20 // indirect github.com/mattn/go-isatty v0.0.20 // indirect
github.com/moby/docker-image-spec v1.3.1 // indirect github.com/moby/docker-image-spec v1.3.1 // indirect
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
github.com/modern-go/reflect2 v1.0.2 // indirect
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect
github.com/pkg/errors v0.9.1 // indirect github.com/pkg/errors v0.9.1 // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect
github.com/segmentio/asm v1.2.0 // indirect github.com/segmentio/asm v1.2.0 // indirect
github.com/spf13/pflag v1.0.5 // indirect
github.com/x448/float16 v0.8.4 // indirect
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.57.0 // indirect go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.57.0 // indirect
go.opentelemetry.io/otel v1.32.0 // indirect go.opentelemetry.io/otel v1.32.0 // indirect
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.22.0 // indirect go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.22.0 // indirect
go.opentelemetry.io/otel/metric v1.32.0 // indirect go.opentelemetry.io/otel/metric v1.32.0 // indirect
go.opentelemetry.io/otel/sdk v1.32.0 // indirect
go.opentelemetry.io/otel/trace v1.32.0 // indirect go.opentelemetry.io/otel/trace v1.32.0 // indirect
golang.org/x/oauth2 v0.25.0 // indirect
golang.org/x/term v0.29.0 // indirect
golang.org/x/text v0.22.0 // indirect golang.org/x/text v0.22.0 // indirect
golang.org/x/time v0.8.0 // indirect
google.golang.org/genproto/googleapis/rpc v0.0.0-20241223144023-3abc09e42ca8 // indirect google.golang.org/genproto/googleapis/rpc v0.0.0-20241223144023-3abc09e42ca8 // indirect
gopkg.in/evanphx/json-patch.v4 v4.12.0 // indirect
gopkg.in/inf.v0 v0.9.1 // indirect
gotest.tools/v3 v3.0.3 // indirect gotest.tools/v3 v3.0.3 // indirect
k8s.io/klog/v2 v2.130.1 // indirect
k8s.io/kube-openapi v0.0.0-20241105132330-32ad38e42d3f // indirect
k8s.io/utils v0.0.0-20241104100929-3ea5e8cea738 // indirect
sigs.k8s.io/json v0.0.0-20241010143419-9aa6b5e7a4b3 // indirect
sigs.k8s.io/structured-merge-diff/v4 v4.4.2 // indirect
sigs.k8s.io/yaml v1.4.0 // indirect
) )
go 1.23.6 go 1.23.6

189
go.sum
View File

@@ -2,16 +2,12 @@ github.com/Azure/go-ansiterm v0.0.0-20170929234023-d6e3b3328b78 h1:w+iIsaOQNcT7O
github.com/Azure/go-ansiterm v0.0.0-20170929234023-d6e3b3328b78/go.mod h1:LmzpDX56iTiv29bbRTIsUNlaFfuhWRQBWjQdVyAevI8= github.com/Azure/go-ansiterm v0.0.0-20170929234023-d6e3b3328b78/go.mod h1:LmzpDX56iTiv29bbRTIsUNlaFfuhWRQBWjQdVyAevI8=
github.com/Microsoft/go-winio v0.6.2 h1:F2VQgta7ecxGYO8k3ZZz3RS8fVIXVxONVUPlNERoyfY= github.com/Microsoft/go-winio v0.6.2 h1:F2VQgta7ecxGYO8k3ZZz3RS8fVIXVxONVUPlNERoyfY=
github.com/Microsoft/go-winio v0.6.2/go.mod h1:yd8OoFMLzJbo9gZq8j5qaps8bJ9aShtEA8Ipt1oGCvU= github.com/Microsoft/go-winio v0.6.2/go.mod h1:yd8OoFMLzJbo9gZq8j5qaps8bJ9aShtEA8Ipt1oGCvU=
github.com/PuerkitoBio/goquery v1.10.0 h1:6fiXdLuUvYs2OJSvNRqlNPoBm6YABE226xrbavY5Wv4=
github.com/PuerkitoBio/goquery v1.10.0/go.mod h1:TjZZl68Q3eGHNBA8CWaxAN7rOU1EbDz3CWuolcO5Yu4=
github.com/PuerkitoBio/goquery v1.10.1 h1:Y8JGYUkXWTGRB6Ars3+j3kN0xg1YqqlwvdTV8WTFQcU= github.com/PuerkitoBio/goquery v1.10.1 h1:Y8JGYUkXWTGRB6Ars3+j3kN0xg1YqqlwvdTV8WTFQcU=
github.com/PuerkitoBio/goquery v1.10.1/go.mod h1:IYiHrOMps66ag56LEH7QYDDupKXyo5A8qrjIx3ZtujY= github.com/PuerkitoBio/goquery v1.10.1/go.mod h1:IYiHrOMps66ag56LEH7QYDDupKXyo5A8qrjIx3ZtujY=
github.com/alexflint/go-arg v1.5.1 h1:nBuWUCpuRy0snAG+uIJ6N0UvYxpxA0/ghA/AaHxlT8Y= github.com/alexflint/go-arg v1.5.1 h1:nBuWUCpuRy0snAG+uIJ6N0UvYxpxA0/ghA/AaHxlT8Y=
github.com/alexflint/go-arg v1.5.1/go.mod h1:A7vTJzvjoaSTypg4biM5uYNTkJ27SkNTArtYXnlqVO8= github.com/alexflint/go-arg v1.5.1/go.mod h1:A7vTJzvjoaSTypg4biM5uYNTkJ27SkNTArtYXnlqVO8=
github.com/alexflint/go-scalar v1.2.0 h1:WR7JPKkeNpnYIOfHRa7ivM21aWAdHD0gEWHCx+WQBRw= github.com/alexflint/go-scalar v1.2.0 h1:WR7JPKkeNpnYIOfHRa7ivM21aWAdHD0gEWHCx+WQBRw=
github.com/alexflint/go-scalar v1.2.0/go.mod h1:LoFvNMqS1CPrMVltza4LvnGKhaSpc3oyLEBUZVhhS2o= github.com/alexflint/go-scalar v1.2.0/go.mod h1:LoFvNMqS1CPrMVltza4LvnGKhaSpc3oyLEBUZVhhS2o=
github.com/andybalholm/cascadia v1.3.2 h1:3Xi6Dw5lHF15JtdcmAHD3i1+T8plmv7BQ/nsViSLyss=
github.com/andybalholm/cascadia v1.3.2/go.mod h1:7gtRlve5FxPPgIgX36uWBX58OdBsSS6lUvCFb+h7KvU=
github.com/andybalholm/cascadia v1.3.3 h1:AG2YHrzJIm4BZ19iwJ/DAua6Btl3IwJX+VI4kktS1LM= github.com/andybalholm/cascadia v1.3.3 h1:AG2YHrzJIm4BZ19iwJ/DAua6Btl3IwJX+VI4kktS1LM=
github.com/andybalholm/cascadia v1.3.3/go.mod h1:xNd9bqTn98Ln4DwST8/nG+H0yuB8Hmgu1YHNnWw0GeA= github.com/andybalholm/cascadia v1.3.3/go.mod h1:xNd9bqTn98Ln4DwST8/nG+H0yuB8Hmgu1YHNnWw0GeA=
github.com/bahlo/generic-list-go v0.2.0 h1:5sz/EEAK+ls5wF+NeqDpk5+iNdMDXrh3z3nPnH1Wvgk= github.com/bahlo/generic-list-go v0.2.0 h1:5sz/EEAK+ls5wF+NeqDpk5+iNdMDXrh3z3nPnH1Wvgk=
@@ -25,22 +21,16 @@ github.com/cenkalti/backoff/v4 v4.3.0/go.mod h1:Y3VNntkOUPxTVeUxJ/G5vcM//AlwfmyY
github.com/containerd/log v0.1.0 h1:TCJt7ioM2cr/tfR8GPbGf9/VRAX8D2B4PjzCpfX540I= github.com/containerd/log v0.1.0 h1:TCJt7ioM2cr/tfR8GPbGf9/VRAX8D2B4PjzCpfX540I=
github.com/containerd/log v0.1.0/go.mod h1:VRRf09a7mHDIRezVKTRCrOq78v577GXq3bSa3EhrzVo= github.com/containerd/log v0.1.0/go.mod h1:VRRf09a7mHDIRezVKTRCrOq78v577GXq3bSa3EhrzVo=
github.com/coreos/go-systemd/v22 v22.5.0/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc= github.com/coreos/go-systemd/v22 v22.5.0/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc=
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
github.com/creack/pty v1.1.11/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= github.com/creack/pty v1.1.11/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM=
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/decred/dcrd/dcrec/secp256k1/v4 v4.3.0 h1:rpfIENRNNilwHwZeG5+P150SMrnNEcHYvcCuK6dPZSg= github.com/decred/dcrd/dcrec/secp256k1/v4 v4.3.0 h1:rpfIENRNNilwHwZeG5+P150SMrnNEcHYvcCuK6dPZSg=
github.com/decred/dcrd/dcrec/secp256k1/v4 v4.3.0/go.mod h1:v57UDF4pDQJcEfFUCRop3lJL149eHGSe9Jvczhzjo/0= github.com/decred/dcrd/dcrec/secp256k1/v4 v4.3.0/go.mod h1:v57UDF4pDQJcEfFUCRop3lJL149eHGSe9Jvczhzjo/0=
github.com/distribution/reference v0.6.0 h1:0IXCQ5g4/QMHHkarYzh5l+u8T3t73zM5QvfrDyIgxBk= github.com/distribution/reference v0.6.0 h1:0IXCQ5g4/QMHHkarYzh5l+u8T3t73zM5QvfrDyIgxBk=
github.com/distribution/reference v0.6.0/go.mod h1:BbU0aIcezP1/5jX/8MP0YiH4SdvB5Y4f/wlDRiLyi3E= github.com/distribution/reference v0.6.0/go.mod h1:BbU0aIcezP1/5jX/8MP0YiH4SdvB5Y4f/wlDRiLyi3E=
github.com/docker/docker v27.3.1+incompatible h1:KttF0XoteNTicmUtBO0L2tP+J7FGRFTjaEF4k6WdhfI=
github.com/docker/docker v27.3.1+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk=
github.com/docker/docker v27.4.0+incompatible h1:I9z7sQ5qyzO0BfAb9IMOawRkAGxhYsidKiTMcm0DU+A=
github.com/docker/docker v27.4.0+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk=
github.com/docker/docker v27.4.1+incompatible h1:ZJvcY7gfwHn1JF48PfbyXg7Jyt9ZCWDW+GGXOIxEwp4=
github.com/docker/docker v27.4.1+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk=
github.com/docker/docker v27.5.0+incompatible h1:um++2NcQtGRTz5eEgO6aJimo6/JxrTXC941hd05JO6U=
github.com/docker/docker v27.5.0+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk=
github.com/docker/docker v27.5.1+incompatible h1:4PYU5dnBYqRQi0294d1FBECqT9ECWeQAIfE8q4YnPY8= github.com/docker/docker v27.5.1+incompatible h1:4PYU5dnBYqRQi0294d1FBECqT9ECWeQAIfE8q4YnPY8=
github.com/docker/docker v27.5.1+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= github.com/docker/docker v27.5.1+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk=
github.com/docker/go-connections v0.5.0 h1:USnMq7hx7gwdVZq1L49hLXaFtUdTADjXGp+uj1Br63c= github.com/docker/go-connections v0.5.0 h1:USnMq7hx7gwdVZq1L49hLXaFtUdTADjXGp+uj1Br63c=
@@ -49,16 +39,14 @@ github.com/docker/go-units v0.5.0 h1:69rxXcBk27SvSaaxTtLh/8llcHD8vYHT7WSdRZ/jvr4
github.com/docker/go-units v0.5.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk= github.com/docker/go-units v0.5.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk=
github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY= github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY=
github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto= github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto=
github.com/emicklei/go-restful/v3 v3.11.0 h1:rAQeMHw1c7zTmncogyy8VvRZwtkmkZ4FxERmMY4rD+g=
github.com/emicklei/go-restful/v3 v3.11.0/go.mod h1:6n3XBCmQQb25CM2LCACGz8ukIrRry+4bhvbpWn3mrbc=
github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg= github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg=
github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U= github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U=
github.com/go-chi/chi/v5 v5.1.0 h1:acVI1TYaD+hhedDJ3r54HyA6sExp3HfXq7QWEEY/xMw= github.com/fxamacker/cbor/v2 v2.7.0 h1:iM5WgngdRBanHcxugY4JySA0nk1wZorNOpTgCMedv5E=
github.com/go-chi/chi/v5 v5.1.0/go.mod h1:DslCQbL2OYiznFReuXYUmQ2hGd1aDpCnlMNITLSKoi8= github.com/fxamacker/cbor/v2 v2.7.0/go.mod h1:pxXPTn3joSm21Gbwsv0w9OSA2y1HFR9qXEeXQVeNoDQ=
github.com/go-chi/chi/v5 v5.2.0 h1:Aj1EtB0qR2Rdo2dG4O94RIU35w2lvQSj6BRA4+qwFL0=
github.com/go-chi/chi/v5 v5.2.0/go.mod h1:DslCQbL2OYiznFReuXYUmQ2hGd1aDpCnlMNITLSKoi8=
github.com/go-chi/chi/v5 v5.2.1 h1:KOIHODQj58PmL80G2Eak4WdvUzjSJSm0vG72crDCqb8= github.com/go-chi/chi/v5 v5.2.1 h1:KOIHODQj58PmL80G2Eak4WdvUzjSJSm0vG72crDCqb8=
github.com/go-chi/chi/v5 v5.2.1/go.mod h1:L2yAIGWB3H+phAw1NxKwWM+7eUH/lU8pOMm5hHcoops= github.com/go-chi/chi/v5 v5.2.1/go.mod h1:L2yAIGWB3H+phAw1NxKwWM+7eUH/lU8pOMm5hHcoops=
github.com/go-chi/jwtauth/v5 v5.3.1 h1:1ePWrjVctvp1tyBq5b/2ER8Th/+RbYc7x4qNsc5rh5A=
github.com/go-chi/jwtauth/v5 v5.3.1/go.mod h1:6Fl2RRmWXs3tJYE1IQGX81FsPoGqDwq9c15j52R5q80=
github.com/go-chi/jwtauth/v5 v5.3.2 h1:s+ON3ATyyMs3Me0kqyuua6Rwu+2zqIIkL0GCaMarwvs= github.com/go-chi/jwtauth/v5 v5.3.2 h1:s+ON3ATyyMs3Me0kqyuua6Rwu+2zqIIkL0GCaMarwvs=
github.com/go-chi/jwtauth/v5 v5.3.2/go.mod h1:O4QvPRuZLZghl9WvfVaON+ARfGzpD2PBX/QY5vUz7aQ= github.com/go-chi/jwtauth/v5 v5.3.2/go.mod h1:O4QvPRuZLZghl9WvfVaON+ARfGzpD2PBX/QY5vUz7aQ=
github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
@@ -66,6 +54,16 @@ github.com/go-logr/logr v1.4.2 h1:6pFjapn8bFcIbiKo3XT4j/BhANplGihG6tvd+8rYgrY=
github.com/go-logr/logr v1.4.2/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= github.com/go-logr/logr v1.4.2/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag=
github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE=
github.com/go-openapi/jsonpointer v0.19.6/go.mod h1:osyAmYz/mB/C3I+WsTTSgw1ONzaLJoLCyoi6/zppojs=
github.com/go-openapi/jsonpointer v0.21.0 h1:YgdVicSA9vH5RiHs9TZW5oyafXZFc6+2Vc1rr/O9oNQ=
github.com/go-openapi/jsonpointer v0.21.0/go.mod h1:IUyH9l/+uyhIYQ/PXVA41Rexl+kOkAPDdXEYns6fzUY=
github.com/go-openapi/jsonreference v0.20.2 h1:3sVjiK66+uXK/6oQ8xgcRKcFgQ5KXa2KvnJRumpMGbE=
github.com/go-openapi/jsonreference v0.20.2/go.mod h1:Bl1zwGIM8/wsvqjsOQLJ/SH+En5Ap4rVB5KVcIDZG2k=
github.com/go-openapi/swag v0.22.3/go.mod h1:UzaqsxGiab7freDnrUUra0MwWfN/q7tE4j+VcZ0yl14=
github.com/go-openapi/swag v0.23.0 h1:vsEVJDUo2hPJ2tu0/Xc+4noaxyEffXNIs3cOULZ+GrE=
github.com/go-openapi/swag v0.23.0/go.mod h1:esZ8ITTYEsH1V2trKHjAN8Ai7xHb8RV+YSZ577vPjgQ=
github.com/go-task/slim-sprig/v3 v3.0.0 h1:sUs3vkvUymDpBKi3qH1YSqBQk9+9D/8M2mN1vB6EwHI=
github.com/go-task/slim-sprig/v3 v3.0.0/go.mod h1:W848ghGpv3Qj3dhTPRyJypKRiqCdHZiAzKg9hl15HA8=
github.com/goccy/go-json v0.10.3 h1:KZ5WoDbxAIgm2HNbYckL0se1fHD6rz5j4ywS6ebzDqA= github.com/goccy/go-json v0.10.3 h1:KZ5WoDbxAIgm2HNbYckL0se1fHD6rz5j4ywS6ebzDqA=
github.com/goccy/go-json v0.10.3/go.mod h1:oq7eo15ShAhp70Anwd5lgX2pLfOS3QCiwU/PULtXL6M= github.com/goccy/go-json v0.10.3/go.mod h1:oq7eo15ShAhp70Anwd5lgX2pLfOS3QCiwU/PULtXL6M=
github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=
@@ -73,18 +71,32 @@ github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q=
github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q=
github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek= github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek=
github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps= github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps=
github.com/google/gnostic-models v0.6.8 h1:yo/ABAfM5IMRsS1VnXjTBvUb61tFIHozhlYvRgGre9I=
github.com/google/gnostic-models v0.6.8/go.mod h1:5n7qKqH0f5wFt+aWF8CW6pZLLNOfYuF5OpfBSENuI8U=
github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
github.com/google/gofuzz v1.2.0 h1:xRy4A+RhZaiKjJ1bPfwQ8sedCA+YS2YcCHW6ec7JMi0=
github.com/google/gofuzz v1.2.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
github.com/google/pprof v0.0.0-20241029153458-d1b30febd7db h1:097atOisP2aRj7vFgYQBbFN4U4JNXUNYpxael3UzMyo=
github.com/google/pprof v0.0.0-20241029153458-d1b30febd7db/go.mod h1:vavhavw2zAxS5dIdcRluK6cSGGPlZynqzFM8NdvU144=
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/grpc-ecosystem/grpc-gateway/v2 v2.16.0 h1:YBftPWNWd4WwGqtY2yeZL2ef8rHAxPBD8KFhJpmcqms= github.com/grpc-ecosystem/grpc-gateway/v2 v2.16.0 h1:YBftPWNWd4WwGqtY2yeZL2ef8rHAxPBD8KFhJpmcqms=
github.com/grpc-ecosystem/grpc-gateway/v2 v2.16.0/go.mod h1:YN5jB8ie0yfIUg6VvR9Kz84aCaG7AsGZnLjhHbUqwPg= github.com/grpc-ecosystem/grpc-gateway/v2 v2.16.0/go.mod h1:YN5jB8ie0yfIUg6VvR9Kz84aCaG7AsGZnLjhHbUqwPg=
github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY=
github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y= github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y=
github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM=
github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo=
github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8= github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8=
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
github.com/kr/pretty v0.2.1 h1:Fmg33tUaq4/8ym9TJN1x7sLJnHVwhP33CNkpYV/7rwI=
github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
@@ -101,8 +113,6 @@ github.com/lestrrat-go/jwx/v2 v2.1.3 h1:Ud4lb2QuxRClYAmRleF50KrbKIoM1TddXgBrneT5
github.com/lestrrat-go/jwx/v2 v2.1.3/go.mod h1:q6uFgbgZfEmQrfJfrCo90QcQOcXFMfbI/fO0NqRtvZo= github.com/lestrrat-go/jwx/v2 v2.1.3/go.mod h1:q6uFgbgZfEmQrfJfrCo90QcQOcXFMfbI/fO0NqRtvZo=
github.com/lestrrat-go/option v1.0.1 h1:oAzP2fvZGQKWkvHa1/SAcFolBEca1oN+mQ7eooNBEYU= github.com/lestrrat-go/option v1.0.1 h1:oAzP2fvZGQKWkvHa1/SAcFolBEca1oN+mQ7eooNBEYU=
github.com/lestrrat-go/option v1.0.1/go.mod h1:5ZHFbivi4xwXxhxY9XHDe2FHo6/Z7WWmtT7T5nBBp3I= github.com/lestrrat-go/option v1.0.1/go.mod h1:5ZHFbivi4xwXxhxY9XHDe2FHo6/Z7WWmtT7T5nBBp3I=
github.com/magiconair/properties v1.8.7 h1:IeQXZAiQcpL9mgcAe1Nu6cX9LLw6ExEHKjN0VQdvPDY=
github.com/magiconair/properties v1.8.7/go.mod h1:Dhd985XPs7jluiymwWYZ0G4Z61jb3vdS329zhj2hYo0=
github.com/magiconair/properties v1.8.9 h1:nWcCbLq1N2v/cpNsy5WvQ37Fb+YElfq20WJ/a8RkpQM= github.com/magiconair/properties v1.8.9 h1:nWcCbLq1N2v/cpNsy5WvQ37Fb+YElfq20WJ/a8RkpQM=
github.com/magiconair/properties v1.8.9/go.mod h1:Dhd985XPs7jluiymwWYZ0G4Z61jb3vdS329zhj2hYo0= github.com/magiconair/properties v1.8.9/go.mod h1:Dhd985XPs7jluiymwWYZ0G4Z61jb3vdS329zhj2hYo0=
github.com/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0= github.com/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0=
@@ -117,8 +127,19 @@ github.com/moby/docker-image-spec v1.3.1 h1:jMKff3w6PgbfSa69GfNg+zN/XLhfXJGnEx3N
github.com/moby/docker-image-spec v1.3.1/go.mod h1:eKmb5VW8vQEh/BAr2yvVNvuiJuY6UIocYsFu/DxxRpo= github.com/moby/docker-image-spec v1.3.1/go.mod h1:eKmb5VW8vQEh/BAr2yvVNvuiJuY6UIocYsFu/DxxRpo=
github.com/moby/term v0.0.0-20201216013528-df9cb8a40635 h1:rzf0wL0CHVc8CEsgyygG0Mn9CNCCPZqOPaz8RiiHYQk= github.com/moby/term v0.0.0-20201216013528-df9cb8a40635 h1:rzf0wL0CHVc8CEsgyygG0Mn9CNCCPZqOPaz8RiiHYQk=
github.com/moby/term v0.0.0-20201216013528-df9cb8a40635/go.mod h1:FBS0z0QWA44HXygs7VXDUOGoN/1TV3RuWkLO04am3wc= github.com/moby/term v0.0.0-20201216013528-df9cb8a40635/go.mod h1:FBS0z0QWA44HXygs7VXDUOGoN/1TV3RuWkLO04am3wc=
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg=
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M=
github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
github.com/morikuni/aec v1.0.0 h1:nP9CBfwrvYnBRgY6qfDQkygYDmYwOilePFkwzv4dU8A= github.com/morikuni/aec v1.0.0 h1:nP9CBfwrvYnBRgY6qfDQkygYDmYwOilePFkwzv4dU8A=
github.com/morikuni/aec v1.0.0/go.mod h1:BbKIizmSmc5MMPqRYbxO4ZU0S0+P200+tUnFx7PXmsc= github.com/morikuni/aec v1.0.0/go.mod h1:BbKIizmSmc5MMPqRYbxO4ZU0S0+P200+tUnFx7PXmsc=
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA=
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ=
github.com/onsi/ginkgo/v2 v2.21.0 h1:7rg/4f3rB88pb5obDgNZrNHrQ4e6WpjonchcpuBRnZM=
github.com/onsi/ginkgo/v2 v2.21.0/go.mod h1:7Du3c42kxCUegi0IImZ1wUQzMBVecgIHjR1C+NkhLQo=
github.com/onsi/gomega v1.35.1 h1:Cwbd75ZBPxFSuZ6T+rN/WCb/gOc6YgFBXLlZLhC7Ds4=
github.com/onsi/gomega v1.35.1/go.mod h1:PvZbdDc8J6XJEpDK4HCuRBm8a6Fzp9/DmhC9C7yFlog=
github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8Oi/yOhh5U= github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8Oi/yOhh5U=
github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM= github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM=
github.com/opencontainers/image-spec v1.1.0 h1:8SG7/vwALn54lVB/0yZ/MMwhFrPYtpEHQb2IpWsCzug= github.com/opencontainers/image-spec v1.1.0 h1:8SG7/vwALn54lVB/0yZ/MMwhFrPYtpEHQb2IpWsCzug=
@@ -126,23 +147,16 @@ github.com/opencontainers/image-spec v1.1.0/go.mod h1:W4s4sFTMaBeK1BQLXbG4AdM2sz
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/puzpuzpuz/xsync/v3 v3.4.0 h1:DuVBAdXuGFHv8adVXjWWZ63pJq+NRXOWVXlKDBZ+mJ4= github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U=
github.com/puzpuzpuz/xsync/v3 v3.4.0/go.mod h1:VjzYrABPabuM4KyBh1Ftq6u8nhwY5tBPKP9jpmh0nnA= github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/puzpuzpuz/xsync/v3 v3.4.1 h1:wWXLKXwzpsduC3kUSahiL45MWxkGb+AQG0dsri4iftA=
github.com/puzpuzpuz/xsync/v3 v3.4.1/go.mod h1:VjzYrABPabuM4KyBh1Ftq6u8nhwY5tBPKP9jpmh0nnA=
github.com/puzpuzpuz/xsync/v3 v3.5.0 h1:i+cMcpEDY1BkNm7lPDkCtE4oElsYLn+EKF8kAu2vXT4= github.com/puzpuzpuz/xsync/v3 v3.5.0 h1:i+cMcpEDY1BkNm7lPDkCtE4oElsYLn+EKF8kAu2vXT4=
github.com/puzpuzpuz/xsync/v3 v3.5.0/go.mod h1:VjzYrABPabuM4KyBh1Ftq6u8nhwY5tBPKP9jpmh0nnA= github.com/puzpuzpuz/xsync/v3 v3.5.0/go.mod h1:VjzYrABPabuM4KyBh1Ftq6u8nhwY5tBPKP9jpmh0nnA=
github.com/rogpeppe/go-internal v1.12.0 h1:exVL4IDcn6na9z1rAb56Vxr+CgyK3nn3O+epU5NdKM8=
github.com/rogpeppe/go-internal v1.12.0/go.mod h1:E+RYuTGaKKdloAfM02xzb0FW3Paa99yedzYV+kq4uf4=
github.com/rs/xid v1.5.0/go.mod h1:trrq9SKmegXys3aeAKXMUTdJsYXVwGY3RLcfgqegfbg= github.com/rs/xid v1.5.0/go.mod h1:trrq9SKmegXys3aeAKXMUTdJsYXVwGY3RLcfgqegfbg=
github.com/rs/zerolog v1.33.0 h1:1cU2KZkvPxNyfgEmhHAz/1A9Bz+llsdYzklWFzgp0r8= github.com/rs/zerolog v1.33.0 h1:1cU2KZkvPxNyfgEmhHAz/1A9Bz+llsdYzklWFzgp0r8=
github.com/rs/zerolog v1.33.0/go.mod h1:/7mN4D5sKwJLZQ2b/znpjC3/GQWY/xaDXUM0kKWRHss= github.com/rs/zerolog v1.33.0/go.mod h1:/7mN4D5sKwJLZQ2b/znpjC3/GQWY/xaDXUM0kKWRHss=
github.com/samber/lo v1.47.0 h1:z7RynLwP5nbyRscyvcD043DWYoOcYRv3mV8lBeqOCLc=
github.com/samber/lo v1.47.0/go.mod h1:RmDH9Ct32Qy3gduHQuKJ3gW1fMHAnE/fAzQuf6He5cU=
github.com/samber/lo v1.48.0 h1:ELOfcaM7vdYPe0egBS2Nxa8LxkY4lR+9LBzj0l6cHJ0=
github.com/samber/lo v1.48.0/go.mod h1:dO6KHFzUKXgP8LDhU0oI8d2hekjXnGOu0DB8Jecxd6o=
github.com/samber/lo v1.49.0 h1:AGnTnQrg1jpFuwECPUSoxZCfVH5W22b605kWSry3YxM=
github.com/samber/lo v1.49.0/go.mod h1:dO6KHFzUKXgP8LDhU0oI8d2hekjXnGOu0DB8Jecxd6o=
github.com/samber/lo v1.49.1 h1:4BIFyVfuQSEpluc7Fua+j1NolZHiEHEpaSEKdsH0tew= github.com/samber/lo v1.49.1 h1:4BIFyVfuQSEpluc7Fua+j1NolZHiEHEpaSEKdsH0tew=
github.com/samber/lo v1.49.1/go.mod h1:dO6KHFzUKXgP8LDhU0oI8d2hekjXnGOu0DB8Jecxd6o= github.com/samber/lo v1.49.1/go.mod h1:dO6KHFzUKXgP8LDhU0oI8d2hekjXnGOu0DB8Jecxd6o=
github.com/segmentio/asm v1.2.0 h1:9BQrFxC+YOHJlTlHGkTrFWf59nbL3XnCoFLTwDCI7ys= github.com/segmentio/asm v1.2.0 h1:9BQrFxC+YOHJlTlHGkTrFWf59nbL3XnCoFLTwDCI7ys=
@@ -151,24 +165,29 @@ github.com/sergi/go-diff v1.3.1 h1:xkr+Oxo4BOQKmkn/B9eMK0g5Kg/983T9DqqPHwYqD+8=
github.com/sergi/go-diff v1.3.1/go.mod h1:aMJSSKb2lpPvRNec0+w3fl7LP9IOFzdc9Pa4NFbPK1I= github.com/sergi/go-diff v1.3.1/go.mod h1:aMJSSKb2lpPvRNec0+w3fl7LP9IOFzdc9Pa4NFbPK1I=
github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ= github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ=
github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ=
github.com/spf13/afero v1.11.0 h1:WJQKhtpdm3v2IzqG8VMqrr6Rf3UYpEF239Jy9wNepM8=
github.com/spf13/afero v1.11.0/go.mod h1:GH9Y3pIexgf1MTIWtNGyogA5MwRIDXGUr+hbWNoBjkY=
github.com/spf13/afero v1.12.0 h1:UcOPyRBYczmFn6yvphxkn9ZEOY65cpwGKb5mL36mrqs= github.com/spf13/afero v1.12.0 h1:UcOPyRBYczmFn6yvphxkn9ZEOY65cpwGKb5mL36mrqs=
github.com/spf13/afero v1.12.0/go.mod h1:ZTlWwG4/ahT8W7T0WQ5uYmjI9duaLQGy3Q2OAl4sk/4= github.com/spf13/afero v1.12.0/go.mod h1:ZTlWwG4/ahT8W7T0WQ5uYmjI9duaLQGy3Q2OAl4sk/4=
github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4=
github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA=
github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
github.com/stretchr/objx v0.5.2 h1:xuMeJ0Sdp5ZMRXx/aWO6RZxdr3beISkG5/G/aIRr3pY= github.com/stretchr/objx v0.5.2 h1:xuMeJ0Sdp5ZMRXx/aWO6RZxdr3beISkG5/G/aIRr3pY=
github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA= github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA=
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA= github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA=
github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
github.com/wk8/go-ordered-map/v2 v2.1.8 h1:5h/BUHu93oj4gIdvHHHGsScSTMijfx5PeYkE/fJgbpc= github.com/wk8/go-ordered-map/v2 v2.1.8 h1:5h/BUHu93oj4gIdvHHHGsScSTMijfx5PeYkE/fJgbpc=
github.com/wk8/go-ordered-map/v2 v2.1.8/go.mod h1:5nJHM5DyteebpVlHnWMV0rPz6Zp7+xBAnxjb1X5vnTw= github.com/wk8/go-ordered-map/v2 v2.1.8/go.mod h1:5nJHM5DyteebpVlHnWMV0rPz6Zp7+xBAnxjb1X5vnTw=
github.com/x448/float16 v0.8.4 h1:qLwI1I70+NjRFUR3zs1JPUCgaCXSh3SW62uAKT1mSBM=
github.com/x448/float16 v0.8.4/go.mod h1:14CWIYCyZA/cWjXOioeEpHeN/83MdbZDRQHoFcYsOfg=
github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
@@ -184,10 +203,10 @@ go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.22.0 h1:FyjCy
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.22.0/go.mod h1:hYwym2nDEeZfG/motx0p7L7J1N1vyzIThemQsb4g2qY= go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.22.0/go.mod h1:hYwym2nDEeZfG/motx0p7L7J1N1vyzIThemQsb4g2qY=
go.opentelemetry.io/otel/metric v1.32.0 h1:xV2umtmNcThh2/a/aCP+h64Xx5wsj8qqnkYZktzNa0M= go.opentelemetry.io/otel/metric v1.32.0 h1:xV2umtmNcThh2/a/aCP+h64Xx5wsj8qqnkYZktzNa0M=
go.opentelemetry.io/otel/metric v1.32.0/go.mod h1:jH7CIbbK6SH2V2wE16W05BHCtIDzauciCRLoc/SyMv8= go.opentelemetry.io/otel/metric v1.32.0/go.mod h1:jH7CIbbK6SH2V2wE16W05BHCtIDzauciCRLoc/SyMv8=
go.opentelemetry.io/otel/sdk v1.22.0 h1:6coWHw9xw7EfClIC/+O31R8IY3/+EiRFHevmHafB2Gw= go.opentelemetry.io/otel/sdk v1.32.0 h1:RNxepc9vK59A8XsgZQouW8ue8Gkb4jpWtJm9ge5lEG4=
go.opentelemetry.io/otel/sdk v1.22.0/go.mod h1:iu7luyVGYovrRpe2fmj3CVKouQNdTOkxtLzPvPz1DOc=
go.opentelemetry.io/otel/sdk v1.31.0/go.mod h1:TfRbMdhvxIIr/B2N2LQW2S5v9m3gOQ/08KsbbO5BPT0=
go.opentelemetry.io/otel/sdk v1.32.0/go.mod h1:LqgegDBjKMmb2GC6/PrTnteJG39I8/vJCAP9LlJXEjU= go.opentelemetry.io/otel/sdk v1.32.0/go.mod h1:LqgegDBjKMmb2GC6/PrTnteJG39I8/vJCAP9LlJXEjU=
go.opentelemetry.io/otel/sdk/metric v1.32.0 h1:rZvFnvmvawYb0alrYkjraqJq0Z4ZUJAiyYCU9snn1CU=
go.opentelemetry.io/otel/sdk/metric v1.32.0/go.mod h1:PWeZlq0zt9YkYAp3gjKZ0eicRYvOh1Gd+X99x6GHpCQ=
go.opentelemetry.io/otel/trace v1.32.0 h1:WIC9mYrXf8TmY/EXuULKc8hR17vE+Hjv2cssQDe03fM= go.opentelemetry.io/otel/trace v1.32.0 h1:WIC9mYrXf8TmY/EXuULKc8hR17vE+Hjv2cssQDe03fM=
go.opentelemetry.io/otel/trace v1.32.0/go.mod h1:+i4rkvCraA+tG6AzwloGaCtkx53Fa+L+V8e9a7YvhT8= go.opentelemetry.io/otel/trace v1.32.0/go.mod h1:+i4rkvCraA+tG6AzwloGaCtkx53Fa+L+V8e9a7YvhT8=
go.opentelemetry.io/proto/otlp v1.0.0 h1:T0TX0tmXU8a3CbNXzEKGeU5mIVOdf0oykP+u2lIVU/I= go.opentelemetry.io/proto/otlp v1.0.0 h1:T0TX0tmXU8a3CbNXzEKGeU5mIVOdf0oykP+u2lIVU/I=
@@ -199,14 +218,7 @@ golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5y
golang.org/x/crypto v0.13.0/go.mod h1:y6Z2r+Rw4iayiXXAIxJIDAJ1zMW4yaTpebo8fPOliYc= golang.org/x/crypto v0.13.0/go.mod h1:y6Z2r+Rw4iayiXXAIxJIDAJ1zMW4yaTpebo8fPOliYc=
golang.org/x/crypto v0.19.0/go.mod h1:Iy9bg/ha4yyC70EfRS8jz+B6ybOBKMaSxLj6P6oBDfU= golang.org/x/crypto v0.19.0/go.mod h1:Iy9bg/ha4yyC70EfRS8jz+B6ybOBKMaSxLj6P6oBDfU=
golang.org/x/crypto v0.23.0/go.mod h1:CKFgDieR+mRhux2Lsu27y0fO304Db0wZe70UKqHu0v8= golang.org/x/crypto v0.23.0/go.mod h1:CKFgDieR+mRhux2Lsu27y0fO304Db0wZe70UKqHu0v8=
golang.org/x/crypto v0.29.0 h1:L5SG1JTTXupVV3n6sUqMTeWbjAyfPwoda2DLX8J8FrQ=
golang.org/x/crypto v0.29.0/go.mod h1:+F4F4N5hv6v38hfeYwTdx20oUvLLc+QfrE9Ax9HtgRg=
golang.org/x/crypto v0.30.0 h1:RwoQn3GkWiMkzlX562cLB7OxWvjH1L8xutO2WoJcRoY=
golang.org/x/crypto v0.30.0/go.mod h1:kDsLvtWBEx7MV9tJOj9bnXsPbxwJQ6csT/x4KIN4Ssk=
golang.org/x/crypto v0.31.0 h1:ihbySMvVjLAeSH1IbfcRTkD/iNscyz8rGzjF/E5hV6U=
golang.org/x/crypto v0.31.0/go.mod h1:kDsLvtWBEx7MV9tJOj9bnXsPbxwJQ6csT/x4KIN4Ssk= golang.org/x/crypto v0.31.0/go.mod h1:kDsLvtWBEx7MV9tJOj9bnXsPbxwJQ6csT/x4KIN4Ssk=
golang.org/x/crypto v0.32.0 h1:euUpcYgM8WcP71gNpTqQCn6rC2t6ULUPiOzfWaXVVfc=
golang.org/x/crypto v0.32.0/go.mod h1:ZnnJkOaASj8g0AjIduWNlq2NRxL0PlBrbKVyZ6V/Ugc=
golang.org/x/crypto v0.33.0 h1:IOBPskki6Lysi0lo9qQvbxiQ+FvsCC/YWOecCHAixus= golang.org/x/crypto v0.33.0 h1:IOBPskki6Lysi0lo9qQvbxiQ+FvsCC/YWOecCHAixus=
golang.org/x/crypto v0.33.0/go.mod h1:bVdXmD7IV/4GdElGPozy6U7lWdRXA4qyRVGJV57uQ5M= golang.org/x/crypto v0.33.0/go.mod h1:bVdXmD7IV/4GdElGPozy6U7lWdRXA4qyRVGJV57uQ5M=
golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
@@ -224,15 +236,14 @@ golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwY
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
golang.org/x/net v0.9.0/go.mod h1:d48xBJpPfHeWQsugry2m+kC02ZBRGRgulfHnEXEuWns=
golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg= golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg=
golang.org/x/net v0.15.0/go.mod h1:idbUs1IY1+zTqbi8yxTbhexhEEk5ur9LInksu6HrEpk= golang.org/x/net v0.15.0/go.mod h1:idbUs1IY1+zTqbi8yxTbhexhEEk5ur9LInksu6HrEpk=
golang.org/x/net v0.21.0/go.mod h1:bIjVDfnllIU7BJ2DNgfnXvpSvtn8VRwhlsaeUTyUS44= golang.org/x/net v0.21.0/go.mod h1:bIjVDfnllIU7BJ2DNgfnXvpSvtn8VRwhlsaeUTyUS44=
golang.org/x/net v0.25.0/go.mod h1:JkAGAh7GEvH74S6FOH42FLoXpXbE/aqXSrIQjXgsiwM= golang.org/x/net v0.25.0/go.mod h1:JkAGAh7GEvH74S6FOH42FLoXpXbE/aqXSrIQjXgsiwM=
golang.org/x/net v0.31.0 h1:68CPQngjLL0r2AlUKiSxtQFKvzRVbnzLwMUn5SzcLHo=
golang.org/x/net v0.31.0/go.mod h1:P4fl1q7dY2hnZFxEk4pPSkDHF+QqjitcnDjUQyMM+pM=
golang.org/x/net v0.33.0 h1:74SYHlV8BIgHIFC/LrYkOGIwL19eTYXQ5wc6TBuO36I= golang.org/x/net v0.33.0 h1:74SYHlV8BIgHIFC/LrYkOGIwL19eTYXQ5wc6TBuO36I=
golang.org/x/net v0.33.0/go.mod h1:HXLR5J+9DxmrqMwG9qjGCxZ+zKXxBru04zlTvWlWuN4= golang.org/x/net v0.33.0/go.mod h1:HXLR5J+9DxmrqMwG9qjGCxZ+zKXxBru04zlTvWlWuN4=
golang.org/x/oauth2 v0.25.0 h1:CY4y7XT9v0cRI9oupztF8AgiIu99L/ksR/Xp/6jrZ70=
golang.org/x/oauth2 v0.25.0/go.mod h1:XYTD2NtWslqkgxebSiOHnXEap4TF09sJSc7H1sXbhtI=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
@@ -241,9 +252,6 @@ golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.3.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y= golang.org/x/sync v0.3.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y=
golang.org/x/sync v0.6.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= golang.org/x/sync v0.6.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
golang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= golang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
golang.org/x/sync v0.9.0 h1:fEo0HyrW1GIgZdpbhCRO0PkJajUS5H9IFUztCgEo2jQ=
golang.org/x/sync v0.9.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
golang.org/x/sync v0.10.0 h1:3NQrjDixjgGwUOCaF8w2+VYHv0Ve/vGYSbdkTa98gmQ=
golang.org/x/sync v0.10.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= golang.org/x/sync v0.10.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
golang.org/x/sync v0.11.0 h1:GGz8+XQP4FvTTrjZPzNKTMFtSXH80RAzG+5ghFPgK9w= golang.org/x/sync v0.11.0 h1:GGz8+XQP4FvTTrjZPzNKTMFtSXH80RAzG+5ghFPgK9w=
golang.org/x/sync v0.11.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= golang.org/x/sync v0.11.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
@@ -258,29 +266,24 @@ golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBc
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.7.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/sys v0.20.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/sys v0.20.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/sys v0.27.0 h1:wBqf8DvsY9Y/2P8gAfPDEYNuS30J4lPHJxXSb/nJZ+s=
golang.org/x/sys v0.27.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/sys v0.28.0 h1:Fksou7UEQUWlKvIdsqzJmUmCX3cZuD2+P3XyyzwMhlA=
golang.org/x/sys v0.28.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/sys v0.28.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/sys v0.29.0 h1:TPYlXGxvx1MGTn2GiZDhnjPA9wZzZeGKHHmKhHYvgaU=
golang.org/x/sys v0.29.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/sys v0.30.0 h1:QjkSwP/36a20jFYWkSue1YwXzLmsV5Gfq7Eiy72C1uc= golang.org/x/sys v0.30.0 h1:QjkSwP/36a20jFYWkSue1YwXzLmsV5Gfq7Eiy72C1uc=
golang.org/x/sys v0.30.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/sys v0.30.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/telemetry v0.0.0-20240228155512-f48c80bd79b2/go.mod h1:TeRTkGYfJXctD9OcfyVLyj2J3IxLnKwHJR8f4D8a3YE= golang.org/x/telemetry v0.0.0-20240228155512-f48c80bd79b2/go.mod h1:TeRTkGYfJXctD9OcfyVLyj2J3IxLnKwHJR8f4D8a3YE=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
golang.org/x/term v0.7.0/go.mod h1:P32HKFT3hSsZrRxla30E9HqToFYAQPCMs/zFMBUFqPY=
golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo= golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo=
golang.org/x/term v0.12.0/go.mod h1:owVbMEjm3cBLCHdkQu9b1opXd4ETQWc3BhuQGKgXgvU= golang.org/x/term v0.12.0/go.mod h1:owVbMEjm3cBLCHdkQu9b1opXd4ETQWc3BhuQGKgXgvU=
golang.org/x/term v0.17.0/go.mod h1:lLRBjIVuehSbZlaOtGMbcMncT+aqLLLmKrsjNrUguwk= golang.org/x/term v0.17.0/go.mod h1:lLRBjIVuehSbZlaOtGMbcMncT+aqLLLmKrsjNrUguwk=
golang.org/x/term v0.20.0/go.mod h1:8UkIAJTvZgivsXaD6/pH6U9ecQzZ45awqEOzuCvwpFY= golang.org/x/term v0.20.0/go.mod h1:8UkIAJTvZgivsXaD6/pH6U9ecQzZ45awqEOzuCvwpFY=
golang.org/x/term v0.27.0/go.mod h1:iMsnZpn0cago0GOrHO2+Y7u7JPn5AylBrcoWkElMTSM= golang.org/x/term v0.27.0/go.mod h1:iMsnZpn0cago0GOrHO2+Y7u7JPn5AylBrcoWkElMTSM=
golang.org/x/term v0.29.0 h1:L6pJp37ocefwRRtYPKSWOWzOtWSxVajvz2ldH/xi3iU=
golang.org/x/term v0.29.0/go.mod h1:6bl4lRlvVuDgSf3179VpIxBF0o10JUpXWOnI7nErv7s=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
@@ -289,14 +292,11 @@ golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
golang.org/x/text v0.15.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= golang.org/x/text v0.15.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
golang.org/x/text v0.20.0 h1:gK/Kv2otX8gz+wn7Rmb3vT96ZwuoxnQlY+HlJVj7Qug=
golang.org/x/text v0.20.0/go.mod h1:D4IsuqiFMhST5bX19pQ9ikHC2GsaKyk/oF+pn3ducp4=
golang.org/x/text v0.21.0 h1:zyQAAkrwaneQ066sspRyJaG9VNi/YJ1NfzcGB3hZ/qo=
golang.org/x/text v0.21.0/go.mod h1:4IBbMaMmOPCJ8SecivzSH54+73PCFmPWxNTLm+vZkEQ= golang.org/x/text v0.21.0/go.mod h1:4IBbMaMmOPCJ8SecivzSH54+73PCFmPWxNTLm+vZkEQ=
golang.org/x/text v0.22.0 h1:bofq7m3/HAFvbF51jz3Q9wLg3jkvSPuiZu/pD1XwgtM= golang.org/x/text v0.22.0 h1:bofq7m3/HAFvbF51jz3Q9wLg3jkvSPuiZu/pD1XwgtM=
golang.org/x/text v0.22.0/go.mod h1:YRoo4H8PVmsu+E3Ou7cqLVH8oXWIHVoX0jqUWALQhfY= golang.org/x/text v0.22.0/go.mod h1:YRoo4H8PVmsu+E3Ou7cqLVH8oXWIHVoX0jqUWALQhfY=
golang.org/x/time v0.5.0 h1:o7cqy6amK/52YcAKIPlM3a+Fpj35zvRj2TP+e1xFSfk= golang.org/x/time v0.8.0 h1:9i3RxcPv3PZnitoVGMPDKZSq1xW1gK1Xy3ArNOGZfEg=
golang.org/x/time v0.5.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM= golang.org/x/time v0.8.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20190624222133-a101b041ded4/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= golang.org/x/tools v0.0.0-20190624222133-a101b041ded4/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
@@ -306,46 +306,29 @@ golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc
golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU= golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU=
golang.org/x/tools v0.13.0/go.mod h1:HvlwmtVNQAhOuCjW7xxvovg8wbNq7LwfXh/k7wXUl58= golang.org/x/tools v0.13.0/go.mod h1:HvlwmtVNQAhOuCjW7xxvovg8wbNq7LwfXh/k7wXUl58=
golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d/go.mod h1:aiJjzUbINMkxbQROHiO6hDPo2LHcIPhhQsa9DLh0yGk= golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d/go.mod h1:aiJjzUbINMkxbQROHiO6hDPo2LHcIPhhQsa9DLh0yGk=
golang.org/x/tools v0.26.0 h1:v/60pFQmzmT9ExmjDv2gGIfi3OqfKoEP6I5+umXlbnQ=
golang.org/x/tools v0.26.0/go.mod h1:TPVVj70c7JJ3WCazhD8OdXcZg/og+b9+tH/KxylGwH0=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
google.golang.org/genproto v0.0.0-20231106174013-bbf56f31fb17 h1:wpZ8pe2x1Q3f2KyT5f8oP/fa9rHAKgFPr/HZdNuS+PQ= google.golang.org/genproto v0.0.0-20241118233622-e639e219e697 h1:ToEetK57OidYuqD4Q5w+vfEnPvPpuTwedCNVohYJfNk=
google.golang.org/genproto/googleapis/api v0.0.0-20240903143218-8af14fe29dc1 h1:hjSy6tcFQZ171igDaN5QHOw2n6vx40juYbC/x67CEhc= google.golang.org/genproto/googleapis/api v0.0.0-20241209162323-e6fa225c2576 h1:CkkIfIt50+lT6NHAVoRYEyAvQGFM7xEwXUUywFvEb3Q=
google.golang.org/genproto/googleapis/api v0.0.0-20240903143218-8af14fe29dc1/go.mod h1:qpvKtACPCQhAdu3PyQgV4l3LMXZEtft7y8QcarRsp9I= google.golang.org/genproto/googleapis/api v0.0.0-20241209162323-e6fa225c2576/go.mod h1:1R3kvZ1dtP3+4p4d3G8uJ8rFk/fWlScl38vanWACI08=
google.golang.org/genproto/googleapis/rpc v0.0.0-20241118233622-e639e219e697 h1:LWZqQOEjDyONlF1H6afSWpAL/znlREo2tHfLoe+8LMA=
google.golang.org/genproto/googleapis/rpc v0.0.0-20241118233622-e639e219e697/go.mod h1:5uTbfoYQed2U9p3KIj2/Zzm02PYhndfdmML0qC3q3FU=
google.golang.org/genproto/googleapis/rpc v0.0.0-20241223144023-3abc09e42ca8 h1:TqExAhdPaB60Ux47Cn0oLV07rGnxZzIsaRhQaqS666A= google.golang.org/genproto/googleapis/rpc v0.0.0-20241223144023-3abc09e42ca8 h1:TqExAhdPaB60Ux47Cn0oLV07rGnxZzIsaRhQaqS666A=
google.golang.org/genproto/googleapis/rpc v0.0.0-20241223144023-3abc09e42ca8/go.mod h1:lcTa1sDdWEIHMWlITnIczmw5w60CF9ffkb8Z+DVmmjA= google.golang.org/genproto/googleapis/rpc v0.0.0-20241223144023-3abc09e42ca8/go.mod h1:lcTa1sDdWEIHMWlITnIczmw5w60CF9ffkb8Z+DVmmjA=
google.golang.org/grpc v1.68.0 h1:aHQeeJbo8zAkAa3pRzrVjZlbz6uSfeOXlJNQM0RAbz0=
google.golang.org/grpc v1.68.0/go.mod h1:fmSPC5AsjSBCK54MyHRx48kpOti1/jRfOlwEWywNjWA=
google.golang.org/grpc v1.68.1 h1:oI5oTa11+ng8r8XMMN7jAOmWfPZWbYpCFaMUTACxkM0=
google.golang.org/grpc v1.68.1/go.mod h1:+q1XYFJjShcqn0QZHvCyeR4CXPA+llXIeUIfIe00waw=
google.golang.org/grpc v1.69.0 h1:quSiOM1GJPmPH5XtU+BCoVXcDVJJAzNcoyfC2cCjGkI=
google.golang.org/grpc v1.69.0/go.mod h1:vyjdE6jLBI76dgpDojsFGNaHlxdjXN9ghpnd2o7JGZ4=
google.golang.org/grpc v1.69.2 h1:U3S9QEtbXC0bYNvRtcoklF3xGtLViumSYxWykJS+7AU=
google.golang.org/grpc v1.69.2/go.mod h1:vyjdE6jLBI76dgpDojsFGNaHlxdjXN9ghpnd2o7JGZ4=
google.golang.org/grpc v1.69.4 h1:MF5TftSMkd8GLw/m0KM6V8CMOCY6NZ1NQDPGFgbTt4A=
google.golang.org/grpc v1.69.4/go.mod h1:vyjdE6jLBI76dgpDojsFGNaHlxdjXN9ghpnd2o7JGZ4=
google.golang.org/grpc v1.70.0 h1:pWFv03aZoHzlRKHWicjsZytKAiYCtNS0dHbXnIdq7jQ= google.golang.org/grpc v1.70.0 h1:pWFv03aZoHzlRKHWicjsZytKAiYCtNS0dHbXnIdq7jQ=
google.golang.org/grpc v1.70.0/go.mod h1:ofIJqVKDXx/JiXrwr2IG4/zwdH9txy3IlF40RmcJSQw= google.golang.org/grpc v1.70.0/go.mod h1:ofIJqVKDXx/JiXrwr2IG4/zwdH9txy3IlF40RmcJSQw=
google.golang.org/protobuf v1.35.2 h1:8Ar7bF+apOIoThw1EdZl0p1oWvMqTHmpA2fRTyZO8io=
google.golang.org/protobuf v1.35.2/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE=
google.golang.org/protobuf v1.36.0 h1:mjIs9gYtt56AzC4ZaffQuh88TZurBGhIJMBZGSxNerQ=
google.golang.org/protobuf v1.36.0/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE=
google.golang.org/protobuf v1.36.1 h1:yBPeRvTftaleIgM3PZ/WBIZ7XM/eEYAaEyCwvyjq/gk=
google.golang.org/protobuf v1.36.1/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE=
google.golang.org/protobuf v1.36.2 h1:R8FeyR1/eLmkutZOM5CWghmo5itiG9z0ktFlTVLuTmU=
google.golang.org/protobuf v1.36.2/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE=
google.golang.org/protobuf v1.36.3 h1:82DV7MYdb8anAVi3qge1wSnMDrnKK7ebr+I0hHRN1BU=
google.golang.org/protobuf v1.36.3/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE=
google.golang.org/protobuf v1.36.4 h1:6A3ZDJHn/eNqc1i+IdefRzy/9PokBTPvcqMySR7NNIM=
google.golang.org/protobuf v1.36.4/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE=
google.golang.org/protobuf v1.36.5 h1:tPhr+woSbjfYvY6/GPufUoYizxw1cF/yFoxJ2fmpwlM= google.golang.org/protobuf v1.36.5 h1:tPhr+woSbjfYvY6/GPufUoYizxw1cF/yFoxJ2fmpwlM=
google.golang.org/protobuf v1.36.5/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE= google.golang.org/protobuf v1.36.5/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 h1:YR8cESwS4TdDjEe65xsg0ogRM/Nc3DYOhEAlW+xobZo=
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
gopkg.in/evanphx/json-patch.v4 v4.12.0 h1:n6jtcsulIzXPJaxegRbvFNNrZDjbij7ny3gmSPG+6V4=
gopkg.in/evanphx/json-patch.v4 v4.12.0/go.mod h1:p8EYWUEYMpynmqDbY58zCKCFZw8pRWMG4EsWvDvM72M=
gopkg.in/inf.v0 v0.9.1 h1:73M5CoZyi3ZLMOyDlQh031Cx6N9NDJ2Vvfl76EDAgDc=
gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw=
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
@@ -354,3 +337,23 @@ gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gotest.tools/v3 v3.0.2/go.mod h1:3SzNCllyD9/Y+b5r9JIKQ474KzkZyqLqEfYqMsX94Bk= gotest.tools/v3 v3.0.2/go.mod h1:3SzNCllyD9/Y+b5r9JIKQ474KzkZyqLqEfYqMsX94Bk=
gotest.tools/v3 v3.0.3 h1:4AuOwCGf4lLR9u3YOe2awrHygurzhO/HeQ6laiA6Sx0= gotest.tools/v3 v3.0.3 h1:4AuOwCGf4lLR9u3YOe2awrHygurzhO/HeQ6laiA6Sx0=
gotest.tools/v3 v3.0.3/go.mod h1:Z7Lb0S5l+klDB31fvDQX8ss/FlKDxtlFlw3Oa8Ymbl8= gotest.tools/v3 v3.0.3/go.mod h1:Z7Lb0S5l+klDB31fvDQX8ss/FlKDxtlFlw3Oa8Ymbl8=
k8s.io/api v0.32.1 h1:f562zw9cy+GvXzXf0CKlVQ7yHJVYzLfL6JAS4kOAaOc=
k8s.io/api v0.32.1/go.mod h1:/Yi/BqkuueW1BgpoePYBRdDYfjPF5sgTr5+YqDZra5k=
k8s.io/apimachinery v0.32.1 h1:683ENpaCBjma4CYqsmZyhEzrGz6cjn1MY/X2jB2hkZs=
k8s.io/apimachinery v0.32.1/go.mod h1:GpHVgxoKlTxClKcteaeuF1Ul/lDVb74KpZcxcmLDElE=
k8s.io/client-go v0.32.1 h1:otM0AxdhdBIaQh7l1Q0jQpmo7WOFIk5FFa4bg6YMdUU=
k8s.io/client-go v0.32.1/go.mod h1:aTTKZY7MdxUaJ/KiUs8D+GssR9zJZi77ZqtzcGXIiDg=
k8s.io/klog/v2 v2.130.1 h1:n9Xl7H1Xvksem4KFG4PYbdQCQxqc/tTUyrgXaOhHSzk=
k8s.io/klog/v2 v2.130.1/go.mod h1:3Jpz1GvMt720eyJH1ckRHK1EDfpxISzJ7I9OYgaDtPE=
k8s.io/kube-openapi v0.0.0-20241105132330-32ad38e42d3f h1:GA7//TjRY9yWGy1poLzYYJJ4JRdzg3+O6e8I+e+8T5Y=
k8s.io/kube-openapi v0.0.0-20241105132330-32ad38e42d3f/go.mod h1:R/HEjbvWI0qdfb8viZUeVZm0X6IZnxAydC7YU42CMw4=
k8s.io/metrics v0.32.1 h1:Ou4nrEtZS2vFf7OJCf9z3+2kr0A00kQzfoSwxg0gXps=
k8s.io/metrics v0.32.1/go.mod h1:cLnai9XKYby1tNMX+xe8p9VLzTqrxYPcmqfCBoWObcM=
k8s.io/utils v0.0.0-20241104100929-3ea5e8cea738 h1:M3sRQVHv7vB20Xc2ybTt7ODCeFj6JSWYFzOFnYeS6Ro=
k8s.io/utils v0.0.0-20241104100929-3ea5e8cea738/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0=
sigs.k8s.io/json v0.0.0-20241010143419-9aa6b5e7a4b3 h1:/Rv+M11QRah1itp8VhT6HoVx1Ray9eB4DBr+K+/sCJ8=
sigs.k8s.io/json v0.0.0-20241010143419-9aa6b5e7a4b3/go.mod h1:18nIHnGi6636UCz6m8i4DhaJ65T6EruyzmoQqI2BVDo=
sigs.k8s.io/structured-merge-diff/v4 v4.4.2 h1:MdmvkGuXi/8io6ixD5wud3vOLwc1rj0aNqRlpuvjmwA=
sigs.k8s.io/structured-merge-diff/v4 v4.4.2/go.mod h1:N8f93tFZh9U6vpxwRArLiikrE5/2tiu1w1AGfACIGE4=
sigs.k8s.io/yaml v1.4.0 h1:Mk1wCc2gy/F0THH0TAp1QYyJNzRm2KCLy3o5ASXVI5E=
sigs.k8s.io/yaml v1.4.0/go.mod h1:Ejl7/uTz7PSA4eKMyQCUTnhZYNmLIl+5c2lQPGR2BPY=

View File

@@ -306,12 +306,12 @@ func (c *Client) FindContainer(ctx context.Context, containerID string) (contain
}, nil }, nil
} }
func (c *Client) ListContainers(ctx context.Context, filter container.ContainerFilter) ([]container.Container, error) { func (c *Client) ListContainers(ctx context.Context, labels container.ContainerLabels) ([]container.Container, error) {
in := &pb.ListContainersRequest{} in := &pb.ListContainersRequest{}
if filter != nil { if labels != nil {
in.Filter = make(map[string]*pb.RepeatedString) in.Filter = make(map[string]*pb.RepeatedString)
for k, v := range filter { for k, v := range labels {
in.Filter[k] = &pb.RepeatedString{Values: v} in.Filter[k] = &pb.RepeatedString{Values: v}
} }
} }

View File

@@ -46,7 +46,7 @@ func (m *MockedClient) ContainerEvents(ctx context.Context, events chan<- contai
return args.Error(0) return args.Error(0)
} }
func (m *MockedClient) ListContainers(ctx context.Context, filter container.ContainerFilter) ([]container.Container, error) { func (m *MockedClient) ListContainers(ctx context.Context, filter container.ContainerLabels) ([]container.Container, error) {
args := m.Called(ctx, filter) args := m.Called(ctx, filter)
return args.Get(0).([]container.Container), args.Error(1) return args.Get(0).([]container.Container), args.Error(1)
} }
@@ -130,7 +130,7 @@ func init() {
FinishedAt: time.Date(2020, 1, 1, 0, 0, 0, 0, time.UTC), FinishedAt: time.Date(2020, 1, 1, 0, 0, 0, 0, time.UTC),
}, nil) }, nil)
server, _ := NewServer(client, certs, "test", container.ContainerFilter{}) server, _ := NewServer(client, certs, "test", container.ContainerLabels{})
go server.Serve(lis) go server.Serve(lis)
} }
@@ -173,7 +173,7 @@ func TestListContainers(t *testing.T) {
t.Fatal(err) t.Fatal(err)
} }
containers, _ := rpc.ListContainers(context.Background(), container.ContainerFilter{}) containers, _ := rpc.ListContainers(context.Background(), container.ContainerLabels{})
assert.Equal(t, containers, []container.Container{ assert.Equal(t, containers, []container.Container{
{ {

View File

@@ -1,6 +1,6 @@
// Code generated by protoc-gen-go. DO NOT EDIT. // Code generated by protoc-gen-go. DO NOT EDIT.
// versions: // versions:
// protoc-gen-go v1.35.1 // protoc-gen-go v1.36.2
// protoc v5.29.3 // protoc v5.29.3
// source: rpc.proto // source: rpc.proto
@@ -22,11 +22,10 @@ const (
) )
type ListContainersRequest struct { type ListContainersRequest struct {
state protoimpl.MessageState state protoimpl.MessageState `protogen:"open.v1"`
sizeCache protoimpl.SizeCache Filter map[string]*RepeatedString `protobuf:"bytes,1,rep,name=filter,proto3" json:"filter,omitempty" protobuf_key:"bytes,1,opt,name=key" protobuf_val:"bytes,2,opt,name=value"`
unknownFields protoimpl.UnknownFields unknownFields protoimpl.UnknownFields
sizeCache protoimpl.SizeCache
Filter map[string]*RepeatedString `protobuf:"bytes,1,rep,name=filter,proto3" json:"filter,omitempty" protobuf_key:"bytes,1,opt,name=key,proto3" protobuf_val:"bytes,2,opt,name=value,proto3"`
} }
func (x *ListContainersRequest) Reset() { func (x *ListContainersRequest) Reset() {
@@ -67,11 +66,10 @@ func (x *ListContainersRequest) GetFilter() map[string]*RepeatedString {
} }
type RepeatedString struct { type RepeatedString struct {
state protoimpl.MessageState state protoimpl.MessageState `protogen:"open.v1"`
sizeCache protoimpl.SizeCache Values []string `protobuf:"bytes,1,rep,name=values,proto3" json:"values,omitempty"`
unknownFields protoimpl.UnknownFields unknownFields protoimpl.UnknownFields
sizeCache protoimpl.SizeCache
Values []string `protobuf:"bytes,1,rep,name=values,proto3" json:"values,omitempty"`
} }
func (x *RepeatedString) Reset() { func (x *RepeatedString) Reset() {
@@ -112,11 +110,10 @@ func (x *RepeatedString) GetValues() []string {
} }
type ListContainersResponse struct { type ListContainersResponse struct {
state protoimpl.MessageState state protoimpl.MessageState `protogen:"open.v1"`
sizeCache protoimpl.SizeCache Containers []*Container `protobuf:"bytes,1,rep,name=containers,proto3" json:"containers,omitempty"`
unknownFields protoimpl.UnknownFields unknownFields protoimpl.UnknownFields
sizeCache protoimpl.SizeCache
Containers []*Container `protobuf:"bytes,1,rep,name=containers,proto3" json:"containers,omitempty"`
} }
func (x *ListContainersResponse) Reset() { func (x *ListContainersResponse) Reset() {
@@ -157,12 +154,11 @@ func (x *ListContainersResponse) GetContainers() []*Container {
} }
type FindContainerRequest struct { type FindContainerRequest struct {
state protoimpl.MessageState state protoimpl.MessageState `protogen:"open.v1"`
sizeCache protoimpl.SizeCache ContainerId string `protobuf:"bytes,1,opt,name=containerId,proto3" json:"containerId,omitempty"`
Filter map[string]*RepeatedString `protobuf:"bytes,2,rep,name=filter,proto3" json:"filter,omitempty" protobuf_key:"bytes,1,opt,name=key" protobuf_val:"bytes,2,opt,name=value"`
unknownFields protoimpl.UnknownFields unknownFields protoimpl.UnknownFields
sizeCache protoimpl.SizeCache
ContainerId string `protobuf:"bytes,1,opt,name=containerId,proto3" json:"containerId,omitempty"`
Filter map[string]*RepeatedString `protobuf:"bytes,2,rep,name=filter,proto3" json:"filter,omitempty" protobuf_key:"bytes,1,opt,name=key,proto3" protobuf_val:"bytes,2,opt,name=value,proto3"`
} }
func (x *FindContainerRequest) Reset() { func (x *FindContainerRequest) Reset() {
@@ -210,11 +206,10 @@ func (x *FindContainerRequest) GetFilter() map[string]*RepeatedString {
} }
type FindContainerResponse struct { type FindContainerResponse struct {
state protoimpl.MessageState state protoimpl.MessageState `protogen:"open.v1"`
sizeCache protoimpl.SizeCache Container *Container `protobuf:"bytes,1,opt,name=container,proto3" json:"container,omitempty"`
unknownFields protoimpl.UnknownFields unknownFields protoimpl.UnknownFields
sizeCache protoimpl.SizeCache
Container *Container `protobuf:"bytes,1,opt,name=container,proto3" json:"container,omitempty"`
} }
func (x *FindContainerResponse) Reset() { func (x *FindContainerResponse) Reset() {
@@ -255,13 +250,12 @@ func (x *FindContainerResponse) GetContainer() *Container {
} }
type StreamLogsRequest struct { type StreamLogsRequest struct {
state protoimpl.MessageState state protoimpl.MessageState `protogen:"open.v1"`
sizeCache protoimpl.SizeCache ContainerId string `protobuf:"bytes,1,opt,name=containerId,proto3" json:"containerId,omitempty"`
Since *timestamppb.Timestamp `protobuf:"bytes,2,opt,name=since,proto3" json:"since,omitempty"`
StreamTypes int32 `protobuf:"varint,3,opt,name=streamTypes,proto3" json:"streamTypes,omitempty"`
unknownFields protoimpl.UnknownFields unknownFields protoimpl.UnknownFields
sizeCache protoimpl.SizeCache
ContainerId string `protobuf:"bytes,1,opt,name=containerId,proto3" json:"containerId,omitempty"`
Since *timestamppb.Timestamp `protobuf:"bytes,2,opt,name=since,proto3" json:"since,omitempty"`
StreamTypes int32 `protobuf:"varint,3,opt,name=streamTypes,proto3" json:"streamTypes,omitempty"`
} }
func (x *StreamLogsRequest) Reset() { func (x *StreamLogsRequest) Reset() {
@@ -316,11 +310,10 @@ func (x *StreamLogsRequest) GetStreamTypes() int32 {
} }
type StreamLogsResponse struct { type StreamLogsResponse struct {
state protoimpl.MessageState state protoimpl.MessageState `protogen:"open.v1"`
sizeCache protoimpl.SizeCache Event *LogEvent `protobuf:"bytes,1,opt,name=event,proto3" json:"event,omitempty"`
unknownFields protoimpl.UnknownFields unknownFields protoimpl.UnknownFields
sizeCache protoimpl.SizeCache
Event *LogEvent `protobuf:"bytes,1,opt,name=event,proto3" json:"event,omitempty"`
} }
func (x *StreamLogsResponse) Reset() { func (x *StreamLogsResponse) Reset() {
@@ -361,14 +354,13 @@ func (x *StreamLogsResponse) GetEvent() *LogEvent {
} }
type LogsBetweenDatesRequest struct { type LogsBetweenDatesRequest struct {
state protoimpl.MessageState state protoimpl.MessageState `protogen:"open.v1"`
sizeCache protoimpl.SizeCache ContainerId string `protobuf:"bytes,1,opt,name=containerId,proto3" json:"containerId,omitempty"`
Since *timestamppb.Timestamp `protobuf:"bytes,2,opt,name=since,proto3" json:"since,omitempty"`
Until *timestamppb.Timestamp `protobuf:"bytes,3,opt,name=until,proto3" json:"until,omitempty"`
StreamTypes int32 `protobuf:"varint,4,opt,name=streamTypes,proto3" json:"streamTypes,omitempty"`
unknownFields protoimpl.UnknownFields unknownFields protoimpl.UnknownFields
sizeCache protoimpl.SizeCache
ContainerId string `protobuf:"bytes,1,opt,name=containerId,proto3" json:"containerId,omitempty"`
Since *timestamppb.Timestamp `protobuf:"bytes,2,opt,name=since,proto3" json:"since,omitempty"`
Until *timestamppb.Timestamp `protobuf:"bytes,3,opt,name=until,proto3" json:"until,omitempty"`
StreamTypes int32 `protobuf:"varint,4,opt,name=streamTypes,proto3" json:"streamTypes,omitempty"`
} }
func (x *LogsBetweenDatesRequest) Reset() { func (x *LogsBetweenDatesRequest) Reset() {
@@ -430,14 +422,13 @@ func (x *LogsBetweenDatesRequest) GetStreamTypes() int32 {
} }
type StreamRawBytesRequest struct { type StreamRawBytesRequest struct {
state protoimpl.MessageState state protoimpl.MessageState `protogen:"open.v1"`
sizeCache protoimpl.SizeCache ContainerId string `protobuf:"bytes,1,opt,name=containerId,proto3" json:"containerId,omitempty"`
Since *timestamppb.Timestamp `protobuf:"bytes,2,opt,name=since,proto3" json:"since,omitempty"`
Until *timestamppb.Timestamp `protobuf:"bytes,3,opt,name=until,proto3" json:"until,omitempty"`
StreamTypes int32 `protobuf:"varint,4,opt,name=streamTypes,proto3" json:"streamTypes,omitempty"`
unknownFields protoimpl.UnknownFields unknownFields protoimpl.UnknownFields
sizeCache protoimpl.SizeCache
ContainerId string `protobuf:"bytes,1,opt,name=containerId,proto3" json:"containerId,omitempty"`
Since *timestamppb.Timestamp `protobuf:"bytes,2,opt,name=since,proto3" json:"since,omitempty"`
Until *timestamppb.Timestamp `protobuf:"bytes,3,opt,name=until,proto3" json:"until,omitempty"`
StreamTypes int32 `protobuf:"varint,4,opt,name=streamTypes,proto3" json:"streamTypes,omitempty"`
} }
func (x *StreamRawBytesRequest) Reset() { func (x *StreamRawBytesRequest) Reset() {
@@ -499,11 +490,10 @@ func (x *StreamRawBytesRequest) GetStreamTypes() int32 {
} }
type StreamRawBytesResponse struct { type StreamRawBytesResponse struct {
state protoimpl.MessageState state protoimpl.MessageState `protogen:"open.v1"`
sizeCache protoimpl.SizeCache Data []byte `protobuf:"bytes,1,opt,name=data,proto3" json:"data,omitempty"`
unknownFields protoimpl.UnknownFields unknownFields protoimpl.UnknownFields
sizeCache protoimpl.SizeCache
Data []byte `protobuf:"bytes,1,opt,name=data,proto3" json:"data,omitempty"`
} }
func (x *StreamRawBytesResponse) Reset() { func (x *StreamRawBytesResponse) Reset() {
@@ -544,9 +534,9 @@ func (x *StreamRawBytesResponse) GetData() []byte {
} }
type StreamEventsRequest struct { type StreamEventsRequest struct {
state protoimpl.MessageState state protoimpl.MessageState `protogen:"open.v1"`
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields unknownFields protoimpl.UnknownFields
sizeCache protoimpl.SizeCache
} }
func (x *StreamEventsRequest) Reset() { func (x *StreamEventsRequest) Reset() {
@@ -580,11 +570,10 @@ func (*StreamEventsRequest) Descriptor() ([]byte, []int) {
} }
type StreamEventsResponse struct { type StreamEventsResponse struct {
state protoimpl.MessageState state protoimpl.MessageState `protogen:"open.v1"`
sizeCache protoimpl.SizeCache Event *ContainerEvent `protobuf:"bytes,1,opt,name=event,proto3" json:"event,omitempty"`
unknownFields protoimpl.UnknownFields unknownFields protoimpl.UnknownFields
sizeCache protoimpl.SizeCache
Event *ContainerEvent `protobuf:"bytes,1,opt,name=event,proto3" json:"event,omitempty"`
} }
func (x *StreamEventsResponse) Reset() { func (x *StreamEventsResponse) Reset() {
@@ -625,9 +614,9 @@ func (x *StreamEventsResponse) GetEvent() *ContainerEvent {
} }
type StreamStatsRequest struct { type StreamStatsRequest struct {
state protoimpl.MessageState state protoimpl.MessageState `protogen:"open.v1"`
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields unknownFields protoimpl.UnknownFields
sizeCache protoimpl.SizeCache
} }
func (x *StreamStatsRequest) Reset() { func (x *StreamStatsRequest) Reset() {
@@ -661,11 +650,10 @@ func (*StreamStatsRequest) Descriptor() ([]byte, []int) {
} }
type StreamStatsResponse struct { type StreamStatsResponse struct {
state protoimpl.MessageState state protoimpl.MessageState `protogen:"open.v1"`
sizeCache protoimpl.SizeCache Stat *ContainerStat `protobuf:"bytes,1,opt,name=stat,proto3" json:"stat,omitempty"`
unknownFields protoimpl.UnknownFields unknownFields protoimpl.UnknownFields
sizeCache protoimpl.SizeCache
Stat *ContainerStat `protobuf:"bytes,1,opt,name=stat,proto3" json:"stat,omitempty"`
} }
func (x *StreamStatsResponse) Reset() { func (x *StreamStatsResponse) Reset() {
@@ -706,9 +694,9 @@ func (x *StreamStatsResponse) GetStat() *ContainerStat {
} }
type HostInfoRequest struct { type HostInfoRequest struct {
state protoimpl.MessageState state protoimpl.MessageState `protogen:"open.v1"`
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields unknownFields protoimpl.UnknownFields
sizeCache protoimpl.SizeCache
} }
func (x *HostInfoRequest) Reset() { func (x *HostInfoRequest) Reset() {
@@ -742,11 +730,10 @@ func (*HostInfoRequest) Descriptor() ([]byte, []int) {
} }
type HostInfoResponse struct { type HostInfoResponse struct {
state protoimpl.MessageState state protoimpl.MessageState `protogen:"open.v1"`
sizeCache protoimpl.SizeCache Host *Host `protobuf:"bytes,1,opt,name=host,proto3" json:"host,omitempty"`
unknownFields protoimpl.UnknownFields unknownFields protoimpl.UnknownFields
sizeCache protoimpl.SizeCache
Host *Host `protobuf:"bytes,1,opt,name=host,proto3" json:"host,omitempty"`
} }
func (x *HostInfoResponse) Reset() { func (x *HostInfoResponse) Reset() {
@@ -787,9 +774,9 @@ func (x *HostInfoResponse) GetHost() *Host {
} }
type StreamContainerStartedRequest struct { type StreamContainerStartedRequest struct {
state protoimpl.MessageState state protoimpl.MessageState `protogen:"open.v1"`
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields unknownFields protoimpl.UnknownFields
sizeCache protoimpl.SizeCache
} }
func (x *StreamContainerStartedRequest) Reset() { func (x *StreamContainerStartedRequest) Reset() {
@@ -823,11 +810,10 @@ func (*StreamContainerStartedRequest) Descriptor() ([]byte, []int) {
} }
type StreamContainerStartedResponse struct { type StreamContainerStartedResponse struct {
state protoimpl.MessageState state protoimpl.MessageState `protogen:"open.v1"`
sizeCache protoimpl.SizeCache Container *Container `protobuf:"bytes,1,opt,name=container,proto3" json:"container,omitempty"`
unknownFields protoimpl.UnknownFields unknownFields protoimpl.UnknownFields
sizeCache protoimpl.SizeCache
Container *Container `protobuf:"bytes,1,opt,name=container,proto3" json:"container,omitempty"`
} }
func (x *StreamContainerStartedResponse) Reset() { func (x *StreamContainerStartedResponse) Reset() {
@@ -868,12 +854,11 @@ func (x *StreamContainerStartedResponse) GetContainer() *Container {
} }
type ContainerActionRequest struct { type ContainerActionRequest struct {
state protoimpl.MessageState state protoimpl.MessageState `protogen:"open.v1"`
sizeCache protoimpl.SizeCache ContainerId string `protobuf:"bytes,1,opt,name=containerId,proto3" json:"containerId,omitempty"`
Action ContainerAction `protobuf:"varint,2,opt,name=action,proto3,enum=protobuf.ContainerAction" json:"action,omitempty"`
unknownFields protoimpl.UnknownFields unknownFields protoimpl.UnknownFields
sizeCache protoimpl.SizeCache
ContainerId string `protobuf:"bytes,1,opt,name=containerId,proto3" json:"containerId,omitempty"`
Action ContainerAction `protobuf:"varint,2,opt,name=action,proto3,enum=protobuf.ContainerAction" json:"action,omitempty"`
} }
func (x *ContainerActionRequest) Reset() { func (x *ContainerActionRequest) Reset() {
@@ -921,9 +906,9 @@ func (x *ContainerActionRequest) GetAction() ContainerAction {
} }
type ContainerActionResponse struct { type ContainerActionResponse struct {
state protoimpl.MessageState state protoimpl.MessageState `protogen:"open.v1"`
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields unknownFields protoimpl.UnknownFields
sizeCache protoimpl.SizeCache
} }
func (x *ContainerActionResponse) Reset() { func (x *ContainerActionResponse) Reset() {

View File

@@ -1,6 +1,6 @@
// Code generated by protoc-gen-go. DO NOT EDIT. // Code generated by protoc-gen-go. DO NOT EDIT.
// versions: // versions:
// protoc-gen-go v1.35.1 // protoc-gen-go v1.36.2
// protoc v5.29.3 // protoc v5.29.3
// source: types.proto // source: types.proto
@@ -72,26 +72,25 @@ func (ContainerAction) EnumDescriptor() ([]byte, []int) {
} }
type Container struct { type Container struct {
state protoimpl.MessageState state protoimpl.MessageState `protogen:"open.v1"`
sizeCache protoimpl.SizeCache Id string `protobuf:"bytes,1,opt,name=id,proto3" json:"id,omitempty"`
Name string `protobuf:"bytes,2,opt,name=name,proto3" json:"name,omitempty"`
Image string `protobuf:"bytes,3,opt,name=image,proto3" json:"image,omitempty"`
Status string `protobuf:"bytes,4,opt,name=status,proto3" json:"status,omitempty"` // deprecated
State string `protobuf:"bytes,5,opt,name=state,proto3" json:"state,omitempty"`
ImageId string `protobuf:"bytes,6,opt,name=ImageId,proto3" json:"ImageId,omitempty"` // deprecated
Created *timestamppb.Timestamp `protobuf:"bytes,7,opt,name=created,proto3" json:"created,omitempty"`
Started *timestamppb.Timestamp `protobuf:"bytes,8,opt,name=started,proto3" json:"started,omitempty"`
Health string `protobuf:"bytes,9,opt,name=health,proto3" json:"health,omitempty"`
Host string `protobuf:"bytes,10,opt,name=host,proto3" json:"host,omitempty"`
Tty bool `protobuf:"varint,11,opt,name=tty,proto3" json:"tty,omitempty"`
Labels map[string]string `protobuf:"bytes,12,rep,name=labels,proto3" json:"labels,omitempty" protobuf_key:"bytes,1,opt,name=key" protobuf_val:"bytes,2,opt,name=value"`
Stats []*ContainerStat `protobuf:"bytes,13,rep,name=stats,proto3" json:"stats,omitempty"`
Group string `protobuf:"bytes,14,opt,name=group,proto3" json:"group,omitempty"`
Command string `protobuf:"bytes,15,opt,name=command,proto3" json:"command,omitempty"`
Finished *timestamppb.Timestamp `protobuf:"bytes,16,opt,name=finished,proto3" json:"finished,omitempty"`
unknownFields protoimpl.UnknownFields unknownFields protoimpl.UnknownFields
sizeCache protoimpl.SizeCache
Id string `protobuf:"bytes,1,opt,name=id,proto3" json:"id,omitempty"`
Name string `protobuf:"bytes,2,opt,name=name,proto3" json:"name,omitempty"`
Image string `protobuf:"bytes,3,opt,name=image,proto3" json:"image,omitempty"`
Status string `protobuf:"bytes,4,opt,name=status,proto3" json:"status,omitempty"` // deprecated
State string `protobuf:"bytes,5,opt,name=state,proto3" json:"state,omitempty"`
ImageId string `protobuf:"bytes,6,opt,name=ImageId,proto3" json:"ImageId,omitempty"` // deprecated
Created *timestamppb.Timestamp `protobuf:"bytes,7,opt,name=created,proto3" json:"created,omitempty"`
Started *timestamppb.Timestamp `protobuf:"bytes,8,opt,name=started,proto3" json:"started,omitempty"`
Health string `protobuf:"bytes,9,opt,name=health,proto3" json:"health,omitempty"`
Host string `protobuf:"bytes,10,opt,name=host,proto3" json:"host,omitempty"`
Tty bool `protobuf:"varint,11,opt,name=tty,proto3" json:"tty,omitempty"`
Labels map[string]string `protobuf:"bytes,12,rep,name=labels,proto3" json:"labels,omitempty" protobuf_key:"bytes,1,opt,name=key,proto3" protobuf_val:"bytes,2,opt,name=value,proto3"`
Stats []*ContainerStat `protobuf:"bytes,13,rep,name=stats,proto3" json:"stats,omitempty"`
Group string `protobuf:"bytes,14,opt,name=group,proto3" json:"group,omitempty"`
Command string `protobuf:"bytes,15,opt,name=command,proto3" json:"command,omitempty"`
Finished *timestamppb.Timestamp `protobuf:"bytes,16,opt,name=finished,proto3" json:"finished,omitempty"`
} }
func (x *Container) Reset() { func (x *Container) Reset() {
@@ -237,14 +236,13 @@ func (x *Container) GetFinished() *timestamppb.Timestamp {
} }
type ContainerStat struct { type ContainerStat struct {
state protoimpl.MessageState state protoimpl.MessageState `protogen:"open.v1"`
sizeCache protoimpl.SizeCache Id string `protobuf:"bytes,1,opt,name=id,proto3" json:"id,omitempty"`
CpuPercent float64 `protobuf:"fixed64,2,opt,name=cpuPercent,proto3" json:"cpuPercent,omitempty"`
MemoryUsage float64 `protobuf:"fixed64,3,opt,name=memoryUsage,proto3" json:"memoryUsage,omitempty"`
MemoryPercent float64 `protobuf:"fixed64,4,opt,name=memoryPercent,proto3" json:"memoryPercent,omitempty"`
unknownFields protoimpl.UnknownFields unknownFields protoimpl.UnknownFields
sizeCache protoimpl.SizeCache
Id string `protobuf:"bytes,1,opt,name=id,proto3" json:"id,omitempty"`
CpuPercent float64 `protobuf:"fixed64,2,opt,name=cpuPercent,proto3" json:"cpuPercent,omitempty"`
MemoryUsage float64 `protobuf:"fixed64,3,opt,name=memoryUsage,proto3" json:"memoryUsage,omitempty"`
MemoryPercent float64 `protobuf:"fixed64,4,opt,name=memoryPercent,proto3" json:"memoryPercent,omitempty"`
} }
func (x *ContainerStat) Reset() { func (x *ContainerStat) Reset() {
@@ -306,17 +304,16 @@ func (x *ContainerStat) GetMemoryPercent() float64 {
} }
type LogEvent struct { type LogEvent struct {
state protoimpl.MessageState state protoimpl.MessageState `protogen:"open.v1"`
sizeCache protoimpl.SizeCache Id uint32 `protobuf:"varint,1,opt,name=id,proto3" json:"id,omitempty"`
ContainerId string `protobuf:"bytes,2,opt,name=containerId,proto3" json:"containerId,omitempty"`
Message *anypb.Any `protobuf:"bytes,3,opt,name=message,proto3" json:"message,omitempty"`
Timestamp *timestamppb.Timestamp `protobuf:"bytes,4,opt,name=timestamp,proto3" json:"timestamp,omitempty"`
Level string `protobuf:"bytes,5,opt,name=level,proto3" json:"level,omitempty"`
Stream string `protobuf:"bytes,6,opt,name=stream,proto3" json:"stream,omitempty"`
Position string `protobuf:"bytes,7,opt,name=position,proto3" json:"position,omitempty"`
unknownFields protoimpl.UnknownFields unknownFields protoimpl.UnknownFields
sizeCache protoimpl.SizeCache
Id uint32 `protobuf:"varint,1,opt,name=id,proto3" json:"id,omitempty"`
ContainerId string `protobuf:"bytes,2,opt,name=containerId,proto3" json:"containerId,omitempty"`
Message *anypb.Any `protobuf:"bytes,3,opt,name=message,proto3" json:"message,omitempty"`
Timestamp *timestamppb.Timestamp `protobuf:"bytes,4,opt,name=timestamp,proto3" json:"timestamp,omitempty"`
Level string `protobuf:"bytes,5,opt,name=level,proto3" json:"level,omitempty"`
Stream string `protobuf:"bytes,6,opt,name=stream,proto3" json:"stream,omitempty"`
Position string `protobuf:"bytes,7,opt,name=position,proto3" json:"position,omitempty"`
} }
func (x *LogEvent) Reset() { func (x *LogEvent) Reset() {
@@ -399,11 +396,10 @@ func (x *LogEvent) GetPosition() string {
} }
type SimpleMessage struct { type SimpleMessage struct {
state protoimpl.MessageState state protoimpl.MessageState `protogen:"open.v1"`
sizeCache protoimpl.SizeCache Message string `protobuf:"bytes,1,opt,name=message,proto3" json:"message,omitempty"`
unknownFields protoimpl.UnknownFields unknownFields protoimpl.UnknownFields
sizeCache protoimpl.SizeCache
Message string `protobuf:"bytes,1,opt,name=message,proto3" json:"message,omitempty"`
} }
func (x *SimpleMessage) Reset() { func (x *SimpleMessage) Reset() {
@@ -444,11 +440,10 @@ func (x *SimpleMessage) GetMessage() string {
} }
type ComplexMessage struct { type ComplexMessage struct {
state protoimpl.MessageState state protoimpl.MessageState `protogen:"open.v1"`
sizeCache protoimpl.SizeCache Data []byte `protobuf:"bytes,1,opt,name=data,proto3" json:"data,omitempty"`
unknownFields protoimpl.UnknownFields unknownFields protoimpl.UnknownFields
sizeCache protoimpl.SizeCache
Data []byte `protobuf:"bytes,1,opt,name=data,proto3" json:"data,omitempty"`
} }
func (x *ComplexMessage) Reset() { func (x *ComplexMessage) Reset() {
@@ -489,14 +484,13 @@ func (x *ComplexMessage) GetData() []byte {
} }
type ContainerEvent struct { type ContainerEvent struct {
state protoimpl.MessageState state protoimpl.MessageState `protogen:"open.v1"`
sizeCache protoimpl.SizeCache ActorId string `protobuf:"bytes,1,opt,name=actorId,proto3" json:"actorId,omitempty"`
Name string `protobuf:"bytes,2,opt,name=name,proto3" json:"name,omitempty"`
Host string `protobuf:"bytes,3,opt,name=host,proto3" json:"host,omitempty"`
Timestamp *timestamppb.Timestamp `protobuf:"bytes,4,opt,name=timestamp,proto3" json:"timestamp,omitempty"`
unknownFields protoimpl.UnknownFields unknownFields protoimpl.UnknownFields
sizeCache protoimpl.SizeCache
ActorId string `protobuf:"bytes,1,opt,name=actorId,proto3" json:"actorId,omitempty"`
Name string `protobuf:"bytes,2,opt,name=name,proto3" json:"name,omitempty"`
Host string `protobuf:"bytes,3,opt,name=host,proto3" json:"host,omitempty"`
Timestamp *timestamppb.Timestamp `protobuf:"bytes,4,opt,name=timestamp,proto3" json:"timestamp,omitempty"`
} }
func (x *ContainerEvent) Reset() { func (x *ContainerEvent) Reset() {
@@ -558,22 +552,21 @@ func (x *ContainerEvent) GetTimestamp() *timestamppb.Timestamp {
} }
type Host struct { type Host struct {
state protoimpl.MessageState state protoimpl.MessageState `protogen:"open.v1"`
sizeCache protoimpl.SizeCache Id string `protobuf:"bytes,1,opt,name=id,proto3" json:"id,omitempty"`
unknownFields protoimpl.UnknownFields Name string `protobuf:"bytes,2,opt,name=name,proto3" json:"name,omitempty"`
NodeAddress string `protobuf:"bytes,3,opt,name=nodeAddress,proto3" json:"nodeAddress,omitempty"`
Id string `protobuf:"bytes,1,opt,name=id,proto3" json:"id,omitempty"` Swarm bool `protobuf:"varint,4,opt,name=swarm,proto3" json:"swarm,omitempty"`
Name string `protobuf:"bytes,2,opt,name=name,proto3" json:"name,omitempty"` Labels map[string]string `protobuf:"bytes,5,rep,name=labels,proto3" json:"labels,omitempty" protobuf_key:"bytes,1,opt,name=key" protobuf_val:"bytes,2,opt,name=value"`
NodeAddress string `protobuf:"bytes,3,opt,name=nodeAddress,proto3" json:"nodeAddress,omitempty"` OperatingSystem string `protobuf:"bytes,6,opt,name=operatingSystem,proto3" json:"operatingSystem,omitempty"`
Swarm bool `protobuf:"varint,4,opt,name=swarm,proto3" json:"swarm,omitempty"` OsVersion string `protobuf:"bytes,7,opt,name=osVersion,proto3" json:"osVersion,omitempty"`
Labels map[string]string `protobuf:"bytes,5,rep,name=labels,proto3" json:"labels,omitempty" protobuf_key:"bytes,1,opt,name=key,proto3" protobuf_val:"bytes,2,opt,name=value,proto3"` OsType string `protobuf:"bytes,8,opt,name=osType,proto3" json:"osType,omitempty"`
OperatingSystem string `protobuf:"bytes,6,opt,name=operatingSystem,proto3" json:"operatingSystem,omitempty"` CpuCores uint32 `protobuf:"varint,9,opt,name=cpuCores,proto3" json:"cpuCores,omitempty"`
OsVersion string `protobuf:"bytes,7,opt,name=osVersion,proto3" json:"osVersion,omitempty"` Memory uint64 `protobuf:"varint,10,opt,name=memory,proto3" json:"memory,omitempty"`
OsType string `protobuf:"bytes,8,opt,name=osType,proto3" json:"osType,omitempty"` AgentVersion string `protobuf:"bytes,11,opt,name=agentVersion,proto3" json:"agentVersion,omitempty"`
CpuCores uint32 `protobuf:"varint,9,opt,name=cpuCores,proto3" json:"cpuCores,omitempty"` DockerVersion string `protobuf:"bytes,12,opt,name=dockerVersion,proto3" json:"dockerVersion,omitempty"`
Memory uint64 `protobuf:"varint,10,opt,name=memory,proto3" json:"memory,omitempty"` unknownFields protoimpl.UnknownFields
AgentVersion string `protobuf:"bytes,11,opt,name=agentVersion,proto3" json:"agentVersion,omitempty"` sizeCache protoimpl.SizeCache
DockerVersion string `protobuf:"bytes,12,opt,name=dockerVersion,proto3" json:"dockerVersion,omitempty"`
} }
func (x *Host) Reset() { func (x *Host) Reset() {

View File

@@ -13,6 +13,7 @@ import (
"github.com/amir20/dozzle/internal/agent/pb" "github.com/amir20/dozzle/internal/agent/pb"
"github.com/amir20/dozzle/internal/container" "github.com/amir20/dozzle/internal/container"
"github.com/amir20/dozzle/internal/docker"
"github.com/rs/zerolog/log" "github.com/rs/zerolog/log"
orderedmap "github.com/wk8/go-ordered-map/v2" orderedmap "github.com/wk8/go-ordered-map/v2"
"google.golang.org/grpc" "google.golang.org/grpc"
@@ -32,12 +33,13 @@ type server struct {
pb.UnimplementedAgentServiceServer pb.UnimplementedAgentServiceServer
} }
func newServer(client container.Client, dozzleVersion string, filter container.ContainerFilter) pb.AgentServiceServer { func newServer(client container.Client, dozzleVersion string, labels container.ContainerLabels) pb.AgentServiceServer {
statsCollector := docker.NewDockerStatsCollector(client, labels)
return &server{ return &server{
client: client, client: client,
version: dozzleVersion, version: dozzleVersion,
store: container.NewContainerStore(context.Background(), client, filter), store: container.NewContainerStore(context.Background(), client, statsCollector, labels),
} }
} }
@@ -47,7 +49,7 @@ func (s *server) StreamLogs(in *pb.StreamLogsRequest, out pb.AgentService_Stream
since = in.Since.AsTime() since = in.Since.AsTime()
} }
c, err := s.store.FindContainer(in.ContainerId, container.ContainerFilter{}) c, err := s.store.FindContainer(in.ContainerId, container.ContainerLabels{})
if err != nil { if err != nil {
return err return err
} }
@@ -57,7 +59,8 @@ func (s *server) StreamLogs(in *pb.StreamLogsRequest, out pb.AgentService_Stream
return err return err
} }
g := container.NewEventGenerator(out.Context(), reader, c) dockerReader := docker.NewLogReader(reader, c.Tty)
g := container.NewEventGenerator(out.Context(), dockerReader, c)
for event := range g.Events { for event := range g.Events {
out.Send(&pb.StreamLogsResponse{ out.Send(&pb.StreamLogsResponse{
@@ -84,7 +87,8 @@ func (s *server) LogsBetweenDates(in *pb.LogsBetweenDatesRequest, out pb.AgentSe
return err return err
} }
g := container.NewEventGenerator(out.Context(), reader, c) dockerReader := docker.NewLogReader(reader, c.Tty)
g := container.NewEventGenerator(out.Context(), dockerReader, c)
for { for {
select { select {
@@ -173,14 +177,14 @@ func (s *server) StreamStats(in *pb.StreamStatsRequest, out pb.AgentService_Stre
} }
func (s *server) FindContainer(ctx context.Context, in *pb.FindContainerRequest) (*pb.FindContainerResponse, error) { func (s *server) FindContainer(ctx context.Context, in *pb.FindContainerRequest) (*pb.FindContainerResponse, error) {
filter := make(container.ContainerFilter) labels := make(container.ContainerLabels)
if in.GetFilter() != nil { if in.GetFilter() != nil {
for k, v := range in.GetFilter() { for k, v := range in.GetFilter() {
filter[k] = append(filter[k], v.GetValues()...) labels[k] = append(labels[k], v.GetValues()...)
} }
} }
container, err := s.store.FindContainer(in.ContainerId, filter) container, err := s.store.FindContainer(in.ContainerId, labels)
if err != nil { if err != nil {
return nil, status.Error(codes.NotFound, err.Error()) return nil, status.Error(codes.NotFound, err.Error())
} }
@@ -205,14 +209,14 @@ func (s *server) FindContainer(ctx context.Context, in *pb.FindContainerRequest)
} }
func (s *server) ListContainers(ctx context.Context, in *pb.ListContainersRequest) (*pb.ListContainersResponse, error) { func (s *server) ListContainers(ctx context.Context, in *pb.ListContainersRequest) (*pb.ListContainersResponse, error) {
filter := make(container.ContainerFilter) labels := make(container.ContainerLabels)
if in.GetFilter() != nil { if in.GetFilter() != nil {
for k, v := range in.GetFilter() { for k, v := range in.GetFilter() {
filter[k] = append(filter[k], v.GetValues()...) labels[k] = append(labels[k], v.GetValues()...)
} }
} }
containers, err := s.store.ListContainers(filter) containers, err := s.store.ListContainers(labels)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@@ -322,7 +326,7 @@ func (s *server) ContainerAction(ctx context.Context, in *pb.ContainerActionRequ
return &pb.ContainerActionResponse{}, nil return &pb.ContainerActionResponse{}, nil
} }
func NewServer(client container.Client, certificates tls.Certificate, dozzleVersion string, filter container.ContainerFilter) (*grpc.Server, error) { func NewServer(client container.Client, certificates tls.Certificate, dozzleVersion string, labels container.ContainerLabels) (*grpc.Server, error) {
caCertPool := x509.NewCertPool() caCertPool := x509.NewCertPool()
c, err := x509.ParseCertificate(certificates.Certificate[0]) c, err := x509.ParseCertificate(certificates.Certificate[0])
if err != nil { if err != nil {
@@ -341,7 +345,7 @@ func NewServer(client container.Client, certificates tls.Certificate, dozzleVers
creds := credentials.NewTLS(tlsConfig) creds := credentials.NewTLS(tlsConfig)
grpcServer := grpc.NewServer(grpc.Creds(creds)) grpcServer := grpc.NewServer(grpc.Creds(creds))
pb.RegisterAgentServiceServer(grpcServer, newServer(client, dozzleVersion, filter)) pb.RegisterAgentServiceServer(grpcServer, newServer(client, dozzleVersion, labels))
return grpcServer, nil return grpcServer, nil
} }

View File

@@ -24,7 +24,7 @@ type User struct {
Name string `json:"name" yaml:"name"` Name string `json:"name" yaml:"name"`
Password string `json:"-" yaml:"password"` Password string `json:"-" yaml:"password"`
Filter string `json:"-" yaml:"filter"` Filter string `json:"-" yaml:"filter"`
ContainerFilter container.ContainerFilter `json:"-" yaml:"-"` ContainerLabels container.ContainerLabels `json:"-" yaml:"-"`
} }
func (u User) AvatarURL() string { func (u User) AvatarURL() string {
@@ -35,12 +35,12 @@ func (u User) AvatarURL() string {
return fmt.Sprintf("https://gravatar.com/avatar/%s?d=https%%3A%%2F%%2Fui-avatars.com%%2Fapi%%2F/%s/128", hashEmail(u.Email), url.QueryEscape(name)) return fmt.Sprintf("https://gravatar.com/avatar/%s?d=https%%3A%%2F%%2Fui-avatars.com%%2Fapi%%2F/%s/128", hashEmail(u.Email), url.QueryEscape(name))
} }
func newUser(username, email, name string, filter container.ContainerFilter) User { func newUser(username, email, name string, labels container.ContainerLabels) User {
return User{ return User{
Username: username, Username: username,
Email: email, Email: email,
Name: name, Name: name,
ContainerFilter: filter, ContainerLabels: labels,
} }
} }
@@ -197,7 +197,7 @@ func UserFromContext(ctx context.Context) *User {
} }
email := claims["email"].(string) email := claims["email"].(string)
name := claims["name"].(string) name := claims["name"].(string)
containerFilter := container.ContainerFilter{} containerFilter := container.ContainerLabels{}
if filter, ok := claims["filter"].(string); ok { if filter, ok := claims["filter"].(string); ok {
containerFilter, err = container.ParseContainerFilter(filter) containerFilter, err = container.ParseContainerFilter(filter)
if err != nil { if err != nil {

View File

@@ -29,7 +29,7 @@ func (s StdType) String() string {
} }
type Client interface { type Client interface {
ListContainers(context.Context, ContainerFilter) ([]Container, error) ListContainers(context.Context, ContainerLabels) ([]Container, error)
FindContainer(context.Context, string) (Container, error) FindContainer(context.Context, string) (Container, error)
ContainerLogs(context.Context, string, time.Time, StdType) (io.ReadCloser, error) ContainerLogs(context.Context, string, time.Time, StdType) (io.ReadCloser, error)
ContainerEvents(context.Context, chan<- ContainerEvent) error ContainerEvents(context.Context, chan<- ContainerEvent) error
@@ -38,5 +38,4 @@ type Client interface {
Ping(context.Context) error Ping(context.Context) error
Host() Host Host() Host
ContainerActions(ctx context.Context, action ContainerAction, containerID string) error ContainerActions(ctx context.Context, action ContainerAction, containerID string) error
IsSwarmMode() bool
} }

View File

@@ -13,34 +13,40 @@ import (
"golang.org/x/sync/semaphore" "golang.org/x/sync/semaphore"
) )
type StatsCollector interface {
Start(parentCtx context.Context) bool
Subscribe(ctx context.Context, stats chan<- ContainerStat)
Stop()
}
type ContainerStore struct { type ContainerStore struct {
containers *xsync.MapOf[string, *Container] containers *xsync.MapOf[string, *Container]
subscribers *xsync.MapOf[context.Context, chan<- ContainerEvent] subscribers *xsync.MapOf[context.Context, chan<- ContainerEvent]
newContainerSubscribers *xsync.MapOf[context.Context, chan<- Container] newContainerSubscribers *xsync.MapOf[context.Context, chan<- Container]
client Client client Client
statsCollector *StatsCollector statsCollector StatsCollector
wg sync.WaitGroup wg sync.WaitGroup
connected atomic.Bool connected atomic.Bool
events chan ContainerEvent events chan ContainerEvent
ctx context.Context ctx context.Context
filter ContainerFilter labels ContainerLabels
} }
const defaultTimeout = 10 * time.Second const defaultTimeout = 10 * time.Second
func NewContainerStore(ctx context.Context, client Client, filter ContainerFilter) *ContainerStore { func NewContainerStore(ctx context.Context, client Client, statsCollect StatsCollector, labels ContainerLabels) *ContainerStore {
log.Debug().Str("host", client.Host().Name).Interface("filter", filter).Msg("initializing container store") log.Debug().Str("host", client.Host().Name).Interface("labels", labels).Msg("initializing container store")
s := &ContainerStore{ s := &ContainerStore{
containers: xsync.NewMapOf[string, *Container](), containers: xsync.NewMapOf[string, *Container](),
client: client, client: client,
subscribers: xsync.NewMapOf[context.Context, chan<- ContainerEvent](), subscribers: xsync.NewMapOf[context.Context, chan<- ContainerEvent](),
newContainerSubscribers: xsync.NewMapOf[context.Context, chan<- Container](), newContainerSubscribers: xsync.NewMapOf[context.Context, chan<- Container](),
statsCollector: NewStatsCollector(client, filter), statsCollector: statsCollect,
wg: sync.WaitGroup{}, wg: sync.WaitGroup{},
events: make(chan ContainerEvent), events: make(chan ContainerEvent),
ctx: ctx, ctx: ctx,
filter: filter, labels: labels,
} }
s.wg.Add(1) s.wg.Add(1)
@@ -68,7 +74,7 @@ func (s *ContainerStore) checkConnectivity() error {
ctx, cancel := context.WithTimeout(context.Background(), defaultTimeout) ctx, cancel := context.WithTimeout(context.Background(), defaultTimeout)
defer cancel() defer cancel()
if containers, err := s.client.ListContainers(ctx, s.filter); err != nil { if containers, err := s.client.ListContainers(ctx, s.labels); err != nil {
return err return err
} else { } else {
s.containers.Clear() s.containers.Clear()
@@ -109,7 +115,7 @@ func (s *ContainerStore) checkConnectivity() error {
return nil return nil
} }
func (s *ContainerStore) ListContainers(filter ContainerFilter) ([]Container, error) { func (s *ContainerStore) ListContainers(labels ContainerLabels) ([]Container, error) {
s.wg.Wait() s.wg.Wait()
if err := s.checkConnectivity(); err != nil { if err := s.checkConnectivity(); err != nil {
@@ -117,8 +123,8 @@ func (s *ContainerStore) ListContainers(filter ContainerFilter) ([]Container, er
} }
containers := make([]Container, 0) containers := make([]Container, 0)
if filter.Exists() { if labels.Exists() {
validContainers, err := s.client.ListContainers(s.ctx, filter) validContainers, err := s.client.ListContainers(s.ctx, labels)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@@ -143,11 +149,10 @@ func (s *ContainerStore) ListContainers(filter ContainerFilter) ([]Container, er
return containers, nil return containers, nil
} }
func (s *ContainerStore) FindContainer(id string, filter ContainerFilter) (Container, error) { func (s *ContainerStore) FindContainer(id string, labels ContainerLabels) (Container, error) {
s.wg.Wait() s.wg.Wait()
if labels.Exists() {
if filter.Exists() { validContainers, err := s.client.ListContainers(s.ctx, labels)
validContainers, err := s.client.ListContainers(s.ctx, filter)
if err != nil { if err != nil {
return Container{}, err return Container{}, err
} }
@@ -245,11 +250,36 @@ func (s *ContainerStore) init() {
case event := <-s.events: case event := <-s.events:
log.Trace().Str("event", event.Name).Str("id", event.ActorID).Msg("received container event") log.Trace().Str("event", event.Name).Str("id", event.ActorID).Msg("received container event")
switch event.Name { switch event.Name {
case "create":
ctx, cancel := context.WithTimeout(context.Background(), 3*time.Second)
if container, err := s.client.FindContainer(ctx, event.ActorID); err == nil {
list, _ := s.client.ListContainers(ctx, s.labels)
// make sure the container is in the list of containers when using filter
valid := lo.ContainsBy(list, func(item Container) bool {
return item.ID == container.ID
})
if valid {
log.Debug().Str("id", container.ID).Msg("container started")
s.containers.Store(container.ID, &container)
s.newContainerSubscribers.Range(func(c context.Context, containers chan<- Container) bool {
select {
case containers <- container:
case <-c.Done():
}
return true
})
}
}
cancel()
case "start": case "start":
ctx, cancel := context.WithTimeout(context.Background(), defaultTimeout) ctx, cancel := context.WithTimeout(context.Background(), defaultTimeout)
if container, err := s.client.FindContainer(ctx, event.ActorID); err == nil { if container, err := s.client.FindContainer(ctx, event.ActorID); err == nil {
list, _ := s.client.ListContainers(ctx, s.filter) list, _ := s.client.ListContainers(ctx, s.labels)
// make sure the container is in the list of containers when using filter // make sure the container is in the list of containers when using filter
valid := lo.ContainsBy(list, func(item Container) bool { valid := lo.ContainsBy(list, func(item Container) bool {
@@ -273,6 +303,43 @@ func (s *ContainerStore) init() {
log.Debug().Str("id", event.ActorID).Msg("container destroyed") log.Debug().Str("id", event.ActorID).Msg("container destroyed")
s.containers.Delete(event.ActorID) s.containers.Delete(event.ActorID)
case "update":
s.containers.Compute(event.ActorID, func(c *Container, loaded bool) (*Container, bool) {
if loaded {
log.Debug().Str("id", c.ID).Msg("container updated")
started := false
if newContainer, err := s.client.FindContainer(context.Background(), c.ID); err == nil {
if newContainer.State == "running" && c.State != "running" {
started = true
}
c.Name = newContainer.Name
c.State = newContainer.State
c.Labels = newContainer.Labels
c.StartedAt = newContainer.StartedAt
c.FinishedAt = newContainer.FinishedAt
c.Created = newContainer.Created
} else {
log.Error().Err(err).Str("id", c.ID).Msg("failed to update container")
}
if started {
s.subscribers.Range(func(ctx context.Context, events chan<- ContainerEvent) bool {
select {
case events <- ContainerEvent{
Name: "start",
ActorID: c.ID,
}:
case <-ctx.Done():
s.subscribers.Delete(ctx)
}
return true
})
}
return c, false
} else {
return c, true
}
})
case "die": case "die":
s.containers.Compute(event.ActorID, func(c *Container, loaded bool) (*Container, bool) { s.containers.Compute(event.ActorID, func(c *Container, loaded bool) (*Container, bool) {
if loaded { if loaded {

View File

@@ -14,7 +14,7 @@ type mockedClient struct {
Client Client
} }
func (m *mockedClient) ListContainers(ctx context.Context, filter ContainerFilter) ([]Container, error) { func (m *mockedClient) ListContainers(ctx context.Context, filter ContainerLabels) ([]Container, error) {
args := m.Called(ctx, filter) args := m.Called(ctx, filter)
return args.Get(0).([]Container), args.Error(1) return args.Get(0).([]Container), args.Error(1)
} }
@@ -65,58 +65,15 @@ func TestContainerStore_List(t *testing.T) {
ctx, cancel := context.WithCancel(context.Background()) ctx, cancel := context.WithCancel(context.Background())
t.Cleanup(cancel) t.Cleanup(cancel)
collector := &fakeStatsCollector{}
store := NewContainerStore(ctx, client, ContainerFilter{}) store := NewContainerStore(ctx, client, collector, ContainerLabels{})
containers, _ := store.ListContainers(ContainerFilter{}) containers, _ := store.ListContainers(ContainerLabels{})
assert.Equal(t, containers[0].ID, "1234") assert.Equal(t, containers[0].ID, "1234")
} }
//TODO fix this test type fakeStatsCollector struct{}
// func TestContainerStore_die(t *testing.T) {
// client := new(mockedClient)
// client.On("ListContainers", mock.Anything, mock.Anything).Return([]Container{
// {
// ID: "1234",
// Name: "test",
// State: "running",
// Stats: utils.NewRingBuffer[ContainerStat](300),
// },
// }, nil)
// client.On("ContainerEvents", mock.Anything, mock.AnythingOfType("chan<- container.ContainerEvent")).Return(nil). func (f *fakeStatsCollector) Subscribe(_ context.Context, _ chan<- ContainerStat) {}
// Run(func(args mock.Arguments) { func (f *fakeStatsCollector) Start(_ context.Context) bool { return true }
// ctx := args.Get(0).(context.Context) func (f *fakeStatsCollector) Stop() {}
// events := args.Get(1).(chan<- ContainerEvent)
// events <- ContainerEvent{
// Name: "die",
// ActorID: "1234",
// Host: "localhost",
// }
// <-ctx.Done()
// })
// client.On("Host").Return(Host{
// ID: "localhost",
// })
// client.On("ContainerStats", mock.Anything, "1234", mock.AnythingOfType("chan<- container.ContainerStat")).Return(nil)
// client.On("FindContainer", mock.Anything, "1234").Return(Container{
// ID: "1234",
// Name: "test",
// Image: "test",
// Stats: utils.NewRingBuffer[ContainerStat](300),
// }, nil)
// ctx, cancel := context.WithCancel(context.Background())
// t.Cleanup(cancel)
// store := NewContainerStore(ctx, client, ContainerFilter{})
// // Wait until we get the event
// events := make(chan ContainerEvent)
// store.SubscribeEvents(ctx, events)
// <-events
// containers, _ := store.ListContainers(ContainerFilter{})
// assert.Equal(t, containers[0].State, "exited")
// }

View File

@@ -1,14 +1,10 @@
package container package container
import ( import (
"bufio"
"bytes"
"context" "context"
"encoding/binary"
"errors" "errors"
"fmt" "fmt"
"hash/fnv" "hash/fnv"
"io"
"strings" "strings"
"sync" "sync"
"time" "time"
@@ -23,30 +19,26 @@ import (
type EventGenerator struct { type EventGenerator struct {
Events chan *LogEvent Events chan *LogEvent
Errors chan error Errors chan error
reader *bufio.Reader reader LogReader
next *LogEvent next *LogEvent
buffer chan *LogEvent buffer chan *LogEvent
tty bool
wg sync.WaitGroup wg sync.WaitGroup
containerID string containerID string
ctx context.Context ctx context.Context
} }
var bufPool = sync.Pool{
New: func() any {
return new(bytes.Buffer)
},
}
var ErrBadHeader = fmt.Errorf("dozzle/docker: unable to read header") var ErrBadHeader = fmt.Errorf("dozzle/docker: unable to read header")
func NewEventGenerator(ctx context.Context, reader io.Reader, container Container) *EventGenerator { type LogReader interface {
Read() (string, StdType, error)
}
func NewEventGenerator(ctx context.Context, reader LogReader, container Container) *EventGenerator {
generator := &EventGenerator{ generator := &EventGenerator{
reader: bufio.NewReader(reader), reader: reader,
buffer: make(chan *LogEvent, 100), buffer: make(chan *LogEvent, 100),
Errors: make(chan error, 1), Errors: make(chan error, 1),
Events: make(chan *LogEvent), Events: make(chan *LogEvent),
tty: container.Tty,
containerID: container.ID, containerID: container.ID,
ctx: ctx, ctx: ctx,
} }
@@ -90,7 +82,7 @@ loop:
func (g *EventGenerator) consumeReader() { func (g *EventGenerator) consumeReader() {
for { for {
message, streamType, readerError := readEvent(g.reader, g.tty) message, streamType, readerError := g.reader.Read()
if message != "" { if message != "" {
logEvent := createEvent(message, streamType) logEvent := createEvent(message, streamType)
logEvent.ContainerID = g.containerID logEvent.ContainerID = g.containerID
@@ -123,50 +115,6 @@ func (g *EventGenerator) peek() *LogEvent {
} }
} }
func readEvent(reader *bufio.Reader, tty bool) (string, StdType, error) {
header := []byte{0, 0, 0, 0, 0, 0, 0, 0}
buffer := bufPool.Get().(*bytes.Buffer)
buffer.Reset()
defer bufPool.Put(buffer)
var streamType StdType = STDOUT
if tty {
message, err := reader.ReadString('\n')
if err != nil {
return message, streamType, err
}
return message, streamType, nil
} else {
n, err := io.ReadFull(reader, header)
if err != nil {
return "", streamType, err
}
if n != 8 {
log.Warn().Bytes("header", header).Msg("short read")
message, _ := reader.ReadString('\n')
return message, streamType, ErrBadHeader
}
switch header[0] {
case 1:
streamType = STDOUT
case 2:
streamType = STDERR
default:
log.Warn().Bytes("header", header).Msg("unknown stream type")
}
count := binary.BigEndian.Uint32(header[4:])
if count == 0 {
return "", streamType, nil
}
_, err = io.CopyN(buffer, reader, int64(count))
if err != nil {
return "", streamType, err
}
return buffer.String(), streamType, nil
}
}
func createEvent(message string, streamType StdType) *LogEvent { func createEvent(message string, streamType StdType) *LogEvent {
h := fnv.New32a() h := fnv.New32a()
h.Write([]byte(message)) h.Write([]byte(message))

View File

@@ -1,12 +1,9 @@
package container package container
import ( import (
"bufio"
"bytes"
"context" "context"
"encoding/binary" "io"
"reflect" "reflect"
"strings"
"sync" "sync"
"testing" "testing"
"time" "time"
@@ -18,9 +15,8 @@ import (
func TestEventGenerator_Events_tty(t *testing.T) { func TestEventGenerator_Events_tty(t *testing.T) {
input := "example input" input := "example input"
reader := bufio.NewReader(strings.NewReader(input))
g := NewEventGenerator(context.Background(), reader, Container{Tty: true}) g := NewEventGenerator(context.Background(), makeFakeReader(input, STDOUT), Container{Tty: true})
event := <-g.Events event := <-g.Events
require.NotNil(t, event, "Expected event to not be nil, but got nil") require.NotNil(t, event, "Expected event to not be nil, but got nil")
@@ -29,9 +25,8 @@ func TestEventGenerator_Events_tty(t *testing.T) {
func TestEventGenerator_Events_non_tty(t *testing.T) { func TestEventGenerator_Events_non_tty(t *testing.T) {
input := "example input" input := "example input"
reader := bytes.NewReader(makeMessage(input, STDOUT))
g := NewEventGenerator(context.Background(), reader, Container{Tty: false}) g := NewEventGenerator(context.Background(), makeFakeReader(input, STDOUT), Container{Tty: false})
event := <-g.Events event := <-g.Events
require.NotNil(t, event, "Expected event to not be nil, but got nil") require.NotNil(t, event, "Expected event to not be nil, but got nil")
@@ -40,9 +35,8 @@ func TestEventGenerator_Events_non_tty(t *testing.T) {
func TestEventGenerator_Events_non_tty_close_channel(t *testing.T) { func TestEventGenerator_Events_non_tty_close_channel(t *testing.T) {
input := "example input" input := "example input"
reader := bytes.NewReader(makeMessage(input, STDOUT))
g := NewEventGenerator(context.Background(), reader, Container{Tty: false}) g := NewEventGenerator(context.Background(), makeFakeReader(input, STDOUT), Container{Tty: false})
<-g.Events <-g.Events
_, ok := <-g.Events _, ok := <-g.Events
@@ -51,20 +45,31 @@ func TestEventGenerator_Events_non_tty_close_channel(t *testing.T) {
func TestEventGenerator_Events_routines_done(t *testing.T) { func TestEventGenerator_Events_routines_done(t *testing.T) {
input := "example input" input := "example input"
reader := bytes.NewReader(makeMessage(input, STDOUT))
g := NewEventGenerator(context.Background(), reader, Container{Tty: false}) g := NewEventGenerator(context.Background(), makeFakeReader(input, STDOUT), Container{Tty: false})
<-g.Events <-g.Events
assert.False(t, waitTimeout(&g.wg, 1*time.Second), "Expected routines to be done") assert.False(t, waitTimeout(&g.wg, 1*time.Second), "Expected routines to be done")
} }
func makeMessage(message string, stream StdType) []byte { type mockLogReader struct {
data := make([]byte, 8) messages []string
binary.BigEndian.PutUint32(data[4:], uint32(len(message))) types []StdType
data[0] = byte(stream / 2) i int
data = append(data, []byte(message)...) }
return data func (m *mockLogReader) Read() (string, StdType, error) {
if m.i >= len(m.messages) {
return "", 0, io.EOF
}
m.i++
return m.messages[m.i-1], m.types[m.i-1], nil
}
func makeFakeReader(message string, stream StdType) LogReader {
return &mockLogReader{
messages: []string{message},
types: []StdType{stream},
}
} }
func waitTimeout(wg *sync.WaitGroup, timeout time.Duration) bool { func waitTimeout(wg *sync.WaitGroup, timeout time.Duration) bool {
@@ -156,23 +161,3 @@ func Test_createEvent(t *testing.T) {
}) })
} }
} }
type mockReadCloser struct {
bytes []byte
}
func (m mockReadCloser) Read(p []byte) (int, error) {
return copy(p, m.bytes), nil
}
func Benchmark_readEvent(b *testing.B) {
b.ReportAllocs()
data := makeMessage("2020-05-13T18:55:37.772853839Z {\"key\": \"value\"}\n", STDOUT)
reader := bufio.NewReader(mockReadCloser{bytes: data})
for i := 0; i < b.N; i++ {
readEvent(reader, true)
}
}

View File

@@ -44,10 +44,10 @@ type ContainerEvent struct {
Time time.Time `json:"time"` Time time.Time `json:"time"`
} }
type ContainerFilter map[string][]string type ContainerLabels map[string][]string
func ParseContainerFilter(commaValues string) (ContainerFilter, error) { func ParseContainerFilter(commaValues string) (ContainerLabels, error) {
filter := make(ContainerFilter) filter := make(ContainerLabels)
if commaValues == "" { if commaValues == "" {
return filter, nil return filter, nil
} }
@@ -65,7 +65,7 @@ func ParseContainerFilter(commaValues string) (ContainerFilter, error) {
return filter, nil return filter, nil
} }
func (f ContainerFilter) Exists() bool { func (f ContainerLabels) Exists() bool {
return len(f) > 0 return len(f) > 0
} }

View File

@@ -17,7 +17,6 @@ import (
docker "github.com/docker/docker/api/types/container" docker "github.com/docker/docker/api/types/container"
"github.com/docker/docker/api/types/events" "github.com/docker/docker/api/types/events"
"github.com/docker/docker/api/types/filters" "github.com/docker/docker/api/types/filters"
"github.com/docker/docker/api/types/swarm"
"github.com/docker/docker/api/types/system" "github.com/docker/docker/api/types/system"
"github.com/docker/docker/client" "github.com/docker/docker/client"
@@ -37,13 +36,13 @@ type DockerCLI interface {
Info(ctx context.Context) (system.Info, error) Info(ctx context.Context) (system.Info, error)
} }
type httpClient struct { type DockerClient struct {
cli DockerCLI cli DockerCLI
host container.Host host container.Host
info system.Info info system.Info
} }
func NewClient(cli DockerCLI, host container.Host) container.Client { func NewClient(cli DockerCLI, host container.Host) *DockerClient {
info, err := cli.Info(context.Background()) info, err := cli.Info(context.Background())
if err != nil { if err != nil {
log.Error().Err(err).Msg("Failed to get docker info") log.Error().Err(err).Msg("Failed to get docker info")
@@ -60,7 +59,7 @@ func NewClient(cli DockerCLI, host container.Host) container.Client {
host.DockerVersion = info.ServerVersion host.DockerVersion = info.ServerVersion
host.Swarm = info.Swarm.NodeID != "" host.Swarm = info.Swarm.NodeID != ""
return &httpClient{ return &DockerClient{
cli: cli, cli: cli,
host: host, host: host,
info: info, info: info,
@@ -68,7 +67,7 @@ func NewClient(cli DockerCLI, host container.Host) container.Client {
} }
// NewClientWithFilters creates a new instance of Client with docker filters // NewClientWithFilters creates a new instance of Client with docker filters
func NewLocalClient(hostname string) (container.Client, error) { func NewLocalClient(hostname string) (*DockerClient, error) {
cli, err := client.NewClientWithOpts(client.FromEnv, client.WithAPIVersionNegotiation(), client.WithUserAgent("Docker-Client/Dozzle")) cli, err := client.NewClientWithOpts(client.FromEnv, client.WithAPIVersionNegotiation(), client.WithUserAgent("Docker-Client/Dozzle"))
if err != nil { if err != nil {
@@ -93,7 +92,7 @@ func NewLocalClient(hostname string) (container.Client, error) {
return NewClient(cli, host), nil return NewClient(cli, host), nil
} }
func NewRemoteClient(host container.Host) (container.Client, error) { func NewRemoteClient(host container.Host) (*DockerClient, error) {
if host.URL.Scheme != "tcp" { if host.URL.Scheme != "tcp" {
return nil, fmt.Errorf("invalid scheme: %s", host.URL.Scheme) return nil, fmt.Errorf("invalid scheme: %s", host.URL.Scheme)
} }
@@ -123,7 +122,7 @@ func NewRemoteClient(host container.Host) (container.Client, error) {
} }
// Finds a container by id, skipping the filters // Finds a container by id, skipping the filters
func (d *httpClient) FindContainer(ctx context.Context, id string) (container.Container, error) { func (d *DockerClient) FindContainer(ctx context.Context, id string) (container.Container, error) {
log.Debug().Str("id", id).Msg("Finding container") log.Debug().Str("id", id).Msg("Finding container")
if json, err := d.cli.ContainerInspect(ctx, id); err == nil { if json, err := d.cli.ContainerInspect(ctx, id); err == nil {
return newContainerFromJSON(json, d.host.ID), nil return newContainerFromJSON(json, d.host.ID), nil
@@ -133,7 +132,7 @@ func (d *httpClient) FindContainer(ctx context.Context, id string) (container.Co
} }
func (d *httpClient) ContainerActions(ctx context.Context, action container.ContainerAction, containerID string) error { func (d *DockerClient) ContainerActions(ctx context.Context, action container.ContainerAction, containerID string) error {
switch action { switch action {
case container.Start: case container.Start:
return d.cli.ContainerStart(ctx, containerID, docker.StartOptions{}) return d.cli.ContainerStart(ctx, containerID, docker.StartOptions{})
@@ -146,10 +145,10 @@ func (d *httpClient) ContainerActions(ctx context.Context, action container.Cont
} }
} }
func (d *httpClient) ListContainers(ctx context.Context, filter container.ContainerFilter) ([]container.Container, error) { func (d *DockerClient) ListContainers(ctx context.Context, labels container.ContainerLabels) ([]container.Container, error) {
log.Debug().Interface("filter", filter).Str("host", d.host.Name).Msg("Listing containers") log.Debug().Interface("labels", labels).Str("host", d.host.Name).Msg("Listing containers")
filterArgs := filters.NewArgs() filterArgs := filters.NewArgs()
for key, values := range filter { for key, values := range labels {
for _, value := range values { for _, value := range values {
filterArgs.Add(key, value) filterArgs.Add(key, value)
} }
@@ -175,7 +174,7 @@ func (d *httpClient) ListContainers(ctx context.Context, filter container.Contai
return containers, nil return containers, nil
} }
func (d *httpClient) ContainerStats(ctx context.Context, id string, stats chan<- container.ContainerStat) error { func (d *DockerClient) ContainerStats(ctx context.Context, id string, stats chan<- container.ContainerStat) error {
response, err := d.cli.ContainerStats(ctx, id, true) response, err := d.cli.ContainerStats(ctx, id, true)
if err != nil { if err != nil {
@@ -225,7 +224,7 @@ func (d *httpClient) ContainerStats(ctx context.Context, id string, stats chan<-
} }
} }
func (d *httpClient) ContainerLogs(ctx context.Context, id string, since time.Time, stdType container.StdType) (io.ReadCloser, error) { func (d *DockerClient) ContainerLogs(ctx context.Context, id string, since time.Time, stdType container.StdType) (io.ReadCloser, error) {
log.Debug().Str("id", id).Time("since", since).Stringer("stdType", stdType).Str("host", d.host.Name).Msg("Streaming logs for container") log.Debug().Str("id", id).Time("since", since).Stringer("stdType", stdType).Str("host", d.host.Name).Msg("Streaming logs for container")
sinceQuery := since.Add(-50 * time.Millisecond).Format(time.RFC3339Nano) sinceQuery := since.Add(-50 * time.Millisecond).Format(time.RFC3339Nano)
@@ -246,7 +245,7 @@ func (d *httpClient) ContainerLogs(ctx context.Context, id string, since time.Ti
return reader, nil return reader, nil
} }
func (d *httpClient) ContainerEvents(ctx context.Context, messages chan<- container.ContainerEvent) error { func (d *DockerClient) ContainerEvents(ctx context.Context, messages chan<- container.ContainerEvent) error {
dockerMessages, err := d.cli.Events(ctx, events.ListOptions{}) dockerMessages, err := d.cli.Events(ctx, events.ListOptions{})
for { for {
@@ -270,7 +269,7 @@ func (d *httpClient) ContainerEvents(ctx context.Context, messages chan<- contai
} }
} }
func (d *httpClient) ContainerLogsBetweenDates(ctx context.Context, id string, from time.Time, to time.Time, stdType container.StdType) (io.ReadCloser, error) { func (d *DockerClient) ContainerLogsBetweenDates(ctx context.Context, id string, from time.Time, to time.Time, stdType container.StdType) (io.ReadCloser, error) {
log.Debug().Str("id", id).Time("from", from).Time("to", to).Stringer("stdType", stdType).Str("host", d.host.Name).Msg("Fetching logs between dates for container") log.Debug().Str("id", id).Time("from", from).Time("to", to).Stringer("stdType", stdType).Str("host", d.host.Name).Msg("Fetching logs between dates for container")
options := docker.LogsOptions{ options := docker.LogsOptions{
ShowStdout: stdType&container.STDOUT != 0, ShowStdout: stdType&container.STDOUT != 0,
@@ -288,20 +287,16 @@ func (d *httpClient) ContainerLogsBetweenDates(ctx context.Context, id string, f
return reader, nil return reader, nil
} }
func (d *httpClient) Ping(ctx context.Context) error { func (d *DockerClient) Ping(ctx context.Context) error {
_, err := d.cli.Ping(ctx) _, err := d.cli.Ping(ctx)
return err return err
} }
func (d *httpClient) Host() container.Host { func (d *DockerClient) Host() container.Host {
log.Debug().Str("host", d.host.Name).Msg("Fetching host") log.Debug().Str("host", d.host.Name).Msg("Fetching host")
return d.host return d.host
} }
func (d *httpClient) IsSwarmMode() bool {
return d.info.Swarm.LocalNodeState != swarm.LocalNodeStateInactive
}
func newContainer(c types.Container, host string) container.Container { func newContainer(c types.Container, host string) container.Container {
name := "no name" name := "no name"
if c.Labels["dev.dozzle.name"] != "" { if c.Labels["dev.dozzle.name"] != "" {

View File

@@ -90,9 +90,9 @@ func (m *mockedProxy) ContainerRestart(ctx context.Context, containerID string,
func Test_dockerClient_ListContainers_null(t *testing.T) { func Test_dockerClient_ListContainers_null(t *testing.T) {
proxy := new(mockedProxy) proxy := new(mockedProxy)
proxy.On("ContainerList", mock.Anything, mock.Anything).Return(nil, nil) proxy.On("ContainerList", mock.Anything, mock.Anything).Return(nil, nil)
client := &httpClient{proxy, container.Host{ID: "localhost"}, system.Info{}} client := &DockerClient{proxy, container.Host{ID: "localhost"}, system.Info{}}
list, err := client.ListContainers(context.Background(), container.ContainerFilter{}) list, err := client.ListContainers(context.Background(), container.ContainerLabels{})
assert.Empty(t, list, "list should be empty") assert.Empty(t, list, "list should be empty")
require.NoError(t, err, "error should not return an error.") require.NoError(t, err, "error should not return an error.")
@@ -102,9 +102,9 @@ func Test_dockerClient_ListContainers_null(t *testing.T) {
func Test_dockerClient_ListContainers_error(t *testing.T) { func Test_dockerClient_ListContainers_error(t *testing.T) {
proxy := new(mockedProxy) proxy := new(mockedProxy)
proxy.On("ContainerList", mock.Anything, mock.Anything).Return(nil, errors.New("test")) proxy.On("ContainerList", mock.Anything, mock.Anything).Return(nil, errors.New("test"))
client := &httpClient{proxy, container.Host{ID: "localhost"}, system.Info{}} client := &DockerClient{proxy, container.Host{ID: "localhost"}, system.Info{}}
list, err := client.ListContainers(context.Background(), container.ContainerFilter{}) list, err := client.ListContainers(context.Background(), container.ContainerLabels{})
assert.Nil(t, list, "list should be nil") assert.Nil(t, list, "list should be nil")
require.Error(t, err, "test.") require.Error(t, err, "test.")
@@ -125,9 +125,9 @@ func Test_dockerClient_ListContainers_happy(t *testing.T) {
proxy := new(mockedProxy) proxy := new(mockedProxy)
proxy.On("ContainerList", mock.Anything, mock.Anything).Return(containers, nil) proxy.On("ContainerList", mock.Anything, mock.Anything).Return(containers, nil)
client := &httpClient{proxy, container.Host{ID: "localhost"}, system.Info{}} client := &DockerClient{proxy, container.Host{ID: "localhost"}, system.Info{}}
list, err := client.ListContainers(context.Background(), container.ContainerFilter{}) list, err := client.ListContainers(context.Background(), container.ContainerLabels{})
require.NoError(t, err, "error should not return an error.") require.NoError(t, err, "error should not return an error.")
Ids := []string{"1234567890_a", "abcdefghijkl"} Ids := []string{"1234567890_a", "abcdefghijkl"}
@@ -159,7 +159,7 @@ func Test_dockerClient_ContainerLogs_happy(t *testing.T) {
Since: "2020-12-31T23:59:59.95Z"} Since: "2020-12-31T23:59:59.95Z"}
proxy.On("ContainerLogs", mock.Anything, id, options).Return(reader, nil) proxy.On("ContainerLogs", mock.Anything, id, options).Return(reader, nil)
client := &httpClient{proxy, container.Host{ID: "localhost"}, system.Info{}} client := &DockerClient{proxy, container.Host{ID: "localhost"}, system.Info{}}
logReader, _ := client.ContainerLogs(context.Background(), id, since, container.STDALL) logReader, _ := client.ContainerLogs(context.Background(), id, since, container.STDALL)
actual, _ := io.ReadAll(logReader) actual, _ := io.ReadAll(logReader)
@@ -173,7 +173,7 @@ func Test_dockerClient_ContainerLogs_error(t *testing.T) {
proxy.On("ContainerLogs", mock.Anything, id, mock.Anything).Return(nil, errors.New("test")) proxy.On("ContainerLogs", mock.Anything, id, mock.Anything).Return(nil, errors.New("test"))
client := &httpClient{proxy, container.Host{ID: "localhost"}, system.Info{}} client := &DockerClient{proxy, container.Host{ID: "localhost"}, system.Info{}}
reader, err := client.ContainerLogs(context.Background(), id, time.Time{}, container.STDALL) reader, err := client.ContainerLogs(context.Background(), id, time.Time{}, container.STDALL)
@@ -189,7 +189,7 @@ func Test_dockerClient_FindContainer_happy(t *testing.T) {
json := types.ContainerJSON{ContainerJSONBase: &types.ContainerJSONBase{ID: "abcdefghijklmnopqrst", State: state}, Config: &docker.Config{Tty: false}} json := types.ContainerJSON{ContainerJSONBase: &types.ContainerJSONBase{ID: "abcdefghijklmnopqrst", State: state}, Config: &docker.Config{Tty: false}}
proxy.On("ContainerInspect", mock.Anything, "abcdefghijkl").Return(json, nil) proxy.On("ContainerInspect", mock.Anything, "abcdefghijkl").Return(json, nil)
client := &httpClient{proxy, container.Host{ID: "localhost"}, system.Info{}} client := &DockerClient{proxy, container.Host{ID: "localhost"}, system.Info{}}
container, err := client.FindContainer(context.Background(), "abcdefghijkl") container, err := client.FindContainer(context.Background(), "abcdefghijkl")
require.NoError(t, err, "error should not be thrown") require.NoError(t, err, "error should not be thrown")
@@ -202,7 +202,7 @@ func Test_dockerClient_FindContainer_happy(t *testing.T) {
func Test_dockerClient_FindContainer_error(t *testing.T) { func Test_dockerClient_FindContainer_error(t *testing.T) {
proxy := new(mockedProxy) proxy := new(mockedProxy)
proxy.On("ContainerInspect", mock.Anything, "not_valid").Return(types.ContainerJSON{}, errors.New("not found")) proxy.On("ContainerInspect", mock.Anything, "not_valid").Return(types.ContainerJSON{}, errors.New("not found"))
client := &httpClient{proxy, container.Host{ID: "localhost"}, system.Info{}} client := &DockerClient{proxy, container.Host{ID: "localhost"}, system.Info{}}
_, err := client.FindContainer(context.Background(), "not_valid") _, err := client.FindContainer(context.Background(), "not_valid")
require.Error(t, err, "error should be thrown") require.Error(t, err, "error should be thrown")
@@ -212,7 +212,7 @@ func Test_dockerClient_FindContainer_error(t *testing.T) {
func Test_dockerClient_ContainerActions_happy(t *testing.T) { func Test_dockerClient_ContainerActions_happy(t *testing.T) {
proxy := new(mockedProxy) proxy := new(mockedProxy)
client := &httpClient{proxy, container.Host{ID: "localhost"}, system.Info{}} client := &DockerClient{proxy, container.Host{ID: "localhost"}, system.Info{}}
state := &types.ContainerState{Status: "running", StartedAt: time.Now().Format(time.RFC3339Nano)} state := &types.ContainerState{Status: "running", StartedAt: time.Now().Format(time.RFC3339Nano)}
json := types.ContainerJSON{ContainerJSONBase: &types.ContainerJSONBase{ID: "abcdefghijkl", State: state}, Config: &docker.Config{Tty: false}} json := types.ContainerJSON{ContainerJSONBase: &types.ContainerJSONBase{ID: "abcdefghijkl", State: state}, Config: &docker.Config{Tty: false}}
@@ -240,7 +240,7 @@ func Test_dockerClient_ContainerActions_happy(t *testing.T) {
func Test_dockerClient_ContainerActions_error(t *testing.T) { func Test_dockerClient_ContainerActions_error(t *testing.T) {
proxy := new(mockedProxy) proxy := new(mockedProxy)
client := &httpClient{proxy, container.Host{ID: "localhost"}, system.Info{}} client := &DockerClient{proxy, container.Host{ID: "localhost"}, system.Info{}}
proxy.On("ContainerInspect", mock.Anything, "random-id").Return(types.ContainerJSON{}, errors.New("not found")) proxy.On("ContainerInspect", mock.Anything, "random-id").Return(types.ContainerJSON{}, errors.New("not found"))
proxy.On("ContainerStart", mock.Anything, mock.Anything, mock.Anything).Return(errors.New("test")) proxy.On("ContainerStart", mock.Anything, mock.Anything, mock.Anything).Return(errors.New("test"))
proxy.On("ContainerStop", mock.Anything, mock.Anything, mock.Anything).Return(errors.New("test")) proxy.On("ContainerStop", mock.Anything, mock.Anything, mock.Anything).Return(errors.New("test"))

View File

@@ -0,0 +1,101 @@
package docker
import (
"bufio"
"bytes"
"encoding/binary"
"errors"
"io"
"sync"
"github.com/amir20/dozzle/internal/container"
)
var ErrBadHeader = errors.New("bad header")
type StdType int
const (
stdout StdType = iota
stderr
)
type LogReader struct {
reader *bufio.Reader
tty bool
pool *sync.Pool
}
func NewLogReader(r io.Reader, tty bool) *LogReader {
return &LogReader{
reader: bufio.NewReader(r),
tty: tty,
pool: &sync.Pool{
New: func() interface{} {
return bytes.NewBuffer(make([]byte, 0, 4096))
},
},
}
}
func (d *LogReader) Read() (string, container.StdType, error) {
message, stdType, err := d.readEvent()
if err != nil {
return "", 0, err
}
var std container.StdType
switch stdType {
case stdout:
std = container.STDOUT
case stderr:
std = container.STDERR
}
return message, std, nil
}
func (d *LogReader) readEvent() (string, StdType, error) {
header := []byte{0, 0, 0, 0, 0, 0, 0, 0}
buffer := d.pool.Get().(*bytes.Buffer)
buffer.Reset()
defer d.pool.Put(buffer)
var streamType StdType = stdout
if d.tty {
message, err := d.reader.ReadString('\n')
if err != nil {
return message, streamType, err
}
return message, streamType, nil
} else {
n, err := io.ReadFull(d.reader, header)
if err != nil {
return "", streamType, err
}
if n != 8 {
message, _ := d.reader.ReadString('\n')
return message, streamType, ErrBadHeader
}
switch header[0] {
case 1:
streamType = stdout
case 2:
streamType = stderr
}
count := binary.BigEndian.Uint32(header[4:])
if count == 0 {
return "", streamType, nil
}
_, err = io.CopyN(buffer, d.reader, int64(count))
if err != nil {
return "", streamType, err
}
return buffer.String(), streamType, nil
}
}

View File

@@ -1,4 +1,4 @@
package container package docker
import ( import (
"context" "context"
@@ -8,35 +8,36 @@ import (
"sync/atomic" "sync/atomic"
"time" "time"
"github.com/amir20/dozzle/internal/container"
"github.com/puzpuzpuz/xsync/v3" "github.com/puzpuzpuz/xsync/v3"
"github.com/rs/zerolog/log" "github.com/rs/zerolog/log"
) )
type StatsCollector struct { type DockerStatsCollector struct {
stream chan ContainerStat stream chan container.ContainerStat
subscribers *xsync.MapOf[context.Context, chan<- ContainerStat] subscribers *xsync.MapOf[context.Context, chan<- container.ContainerStat]
client Client client container.Client
cancelers *xsync.MapOf[string, context.CancelFunc] cancelers *xsync.MapOf[string, context.CancelFunc]
stopper context.CancelFunc stopper context.CancelFunc
timer *time.Timer timer *time.Timer
mu sync.Mutex mu sync.Mutex
totalStarted atomic.Int32 totalStarted atomic.Int32
filter ContainerFilter labels container.ContainerLabels
} }
var timeToStop = 6 * time.Hour var timeToStop = 6 * time.Hour
func NewStatsCollector(client Client, filter ContainerFilter) *StatsCollector { func NewDockerStatsCollector(client container.Client, labels container.ContainerLabels) *DockerStatsCollector {
return &StatsCollector{ return &DockerStatsCollector{
stream: make(chan ContainerStat), stream: make(chan container.ContainerStat),
subscribers: xsync.NewMapOf[context.Context, chan<- ContainerStat](), subscribers: xsync.NewMapOf[context.Context, chan<- container.ContainerStat](),
client: client, client: client,
cancelers: xsync.NewMapOf[string, context.CancelFunc](), cancelers: xsync.NewMapOf[string, context.CancelFunc](),
filter: filter, labels: labels,
} }
} }
func (c *StatsCollector) Subscribe(ctx context.Context, stats chan<- ContainerStat) { func (c *DockerStatsCollector) Subscribe(ctx context.Context, stats chan<- container.ContainerStat) {
c.subscribers.Store(ctx, stats) c.subscribers.Store(ctx, stats)
go func() { go func() {
<-ctx.Done() <-ctx.Done()
@@ -44,7 +45,7 @@ func (c *StatsCollector) Subscribe(ctx context.Context, stats chan<- ContainerSt
}() }()
} }
func (c *StatsCollector) forceStop() { func (c *DockerStatsCollector) forceStop() {
c.mu.Lock() c.mu.Lock()
defer c.mu.Unlock() defer c.mu.Unlock()
if c.stopper != nil { if c.stopper != nil {
@@ -54,7 +55,7 @@ func (c *StatsCollector) forceStop() {
} }
} }
func (c *StatsCollector) Stop() { func (c *DockerStatsCollector) Stop() {
c.mu.Lock() c.mu.Lock()
defer c.mu.Unlock() defer c.mu.Unlock()
if c.totalStarted.Add(-1) == 0 { if c.totalStarted.Add(-1) == 0 {
@@ -64,7 +65,7 @@ func (c *StatsCollector) Stop() {
} }
} }
func (c *StatsCollector) reset() { func (c *DockerStatsCollector) reset() {
c.mu.Lock() c.mu.Lock()
defer c.mu.Unlock() defer c.mu.Unlock()
if c.timer != nil { if c.timer != nil {
@@ -73,7 +74,7 @@ func (c *StatsCollector) reset() {
c.timer = nil c.timer = nil
} }
func streamStats(parent context.Context, sc *StatsCollector, id string) { func streamStats(parent context.Context, sc *DockerStatsCollector, id string) {
ctx, cancel := context.WithCancel(parent) ctx, cancel := context.WithCancel(parent)
sc.cancelers.Store(id, cancel) sc.cancelers.Store(id, cancel)
log.Debug().Str("container", id).Str("host", sc.client.Host().Name).Msg("starting to stream stats") log.Debug().Str("container", id).Str("host", sc.client.Host().Name).Msg("starting to stream stats")
@@ -86,7 +87,7 @@ func streamStats(parent context.Context, sc *StatsCollector, id string) {
} }
// Start starts the stats collector and blocks until it's stopped. It returns true if the collector was stopped, false if it was already running // Start starts the stats collector and blocks until it's stopped. It returns true if the collector was stopped, false if it was already running
func (sc *StatsCollector) Start(parentCtx context.Context) bool { func (sc *DockerStatsCollector) Start(parentCtx context.Context) bool {
sc.reset() sc.reset()
sc.totalStarted.Add(1) sc.totalStarted.Add(1)
@@ -99,8 +100,8 @@ func (sc *StatsCollector) Start(parentCtx context.Context) bool {
ctx, sc.stopper = context.WithCancel(parentCtx) ctx, sc.stopper = context.WithCancel(parentCtx)
sc.mu.Unlock() sc.mu.Unlock()
timeoutCtx, cancel := context.WithTimeout(parentCtx, defaultTimeout) timeoutCtx, cancel := context.WithTimeout(parentCtx, 3*time.Second) // 3 seconds to list containers is hard limit
if containers, err := sc.client.ListContainers(timeoutCtx, sc.filter); err == nil { if containers, err := sc.client.ListContainers(timeoutCtx, sc.labels); err == nil {
for _, c := range containers { for _, c := range containers {
if c.State == "running" { if c.State == "running" {
go streamStats(ctx, sc, c.ID) go streamStats(ctx, sc, c.ID)
@@ -111,7 +112,7 @@ func (sc *StatsCollector) Start(parentCtx context.Context) bool {
} }
cancel() cancel()
events := make(chan ContainerEvent) events := make(chan container.ContainerEvent)
go func() { go func() {
log.Debug().Str("host", sc.client.Host().Name).Msg("starting to listen to docker events") log.Debug().Str("host", sc.client.Host().Name).Msg("starting to listen to docker events")
@@ -142,7 +143,7 @@ func (sc *StatsCollector) Start(parentCtx context.Context) bool {
log.Info().Str("host", sc.client.Host().Name).Msg("stopped container stats collector") log.Info().Str("host", sc.client.Host().Name).Msg("stopped container stats collector")
return true return true
case stat := <-sc.stream: case stat := <-sc.stream:
sc.subscribers.Range(func(c context.Context, stats chan<- ContainerStat) bool { sc.subscribers.Range(func(c context.Context, stats chan<- container.ContainerStat) bool {
select { select {
case stats <- stat: case stats <- stat:
case <-c.Done(): case <-c.Done():

View File

@@ -1,16 +1,47 @@
package container package docker
import ( import (
"context" "context"
"testing" "testing"
"github.com/amir20/dozzle/internal/container"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
"github.com/stretchr/testify/mock" "github.com/stretchr/testify/mock"
) )
func startedCollector(ctx context.Context) *StatsCollector { type mockedClient struct {
mock.Mock
container.Client
}
func (m *mockedClient) ListContainers(ctx context.Context, filter container.ContainerLabels) ([]container.Container, error) {
args := m.Called(ctx, filter)
return args.Get(0).([]container.Container), args.Error(1)
}
func (m *mockedClient) FindContainer(ctx context.Context, id string) (container.Container, error) {
args := m.Called(ctx, id)
return args.Get(0).(container.Container), args.Error(1)
}
func (m *mockedClient) ContainerEvents(ctx context.Context, events chan<- container.ContainerEvent) error {
args := m.Called(ctx, events)
return args.Error(0)
}
func (m *mockedClient) ContainerStats(ctx context.Context, id string, stats chan<- container.ContainerStat) error {
args := m.Called(ctx, id, stats)
return args.Error(0)
}
func (m *mockedClient) Host() container.Host {
args := m.Called()
return args.Get(0).(container.Host)
}
func startedCollector(ctx context.Context) *DockerStatsCollector {
client := new(mockedClient) client := new(mockedClient)
client.On("ListContainers", mock.Anything, mock.Anything).Return([]Container{ client.On("ListContainers", mock.Anything, mock.Anything).Return([]container.Container{
{ {
ID: "1234", ID: "1234",
Name: "test", Name: "test",
@@ -26,17 +57,17 @@ func startedCollector(ctx context.Context) *StatsCollector {
client.On("ContainerStats", mock.Anything, mock.Anything, mock.AnythingOfType("chan<- container.ContainerStat")). client.On("ContainerStats", mock.Anything, mock.Anything, mock.AnythingOfType("chan<- container.ContainerStat")).
Return(nil). Return(nil).
Run(func(args mock.Arguments) { Run(func(args mock.Arguments) {
stats := args.Get(2).(chan<- ContainerStat) stats := args.Get(2).(chan<- container.ContainerStat)
stats <- ContainerStat{ stats <- container.ContainerStat{
ID: "1234", ID: "1234",
} }
}) })
client.On("Host").Return(Host{ client.On("Host").Return(container.Host{
ID: "localhost", ID: "localhost",
}) })
collector := NewStatsCollector(client, ContainerFilter{}) collector := NewDockerStatsCollector(client, container.ContainerLabels{})
stats := make(chan ContainerStat) stats := make(chan container.ContainerStat)
collector.Subscribe(ctx, stats) collector.Subscribe(ctx, stats)

View File

@@ -14,7 +14,7 @@ func RPCRequest(ctx context.Context, addr string, certs tls.Certificate) error {
if err != nil { if err != nil {
log.Fatal().Err(err).Msg("Failed to create agent client") log.Fatal().Err(err).Msg("Failed to create agent client")
} }
containers, err := client.ListContainers(ctx, container.ContainerFilter{}) containers, err := client.ListContainers(ctx, container.ContainerLabels{})
log.Trace().Int("containers", len(containers)).Msg("Healtcheck RPC request completed") log.Trace().Int("containers", len(containers)).Msg("Healtcheck RPC request completed")
return err return err
} }

223
internal/k8s/client.go Normal file
View File

@@ -0,0 +1,223 @@
package k8s
import (
"context"
"fmt"
"io"
"strings"
"time"
"os"
"github.com/amir20/dozzle/internal/container"
"github.com/amir20/dozzle/internal/utils"
corev1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"github.com/rs/zerolog/log"
"k8s.io/client-go/kubernetes"
"k8s.io/client-go/rest"
"k8s.io/client-go/tools/clientcmd"
)
type K8sClient struct {
Clientset *kubernetes.Clientset
namespace string
config *rest.Config
}
func NewK8sClient(namespace string) (*K8sClient, error) {
var config *rest.Config
var err error
// Check if we're running in cluster
if os.Getenv("KUBERNETES_SERVICE_HOST") != "" {
config, err = rest.InClusterConfig()
if err != nil {
return nil, err
}
log.Info().Msg("Running in-cluster mode")
} else {
kubeconfig := os.Getenv("KUBECONFIG")
if kubeconfig == "" {
kubeconfig = os.Getenv("HOME") + "/.kube/config"
}
config, err = clientcmd.BuildConfigFromFlags("", kubeconfig)
if err != nil {
return nil, err
}
log.Info().Msgf("Running in local mode with kubeconfig: %s", kubeconfig)
}
clientset, err := kubernetes.NewForConfig(config)
if err != nil {
return nil, err
}
return &K8sClient{
Clientset: clientset,
namespace: namespace,
config: config,
}, nil
}
func (k *K8sClient) ListContainers(ctx context.Context, labels container.ContainerLabels) ([]container.Container, error) {
pods, err := k.Clientset.CoreV1().Pods(k.namespace).List(ctx, metav1.ListOptions{})
if err != nil {
return nil, err
}
var containers []container.Container
for _, pod := range pods.Items {
for _, c := range pod.Spec.Containers {
containers = append(containers, container.Container{
ID: pod.Name + ":" + c.Name,
Name: pod.Name + "/" + c.Name,
Image: c.Image,
Created: pod.CreationTimestamp.Time,
State: phaseToState(pod.Status.Phase),
Tty: c.TTY,
Host: pod.Spec.NodeName,
})
}
}
return containers, nil
}
func phaseToState(phase corev1.PodPhase) string {
switch phase {
case corev1.PodPending:
return "created"
case corev1.PodRunning:
return "running"
case corev1.PodSucceeded:
return "exited"
case corev1.PodFailed:
return "exited"
case corev1.PodUnknown:
return "unknown"
default:
return "unknown"
}
}
func (k *K8sClient) FindContainer(ctx context.Context, id string) (container.Container, error) {
log.Debug().Str("id", id).Msg("Finding container")
podName, containerName := parsePodContainerID(id)
pod, err := k.Clientset.CoreV1().Pods(k.namespace).Get(ctx, podName, metav1.GetOptions{})
if err != nil {
return container.Container{}, err
}
for _, c := range pod.Spec.Containers {
if c.Name == containerName {
started := time.Time{}
if pod.Status.StartTime != nil {
started = pod.Status.StartTime.Time
}
return container.Container{
ID: pod.Name + ":" + c.Name,
Name: pod.Name + "/" + c.Name,
Image: c.Image,
Created: pod.CreationTimestamp.Time,
State: phaseToState(pod.Status.Phase),
StartedAt: started,
Command: strings.Join(c.Command, " "),
Host: pod.Spec.NodeName,
Tty: c.TTY,
Stats: utils.NewRingBuffer[container.ContainerStat](300),
}, nil
}
}
return container.Container{}, fmt.Errorf("container %s not found in pod %s", containerName, podName)
}
func (k *K8sClient) ContainerLogs(ctx context.Context, id string, since time.Time, stdType container.StdType) (io.ReadCloser, error) {
podName, containerName := parsePodContainerID(id)
opts := &corev1.PodLogOptions{
Container: containerName,
Follow: true,
Previous: false,
Timestamps: true,
SinceTime: &metav1.Time{Time: since},
}
return k.Clientset.CoreV1().Pods(k.namespace).GetLogs(podName, opts).Stream(ctx)
}
func (k *K8sClient) ContainerLogsBetweenDates(ctx context.Context, id string, start time.Time, end time.Time, stdType container.StdType) (io.ReadCloser, error) {
podName, containerName := parsePodContainerID(id)
opts := &corev1.PodLogOptions{
Container: containerName,
Follow: false,
Timestamps: true,
SinceTime: &metav1.Time{Time: start},
}
return k.Clientset.CoreV1().Pods(k.namespace).GetLogs(podName, opts).Stream(ctx)
}
func (k *K8sClient) ContainerEvents(ctx context.Context, ch chan<- container.ContainerEvent) error {
watch, err := k.Clientset.CoreV1().Pods(k.namespace).Watch(ctx, metav1.ListOptions{})
if err != nil {
return err
}
for event := range watch.ResultChan() {
log.Debug().Interface("event.type", event.Type).Msg("Received kubernetes event")
pod, ok := event.Object.(*corev1.Pod)
if !ok {
continue
}
name := ""
switch event.Type {
case "ADDED":
name = "create"
case "DELETED":
name = "destroy"
case "MODIFIED":
name = "update"
}
log.Debug().Interface("event.Type", event.Type).Str("name", name).Interface("StartTime", pod.Status.StartTime).Msg("Sending container event")
for _, c := range pod.Spec.Containers {
ch <- container.ContainerEvent{
Name: name,
ActorID: pod.Name + ":" + c.Name,
Host: pod.Spec.NodeName,
Time: time.Now(),
}
}
}
return nil
}
func (k *K8sClient) ContainerStats(ctx context.Context, id string, stats chan<- container.ContainerStat) error {
panic("not implemented")
}
func (k *K8sClient) Ping(ctx context.Context) error {
_, err := k.Clientset.CoreV1().Pods(k.namespace).List(ctx, metav1.ListOptions{Limit: 1})
return err
}
func (k *K8sClient) Host() container.Host {
return container.Host{}
}
func (k *K8sClient) ContainerActions(ctx context.Context, action container.ContainerAction, containerID string) error {
// Implementation for container actions (start, stop, restart, etc.)
return nil
}
// Helper function to parse pod and container names from container ID
func parsePodContainerID(id string) (string, string) {
parts := strings.Split(id, ":")
return parts[0], parts[1]
}

View File

@@ -0,0 +1,27 @@
package k8s
import (
"bufio"
"io"
"github.com/amir20/dozzle/internal/container"
)
type LogReader struct {
reader *bufio.Reader
}
func NewLogReader(reader io.ReadCloser) *LogReader {
return &LogReader{
reader: bufio.NewReader(reader),
}
}
func (r *LogReader) Read() (string, container.StdType, error) {
line, err := r.reader.ReadString('\n')
if err != nil {
return "", 0, err
}
return line, container.STDOUT, nil
}

View File

@@ -0,0 +1,124 @@
package k8s
import (
"context"
"sync"
"sync/atomic"
"time"
"github.com/amir20/dozzle/internal/container"
"github.com/puzpuzpuz/xsync/v3"
"github.com/rs/zerolog/log"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
metricsclient "k8s.io/metrics/pkg/client/clientset/versioned"
)
var timeToStop = 2 * time.Hour
type K8sStatsCollector struct {
client *K8sClient
metrics *metricsclient.Clientset
subscribers *xsync.MapOf[context.Context, chan<- container.ContainerStat]
stopper context.CancelFunc
timer *time.Timer
mu sync.Mutex
totalStarted atomic.Int32
labels container.ContainerLabels
}
func NewK8sStatsCollector(client *K8sClient, labels container.ContainerLabels) (*K8sStatsCollector, error) {
metricsClient, err := metricsclient.NewForConfig(client.config)
if err != nil {
return nil, err
}
return &K8sStatsCollector{
subscribers: xsync.NewMapOf[context.Context, chan<- container.ContainerStat](),
client: client,
labels: labels,
metrics: metricsClient,
}, nil
}
func (c *K8sStatsCollector) Subscribe(ctx context.Context, stats chan<- container.ContainerStat) {
c.subscribers.Store(ctx, stats)
go func() {
<-ctx.Done()
c.subscribers.Delete(ctx)
}()
}
func (c *K8sStatsCollector) Stop() {
c.mu.Lock()
defer c.mu.Unlock()
if c.totalStarted.Add(-1) == 0 {
c.timer = time.AfterFunc(timeToStop, func() {
c.forceStop()
})
}
}
func (c *K8sStatsCollector) forceStop() {
c.mu.Lock()
defer c.mu.Unlock()
if c.stopper != nil {
c.stopper()
c.stopper = nil
log.Debug().Msg("stopped container k8s stats collector")
}
}
func (c *K8sStatsCollector) reset() {
c.mu.Lock()
defer c.mu.Unlock()
if c.timer != nil {
c.timer.Stop()
}
c.timer = nil
}
// Start starts the stats collector and blocks until it's stopped. It returns true if the collector was stopped, false if it was already running
func (sc *K8sStatsCollector) Start(parentCtx context.Context) bool {
sc.reset()
sc.totalStarted.Add(1)
sc.mu.Lock()
if sc.stopper != nil {
sc.mu.Unlock()
return false
}
var ctx context.Context
ctx, sc.stopper = context.WithCancel(parentCtx)
sc.mu.Unlock()
ticker := time.NewTicker(1 * time.Second)
for {
select {
case <-ticker.C:
metricList, err := sc.metrics.MetricsV1beta1().PodMetricses(sc.client.namespace).List(ctx, metav1.ListOptions{})
if err != nil {
panic(err.Error())
}
for _, pod := range metricList.Items {
for _, c := range pod.Containers {
stat := container.ContainerStat{
ID: pod.Name + ":" + c.Name,
CPUPercent: float64(c.Usage.Cpu().MilliValue()) / 1000 * 100,
MemoryUsage: c.Usage.Memory().AsApproximateFloat64(),
}
log.Trace().Interface("stat", stat).Msg("k8s stats")
sc.subscribers.Range(func(c context.Context, stats chan<- container.ContainerStat) bool {
select {
case stats <- stat:
case <-c.Done():
sc.subscribers.Delete(c)
}
return true
})
}
}
case <-ctx.Done():
return true
}
}
}

View File

@@ -6,12 +6,13 @@ import (
"github.com/amir20/dozzle/internal/container" "github.com/amir20/dozzle/internal/container"
"github.com/amir20/dozzle/internal/docker" "github.com/amir20/dozzle/internal/docker"
container_support "github.com/amir20/dozzle/internal/support/container"
docker_support "github.com/amir20/dozzle/internal/support/docker" docker_support "github.com/amir20/dozzle/internal/support/docker"
"github.com/rs/zerolog/log" "github.com/rs/zerolog/log"
) )
func CreateMultiHostService(embeddedCerts embed.FS, args Args) (container.Client, *docker_support.MultiHostService) { func CreateMultiHostService(embeddedCerts embed.FS, args Args) (container.Client, *docker_support.MultiHostService) {
var clients []docker_support.ClientService var clients []container_support.ClientService
if len(args.RemoteHost) > 0 { if len(args.RemoteHost) > 0 {
log.Info().Msg(`Consider using Dozzle's remote agent to manage remote hosts. See https://dozzle.dev/guide/agent for more information`) log.Info().Msg(`Consider using Dozzle's remote agent to manage remote hosts. See https://dozzle.dev/guide/agent for more information`)
} }

View File

@@ -1,4 +1,4 @@
package docker_support package container_support
import ( import (
"context" "context"
@@ -22,7 +22,7 @@ func NewAgentService(client *agent.Client) ClientService {
} }
} }
func (a *agentService) FindContainer(ctx context.Context, id string, filter container.ContainerFilter) (container.Container, error) { func (a *agentService) FindContainer(ctx context.Context, id string, labels container.ContainerLabels) (container.Container, error) {
return a.client.FindContainer(ctx, id) return a.client.FindContainer(ctx, id)
} }
@@ -38,9 +38,9 @@ func (a *agentService) StreamLogs(ctx context.Context, container container.Conta
return a.client.StreamContainerLogs(ctx, container.ID, from, stdTypes, events) return a.client.StreamContainerLogs(ctx, container.ID, from, stdTypes, events)
} }
func (a *agentService) ListContainers(ctx context.Context, filter container.ContainerFilter) ([]container.Container, error) { func (a *agentService) ListContainers(ctx context.Context, labels container.ContainerLabels) ([]container.Container, error) {
log.Debug().Interface("filter", filter).Msg("Listing containers from agent") log.Debug().Interface("labels", labels).Msg("Listing containers from agent")
return a.client.ListContainers(ctx, filter) return a.client.ListContainers(ctx, labels)
} }
func (a *agentService) Host(ctx context.Context) (container.Host, error) { func (a *agentService) Host(ctx context.Context) (container.Host, error) {

View File

@@ -0,0 +1,28 @@
package container_support
import (
"context"
"io"
"time"
"github.com/amir20/dozzle/internal/container"
)
type ContainerFilter = func(*container.Container) bool
type ClientService interface {
FindContainer(ctx context.Context, id string, labels container.ContainerLabels) (container.Container, error)
ListContainers(ctx context.Context, filter container.ContainerLabels) ([]container.Container, error)
Host(ctx context.Context) (container.Host, error)
ContainerAction(ctx context.Context, container container.Container, action container.ContainerAction) error
LogsBetweenDates(ctx context.Context, container container.Container, from time.Time, to time.Time, stdTypes container.StdType) (<-chan *container.LogEvent, error)
RawLogs(ctx context.Context, container container.Container, from time.Time, to time.Time, stdTypes container.StdType) (io.ReadCloser, error)
// Subscriptions
SubscribeStats(ctx context.Context, stats chan<- container.ContainerStat)
SubscribeEvents(ctx context.Context, events chan<- container.ContainerEvent)
SubscribeContainersStarted(ctx context.Context, containers chan<- container.Container)
// Blocking streaming functions that should be used in a goroutine
StreamLogs(ctx context.Context, container container.Container, from time.Time, stdTypes container.StdType, events chan<- *container.LogEvent) error
}

View File

@@ -1,4 +1,4 @@
package docker_support package container_support
import ( import (
"context" "context"
@@ -8,23 +8,30 @@ import (
"github.com/amir20/dozzle/internal/container" "github.com/amir20/dozzle/internal/container"
) )
type containerService struct { type ContainerService struct {
clientService ClientService clientService ClientService
Container container.Container Container container.Container
} }
func (c *containerService) RawLogs(ctx context.Context, from time.Time, to time.Time, stdTypes container.StdType) (io.ReadCloser, error) { func NewContainerService(clientService ClientService, container container.Container) *ContainerService {
return &ContainerService{
clientService: clientService,
Container: container,
}
}
func (c *ContainerService) RawLogs(ctx context.Context, from time.Time, to time.Time, stdTypes container.StdType) (io.ReadCloser, error) {
return c.clientService.RawLogs(ctx, c.Container, from, to, stdTypes) return c.clientService.RawLogs(ctx, c.Container, from, to, stdTypes)
} }
func (c *containerService) LogsBetweenDates(ctx context.Context, from time.Time, to time.Time, stdTypes container.StdType) (<-chan *container.LogEvent, error) { func (c *ContainerService) LogsBetweenDates(ctx context.Context, from time.Time, to time.Time, stdTypes container.StdType) (<-chan *container.LogEvent, error) {
return c.clientService.LogsBetweenDates(ctx, c.Container, from, to, stdTypes) return c.clientService.LogsBetweenDates(ctx, c.Container, from, to, stdTypes)
} }
func (c *containerService) StreamLogs(ctx context.Context, from time.Time, stdTypes container.StdType, events chan<- *container.LogEvent) error { func (c *ContainerService) StreamLogs(ctx context.Context, from time.Time, stdTypes container.StdType, events chan<- *container.LogEvent) error {
return c.clientService.StreamLogs(ctx, c.Container, from, stdTypes, events) return c.clientService.StreamLogs(ctx, c.Container, from, stdTypes, events)
} }
func (c *containerService) Action(ctx context.Context, action container.ContainerAction) error { func (c *ContainerService) Action(ctx context.Context, action container.ContainerAction) error {
return c.clientService.ContainerAction(ctx, c.Container, action) return c.clientService.ContainerAction(ctx, c.Container, action)
} }

View File

@@ -1,99 +0,0 @@
package docker_support
import (
"context"
"io"
"time"
"github.com/amir20/dozzle/internal/container"
)
type ClientService interface {
FindContainer(ctx context.Context, id string, filter container.ContainerFilter) (container.Container, error)
ListContainers(ctx context.Context, filter container.ContainerFilter) ([]container.Container, error)
Host(ctx context.Context) (container.Host, error)
ContainerAction(ctx context.Context, container container.Container, action container.ContainerAction) error
LogsBetweenDates(ctx context.Context, container container.Container, from time.Time, to time.Time, stdTypes container.StdType) (<-chan *container.LogEvent, error)
RawLogs(ctx context.Context, container container.Container, from time.Time, to time.Time, stdTypes container.StdType) (io.ReadCloser, error)
// Subscriptions
SubscribeStats(ctx context.Context, stats chan<- container.ContainerStat)
SubscribeEvents(ctx context.Context, events chan<- container.ContainerEvent)
SubscribeContainersStarted(ctx context.Context, containers chan<- container.Container)
// Blocking streaming functions that should be used in a goroutine
StreamLogs(ctx context.Context, container container.Container, from time.Time, stdTypes container.StdType, events chan<- *container.LogEvent) error
}
type dockerClientService struct {
client container.Client
store *container.ContainerStore
}
func NewDockerClientService(client container.Client, filter container.ContainerFilter) ClientService {
return &dockerClientService{
client: client,
store: container.NewContainerStore(context.Background(), client, filter),
}
}
func (d *dockerClientService) RawLogs(ctx context.Context, container container.Container, from time.Time, to time.Time, stdTypes container.StdType) (io.ReadCloser, error) {
return d.client.ContainerLogsBetweenDates(ctx, container.ID, from, to, stdTypes)
}
func (d *dockerClientService) LogsBetweenDates(ctx context.Context, c container.Container, from time.Time, to time.Time, stdTypes container.StdType) (<-chan *container.LogEvent, error) {
reader, err := d.client.ContainerLogsBetweenDates(ctx, c.ID, from, to, stdTypes)
if err != nil {
return nil, err
}
g := container.NewEventGenerator(ctx, reader, c)
return g.Events, nil
}
func (d *dockerClientService) StreamLogs(ctx context.Context, c container.Container, from time.Time, stdTypes container.StdType, events chan<- *container.LogEvent) error {
reader, err := d.client.ContainerLogs(ctx, c.ID, from, stdTypes)
if err != nil {
return err
}
g := container.NewEventGenerator(ctx, reader, c)
for event := range g.Events {
events <- event
}
select {
case e := <-g.Errors:
return e
default:
return nil
}
}
func (d *dockerClientService) FindContainer(ctx context.Context, id string, filter container.ContainerFilter) (container.Container, error) {
return d.store.FindContainer(id, filter)
}
func (d *dockerClientService) ContainerAction(ctx context.Context, container container.Container, action container.ContainerAction) error {
return d.client.ContainerActions(ctx, action, container.ID)
}
func (d *dockerClientService) ListContainers(ctx context.Context, filter container.ContainerFilter) ([]container.Container, error) {
return d.store.ListContainers(filter)
}
func (d *dockerClientService) Host(ctx context.Context) (container.Host, error) {
return d.client.Host(), nil
}
func (d *dockerClientService) SubscribeStats(ctx context.Context, stats chan<- container.ContainerStat) {
d.store.SubscribeStats(ctx, stats)
}
func (d *dockerClientService) SubscribeEvents(ctx context.Context, events chan<- container.ContainerEvent) {
d.store.SubscribeEvents(ctx, events)
}
func (d *dockerClientService) SubscribeContainersStarted(ctx context.Context, containers chan<- container.Container) {
d.store.SubscribeNewContainers(ctx, containers)
}

View File

@@ -0,0 +1,111 @@
package docker_support
import (
"context"
"io"
"time"
"github.com/amir20/dozzle/internal/container"
"github.com/amir20/dozzle/internal/docker"
"github.com/docker/docker/pkg/stdcopy"
"github.com/rs/zerolog/log"
)
type DockerClientService struct {
client container.Client
store *container.ContainerStore
}
func NewDockerClientService(client container.Client, labels container.ContainerLabels) *DockerClientService {
statsCollector := docker.NewDockerStatsCollector(client, labels)
return &DockerClientService{
client: client,
store: container.NewContainerStore(context.Background(), client, statsCollector, labels),
}
}
func (d *DockerClientService) RawLogs(ctx context.Context, container container.Container, from time.Time, to time.Time, stdTypes container.StdType) (io.ReadCloser, error) {
reader, err := d.client.ContainerLogsBetweenDates(ctx, container.ID, from, to, stdTypes)
if err != nil {
return nil, err
}
in, out := io.Pipe()
go func() {
if container.Tty {
if _, err := io.Copy(out, reader); err != nil {
log.Error().Err(err).Msgf("error copying logs for container %s", container.ID)
}
} else {
if _, err := stdcopy.StdCopy(out, out, reader); err != nil {
log.Error().Err(err).Msgf("error copying logs for container %s", container.ID)
}
}
out.Close()
}()
return in, nil
}
func (d *DockerClientService) LogsBetweenDates(ctx context.Context, c container.Container, from time.Time, to time.Time, stdTypes container.StdType) (<-chan *container.LogEvent, error) {
reader, err := d.client.ContainerLogsBetweenDates(ctx, c.ID, from, to, stdTypes)
if err != nil {
return nil, err
}
dockerReader := docker.NewLogReader(reader, c.Tty)
g := container.NewEventGenerator(ctx, dockerReader, c)
return g.Events, nil
}
func (d *DockerClientService) StreamLogs(ctx context.Context, c container.Container, from time.Time, stdTypes container.StdType, events chan<- *container.LogEvent) error {
reader, err := d.client.ContainerLogs(ctx, c.ID, from, stdTypes)
if err != nil {
return err
}
dockerReader := docker.NewLogReader(reader, c.Tty)
g := container.NewEventGenerator(ctx, dockerReader, c)
for event := range g.Events {
events <- event
}
select {
case e := <-g.Errors:
return e
default:
return nil
}
}
func (d *DockerClientService) FindContainer(ctx context.Context, id string, labels container.ContainerLabels) (container.Container, error) {
return d.store.FindContainer(id, labels)
}
func (d *DockerClientService) ContainerAction(ctx context.Context, container container.Container, action container.ContainerAction) error {
return d.client.ContainerActions(ctx, action, container.ID)
}
func (d *DockerClientService) ListContainers(ctx context.Context, labels container.ContainerLabels) ([]container.Container, error) {
return d.store.ListContainers(labels)
}
func (d *DockerClientService) Host(ctx context.Context) (container.Host, error) {
return d.client.Host(), nil
}
func (d *DockerClientService) SubscribeStats(ctx context.Context, stats chan<- container.ContainerStat) {
d.store.SubscribeStats(ctx, stats)
}
func (d *DockerClientService) SubscribeEvents(ctx context.Context, events chan<- container.ContainerEvent) {
d.store.SubscribeEvents(ctx, events)
}
func (d *DockerClientService) SubscribeContainersStarted(ctx context.Context, containers chan<- container.Container) {
d.store.SubscribeNewContainers(ctx, containers)
}

View File

@@ -6,11 +6,10 @@ import (
"time" "time"
"github.com/amir20/dozzle/internal/container" "github.com/amir20/dozzle/internal/container"
container_support "github.com/amir20/dozzle/internal/support/container"
"github.com/rs/zerolog/log" "github.com/rs/zerolog/log"
) )
type ContainerFilter = func(*container.Container) bool
type HostUnavailableError struct { type HostUnavailableError struct {
Host container.Host Host container.Host
Err error Err error
@@ -21,9 +20,9 @@ func (h *HostUnavailableError) Error() string {
} }
type ClientManager interface { type ClientManager interface {
Find(id string) (ClientService, bool) Find(id string) (container_support.ClientService, bool)
List() []ClientService List() []container_support.ClientService
RetryAndList() ([]ClientService, []error) RetryAndList() ([]container_support.ClientService, []error)
Subscribe(ctx context.Context, channel chan<- container.Host) Subscribe(ctx context.Context, channel chan<- container.Host)
Hosts(ctx context.Context) []container.Host Hosts(ctx context.Context) []container.Host
LocalClients() []container.Client LocalClients() []container.Client
@@ -43,25 +42,22 @@ func NewMultiHostService(manager ClientManager, timeout time.Duration) *MultiHos
return m return m
} }
func (m *MultiHostService) FindContainer(host string, id string, filter container.ContainerFilter) (*containerService, error) { func (m *MultiHostService) FindContainer(host string, id string, labels container.ContainerLabels) (*container_support.ContainerService, error) {
client, ok := m.manager.Find(host) client, ok := m.manager.Find(host)
if !ok { if !ok {
return nil, fmt.Errorf("host %s not found", host) return nil, fmt.Errorf("host %s not found", host)
} }
ctx, cancel := context.WithTimeout(context.Background(), m.timeout) ctx, cancel := context.WithTimeout(context.Background(), m.timeout)
defer cancel() defer cancel()
container, err := client.FindContainer(ctx, id, filter) container, err := client.FindContainer(ctx, id, labels)
if err != nil { if err != nil {
return nil, err return nil, err
} }
return &containerService{ return container_support.NewContainerService(client, container), nil
clientService: client,
Container: container,
}, nil
} }
func (m *MultiHostService) ListContainersForHost(host string, filter container.ContainerFilter) ([]container.Container, error) { func (m *MultiHostService) ListContainersForHost(host string, labels container.ContainerLabels) ([]container.Container, error) {
client, ok := m.manager.Find(host) client, ok := m.manager.Find(host)
if !ok { if !ok {
return nil, fmt.Errorf("host %s not found", host) return nil, fmt.Errorf("host %s not found", host)
@@ -69,17 +65,17 @@ func (m *MultiHostService) ListContainersForHost(host string, filter container.C
ctx, cancel := context.WithTimeout(context.Background(), m.timeout) ctx, cancel := context.WithTimeout(context.Background(), m.timeout)
defer cancel() defer cancel()
return client.ListContainers(ctx, filter) return client.ListContainers(ctx, labels)
} }
func (m *MultiHostService) ListAllContainers(filter container.ContainerFilter) ([]container.Container, []error) { func (m *MultiHostService) ListAllContainers(labels container.ContainerLabels) ([]container.Container, []error) {
containers := make([]container.Container, 0) containers := make([]container.Container, 0)
clients, errors := m.manager.RetryAndList() clients, errors := m.manager.RetryAndList()
for _, client := range clients { for _, client := range clients {
ctx, cancel := context.WithTimeout(context.Background(), m.timeout) ctx, cancel := context.WithTimeout(context.Background(), m.timeout)
defer cancel() defer cancel()
list, err := client.ListContainers(ctx, filter) list, err := client.ListContainers(ctx, labels)
if err != nil { if err != nil {
host, _ := client.Host(ctx) host, _ := client.Host(ctx)
log.Debug().Err(err).Str("host", host.Name).Msg("error listing containers") log.Debug().Err(err).Str("host", host.Name).Msg("error listing containers")
@@ -94,8 +90,8 @@ func (m *MultiHostService) ListAllContainers(filter container.ContainerFilter) (
return containers, errors return containers, errors
} }
func (m *MultiHostService) ListAllContainersFiltered(userFilter container.ContainerFilter, filter ContainerFilter) ([]container.Container, []error) { func (m *MultiHostService) ListAllContainersFiltered(userLabels container.ContainerLabels, filter container_support.ContainerFilter) ([]container.Container, []error) {
containers, err := m.ListAllContainers(userFilter) containers, err := m.ListAllContainers(userLabels)
filtered := make([]container.Container, 0, len(containers)) filtered := make([]container.Container, 0, len(containers))
for _, container := range containers { for _, container := range containers {
if filter(&container) { if filter(&container) {
@@ -112,7 +108,7 @@ func (m *MultiHostService) SubscribeEventsAndStats(ctx context.Context, events c
} }
} }
func (m *MultiHostService) SubscribeContainersStarted(ctx context.Context, containers chan<- container.Container, filter ContainerFilter) { func (m *MultiHostService) SubscribeContainersStarted(ctx context.Context, containers chan<- container.Container, filter container_support.ContainerFilter) {
newContainers := make(chan container.Container) newContainers := make(chan container.Container)
for _, client := range m.manager.List() { for _, client := range m.manager.List() {
client.SubscribeContainersStarted(ctx, newContainers) client.SubscribeContainersStarted(ctx, newContainers)
@@ -135,10 +131,6 @@ func (m *MultiHostService) SubscribeContainersStarted(ctx context.Context, conta
}() }()
} }
func (m *MultiHostService) TotalClients() int {
return len(m.manager.List())
}
func (m *MultiHostService) Hosts() []container.Host { func (m *MultiHostService) Hosts() []container.Host {
ctx, cancel := context.WithTimeout(context.Background(), m.timeout) ctx, cancel := context.WithTimeout(context.Background(), m.timeout)
defer cancel() defer cancel()
@@ -161,3 +153,7 @@ func (m *MultiHostService) SubscribeAvailableHosts(ctx context.Context, hosts ch
func (m *MultiHostService) LocalClients() []container.Client { func (m *MultiHostService) LocalClients() []container.Client {
return m.manager.LocalClients() return m.manager.LocalClients()
} }
func (m *MultiHostService) TotalClients() int {
return len(m.manager.List())
}

View File

@@ -9,6 +9,8 @@ import (
"github.com/amir20/dozzle/internal/agent" "github.com/amir20/dozzle/internal/agent"
"github.com/amir20/dozzle/internal/container" "github.com/amir20/dozzle/internal/container"
container_support "github.com/amir20/dozzle/internal/support/container"
"github.com/puzpuzpuz/xsync/v3" "github.com/puzpuzpuz/xsync/v3"
"github.com/samber/lo" "github.com/samber/lo"
lop "github.com/samber/lo/parallel" lop "github.com/samber/lo/parallel"
@@ -17,7 +19,7 @@ import (
) )
type RetriableClientManager struct { type RetriableClientManager struct {
clients map[string]ClientService clients map[string]container_support.ClientService
failedAgents []string failedAgents []string
certs tls.Certificate certs tls.Certificate
mu sync.RWMutex mu sync.RWMutex
@@ -25,8 +27,8 @@ type RetriableClientManager struct {
timeout time.Duration timeout time.Duration
} }
func NewRetriableClientManager(agents []string, timeout time.Duration, certs tls.Certificate, clients ...ClientService) *RetriableClientManager { func NewRetriableClientManager(agents []string, timeout time.Duration, certs tls.Certificate, clients ...container_support.ClientService) *RetriableClientManager {
clientMap := make(map[string]ClientService) clientMap := make(map[string]container_support.ClientService)
for _, client := range clients { for _, client := range clients {
ctx, cancel := context.WithTimeout(context.Background(), timeout) ctx, cancel := context.WithTimeout(context.Background(), timeout)
defer cancel() defer cancel()
@@ -64,7 +66,7 @@ func NewRetriableClientManager(agents []string, timeout time.Duration, certs tls
if _, ok := clientMap[host.ID]; ok { if _, ok := clientMap[host.ID]; ok {
log.Warn().Str("host", host.Name).Str("id", host.ID).Msg("duplicate host with same ID found") log.Warn().Str("host", host.Name).Str("id", host.ID).Msg("duplicate host with same ID found")
} else { } else {
clientMap[host.ID] = NewAgentService(agent) clientMap[host.ID] = container_support.NewAgentService(agent)
} }
} }
@@ -86,7 +88,7 @@ func (m *RetriableClientManager) Subscribe(ctx context.Context, channel chan<- c
}() }()
} }
func (m *RetriableClientManager) RetryAndList() ([]ClientService, []error) { func (m *RetriableClientManager) RetryAndList() ([]container_support.ClientService, []error) {
m.mu.Lock() m.mu.Lock()
errors := make([]error, 0) errors := make([]error, 0)
if len(m.failedAgents) > 0 { if len(m.failedAgents) > 0 {
@@ -110,7 +112,7 @@ func (m *RetriableClientManager) RetryAndList() ([]ClientService, []error) {
continue continue
} }
m.clients[host.ID] = NewAgentService(agent) m.clients[host.ID] = container_support.NewAgentService(agent)
m.subscribers.Range(func(ctx context.Context, channel chan<- container.Host) bool { m.subscribers.Range(func(ctx context.Context, channel chan<- container.Host) bool {
host.Available = true host.Available = true
@@ -133,14 +135,14 @@ func (m *RetriableClientManager) RetryAndList() ([]ClientService, []error) {
return m.List(), errors return m.List(), errors
} }
func (m *RetriableClientManager) List() []ClientService { func (m *RetriableClientManager) List() []container_support.ClientService {
m.mu.RLock() m.mu.RLock()
defer m.mu.RUnlock() defer m.mu.RUnlock()
return lo.Values(m.clients) return lo.Values(m.clients)
} }
func (m *RetriableClientManager) Find(id string) (ClientService, bool) { func (m *RetriableClientManager) Find(id string) (container_support.ClientService, bool) {
m.mu.RLock() m.mu.RLock()
defer m.mu.RUnlock() defer m.mu.RUnlock()
@@ -155,7 +157,7 @@ func (m *RetriableClientManager) String() string {
func (m *RetriableClientManager) Hosts(ctx context.Context) []container.Host { func (m *RetriableClientManager) Hosts(ctx context.Context) []container.Host {
clients := m.List() clients := m.List()
hosts := lop.Map(clients, func(client ClientService, _ int) container.Host { hosts := lop.Map(clients, func(client container_support.ClientService, _ int) container.Host {
host, err := client.Host(ctx) host, err := client.Host(ctx)
if err != nil { if err != nil {
log.Warn().Err(err).Str("host", host.Name).Msg("error fetching host info for client") log.Warn().Err(err).Str("host", host.Name).Msg("error fetching host info for client")
@@ -186,7 +188,7 @@ func (m *RetriableClientManager) LocalClients() []container.Client {
clients := make([]container.Client, 0) clients := make([]container.Client, 0)
for _, service := range services { for _, service := range services {
if clientService, ok := service.(*dockerClientService); ok { if clientService, ok := service.(*DockerClientService); ok {
clients = append(clients, clientService.client) clients = append(clients, clientService.client)
} }
} }

View File

@@ -11,6 +11,9 @@ import (
"github.com/amir20/dozzle/internal/agent" "github.com/amir20/dozzle/internal/agent"
"github.com/amir20/dozzle/internal/container" "github.com/amir20/dozzle/internal/container"
"github.com/amir20/dozzle/internal/docker"
container_support "github.com/amir20/dozzle/internal/support/container"
"github.com/puzpuzpuz/xsync/v3" "github.com/puzpuzpuz/xsync/v3"
"github.com/samber/lo" "github.com/samber/lo"
lop "github.com/samber/lo/parallel" lop "github.com/samber/lo/parallel"
@@ -19,7 +22,7 @@ import (
) )
type SwarmClientManager struct { type SwarmClientManager struct {
clients map[string]ClientService clients map[string]container_support.ClientService
certs tls.Certificate certs tls.Certificate
mu sync.RWMutex mu sync.RWMutex
subscribers *xsync.MapOf[context.Context, chan<- container.Host] subscribers *xsync.MapOf[context.Context, chan<- container.Host]
@@ -47,9 +50,9 @@ func localIPs() []string {
return ips return ips
} }
func NewSwarmClientManager(localClient container.Client, certs tls.Certificate, timeout time.Duration, agentManager *RetriableClientManager, filter container.ContainerFilter) *SwarmClientManager { func NewSwarmClientManager(localClient *docker.DockerClient, certs tls.Certificate, timeout time.Duration, agentManager *RetriableClientManager, labels container.ContainerLabels) *SwarmClientManager {
clientMap := make(map[string]ClientService) clientMap := make(map[string]container_support.ClientService)
localService := NewDockerClientService(localClient, filter) localService := NewDockerClientService(localClient, labels)
clientMap[localClient.Host().ID] = localService clientMap[localClient.Host().ID] = localService
id, ok := os.LookupEnv("HOSTNAME") id, ok := os.LookupEnv("HOSTNAME")
@@ -90,7 +93,7 @@ func (m *SwarmClientManager) Subscribe(ctx context.Context, channel chan<- conta
}() }()
} }
func (m *SwarmClientManager) RetryAndList() ([]ClientService, []error) { func (m *SwarmClientManager) RetryAndList() ([]container_support.ClientService, []error) {
m.mu.Lock() m.mu.Lock()
ips, err := net.LookupIP(fmt.Sprintf("tasks.%s", m.name)) ips, err := net.LookupIP(fmt.Sprintf("tasks.%s", m.name))
@@ -104,7 +107,7 @@ func (m *SwarmClientManager) RetryAndList() ([]ClientService, []error) {
} }
clients := lo.Values(m.clients) clients := lo.Values(m.clients)
endpoints := lo.KeyBy(clients, func(client ClientService) string { endpoints := lo.KeyBy(clients, func(client container_support.ClientService) string {
ctx, cancel := context.WithTimeout(context.Background(), m.timeout) ctx, cancel := context.WithTimeout(context.Background(), m.timeout)
defer cancel() defer cancel()
host, _ := client.Host(ctx) host, _ := client.Host(ctx)
@@ -155,7 +158,7 @@ func (m *SwarmClientManager) RetryAndList() ([]ClientService, []error) {
continue continue
} }
client := NewAgentService(agent) client := container_support.NewAgentService(agent)
m.clients[host.ID] = client m.clients[host.ID] = client
log.Info().Stringer("ip", ip).Str("id", host.ID).Str("name", host.Name).Msg("added new swarm agent") log.Info().Stringer("ip", ip).Str("id", host.ID).Str("name", host.Name).Msg("added new swarm agent")
@@ -182,7 +185,7 @@ func (m *SwarmClientManager) RetryAndList() ([]ClientService, []error) {
return m.List(), errors return m.List(), errors
} }
func (m *SwarmClientManager) List() []ClientService { func (m *SwarmClientManager) List() []container_support.ClientService {
m.mu.RLock() m.mu.RLock()
defer m.mu.RUnlock() defer m.mu.RUnlock()
@@ -192,7 +195,7 @@ func (m *SwarmClientManager) List() []ClientService {
return append(agents, clients...) return append(agents, clients...)
} }
func (m *SwarmClientManager) Find(id string) (ClientService, bool) { func (m *SwarmClientManager) Find(id string) (container_support.ClientService, bool) {
m.mu.RLock() m.mu.RLock()
defer m.mu.RUnlock() defer m.mu.RUnlock()
@@ -210,7 +213,7 @@ func (m *SwarmClientManager) Hosts(ctx context.Context) []container.Host {
clients := lo.Values(m.clients) clients := lo.Values(m.clients)
m.mu.RUnlock() m.mu.RUnlock()
swarmNodes := lop.Map(clients, func(client ClientService, _ int) container.Host { swarmNodes := lop.Map(clients, func(client container_support.ClientService, _ int) container.Host {
host, err := client.Host(ctx) host, err := client.Host(ctx)
if err != nil { if err != nil {
log.Warn().Err(err).Str("id", host.ID).Msg("error getting host from client") log.Warn().Err(err).Str("id", host.ID).Msg("error getting host from client")

View File

@@ -0,0 +1,133 @@
package k8s_support
import (
"context"
"fmt"
"time"
"github.com/amir20/dozzle/internal/container"
"github.com/amir20/dozzle/internal/k8s"
container_support "github.com/amir20/dozzle/internal/support/container"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
)
type K8sClusterService struct {
client *K8sClientService
timeout time.Duration
hosts []container.Host
}
func NewK8sClusterService(client *k8s.K8sClient, timeout time.Duration) (*K8sClusterService, error) {
hosts := make([]container.Host, 0)
nodes, err := client.Clientset.CoreV1().Nodes().List(context.Background(), metav1.ListOptions{})
if err != nil {
return nil, err
}
if len(nodes.Items) == 0 {
return nil, fmt.Errorf("nodes not found")
}
for _, node := range nodes.Items {
hosts = append(hosts, container.Host{
ID: node.Name,
Name: node.Name,
MemTotal: node.Status.Capacity.Memory().Value(),
NCPU: int(node.Status.Capacity.Cpu().Value()),
DockerVersion: node.Status.NodeInfo.ContainerRuntimeVersion,
Type: "k8s",
Available: true,
})
}
return &K8sClusterService{
client: NewK8sClientService(client, container.ContainerLabels{}),
timeout: timeout,
hosts: hosts,
}, nil
}
func (m *K8sClusterService) FindContainer(host string, id string, labels container.ContainerLabels) (*container_support.ContainerService, error) {
container, err := m.client.FindContainer(context.Background(), id, labels)
if err != nil {
return nil, err
}
return container_support.NewContainerService(m.client, container), nil
}
func (m *K8sClusterService) ListContainersForHost(host string, labels container.ContainerLabels) ([]container.Container, error) {
containers, err := m.client.ListContainers(context.Background(), labels)
if err != nil {
return nil, err
}
filteredContainers := make([]container.Container, 0)
for _, container := range containers {
if container.Host == host {
filteredContainers = append(filteredContainers, container)
}
}
return filteredContainers, nil
}
func (m *K8sClusterService) ListAllContainers(labels container.ContainerLabels) ([]container.Container, []error) {
containers, err := m.client.ListContainers(context.Background(), labels)
if err != nil {
return nil, []error{err}
}
return containers, nil
}
func (m *K8sClusterService) ListAllContainersFiltered(userLabels container.ContainerLabels, filter container_support.ContainerFilter) ([]container.Container, []error) {
containers, err := m.ListAllContainers(userLabels)
filtered := make([]container.Container, 0, len(containers))
for _, container := range containers {
if filter(&container) {
filtered = append(filtered, container)
}
}
return filtered, err
}
func (m *K8sClusterService) SubscribeEventsAndStats(ctx context.Context, events chan<- container.ContainerEvent, stats chan<- container.ContainerStat) {
m.client.SubscribeEvents(ctx, events)
m.client.SubscribeStats(ctx, stats)
}
func (m *K8sClusterService) SubscribeContainersStarted(ctx context.Context, containers chan<- container.Container, filter container_support.ContainerFilter) {
newContainers := make(chan container.Container)
m.client.SubscribeContainersStarted(ctx, newContainers)
go func() {
<-ctx.Done()
close(newContainers)
}()
go func() {
for container := range newContainers {
if filter(&container) {
select {
case containers <- container:
case <-ctx.Done():
return
}
}
}
}()
}
func (m *K8sClusterService) Hosts() []container.Host {
return m.hosts
}
func (m *K8sClusterService) LocalHost() (container.Host, error) {
return container.Host{}, nil
}
func (m *K8sClusterService) SubscribeAvailableHosts(ctx context.Context, hosts chan<- container.Host) {
}
func (m *K8sClusterService) LocalClients() []container.Client {
return nil
}

View File

@@ -0,0 +1,92 @@
package k8s_support
import (
"context"
"io"
"github.com/rs/zerolog/log"
"time"
"github.com/amir20/dozzle/internal/container"
"github.com/amir20/dozzle/internal/k8s"
)
type K8sClientService struct {
client *k8s.K8sClient
store *container.ContainerStore
}
func NewK8sClientService(client *k8s.K8sClient, labels container.ContainerLabels) *K8sClientService {
statsCollector, err := k8s.NewK8sStatsCollector(client, labels)
if err != nil {
log.Fatal().Err(err).Msg("Could not create k8s stats collector")
}
return &K8sClientService{
client: client,
store: container.NewContainerStore(context.Background(), client, statsCollector, labels),
}
}
func (k *K8sClientService) FindContainer(ctx context.Context, id string, labels container.ContainerLabels) (container.Container, error) {
return k.store.FindContainer(id, labels)
}
func (k *K8sClientService) ListContainers(ctx context.Context, labels container.ContainerLabels) ([]container.Container, error) {
return k.store.ListContainers(labels)
}
func (k *K8sClientService) Host(ctx context.Context) (container.Host, error) {
return k.client.Host(), nil
}
func (k *K8sClientService) ContainerAction(ctx context.Context, container container.Container, action container.ContainerAction) error {
return k.client.ContainerActions(ctx, action, container.ID)
}
func (k *K8sClientService) LogsBetweenDates(ctx context.Context, c container.Container, from time.Time, to time.Time, stdTypes container.StdType) (<-chan *container.LogEvent, error) {
reader, err := k.client.ContainerLogsBetweenDates(ctx, c.ID, from, to, stdTypes)
if err != nil {
return nil, err
}
k8sReader := k8s.NewLogReader(reader)
g := container.NewEventGenerator(ctx, k8sReader, c)
return g.Events, nil
}
func (k *K8sClientService) RawLogs(ctx context.Context, container container.Container, from time.Time, to time.Time, stdTypes container.StdType) (io.ReadCloser, error) {
return k.client.ContainerLogsBetweenDates(ctx, container.ID, from, to, stdTypes)
}
func (k *K8sClientService) StreamLogs(ctx context.Context, c container.Container, from time.Time, stdTypes container.StdType, events chan<- *container.LogEvent) error {
reader, err := k.client.ContainerLogs(ctx, c.ID, from, stdTypes)
if err != nil {
return err
}
k8sReader := k8s.NewLogReader(reader)
g := container.NewEventGenerator(ctx, k8sReader, c)
for event := range g.Events {
events <- event
}
select {
case e := <-g.Errors:
return e
default:
return nil
}
}
func (k *K8sClientService) SubscribeStats(ctx context.Context, stats chan<- container.ContainerStat) {
k.store.SubscribeStats(ctx, stats)
}
func (k *K8sClientService) SubscribeEvents(ctx context.Context, events chan<- container.ContainerEvent) {
k.store.SubscribeEvents(ctx, events)
}
func (k *K8sClientService) SubscribeContainersStarted(ctx context.Context, containers chan<- container.Container) {
k.store.SubscribeNewContainers(ctx, containers)
}

View File

@@ -15,15 +15,15 @@ func (h *handler) containerActions(w http.ResponseWriter, r *http.Request) {
action := chi.URLParam(r, "action") action := chi.URLParam(r, "action")
id := chi.URLParam(r, "id") id := chi.URLParam(r, "id")
usersFilter := h.config.Filter userLabels := h.config.Labels
if h.config.Authorization.Provider != NONE { if h.config.Authorization.Provider != NONE {
user := auth.UserFromContext(r.Context()) user := auth.UserFromContext(r.Context())
if user.ContainerFilter.Exists() { if user.ContainerLabels.Exists() {
usersFilter = user.ContainerFilter userLabels = user.ContainerLabels
} }
} }
containerService, err := h.multiHostService.FindContainer(hostKey(r), id, usersFilter) containerService, err := h.hostService.FindContainer(hostKey(r), id, userLabels)
if err != nil { if err != nil {
log.Error().Err(err).Msg("error while trying to find container") log.Error().Err(err).Msg("error while trying to find container")
http.Error(w, err.Error(), http.StatusNotFound) http.Error(w, err.Error(), http.StatusNotFound)

View File

@@ -9,8 +9,8 @@ import (
func (h *handler) debugStore(w http.ResponseWriter, r *http.Request) { func (h *handler) debugStore(w http.ResponseWriter, r *http.Request) {
respone := make(map[string]interface{}) respone := make(map[string]interface{})
respone["hosts"] = h.multiHostService.Hosts() respone["hosts"] = h.hostService.Hosts()
containers, errors := h.multiHostService.ListAllContainers(container.ContainerFilter{}) containers, errors := h.hostService.ListAllContainers(container.ContainerLabels{})
respone["containers"] = containers respone["containers"] = containers
respone["errors"] = errors respone["errors"] = errors

View File

@@ -10,7 +10,6 @@ import (
"github.com/amir20/dozzle/internal/auth" "github.com/amir20/dozzle/internal/auth"
"github.com/amir20/dozzle/internal/container" "github.com/amir20/dozzle/internal/container"
"github.com/docker/docker/pkg/stdcopy"
"github.com/go-chi/chi/v5" "github.com/go-chi/chi/v5"
"github.com/rs/zerolog/log" "github.com/rs/zerolog/log"
) )
@@ -23,11 +22,11 @@ func (h *handler) downloadLogs(w http.ResponseWriter, r *http.Request) {
return return
} }
usersFilter := h.config.Filter userLabels := h.config.Labels
if h.config.Authorization.Provider != NONE { if h.config.Authorization.Provider != NONE {
user := auth.UserFromContext(r.Context()) user := auth.UserFromContext(r.Context())
if user.ContainerFilter.Exists() { if user.ContainerLabels.Exists() {
usersFilter = user.ContainerFilter userLabels = user.ContainerLabels
} }
} }
@@ -66,7 +65,7 @@ func (h *handler) downloadLogs(w http.ResponseWriter, r *http.Request) {
host := parts[0] host := parts[0]
id := parts[1] id := parts[1]
containerService, err := h.multiHostService.FindContainer(host, id, usersFilter) containerService, err := h.hostService.FindContainer(host, id, userLabels)
if err != nil { if err != nil {
log.Error().Err(err).Msgf("error finding container %s", id) log.Error().Err(err).Msgf("error finding container %s", id)
http.Error(w, fmt.Sprintf("error finding container %s: %v", id, err), http.StatusBadRequest) http.Error(w, fmt.Sprintf("error finding container %s: %v", id, err), http.StatusBadRequest)
@@ -90,19 +89,12 @@ func (h *handler) downloadLogs(w http.ResponseWriter, r *http.Request) {
return return
} }
// Copy logs directly to zip entry // Copy logs to zip file
if containerService.Container.Tty { _, err = io.Copy(f, reader)
if _, err := io.Copy(f, reader); err != nil { if err != nil {
log.Error().Err(err).Msgf("error copying logs for container %s", id) log.Error().Err(err).Msgf("error copying logs for container %s", id)
http.Error(w, fmt.Sprintf("error copying logs for container %s: %v", id, err), http.StatusInternalServerError) http.Error(w, fmt.Sprintf("error copying logs for container %s: %v", id, err), http.StatusInternalServerError)
return return
}
} else {
if _, err := stdcopy.StdCopy(f, f, reader); err != nil {
log.Error().Err(err).Msgf("error copying logs for container %s", id)
http.Error(w, fmt.Sprintf("error copying logs for container %s: %v", id, err), http.StatusInternalServerError)
return
}
} }
} }
} }

View File

@@ -24,18 +24,18 @@ func (h *handler) streamEvents(w http.ResponseWriter, r *http.Request) {
stats := make(chan container.ContainerStat) stats := make(chan container.ContainerStat)
availableHosts := make(chan container.Host) availableHosts := make(chan container.Host)
h.multiHostService.SubscribeEventsAndStats(r.Context(), events, stats) h.hostService.SubscribeEventsAndStats(r.Context(), events, stats)
h.multiHostService.SubscribeAvailableHosts(r.Context(), availableHosts) h.hostService.SubscribeAvailableHosts(r.Context(), availableHosts)
usersFilter := h.config.Filter userLabels := h.config.Labels
if h.config.Authorization.Provider != NONE { if h.config.Authorization.Provider != NONE {
user := auth.UserFromContext(r.Context()) user := auth.UserFromContext(r.Context())
if user.ContainerFilter.Exists() { if user.ContainerLabels.Exists() {
usersFilter = user.ContainerFilter userLabels = user.ContainerLabels
} }
} }
allContainers, errors := h.multiHostService.ListAllContainers(usersFilter) allContainers, errors := h.hostService.ListAllContainers(userLabels)
for _, err := range errors { for _, err := range errors {
log.Warn().Err(err).Msg("error listing containers") log.Warn().Err(err).Msg("error listing containers")
@@ -73,7 +73,7 @@ func (h *handler) streamEvents(w http.ResponseWriter, r *http.Request) {
if event.Name == "start" || event.Name == "rename" { if event.Name == "start" || event.Name == "rename" {
log.Debug().Str("action", event.Name).Str("id", event.ActorID).Msg("container event") log.Debug().Str("action", event.Name).Str("id", event.ActorID).Msg("container event")
if containers, err := h.multiHostService.ListContainersForHost(event.Host, usersFilter); err == nil { if containers, err := h.hostService.ListContainersForHost(event.Host, userLabels); err == nil {
if err := sseWriter.Event("containers-changed", containers); err != nil { if err := sseWriter.Event("containers-changed", containers); err != nil {
log.Error().Err(err).Msg("error writing containers to event stream") log.Error().Err(err).Msg("error writing containers to event stream")
return return
@@ -88,7 +88,7 @@ func (h *handler) streamEvents(w http.ResponseWriter, r *http.Request) {
case "update": case "update":
log.Debug().Str("id", event.ActorID).Msg("container updated") log.Debug().Str("id", event.ActorID).Msg("container updated")
if containerService, err := h.multiHostService.FindContainer(event.Host, event.ActorID, usersFilter); err == nil { if containerService, err := h.hostService.FindContainer(event.Host, event.ActorID, userLabels); err == nil {
if err := sseWriter.Event("container-updated", containerService.Container); err != nil { if err := sseWriter.Event("container-updated", containerService.Container); err != nil {
log.Error().Err(err).Msg("error writing event to event stream") log.Error().Err(err).Msg("error writing event to event stream")
return return
@@ -123,7 +123,7 @@ func sendBeaconEvent(h *handler, r *http.Request, runningContainers int) {
b := types.BeaconEvent{ b := types.BeaconEvent{
AuthProvider: string(h.config.Authorization.Provider), AuthProvider: string(h.config.Authorization.Provider),
Browser: r.Header.Get("User-Agent"), Browser: r.Header.Get("User-Agent"),
Clients: h.multiHostService.TotalClients(), Clients: len(h.hostService.Hosts()),
HasActions: h.config.EnableActions, HasActions: h.config.EnableActions,
HasCustomAddress: h.config.Addr != ":8080", HasCustomAddress: h.config.Addr != ":8080",
HasCustomBase: h.config.Base != "/", HasCustomBase: h.config.Base != "/",
@@ -133,9 +133,9 @@ func sendBeaconEvent(h *handler, r *http.Request, runningContainers int) {
Version: h.config.Version, Version: h.config.Version,
} }
local, err := h.multiHostService.LocalHost() local, err := h.hostService.LocalHost()
if err == nil { if err == nil {
b.ServerID = local.ID b.ServerID = local.ID // TODO : fix this for k8s
} }
if err := analytics.SendBeacon(b); err != nil { if err := analytics.SendBeacon(b); err != nil {

View File

@@ -54,7 +54,7 @@ func Test_handler_streamEvents_happy(t *testing.T) {
}) })
// This is needed so that the server is initialized for store // This is needed so that the server is initialized for store
manager := docker_support.NewRetriableClientManager(nil, 3*time.Second, tls.Certificate{}, docker_support.NewDockerClientService(mockedClient, container.ContainerFilter{})) manager := docker_support.NewRetriableClientManager(nil, 3*time.Second, tls.Certificate{}, docker_support.NewDockerClientService(mockedClient, container.ContainerLabels{}))
multiHostService := docker_support.NewMultiHostService(manager, 3*time.Second) multiHostService := docker_support.NewMultiHostService(manager, 3*time.Second)
server := CreateServer(multiHostService, nil, Config{Base: "/", Authorization: Authorization{Provider: NONE}}) server := CreateServer(multiHostService, nil, Config{Base: "/", Authorization: Authorization{Provider: NONE}})

View File

@@ -9,7 +9,7 @@ import (
func (h *handler) healthcheck(w http.ResponseWriter, r *http.Request) { func (h *handler) healthcheck(w http.ResponseWriter, r *http.Request) {
log.Debug().Msg("Executing healthcheck") log.Debug().Msg("Executing healthcheck")
clients := h.multiHostService.LocalClients() clients := h.hostService.LocalClients()
for _, client := range clients { for _, client := range clients {
if err := client.Ping(r.Context()); err != nil { if err := client.Ping(r.Context()); err != nil {
log.Error().Err(err).Str("host", client.Host().Name).Msg("error pinging host") log.Error().Err(err).Str("host", client.Host().Name).Msg("error pinging host")

View File

@@ -43,7 +43,7 @@ func (h *handler) executeTemplate(w http.ResponseWriter, req *http.Request) {
base = h.config.Base base = h.config.Base
} }
hosts := h.multiHostService.Hosts() hosts := h.hostService.Hosts()
sort.Slice(hosts, func(i, j int) bool { sort.Slice(hosts, func(i, j int) bool {
return hosts[i].Name < hosts[j].Name return hosts[i].Name < hosts[j].Name
}) })

View File

@@ -18,6 +18,7 @@ import (
"github.com/amir20/dozzle/internal/auth" "github.com/amir20/dozzle/internal/auth"
"github.com/amir20/dozzle/internal/container" "github.com/amir20/dozzle/internal/container"
container_support "github.com/amir20/dozzle/internal/support/container"
"github.com/amir20/dozzle/internal/support/search" "github.com/amir20/dozzle/internal/support/search"
support_web "github.com/amir20/dozzle/internal/support/web" support_web "github.com/amir20/dozzle/internal/support/web"
"github.com/amir20/dozzle/internal/utils" "github.com/amir20/dozzle/internal/utils"
@@ -47,15 +48,15 @@ func (h *handler) fetchLogsBetweenDates(w http.ResponseWriter, r *http.Request)
return return
} }
usersFilter := h.config.Filter usersLabels := h.config.Labels
if h.config.Authorization.Provider != NONE { if h.config.Authorization.Provider != NONE {
user := auth.UserFromContext(r.Context()) user := auth.UserFromContext(r.Context())
if user.ContainerFilter.Exists() { if user.ContainerLabels.Exists() {
usersFilter = user.ContainerFilter usersLabels = user.ContainerLabels
} }
} }
containerService, err := h.multiHostService.FindContainer(hostKey(r), id, usersFilter) containerService, err := h.hostService.FindContainer(hostKey(r), id, usersLabels)
if err != nil { if err != nil {
http.Error(w, err.Error(), http.StatusNotFound) http.Error(w, err.Error(), http.StatusNotFound)
return return
@@ -227,7 +228,7 @@ func (h *handler) streamHostLogs(w http.ResponseWriter, r *http.Request) {
}) })
} }
func (h *handler) streamLogsForContainers(w http.ResponseWriter, r *http.Request, containerFilter ContainerFilter) { func (h *handler) streamLogsForContainers(w http.ResponseWriter, r *http.Request, containerFilter container_support.ContainerFilter) {
var stdTypes container.StdType var stdTypes container.StdType
if r.URL.Query().Has("stdout") { if r.URL.Query().Has("stdout") {
stdTypes |= container.STDOUT stdTypes |= container.STDOUT
@@ -248,15 +249,15 @@ func (h *handler) streamLogsForContainers(w http.ResponseWriter, r *http.Request
return return
} }
usersFilter := h.config.Filter userLabels := h.config.Labels
if h.config.Authorization.Provider != NONE { if h.config.Authorization.Provider != NONE {
user := auth.UserFromContext(r.Context()) user := auth.UserFromContext(r.Context())
if user.ContainerFilter.Exists() { if user.ContainerLabels.Exists() {
usersFilter = user.ContainerFilter userLabels = user.ContainerLabels
} }
} }
existingContainers, errs := h.multiHostService.ListAllContainersFiltered(usersFilter, containerFilter) existingContainers, errs := h.hostService.ListAllContainersFiltered(userLabels, containerFilter)
if len(errs) > 0 { if len(errs) > 0 {
log.Warn().Err(errs[0]).Msg("error while listing containers") log.Warn().Err(errs[0]).Msg("error while listing containers")
} }
@@ -290,7 +291,7 @@ func (h *handler) streamLogsForContainers(w http.ResponseWriter, r *http.Request
events := make([]*container.LogEvent, 0) events := make([]*container.LogEvent, 0)
stillRunning := false stillRunning := false
for _, container := range existingContainers { for _, container := range existingContainers {
containerService, err := h.multiHostService.FindContainer(container.Host, container.ID, usersFilter) containerService, err := h.hostService.FindContainer(container.Host, container.ID, userLabels)
if err != nil { if err != nil {
log.Error().Err(err).Msg("error while finding container") log.Error().Err(err).Msg("error while finding container")
@@ -337,7 +338,7 @@ func (h *handler) streamLogsForContainers(w http.ResponseWriter, r *http.Request
} }
streamLogs := func(c container.Container) { streamLogs := func(c container.Container) {
containerService, err := h.multiHostService.FindContainer(c.Host, c.ID, usersFilter) containerService, err := h.hostService.FindContainer(c.Host, c.ID, userLabels)
if err != nil { if err != nil {
log.Error().Err(err).Msg("error while finding container") log.Error().Err(err).Msg("error while finding container")
return return
@@ -369,7 +370,7 @@ func (h *handler) streamLogsForContainers(w http.ResponseWriter, r *http.Request
} }
newContainers := make(chan container.Container) newContainers := make(chan container.Container)
h.multiHostService.SubscribeContainersStarted(r.Context(), newContainers, containerFilter) h.hostService.SubscribeContainersStarted(r.Context(), newContainers, containerFilter)
ticker := time.NewTicker(5 * time.Second) ticker := time.NewTicker(5 * time.Second)
sseWriter.Ping() sseWriter.Ping()
@@ -388,7 +389,7 @@ loop:
} }
sseWriter.Message(logEvent) sseWriter.Message(logEvent)
case c := <-newContainers: case c := <-newContainers:
if _, err := h.multiHostService.FindContainer(c.Host, c.ID, usersFilter); err == nil { if _, err := h.hostService.FindContainer(c.Host, c.ID, userLabels); err == nil {
events <- &container.ContainerEvent{ActorID: c.ID, Name: "container-started", Host: c.Host} events <- &container.ContainerEvent{ActorID: c.ID, Name: "container-started", Host: c.Host}
go streamLogs(c) go streamLogs(c)
} }

View File

@@ -1,6 +1,7 @@
package web package web
import ( import (
"context"
"io/fs" "io/fs"
"time" "time"
@@ -9,7 +10,7 @@ import (
"github.com/amir20/dozzle/internal/auth" "github.com/amir20/dozzle/internal/auth"
"github.com/amir20/dozzle/internal/container" "github.com/amir20/dozzle/internal/container"
docker_support "github.com/amir20/dozzle/internal/support/docker" container_support "github.com/amir20/dozzle/internal/support/container"
"github.com/go-chi/chi/v5" "github.com/go-chi/chi/v5"
"github.com/go-chi/chi/v5/middleware" "github.com/go-chi/chi/v5/middleware"
@@ -34,7 +35,7 @@ type Config struct {
Dev bool Dev bool
Authorization Authorization Authorization Authorization
EnableActions bool EnableActions bool
Filter container.ContainerFilter Labels container.ContainerLabels
} }
type Authorization struct { type Authorization struct {
@@ -48,20 +49,30 @@ type Authorizer interface {
CreateToken(string, string) (string, error) CreateToken(string, string) (string, error)
} }
type handler struct { type HostService interface {
content fs.FS FindContainer(host string, id string, labels container.ContainerLabels) (*container_support.ContainerService, error)
config *Config ListContainersForHost(host string, labels container.ContainerLabels) ([]container.Container, error)
multiHostService *docker_support.MultiHostService ListAllContainers(labels container.ContainerLabels) ([]container.Container, []error)
ListAllContainersFiltered(userFilter container.ContainerLabels, filter container_support.ContainerFilter) ([]container.Container, []error)
SubscribeEventsAndStats(ctx context.Context, events chan<- container.ContainerEvent, stats chan<- container.ContainerStat)
SubscribeContainersStarted(ctx context.Context, containers chan<- container.Container, filter container_support.ContainerFilter)
Hosts() []container.Host
LocalHost() (container.Host, error)
SubscribeAvailableHosts(ctx context.Context, hosts chan<- container.Host)
LocalClients() []container.Client
} }
type MultiHostService = docker_support.MultiHostService type handler struct {
type ContainerFilter = docker_support.ContainerFilter content fs.FS
config *Config
hostService HostService
}
func CreateServer(multiHostService *MultiHostService, content fs.FS, config Config) *http.Server { func CreateServer(hostService HostService, content fs.FS, config Config) *http.Server {
handler := &handler{ handler := &handler{
content: content, content: content,
config: &config, config: &config,
multiHostService: multiHostService, hostService: hostService,
} }
return &http.Server{Addr: config.Addr, Handler: createRouter(handler)} return &http.Server{Addr: config.Addr, Handler: createRouter(handler)}

View File

@@ -38,8 +38,8 @@ func (m *MockedClient) ContainerEvents(ctx context.Context, events chan<- contai
return args.Error(0) return args.Error(0)
} }
func (m *MockedClient) ListContainers(ctx context.Context, filter container.ContainerFilter) ([]container.Container, error) { func (m *MockedClient) ListContainers(ctx context.Context, labels container.ContainerLabels) ([]container.Container, error) {
args := m.Called(ctx, filter) args := m.Called(ctx, labels)
return args.Get(0).([]container.Container), args.Error(1) return args.Get(0).([]container.Container), args.Error(1)
} }
@@ -86,12 +86,12 @@ func createHandler(client container.Client, content fs.FS, config Config) *chi.M
content = afero.NewIOFS(fs) content = afero.NewIOFS(fs)
} }
manager := docker_support.NewRetriableClientManager(nil, 3*time.Second, tls.Certificate{}, docker_support.NewDockerClientService(client, container.ContainerFilter{})) manager := docker_support.NewRetriableClientManager(nil, 3*time.Second, tls.Certificate{}, docker_support.NewDockerClientService(client, container.ContainerLabels{}))
multiHostService := docker_support.NewMultiHostService(manager, 3*time.Second) multiHostService := docker_support.NewMultiHostService(manager, 3*time.Second)
return createRouter(&handler{ return createRouter(&handler{
multiHostService: multiHostService, hostService: multiHostService,
content: content, content: content,
config: &config, config: &config,
}) })
} }

33
main.go
View File

@@ -19,8 +19,10 @@ import (
"github.com/amir20/dozzle/internal/container" "github.com/amir20/dozzle/internal/container"
"github.com/amir20/dozzle/internal/docker" "github.com/amir20/dozzle/internal/docker"
"github.com/amir20/dozzle/internal/healthcheck" "github.com/amir20/dozzle/internal/healthcheck"
"github.com/amir20/dozzle/internal/k8s"
"github.com/amir20/dozzle/internal/support/cli" "github.com/amir20/dozzle/internal/support/cli"
docker_support "github.com/amir20/dozzle/internal/support/docker" docker_support "github.com/amir20/dozzle/internal/support/docker"
k8s_support "github.com/amir20/dozzle/internal/support/k8s"
"github.com/amir20/dozzle/internal/web" "github.com/amir20/dozzle/internal/web"
"github.com/rs/zerolog/log" "github.com/rs/zerolog/log"
) )
@@ -160,17 +162,17 @@ func main() {
log.Info().Msgf("Dozzle version %s", args.Version()) log.Info().Msgf("Dozzle version %s", args.Version())
var multiHostService *docker_support.MultiHostService var hostService web.HostService
if args.Mode == "server" { if args.Mode == "server" {
var localClient container.Client var localClient container.Client
localClient, multiHostService = cli.CreateMultiHostService(certs, args) localClient, multiHostService := cli.CreateMultiHostService(certs, args)
if multiHostService.TotalClients() == 0 { if multiHostService.TotalClients() == 0 {
log.Fatal().Msg("Could not connect to any Docker Engine") log.Fatal().Msg("Could not connect to any Docker Engine")
} else { } else {
log.Info().Int("clients", multiHostService.TotalClients()).Msg("Connected to Docker") log.Info().Int("clients", multiHostService.TotalClients()).Msg("Connected to Docker")
} }
go cli.StartEvent(args, "server", localClient, "") go cli.StartEvent(args, "server", localClient, "")
hostService = multiHostService
} else if args.Mode == "swarm" { } else if args.Mode == "swarm" {
localClient, err := docker.NewLocalClient("") localClient, err := docker.NewLocalClient("")
if err != nil { if err != nil {
@@ -182,7 +184,7 @@ func main() {
} }
agentManager := docker_support.NewRetriableClientManager(args.RemoteAgent, args.Timeout, certs) agentManager := docker_support.NewRetriableClientManager(args.RemoteAgent, args.Timeout, certs)
manager := docker_support.NewSwarmClientManager(localClient, certs, args.Timeout, agentManager, args.Filter) manager := docker_support.NewSwarmClientManager(localClient, certs, args.Timeout, agentManager, args.Filter)
multiHostService = docker_support.NewMultiHostService(manager, args.Timeout) hostService = docker_support.NewMultiHostService(manager, args.Timeout)
log.Info().Msg("Starting in swarm mode") log.Info().Msg("Starting in swarm mode")
listener, err := net.Listen("tcp", ":7007") listener, err := net.Listen("tcp", ":7007")
if err != nil { if err != nil {
@@ -194,16 +196,29 @@ func main() {
} }
go cli.StartEvent(args, "swarm", localClient, "") go cli.StartEvent(args, "swarm", localClient, "")
go func() { go func() {
log.Info().Msgf("Dozzle agent version %s", args.Version()) log.Info().Msgf("Dozzle agent version in swarm mode %s", args.Version())
if err := server.Serve(listener); err != nil { if err := server.Serve(listener); err != nil {
log.Error().Err(err).Msg("failed to serve") log.Error().Err(err).Msg("failed to serve")
} }
}() }()
} else if args.Mode == "k8s" {
localClient, err := k8s.NewK8sClient("default")
if err != nil {
log.Fatal().Err(err).Msg("Could not create k8s client")
}
clusterService, err := k8s_support.NewK8sClusterService(localClient, args.Timeout)
if err != nil {
log.Fatal().Err(err).Msg("Could not create k8s cluster service")
}
go cli.StartEvent(args, "k8s", nil, "")
hostService = clusterService
} else { } else {
log.Fatal().Str("mode", args.Mode).Msg("Invalid mode") log.Fatal().Str("mode", args.Mode).Msg("Invalid mode")
} }
srv := createServer(args, multiHostService) srv := createServer(args, hostService)
go func() { go func() {
log.Info().Msgf("Accepting connections on %s", args.Addr) log.Info().Msgf("Accepting connections on %s", args.Addr)
if err := srv.ListenAndServe(); err != http.ErrServerClosed { if err := srv.ListenAndServe(); err != http.ErrServerClosed {
@@ -224,7 +239,7 @@ func main() {
log.Debug().Msg("shut down complete") log.Debug().Msg("shut down complete")
} }
func createServer(args cli.Args, multiHostService *docker_support.MultiHostService) *http.Server { func createServer(args cli.Args, hostService web.HostService) *http.Server {
_, dev := os.LookupEnv("DEV") _, dev := os.LookupEnv("DEV")
var provider web.AuthProvider = web.NONE var provider web.AuthProvider = web.NONE
@@ -286,7 +301,7 @@ func createServer(args cli.Args, multiHostService *docker_support.MultiHostServi
TTL: authTTL, TTL: authTTL,
}, },
EnableActions: args.EnableActions, EnableActions: args.EnableActions,
Filter: args.Filter, Labels: args.Filter,
} }
assets, err := fs.Sub(content, "dist") assets, err := fs.Sub(content, "dist")
@@ -313,5 +328,5 @@ func createServer(args cli.Args, multiHostService *docker_support.MultiHostServi
} }
} }
return web.CreateServer(multiHostService, assets, config) return web.CreateServer(hostService, assets, config)
} }