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:
1
assets/components.d.ts
vendored
1
assets/components.d.ts
vendored
@@ -12,6 +12,7 @@ declare module 'vue' {
|
||||
'Carbon:circleSolid': typeof import('~icons/carbon/circle-solid')['default']
|
||||
'Carbon:copyFile': typeof import('~icons/carbon/copy-file')['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:play': typeof import('~icons/carbon/play')['default']
|
||||
'Carbon:restart': typeof import('~icons/carbon/restart')['default']
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
<mdi:satellite-variant v-if="type == 'agent'" />
|
||||
<ph:globe-simple v-else-if="type == 'remote'" />
|
||||
<mdi:hexagon-multiple v-else-if="type == 'swarm'" />
|
||||
<carbon:logo-kubernetes v-else-if="type == 'k8s'" />
|
||||
<ph:computer-tower v-else />
|
||||
</template>
|
||||
<script setup lang="ts">
|
||||
|
||||
@@ -3,7 +3,7 @@ export type Host = {
|
||||
name: string;
|
||||
nCPU: number;
|
||||
memTotal: number;
|
||||
type: "agent" | "local" | "remote" | "swarm";
|
||||
type: "agent" | "local" | "remote" | "swarm" | "k8s";
|
||||
endpoint: string;
|
||||
available: boolean;
|
||||
dockerVersion: string;
|
||||
|
||||
18
examples/ingress.yml
Normal file
18
examples/ingress.yml
Normal 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
71
examples/k8s.dozzle.yml
Normal 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
38
go.mod
@@ -36,6 +36,10 @@ require (
|
||||
golang.org/x/sync v0.11.0
|
||||
google.golang.org/grpc v1.70.0
|
||||
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 (
|
||||
@@ -45,14 +49,25 @@ require (
|
||||
github.com/buger/jsonparser v1.1.1 // indirect
|
||||
github.com/cenkalti/backoff/v4 v4.3.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/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/fxamacker/cbor/v2 v2.7.0 // indirect
|
||||
github.com/go-logr/logr v1.4.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/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/httpcc v1.0.1 // 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-isatty v0.0.20 // 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/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/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/otel v1.32.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/sdk 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/time v0.8.0 // 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
|
||||
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
|
||||
|
||||
189
go.sum
189
go.sum
@@ -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/Microsoft/go-winio v0.6.2 h1:F2VQgta7ecxGYO8k3ZZz3RS8fVIXVxONVUPlNERoyfY=
|
||||
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/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/go.mod h1:A7vTJzvjoaSTypg4biM5uYNTkJ27SkNTArtYXnlqVO8=
|
||||
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/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/go.mod h1:xNd9bqTn98Ln4DwST8/nG+H0yuB8Hmgu1YHNnWw0GeA=
|
||||
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/go.mod h1:VRRf09a7mHDIRezVKTRCrOq78v577GXq3bSa3EhrzVo=
|
||||
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/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.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/go.mod h1:v57UDF4pDQJcEfFUCRop3lJL149eHGSe9Jvczhzjo/0=
|
||||
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/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/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk=
|
||||
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/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/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/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U=
|
||||
github.com/go-chi/chi/v5 v5.1.0 h1:acVI1TYaD+hhedDJ3r54HyA6sExp3HfXq7QWEEY/xMw=
|
||||
github.com/go-chi/chi/v5 v5.1.0/go.mod h1:DslCQbL2OYiznFReuXYUmQ2hGd1aDpCnlMNITLSKoi8=
|
||||
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/fxamacker/cbor/v2 v2.7.0 h1:iM5WgngdRBanHcxugY4JySA0nk1wZorNOpTgCMedv5E=
|
||||
github.com/fxamacker/cbor/v2 v2.7.0/go.mod h1:pxXPTn3joSm21Gbwsv0w9OSA2y1HFR9qXEeXQVeNoDQ=
|
||||
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/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/go.mod h1:O4QvPRuZLZghl9WvfVaON+ARfGzpD2PBX/QY5vUz7aQ=
|
||||
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/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag=
|
||||
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/go.mod h1:oq7eo15ShAhp70Anwd5lgX2pLfOS3QCiwU/PULtXL6M=
|
||||
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/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek=
|
||||
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.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/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/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/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/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.2.1 h1:Fmg33tUaq4/8ym9TJN1x7sLJnHVwhP33CNkpYV/7rwI=
|
||||
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/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
|
||||
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/option v1.0.1 h1:oAzP2fvZGQKWkvHa1/SAcFolBEca1oN+mQ7eooNBEYU=
|
||||
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/go.mod h1:Dhd985XPs7jluiymwWYZ0G4Z61jb3vdS329zhj2hYo0=
|
||||
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/term v0.0.0-20201216013528-df9cb8a40635 h1:rzf0wL0CHVc8CEsgyygG0Mn9CNCCPZqOPaz8RiiHYQk=
|
||||
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/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/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM=
|
||||
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.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
|
||||
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/puzpuzpuz/xsync/v3 v3.4.0 h1:DuVBAdXuGFHv8adVXjWWZ63pJq+NRXOWVXlKDBZ+mJ4=
|
||||
github.com/puzpuzpuz/xsync/v3 v3.4.0/go.mod h1:VjzYrABPabuM4KyBh1Ftq6u8nhwY5tBPKP9jpmh0nnA=
|
||||
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/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U=
|
||||
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
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/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/zerolog v1.33.0 h1:1cU2KZkvPxNyfgEmhHAz/1A9Bz+llsdYzklWFzgp0r8=
|
||||
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/go.mod h1:dO6KHFzUKXgP8LDhU0oI8d2hekjXnGOu0DB8Jecxd6o=
|
||||
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/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ=
|
||||
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/go.mod h1:ZTlWwG4/ahT8W7T0WQ5uYmjI9duaLQGy3Q2OAl4sk/4=
|
||||
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.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/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA=
|
||||
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.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.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg=
|
||||
github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
|
||||
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
|
||||
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/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/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.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
||||
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/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/sdk v1.22.0 h1:6coWHw9xw7EfClIC/+O31R8IY3/+EiRFHevmHafB2Gw=
|
||||
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 h1:RNxepc9vK59A8XsgZQouW8ue8Gkb4jpWtJm9ge5lEG4=
|
||||
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/go.mod h1:+i4rkvCraA+tG6AzwloGaCtkx53Fa+L+V8e9a7YvhT8=
|
||||
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.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.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.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/go.mod h1:bVdXmD7IV/4GdElGPozy6U7lWdRXA4qyRVGJV57uQ5M=
|
||||
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-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.9.0/go.mod h1:d48xBJpPfHeWQsugry2m+kC02ZBRGRgulfHnEXEuWns=
|
||||
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.21.0/go.mod h1:bIjVDfnllIU7BJ2DNgfnXvpSvtn8VRwhlsaeUTyUS44=
|
||||
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/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-20190911185100-cd5d95a43a6e/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.6.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.11.0 h1:GGz8+XQP4FvTTrjZPzNKTMFtSXH80RAzG+5ghFPgK9w=
|
||||
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.5.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.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.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.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/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
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-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.7.0/go.mod h1:P32HKFT3hSsZrRxla30E9HqToFYAQPCMs/zFMBUFqPY=
|
||||
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.17.0/go.mod h1:lLRBjIVuehSbZlaOtGMbcMncT+aqLLLmKrsjNrUguwk=
|
||||
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.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.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
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.14.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.22.0 h1:bofq7m3/HAFvbF51jz3Q9wLg3jkvSPuiZu/pD1XwgtM=
|
||||
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.5.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM=
|
||||
golang.org/x/time v0.8.0 h1:9i3RxcPv3PZnitoVGMPDKZSq1xW1gK1Xy3ArNOGZfEg=
|
||||
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-20190624222133-a101b041ded4/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
|
||||
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.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.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-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-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/googleapis/api v0.0.0-20240903143218-8af14fe29dc1 h1:hjSy6tcFQZ171igDaN5QHOw2n6vx40juYbC/x67CEhc=
|
||||
google.golang.org/genproto/googleapis/api v0.0.0-20240903143218-8af14fe29dc1/go.mod h1:qpvKtACPCQhAdu3PyQgV4l3LMXZEtft7y8QcarRsp9I=
|
||||
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 v0.0.0-20241118233622-e639e219e697 h1:ToEetK57OidYuqD4Q5w+vfEnPvPpuTwedCNVohYJfNk=
|
||||
google.golang.org/genproto/googleapis/api v0.0.0-20241209162323-e6fa225c2576 h1:CkkIfIt50+lT6NHAVoRYEyAvQGFM7xEwXUUywFvEb3Q=
|
||||
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-20241223144023-3abc09e42ca8 h1:TqExAhdPaB60Ux47Cn0oLV07rGnxZzIsaRhQaqS666A=
|
||||
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/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/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 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-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.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
|
||||
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.3 h1:4AuOwCGf4lLR9u3YOe2awrHygurzhO/HeQ6laiA6Sx0=
|
||||
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=
|
||||
|
||||
@@ -306,12 +306,12 @@ func (c *Client) FindContainer(ctx context.Context, containerID string) (contain
|
||||
}, 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{}
|
||||
|
||||
if filter != nil {
|
||||
if labels != nil {
|
||||
in.Filter = make(map[string]*pb.RepeatedString)
|
||||
for k, v := range filter {
|
||||
for k, v := range labels {
|
||||
in.Filter[k] = &pb.RepeatedString{Values: v}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -46,7 +46,7 @@ func (m *MockedClient) ContainerEvents(ctx context.Context, events chan<- contai
|
||||
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)
|
||||
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),
|
||||
}, nil)
|
||||
|
||||
server, _ := NewServer(client, certs, "test", container.ContainerFilter{})
|
||||
server, _ := NewServer(client, certs, "test", container.ContainerLabels{})
|
||||
|
||||
go server.Serve(lis)
|
||||
}
|
||||
@@ -173,7 +173,7 @@ func TestListContainers(t *testing.T) {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
containers, _ := rpc.ListContainers(context.Background(), container.ContainerFilter{})
|
||||
containers, _ := rpc.ListContainers(context.Background(), container.ContainerLabels{})
|
||||
|
||||
assert.Equal(t, containers, []container.Container{
|
||||
{
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
// Code generated by protoc-gen-go. DO NOT EDIT.
|
||||
// versions:
|
||||
// protoc-gen-go v1.35.1
|
||||
// protoc-gen-go v1.36.2
|
||||
// protoc v5.29.3
|
||||
// source: rpc.proto
|
||||
|
||||
@@ -22,11 +22,10 @@ const (
|
||||
)
|
||||
|
||||
type ListContainersRequest struct {
|
||||
state protoimpl.MessageState
|
||||
sizeCache protoimpl.SizeCache
|
||||
state protoimpl.MessageState `protogen:"open.v1"`
|
||||
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
|
||||
|
||||
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"`
|
||||
sizeCache protoimpl.SizeCache
|
||||
}
|
||||
|
||||
func (x *ListContainersRequest) Reset() {
|
||||
@@ -67,11 +66,10 @@ func (x *ListContainersRequest) GetFilter() map[string]*RepeatedString {
|
||||
}
|
||||
|
||||
type RepeatedString struct {
|
||||
state protoimpl.MessageState
|
||||
sizeCache protoimpl.SizeCache
|
||||
unknownFields protoimpl.UnknownFields
|
||||
|
||||
state protoimpl.MessageState `protogen:"open.v1"`
|
||||
Values []string `protobuf:"bytes,1,rep,name=values,proto3" json:"values,omitempty"`
|
||||
unknownFields protoimpl.UnknownFields
|
||||
sizeCache protoimpl.SizeCache
|
||||
}
|
||||
|
||||
func (x *RepeatedString) Reset() {
|
||||
@@ -112,11 +110,10 @@ func (x *RepeatedString) GetValues() []string {
|
||||
}
|
||||
|
||||
type ListContainersResponse struct {
|
||||
state protoimpl.MessageState
|
||||
sizeCache protoimpl.SizeCache
|
||||
unknownFields protoimpl.UnknownFields
|
||||
|
||||
state protoimpl.MessageState `protogen:"open.v1"`
|
||||
Containers []*Container `protobuf:"bytes,1,rep,name=containers,proto3" json:"containers,omitempty"`
|
||||
unknownFields protoimpl.UnknownFields
|
||||
sizeCache protoimpl.SizeCache
|
||||
}
|
||||
|
||||
func (x *ListContainersResponse) Reset() {
|
||||
@@ -157,12 +154,11 @@ func (x *ListContainersResponse) GetContainers() []*Container {
|
||||
}
|
||||
|
||||
type FindContainerRequest struct {
|
||||
state protoimpl.MessageState
|
||||
sizeCache protoimpl.SizeCache
|
||||
unknownFields protoimpl.UnknownFields
|
||||
|
||||
state protoimpl.MessageState `protogen:"open.v1"`
|
||||
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"`
|
||||
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
|
||||
sizeCache protoimpl.SizeCache
|
||||
}
|
||||
|
||||
func (x *FindContainerRequest) Reset() {
|
||||
@@ -210,11 +206,10 @@ func (x *FindContainerRequest) GetFilter() map[string]*RepeatedString {
|
||||
}
|
||||
|
||||
type FindContainerResponse struct {
|
||||
state protoimpl.MessageState
|
||||
sizeCache protoimpl.SizeCache
|
||||
unknownFields protoimpl.UnknownFields
|
||||
|
||||
state protoimpl.MessageState `protogen:"open.v1"`
|
||||
Container *Container `protobuf:"bytes,1,opt,name=container,proto3" json:"container,omitempty"`
|
||||
unknownFields protoimpl.UnknownFields
|
||||
sizeCache protoimpl.SizeCache
|
||||
}
|
||||
|
||||
func (x *FindContainerResponse) Reset() {
|
||||
@@ -255,13 +250,12 @@ func (x *FindContainerResponse) GetContainer() *Container {
|
||||
}
|
||||
|
||||
type StreamLogsRequest struct {
|
||||
state protoimpl.MessageState
|
||||
sizeCache protoimpl.SizeCache
|
||||
unknownFields protoimpl.UnknownFields
|
||||
|
||||
state protoimpl.MessageState `protogen:"open.v1"`
|
||||
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
|
||||
sizeCache protoimpl.SizeCache
|
||||
}
|
||||
|
||||
func (x *StreamLogsRequest) Reset() {
|
||||
@@ -316,11 +310,10 @@ func (x *StreamLogsRequest) GetStreamTypes() int32 {
|
||||
}
|
||||
|
||||
type StreamLogsResponse struct {
|
||||
state protoimpl.MessageState
|
||||
sizeCache protoimpl.SizeCache
|
||||
unknownFields protoimpl.UnknownFields
|
||||
|
||||
state protoimpl.MessageState `protogen:"open.v1"`
|
||||
Event *LogEvent `protobuf:"bytes,1,opt,name=event,proto3" json:"event,omitempty"`
|
||||
unknownFields protoimpl.UnknownFields
|
||||
sizeCache protoimpl.SizeCache
|
||||
}
|
||||
|
||||
func (x *StreamLogsResponse) Reset() {
|
||||
@@ -361,14 +354,13 @@ func (x *StreamLogsResponse) GetEvent() *LogEvent {
|
||||
}
|
||||
|
||||
type LogsBetweenDatesRequest struct {
|
||||
state protoimpl.MessageState
|
||||
sizeCache protoimpl.SizeCache
|
||||
unknownFields protoimpl.UnknownFields
|
||||
|
||||
state protoimpl.MessageState `protogen:"open.v1"`
|
||||
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
|
||||
sizeCache protoimpl.SizeCache
|
||||
}
|
||||
|
||||
func (x *LogsBetweenDatesRequest) Reset() {
|
||||
@@ -430,14 +422,13 @@ func (x *LogsBetweenDatesRequest) GetStreamTypes() int32 {
|
||||
}
|
||||
|
||||
type StreamRawBytesRequest struct {
|
||||
state protoimpl.MessageState
|
||||
sizeCache protoimpl.SizeCache
|
||||
unknownFields protoimpl.UnknownFields
|
||||
|
||||
state protoimpl.MessageState `protogen:"open.v1"`
|
||||
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
|
||||
sizeCache protoimpl.SizeCache
|
||||
}
|
||||
|
||||
func (x *StreamRawBytesRequest) Reset() {
|
||||
@@ -499,11 +490,10 @@ func (x *StreamRawBytesRequest) GetStreamTypes() int32 {
|
||||
}
|
||||
|
||||
type StreamRawBytesResponse struct {
|
||||
state protoimpl.MessageState
|
||||
sizeCache protoimpl.SizeCache
|
||||
unknownFields protoimpl.UnknownFields
|
||||
|
||||
state protoimpl.MessageState `protogen:"open.v1"`
|
||||
Data []byte `protobuf:"bytes,1,opt,name=data,proto3" json:"data,omitempty"`
|
||||
unknownFields protoimpl.UnknownFields
|
||||
sizeCache protoimpl.SizeCache
|
||||
}
|
||||
|
||||
func (x *StreamRawBytesResponse) Reset() {
|
||||
@@ -544,9 +534,9 @@ func (x *StreamRawBytesResponse) GetData() []byte {
|
||||
}
|
||||
|
||||
type StreamEventsRequest struct {
|
||||
state protoimpl.MessageState
|
||||
sizeCache protoimpl.SizeCache
|
||||
state protoimpl.MessageState `protogen:"open.v1"`
|
||||
unknownFields protoimpl.UnknownFields
|
||||
sizeCache protoimpl.SizeCache
|
||||
}
|
||||
|
||||
func (x *StreamEventsRequest) Reset() {
|
||||
@@ -580,11 +570,10 @@ func (*StreamEventsRequest) Descriptor() ([]byte, []int) {
|
||||
}
|
||||
|
||||
type StreamEventsResponse struct {
|
||||
state protoimpl.MessageState
|
||||
sizeCache protoimpl.SizeCache
|
||||
unknownFields protoimpl.UnknownFields
|
||||
|
||||
state protoimpl.MessageState `protogen:"open.v1"`
|
||||
Event *ContainerEvent `protobuf:"bytes,1,opt,name=event,proto3" json:"event,omitempty"`
|
||||
unknownFields protoimpl.UnknownFields
|
||||
sizeCache protoimpl.SizeCache
|
||||
}
|
||||
|
||||
func (x *StreamEventsResponse) Reset() {
|
||||
@@ -625,9 +614,9 @@ func (x *StreamEventsResponse) GetEvent() *ContainerEvent {
|
||||
}
|
||||
|
||||
type StreamStatsRequest struct {
|
||||
state protoimpl.MessageState
|
||||
sizeCache protoimpl.SizeCache
|
||||
state protoimpl.MessageState `protogen:"open.v1"`
|
||||
unknownFields protoimpl.UnknownFields
|
||||
sizeCache protoimpl.SizeCache
|
||||
}
|
||||
|
||||
func (x *StreamStatsRequest) Reset() {
|
||||
@@ -661,11 +650,10 @@ func (*StreamStatsRequest) Descriptor() ([]byte, []int) {
|
||||
}
|
||||
|
||||
type StreamStatsResponse struct {
|
||||
state protoimpl.MessageState
|
||||
sizeCache protoimpl.SizeCache
|
||||
unknownFields protoimpl.UnknownFields
|
||||
|
||||
state protoimpl.MessageState `protogen:"open.v1"`
|
||||
Stat *ContainerStat `protobuf:"bytes,1,opt,name=stat,proto3" json:"stat,omitempty"`
|
||||
unknownFields protoimpl.UnknownFields
|
||||
sizeCache protoimpl.SizeCache
|
||||
}
|
||||
|
||||
func (x *StreamStatsResponse) Reset() {
|
||||
@@ -706,9 +694,9 @@ func (x *StreamStatsResponse) GetStat() *ContainerStat {
|
||||
}
|
||||
|
||||
type HostInfoRequest struct {
|
||||
state protoimpl.MessageState
|
||||
sizeCache protoimpl.SizeCache
|
||||
state protoimpl.MessageState `protogen:"open.v1"`
|
||||
unknownFields protoimpl.UnknownFields
|
||||
sizeCache protoimpl.SizeCache
|
||||
}
|
||||
|
||||
func (x *HostInfoRequest) Reset() {
|
||||
@@ -742,11 +730,10 @@ func (*HostInfoRequest) Descriptor() ([]byte, []int) {
|
||||
}
|
||||
|
||||
type HostInfoResponse struct {
|
||||
state protoimpl.MessageState
|
||||
sizeCache protoimpl.SizeCache
|
||||
unknownFields protoimpl.UnknownFields
|
||||
|
||||
state protoimpl.MessageState `protogen:"open.v1"`
|
||||
Host *Host `protobuf:"bytes,1,opt,name=host,proto3" json:"host,omitempty"`
|
||||
unknownFields protoimpl.UnknownFields
|
||||
sizeCache protoimpl.SizeCache
|
||||
}
|
||||
|
||||
func (x *HostInfoResponse) Reset() {
|
||||
@@ -787,9 +774,9 @@ func (x *HostInfoResponse) GetHost() *Host {
|
||||
}
|
||||
|
||||
type StreamContainerStartedRequest struct {
|
||||
state protoimpl.MessageState
|
||||
sizeCache protoimpl.SizeCache
|
||||
state protoimpl.MessageState `protogen:"open.v1"`
|
||||
unknownFields protoimpl.UnknownFields
|
||||
sizeCache protoimpl.SizeCache
|
||||
}
|
||||
|
||||
func (x *StreamContainerStartedRequest) Reset() {
|
||||
@@ -823,11 +810,10 @@ func (*StreamContainerStartedRequest) Descriptor() ([]byte, []int) {
|
||||
}
|
||||
|
||||
type StreamContainerStartedResponse struct {
|
||||
state protoimpl.MessageState
|
||||
sizeCache protoimpl.SizeCache
|
||||
unknownFields protoimpl.UnknownFields
|
||||
|
||||
state protoimpl.MessageState `protogen:"open.v1"`
|
||||
Container *Container `protobuf:"bytes,1,opt,name=container,proto3" json:"container,omitempty"`
|
||||
unknownFields protoimpl.UnknownFields
|
||||
sizeCache protoimpl.SizeCache
|
||||
}
|
||||
|
||||
func (x *StreamContainerStartedResponse) Reset() {
|
||||
@@ -868,12 +854,11 @@ func (x *StreamContainerStartedResponse) GetContainer() *Container {
|
||||
}
|
||||
|
||||
type ContainerActionRequest struct {
|
||||
state protoimpl.MessageState
|
||||
sizeCache protoimpl.SizeCache
|
||||
unknownFields protoimpl.UnknownFields
|
||||
|
||||
state protoimpl.MessageState `protogen:"open.v1"`
|
||||
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
|
||||
sizeCache protoimpl.SizeCache
|
||||
}
|
||||
|
||||
func (x *ContainerActionRequest) Reset() {
|
||||
@@ -921,9 +906,9 @@ func (x *ContainerActionRequest) GetAction() ContainerAction {
|
||||
}
|
||||
|
||||
type ContainerActionResponse struct {
|
||||
state protoimpl.MessageState
|
||||
sizeCache protoimpl.SizeCache
|
||||
state protoimpl.MessageState `protogen:"open.v1"`
|
||||
unknownFields protoimpl.UnknownFields
|
||||
sizeCache protoimpl.SizeCache
|
||||
}
|
||||
|
||||
func (x *ContainerActionResponse) Reset() {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
// Code generated by protoc-gen-go. DO NOT EDIT.
|
||||
// versions:
|
||||
// protoc-gen-go v1.35.1
|
||||
// protoc-gen-go v1.36.2
|
||||
// protoc v5.29.3
|
||||
// source: types.proto
|
||||
|
||||
@@ -72,10 +72,7 @@ func (ContainerAction) EnumDescriptor() ([]byte, []int) {
|
||||
}
|
||||
|
||||
type Container struct {
|
||||
state protoimpl.MessageState
|
||||
sizeCache protoimpl.SizeCache
|
||||
unknownFields protoimpl.UnknownFields
|
||||
|
||||
state protoimpl.MessageState `protogen:"open.v1"`
|
||||
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"`
|
||||
@@ -87,11 +84,13 @@ type Container struct {
|
||||
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"`
|
||||
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
|
||||
sizeCache protoimpl.SizeCache
|
||||
}
|
||||
|
||||
func (x *Container) Reset() {
|
||||
@@ -237,14 +236,13 @@ func (x *Container) GetFinished() *timestamppb.Timestamp {
|
||||
}
|
||||
|
||||
type ContainerStat struct {
|
||||
state protoimpl.MessageState
|
||||
sizeCache protoimpl.SizeCache
|
||||
unknownFields protoimpl.UnknownFields
|
||||
|
||||
state protoimpl.MessageState `protogen:"open.v1"`
|
||||
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
|
||||
sizeCache protoimpl.SizeCache
|
||||
}
|
||||
|
||||
func (x *ContainerStat) Reset() {
|
||||
@@ -306,10 +304,7 @@ func (x *ContainerStat) GetMemoryPercent() float64 {
|
||||
}
|
||||
|
||||
type LogEvent struct {
|
||||
state protoimpl.MessageState
|
||||
sizeCache protoimpl.SizeCache
|
||||
unknownFields protoimpl.UnknownFields
|
||||
|
||||
state protoimpl.MessageState `protogen:"open.v1"`
|
||||
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"`
|
||||
@@ -317,6 +312,8 @@ type LogEvent struct {
|
||||
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
|
||||
sizeCache protoimpl.SizeCache
|
||||
}
|
||||
|
||||
func (x *LogEvent) Reset() {
|
||||
@@ -399,11 +396,10 @@ func (x *LogEvent) GetPosition() string {
|
||||
}
|
||||
|
||||
type SimpleMessage struct {
|
||||
state protoimpl.MessageState
|
||||
sizeCache protoimpl.SizeCache
|
||||
unknownFields protoimpl.UnknownFields
|
||||
|
||||
state protoimpl.MessageState `protogen:"open.v1"`
|
||||
Message string `protobuf:"bytes,1,opt,name=message,proto3" json:"message,omitempty"`
|
||||
unknownFields protoimpl.UnknownFields
|
||||
sizeCache protoimpl.SizeCache
|
||||
}
|
||||
|
||||
func (x *SimpleMessage) Reset() {
|
||||
@@ -444,11 +440,10 @@ func (x *SimpleMessage) GetMessage() string {
|
||||
}
|
||||
|
||||
type ComplexMessage struct {
|
||||
state protoimpl.MessageState
|
||||
sizeCache protoimpl.SizeCache
|
||||
unknownFields protoimpl.UnknownFields
|
||||
|
||||
state protoimpl.MessageState `protogen:"open.v1"`
|
||||
Data []byte `protobuf:"bytes,1,opt,name=data,proto3" json:"data,omitempty"`
|
||||
unknownFields protoimpl.UnknownFields
|
||||
sizeCache protoimpl.SizeCache
|
||||
}
|
||||
|
||||
func (x *ComplexMessage) Reset() {
|
||||
@@ -489,14 +484,13 @@ func (x *ComplexMessage) GetData() []byte {
|
||||
}
|
||||
|
||||
type ContainerEvent struct {
|
||||
state protoimpl.MessageState
|
||||
sizeCache protoimpl.SizeCache
|
||||
unknownFields protoimpl.UnknownFields
|
||||
|
||||
state protoimpl.MessageState `protogen:"open.v1"`
|
||||
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
|
||||
sizeCache protoimpl.SizeCache
|
||||
}
|
||||
|
||||
func (x *ContainerEvent) Reset() {
|
||||
@@ -558,15 +552,12 @@ func (x *ContainerEvent) GetTimestamp() *timestamppb.Timestamp {
|
||||
}
|
||||
|
||||
type Host struct {
|
||||
state protoimpl.MessageState
|
||||
sizeCache protoimpl.SizeCache
|
||||
unknownFields protoimpl.UnknownFields
|
||||
|
||||
state protoimpl.MessageState `protogen:"open.v1"`
|
||||
Id string `protobuf:"bytes,1,opt,name=id,proto3" json:"id,omitempty"`
|
||||
Name string `protobuf:"bytes,2,opt,name=name,proto3" json:"name,omitempty"`
|
||||
NodeAddress string `protobuf:"bytes,3,opt,name=nodeAddress,proto3" json:"nodeAddress,omitempty"`
|
||||
Swarm bool `protobuf:"varint,4,opt,name=swarm,proto3" json:"swarm,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"`
|
||||
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"`
|
||||
OperatingSystem string `protobuf:"bytes,6,opt,name=operatingSystem,proto3" json:"operatingSystem,omitempty"`
|
||||
OsVersion string `protobuf:"bytes,7,opt,name=osVersion,proto3" json:"osVersion,omitempty"`
|
||||
OsType string `protobuf:"bytes,8,opt,name=osType,proto3" json:"osType,omitempty"`
|
||||
@@ -574,6 +565,8 @@ type Host struct {
|
||||
Memory uint64 `protobuf:"varint,10,opt,name=memory,proto3" json:"memory,omitempty"`
|
||||
AgentVersion string `protobuf:"bytes,11,opt,name=agentVersion,proto3" json:"agentVersion,omitempty"`
|
||||
DockerVersion string `protobuf:"bytes,12,opt,name=dockerVersion,proto3" json:"dockerVersion,omitempty"`
|
||||
unknownFields protoimpl.UnknownFields
|
||||
sizeCache protoimpl.SizeCache
|
||||
}
|
||||
|
||||
func (x *Host) Reset() {
|
||||
|
||||
@@ -13,6 +13,7 @@ import (
|
||||
|
||||
"github.com/amir20/dozzle/internal/agent/pb"
|
||||
"github.com/amir20/dozzle/internal/container"
|
||||
"github.com/amir20/dozzle/internal/docker"
|
||||
"github.com/rs/zerolog/log"
|
||||
orderedmap "github.com/wk8/go-ordered-map/v2"
|
||||
"google.golang.org/grpc"
|
||||
@@ -32,12 +33,13 @@ type server struct {
|
||||
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{
|
||||
client: client,
|
||||
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()
|
||||
}
|
||||
|
||||
c, err := s.store.FindContainer(in.ContainerId, container.ContainerFilter{})
|
||||
c, err := s.store.FindContainer(in.ContainerId, container.ContainerLabels{})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -57,7 +59,8 @@ func (s *server) StreamLogs(in *pb.StreamLogsRequest, out pb.AgentService_Stream
|
||||
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 {
|
||||
out.Send(&pb.StreamLogsResponse{
|
||||
@@ -84,7 +87,8 @@ func (s *server) LogsBetweenDates(in *pb.LogsBetweenDatesRequest, out pb.AgentSe
|
||||
return err
|
||||
}
|
||||
|
||||
g := container.NewEventGenerator(out.Context(), reader, c)
|
||||
dockerReader := docker.NewLogReader(reader, c.Tty)
|
||||
g := container.NewEventGenerator(out.Context(), dockerReader, c)
|
||||
|
||||
for {
|
||||
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) {
|
||||
filter := make(container.ContainerFilter)
|
||||
labels := make(container.ContainerLabels)
|
||||
if in.GetFilter() != nil {
|
||||
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 {
|
||||
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) {
|
||||
filter := make(container.ContainerFilter)
|
||||
labels := make(container.ContainerLabels)
|
||||
if in.GetFilter() != nil {
|
||||
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 {
|
||||
return nil, err
|
||||
}
|
||||
@@ -322,7 +326,7 @@ func (s *server) ContainerAction(ctx context.Context, in *pb.ContainerActionRequ
|
||||
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()
|
||||
c, err := x509.ParseCertificate(certificates.Certificate[0])
|
||||
if err != nil {
|
||||
@@ -341,7 +345,7 @@ func NewServer(client container.Client, certificates tls.Certificate, dozzleVers
|
||||
creds := credentials.NewTLS(tlsConfig)
|
||||
|
||||
grpcServer := grpc.NewServer(grpc.Creds(creds))
|
||||
pb.RegisterAgentServiceServer(grpcServer, newServer(client, dozzleVersion, filter))
|
||||
pb.RegisterAgentServiceServer(grpcServer, newServer(client, dozzleVersion, labels))
|
||||
|
||||
return grpcServer, nil
|
||||
}
|
||||
|
||||
@@ -24,7 +24,7 @@ type User struct {
|
||||
Name string `json:"name" yaml:"name"`
|
||||
Password string `json:"-" yaml:"password"`
|
||||
Filter string `json:"-" yaml:"filter"`
|
||||
ContainerFilter container.ContainerFilter `json:"-" yaml:"-"`
|
||||
ContainerLabels container.ContainerLabels `json:"-" yaml:"-"`
|
||||
}
|
||||
|
||||
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))
|
||||
}
|
||||
|
||||
func newUser(username, email, name string, filter container.ContainerFilter) User {
|
||||
func newUser(username, email, name string, labels container.ContainerLabels) User {
|
||||
return User{
|
||||
Username: username,
|
||||
Email: email,
|
||||
Name: name,
|
||||
ContainerFilter: filter,
|
||||
ContainerLabels: labels,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -197,7 +197,7 @@ func UserFromContext(ctx context.Context) *User {
|
||||
}
|
||||
email := claims["email"].(string)
|
||||
name := claims["name"].(string)
|
||||
containerFilter := container.ContainerFilter{}
|
||||
containerFilter := container.ContainerLabels{}
|
||||
if filter, ok := claims["filter"].(string); ok {
|
||||
containerFilter, err = container.ParseContainerFilter(filter)
|
||||
if err != nil {
|
||||
|
||||
@@ -29,7 +29,7 @@ func (s StdType) String() string {
|
||||
}
|
||||
|
||||
type Client interface {
|
||||
ListContainers(context.Context, ContainerFilter) ([]Container, error)
|
||||
ListContainers(context.Context, ContainerLabels) ([]Container, error)
|
||||
FindContainer(context.Context, string) (Container, error)
|
||||
ContainerLogs(context.Context, string, time.Time, StdType) (io.ReadCloser, error)
|
||||
ContainerEvents(context.Context, chan<- ContainerEvent) error
|
||||
@@ -38,5 +38,4 @@ type Client interface {
|
||||
Ping(context.Context) error
|
||||
Host() Host
|
||||
ContainerActions(ctx context.Context, action ContainerAction, containerID string) error
|
||||
IsSwarmMode() bool
|
||||
}
|
||||
|
||||
@@ -13,34 +13,40 @@ import (
|
||||
"golang.org/x/sync/semaphore"
|
||||
)
|
||||
|
||||
type StatsCollector interface {
|
||||
Start(parentCtx context.Context) bool
|
||||
Subscribe(ctx context.Context, stats chan<- ContainerStat)
|
||||
Stop()
|
||||
}
|
||||
|
||||
type ContainerStore struct {
|
||||
containers *xsync.MapOf[string, *Container]
|
||||
subscribers *xsync.MapOf[context.Context, chan<- ContainerEvent]
|
||||
newContainerSubscribers *xsync.MapOf[context.Context, chan<- Container]
|
||||
client Client
|
||||
statsCollector *StatsCollector
|
||||
statsCollector StatsCollector
|
||||
wg sync.WaitGroup
|
||||
connected atomic.Bool
|
||||
events chan ContainerEvent
|
||||
ctx context.Context
|
||||
filter ContainerFilter
|
||||
labels ContainerLabels
|
||||
}
|
||||
|
||||
const defaultTimeout = 10 * time.Second
|
||||
|
||||
func NewContainerStore(ctx context.Context, client Client, filter ContainerFilter) *ContainerStore {
|
||||
log.Debug().Str("host", client.Host().Name).Interface("filter", filter).Msg("initializing container store")
|
||||
func NewContainerStore(ctx context.Context, client Client, statsCollect StatsCollector, labels ContainerLabels) *ContainerStore {
|
||||
log.Debug().Str("host", client.Host().Name).Interface("labels", labels).Msg("initializing container store")
|
||||
|
||||
s := &ContainerStore{
|
||||
containers: xsync.NewMapOf[string, *Container](),
|
||||
client: client,
|
||||
subscribers: xsync.NewMapOf[context.Context, chan<- ContainerEvent](),
|
||||
newContainerSubscribers: xsync.NewMapOf[context.Context, chan<- Container](),
|
||||
statsCollector: NewStatsCollector(client, filter),
|
||||
statsCollector: statsCollect,
|
||||
wg: sync.WaitGroup{},
|
||||
events: make(chan ContainerEvent),
|
||||
ctx: ctx,
|
||||
filter: filter,
|
||||
labels: labels,
|
||||
}
|
||||
|
||||
s.wg.Add(1)
|
||||
@@ -68,7 +74,7 @@ func (s *ContainerStore) checkConnectivity() error {
|
||||
|
||||
ctx, cancel := context.WithTimeout(context.Background(), defaultTimeout)
|
||||
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
|
||||
} else {
|
||||
s.containers.Clear()
|
||||
@@ -109,7 +115,7 @@ func (s *ContainerStore) checkConnectivity() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *ContainerStore) ListContainers(filter ContainerFilter) ([]Container, error) {
|
||||
func (s *ContainerStore) ListContainers(labels ContainerLabels) ([]Container, error) {
|
||||
s.wg.Wait()
|
||||
|
||||
if err := s.checkConnectivity(); err != nil {
|
||||
@@ -117,8 +123,8 @@ func (s *ContainerStore) ListContainers(filter ContainerFilter) ([]Container, er
|
||||
}
|
||||
|
||||
containers := make([]Container, 0)
|
||||
if filter.Exists() {
|
||||
validContainers, err := s.client.ListContainers(s.ctx, filter)
|
||||
if labels.Exists() {
|
||||
validContainers, err := s.client.ListContainers(s.ctx, labels)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -143,11 +149,10 @@ func (s *ContainerStore) ListContainers(filter ContainerFilter) ([]Container, er
|
||||
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()
|
||||
|
||||
if filter.Exists() {
|
||||
validContainers, err := s.client.ListContainers(s.ctx, filter)
|
||||
if labels.Exists() {
|
||||
validContainers, err := s.client.ListContainers(s.ctx, labels)
|
||||
if err != nil {
|
||||
return Container{}, err
|
||||
}
|
||||
@@ -245,11 +250,36 @@ func (s *ContainerStore) init() {
|
||||
case event := <-s.events:
|
||||
log.Trace().Str("event", event.Name).Str("id", event.ActorID).Msg("received container event")
|
||||
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":
|
||||
ctx, cancel := context.WithTimeout(context.Background(), defaultTimeout)
|
||||
|
||||
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
|
||||
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")
|
||||
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":
|
||||
s.containers.Compute(event.ActorID, func(c *Container, loaded bool) (*Container, bool) {
|
||||
if loaded {
|
||||
|
||||
@@ -14,7 +14,7 @@ type mockedClient struct {
|
||||
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)
|
||||
return args.Get(0).([]Container), args.Error(1)
|
||||
}
|
||||
@@ -65,58 +65,15 @@ func TestContainerStore_List(t *testing.T) {
|
||||
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
t.Cleanup(cancel)
|
||||
|
||||
store := NewContainerStore(ctx, client, ContainerFilter{})
|
||||
containers, _ := store.ListContainers(ContainerFilter{})
|
||||
collector := &fakeStatsCollector{}
|
||||
store := NewContainerStore(ctx, client, collector, ContainerLabels{})
|
||||
containers, _ := store.ListContainers(ContainerLabels{})
|
||||
|
||||
assert.Equal(t, containers[0].ID, "1234")
|
||||
}
|
||||
|
||||
//TODO fix this test
|
||||
// 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)
|
||||
type fakeStatsCollector struct{}
|
||||
|
||||
// client.On("ContainerEvents", mock.Anything, mock.AnythingOfType("chan<- container.ContainerEvent")).Return(nil).
|
||||
// Run(func(args mock.Arguments) {
|
||||
// ctx := args.Get(0).(context.Context)
|
||||
// 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")
|
||||
// }
|
||||
func (f *fakeStatsCollector) Subscribe(_ context.Context, _ chan<- ContainerStat) {}
|
||||
func (f *fakeStatsCollector) Start(_ context.Context) bool { return true }
|
||||
func (f *fakeStatsCollector) Stop() {}
|
||||
|
||||
@@ -1,14 +1,10 @@
|
||||
package container
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"bytes"
|
||||
"context"
|
||||
"encoding/binary"
|
||||
"errors"
|
||||
"fmt"
|
||||
"hash/fnv"
|
||||
"io"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
@@ -23,30 +19,26 @@ import (
|
||||
type EventGenerator struct {
|
||||
Events chan *LogEvent
|
||||
Errors chan error
|
||||
reader *bufio.Reader
|
||||
reader LogReader
|
||||
next *LogEvent
|
||||
buffer chan *LogEvent
|
||||
tty bool
|
||||
wg sync.WaitGroup
|
||||
containerID string
|
||||
ctx context.Context
|
||||
}
|
||||
|
||||
var bufPool = sync.Pool{
|
||||
New: func() any {
|
||||
return new(bytes.Buffer)
|
||||
},
|
||||
}
|
||||
|
||||
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{
|
||||
reader: bufio.NewReader(reader),
|
||||
reader: reader,
|
||||
buffer: make(chan *LogEvent, 100),
|
||||
Errors: make(chan error, 1),
|
||||
Events: make(chan *LogEvent),
|
||||
tty: container.Tty,
|
||||
containerID: container.ID,
|
||||
ctx: ctx,
|
||||
}
|
||||
@@ -90,7 +82,7 @@ loop:
|
||||
|
||||
func (g *EventGenerator) consumeReader() {
|
||||
for {
|
||||
message, streamType, readerError := readEvent(g.reader, g.tty)
|
||||
message, streamType, readerError := g.reader.Read()
|
||||
if message != "" {
|
||||
logEvent := createEvent(message, streamType)
|
||||
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 {
|
||||
h := fnv.New32a()
|
||||
h.Write([]byte(message))
|
||||
|
||||
@@ -1,12 +1,9 @@
|
||||
package container
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"bytes"
|
||||
"context"
|
||||
"encoding/binary"
|
||||
"io"
|
||||
"reflect"
|
||||
"strings"
|
||||
"sync"
|
||||
"testing"
|
||||
"time"
|
||||
@@ -18,9 +15,8 @@ import (
|
||||
|
||||
func TestEventGenerator_Events_tty(t *testing.T) {
|
||||
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
|
||||
|
||||
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) {
|
||||
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
|
||||
|
||||
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) {
|
||||
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
|
||||
_, 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) {
|
||||
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
|
||||
assert.False(t, waitTimeout(&g.wg, 1*time.Second), "Expected routines to be done")
|
||||
}
|
||||
|
||||
func makeMessage(message string, stream StdType) []byte {
|
||||
data := make([]byte, 8)
|
||||
binary.BigEndian.PutUint32(data[4:], uint32(len(message)))
|
||||
data[0] = byte(stream / 2)
|
||||
data = append(data, []byte(message)...)
|
||||
type mockLogReader struct {
|
||||
messages []string
|
||||
types []StdType
|
||||
i int
|
||||
}
|
||||
|
||||
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 {
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -44,10 +44,10 @@ type ContainerEvent struct {
|
||||
Time time.Time `json:"time"`
|
||||
}
|
||||
|
||||
type ContainerFilter map[string][]string
|
||||
type ContainerLabels map[string][]string
|
||||
|
||||
func ParseContainerFilter(commaValues string) (ContainerFilter, error) {
|
||||
filter := make(ContainerFilter)
|
||||
func ParseContainerFilter(commaValues string) (ContainerLabels, error) {
|
||||
filter := make(ContainerLabels)
|
||||
if commaValues == "" {
|
||||
return filter, nil
|
||||
}
|
||||
@@ -65,7 +65,7 @@ func ParseContainerFilter(commaValues string) (ContainerFilter, error) {
|
||||
return filter, nil
|
||||
}
|
||||
|
||||
func (f ContainerFilter) Exists() bool {
|
||||
func (f ContainerLabels) Exists() bool {
|
||||
return len(f) > 0
|
||||
}
|
||||
|
||||
|
||||
@@ -17,7 +17,6 @@ import (
|
||||
docker "github.com/docker/docker/api/types/container"
|
||||
"github.com/docker/docker/api/types/events"
|
||||
"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/client"
|
||||
|
||||
@@ -37,13 +36,13 @@ type DockerCLI interface {
|
||||
Info(ctx context.Context) (system.Info, error)
|
||||
}
|
||||
|
||||
type httpClient struct {
|
||||
type DockerClient struct {
|
||||
cli DockerCLI
|
||||
host container.Host
|
||||
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())
|
||||
if err != nil {
|
||||
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.Swarm = info.Swarm.NodeID != ""
|
||||
|
||||
return &httpClient{
|
||||
return &DockerClient{
|
||||
cli: cli,
|
||||
host: host,
|
||||
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
|
||||
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"))
|
||||
|
||||
if err != nil {
|
||||
@@ -93,7 +92,7 @@ func NewLocalClient(hostname string) (container.Client, error) {
|
||||
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" {
|
||||
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
|
||||
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")
|
||||
if json, err := d.cli.ContainerInspect(ctx, id); err == 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 {
|
||||
case container.Start:
|
||||
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) {
|
||||
log.Debug().Interface("filter", filter).Str("host", d.host.Name).Msg("Listing containers")
|
||||
func (d *DockerClient) ListContainers(ctx context.Context, labels container.ContainerLabels) ([]container.Container, error) {
|
||||
log.Debug().Interface("labels", labels).Str("host", d.host.Name).Msg("Listing containers")
|
||||
filterArgs := filters.NewArgs()
|
||||
for key, values := range filter {
|
||||
for key, values := range labels {
|
||||
for _, value := range values {
|
||||
filterArgs.Add(key, value)
|
||||
}
|
||||
@@ -175,7 +174,7 @@ func (d *httpClient) ListContainers(ctx context.Context, filter container.Contai
|
||||
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)
|
||||
|
||||
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")
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
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{})
|
||||
|
||||
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")
|
||||
options := docker.LogsOptions{
|
||||
ShowStdout: stdType&container.STDOUT != 0,
|
||||
@@ -288,20 +287,16 @@ func (d *httpClient) ContainerLogsBetweenDates(ctx context.Context, id string, f
|
||||
return reader, nil
|
||||
}
|
||||
|
||||
func (d *httpClient) Ping(ctx context.Context) error {
|
||||
func (d *DockerClient) Ping(ctx context.Context) error {
|
||||
_, err := d.cli.Ping(ctx)
|
||||
return err
|
||||
}
|
||||
|
||||
func (d *httpClient) Host() container.Host {
|
||||
func (d *DockerClient) Host() container.Host {
|
||||
log.Debug().Str("host", d.host.Name).Msg("Fetching 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 {
|
||||
name := "no name"
|
||||
if c.Labels["dev.dozzle.name"] != "" {
|
||||
|
||||
@@ -90,9 +90,9 @@ func (m *mockedProxy) ContainerRestart(ctx context.Context, containerID string,
|
||||
func Test_dockerClient_ListContainers_null(t *testing.T) {
|
||||
proxy := new(mockedProxy)
|
||||
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")
|
||||
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) {
|
||||
proxy := new(mockedProxy)
|
||||
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")
|
||||
require.Error(t, err, "test.")
|
||||
|
||||
@@ -125,9 +125,9 @@ func Test_dockerClient_ListContainers_happy(t *testing.T) {
|
||||
|
||||
proxy := new(mockedProxy)
|
||||
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.")
|
||||
|
||||
Ids := []string{"1234567890_a", "abcdefghijkl"}
|
||||
@@ -159,7 +159,7 @@ func Test_dockerClient_ContainerLogs_happy(t *testing.T) {
|
||||
Since: "2020-12-31T23:59:59.95Z"}
|
||||
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)
|
||||
|
||||
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"))
|
||||
|
||||
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)
|
||||
|
||||
@@ -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}}
|
||||
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")
|
||||
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) {
|
||||
proxy := new(mockedProxy)
|
||||
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")
|
||||
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) {
|
||||
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)}
|
||||
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) {
|
||||
|
||||
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("ContainerStart", mock.Anything, mock.Anything, mock.Anything).Return(errors.New("test"))
|
||||
proxy.On("ContainerStop", mock.Anything, mock.Anything, mock.Anything).Return(errors.New("test"))
|
||||
|
||||
101
internal/docker/log_reader.go
Normal file
101
internal/docker/log_reader.go
Normal 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
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
package container
|
||||
package docker
|
||||
|
||||
import (
|
||||
"context"
|
||||
@@ -8,35 +8,36 @@ import (
|
||||
"sync/atomic"
|
||||
"time"
|
||||
|
||||
"github.com/amir20/dozzle/internal/container"
|
||||
"github.com/puzpuzpuz/xsync/v3"
|
||||
"github.com/rs/zerolog/log"
|
||||
)
|
||||
|
||||
type StatsCollector struct {
|
||||
stream chan ContainerStat
|
||||
subscribers *xsync.MapOf[context.Context, chan<- ContainerStat]
|
||||
client Client
|
||||
type DockerStatsCollector struct {
|
||||
stream chan container.ContainerStat
|
||||
subscribers *xsync.MapOf[context.Context, chan<- container.ContainerStat]
|
||||
client container.Client
|
||||
cancelers *xsync.MapOf[string, context.CancelFunc]
|
||||
stopper context.CancelFunc
|
||||
timer *time.Timer
|
||||
mu sync.Mutex
|
||||
totalStarted atomic.Int32
|
||||
filter ContainerFilter
|
||||
labels container.ContainerLabels
|
||||
}
|
||||
|
||||
var timeToStop = 6 * time.Hour
|
||||
|
||||
func NewStatsCollector(client Client, filter ContainerFilter) *StatsCollector {
|
||||
return &StatsCollector{
|
||||
stream: make(chan ContainerStat),
|
||||
subscribers: xsync.NewMapOf[context.Context, chan<- ContainerStat](),
|
||||
func NewDockerStatsCollector(client container.Client, labels container.ContainerLabels) *DockerStatsCollector {
|
||||
return &DockerStatsCollector{
|
||||
stream: make(chan container.ContainerStat),
|
||||
subscribers: xsync.NewMapOf[context.Context, chan<- container.ContainerStat](),
|
||||
client: client,
|
||||
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)
|
||||
go func() {
|
||||
<-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()
|
||||
defer c.mu.Unlock()
|
||||
if c.stopper != nil {
|
||||
@@ -54,7 +55,7 @@ func (c *StatsCollector) forceStop() {
|
||||
}
|
||||
}
|
||||
|
||||
func (c *StatsCollector) Stop() {
|
||||
func (c *DockerStatsCollector) Stop() {
|
||||
c.mu.Lock()
|
||||
defer c.mu.Unlock()
|
||||
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()
|
||||
defer c.mu.Unlock()
|
||||
if c.timer != nil {
|
||||
@@ -73,7 +74,7 @@ func (c *StatsCollector) reset() {
|
||||
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)
|
||||
sc.cancelers.Store(id, cancel)
|
||||
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
|
||||
func (sc *StatsCollector) Start(parentCtx context.Context) bool {
|
||||
func (sc *DockerStatsCollector) Start(parentCtx context.Context) bool {
|
||||
sc.reset()
|
||||
sc.totalStarted.Add(1)
|
||||
|
||||
@@ -99,8 +100,8 @@ func (sc *StatsCollector) Start(parentCtx context.Context) bool {
|
||||
ctx, sc.stopper = context.WithCancel(parentCtx)
|
||||
sc.mu.Unlock()
|
||||
|
||||
timeoutCtx, cancel := context.WithTimeout(parentCtx, defaultTimeout)
|
||||
if containers, err := sc.client.ListContainers(timeoutCtx, sc.filter); err == nil {
|
||||
timeoutCtx, cancel := context.WithTimeout(parentCtx, 3*time.Second) // 3 seconds to list containers is hard limit
|
||||
if containers, err := sc.client.ListContainers(timeoutCtx, sc.labels); err == nil {
|
||||
for _, c := range containers {
|
||||
if c.State == "running" {
|
||||
go streamStats(ctx, sc, c.ID)
|
||||
@@ -111,7 +112,7 @@ func (sc *StatsCollector) Start(parentCtx context.Context) bool {
|
||||
}
|
||||
cancel()
|
||||
|
||||
events := make(chan ContainerEvent)
|
||||
events := make(chan container.ContainerEvent)
|
||||
|
||||
go func() {
|
||||
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")
|
||||
return true
|
||||
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 {
|
||||
case stats <- stat:
|
||||
case <-c.Done():
|
||||
@@ -1,16 +1,47 @@
|
||||
package container
|
||||
package docker
|
||||
|
||||
import (
|
||||
"context"
|
||||
"testing"
|
||||
|
||||
"github.com/amir20/dozzle/internal/container"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"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.On("ListContainers", mock.Anything, mock.Anything).Return([]Container{
|
||||
client.On("ListContainers", mock.Anything, mock.Anything).Return([]container.Container{
|
||||
{
|
||||
ID: "1234",
|
||||
Name: "test",
|
||||
@@ -26,17 +57,17 @@ func startedCollector(ctx context.Context) *StatsCollector {
|
||||
client.On("ContainerStats", mock.Anything, mock.Anything, mock.AnythingOfType("chan<- container.ContainerStat")).
|
||||
Return(nil).
|
||||
Run(func(args mock.Arguments) {
|
||||
stats := args.Get(2).(chan<- ContainerStat)
|
||||
stats <- ContainerStat{
|
||||
stats := args.Get(2).(chan<- container.ContainerStat)
|
||||
stats <- container.ContainerStat{
|
||||
ID: "1234",
|
||||
}
|
||||
})
|
||||
client.On("Host").Return(Host{
|
||||
client.On("Host").Return(container.Host{
|
||||
ID: "localhost",
|
||||
})
|
||||
|
||||
collector := NewStatsCollector(client, ContainerFilter{})
|
||||
stats := make(chan ContainerStat)
|
||||
collector := NewDockerStatsCollector(client, container.ContainerLabels{})
|
||||
stats := make(chan container.ContainerStat)
|
||||
|
||||
collector.Subscribe(ctx, stats)
|
||||
|
||||
@@ -14,7 +14,7 @@ func RPCRequest(ctx context.Context, addr string, certs tls.Certificate) error {
|
||||
if err != nil {
|
||||
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")
|
||||
return err
|
||||
}
|
||||
|
||||
223
internal/k8s/client.go
Normal file
223
internal/k8s/client.go
Normal 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]
|
||||
}
|
||||
27
internal/k8s/log_reader.go
Normal file
27
internal/k8s/log_reader.go
Normal 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
|
||||
}
|
||||
124
internal/k8s/stats_collector.go
Normal file
124
internal/k8s/stats_collector.go
Normal 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
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -6,12 +6,13 @@ import (
|
||||
|
||||
"github.com/amir20/dozzle/internal/container"
|
||||
"github.com/amir20/dozzle/internal/docker"
|
||||
container_support "github.com/amir20/dozzle/internal/support/container"
|
||||
docker_support "github.com/amir20/dozzle/internal/support/docker"
|
||||
"github.com/rs/zerolog/log"
|
||||
)
|
||||
|
||||
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 {
|
||||
log.Info().Msg(`Consider using Dozzle's remote agent to manage remote hosts. See https://dozzle.dev/guide/agent for more information`)
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
package docker_support
|
||||
package container_support
|
||||
|
||||
import (
|
||||
"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)
|
||||
}
|
||||
|
||||
@@ -38,9 +38,9 @@ func (a *agentService) StreamLogs(ctx context.Context, container container.Conta
|
||||
return a.client.StreamContainerLogs(ctx, container.ID, from, stdTypes, events)
|
||||
}
|
||||
|
||||
func (a *agentService) ListContainers(ctx context.Context, filter container.ContainerFilter) ([]container.Container, error) {
|
||||
log.Debug().Interface("filter", filter).Msg("Listing containers from agent")
|
||||
return a.client.ListContainers(ctx, filter)
|
||||
func (a *agentService) ListContainers(ctx context.Context, labels container.ContainerLabels) ([]container.Container, error) {
|
||||
log.Debug().Interface("labels", labels).Msg("Listing containers from agent")
|
||||
return a.client.ListContainers(ctx, labels)
|
||||
}
|
||||
|
||||
func (a *agentService) Host(ctx context.Context) (container.Host, error) {
|
||||
28
internal/support/container/client_service.go
Normal file
28
internal/support/container/client_service.go
Normal 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
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
package docker_support
|
||||
package container_support
|
||||
|
||||
import (
|
||||
"context"
|
||||
@@ -8,23 +8,30 @@ import (
|
||||
"github.com/amir20/dozzle/internal/container"
|
||||
)
|
||||
|
||||
type containerService struct {
|
||||
type ContainerService struct {
|
||||
clientService ClientService
|
||||
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)
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
111
internal/support/docker/docker_service.go
Normal file
111
internal/support/docker/docker_service.go
Normal 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)
|
||||
}
|
||||
@@ -6,11 +6,10 @@ import (
|
||||
"time"
|
||||
|
||||
"github.com/amir20/dozzle/internal/container"
|
||||
container_support "github.com/amir20/dozzle/internal/support/container"
|
||||
"github.com/rs/zerolog/log"
|
||||
)
|
||||
|
||||
type ContainerFilter = func(*container.Container) bool
|
||||
|
||||
type HostUnavailableError struct {
|
||||
Host container.Host
|
||||
Err error
|
||||
@@ -21,9 +20,9 @@ func (h *HostUnavailableError) Error() string {
|
||||
}
|
||||
|
||||
type ClientManager interface {
|
||||
Find(id string) (ClientService, bool)
|
||||
List() []ClientService
|
||||
RetryAndList() ([]ClientService, []error)
|
||||
Find(id string) (container_support.ClientService, bool)
|
||||
List() []container_support.ClientService
|
||||
RetryAndList() ([]container_support.ClientService, []error)
|
||||
Subscribe(ctx context.Context, channel chan<- container.Host)
|
||||
Hosts(ctx context.Context) []container.Host
|
||||
LocalClients() []container.Client
|
||||
@@ -43,25 +42,22 @@ func NewMultiHostService(manager ClientManager, timeout time.Duration) *MultiHos
|
||||
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)
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("host %s not found", host)
|
||||
}
|
||||
ctx, cancel := context.WithTimeout(context.Background(), m.timeout)
|
||||
defer cancel()
|
||||
container, err := client.FindContainer(ctx, id, filter)
|
||||
container, err := client.FindContainer(ctx, id, labels)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &containerService{
|
||||
clientService: client,
|
||||
Container: container,
|
||||
}, nil
|
||||
return container_support.NewContainerService(client, 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)
|
||||
if !ok {
|
||||
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)
|
||||
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)
|
||||
clients, errors := m.manager.RetryAndList()
|
||||
|
||||
for _, client := range clients {
|
||||
ctx, cancel := context.WithTimeout(context.Background(), m.timeout)
|
||||
defer cancel()
|
||||
list, err := client.ListContainers(ctx, filter)
|
||||
list, err := client.ListContainers(ctx, labels)
|
||||
if err != nil {
|
||||
host, _ := client.Host(ctx)
|
||||
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
|
||||
}
|
||||
|
||||
func (m *MultiHostService) ListAllContainersFiltered(userFilter container.ContainerFilter, filter ContainerFilter) ([]container.Container, []error) {
|
||||
containers, err := m.ListAllContainers(userFilter)
|
||||
func (m *MultiHostService) 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) {
|
||||
@@ -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)
|
||||
for _, client := range m.manager.List() {
|
||||
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 {
|
||||
ctx, cancel := context.WithTimeout(context.Background(), m.timeout)
|
||||
defer cancel()
|
||||
@@ -161,3 +153,7 @@ func (m *MultiHostService) SubscribeAvailableHosts(ctx context.Context, hosts ch
|
||||
func (m *MultiHostService) LocalClients() []container.Client {
|
||||
return m.manager.LocalClients()
|
||||
}
|
||||
|
||||
func (m *MultiHostService) TotalClients() int {
|
||||
return len(m.manager.List())
|
||||
}
|
||||
|
||||
@@ -9,6 +9,8 @@ import (
|
||||
|
||||
"github.com/amir20/dozzle/internal/agent"
|
||||
"github.com/amir20/dozzle/internal/container"
|
||||
container_support "github.com/amir20/dozzle/internal/support/container"
|
||||
|
||||
"github.com/puzpuzpuz/xsync/v3"
|
||||
"github.com/samber/lo"
|
||||
lop "github.com/samber/lo/parallel"
|
||||
@@ -17,7 +19,7 @@ import (
|
||||
)
|
||||
|
||||
type RetriableClientManager struct {
|
||||
clients map[string]ClientService
|
||||
clients map[string]container_support.ClientService
|
||||
failedAgents []string
|
||||
certs tls.Certificate
|
||||
mu sync.RWMutex
|
||||
@@ -25,8 +27,8 @@ type RetriableClientManager struct {
|
||||
timeout time.Duration
|
||||
}
|
||||
|
||||
func NewRetriableClientManager(agents []string, timeout time.Duration, certs tls.Certificate, clients ...ClientService) *RetriableClientManager {
|
||||
clientMap := make(map[string]ClientService)
|
||||
func NewRetriableClientManager(agents []string, timeout time.Duration, certs tls.Certificate, clients ...container_support.ClientService) *RetriableClientManager {
|
||||
clientMap := make(map[string]container_support.ClientService)
|
||||
for _, client := range clients {
|
||||
ctx, cancel := context.WithTimeout(context.Background(), timeout)
|
||||
defer cancel()
|
||||
@@ -64,7 +66,7 @@ func NewRetriableClientManager(agents []string, timeout time.Duration, certs tls
|
||||
if _, ok := clientMap[host.ID]; ok {
|
||||
log.Warn().Str("host", host.Name).Str("id", host.ID).Msg("duplicate host with same ID found")
|
||||
} 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()
|
||||
errors := make([]error, 0)
|
||||
if len(m.failedAgents) > 0 {
|
||||
@@ -110,7 +112,7 @@ func (m *RetriableClientManager) RetryAndList() ([]ClientService, []error) {
|
||||
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 {
|
||||
host.Available = true
|
||||
|
||||
@@ -133,14 +135,14 @@ func (m *RetriableClientManager) RetryAndList() ([]ClientService, []error) {
|
||||
return m.List(), errors
|
||||
}
|
||||
|
||||
func (m *RetriableClientManager) List() []ClientService {
|
||||
func (m *RetriableClientManager) List() []container_support.ClientService {
|
||||
m.mu.RLock()
|
||||
defer m.mu.RUnlock()
|
||||
|
||||
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()
|
||||
defer m.mu.RUnlock()
|
||||
|
||||
@@ -155,7 +157,7 @@ func (m *RetriableClientManager) String() string {
|
||||
func (m *RetriableClientManager) Hosts(ctx context.Context) []container.Host {
|
||||
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)
|
||||
if err != nil {
|
||||
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)
|
||||
|
||||
for _, service := range services {
|
||||
if clientService, ok := service.(*dockerClientService); ok {
|
||||
if clientService, ok := service.(*DockerClientService); ok {
|
||||
clients = append(clients, clientService.client)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -11,6 +11,9 @@ import (
|
||||
|
||||
"github.com/amir20/dozzle/internal/agent"
|
||||
"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/samber/lo"
|
||||
lop "github.com/samber/lo/parallel"
|
||||
@@ -19,7 +22,7 @@ import (
|
||||
)
|
||||
|
||||
type SwarmClientManager struct {
|
||||
clients map[string]ClientService
|
||||
clients map[string]container_support.ClientService
|
||||
certs tls.Certificate
|
||||
mu sync.RWMutex
|
||||
subscribers *xsync.MapOf[context.Context, chan<- container.Host]
|
||||
@@ -47,9 +50,9 @@ func localIPs() []string {
|
||||
return ips
|
||||
}
|
||||
|
||||
func NewSwarmClientManager(localClient container.Client, certs tls.Certificate, timeout time.Duration, agentManager *RetriableClientManager, filter container.ContainerFilter) *SwarmClientManager {
|
||||
clientMap := make(map[string]ClientService)
|
||||
localService := NewDockerClientService(localClient, filter)
|
||||
func NewSwarmClientManager(localClient *docker.DockerClient, certs tls.Certificate, timeout time.Duration, agentManager *RetriableClientManager, labels container.ContainerLabels) *SwarmClientManager {
|
||||
clientMap := make(map[string]container_support.ClientService)
|
||||
localService := NewDockerClientService(localClient, labels)
|
||||
clientMap[localClient.Host().ID] = localService
|
||||
|
||||
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()
|
||||
|
||||
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)
|
||||
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)
|
||||
defer cancel()
|
||||
host, _ := client.Host(ctx)
|
||||
@@ -155,7 +158,7 @@ func (m *SwarmClientManager) RetryAndList() ([]ClientService, []error) {
|
||||
continue
|
||||
}
|
||||
|
||||
client := NewAgentService(agent)
|
||||
client := container_support.NewAgentService(agent)
|
||||
m.clients[host.ID] = client
|
||||
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
|
||||
}
|
||||
|
||||
func (m *SwarmClientManager) List() []ClientService {
|
||||
func (m *SwarmClientManager) List() []container_support.ClientService {
|
||||
m.mu.RLock()
|
||||
defer m.mu.RUnlock()
|
||||
|
||||
@@ -192,7 +195,7 @@ func (m *SwarmClientManager) List() []ClientService {
|
||||
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()
|
||||
defer m.mu.RUnlock()
|
||||
|
||||
@@ -210,7 +213,7 @@ func (m *SwarmClientManager) Hosts(ctx context.Context) []container.Host {
|
||||
clients := lo.Values(m.clients)
|
||||
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)
|
||||
if err != nil {
|
||||
log.Warn().Err(err).Str("id", host.ID).Msg("error getting host from client")
|
||||
|
||||
133
internal/support/k8s/k8s_cluster_service.go
Normal file
133
internal/support/k8s/k8s_cluster_service.go
Normal 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
|
||||
}
|
||||
92
internal/support/k8s/k8s_service.go
Normal file
92
internal/support/k8s/k8s_service.go
Normal 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)
|
||||
}
|
||||
@@ -15,15 +15,15 @@ func (h *handler) containerActions(w http.ResponseWriter, r *http.Request) {
|
||||
action := chi.URLParam(r, "action")
|
||||
id := chi.URLParam(r, "id")
|
||||
|
||||
usersFilter := h.config.Filter
|
||||
userLabels := h.config.Labels
|
||||
if h.config.Authorization.Provider != NONE {
|
||||
user := auth.UserFromContext(r.Context())
|
||||
if user.ContainerFilter.Exists() {
|
||||
usersFilter = user.ContainerFilter
|
||||
if user.ContainerLabels.Exists() {
|
||||
userLabels = user.ContainerLabels
|
||||
}
|
||||
}
|
||||
|
||||
containerService, err := h.multiHostService.FindContainer(hostKey(r), id, usersFilter)
|
||||
containerService, err := h.hostService.FindContainer(hostKey(r), id, userLabels)
|
||||
if err != nil {
|
||||
log.Error().Err(err).Msg("error while trying to find container")
|
||||
http.Error(w, err.Error(), http.StatusNotFound)
|
||||
|
||||
@@ -9,8 +9,8 @@ import (
|
||||
|
||||
func (h *handler) debugStore(w http.ResponseWriter, r *http.Request) {
|
||||
respone := make(map[string]interface{})
|
||||
respone["hosts"] = h.multiHostService.Hosts()
|
||||
containers, errors := h.multiHostService.ListAllContainers(container.ContainerFilter{})
|
||||
respone["hosts"] = h.hostService.Hosts()
|
||||
containers, errors := h.hostService.ListAllContainers(container.ContainerLabels{})
|
||||
respone["containers"] = containers
|
||||
respone["errors"] = errors
|
||||
|
||||
|
||||
@@ -10,7 +10,6 @@ import (
|
||||
|
||||
"github.com/amir20/dozzle/internal/auth"
|
||||
"github.com/amir20/dozzle/internal/container"
|
||||
"github.com/docker/docker/pkg/stdcopy"
|
||||
"github.com/go-chi/chi/v5"
|
||||
"github.com/rs/zerolog/log"
|
||||
)
|
||||
@@ -23,11 +22,11 @@ func (h *handler) downloadLogs(w http.ResponseWriter, r *http.Request) {
|
||||
return
|
||||
}
|
||||
|
||||
usersFilter := h.config.Filter
|
||||
userLabels := h.config.Labels
|
||||
if h.config.Authorization.Provider != NONE {
|
||||
user := auth.UserFromContext(r.Context())
|
||||
if user.ContainerFilter.Exists() {
|
||||
usersFilter = user.ContainerFilter
|
||||
if user.ContainerLabels.Exists() {
|
||||
userLabels = user.ContainerLabels
|
||||
}
|
||||
}
|
||||
|
||||
@@ -66,7 +65,7 @@ func (h *handler) downloadLogs(w http.ResponseWriter, r *http.Request) {
|
||||
|
||||
host := parts[0]
|
||||
id := parts[1]
|
||||
containerService, err := h.multiHostService.FindContainer(host, id, usersFilter)
|
||||
containerService, err := h.hostService.FindContainer(host, id, userLabels)
|
||||
if err != nil {
|
||||
log.Error().Err(err).Msgf("error finding container %s", id)
|
||||
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
|
||||
}
|
||||
|
||||
// Copy logs directly to zip entry
|
||||
if containerService.Container.Tty {
|
||||
if _, err := io.Copy(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
|
||||
}
|
||||
} else {
|
||||
if _, err := stdcopy.StdCopy(f, f, reader); err != nil {
|
||||
// Copy logs to zip file
|
||||
_, err = io.Copy(f, reader)
|
||||
if 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
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -24,18 +24,18 @@ func (h *handler) streamEvents(w http.ResponseWriter, r *http.Request) {
|
||||
stats := make(chan container.ContainerStat)
|
||||
availableHosts := make(chan container.Host)
|
||||
|
||||
h.multiHostService.SubscribeEventsAndStats(r.Context(), events, stats)
|
||||
h.multiHostService.SubscribeAvailableHosts(r.Context(), availableHosts)
|
||||
h.hostService.SubscribeEventsAndStats(r.Context(), events, stats)
|
||||
h.hostService.SubscribeAvailableHosts(r.Context(), availableHosts)
|
||||
|
||||
usersFilter := h.config.Filter
|
||||
userLabels := h.config.Labels
|
||||
if h.config.Authorization.Provider != NONE {
|
||||
user := auth.UserFromContext(r.Context())
|
||||
if user.ContainerFilter.Exists() {
|
||||
usersFilter = user.ContainerFilter
|
||||
if user.ContainerLabels.Exists() {
|
||||
userLabels = user.ContainerLabels
|
||||
}
|
||||
}
|
||||
|
||||
allContainers, errors := h.multiHostService.ListAllContainers(usersFilter)
|
||||
allContainers, errors := h.hostService.ListAllContainers(userLabels)
|
||||
|
||||
for _, err := range errors {
|
||||
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" {
|
||||
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 {
|
||||
log.Error().Err(err).Msg("error writing containers to event stream")
|
||||
return
|
||||
@@ -88,7 +88,7 @@ func (h *handler) streamEvents(w http.ResponseWriter, r *http.Request) {
|
||||
|
||||
case "update":
|
||||
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 {
|
||||
log.Error().Err(err).Msg("error writing event to event stream")
|
||||
return
|
||||
@@ -123,7 +123,7 @@ func sendBeaconEvent(h *handler, r *http.Request, runningContainers int) {
|
||||
b := types.BeaconEvent{
|
||||
AuthProvider: string(h.config.Authorization.Provider),
|
||||
Browser: r.Header.Get("User-Agent"),
|
||||
Clients: h.multiHostService.TotalClients(),
|
||||
Clients: len(h.hostService.Hosts()),
|
||||
HasActions: h.config.EnableActions,
|
||||
HasCustomAddress: h.config.Addr != ":8080",
|
||||
HasCustomBase: h.config.Base != "/",
|
||||
@@ -133,9 +133,9 @@ func sendBeaconEvent(h *handler, r *http.Request, runningContainers int) {
|
||||
Version: h.config.Version,
|
||||
}
|
||||
|
||||
local, err := h.multiHostService.LocalHost()
|
||||
local, err := h.hostService.LocalHost()
|
||||
if err == nil {
|
||||
b.ServerID = local.ID
|
||||
b.ServerID = local.ID // TODO : fix this for k8s
|
||||
}
|
||||
|
||||
if err := analytics.SendBeacon(b); err != nil {
|
||||
|
||||
@@ -54,7 +54,7 @@ func Test_handler_streamEvents_happy(t *testing.T) {
|
||||
})
|
||||
|
||||
// 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)
|
||||
|
||||
server := CreateServer(multiHostService, nil, Config{Base: "/", Authorization: Authorization{Provider: NONE}})
|
||||
|
||||
@@ -9,7 +9,7 @@ import (
|
||||
func (h *handler) healthcheck(w http.ResponseWriter, r *http.Request) {
|
||||
log.Debug().Msg("Executing healthcheck")
|
||||
|
||||
clients := h.multiHostService.LocalClients()
|
||||
clients := h.hostService.LocalClients()
|
||||
for _, client := range clients {
|
||||
if err := client.Ping(r.Context()); err != nil {
|
||||
log.Error().Err(err).Str("host", client.Host().Name).Msg("error pinging host")
|
||||
|
||||
@@ -43,7 +43,7 @@ func (h *handler) executeTemplate(w http.ResponseWriter, req *http.Request) {
|
||||
base = h.config.Base
|
||||
}
|
||||
|
||||
hosts := h.multiHostService.Hosts()
|
||||
hosts := h.hostService.Hosts()
|
||||
sort.Slice(hosts, func(i, j int) bool {
|
||||
return hosts[i].Name < hosts[j].Name
|
||||
})
|
||||
|
||||
@@ -18,6 +18,7 @@ import (
|
||||
|
||||
"github.com/amir20/dozzle/internal/auth"
|
||||
"github.com/amir20/dozzle/internal/container"
|
||||
container_support "github.com/amir20/dozzle/internal/support/container"
|
||||
"github.com/amir20/dozzle/internal/support/search"
|
||||
support_web "github.com/amir20/dozzle/internal/support/web"
|
||||
"github.com/amir20/dozzle/internal/utils"
|
||||
@@ -47,15 +48,15 @@ func (h *handler) fetchLogsBetweenDates(w http.ResponseWriter, r *http.Request)
|
||||
return
|
||||
}
|
||||
|
||||
usersFilter := h.config.Filter
|
||||
usersLabels := h.config.Labels
|
||||
if h.config.Authorization.Provider != NONE {
|
||||
user := auth.UserFromContext(r.Context())
|
||||
if user.ContainerFilter.Exists() {
|
||||
usersFilter = user.ContainerFilter
|
||||
if user.ContainerLabels.Exists() {
|
||||
usersLabels = user.ContainerLabels
|
||||
}
|
||||
}
|
||||
|
||||
containerService, err := h.multiHostService.FindContainer(hostKey(r), id, usersFilter)
|
||||
containerService, err := h.hostService.FindContainer(hostKey(r), id, usersLabels)
|
||||
if err != nil {
|
||||
http.Error(w, err.Error(), http.StatusNotFound)
|
||||
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
|
||||
if r.URL.Query().Has("stdout") {
|
||||
stdTypes |= container.STDOUT
|
||||
@@ -248,15 +249,15 @@ func (h *handler) streamLogsForContainers(w http.ResponseWriter, r *http.Request
|
||||
return
|
||||
}
|
||||
|
||||
usersFilter := h.config.Filter
|
||||
userLabels := h.config.Labels
|
||||
if h.config.Authorization.Provider != NONE {
|
||||
user := auth.UserFromContext(r.Context())
|
||||
if user.ContainerFilter.Exists() {
|
||||
usersFilter = user.ContainerFilter
|
||||
if user.ContainerLabels.Exists() {
|
||||
userLabels = user.ContainerLabels
|
||||
}
|
||||
}
|
||||
|
||||
existingContainers, errs := h.multiHostService.ListAllContainersFiltered(usersFilter, containerFilter)
|
||||
existingContainers, errs := h.hostService.ListAllContainersFiltered(userLabels, containerFilter)
|
||||
if len(errs) > 0 {
|
||||
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)
|
||||
stillRunning := false
|
||||
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 {
|
||||
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) {
|
||||
containerService, err := h.multiHostService.FindContainer(c.Host, c.ID, usersFilter)
|
||||
containerService, err := h.hostService.FindContainer(c.Host, c.ID, userLabels)
|
||||
if err != nil {
|
||||
log.Error().Err(err).Msg("error while finding container")
|
||||
return
|
||||
@@ -369,7 +370,7 @@ func (h *handler) streamLogsForContainers(w http.ResponseWriter, r *http.Request
|
||||
}
|
||||
|
||||
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)
|
||||
sseWriter.Ping()
|
||||
@@ -388,7 +389,7 @@ loop:
|
||||
}
|
||||
sseWriter.Message(logEvent)
|
||||
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}
|
||||
go streamLogs(c)
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package web
|
||||
|
||||
import (
|
||||
"context"
|
||||
"io/fs"
|
||||
"time"
|
||||
|
||||
@@ -9,7 +10,7 @@ import (
|
||||
|
||||
"github.com/amir20/dozzle/internal/auth"
|
||||
"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/middleware"
|
||||
@@ -34,7 +35,7 @@ type Config struct {
|
||||
Dev bool
|
||||
Authorization Authorization
|
||||
EnableActions bool
|
||||
Filter container.ContainerFilter
|
||||
Labels container.ContainerLabels
|
||||
}
|
||||
|
||||
type Authorization struct {
|
||||
@@ -48,20 +49,30 @@ type Authorizer interface {
|
||||
CreateToken(string, string) (string, error)
|
||||
}
|
||||
|
||||
type HostService interface {
|
||||
FindContainer(host string, id string, labels container.ContainerLabels) (*container_support.ContainerService, error)
|
||||
ListContainersForHost(host string, labels container.ContainerLabels) ([]container.Container, error)
|
||||
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 handler struct {
|
||||
content fs.FS
|
||||
config *Config
|
||||
multiHostService *docker_support.MultiHostService
|
||||
hostService HostService
|
||||
}
|
||||
|
||||
type MultiHostService = docker_support.MultiHostService
|
||||
type ContainerFilter = docker_support.ContainerFilter
|
||||
|
||||
func CreateServer(multiHostService *MultiHostService, content fs.FS, config Config) *http.Server {
|
||||
func CreateServer(hostService HostService, content fs.FS, config Config) *http.Server {
|
||||
handler := &handler{
|
||||
content: content,
|
||||
config: &config,
|
||||
multiHostService: multiHostService,
|
||||
hostService: hostService,
|
||||
}
|
||||
|
||||
return &http.Server{Addr: config.Addr, Handler: createRouter(handler)}
|
||||
|
||||
@@ -38,8 +38,8 @@ func (m *MockedClient) ContainerEvents(ctx context.Context, events chan<- contai
|
||||
return args.Error(0)
|
||||
}
|
||||
|
||||
func (m *MockedClient) ListContainers(ctx context.Context, filter container.ContainerFilter) ([]container.Container, error) {
|
||||
args := m.Called(ctx, filter)
|
||||
func (m *MockedClient) ListContainers(ctx context.Context, labels container.ContainerLabels) ([]container.Container, error) {
|
||||
args := m.Called(ctx, labels)
|
||||
return args.Get(0).([]container.Container), args.Error(1)
|
||||
}
|
||||
|
||||
@@ -86,10 +86,10 @@ func createHandler(client container.Client, content fs.FS, config Config) *chi.M
|
||||
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)
|
||||
return createRouter(&handler{
|
||||
multiHostService: multiHostService,
|
||||
hostService: multiHostService,
|
||||
content: content,
|
||||
config: &config,
|
||||
})
|
||||
|
||||
33
main.go
33
main.go
@@ -19,8 +19,10 @@ import (
|
||||
"github.com/amir20/dozzle/internal/container"
|
||||
"github.com/amir20/dozzle/internal/docker"
|
||||
"github.com/amir20/dozzle/internal/healthcheck"
|
||||
"github.com/amir20/dozzle/internal/k8s"
|
||||
"github.com/amir20/dozzle/internal/support/cli"
|
||||
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/rs/zerolog/log"
|
||||
)
|
||||
@@ -160,17 +162,17 @@ func main() {
|
||||
|
||||
log.Info().Msgf("Dozzle version %s", args.Version())
|
||||
|
||||
var multiHostService *docker_support.MultiHostService
|
||||
var hostService web.HostService
|
||||
if args.Mode == "server" {
|
||||
var localClient container.Client
|
||||
localClient, multiHostService = cli.CreateMultiHostService(certs, args)
|
||||
localClient, multiHostService := cli.CreateMultiHostService(certs, args)
|
||||
if multiHostService.TotalClients() == 0 {
|
||||
log.Fatal().Msg("Could not connect to any Docker Engine")
|
||||
} else {
|
||||
log.Info().Int("clients", multiHostService.TotalClients()).Msg("Connected to Docker")
|
||||
}
|
||||
go cli.StartEvent(args, "server", localClient, "")
|
||||
|
||||
hostService = multiHostService
|
||||
} else if args.Mode == "swarm" {
|
||||
localClient, err := docker.NewLocalClient("")
|
||||
if err != nil {
|
||||
@@ -182,7 +184,7 @@ func main() {
|
||||
}
|
||||
agentManager := docker_support.NewRetriableClientManager(args.RemoteAgent, args.Timeout, certs)
|
||||
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")
|
||||
listener, err := net.Listen("tcp", ":7007")
|
||||
if err != nil {
|
||||
@@ -194,16 +196,29 @@ func main() {
|
||||
}
|
||||
go cli.StartEvent(args, "swarm", localClient, "")
|
||||
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 {
|
||||
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 {
|
||||
log.Fatal().Str("mode", args.Mode).Msg("Invalid mode")
|
||||
}
|
||||
|
||||
srv := createServer(args, multiHostService)
|
||||
srv := createServer(args, hostService)
|
||||
go func() {
|
||||
log.Info().Msgf("Accepting connections on %s", args.Addr)
|
||||
if err := srv.ListenAndServe(); err != http.ErrServerClosed {
|
||||
@@ -224,7 +239,7 @@ func main() {
|
||||
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")
|
||||
|
||||
var provider web.AuthProvider = web.NONE
|
||||
@@ -286,7 +301,7 @@ func createServer(args cli.Args, multiHostService *docker_support.MultiHostServi
|
||||
TTL: authTTL,
|
||||
},
|
||||
EnableActions: args.EnableActions,
|
||||
Filter: args.Filter,
|
||||
Labels: args.Filter,
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user