mirror of
https://github.com/amir20/dozzle.git
synced 2025-12-21 21:33:18 +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:circleSolid': typeof import('~icons/carbon/circle-solid')['default']
|
||||||
'Carbon:copyFile': typeof import('~icons/carbon/copy-file')['default']
|
'Carbon:copyFile': typeof import('~icons/carbon/copy-file')['default']
|
||||||
'Carbon:information': typeof import('~icons/carbon/information')['default']
|
'Carbon:information': typeof import('~icons/carbon/information')['default']
|
||||||
|
'Carbon:logoKubernetes': typeof import('~icons/carbon/logo-kubernetes')['default']
|
||||||
'Carbon:macShift': typeof import('~icons/carbon/mac-shift')['default']
|
'Carbon:macShift': typeof import('~icons/carbon/mac-shift')['default']
|
||||||
'Carbon:play': typeof import('~icons/carbon/play')['default']
|
'Carbon:play': typeof import('~icons/carbon/play')['default']
|
||||||
'Carbon:restart': typeof import('~icons/carbon/restart')['default']
|
'Carbon:restart': typeof import('~icons/carbon/restart')['default']
|
||||||
|
|||||||
@@ -2,6 +2,7 @@
|
|||||||
<mdi:satellite-variant v-if="type == 'agent'" />
|
<mdi:satellite-variant v-if="type == 'agent'" />
|
||||||
<ph:globe-simple v-else-if="type == 'remote'" />
|
<ph:globe-simple v-else-if="type == 'remote'" />
|
||||||
<mdi:hexagon-multiple v-else-if="type == 'swarm'" />
|
<mdi:hexagon-multiple v-else-if="type == 'swarm'" />
|
||||||
|
<carbon:logo-kubernetes v-else-if="type == 'k8s'" />
|
||||||
<ph:computer-tower v-else />
|
<ph:computer-tower v-else />
|
||||||
</template>
|
</template>
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ export type Host = {
|
|||||||
name: string;
|
name: string;
|
||||||
nCPU: number;
|
nCPU: number;
|
||||||
memTotal: number;
|
memTotal: number;
|
||||||
type: "agent" | "local" | "remote" | "swarm";
|
type: "agent" | "local" | "remote" | "swarm" | "k8s";
|
||||||
endpoint: string;
|
endpoint: string;
|
||||||
available: boolean;
|
available: boolean;
|
||||||
dockerVersion: string;
|
dockerVersion: string;
|
||||||
|
|||||||
18
examples/ingress.yml
Normal file
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
|
golang.org/x/sync v0.11.0
|
||||||
google.golang.org/grpc v1.70.0
|
google.golang.org/grpc v1.70.0
|
||||||
google.golang.org/protobuf v1.36.5
|
google.golang.org/protobuf v1.36.5
|
||||||
|
k8s.io/api v0.32.1
|
||||||
|
k8s.io/apimachinery v0.32.1
|
||||||
|
k8s.io/client-go v0.32.1
|
||||||
|
k8s.io/metrics v0.32.1
|
||||||
)
|
)
|
||||||
|
|
||||||
require (
|
require (
|
||||||
@@ -45,14 +49,25 @@ require (
|
|||||||
github.com/buger/jsonparser v1.1.1 // indirect
|
github.com/buger/jsonparser v1.1.1 // indirect
|
||||||
github.com/cenkalti/backoff/v4 v4.3.0 // indirect
|
github.com/cenkalti/backoff/v4 v4.3.0 // indirect
|
||||||
github.com/containerd/log v0.1.0 // indirect
|
github.com/containerd/log v0.1.0 // indirect
|
||||||
github.com/davecgh/go-spew v1.1.1 // indirect
|
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect
|
||||||
github.com/decred/dcrd/dcrec/secp256k1/v4 v4.3.0 // indirect
|
github.com/decred/dcrd/dcrec/secp256k1/v4 v4.3.0 // indirect
|
||||||
github.com/distribution/reference v0.6.0 // indirect
|
github.com/distribution/reference v0.6.0 // indirect
|
||||||
|
github.com/emicklei/go-restful/v3 v3.11.0 // indirect
|
||||||
github.com/felixge/httpsnoop v1.0.4 // indirect
|
github.com/felixge/httpsnoop v1.0.4 // indirect
|
||||||
|
github.com/fxamacker/cbor/v2 v2.7.0 // indirect
|
||||||
github.com/go-logr/logr v1.4.2 // indirect
|
github.com/go-logr/logr v1.4.2 // indirect
|
||||||
github.com/go-logr/stdr v1.2.2 // indirect
|
github.com/go-logr/stdr v1.2.2 // indirect
|
||||||
|
github.com/go-openapi/jsonpointer v0.21.0 // indirect
|
||||||
|
github.com/go-openapi/jsonreference v0.20.2 // indirect
|
||||||
|
github.com/go-openapi/swag v0.23.0 // indirect
|
||||||
github.com/goccy/go-json v0.10.3 // indirect
|
github.com/goccy/go-json v0.10.3 // indirect
|
||||||
github.com/kr/pretty v0.2.1 // indirect
|
github.com/golang/protobuf v1.5.4 // indirect
|
||||||
|
github.com/google/gnostic-models v0.6.8 // indirect
|
||||||
|
github.com/google/go-cmp v0.6.0 // indirect
|
||||||
|
github.com/google/gofuzz v1.2.0 // indirect
|
||||||
|
github.com/google/uuid v1.6.0 // indirect
|
||||||
|
github.com/josharian/intern v1.0.0 // indirect
|
||||||
|
github.com/json-iterator/go v1.1.12 // indirect
|
||||||
github.com/lestrrat-go/blackmagic v1.0.2 // indirect
|
github.com/lestrrat-go/blackmagic v1.0.2 // indirect
|
||||||
github.com/lestrrat-go/httpcc v1.0.1 // indirect
|
github.com/lestrrat-go/httpcc v1.0.1 // indirect
|
||||||
github.com/lestrrat-go/httprc v1.0.6 // indirect
|
github.com/lestrrat-go/httprc v1.0.6 // indirect
|
||||||
@@ -63,18 +78,33 @@ require (
|
|||||||
github.com/mattn/go-colorable v0.1.13 // indirect
|
github.com/mattn/go-colorable v0.1.13 // indirect
|
||||||
github.com/mattn/go-isatty v0.0.20 // indirect
|
github.com/mattn/go-isatty v0.0.20 // indirect
|
||||||
github.com/moby/docker-image-spec v1.3.1 // indirect
|
github.com/moby/docker-image-spec v1.3.1 // indirect
|
||||||
|
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
|
||||||
|
github.com/modern-go/reflect2 v1.0.2 // indirect
|
||||||
|
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect
|
||||||
github.com/pkg/errors v0.9.1 // indirect
|
github.com/pkg/errors v0.9.1 // indirect
|
||||||
github.com/pmezard/go-difflib v1.0.0 // indirect
|
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect
|
||||||
github.com/segmentio/asm v1.2.0 // indirect
|
github.com/segmentio/asm v1.2.0 // indirect
|
||||||
|
github.com/spf13/pflag v1.0.5 // indirect
|
||||||
|
github.com/x448/float16 v0.8.4 // indirect
|
||||||
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.57.0 // indirect
|
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.57.0 // indirect
|
||||||
go.opentelemetry.io/otel v1.32.0 // indirect
|
go.opentelemetry.io/otel v1.32.0 // indirect
|
||||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.22.0 // indirect
|
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.22.0 // indirect
|
||||||
go.opentelemetry.io/otel/metric v1.32.0 // indirect
|
go.opentelemetry.io/otel/metric v1.32.0 // indirect
|
||||||
go.opentelemetry.io/otel/sdk v1.32.0 // indirect
|
|
||||||
go.opentelemetry.io/otel/trace v1.32.0 // indirect
|
go.opentelemetry.io/otel/trace v1.32.0 // indirect
|
||||||
|
golang.org/x/oauth2 v0.25.0 // indirect
|
||||||
|
golang.org/x/term v0.29.0 // indirect
|
||||||
golang.org/x/text v0.22.0 // indirect
|
golang.org/x/text v0.22.0 // indirect
|
||||||
|
golang.org/x/time v0.8.0 // indirect
|
||||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20241223144023-3abc09e42ca8 // indirect
|
google.golang.org/genproto/googleapis/rpc v0.0.0-20241223144023-3abc09e42ca8 // indirect
|
||||||
|
gopkg.in/evanphx/json-patch.v4 v4.12.0 // indirect
|
||||||
|
gopkg.in/inf.v0 v0.9.1 // indirect
|
||||||
gotest.tools/v3 v3.0.3 // indirect
|
gotest.tools/v3 v3.0.3 // indirect
|
||||||
|
k8s.io/klog/v2 v2.130.1 // indirect
|
||||||
|
k8s.io/kube-openapi v0.0.0-20241105132330-32ad38e42d3f // indirect
|
||||||
|
k8s.io/utils v0.0.0-20241104100929-3ea5e8cea738 // indirect
|
||||||
|
sigs.k8s.io/json v0.0.0-20241010143419-9aa6b5e7a4b3 // indirect
|
||||||
|
sigs.k8s.io/structured-merge-diff/v4 v4.4.2 // indirect
|
||||||
|
sigs.k8s.io/yaml v1.4.0 // indirect
|
||||||
)
|
)
|
||||||
|
|
||||||
go 1.23.6
|
go 1.23.6
|
||||||
|
|||||||
189
go.sum
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/Azure/go-ansiterm v0.0.0-20170929234023-d6e3b3328b78/go.mod h1:LmzpDX56iTiv29bbRTIsUNlaFfuhWRQBWjQdVyAevI8=
|
||||||
github.com/Microsoft/go-winio v0.6.2 h1:F2VQgta7ecxGYO8k3ZZz3RS8fVIXVxONVUPlNERoyfY=
|
github.com/Microsoft/go-winio v0.6.2 h1:F2VQgta7ecxGYO8k3ZZz3RS8fVIXVxONVUPlNERoyfY=
|
||||||
github.com/Microsoft/go-winio v0.6.2/go.mod h1:yd8OoFMLzJbo9gZq8j5qaps8bJ9aShtEA8Ipt1oGCvU=
|
github.com/Microsoft/go-winio v0.6.2/go.mod h1:yd8OoFMLzJbo9gZq8j5qaps8bJ9aShtEA8Ipt1oGCvU=
|
||||||
github.com/PuerkitoBio/goquery v1.10.0 h1:6fiXdLuUvYs2OJSvNRqlNPoBm6YABE226xrbavY5Wv4=
|
|
||||||
github.com/PuerkitoBio/goquery v1.10.0/go.mod h1:TjZZl68Q3eGHNBA8CWaxAN7rOU1EbDz3CWuolcO5Yu4=
|
|
||||||
github.com/PuerkitoBio/goquery v1.10.1 h1:Y8JGYUkXWTGRB6Ars3+j3kN0xg1YqqlwvdTV8WTFQcU=
|
github.com/PuerkitoBio/goquery v1.10.1 h1:Y8JGYUkXWTGRB6Ars3+j3kN0xg1YqqlwvdTV8WTFQcU=
|
||||||
github.com/PuerkitoBio/goquery v1.10.1/go.mod h1:IYiHrOMps66ag56LEH7QYDDupKXyo5A8qrjIx3ZtujY=
|
github.com/PuerkitoBio/goquery v1.10.1/go.mod h1:IYiHrOMps66ag56LEH7QYDDupKXyo5A8qrjIx3ZtujY=
|
||||||
github.com/alexflint/go-arg v1.5.1 h1:nBuWUCpuRy0snAG+uIJ6N0UvYxpxA0/ghA/AaHxlT8Y=
|
github.com/alexflint/go-arg v1.5.1 h1:nBuWUCpuRy0snAG+uIJ6N0UvYxpxA0/ghA/AaHxlT8Y=
|
||||||
github.com/alexflint/go-arg v1.5.1/go.mod h1:A7vTJzvjoaSTypg4biM5uYNTkJ27SkNTArtYXnlqVO8=
|
github.com/alexflint/go-arg v1.5.1/go.mod h1:A7vTJzvjoaSTypg4biM5uYNTkJ27SkNTArtYXnlqVO8=
|
||||||
github.com/alexflint/go-scalar v1.2.0 h1:WR7JPKkeNpnYIOfHRa7ivM21aWAdHD0gEWHCx+WQBRw=
|
github.com/alexflint/go-scalar v1.2.0 h1:WR7JPKkeNpnYIOfHRa7ivM21aWAdHD0gEWHCx+WQBRw=
|
||||||
github.com/alexflint/go-scalar v1.2.0/go.mod h1:LoFvNMqS1CPrMVltza4LvnGKhaSpc3oyLEBUZVhhS2o=
|
github.com/alexflint/go-scalar v1.2.0/go.mod h1:LoFvNMqS1CPrMVltza4LvnGKhaSpc3oyLEBUZVhhS2o=
|
||||||
github.com/andybalholm/cascadia v1.3.2 h1:3Xi6Dw5lHF15JtdcmAHD3i1+T8plmv7BQ/nsViSLyss=
|
|
||||||
github.com/andybalholm/cascadia v1.3.2/go.mod h1:7gtRlve5FxPPgIgX36uWBX58OdBsSS6lUvCFb+h7KvU=
|
|
||||||
github.com/andybalholm/cascadia v1.3.3 h1:AG2YHrzJIm4BZ19iwJ/DAua6Btl3IwJX+VI4kktS1LM=
|
github.com/andybalholm/cascadia v1.3.3 h1:AG2YHrzJIm4BZ19iwJ/DAua6Btl3IwJX+VI4kktS1LM=
|
||||||
github.com/andybalholm/cascadia v1.3.3/go.mod h1:xNd9bqTn98Ln4DwST8/nG+H0yuB8Hmgu1YHNnWw0GeA=
|
github.com/andybalholm/cascadia v1.3.3/go.mod h1:xNd9bqTn98Ln4DwST8/nG+H0yuB8Hmgu1YHNnWw0GeA=
|
||||||
github.com/bahlo/generic-list-go v0.2.0 h1:5sz/EEAK+ls5wF+NeqDpk5+iNdMDXrh3z3nPnH1Wvgk=
|
github.com/bahlo/generic-list-go v0.2.0 h1:5sz/EEAK+ls5wF+NeqDpk5+iNdMDXrh3z3nPnH1Wvgk=
|
||||||
@@ -25,22 +21,16 @@ github.com/cenkalti/backoff/v4 v4.3.0/go.mod h1:Y3VNntkOUPxTVeUxJ/G5vcM//AlwfmyY
|
|||||||
github.com/containerd/log v0.1.0 h1:TCJt7ioM2cr/tfR8GPbGf9/VRAX8D2B4PjzCpfX540I=
|
github.com/containerd/log v0.1.0 h1:TCJt7ioM2cr/tfR8GPbGf9/VRAX8D2B4PjzCpfX540I=
|
||||||
github.com/containerd/log v0.1.0/go.mod h1:VRRf09a7mHDIRezVKTRCrOq78v577GXq3bSa3EhrzVo=
|
github.com/containerd/log v0.1.0/go.mod h1:VRRf09a7mHDIRezVKTRCrOq78v577GXq3bSa3EhrzVo=
|
||||||
github.com/coreos/go-systemd/v22 v22.5.0/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc=
|
github.com/coreos/go-systemd/v22 v22.5.0/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc=
|
||||||
|
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
|
||||||
github.com/creack/pty v1.1.11/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
|
github.com/creack/pty v1.1.11/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
|
||||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
|
||||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||||
|
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM=
|
||||||
|
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||||
github.com/decred/dcrd/dcrec/secp256k1/v4 v4.3.0 h1:rpfIENRNNilwHwZeG5+P150SMrnNEcHYvcCuK6dPZSg=
|
github.com/decred/dcrd/dcrec/secp256k1/v4 v4.3.0 h1:rpfIENRNNilwHwZeG5+P150SMrnNEcHYvcCuK6dPZSg=
|
||||||
github.com/decred/dcrd/dcrec/secp256k1/v4 v4.3.0/go.mod h1:v57UDF4pDQJcEfFUCRop3lJL149eHGSe9Jvczhzjo/0=
|
github.com/decred/dcrd/dcrec/secp256k1/v4 v4.3.0/go.mod h1:v57UDF4pDQJcEfFUCRop3lJL149eHGSe9Jvczhzjo/0=
|
||||||
github.com/distribution/reference v0.6.0 h1:0IXCQ5g4/QMHHkarYzh5l+u8T3t73zM5QvfrDyIgxBk=
|
github.com/distribution/reference v0.6.0 h1:0IXCQ5g4/QMHHkarYzh5l+u8T3t73zM5QvfrDyIgxBk=
|
||||||
github.com/distribution/reference v0.6.0/go.mod h1:BbU0aIcezP1/5jX/8MP0YiH4SdvB5Y4f/wlDRiLyi3E=
|
github.com/distribution/reference v0.6.0/go.mod h1:BbU0aIcezP1/5jX/8MP0YiH4SdvB5Y4f/wlDRiLyi3E=
|
||||||
github.com/docker/docker v27.3.1+incompatible h1:KttF0XoteNTicmUtBO0L2tP+J7FGRFTjaEF4k6WdhfI=
|
|
||||||
github.com/docker/docker v27.3.1+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk=
|
|
||||||
github.com/docker/docker v27.4.0+incompatible h1:I9z7sQ5qyzO0BfAb9IMOawRkAGxhYsidKiTMcm0DU+A=
|
|
||||||
github.com/docker/docker v27.4.0+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk=
|
|
||||||
github.com/docker/docker v27.4.1+incompatible h1:ZJvcY7gfwHn1JF48PfbyXg7Jyt9ZCWDW+GGXOIxEwp4=
|
|
||||||
github.com/docker/docker v27.4.1+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk=
|
|
||||||
github.com/docker/docker v27.5.0+incompatible h1:um++2NcQtGRTz5eEgO6aJimo6/JxrTXC941hd05JO6U=
|
|
||||||
github.com/docker/docker v27.5.0+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk=
|
|
||||||
github.com/docker/docker v27.5.1+incompatible h1:4PYU5dnBYqRQi0294d1FBECqT9ECWeQAIfE8q4YnPY8=
|
github.com/docker/docker v27.5.1+incompatible h1:4PYU5dnBYqRQi0294d1FBECqT9ECWeQAIfE8q4YnPY8=
|
||||||
github.com/docker/docker v27.5.1+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk=
|
github.com/docker/docker v27.5.1+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk=
|
||||||
github.com/docker/go-connections v0.5.0 h1:USnMq7hx7gwdVZq1L49hLXaFtUdTADjXGp+uj1Br63c=
|
github.com/docker/go-connections v0.5.0 h1:USnMq7hx7gwdVZq1L49hLXaFtUdTADjXGp+uj1Br63c=
|
||||||
@@ -49,16 +39,14 @@ github.com/docker/go-units v0.5.0 h1:69rxXcBk27SvSaaxTtLh/8llcHD8vYHT7WSdRZ/jvr4
|
|||||||
github.com/docker/go-units v0.5.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk=
|
github.com/docker/go-units v0.5.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk=
|
||||||
github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY=
|
github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY=
|
||||||
github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto=
|
github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto=
|
||||||
|
github.com/emicklei/go-restful/v3 v3.11.0 h1:rAQeMHw1c7zTmncogyy8VvRZwtkmkZ4FxERmMY4rD+g=
|
||||||
|
github.com/emicklei/go-restful/v3 v3.11.0/go.mod h1:6n3XBCmQQb25CM2LCACGz8ukIrRry+4bhvbpWn3mrbc=
|
||||||
github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg=
|
github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg=
|
||||||
github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U=
|
github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U=
|
||||||
github.com/go-chi/chi/v5 v5.1.0 h1:acVI1TYaD+hhedDJ3r54HyA6sExp3HfXq7QWEEY/xMw=
|
github.com/fxamacker/cbor/v2 v2.7.0 h1:iM5WgngdRBanHcxugY4JySA0nk1wZorNOpTgCMedv5E=
|
||||||
github.com/go-chi/chi/v5 v5.1.0/go.mod h1:DslCQbL2OYiznFReuXYUmQ2hGd1aDpCnlMNITLSKoi8=
|
github.com/fxamacker/cbor/v2 v2.7.0/go.mod h1:pxXPTn3joSm21Gbwsv0w9OSA2y1HFR9qXEeXQVeNoDQ=
|
||||||
github.com/go-chi/chi/v5 v5.2.0 h1:Aj1EtB0qR2Rdo2dG4O94RIU35w2lvQSj6BRA4+qwFL0=
|
|
||||||
github.com/go-chi/chi/v5 v5.2.0/go.mod h1:DslCQbL2OYiznFReuXYUmQ2hGd1aDpCnlMNITLSKoi8=
|
|
||||||
github.com/go-chi/chi/v5 v5.2.1 h1:KOIHODQj58PmL80G2Eak4WdvUzjSJSm0vG72crDCqb8=
|
github.com/go-chi/chi/v5 v5.2.1 h1:KOIHODQj58PmL80G2Eak4WdvUzjSJSm0vG72crDCqb8=
|
||||||
github.com/go-chi/chi/v5 v5.2.1/go.mod h1:L2yAIGWB3H+phAw1NxKwWM+7eUH/lU8pOMm5hHcoops=
|
github.com/go-chi/chi/v5 v5.2.1/go.mod h1:L2yAIGWB3H+phAw1NxKwWM+7eUH/lU8pOMm5hHcoops=
|
||||||
github.com/go-chi/jwtauth/v5 v5.3.1 h1:1ePWrjVctvp1tyBq5b/2ER8Th/+RbYc7x4qNsc5rh5A=
|
|
||||||
github.com/go-chi/jwtauth/v5 v5.3.1/go.mod h1:6Fl2RRmWXs3tJYE1IQGX81FsPoGqDwq9c15j52R5q80=
|
|
||||||
github.com/go-chi/jwtauth/v5 v5.3.2 h1:s+ON3ATyyMs3Me0kqyuua6Rwu+2zqIIkL0GCaMarwvs=
|
github.com/go-chi/jwtauth/v5 v5.3.2 h1:s+ON3ATyyMs3Me0kqyuua6Rwu+2zqIIkL0GCaMarwvs=
|
||||||
github.com/go-chi/jwtauth/v5 v5.3.2/go.mod h1:O4QvPRuZLZghl9WvfVaON+ARfGzpD2PBX/QY5vUz7aQ=
|
github.com/go-chi/jwtauth/v5 v5.3.2/go.mod h1:O4QvPRuZLZghl9WvfVaON+ARfGzpD2PBX/QY5vUz7aQ=
|
||||||
github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
|
github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
|
||||||
@@ -66,6 +54,16 @@ github.com/go-logr/logr v1.4.2 h1:6pFjapn8bFcIbiKo3XT4j/BhANplGihG6tvd+8rYgrY=
|
|||||||
github.com/go-logr/logr v1.4.2/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
|
github.com/go-logr/logr v1.4.2/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
|
||||||
github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag=
|
github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag=
|
||||||
github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE=
|
github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE=
|
||||||
|
github.com/go-openapi/jsonpointer v0.19.6/go.mod h1:osyAmYz/mB/C3I+WsTTSgw1ONzaLJoLCyoi6/zppojs=
|
||||||
|
github.com/go-openapi/jsonpointer v0.21.0 h1:YgdVicSA9vH5RiHs9TZW5oyafXZFc6+2Vc1rr/O9oNQ=
|
||||||
|
github.com/go-openapi/jsonpointer v0.21.0/go.mod h1:IUyH9l/+uyhIYQ/PXVA41Rexl+kOkAPDdXEYns6fzUY=
|
||||||
|
github.com/go-openapi/jsonreference v0.20.2 h1:3sVjiK66+uXK/6oQ8xgcRKcFgQ5KXa2KvnJRumpMGbE=
|
||||||
|
github.com/go-openapi/jsonreference v0.20.2/go.mod h1:Bl1zwGIM8/wsvqjsOQLJ/SH+En5Ap4rVB5KVcIDZG2k=
|
||||||
|
github.com/go-openapi/swag v0.22.3/go.mod h1:UzaqsxGiab7freDnrUUra0MwWfN/q7tE4j+VcZ0yl14=
|
||||||
|
github.com/go-openapi/swag v0.23.0 h1:vsEVJDUo2hPJ2tu0/Xc+4noaxyEffXNIs3cOULZ+GrE=
|
||||||
|
github.com/go-openapi/swag v0.23.0/go.mod h1:esZ8ITTYEsH1V2trKHjAN8Ai7xHb8RV+YSZ577vPjgQ=
|
||||||
|
github.com/go-task/slim-sprig/v3 v3.0.0 h1:sUs3vkvUymDpBKi3qH1YSqBQk9+9D/8M2mN1vB6EwHI=
|
||||||
|
github.com/go-task/slim-sprig/v3 v3.0.0/go.mod h1:W848ghGpv3Qj3dhTPRyJypKRiqCdHZiAzKg9hl15HA8=
|
||||||
github.com/goccy/go-json v0.10.3 h1:KZ5WoDbxAIgm2HNbYckL0se1fHD6rz5j4ywS6ebzDqA=
|
github.com/goccy/go-json v0.10.3 h1:KZ5WoDbxAIgm2HNbYckL0se1fHD6rz5j4ywS6ebzDqA=
|
||||||
github.com/goccy/go-json v0.10.3/go.mod h1:oq7eo15ShAhp70Anwd5lgX2pLfOS3QCiwU/PULtXL6M=
|
github.com/goccy/go-json v0.10.3/go.mod h1:oq7eo15ShAhp70Anwd5lgX2pLfOS3QCiwU/PULtXL6M=
|
||||||
github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=
|
github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=
|
||||||
@@ -73,18 +71,32 @@ github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q=
|
|||||||
github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q=
|
github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q=
|
||||||
github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek=
|
github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek=
|
||||||
github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps=
|
github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps=
|
||||||
|
github.com/google/gnostic-models v0.6.8 h1:yo/ABAfM5IMRsS1VnXjTBvUb61tFIHozhlYvRgGre9I=
|
||||||
|
github.com/google/gnostic-models v0.6.8/go.mod h1:5n7qKqH0f5wFt+aWF8CW6pZLLNOfYuF5OpfBSENuI8U=
|
||||||
github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
|
github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
|
||||||
github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||||
|
github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
|
||||||
github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
|
github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
|
||||||
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
|
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
|
||||||
|
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
|
||||||
|
github.com/google/gofuzz v1.2.0 h1:xRy4A+RhZaiKjJ1bPfwQ8sedCA+YS2YcCHW6ec7JMi0=
|
||||||
|
github.com/google/gofuzz v1.2.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
|
||||||
|
github.com/google/pprof v0.0.0-20241029153458-d1b30febd7db h1:097atOisP2aRj7vFgYQBbFN4U4JNXUNYpxael3UzMyo=
|
||||||
|
github.com/google/pprof v0.0.0-20241029153458-d1b30febd7db/go.mod h1:vavhavw2zAxS5dIdcRluK6cSGGPlZynqzFM8NdvU144=
|
||||||
|
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
|
||||||
|
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||||
github.com/grpc-ecosystem/grpc-gateway/v2 v2.16.0 h1:YBftPWNWd4WwGqtY2yeZL2ef8rHAxPBD8KFhJpmcqms=
|
github.com/grpc-ecosystem/grpc-gateway/v2 v2.16.0 h1:YBftPWNWd4WwGqtY2yeZL2ef8rHAxPBD8KFhJpmcqms=
|
||||||
github.com/grpc-ecosystem/grpc-gateway/v2 v2.16.0/go.mod h1:YN5jB8ie0yfIUg6VvR9Kz84aCaG7AsGZnLjhHbUqwPg=
|
github.com/grpc-ecosystem/grpc-gateway/v2 v2.16.0/go.mod h1:YN5jB8ie0yfIUg6VvR9Kz84aCaG7AsGZnLjhHbUqwPg=
|
||||||
|
github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY=
|
||||||
github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y=
|
github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y=
|
||||||
|
github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM=
|
||||||
|
github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo=
|
||||||
github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8=
|
github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8=
|
||||||
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
|
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
|
||||||
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
|
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
|
||||||
github.com/kr/pretty v0.2.1 h1:Fmg33tUaq4/8ym9TJN1x7sLJnHVwhP33CNkpYV/7rwI=
|
|
||||||
github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
|
github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
|
||||||
|
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
|
||||||
|
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
|
||||||
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
|
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
|
||||||
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
|
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
|
||||||
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
|
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
|
||||||
@@ -101,8 +113,6 @@ github.com/lestrrat-go/jwx/v2 v2.1.3 h1:Ud4lb2QuxRClYAmRleF50KrbKIoM1TddXgBrneT5
|
|||||||
github.com/lestrrat-go/jwx/v2 v2.1.3/go.mod h1:q6uFgbgZfEmQrfJfrCo90QcQOcXFMfbI/fO0NqRtvZo=
|
github.com/lestrrat-go/jwx/v2 v2.1.3/go.mod h1:q6uFgbgZfEmQrfJfrCo90QcQOcXFMfbI/fO0NqRtvZo=
|
||||||
github.com/lestrrat-go/option v1.0.1 h1:oAzP2fvZGQKWkvHa1/SAcFolBEca1oN+mQ7eooNBEYU=
|
github.com/lestrrat-go/option v1.0.1 h1:oAzP2fvZGQKWkvHa1/SAcFolBEca1oN+mQ7eooNBEYU=
|
||||||
github.com/lestrrat-go/option v1.0.1/go.mod h1:5ZHFbivi4xwXxhxY9XHDe2FHo6/Z7WWmtT7T5nBBp3I=
|
github.com/lestrrat-go/option v1.0.1/go.mod h1:5ZHFbivi4xwXxhxY9XHDe2FHo6/Z7WWmtT7T5nBBp3I=
|
||||||
github.com/magiconair/properties v1.8.7 h1:IeQXZAiQcpL9mgcAe1Nu6cX9LLw6ExEHKjN0VQdvPDY=
|
|
||||||
github.com/magiconair/properties v1.8.7/go.mod h1:Dhd985XPs7jluiymwWYZ0G4Z61jb3vdS329zhj2hYo0=
|
|
||||||
github.com/magiconair/properties v1.8.9 h1:nWcCbLq1N2v/cpNsy5WvQ37Fb+YElfq20WJ/a8RkpQM=
|
github.com/magiconair/properties v1.8.9 h1:nWcCbLq1N2v/cpNsy5WvQ37Fb+YElfq20WJ/a8RkpQM=
|
||||||
github.com/magiconair/properties v1.8.9/go.mod h1:Dhd985XPs7jluiymwWYZ0G4Z61jb3vdS329zhj2hYo0=
|
github.com/magiconair/properties v1.8.9/go.mod h1:Dhd985XPs7jluiymwWYZ0G4Z61jb3vdS329zhj2hYo0=
|
||||||
github.com/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0=
|
github.com/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0=
|
||||||
@@ -117,8 +127,19 @@ github.com/moby/docker-image-spec v1.3.1 h1:jMKff3w6PgbfSa69GfNg+zN/XLhfXJGnEx3N
|
|||||||
github.com/moby/docker-image-spec v1.3.1/go.mod h1:eKmb5VW8vQEh/BAr2yvVNvuiJuY6UIocYsFu/DxxRpo=
|
github.com/moby/docker-image-spec v1.3.1/go.mod h1:eKmb5VW8vQEh/BAr2yvVNvuiJuY6UIocYsFu/DxxRpo=
|
||||||
github.com/moby/term v0.0.0-20201216013528-df9cb8a40635 h1:rzf0wL0CHVc8CEsgyygG0Mn9CNCCPZqOPaz8RiiHYQk=
|
github.com/moby/term v0.0.0-20201216013528-df9cb8a40635 h1:rzf0wL0CHVc8CEsgyygG0Mn9CNCCPZqOPaz8RiiHYQk=
|
||||||
github.com/moby/term v0.0.0-20201216013528-df9cb8a40635/go.mod h1:FBS0z0QWA44HXygs7VXDUOGoN/1TV3RuWkLO04am3wc=
|
github.com/moby/term v0.0.0-20201216013528-df9cb8a40635/go.mod h1:FBS0z0QWA44HXygs7VXDUOGoN/1TV3RuWkLO04am3wc=
|
||||||
|
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
|
||||||
|
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg=
|
||||||
|
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
|
||||||
|
github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M=
|
||||||
|
github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
|
||||||
github.com/morikuni/aec v1.0.0 h1:nP9CBfwrvYnBRgY6qfDQkygYDmYwOilePFkwzv4dU8A=
|
github.com/morikuni/aec v1.0.0 h1:nP9CBfwrvYnBRgY6qfDQkygYDmYwOilePFkwzv4dU8A=
|
||||||
github.com/morikuni/aec v1.0.0/go.mod h1:BbKIizmSmc5MMPqRYbxO4ZU0S0+P200+tUnFx7PXmsc=
|
github.com/morikuni/aec v1.0.0/go.mod h1:BbKIizmSmc5MMPqRYbxO4ZU0S0+P200+tUnFx7PXmsc=
|
||||||
|
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA=
|
||||||
|
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ=
|
||||||
|
github.com/onsi/ginkgo/v2 v2.21.0 h1:7rg/4f3rB88pb5obDgNZrNHrQ4e6WpjonchcpuBRnZM=
|
||||||
|
github.com/onsi/ginkgo/v2 v2.21.0/go.mod h1:7Du3c42kxCUegi0IImZ1wUQzMBVecgIHjR1C+NkhLQo=
|
||||||
|
github.com/onsi/gomega v1.35.1 h1:Cwbd75ZBPxFSuZ6T+rN/WCb/gOc6YgFBXLlZLhC7Ds4=
|
||||||
|
github.com/onsi/gomega v1.35.1/go.mod h1:PvZbdDc8J6XJEpDK4HCuRBm8a6Fzp9/DmhC9C7yFlog=
|
||||||
github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8Oi/yOhh5U=
|
github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8Oi/yOhh5U=
|
||||||
github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM=
|
github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM=
|
||||||
github.com/opencontainers/image-spec v1.1.0 h1:8SG7/vwALn54lVB/0yZ/MMwhFrPYtpEHQb2IpWsCzug=
|
github.com/opencontainers/image-spec v1.1.0 h1:8SG7/vwALn54lVB/0yZ/MMwhFrPYtpEHQb2IpWsCzug=
|
||||||
@@ -126,23 +147,16 @@ github.com/opencontainers/image-spec v1.1.0/go.mod h1:W4s4sFTMaBeK1BQLXbG4AdM2sz
|
|||||||
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||||
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
|
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
|
||||||
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
|
||||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||||
github.com/puzpuzpuz/xsync/v3 v3.4.0 h1:DuVBAdXuGFHv8adVXjWWZ63pJq+NRXOWVXlKDBZ+mJ4=
|
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U=
|
||||||
github.com/puzpuzpuz/xsync/v3 v3.4.0/go.mod h1:VjzYrABPabuM4KyBh1Ftq6u8nhwY5tBPKP9jpmh0nnA=
|
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||||
github.com/puzpuzpuz/xsync/v3 v3.4.1 h1:wWXLKXwzpsduC3kUSahiL45MWxkGb+AQG0dsri4iftA=
|
|
||||||
github.com/puzpuzpuz/xsync/v3 v3.4.1/go.mod h1:VjzYrABPabuM4KyBh1Ftq6u8nhwY5tBPKP9jpmh0nnA=
|
|
||||||
github.com/puzpuzpuz/xsync/v3 v3.5.0 h1:i+cMcpEDY1BkNm7lPDkCtE4oElsYLn+EKF8kAu2vXT4=
|
github.com/puzpuzpuz/xsync/v3 v3.5.0 h1:i+cMcpEDY1BkNm7lPDkCtE4oElsYLn+EKF8kAu2vXT4=
|
||||||
github.com/puzpuzpuz/xsync/v3 v3.5.0/go.mod h1:VjzYrABPabuM4KyBh1Ftq6u8nhwY5tBPKP9jpmh0nnA=
|
github.com/puzpuzpuz/xsync/v3 v3.5.0/go.mod h1:VjzYrABPabuM4KyBh1Ftq6u8nhwY5tBPKP9jpmh0nnA=
|
||||||
|
github.com/rogpeppe/go-internal v1.12.0 h1:exVL4IDcn6na9z1rAb56Vxr+CgyK3nn3O+epU5NdKM8=
|
||||||
|
github.com/rogpeppe/go-internal v1.12.0/go.mod h1:E+RYuTGaKKdloAfM02xzb0FW3Paa99yedzYV+kq4uf4=
|
||||||
github.com/rs/xid v1.5.0/go.mod h1:trrq9SKmegXys3aeAKXMUTdJsYXVwGY3RLcfgqegfbg=
|
github.com/rs/xid v1.5.0/go.mod h1:trrq9SKmegXys3aeAKXMUTdJsYXVwGY3RLcfgqegfbg=
|
||||||
github.com/rs/zerolog v1.33.0 h1:1cU2KZkvPxNyfgEmhHAz/1A9Bz+llsdYzklWFzgp0r8=
|
github.com/rs/zerolog v1.33.0 h1:1cU2KZkvPxNyfgEmhHAz/1A9Bz+llsdYzklWFzgp0r8=
|
||||||
github.com/rs/zerolog v1.33.0/go.mod h1:/7mN4D5sKwJLZQ2b/znpjC3/GQWY/xaDXUM0kKWRHss=
|
github.com/rs/zerolog v1.33.0/go.mod h1:/7mN4D5sKwJLZQ2b/znpjC3/GQWY/xaDXUM0kKWRHss=
|
||||||
github.com/samber/lo v1.47.0 h1:z7RynLwP5nbyRscyvcD043DWYoOcYRv3mV8lBeqOCLc=
|
|
||||||
github.com/samber/lo v1.47.0/go.mod h1:RmDH9Ct32Qy3gduHQuKJ3gW1fMHAnE/fAzQuf6He5cU=
|
|
||||||
github.com/samber/lo v1.48.0 h1:ELOfcaM7vdYPe0egBS2Nxa8LxkY4lR+9LBzj0l6cHJ0=
|
|
||||||
github.com/samber/lo v1.48.0/go.mod h1:dO6KHFzUKXgP8LDhU0oI8d2hekjXnGOu0DB8Jecxd6o=
|
|
||||||
github.com/samber/lo v1.49.0 h1:AGnTnQrg1jpFuwECPUSoxZCfVH5W22b605kWSry3YxM=
|
|
||||||
github.com/samber/lo v1.49.0/go.mod h1:dO6KHFzUKXgP8LDhU0oI8d2hekjXnGOu0DB8Jecxd6o=
|
|
||||||
github.com/samber/lo v1.49.1 h1:4BIFyVfuQSEpluc7Fua+j1NolZHiEHEpaSEKdsH0tew=
|
github.com/samber/lo v1.49.1 h1:4BIFyVfuQSEpluc7Fua+j1NolZHiEHEpaSEKdsH0tew=
|
||||||
github.com/samber/lo v1.49.1/go.mod h1:dO6KHFzUKXgP8LDhU0oI8d2hekjXnGOu0DB8Jecxd6o=
|
github.com/samber/lo v1.49.1/go.mod h1:dO6KHFzUKXgP8LDhU0oI8d2hekjXnGOu0DB8Jecxd6o=
|
||||||
github.com/segmentio/asm v1.2.0 h1:9BQrFxC+YOHJlTlHGkTrFWf59nbL3XnCoFLTwDCI7ys=
|
github.com/segmentio/asm v1.2.0 h1:9BQrFxC+YOHJlTlHGkTrFWf59nbL3XnCoFLTwDCI7ys=
|
||||||
@@ -151,24 +165,29 @@ github.com/sergi/go-diff v1.3.1 h1:xkr+Oxo4BOQKmkn/B9eMK0g5Kg/983T9DqqPHwYqD+8=
|
|||||||
github.com/sergi/go-diff v1.3.1/go.mod h1:aMJSSKb2lpPvRNec0+w3fl7LP9IOFzdc9Pa4NFbPK1I=
|
github.com/sergi/go-diff v1.3.1/go.mod h1:aMJSSKb2lpPvRNec0+w3fl7LP9IOFzdc9Pa4NFbPK1I=
|
||||||
github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ=
|
github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ=
|
||||||
github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ=
|
github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ=
|
||||||
github.com/spf13/afero v1.11.0 h1:WJQKhtpdm3v2IzqG8VMqrr6Rf3UYpEF239Jy9wNepM8=
|
|
||||||
github.com/spf13/afero v1.11.0/go.mod h1:GH9Y3pIexgf1MTIWtNGyogA5MwRIDXGUr+hbWNoBjkY=
|
|
||||||
github.com/spf13/afero v1.12.0 h1:UcOPyRBYczmFn6yvphxkn9ZEOY65cpwGKb5mL36mrqs=
|
github.com/spf13/afero v1.12.0 h1:UcOPyRBYczmFn6yvphxkn9ZEOY65cpwGKb5mL36mrqs=
|
||||||
github.com/spf13/afero v1.12.0/go.mod h1:ZTlWwG4/ahT8W7T0WQ5uYmjI9duaLQGy3Q2OAl4sk/4=
|
github.com/spf13/afero v1.12.0/go.mod h1:ZTlWwG4/ahT8W7T0WQ5uYmjI9duaLQGy3Q2OAl4sk/4=
|
||||||
github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4=
|
github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4=
|
||||||
|
github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA=
|
||||||
|
github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
|
||||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||||
|
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
|
||||||
|
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
|
||||||
github.com/stretchr/objx v0.5.2 h1:xuMeJ0Sdp5ZMRXx/aWO6RZxdr3beISkG5/G/aIRr3pY=
|
github.com/stretchr/objx v0.5.2 h1:xuMeJ0Sdp5ZMRXx/aWO6RZxdr3beISkG5/G/aIRr3pY=
|
||||||
github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA=
|
github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA=
|
||||||
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
|
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
|
||||||
|
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
|
||||||
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
|
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
|
||||||
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||||
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||||
github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg=
|
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
|
||||||
github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
|
github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
|
||||||
github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA=
|
github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA=
|
||||||
github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
|
github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
|
||||||
github.com/wk8/go-ordered-map/v2 v2.1.8 h1:5h/BUHu93oj4gIdvHHHGsScSTMijfx5PeYkE/fJgbpc=
|
github.com/wk8/go-ordered-map/v2 v2.1.8 h1:5h/BUHu93oj4gIdvHHHGsScSTMijfx5PeYkE/fJgbpc=
|
||||||
github.com/wk8/go-ordered-map/v2 v2.1.8/go.mod h1:5nJHM5DyteebpVlHnWMV0rPz6Zp7+xBAnxjb1X5vnTw=
|
github.com/wk8/go-ordered-map/v2 v2.1.8/go.mod h1:5nJHM5DyteebpVlHnWMV0rPz6Zp7+xBAnxjb1X5vnTw=
|
||||||
|
github.com/x448/float16 v0.8.4 h1:qLwI1I70+NjRFUR3zs1JPUCgaCXSh3SW62uAKT1mSBM=
|
||||||
|
github.com/x448/float16 v0.8.4/go.mod h1:14CWIYCyZA/cWjXOioeEpHeN/83MdbZDRQHoFcYsOfg=
|
||||||
github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
||||||
github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
||||||
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
|
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
|
||||||
@@ -184,10 +203,10 @@ go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.22.0 h1:FyjCy
|
|||||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.22.0/go.mod h1:hYwym2nDEeZfG/motx0p7L7J1N1vyzIThemQsb4g2qY=
|
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.22.0/go.mod h1:hYwym2nDEeZfG/motx0p7L7J1N1vyzIThemQsb4g2qY=
|
||||||
go.opentelemetry.io/otel/metric v1.32.0 h1:xV2umtmNcThh2/a/aCP+h64Xx5wsj8qqnkYZktzNa0M=
|
go.opentelemetry.io/otel/metric v1.32.0 h1:xV2umtmNcThh2/a/aCP+h64Xx5wsj8qqnkYZktzNa0M=
|
||||||
go.opentelemetry.io/otel/metric v1.32.0/go.mod h1:jH7CIbbK6SH2V2wE16W05BHCtIDzauciCRLoc/SyMv8=
|
go.opentelemetry.io/otel/metric v1.32.0/go.mod h1:jH7CIbbK6SH2V2wE16W05BHCtIDzauciCRLoc/SyMv8=
|
||||||
go.opentelemetry.io/otel/sdk v1.22.0 h1:6coWHw9xw7EfClIC/+O31R8IY3/+EiRFHevmHafB2Gw=
|
go.opentelemetry.io/otel/sdk v1.32.0 h1:RNxepc9vK59A8XsgZQouW8ue8Gkb4jpWtJm9ge5lEG4=
|
||||||
go.opentelemetry.io/otel/sdk v1.22.0/go.mod h1:iu7luyVGYovrRpe2fmj3CVKouQNdTOkxtLzPvPz1DOc=
|
|
||||||
go.opentelemetry.io/otel/sdk v1.31.0/go.mod h1:TfRbMdhvxIIr/B2N2LQW2S5v9m3gOQ/08KsbbO5BPT0=
|
|
||||||
go.opentelemetry.io/otel/sdk v1.32.0/go.mod h1:LqgegDBjKMmb2GC6/PrTnteJG39I8/vJCAP9LlJXEjU=
|
go.opentelemetry.io/otel/sdk v1.32.0/go.mod h1:LqgegDBjKMmb2GC6/PrTnteJG39I8/vJCAP9LlJXEjU=
|
||||||
|
go.opentelemetry.io/otel/sdk/metric v1.32.0 h1:rZvFnvmvawYb0alrYkjraqJq0Z4ZUJAiyYCU9snn1CU=
|
||||||
|
go.opentelemetry.io/otel/sdk/metric v1.32.0/go.mod h1:PWeZlq0zt9YkYAp3gjKZ0eicRYvOh1Gd+X99x6GHpCQ=
|
||||||
go.opentelemetry.io/otel/trace v1.32.0 h1:WIC9mYrXf8TmY/EXuULKc8hR17vE+Hjv2cssQDe03fM=
|
go.opentelemetry.io/otel/trace v1.32.0 h1:WIC9mYrXf8TmY/EXuULKc8hR17vE+Hjv2cssQDe03fM=
|
||||||
go.opentelemetry.io/otel/trace v1.32.0/go.mod h1:+i4rkvCraA+tG6AzwloGaCtkx53Fa+L+V8e9a7YvhT8=
|
go.opentelemetry.io/otel/trace v1.32.0/go.mod h1:+i4rkvCraA+tG6AzwloGaCtkx53Fa+L+V8e9a7YvhT8=
|
||||||
go.opentelemetry.io/proto/otlp v1.0.0 h1:T0TX0tmXU8a3CbNXzEKGeU5mIVOdf0oykP+u2lIVU/I=
|
go.opentelemetry.io/proto/otlp v1.0.0 h1:T0TX0tmXU8a3CbNXzEKGeU5mIVOdf0oykP+u2lIVU/I=
|
||||||
@@ -199,14 +218,7 @@ golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5y
|
|||||||
golang.org/x/crypto v0.13.0/go.mod h1:y6Z2r+Rw4iayiXXAIxJIDAJ1zMW4yaTpebo8fPOliYc=
|
golang.org/x/crypto v0.13.0/go.mod h1:y6Z2r+Rw4iayiXXAIxJIDAJ1zMW4yaTpebo8fPOliYc=
|
||||||
golang.org/x/crypto v0.19.0/go.mod h1:Iy9bg/ha4yyC70EfRS8jz+B6ybOBKMaSxLj6P6oBDfU=
|
golang.org/x/crypto v0.19.0/go.mod h1:Iy9bg/ha4yyC70EfRS8jz+B6ybOBKMaSxLj6P6oBDfU=
|
||||||
golang.org/x/crypto v0.23.0/go.mod h1:CKFgDieR+mRhux2Lsu27y0fO304Db0wZe70UKqHu0v8=
|
golang.org/x/crypto v0.23.0/go.mod h1:CKFgDieR+mRhux2Lsu27y0fO304Db0wZe70UKqHu0v8=
|
||||||
golang.org/x/crypto v0.29.0 h1:L5SG1JTTXupVV3n6sUqMTeWbjAyfPwoda2DLX8J8FrQ=
|
|
||||||
golang.org/x/crypto v0.29.0/go.mod h1:+F4F4N5hv6v38hfeYwTdx20oUvLLc+QfrE9Ax9HtgRg=
|
|
||||||
golang.org/x/crypto v0.30.0 h1:RwoQn3GkWiMkzlX562cLB7OxWvjH1L8xutO2WoJcRoY=
|
|
||||||
golang.org/x/crypto v0.30.0/go.mod h1:kDsLvtWBEx7MV9tJOj9bnXsPbxwJQ6csT/x4KIN4Ssk=
|
|
||||||
golang.org/x/crypto v0.31.0 h1:ihbySMvVjLAeSH1IbfcRTkD/iNscyz8rGzjF/E5hV6U=
|
|
||||||
golang.org/x/crypto v0.31.0/go.mod h1:kDsLvtWBEx7MV9tJOj9bnXsPbxwJQ6csT/x4KIN4Ssk=
|
golang.org/x/crypto v0.31.0/go.mod h1:kDsLvtWBEx7MV9tJOj9bnXsPbxwJQ6csT/x4KIN4Ssk=
|
||||||
golang.org/x/crypto v0.32.0 h1:euUpcYgM8WcP71gNpTqQCn6rC2t6ULUPiOzfWaXVVfc=
|
|
||||||
golang.org/x/crypto v0.32.0/go.mod h1:ZnnJkOaASj8g0AjIduWNlq2NRxL0PlBrbKVyZ6V/Ugc=
|
|
||||||
golang.org/x/crypto v0.33.0 h1:IOBPskki6Lysi0lo9qQvbxiQ+FvsCC/YWOecCHAixus=
|
golang.org/x/crypto v0.33.0 h1:IOBPskki6Lysi0lo9qQvbxiQ+FvsCC/YWOecCHAixus=
|
||||||
golang.org/x/crypto v0.33.0/go.mod h1:bVdXmD7IV/4GdElGPozy6U7lWdRXA4qyRVGJV57uQ5M=
|
golang.org/x/crypto v0.33.0/go.mod h1:bVdXmD7IV/4GdElGPozy6U7lWdRXA4qyRVGJV57uQ5M=
|
||||||
golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||||
@@ -224,15 +236,14 @@ golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwY
|
|||||||
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
|
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
|
||||||
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
|
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
|
||||||
golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
|
golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
|
||||||
golang.org/x/net v0.9.0/go.mod h1:d48xBJpPfHeWQsugry2m+kC02ZBRGRgulfHnEXEuWns=
|
|
||||||
golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg=
|
golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg=
|
||||||
golang.org/x/net v0.15.0/go.mod h1:idbUs1IY1+zTqbi8yxTbhexhEEk5ur9LInksu6HrEpk=
|
golang.org/x/net v0.15.0/go.mod h1:idbUs1IY1+zTqbi8yxTbhexhEEk5ur9LInksu6HrEpk=
|
||||||
golang.org/x/net v0.21.0/go.mod h1:bIjVDfnllIU7BJ2DNgfnXvpSvtn8VRwhlsaeUTyUS44=
|
golang.org/x/net v0.21.0/go.mod h1:bIjVDfnllIU7BJ2DNgfnXvpSvtn8VRwhlsaeUTyUS44=
|
||||||
golang.org/x/net v0.25.0/go.mod h1:JkAGAh7GEvH74S6FOH42FLoXpXbE/aqXSrIQjXgsiwM=
|
golang.org/x/net v0.25.0/go.mod h1:JkAGAh7GEvH74S6FOH42FLoXpXbE/aqXSrIQjXgsiwM=
|
||||||
golang.org/x/net v0.31.0 h1:68CPQngjLL0r2AlUKiSxtQFKvzRVbnzLwMUn5SzcLHo=
|
|
||||||
golang.org/x/net v0.31.0/go.mod h1:P4fl1q7dY2hnZFxEk4pPSkDHF+QqjitcnDjUQyMM+pM=
|
|
||||||
golang.org/x/net v0.33.0 h1:74SYHlV8BIgHIFC/LrYkOGIwL19eTYXQ5wc6TBuO36I=
|
golang.org/x/net v0.33.0 h1:74SYHlV8BIgHIFC/LrYkOGIwL19eTYXQ5wc6TBuO36I=
|
||||||
golang.org/x/net v0.33.0/go.mod h1:HXLR5J+9DxmrqMwG9qjGCxZ+zKXxBru04zlTvWlWuN4=
|
golang.org/x/net v0.33.0/go.mod h1:HXLR5J+9DxmrqMwG9qjGCxZ+zKXxBru04zlTvWlWuN4=
|
||||||
|
golang.org/x/oauth2 v0.25.0 h1:CY4y7XT9v0cRI9oupztF8AgiIu99L/ksR/Xp/6jrZ70=
|
||||||
|
golang.org/x/oauth2 v0.25.0/go.mod h1:XYTD2NtWslqkgxebSiOHnXEap4TF09sJSc7H1sXbhtI=
|
||||||
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
@@ -241,9 +252,6 @@ golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
|||||||
golang.org/x/sync v0.3.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y=
|
golang.org/x/sync v0.3.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y=
|
||||||
golang.org/x/sync v0.6.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
|
golang.org/x/sync v0.6.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
|
||||||
golang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
|
golang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
|
||||||
golang.org/x/sync v0.9.0 h1:fEo0HyrW1GIgZdpbhCRO0PkJajUS5H9IFUztCgEo2jQ=
|
|
||||||
golang.org/x/sync v0.9.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
|
|
||||||
golang.org/x/sync v0.10.0 h1:3NQrjDixjgGwUOCaF8w2+VYHv0Ve/vGYSbdkTa98gmQ=
|
|
||||||
golang.org/x/sync v0.10.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
|
golang.org/x/sync v0.10.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
|
||||||
golang.org/x/sync v0.11.0 h1:GGz8+XQP4FvTTrjZPzNKTMFtSXH80RAzG+5ghFPgK9w=
|
golang.org/x/sync v0.11.0 h1:GGz8+XQP4FvTTrjZPzNKTMFtSXH80RAzG+5ghFPgK9w=
|
||||||
golang.org/x/sync v0.11.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
|
golang.org/x/sync v0.11.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
|
||||||
@@ -258,29 +266,24 @@ golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBc
|
|||||||
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.7.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
|
||||||
golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||||
golang.org/x/sys v0.20.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
golang.org/x/sys v0.20.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||||
golang.org/x/sys v0.27.0 h1:wBqf8DvsY9Y/2P8gAfPDEYNuS30J4lPHJxXSb/nJZ+s=
|
|
||||||
golang.org/x/sys v0.27.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
|
||||||
golang.org/x/sys v0.28.0 h1:Fksou7UEQUWlKvIdsqzJmUmCX3cZuD2+P3XyyzwMhlA=
|
|
||||||
golang.org/x/sys v0.28.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
golang.org/x/sys v0.28.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||||
golang.org/x/sys v0.29.0 h1:TPYlXGxvx1MGTn2GiZDhnjPA9wZzZeGKHHmKhHYvgaU=
|
|
||||||
golang.org/x/sys v0.29.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
|
||||||
golang.org/x/sys v0.30.0 h1:QjkSwP/36a20jFYWkSue1YwXzLmsV5Gfq7Eiy72C1uc=
|
golang.org/x/sys v0.30.0 h1:QjkSwP/36a20jFYWkSue1YwXzLmsV5Gfq7Eiy72C1uc=
|
||||||
golang.org/x/sys v0.30.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
golang.org/x/sys v0.30.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||||
golang.org/x/telemetry v0.0.0-20240228155512-f48c80bd79b2/go.mod h1:TeRTkGYfJXctD9OcfyVLyj2J3IxLnKwHJR8f4D8a3YE=
|
golang.org/x/telemetry v0.0.0-20240228155512-f48c80bd79b2/go.mod h1:TeRTkGYfJXctD9OcfyVLyj2J3IxLnKwHJR8f4D8a3YE=
|
||||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||||
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
||||||
golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
|
golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
|
||||||
golang.org/x/term v0.7.0/go.mod h1:P32HKFT3hSsZrRxla30E9HqToFYAQPCMs/zFMBUFqPY=
|
|
||||||
golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo=
|
golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo=
|
||||||
golang.org/x/term v0.12.0/go.mod h1:owVbMEjm3cBLCHdkQu9b1opXd4ETQWc3BhuQGKgXgvU=
|
golang.org/x/term v0.12.0/go.mod h1:owVbMEjm3cBLCHdkQu9b1opXd4ETQWc3BhuQGKgXgvU=
|
||||||
golang.org/x/term v0.17.0/go.mod h1:lLRBjIVuehSbZlaOtGMbcMncT+aqLLLmKrsjNrUguwk=
|
golang.org/x/term v0.17.0/go.mod h1:lLRBjIVuehSbZlaOtGMbcMncT+aqLLLmKrsjNrUguwk=
|
||||||
golang.org/x/term v0.20.0/go.mod h1:8UkIAJTvZgivsXaD6/pH6U9ecQzZ45awqEOzuCvwpFY=
|
golang.org/x/term v0.20.0/go.mod h1:8UkIAJTvZgivsXaD6/pH6U9ecQzZ45awqEOzuCvwpFY=
|
||||||
golang.org/x/term v0.27.0/go.mod h1:iMsnZpn0cago0GOrHO2+Y7u7JPn5AylBrcoWkElMTSM=
|
golang.org/x/term v0.27.0/go.mod h1:iMsnZpn0cago0GOrHO2+Y7u7JPn5AylBrcoWkElMTSM=
|
||||||
|
golang.org/x/term v0.29.0 h1:L6pJp37ocefwRRtYPKSWOWzOtWSxVajvz2ldH/xi3iU=
|
||||||
|
golang.org/x/term v0.29.0/go.mod h1:6bl4lRlvVuDgSf3179VpIxBF0o10JUpXWOnI7nErv7s=
|
||||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||||
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||||
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
|
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
|
||||||
@@ -289,14 +292,11 @@ golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
|
|||||||
golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
|
golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
|
||||||
golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
|
golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
|
||||||
golang.org/x/text v0.15.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
|
golang.org/x/text v0.15.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
|
||||||
golang.org/x/text v0.20.0 h1:gK/Kv2otX8gz+wn7Rmb3vT96ZwuoxnQlY+HlJVj7Qug=
|
|
||||||
golang.org/x/text v0.20.0/go.mod h1:D4IsuqiFMhST5bX19pQ9ikHC2GsaKyk/oF+pn3ducp4=
|
|
||||||
golang.org/x/text v0.21.0 h1:zyQAAkrwaneQ066sspRyJaG9VNi/YJ1NfzcGB3hZ/qo=
|
|
||||||
golang.org/x/text v0.21.0/go.mod h1:4IBbMaMmOPCJ8SecivzSH54+73PCFmPWxNTLm+vZkEQ=
|
golang.org/x/text v0.21.0/go.mod h1:4IBbMaMmOPCJ8SecivzSH54+73PCFmPWxNTLm+vZkEQ=
|
||||||
golang.org/x/text v0.22.0 h1:bofq7m3/HAFvbF51jz3Q9wLg3jkvSPuiZu/pD1XwgtM=
|
golang.org/x/text v0.22.0 h1:bofq7m3/HAFvbF51jz3Q9wLg3jkvSPuiZu/pD1XwgtM=
|
||||||
golang.org/x/text v0.22.0/go.mod h1:YRoo4H8PVmsu+E3Ou7cqLVH8oXWIHVoX0jqUWALQhfY=
|
golang.org/x/text v0.22.0/go.mod h1:YRoo4H8PVmsu+E3Ou7cqLVH8oXWIHVoX0jqUWALQhfY=
|
||||||
golang.org/x/time v0.5.0 h1:o7cqy6amK/52YcAKIPlM3a+Fpj35zvRj2TP+e1xFSfk=
|
golang.org/x/time v0.8.0 h1:9i3RxcPv3PZnitoVGMPDKZSq1xW1gK1Xy3ArNOGZfEg=
|
||||||
golang.org/x/time v0.5.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM=
|
golang.org/x/time v0.8.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM=
|
||||||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||||
golang.org/x/tools v0.0.0-20190624222133-a101b041ded4/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
|
golang.org/x/tools v0.0.0-20190624222133-a101b041ded4/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
|
||||||
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||||
@@ -306,46 +306,29 @@ golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc
|
|||||||
golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU=
|
golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU=
|
||||||
golang.org/x/tools v0.13.0/go.mod h1:HvlwmtVNQAhOuCjW7xxvovg8wbNq7LwfXh/k7wXUl58=
|
golang.org/x/tools v0.13.0/go.mod h1:HvlwmtVNQAhOuCjW7xxvovg8wbNq7LwfXh/k7wXUl58=
|
||||||
golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d/go.mod h1:aiJjzUbINMkxbQROHiO6hDPo2LHcIPhhQsa9DLh0yGk=
|
golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d/go.mod h1:aiJjzUbINMkxbQROHiO6hDPo2LHcIPhhQsa9DLh0yGk=
|
||||||
|
golang.org/x/tools v0.26.0 h1:v/60pFQmzmT9ExmjDv2gGIfi3OqfKoEP6I5+umXlbnQ=
|
||||||
|
golang.org/x/tools v0.26.0/go.mod h1:TPVVj70c7JJ3WCazhD8OdXcZg/og+b9+tH/KxylGwH0=
|
||||||
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||||
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||||
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||||
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||||
google.golang.org/genproto v0.0.0-20231106174013-bbf56f31fb17 h1:wpZ8pe2x1Q3f2KyT5f8oP/fa9rHAKgFPr/HZdNuS+PQ=
|
google.golang.org/genproto v0.0.0-20241118233622-e639e219e697 h1:ToEetK57OidYuqD4Q5w+vfEnPvPpuTwedCNVohYJfNk=
|
||||||
google.golang.org/genproto/googleapis/api v0.0.0-20240903143218-8af14fe29dc1 h1:hjSy6tcFQZ171igDaN5QHOw2n6vx40juYbC/x67CEhc=
|
google.golang.org/genproto/googleapis/api v0.0.0-20241209162323-e6fa225c2576 h1:CkkIfIt50+lT6NHAVoRYEyAvQGFM7xEwXUUywFvEb3Q=
|
||||||
google.golang.org/genproto/googleapis/api v0.0.0-20240903143218-8af14fe29dc1/go.mod h1:qpvKtACPCQhAdu3PyQgV4l3LMXZEtft7y8QcarRsp9I=
|
google.golang.org/genproto/googleapis/api v0.0.0-20241209162323-e6fa225c2576/go.mod h1:1R3kvZ1dtP3+4p4d3G8uJ8rFk/fWlScl38vanWACI08=
|
||||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20241118233622-e639e219e697 h1:LWZqQOEjDyONlF1H6afSWpAL/znlREo2tHfLoe+8LMA=
|
|
||||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20241118233622-e639e219e697/go.mod h1:5uTbfoYQed2U9p3KIj2/Zzm02PYhndfdmML0qC3q3FU=
|
|
||||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20241223144023-3abc09e42ca8 h1:TqExAhdPaB60Ux47Cn0oLV07rGnxZzIsaRhQaqS666A=
|
google.golang.org/genproto/googleapis/rpc v0.0.0-20241223144023-3abc09e42ca8 h1:TqExAhdPaB60Ux47Cn0oLV07rGnxZzIsaRhQaqS666A=
|
||||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20241223144023-3abc09e42ca8/go.mod h1:lcTa1sDdWEIHMWlITnIczmw5w60CF9ffkb8Z+DVmmjA=
|
google.golang.org/genproto/googleapis/rpc v0.0.0-20241223144023-3abc09e42ca8/go.mod h1:lcTa1sDdWEIHMWlITnIczmw5w60CF9ffkb8Z+DVmmjA=
|
||||||
google.golang.org/grpc v1.68.0 h1:aHQeeJbo8zAkAa3pRzrVjZlbz6uSfeOXlJNQM0RAbz0=
|
|
||||||
google.golang.org/grpc v1.68.0/go.mod h1:fmSPC5AsjSBCK54MyHRx48kpOti1/jRfOlwEWywNjWA=
|
|
||||||
google.golang.org/grpc v1.68.1 h1:oI5oTa11+ng8r8XMMN7jAOmWfPZWbYpCFaMUTACxkM0=
|
|
||||||
google.golang.org/grpc v1.68.1/go.mod h1:+q1XYFJjShcqn0QZHvCyeR4CXPA+llXIeUIfIe00waw=
|
|
||||||
google.golang.org/grpc v1.69.0 h1:quSiOM1GJPmPH5XtU+BCoVXcDVJJAzNcoyfC2cCjGkI=
|
|
||||||
google.golang.org/grpc v1.69.0/go.mod h1:vyjdE6jLBI76dgpDojsFGNaHlxdjXN9ghpnd2o7JGZ4=
|
|
||||||
google.golang.org/grpc v1.69.2 h1:U3S9QEtbXC0bYNvRtcoklF3xGtLViumSYxWykJS+7AU=
|
|
||||||
google.golang.org/grpc v1.69.2/go.mod h1:vyjdE6jLBI76dgpDojsFGNaHlxdjXN9ghpnd2o7JGZ4=
|
|
||||||
google.golang.org/grpc v1.69.4 h1:MF5TftSMkd8GLw/m0KM6V8CMOCY6NZ1NQDPGFgbTt4A=
|
|
||||||
google.golang.org/grpc v1.69.4/go.mod h1:vyjdE6jLBI76dgpDojsFGNaHlxdjXN9ghpnd2o7JGZ4=
|
|
||||||
google.golang.org/grpc v1.70.0 h1:pWFv03aZoHzlRKHWicjsZytKAiYCtNS0dHbXnIdq7jQ=
|
google.golang.org/grpc v1.70.0 h1:pWFv03aZoHzlRKHWicjsZytKAiYCtNS0dHbXnIdq7jQ=
|
||||||
google.golang.org/grpc v1.70.0/go.mod h1:ofIJqVKDXx/JiXrwr2IG4/zwdH9txy3IlF40RmcJSQw=
|
google.golang.org/grpc v1.70.0/go.mod h1:ofIJqVKDXx/JiXrwr2IG4/zwdH9txy3IlF40RmcJSQw=
|
||||||
google.golang.org/protobuf v1.35.2 h1:8Ar7bF+apOIoThw1EdZl0p1oWvMqTHmpA2fRTyZO8io=
|
|
||||||
google.golang.org/protobuf v1.35.2/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE=
|
|
||||||
google.golang.org/protobuf v1.36.0 h1:mjIs9gYtt56AzC4ZaffQuh88TZurBGhIJMBZGSxNerQ=
|
|
||||||
google.golang.org/protobuf v1.36.0/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE=
|
|
||||||
google.golang.org/protobuf v1.36.1 h1:yBPeRvTftaleIgM3PZ/WBIZ7XM/eEYAaEyCwvyjq/gk=
|
|
||||||
google.golang.org/protobuf v1.36.1/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE=
|
|
||||||
google.golang.org/protobuf v1.36.2 h1:R8FeyR1/eLmkutZOM5CWghmo5itiG9z0ktFlTVLuTmU=
|
|
||||||
google.golang.org/protobuf v1.36.2/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE=
|
|
||||||
google.golang.org/protobuf v1.36.3 h1:82DV7MYdb8anAVi3qge1wSnMDrnKK7ebr+I0hHRN1BU=
|
|
||||||
google.golang.org/protobuf v1.36.3/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE=
|
|
||||||
google.golang.org/protobuf v1.36.4 h1:6A3ZDJHn/eNqc1i+IdefRzy/9PokBTPvcqMySR7NNIM=
|
|
||||||
google.golang.org/protobuf v1.36.4/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE=
|
|
||||||
google.golang.org/protobuf v1.36.5 h1:tPhr+woSbjfYvY6/GPufUoYizxw1cF/yFoxJ2fmpwlM=
|
google.golang.org/protobuf v1.36.5 h1:tPhr+woSbjfYvY6/GPufUoYizxw1cF/yFoxJ2fmpwlM=
|
||||||
google.golang.org/protobuf v1.36.5/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE=
|
google.golang.org/protobuf v1.36.5/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE=
|
||||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||||
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 h1:YR8cESwS4TdDjEe65xsg0ogRM/Nc3DYOhEAlW+xobZo=
|
|
||||||
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||||
|
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
|
||||||
|
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
|
||||||
|
gopkg.in/evanphx/json-patch.v4 v4.12.0 h1:n6jtcsulIzXPJaxegRbvFNNrZDjbij7ny3gmSPG+6V4=
|
||||||
|
gopkg.in/evanphx/json-patch.v4 v4.12.0/go.mod h1:p8EYWUEYMpynmqDbY58zCKCFZw8pRWMG4EsWvDvM72M=
|
||||||
|
gopkg.in/inf.v0 v0.9.1 h1:73M5CoZyi3ZLMOyDlQh031Cx6N9NDJ2Vvfl76EDAgDc=
|
||||||
|
gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw=
|
||||||
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||||
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
|
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
|
||||||
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||||
@@ -354,3 +337,23 @@ gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
|||||||
gotest.tools/v3 v3.0.2/go.mod h1:3SzNCllyD9/Y+b5r9JIKQ474KzkZyqLqEfYqMsX94Bk=
|
gotest.tools/v3 v3.0.2/go.mod h1:3SzNCllyD9/Y+b5r9JIKQ474KzkZyqLqEfYqMsX94Bk=
|
||||||
gotest.tools/v3 v3.0.3 h1:4AuOwCGf4lLR9u3YOe2awrHygurzhO/HeQ6laiA6Sx0=
|
gotest.tools/v3 v3.0.3 h1:4AuOwCGf4lLR9u3YOe2awrHygurzhO/HeQ6laiA6Sx0=
|
||||||
gotest.tools/v3 v3.0.3/go.mod h1:Z7Lb0S5l+klDB31fvDQX8ss/FlKDxtlFlw3Oa8Ymbl8=
|
gotest.tools/v3 v3.0.3/go.mod h1:Z7Lb0S5l+klDB31fvDQX8ss/FlKDxtlFlw3Oa8Ymbl8=
|
||||||
|
k8s.io/api v0.32.1 h1:f562zw9cy+GvXzXf0CKlVQ7yHJVYzLfL6JAS4kOAaOc=
|
||||||
|
k8s.io/api v0.32.1/go.mod h1:/Yi/BqkuueW1BgpoePYBRdDYfjPF5sgTr5+YqDZra5k=
|
||||||
|
k8s.io/apimachinery v0.32.1 h1:683ENpaCBjma4CYqsmZyhEzrGz6cjn1MY/X2jB2hkZs=
|
||||||
|
k8s.io/apimachinery v0.32.1/go.mod h1:GpHVgxoKlTxClKcteaeuF1Ul/lDVb74KpZcxcmLDElE=
|
||||||
|
k8s.io/client-go v0.32.1 h1:otM0AxdhdBIaQh7l1Q0jQpmo7WOFIk5FFa4bg6YMdUU=
|
||||||
|
k8s.io/client-go v0.32.1/go.mod h1:aTTKZY7MdxUaJ/KiUs8D+GssR9zJZi77ZqtzcGXIiDg=
|
||||||
|
k8s.io/klog/v2 v2.130.1 h1:n9Xl7H1Xvksem4KFG4PYbdQCQxqc/tTUyrgXaOhHSzk=
|
||||||
|
k8s.io/klog/v2 v2.130.1/go.mod h1:3Jpz1GvMt720eyJH1ckRHK1EDfpxISzJ7I9OYgaDtPE=
|
||||||
|
k8s.io/kube-openapi v0.0.0-20241105132330-32ad38e42d3f h1:GA7//TjRY9yWGy1poLzYYJJ4JRdzg3+O6e8I+e+8T5Y=
|
||||||
|
k8s.io/kube-openapi v0.0.0-20241105132330-32ad38e42d3f/go.mod h1:R/HEjbvWI0qdfb8viZUeVZm0X6IZnxAydC7YU42CMw4=
|
||||||
|
k8s.io/metrics v0.32.1 h1:Ou4nrEtZS2vFf7OJCf9z3+2kr0A00kQzfoSwxg0gXps=
|
||||||
|
k8s.io/metrics v0.32.1/go.mod h1:cLnai9XKYby1tNMX+xe8p9VLzTqrxYPcmqfCBoWObcM=
|
||||||
|
k8s.io/utils v0.0.0-20241104100929-3ea5e8cea738 h1:M3sRQVHv7vB20Xc2ybTt7ODCeFj6JSWYFzOFnYeS6Ro=
|
||||||
|
k8s.io/utils v0.0.0-20241104100929-3ea5e8cea738/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0=
|
||||||
|
sigs.k8s.io/json v0.0.0-20241010143419-9aa6b5e7a4b3 h1:/Rv+M11QRah1itp8VhT6HoVx1Ray9eB4DBr+K+/sCJ8=
|
||||||
|
sigs.k8s.io/json v0.0.0-20241010143419-9aa6b5e7a4b3/go.mod h1:18nIHnGi6636UCz6m8i4DhaJ65T6EruyzmoQqI2BVDo=
|
||||||
|
sigs.k8s.io/structured-merge-diff/v4 v4.4.2 h1:MdmvkGuXi/8io6ixD5wud3vOLwc1rj0aNqRlpuvjmwA=
|
||||||
|
sigs.k8s.io/structured-merge-diff/v4 v4.4.2/go.mod h1:N8f93tFZh9U6vpxwRArLiikrE5/2tiu1w1AGfACIGE4=
|
||||||
|
sigs.k8s.io/yaml v1.4.0 h1:Mk1wCc2gy/F0THH0TAp1QYyJNzRm2KCLy3o5ASXVI5E=
|
||||||
|
sigs.k8s.io/yaml v1.4.0/go.mod h1:Ejl7/uTz7PSA4eKMyQCUTnhZYNmLIl+5c2lQPGR2BPY=
|
||||||
|
|||||||
@@ -306,12 +306,12 @@ func (c *Client) FindContainer(ctx context.Context, containerID string) (contain
|
|||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Client) ListContainers(ctx context.Context, filter container.ContainerFilter) ([]container.Container, error) {
|
func (c *Client) ListContainers(ctx context.Context, labels container.ContainerLabels) ([]container.Container, error) {
|
||||||
in := &pb.ListContainersRequest{}
|
in := &pb.ListContainersRequest{}
|
||||||
|
|
||||||
if filter != nil {
|
if labels != nil {
|
||||||
in.Filter = make(map[string]*pb.RepeatedString)
|
in.Filter = make(map[string]*pb.RepeatedString)
|
||||||
for k, v := range filter {
|
for k, v := range labels {
|
||||||
in.Filter[k] = &pb.RepeatedString{Values: v}
|
in.Filter[k] = &pb.RepeatedString{Values: v}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -46,7 +46,7 @@ func (m *MockedClient) ContainerEvents(ctx context.Context, events chan<- contai
|
|||||||
return args.Error(0)
|
return args.Error(0)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m *MockedClient) ListContainers(ctx context.Context, filter container.ContainerFilter) ([]container.Container, error) {
|
func (m *MockedClient) ListContainers(ctx context.Context, filter container.ContainerLabels) ([]container.Container, error) {
|
||||||
args := m.Called(ctx, filter)
|
args := m.Called(ctx, filter)
|
||||||
return args.Get(0).([]container.Container), args.Error(1)
|
return args.Get(0).([]container.Container), args.Error(1)
|
||||||
}
|
}
|
||||||
@@ -130,7 +130,7 @@ func init() {
|
|||||||
FinishedAt: time.Date(2020, 1, 1, 0, 0, 0, 0, time.UTC),
|
FinishedAt: time.Date(2020, 1, 1, 0, 0, 0, 0, time.UTC),
|
||||||
}, nil)
|
}, nil)
|
||||||
|
|
||||||
server, _ := NewServer(client, certs, "test", container.ContainerFilter{})
|
server, _ := NewServer(client, certs, "test", container.ContainerLabels{})
|
||||||
|
|
||||||
go server.Serve(lis)
|
go server.Serve(lis)
|
||||||
}
|
}
|
||||||
@@ -173,7 +173,7 @@ func TestListContainers(t *testing.T) {
|
|||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
containers, _ := rpc.ListContainers(context.Background(), container.ContainerFilter{})
|
containers, _ := rpc.ListContainers(context.Background(), container.ContainerLabels{})
|
||||||
|
|
||||||
assert.Equal(t, containers, []container.Container{
|
assert.Equal(t, containers, []container.Container{
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
// Code generated by protoc-gen-go. DO NOT EDIT.
|
// Code generated by protoc-gen-go. DO NOT EDIT.
|
||||||
// versions:
|
// versions:
|
||||||
// protoc-gen-go v1.35.1
|
// protoc-gen-go v1.36.2
|
||||||
// protoc v5.29.3
|
// protoc v5.29.3
|
||||||
// source: rpc.proto
|
// source: rpc.proto
|
||||||
|
|
||||||
@@ -22,11 +22,10 @@ const (
|
|||||||
)
|
)
|
||||||
|
|
||||||
type ListContainersRequest struct {
|
type ListContainersRequest struct {
|
||||||
state protoimpl.MessageState
|
state protoimpl.MessageState `protogen:"open.v1"`
|
||||||
sizeCache protoimpl.SizeCache
|
Filter map[string]*RepeatedString `protobuf:"bytes,1,rep,name=filter,proto3" json:"filter,omitempty" protobuf_key:"bytes,1,opt,name=key" protobuf_val:"bytes,2,opt,name=value"`
|
||||||
unknownFields protoimpl.UnknownFields
|
unknownFields protoimpl.UnknownFields
|
||||||
|
sizeCache protoimpl.SizeCache
|
||||||
Filter map[string]*RepeatedString `protobuf:"bytes,1,rep,name=filter,proto3" json:"filter,omitempty" protobuf_key:"bytes,1,opt,name=key,proto3" protobuf_val:"bytes,2,opt,name=value,proto3"`
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (x *ListContainersRequest) Reset() {
|
func (x *ListContainersRequest) Reset() {
|
||||||
@@ -67,11 +66,10 @@ func (x *ListContainersRequest) GetFilter() map[string]*RepeatedString {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type RepeatedString struct {
|
type RepeatedString struct {
|
||||||
state protoimpl.MessageState
|
state protoimpl.MessageState `protogen:"open.v1"`
|
||||||
sizeCache protoimpl.SizeCache
|
|
||||||
unknownFields protoimpl.UnknownFields
|
|
||||||
|
|
||||||
Values []string `protobuf:"bytes,1,rep,name=values,proto3" json:"values,omitempty"`
|
Values []string `protobuf:"bytes,1,rep,name=values,proto3" json:"values,omitempty"`
|
||||||
|
unknownFields protoimpl.UnknownFields
|
||||||
|
sizeCache protoimpl.SizeCache
|
||||||
}
|
}
|
||||||
|
|
||||||
func (x *RepeatedString) Reset() {
|
func (x *RepeatedString) Reset() {
|
||||||
@@ -112,11 +110,10 @@ func (x *RepeatedString) GetValues() []string {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type ListContainersResponse struct {
|
type ListContainersResponse struct {
|
||||||
state protoimpl.MessageState
|
state protoimpl.MessageState `protogen:"open.v1"`
|
||||||
sizeCache protoimpl.SizeCache
|
|
||||||
unknownFields protoimpl.UnknownFields
|
|
||||||
|
|
||||||
Containers []*Container `protobuf:"bytes,1,rep,name=containers,proto3" json:"containers,omitempty"`
|
Containers []*Container `protobuf:"bytes,1,rep,name=containers,proto3" json:"containers,omitempty"`
|
||||||
|
unknownFields protoimpl.UnknownFields
|
||||||
|
sizeCache protoimpl.SizeCache
|
||||||
}
|
}
|
||||||
|
|
||||||
func (x *ListContainersResponse) Reset() {
|
func (x *ListContainersResponse) Reset() {
|
||||||
@@ -157,12 +154,11 @@ func (x *ListContainersResponse) GetContainers() []*Container {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type FindContainerRequest struct {
|
type FindContainerRequest struct {
|
||||||
state protoimpl.MessageState
|
state protoimpl.MessageState `protogen:"open.v1"`
|
||||||
sizeCache protoimpl.SizeCache
|
|
||||||
unknownFields protoimpl.UnknownFields
|
|
||||||
|
|
||||||
ContainerId string `protobuf:"bytes,1,opt,name=containerId,proto3" json:"containerId,omitempty"`
|
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() {
|
func (x *FindContainerRequest) Reset() {
|
||||||
@@ -210,11 +206,10 @@ func (x *FindContainerRequest) GetFilter() map[string]*RepeatedString {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type FindContainerResponse struct {
|
type FindContainerResponse struct {
|
||||||
state protoimpl.MessageState
|
state protoimpl.MessageState `protogen:"open.v1"`
|
||||||
sizeCache protoimpl.SizeCache
|
|
||||||
unknownFields protoimpl.UnknownFields
|
|
||||||
|
|
||||||
Container *Container `protobuf:"bytes,1,opt,name=container,proto3" json:"container,omitempty"`
|
Container *Container `protobuf:"bytes,1,opt,name=container,proto3" json:"container,omitempty"`
|
||||||
|
unknownFields protoimpl.UnknownFields
|
||||||
|
sizeCache protoimpl.SizeCache
|
||||||
}
|
}
|
||||||
|
|
||||||
func (x *FindContainerResponse) Reset() {
|
func (x *FindContainerResponse) Reset() {
|
||||||
@@ -255,13 +250,12 @@ func (x *FindContainerResponse) GetContainer() *Container {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type StreamLogsRequest struct {
|
type StreamLogsRequest struct {
|
||||||
state protoimpl.MessageState
|
state protoimpl.MessageState `protogen:"open.v1"`
|
||||||
sizeCache protoimpl.SizeCache
|
|
||||||
unknownFields protoimpl.UnknownFields
|
|
||||||
|
|
||||||
ContainerId string `protobuf:"bytes,1,opt,name=containerId,proto3" json:"containerId,omitempty"`
|
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"`
|
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"`
|
StreamTypes int32 `protobuf:"varint,3,opt,name=streamTypes,proto3" json:"streamTypes,omitempty"`
|
||||||
|
unknownFields protoimpl.UnknownFields
|
||||||
|
sizeCache protoimpl.SizeCache
|
||||||
}
|
}
|
||||||
|
|
||||||
func (x *StreamLogsRequest) Reset() {
|
func (x *StreamLogsRequest) Reset() {
|
||||||
@@ -316,11 +310,10 @@ func (x *StreamLogsRequest) GetStreamTypes() int32 {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type StreamLogsResponse struct {
|
type StreamLogsResponse struct {
|
||||||
state protoimpl.MessageState
|
state protoimpl.MessageState `protogen:"open.v1"`
|
||||||
sizeCache protoimpl.SizeCache
|
|
||||||
unknownFields protoimpl.UnknownFields
|
|
||||||
|
|
||||||
Event *LogEvent `protobuf:"bytes,1,opt,name=event,proto3" json:"event,omitempty"`
|
Event *LogEvent `protobuf:"bytes,1,opt,name=event,proto3" json:"event,omitempty"`
|
||||||
|
unknownFields protoimpl.UnknownFields
|
||||||
|
sizeCache protoimpl.SizeCache
|
||||||
}
|
}
|
||||||
|
|
||||||
func (x *StreamLogsResponse) Reset() {
|
func (x *StreamLogsResponse) Reset() {
|
||||||
@@ -361,14 +354,13 @@ func (x *StreamLogsResponse) GetEvent() *LogEvent {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type LogsBetweenDatesRequest struct {
|
type LogsBetweenDatesRequest struct {
|
||||||
state protoimpl.MessageState
|
state protoimpl.MessageState `protogen:"open.v1"`
|
||||||
sizeCache protoimpl.SizeCache
|
|
||||||
unknownFields protoimpl.UnknownFields
|
|
||||||
|
|
||||||
ContainerId string `protobuf:"bytes,1,opt,name=containerId,proto3" json:"containerId,omitempty"`
|
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"`
|
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"`
|
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"`
|
StreamTypes int32 `protobuf:"varint,4,opt,name=streamTypes,proto3" json:"streamTypes,omitempty"`
|
||||||
|
unknownFields protoimpl.UnknownFields
|
||||||
|
sizeCache protoimpl.SizeCache
|
||||||
}
|
}
|
||||||
|
|
||||||
func (x *LogsBetweenDatesRequest) Reset() {
|
func (x *LogsBetweenDatesRequest) Reset() {
|
||||||
@@ -430,14 +422,13 @@ func (x *LogsBetweenDatesRequest) GetStreamTypes() int32 {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type StreamRawBytesRequest struct {
|
type StreamRawBytesRequest struct {
|
||||||
state protoimpl.MessageState
|
state protoimpl.MessageState `protogen:"open.v1"`
|
||||||
sizeCache protoimpl.SizeCache
|
|
||||||
unknownFields protoimpl.UnknownFields
|
|
||||||
|
|
||||||
ContainerId string `protobuf:"bytes,1,opt,name=containerId,proto3" json:"containerId,omitempty"`
|
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"`
|
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"`
|
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"`
|
StreamTypes int32 `protobuf:"varint,4,opt,name=streamTypes,proto3" json:"streamTypes,omitempty"`
|
||||||
|
unknownFields protoimpl.UnknownFields
|
||||||
|
sizeCache protoimpl.SizeCache
|
||||||
}
|
}
|
||||||
|
|
||||||
func (x *StreamRawBytesRequest) Reset() {
|
func (x *StreamRawBytesRequest) Reset() {
|
||||||
@@ -499,11 +490,10 @@ func (x *StreamRawBytesRequest) GetStreamTypes() int32 {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type StreamRawBytesResponse struct {
|
type StreamRawBytesResponse struct {
|
||||||
state protoimpl.MessageState
|
state protoimpl.MessageState `protogen:"open.v1"`
|
||||||
sizeCache protoimpl.SizeCache
|
|
||||||
unknownFields protoimpl.UnknownFields
|
|
||||||
|
|
||||||
Data []byte `protobuf:"bytes,1,opt,name=data,proto3" json:"data,omitempty"`
|
Data []byte `protobuf:"bytes,1,opt,name=data,proto3" json:"data,omitempty"`
|
||||||
|
unknownFields protoimpl.UnknownFields
|
||||||
|
sizeCache protoimpl.SizeCache
|
||||||
}
|
}
|
||||||
|
|
||||||
func (x *StreamRawBytesResponse) Reset() {
|
func (x *StreamRawBytesResponse) Reset() {
|
||||||
@@ -544,9 +534,9 @@ func (x *StreamRawBytesResponse) GetData() []byte {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type StreamEventsRequest struct {
|
type StreamEventsRequest struct {
|
||||||
state protoimpl.MessageState
|
state protoimpl.MessageState `protogen:"open.v1"`
|
||||||
sizeCache protoimpl.SizeCache
|
|
||||||
unknownFields protoimpl.UnknownFields
|
unknownFields protoimpl.UnknownFields
|
||||||
|
sizeCache protoimpl.SizeCache
|
||||||
}
|
}
|
||||||
|
|
||||||
func (x *StreamEventsRequest) Reset() {
|
func (x *StreamEventsRequest) Reset() {
|
||||||
@@ -580,11 +570,10 @@ func (*StreamEventsRequest) Descriptor() ([]byte, []int) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type StreamEventsResponse struct {
|
type StreamEventsResponse struct {
|
||||||
state protoimpl.MessageState
|
state protoimpl.MessageState `protogen:"open.v1"`
|
||||||
sizeCache protoimpl.SizeCache
|
|
||||||
unknownFields protoimpl.UnknownFields
|
|
||||||
|
|
||||||
Event *ContainerEvent `protobuf:"bytes,1,opt,name=event,proto3" json:"event,omitempty"`
|
Event *ContainerEvent `protobuf:"bytes,1,opt,name=event,proto3" json:"event,omitempty"`
|
||||||
|
unknownFields protoimpl.UnknownFields
|
||||||
|
sizeCache protoimpl.SizeCache
|
||||||
}
|
}
|
||||||
|
|
||||||
func (x *StreamEventsResponse) Reset() {
|
func (x *StreamEventsResponse) Reset() {
|
||||||
@@ -625,9 +614,9 @@ func (x *StreamEventsResponse) GetEvent() *ContainerEvent {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type StreamStatsRequest struct {
|
type StreamStatsRequest struct {
|
||||||
state protoimpl.MessageState
|
state protoimpl.MessageState `protogen:"open.v1"`
|
||||||
sizeCache protoimpl.SizeCache
|
|
||||||
unknownFields protoimpl.UnknownFields
|
unknownFields protoimpl.UnknownFields
|
||||||
|
sizeCache protoimpl.SizeCache
|
||||||
}
|
}
|
||||||
|
|
||||||
func (x *StreamStatsRequest) Reset() {
|
func (x *StreamStatsRequest) Reset() {
|
||||||
@@ -661,11 +650,10 @@ func (*StreamStatsRequest) Descriptor() ([]byte, []int) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type StreamStatsResponse struct {
|
type StreamStatsResponse struct {
|
||||||
state protoimpl.MessageState
|
state protoimpl.MessageState `protogen:"open.v1"`
|
||||||
sizeCache protoimpl.SizeCache
|
|
||||||
unknownFields protoimpl.UnknownFields
|
|
||||||
|
|
||||||
Stat *ContainerStat `protobuf:"bytes,1,opt,name=stat,proto3" json:"stat,omitempty"`
|
Stat *ContainerStat `protobuf:"bytes,1,opt,name=stat,proto3" json:"stat,omitempty"`
|
||||||
|
unknownFields protoimpl.UnknownFields
|
||||||
|
sizeCache protoimpl.SizeCache
|
||||||
}
|
}
|
||||||
|
|
||||||
func (x *StreamStatsResponse) Reset() {
|
func (x *StreamStatsResponse) Reset() {
|
||||||
@@ -706,9 +694,9 @@ func (x *StreamStatsResponse) GetStat() *ContainerStat {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type HostInfoRequest struct {
|
type HostInfoRequest struct {
|
||||||
state protoimpl.MessageState
|
state protoimpl.MessageState `protogen:"open.v1"`
|
||||||
sizeCache protoimpl.SizeCache
|
|
||||||
unknownFields protoimpl.UnknownFields
|
unknownFields protoimpl.UnknownFields
|
||||||
|
sizeCache protoimpl.SizeCache
|
||||||
}
|
}
|
||||||
|
|
||||||
func (x *HostInfoRequest) Reset() {
|
func (x *HostInfoRequest) Reset() {
|
||||||
@@ -742,11 +730,10 @@ func (*HostInfoRequest) Descriptor() ([]byte, []int) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type HostInfoResponse struct {
|
type HostInfoResponse struct {
|
||||||
state protoimpl.MessageState
|
state protoimpl.MessageState `protogen:"open.v1"`
|
||||||
sizeCache protoimpl.SizeCache
|
|
||||||
unknownFields protoimpl.UnknownFields
|
|
||||||
|
|
||||||
Host *Host `protobuf:"bytes,1,opt,name=host,proto3" json:"host,omitempty"`
|
Host *Host `protobuf:"bytes,1,opt,name=host,proto3" json:"host,omitempty"`
|
||||||
|
unknownFields protoimpl.UnknownFields
|
||||||
|
sizeCache protoimpl.SizeCache
|
||||||
}
|
}
|
||||||
|
|
||||||
func (x *HostInfoResponse) Reset() {
|
func (x *HostInfoResponse) Reset() {
|
||||||
@@ -787,9 +774,9 @@ func (x *HostInfoResponse) GetHost() *Host {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type StreamContainerStartedRequest struct {
|
type StreamContainerStartedRequest struct {
|
||||||
state protoimpl.MessageState
|
state protoimpl.MessageState `protogen:"open.v1"`
|
||||||
sizeCache protoimpl.SizeCache
|
|
||||||
unknownFields protoimpl.UnknownFields
|
unknownFields protoimpl.UnknownFields
|
||||||
|
sizeCache protoimpl.SizeCache
|
||||||
}
|
}
|
||||||
|
|
||||||
func (x *StreamContainerStartedRequest) Reset() {
|
func (x *StreamContainerStartedRequest) Reset() {
|
||||||
@@ -823,11 +810,10 @@ func (*StreamContainerStartedRequest) Descriptor() ([]byte, []int) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type StreamContainerStartedResponse struct {
|
type StreamContainerStartedResponse struct {
|
||||||
state protoimpl.MessageState
|
state protoimpl.MessageState `protogen:"open.v1"`
|
||||||
sizeCache protoimpl.SizeCache
|
|
||||||
unknownFields protoimpl.UnknownFields
|
|
||||||
|
|
||||||
Container *Container `protobuf:"bytes,1,opt,name=container,proto3" json:"container,omitempty"`
|
Container *Container `protobuf:"bytes,1,opt,name=container,proto3" json:"container,omitempty"`
|
||||||
|
unknownFields protoimpl.UnknownFields
|
||||||
|
sizeCache protoimpl.SizeCache
|
||||||
}
|
}
|
||||||
|
|
||||||
func (x *StreamContainerStartedResponse) Reset() {
|
func (x *StreamContainerStartedResponse) Reset() {
|
||||||
@@ -868,12 +854,11 @@ func (x *StreamContainerStartedResponse) GetContainer() *Container {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type ContainerActionRequest struct {
|
type ContainerActionRequest struct {
|
||||||
state protoimpl.MessageState
|
state protoimpl.MessageState `protogen:"open.v1"`
|
||||||
sizeCache protoimpl.SizeCache
|
|
||||||
unknownFields protoimpl.UnknownFields
|
|
||||||
|
|
||||||
ContainerId string `protobuf:"bytes,1,opt,name=containerId,proto3" json:"containerId,omitempty"`
|
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"`
|
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() {
|
func (x *ContainerActionRequest) Reset() {
|
||||||
@@ -921,9 +906,9 @@ func (x *ContainerActionRequest) GetAction() ContainerAction {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type ContainerActionResponse struct {
|
type ContainerActionResponse struct {
|
||||||
state protoimpl.MessageState
|
state protoimpl.MessageState `protogen:"open.v1"`
|
||||||
sizeCache protoimpl.SizeCache
|
|
||||||
unknownFields protoimpl.UnknownFields
|
unknownFields protoimpl.UnknownFields
|
||||||
|
sizeCache protoimpl.SizeCache
|
||||||
}
|
}
|
||||||
|
|
||||||
func (x *ContainerActionResponse) Reset() {
|
func (x *ContainerActionResponse) Reset() {
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
// Code generated by protoc-gen-go. DO NOT EDIT.
|
// Code generated by protoc-gen-go. DO NOT EDIT.
|
||||||
// versions:
|
// versions:
|
||||||
// protoc-gen-go v1.35.1
|
// protoc-gen-go v1.36.2
|
||||||
// protoc v5.29.3
|
// protoc v5.29.3
|
||||||
// source: types.proto
|
// source: types.proto
|
||||||
|
|
||||||
@@ -72,10 +72,7 @@ func (ContainerAction) EnumDescriptor() ([]byte, []int) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type Container struct {
|
type Container struct {
|
||||||
state protoimpl.MessageState
|
state protoimpl.MessageState `protogen:"open.v1"`
|
||||||
sizeCache protoimpl.SizeCache
|
|
||||||
unknownFields protoimpl.UnknownFields
|
|
||||||
|
|
||||||
Id string `protobuf:"bytes,1,opt,name=id,proto3" json:"id,omitempty"`
|
Id string `protobuf:"bytes,1,opt,name=id,proto3" json:"id,omitempty"`
|
||||||
Name string `protobuf:"bytes,2,opt,name=name,proto3" json:"name,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"`
|
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"`
|
Health string `protobuf:"bytes,9,opt,name=health,proto3" json:"health,omitempty"`
|
||||||
Host string `protobuf:"bytes,10,opt,name=host,proto3" json:"host,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"`
|
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"`
|
Stats []*ContainerStat `protobuf:"bytes,13,rep,name=stats,proto3" json:"stats,omitempty"`
|
||||||
Group string `protobuf:"bytes,14,opt,name=group,proto3" json:"group,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"`
|
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"`
|
Finished *timestamppb.Timestamp `protobuf:"bytes,16,opt,name=finished,proto3" json:"finished,omitempty"`
|
||||||
|
unknownFields protoimpl.UnknownFields
|
||||||
|
sizeCache protoimpl.SizeCache
|
||||||
}
|
}
|
||||||
|
|
||||||
func (x *Container) Reset() {
|
func (x *Container) Reset() {
|
||||||
@@ -237,14 +236,13 @@ func (x *Container) GetFinished() *timestamppb.Timestamp {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type ContainerStat struct {
|
type ContainerStat struct {
|
||||||
state protoimpl.MessageState
|
state protoimpl.MessageState `protogen:"open.v1"`
|
||||||
sizeCache protoimpl.SizeCache
|
|
||||||
unknownFields protoimpl.UnknownFields
|
|
||||||
|
|
||||||
Id string `protobuf:"bytes,1,opt,name=id,proto3" json:"id,omitempty"`
|
Id string `protobuf:"bytes,1,opt,name=id,proto3" json:"id,omitempty"`
|
||||||
CpuPercent float64 `protobuf:"fixed64,2,opt,name=cpuPercent,proto3" json:"cpuPercent,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"`
|
MemoryUsage float64 `protobuf:"fixed64,3,opt,name=memoryUsage,proto3" json:"memoryUsage,omitempty"`
|
||||||
MemoryPercent float64 `protobuf:"fixed64,4,opt,name=memoryPercent,proto3" json:"memoryPercent,omitempty"`
|
MemoryPercent float64 `protobuf:"fixed64,4,opt,name=memoryPercent,proto3" json:"memoryPercent,omitempty"`
|
||||||
|
unknownFields protoimpl.UnknownFields
|
||||||
|
sizeCache protoimpl.SizeCache
|
||||||
}
|
}
|
||||||
|
|
||||||
func (x *ContainerStat) Reset() {
|
func (x *ContainerStat) Reset() {
|
||||||
@@ -306,10 +304,7 @@ func (x *ContainerStat) GetMemoryPercent() float64 {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type LogEvent struct {
|
type LogEvent struct {
|
||||||
state protoimpl.MessageState
|
state protoimpl.MessageState `protogen:"open.v1"`
|
||||||
sizeCache protoimpl.SizeCache
|
|
||||||
unknownFields protoimpl.UnknownFields
|
|
||||||
|
|
||||||
Id uint32 `protobuf:"varint,1,opt,name=id,proto3" json:"id,omitempty"`
|
Id uint32 `protobuf:"varint,1,opt,name=id,proto3" json:"id,omitempty"`
|
||||||
ContainerId string `protobuf:"bytes,2,opt,name=containerId,proto3" json:"containerId,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"`
|
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"`
|
Level string `protobuf:"bytes,5,opt,name=level,proto3" json:"level,omitempty"`
|
||||||
Stream string `protobuf:"bytes,6,opt,name=stream,proto3" json:"stream,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"`
|
Position string `protobuf:"bytes,7,opt,name=position,proto3" json:"position,omitempty"`
|
||||||
|
unknownFields protoimpl.UnknownFields
|
||||||
|
sizeCache protoimpl.SizeCache
|
||||||
}
|
}
|
||||||
|
|
||||||
func (x *LogEvent) Reset() {
|
func (x *LogEvent) Reset() {
|
||||||
@@ -399,11 +396,10 @@ func (x *LogEvent) GetPosition() string {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type SimpleMessage struct {
|
type SimpleMessage struct {
|
||||||
state protoimpl.MessageState
|
state protoimpl.MessageState `protogen:"open.v1"`
|
||||||
sizeCache protoimpl.SizeCache
|
|
||||||
unknownFields protoimpl.UnknownFields
|
|
||||||
|
|
||||||
Message string `protobuf:"bytes,1,opt,name=message,proto3" json:"message,omitempty"`
|
Message string `protobuf:"bytes,1,opt,name=message,proto3" json:"message,omitempty"`
|
||||||
|
unknownFields protoimpl.UnknownFields
|
||||||
|
sizeCache protoimpl.SizeCache
|
||||||
}
|
}
|
||||||
|
|
||||||
func (x *SimpleMessage) Reset() {
|
func (x *SimpleMessage) Reset() {
|
||||||
@@ -444,11 +440,10 @@ func (x *SimpleMessage) GetMessage() string {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type ComplexMessage struct {
|
type ComplexMessage struct {
|
||||||
state protoimpl.MessageState
|
state protoimpl.MessageState `protogen:"open.v1"`
|
||||||
sizeCache protoimpl.SizeCache
|
|
||||||
unknownFields protoimpl.UnknownFields
|
|
||||||
|
|
||||||
Data []byte `protobuf:"bytes,1,opt,name=data,proto3" json:"data,omitempty"`
|
Data []byte `protobuf:"bytes,1,opt,name=data,proto3" json:"data,omitempty"`
|
||||||
|
unknownFields protoimpl.UnknownFields
|
||||||
|
sizeCache protoimpl.SizeCache
|
||||||
}
|
}
|
||||||
|
|
||||||
func (x *ComplexMessage) Reset() {
|
func (x *ComplexMessage) Reset() {
|
||||||
@@ -489,14 +484,13 @@ func (x *ComplexMessage) GetData() []byte {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type ContainerEvent struct {
|
type ContainerEvent struct {
|
||||||
state protoimpl.MessageState
|
state protoimpl.MessageState `protogen:"open.v1"`
|
||||||
sizeCache protoimpl.SizeCache
|
|
||||||
unknownFields protoimpl.UnknownFields
|
|
||||||
|
|
||||||
ActorId string `protobuf:"bytes,1,opt,name=actorId,proto3" json:"actorId,omitempty"`
|
ActorId string `protobuf:"bytes,1,opt,name=actorId,proto3" json:"actorId,omitempty"`
|
||||||
Name string `protobuf:"bytes,2,opt,name=name,proto3" json:"name,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"`
|
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"`
|
Timestamp *timestamppb.Timestamp `protobuf:"bytes,4,opt,name=timestamp,proto3" json:"timestamp,omitempty"`
|
||||||
|
unknownFields protoimpl.UnknownFields
|
||||||
|
sizeCache protoimpl.SizeCache
|
||||||
}
|
}
|
||||||
|
|
||||||
func (x *ContainerEvent) Reset() {
|
func (x *ContainerEvent) Reset() {
|
||||||
@@ -558,15 +552,12 @@ func (x *ContainerEvent) GetTimestamp() *timestamppb.Timestamp {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type Host struct {
|
type Host struct {
|
||||||
state protoimpl.MessageState
|
state protoimpl.MessageState `protogen:"open.v1"`
|
||||||
sizeCache protoimpl.SizeCache
|
|
||||||
unknownFields protoimpl.UnknownFields
|
|
||||||
|
|
||||||
Id string `protobuf:"bytes,1,opt,name=id,proto3" json:"id,omitempty"`
|
Id string `protobuf:"bytes,1,opt,name=id,proto3" json:"id,omitempty"`
|
||||||
Name string `protobuf:"bytes,2,opt,name=name,proto3" json:"name,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"`
|
NodeAddress string `protobuf:"bytes,3,opt,name=nodeAddress,proto3" json:"nodeAddress,omitempty"`
|
||||||
Swarm bool `protobuf:"varint,4,opt,name=swarm,proto3" json:"swarm,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"`
|
OperatingSystem string `protobuf:"bytes,6,opt,name=operatingSystem,proto3" json:"operatingSystem,omitempty"`
|
||||||
OsVersion string `protobuf:"bytes,7,opt,name=osVersion,proto3" json:"osVersion,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"`
|
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"`
|
Memory uint64 `protobuf:"varint,10,opt,name=memory,proto3" json:"memory,omitempty"`
|
||||||
AgentVersion string `protobuf:"bytes,11,opt,name=agentVersion,proto3" json:"agentVersion,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"`
|
DockerVersion string `protobuf:"bytes,12,opt,name=dockerVersion,proto3" json:"dockerVersion,omitempty"`
|
||||||
|
unknownFields protoimpl.UnknownFields
|
||||||
|
sizeCache protoimpl.SizeCache
|
||||||
}
|
}
|
||||||
|
|
||||||
func (x *Host) Reset() {
|
func (x *Host) Reset() {
|
||||||
|
|||||||
@@ -13,6 +13,7 @@ import (
|
|||||||
|
|
||||||
"github.com/amir20/dozzle/internal/agent/pb"
|
"github.com/amir20/dozzle/internal/agent/pb"
|
||||||
"github.com/amir20/dozzle/internal/container"
|
"github.com/amir20/dozzle/internal/container"
|
||||||
|
"github.com/amir20/dozzle/internal/docker"
|
||||||
"github.com/rs/zerolog/log"
|
"github.com/rs/zerolog/log"
|
||||||
orderedmap "github.com/wk8/go-ordered-map/v2"
|
orderedmap "github.com/wk8/go-ordered-map/v2"
|
||||||
"google.golang.org/grpc"
|
"google.golang.org/grpc"
|
||||||
@@ -32,12 +33,13 @@ type server struct {
|
|||||||
pb.UnimplementedAgentServiceServer
|
pb.UnimplementedAgentServiceServer
|
||||||
}
|
}
|
||||||
|
|
||||||
func newServer(client container.Client, dozzleVersion string, filter container.ContainerFilter) pb.AgentServiceServer {
|
func newServer(client container.Client, dozzleVersion string, labels container.ContainerLabels) pb.AgentServiceServer {
|
||||||
|
statsCollector := docker.NewDockerStatsCollector(client, labels)
|
||||||
return &server{
|
return &server{
|
||||||
client: client,
|
client: client,
|
||||||
version: dozzleVersion,
|
version: dozzleVersion,
|
||||||
|
|
||||||
store: container.NewContainerStore(context.Background(), client, filter),
|
store: container.NewContainerStore(context.Background(), client, statsCollector, labels),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -47,7 +49,7 @@ func (s *server) StreamLogs(in *pb.StreamLogsRequest, out pb.AgentService_Stream
|
|||||||
since = in.Since.AsTime()
|
since = in.Since.AsTime()
|
||||||
}
|
}
|
||||||
|
|
||||||
c, err := s.store.FindContainer(in.ContainerId, container.ContainerFilter{})
|
c, err := s.store.FindContainer(in.ContainerId, container.ContainerLabels{})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@@ -57,7 +59,8 @@ func (s *server) StreamLogs(in *pb.StreamLogsRequest, out pb.AgentService_Stream
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
g := container.NewEventGenerator(out.Context(), reader, c)
|
dockerReader := docker.NewLogReader(reader, c.Tty)
|
||||||
|
g := container.NewEventGenerator(out.Context(), dockerReader, c)
|
||||||
|
|
||||||
for event := range g.Events {
|
for event := range g.Events {
|
||||||
out.Send(&pb.StreamLogsResponse{
|
out.Send(&pb.StreamLogsResponse{
|
||||||
@@ -84,7 +87,8 @@ func (s *server) LogsBetweenDates(in *pb.LogsBetweenDatesRequest, out pb.AgentSe
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
g := container.NewEventGenerator(out.Context(), reader, c)
|
dockerReader := docker.NewLogReader(reader, c.Tty)
|
||||||
|
g := container.NewEventGenerator(out.Context(), dockerReader, c)
|
||||||
|
|
||||||
for {
|
for {
|
||||||
select {
|
select {
|
||||||
@@ -173,14 +177,14 @@ func (s *server) StreamStats(in *pb.StreamStatsRequest, out pb.AgentService_Stre
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (s *server) FindContainer(ctx context.Context, in *pb.FindContainerRequest) (*pb.FindContainerResponse, error) {
|
func (s *server) FindContainer(ctx context.Context, in *pb.FindContainerRequest) (*pb.FindContainerResponse, error) {
|
||||||
filter := make(container.ContainerFilter)
|
labels := make(container.ContainerLabels)
|
||||||
if in.GetFilter() != nil {
|
if in.GetFilter() != nil {
|
||||||
for k, v := range in.GetFilter() {
|
for k, v := range in.GetFilter() {
|
||||||
filter[k] = append(filter[k], v.GetValues()...)
|
labels[k] = append(labels[k], v.GetValues()...)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
container, err := s.store.FindContainer(in.ContainerId, filter)
|
container, err := s.store.FindContainer(in.ContainerId, labels)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, status.Error(codes.NotFound, err.Error())
|
return nil, status.Error(codes.NotFound, err.Error())
|
||||||
}
|
}
|
||||||
@@ -205,14 +209,14 @@ func (s *server) FindContainer(ctx context.Context, in *pb.FindContainerRequest)
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (s *server) ListContainers(ctx context.Context, in *pb.ListContainersRequest) (*pb.ListContainersResponse, error) {
|
func (s *server) ListContainers(ctx context.Context, in *pb.ListContainersRequest) (*pb.ListContainersResponse, error) {
|
||||||
filter := make(container.ContainerFilter)
|
labels := make(container.ContainerLabels)
|
||||||
if in.GetFilter() != nil {
|
if in.GetFilter() != nil {
|
||||||
for k, v := range in.GetFilter() {
|
for k, v := range in.GetFilter() {
|
||||||
filter[k] = append(filter[k], v.GetValues()...)
|
labels[k] = append(labels[k], v.GetValues()...)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
containers, err := s.store.ListContainers(filter)
|
containers, err := s.store.ListContainers(labels)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@@ -322,7 +326,7 @@ func (s *server) ContainerAction(ctx context.Context, in *pb.ContainerActionRequ
|
|||||||
return &pb.ContainerActionResponse{}, nil
|
return &pb.ContainerActionResponse{}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewServer(client container.Client, certificates tls.Certificate, dozzleVersion string, filter container.ContainerFilter) (*grpc.Server, error) {
|
func NewServer(client container.Client, certificates tls.Certificate, dozzleVersion string, labels container.ContainerLabels) (*grpc.Server, error) {
|
||||||
caCertPool := x509.NewCertPool()
|
caCertPool := x509.NewCertPool()
|
||||||
c, err := x509.ParseCertificate(certificates.Certificate[0])
|
c, err := x509.ParseCertificate(certificates.Certificate[0])
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -341,7 +345,7 @@ func NewServer(client container.Client, certificates tls.Certificate, dozzleVers
|
|||||||
creds := credentials.NewTLS(tlsConfig)
|
creds := credentials.NewTLS(tlsConfig)
|
||||||
|
|
||||||
grpcServer := grpc.NewServer(grpc.Creds(creds))
|
grpcServer := grpc.NewServer(grpc.Creds(creds))
|
||||||
pb.RegisterAgentServiceServer(grpcServer, newServer(client, dozzleVersion, filter))
|
pb.RegisterAgentServiceServer(grpcServer, newServer(client, dozzleVersion, labels))
|
||||||
|
|
||||||
return grpcServer, nil
|
return grpcServer, nil
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -24,7 +24,7 @@ type User struct {
|
|||||||
Name string `json:"name" yaml:"name"`
|
Name string `json:"name" yaml:"name"`
|
||||||
Password string `json:"-" yaml:"password"`
|
Password string `json:"-" yaml:"password"`
|
||||||
Filter string `json:"-" yaml:"filter"`
|
Filter string `json:"-" yaml:"filter"`
|
||||||
ContainerFilter container.ContainerFilter `json:"-" yaml:"-"`
|
ContainerLabels container.ContainerLabels `json:"-" yaml:"-"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func (u User) AvatarURL() string {
|
func (u User) AvatarURL() string {
|
||||||
@@ -35,12 +35,12 @@ func (u User) AvatarURL() string {
|
|||||||
return fmt.Sprintf("https://gravatar.com/avatar/%s?d=https%%3A%%2F%%2Fui-avatars.com%%2Fapi%%2F/%s/128", hashEmail(u.Email), url.QueryEscape(name))
|
return fmt.Sprintf("https://gravatar.com/avatar/%s?d=https%%3A%%2F%%2Fui-avatars.com%%2Fapi%%2F/%s/128", hashEmail(u.Email), url.QueryEscape(name))
|
||||||
}
|
}
|
||||||
|
|
||||||
func newUser(username, email, name string, filter container.ContainerFilter) User {
|
func newUser(username, email, name string, labels container.ContainerLabels) User {
|
||||||
return User{
|
return User{
|
||||||
Username: username,
|
Username: username,
|
||||||
Email: email,
|
Email: email,
|
||||||
Name: name,
|
Name: name,
|
||||||
ContainerFilter: filter,
|
ContainerLabels: labels,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -197,7 +197,7 @@ func UserFromContext(ctx context.Context) *User {
|
|||||||
}
|
}
|
||||||
email := claims["email"].(string)
|
email := claims["email"].(string)
|
||||||
name := claims["name"].(string)
|
name := claims["name"].(string)
|
||||||
containerFilter := container.ContainerFilter{}
|
containerFilter := container.ContainerLabels{}
|
||||||
if filter, ok := claims["filter"].(string); ok {
|
if filter, ok := claims["filter"].(string); ok {
|
||||||
containerFilter, err = container.ParseContainerFilter(filter)
|
containerFilter, err = container.ParseContainerFilter(filter)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|||||||
@@ -29,7 +29,7 @@ func (s StdType) String() string {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type Client interface {
|
type Client interface {
|
||||||
ListContainers(context.Context, ContainerFilter) ([]Container, error)
|
ListContainers(context.Context, ContainerLabels) ([]Container, error)
|
||||||
FindContainer(context.Context, string) (Container, error)
|
FindContainer(context.Context, string) (Container, error)
|
||||||
ContainerLogs(context.Context, string, time.Time, StdType) (io.ReadCloser, error)
|
ContainerLogs(context.Context, string, time.Time, StdType) (io.ReadCloser, error)
|
||||||
ContainerEvents(context.Context, chan<- ContainerEvent) error
|
ContainerEvents(context.Context, chan<- ContainerEvent) error
|
||||||
@@ -38,5 +38,4 @@ type Client interface {
|
|||||||
Ping(context.Context) error
|
Ping(context.Context) error
|
||||||
Host() Host
|
Host() Host
|
||||||
ContainerActions(ctx context.Context, action ContainerAction, containerID string) error
|
ContainerActions(ctx context.Context, action ContainerAction, containerID string) error
|
||||||
IsSwarmMode() bool
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -13,34 +13,40 @@ import (
|
|||||||
"golang.org/x/sync/semaphore"
|
"golang.org/x/sync/semaphore"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
type StatsCollector interface {
|
||||||
|
Start(parentCtx context.Context) bool
|
||||||
|
Subscribe(ctx context.Context, stats chan<- ContainerStat)
|
||||||
|
Stop()
|
||||||
|
}
|
||||||
|
|
||||||
type ContainerStore struct {
|
type ContainerStore struct {
|
||||||
containers *xsync.MapOf[string, *Container]
|
containers *xsync.MapOf[string, *Container]
|
||||||
subscribers *xsync.MapOf[context.Context, chan<- ContainerEvent]
|
subscribers *xsync.MapOf[context.Context, chan<- ContainerEvent]
|
||||||
newContainerSubscribers *xsync.MapOf[context.Context, chan<- Container]
|
newContainerSubscribers *xsync.MapOf[context.Context, chan<- Container]
|
||||||
client Client
|
client Client
|
||||||
statsCollector *StatsCollector
|
statsCollector StatsCollector
|
||||||
wg sync.WaitGroup
|
wg sync.WaitGroup
|
||||||
connected atomic.Bool
|
connected atomic.Bool
|
||||||
events chan ContainerEvent
|
events chan ContainerEvent
|
||||||
ctx context.Context
|
ctx context.Context
|
||||||
filter ContainerFilter
|
labels ContainerLabels
|
||||||
}
|
}
|
||||||
|
|
||||||
const defaultTimeout = 10 * time.Second
|
const defaultTimeout = 10 * time.Second
|
||||||
|
|
||||||
func NewContainerStore(ctx context.Context, client Client, filter ContainerFilter) *ContainerStore {
|
func NewContainerStore(ctx context.Context, client Client, statsCollect StatsCollector, labels ContainerLabels) *ContainerStore {
|
||||||
log.Debug().Str("host", client.Host().Name).Interface("filter", filter).Msg("initializing container store")
|
log.Debug().Str("host", client.Host().Name).Interface("labels", labels).Msg("initializing container store")
|
||||||
|
|
||||||
s := &ContainerStore{
|
s := &ContainerStore{
|
||||||
containers: xsync.NewMapOf[string, *Container](),
|
containers: xsync.NewMapOf[string, *Container](),
|
||||||
client: client,
|
client: client,
|
||||||
subscribers: xsync.NewMapOf[context.Context, chan<- ContainerEvent](),
|
subscribers: xsync.NewMapOf[context.Context, chan<- ContainerEvent](),
|
||||||
newContainerSubscribers: xsync.NewMapOf[context.Context, chan<- Container](),
|
newContainerSubscribers: xsync.NewMapOf[context.Context, chan<- Container](),
|
||||||
statsCollector: NewStatsCollector(client, filter),
|
statsCollector: statsCollect,
|
||||||
wg: sync.WaitGroup{},
|
wg: sync.WaitGroup{},
|
||||||
events: make(chan ContainerEvent),
|
events: make(chan ContainerEvent),
|
||||||
ctx: ctx,
|
ctx: ctx,
|
||||||
filter: filter,
|
labels: labels,
|
||||||
}
|
}
|
||||||
|
|
||||||
s.wg.Add(1)
|
s.wg.Add(1)
|
||||||
@@ -68,7 +74,7 @@ func (s *ContainerStore) checkConnectivity() error {
|
|||||||
|
|
||||||
ctx, cancel := context.WithTimeout(context.Background(), defaultTimeout)
|
ctx, cancel := context.WithTimeout(context.Background(), defaultTimeout)
|
||||||
defer cancel()
|
defer cancel()
|
||||||
if containers, err := s.client.ListContainers(ctx, s.filter); err != nil {
|
if containers, err := s.client.ListContainers(ctx, s.labels); err != nil {
|
||||||
return err
|
return err
|
||||||
} else {
|
} else {
|
||||||
s.containers.Clear()
|
s.containers.Clear()
|
||||||
@@ -109,7 +115,7 @@ func (s *ContainerStore) checkConnectivity() error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *ContainerStore) ListContainers(filter ContainerFilter) ([]Container, error) {
|
func (s *ContainerStore) ListContainers(labels ContainerLabels) ([]Container, error) {
|
||||||
s.wg.Wait()
|
s.wg.Wait()
|
||||||
|
|
||||||
if err := s.checkConnectivity(); err != nil {
|
if err := s.checkConnectivity(); err != nil {
|
||||||
@@ -117,8 +123,8 @@ func (s *ContainerStore) ListContainers(filter ContainerFilter) ([]Container, er
|
|||||||
}
|
}
|
||||||
|
|
||||||
containers := make([]Container, 0)
|
containers := make([]Container, 0)
|
||||||
if filter.Exists() {
|
if labels.Exists() {
|
||||||
validContainers, err := s.client.ListContainers(s.ctx, filter)
|
validContainers, err := s.client.ListContainers(s.ctx, labels)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@@ -143,11 +149,10 @@ func (s *ContainerStore) ListContainers(filter ContainerFilter) ([]Container, er
|
|||||||
return containers, nil
|
return containers, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *ContainerStore) FindContainer(id string, filter ContainerFilter) (Container, error) {
|
func (s *ContainerStore) FindContainer(id string, labels ContainerLabels) (Container, error) {
|
||||||
s.wg.Wait()
|
s.wg.Wait()
|
||||||
|
if labels.Exists() {
|
||||||
if filter.Exists() {
|
validContainers, err := s.client.ListContainers(s.ctx, labels)
|
||||||
validContainers, err := s.client.ListContainers(s.ctx, filter)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return Container{}, err
|
return Container{}, err
|
||||||
}
|
}
|
||||||
@@ -245,11 +250,36 @@ func (s *ContainerStore) init() {
|
|||||||
case event := <-s.events:
|
case event := <-s.events:
|
||||||
log.Trace().Str("event", event.Name).Str("id", event.ActorID).Msg("received container event")
|
log.Trace().Str("event", event.Name).Str("id", event.ActorID).Msg("received container event")
|
||||||
switch event.Name {
|
switch event.Name {
|
||||||
|
case "create":
|
||||||
|
ctx, cancel := context.WithTimeout(context.Background(), 3*time.Second)
|
||||||
|
|
||||||
|
if container, err := s.client.FindContainer(ctx, event.ActorID); err == nil {
|
||||||
|
list, _ := s.client.ListContainers(ctx, s.labels)
|
||||||
|
|
||||||
|
// make sure the container is in the list of containers when using filter
|
||||||
|
valid := lo.ContainsBy(list, func(item Container) bool {
|
||||||
|
return item.ID == container.ID
|
||||||
|
})
|
||||||
|
|
||||||
|
if valid {
|
||||||
|
log.Debug().Str("id", container.ID).Msg("container started")
|
||||||
|
s.containers.Store(container.ID, &container)
|
||||||
|
s.newContainerSubscribers.Range(func(c context.Context, containers chan<- Container) bool {
|
||||||
|
select {
|
||||||
|
case containers <- container:
|
||||||
|
case <-c.Done():
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
cancel()
|
||||||
|
|
||||||
case "start":
|
case "start":
|
||||||
ctx, cancel := context.WithTimeout(context.Background(), defaultTimeout)
|
ctx, cancel := context.WithTimeout(context.Background(), defaultTimeout)
|
||||||
|
|
||||||
if container, err := s.client.FindContainer(ctx, event.ActorID); err == nil {
|
if container, err := s.client.FindContainer(ctx, event.ActorID); err == nil {
|
||||||
list, _ := s.client.ListContainers(ctx, s.filter)
|
list, _ := s.client.ListContainers(ctx, s.labels)
|
||||||
|
|
||||||
// make sure the container is in the list of containers when using filter
|
// make sure the container is in the list of containers when using filter
|
||||||
valid := lo.ContainsBy(list, func(item Container) bool {
|
valid := lo.ContainsBy(list, func(item Container) bool {
|
||||||
@@ -273,6 +303,43 @@ func (s *ContainerStore) init() {
|
|||||||
log.Debug().Str("id", event.ActorID).Msg("container destroyed")
|
log.Debug().Str("id", event.ActorID).Msg("container destroyed")
|
||||||
s.containers.Delete(event.ActorID)
|
s.containers.Delete(event.ActorID)
|
||||||
|
|
||||||
|
case "update":
|
||||||
|
s.containers.Compute(event.ActorID, func(c *Container, loaded bool) (*Container, bool) {
|
||||||
|
if loaded {
|
||||||
|
log.Debug().Str("id", c.ID).Msg("container updated")
|
||||||
|
started := false
|
||||||
|
if newContainer, err := s.client.FindContainer(context.Background(), c.ID); err == nil {
|
||||||
|
if newContainer.State == "running" && c.State != "running" {
|
||||||
|
started = true
|
||||||
|
}
|
||||||
|
c.Name = newContainer.Name
|
||||||
|
c.State = newContainer.State
|
||||||
|
c.Labels = newContainer.Labels
|
||||||
|
c.StartedAt = newContainer.StartedAt
|
||||||
|
c.FinishedAt = newContainer.FinishedAt
|
||||||
|
c.Created = newContainer.Created
|
||||||
|
} else {
|
||||||
|
log.Error().Err(err).Str("id", c.ID).Msg("failed to update container")
|
||||||
|
}
|
||||||
|
if started {
|
||||||
|
s.subscribers.Range(func(ctx context.Context, events chan<- ContainerEvent) bool {
|
||||||
|
select {
|
||||||
|
case events <- ContainerEvent{
|
||||||
|
Name: "start",
|
||||||
|
ActorID: c.ID,
|
||||||
|
}:
|
||||||
|
case <-ctx.Done():
|
||||||
|
s.subscribers.Delete(ctx)
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
})
|
||||||
|
}
|
||||||
|
return c, false
|
||||||
|
} else {
|
||||||
|
return c, true
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
case "die":
|
case "die":
|
||||||
s.containers.Compute(event.ActorID, func(c *Container, loaded bool) (*Container, bool) {
|
s.containers.Compute(event.ActorID, func(c *Container, loaded bool) (*Container, bool) {
|
||||||
if loaded {
|
if loaded {
|
||||||
|
|||||||
@@ -14,7 +14,7 @@ type mockedClient struct {
|
|||||||
Client
|
Client
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m *mockedClient) ListContainers(ctx context.Context, filter ContainerFilter) ([]Container, error) {
|
func (m *mockedClient) ListContainers(ctx context.Context, filter ContainerLabels) ([]Container, error) {
|
||||||
args := m.Called(ctx, filter)
|
args := m.Called(ctx, filter)
|
||||||
return args.Get(0).([]Container), args.Error(1)
|
return args.Get(0).([]Container), args.Error(1)
|
||||||
}
|
}
|
||||||
@@ -65,58 +65,15 @@ func TestContainerStore_List(t *testing.T) {
|
|||||||
|
|
||||||
ctx, cancel := context.WithCancel(context.Background())
|
ctx, cancel := context.WithCancel(context.Background())
|
||||||
t.Cleanup(cancel)
|
t.Cleanup(cancel)
|
||||||
|
collector := &fakeStatsCollector{}
|
||||||
store := NewContainerStore(ctx, client, ContainerFilter{})
|
store := NewContainerStore(ctx, client, collector, ContainerLabels{})
|
||||||
containers, _ := store.ListContainers(ContainerFilter{})
|
containers, _ := store.ListContainers(ContainerLabels{})
|
||||||
|
|
||||||
assert.Equal(t, containers[0].ID, "1234")
|
assert.Equal(t, containers[0].ID, "1234")
|
||||||
}
|
}
|
||||||
|
|
||||||
//TODO fix this test
|
type fakeStatsCollector struct{}
|
||||||
// func TestContainerStore_die(t *testing.T) {
|
|
||||||
// client := new(mockedClient)
|
|
||||||
// client.On("ListContainers", mock.Anything, mock.Anything).Return([]Container{
|
|
||||||
// {
|
|
||||||
// ID: "1234",
|
|
||||||
// Name: "test",
|
|
||||||
// State: "running",
|
|
||||||
// Stats: utils.NewRingBuffer[ContainerStat](300),
|
|
||||||
// },
|
|
||||||
// }, nil)
|
|
||||||
|
|
||||||
// client.On("ContainerEvents", mock.Anything, mock.AnythingOfType("chan<- container.ContainerEvent")).Return(nil).
|
func (f *fakeStatsCollector) Subscribe(_ context.Context, _ chan<- ContainerStat) {}
|
||||||
// Run(func(args mock.Arguments) {
|
func (f *fakeStatsCollector) Start(_ context.Context) bool { return true }
|
||||||
// ctx := args.Get(0).(context.Context)
|
func (f *fakeStatsCollector) Stop() {}
|
||||||
// events := args.Get(1).(chan<- ContainerEvent)
|
|
||||||
// events <- ContainerEvent{
|
|
||||||
// Name: "die",
|
|
||||||
// ActorID: "1234",
|
|
||||||
// Host: "localhost",
|
|
||||||
// }
|
|
||||||
// <-ctx.Done()
|
|
||||||
// })
|
|
||||||
// client.On("Host").Return(Host{
|
|
||||||
// ID: "localhost",
|
|
||||||
// })
|
|
||||||
|
|
||||||
// client.On("ContainerStats", mock.Anything, "1234", mock.AnythingOfType("chan<- container.ContainerStat")).Return(nil)
|
|
||||||
|
|
||||||
// client.On("FindContainer", mock.Anything, "1234").Return(Container{
|
|
||||||
// ID: "1234",
|
|
||||||
// Name: "test",
|
|
||||||
// Image: "test",
|
|
||||||
// Stats: utils.NewRingBuffer[ContainerStat](300),
|
|
||||||
// }, nil)
|
|
||||||
|
|
||||||
// ctx, cancel := context.WithCancel(context.Background())
|
|
||||||
// t.Cleanup(cancel)
|
|
||||||
// store := NewContainerStore(ctx, client, ContainerFilter{})
|
|
||||||
|
|
||||||
// // Wait until we get the event
|
|
||||||
// events := make(chan ContainerEvent)
|
|
||||||
// store.SubscribeEvents(ctx, events)
|
|
||||||
// <-events
|
|
||||||
|
|
||||||
// containers, _ := store.ListContainers(ContainerFilter{})
|
|
||||||
// assert.Equal(t, containers[0].State, "exited")
|
|
||||||
// }
|
|
||||||
|
|||||||
@@ -1,14 +1,10 @@
|
|||||||
package container
|
package container
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bufio"
|
|
||||||
"bytes"
|
|
||||||
"context"
|
"context"
|
||||||
"encoding/binary"
|
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"hash/fnv"
|
"hash/fnv"
|
||||||
"io"
|
|
||||||
"strings"
|
"strings"
|
||||||
"sync"
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
@@ -23,30 +19,26 @@ import (
|
|||||||
type EventGenerator struct {
|
type EventGenerator struct {
|
||||||
Events chan *LogEvent
|
Events chan *LogEvent
|
||||||
Errors chan error
|
Errors chan error
|
||||||
reader *bufio.Reader
|
reader LogReader
|
||||||
next *LogEvent
|
next *LogEvent
|
||||||
buffer chan *LogEvent
|
buffer chan *LogEvent
|
||||||
tty bool
|
|
||||||
wg sync.WaitGroup
|
wg sync.WaitGroup
|
||||||
containerID string
|
containerID string
|
||||||
ctx context.Context
|
ctx context.Context
|
||||||
}
|
}
|
||||||
|
|
||||||
var bufPool = sync.Pool{
|
|
||||||
New: func() any {
|
|
||||||
return new(bytes.Buffer)
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
var ErrBadHeader = fmt.Errorf("dozzle/docker: unable to read header")
|
var ErrBadHeader = fmt.Errorf("dozzle/docker: unable to read header")
|
||||||
|
|
||||||
func NewEventGenerator(ctx context.Context, reader io.Reader, container Container) *EventGenerator {
|
type LogReader interface {
|
||||||
|
Read() (string, StdType, error)
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewEventGenerator(ctx context.Context, reader LogReader, container Container) *EventGenerator {
|
||||||
generator := &EventGenerator{
|
generator := &EventGenerator{
|
||||||
reader: bufio.NewReader(reader),
|
reader: reader,
|
||||||
buffer: make(chan *LogEvent, 100),
|
buffer: make(chan *LogEvent, 100),
|
||||||
Errors: make(chan error, 1),
|
Errors: make(chan error, 1),
|
||||||
Events: make(chan *LogEvent),
|
Events: make(chan *LogEvent),
|
||||||
tty: container.Tty,
|
|
||||||
containerID: container.ID,
|
containerID: container.ID,
|
||||||
ctx: ctx,
|
ctx: ctx,
|
||||||
}
|
}
|
||||||
@@ -90,7 +82,7 @@ loop:
|
|||||||
|
|
||||||
func (g *EventGenerator) consumeReader() {
|
func (g *EventGenerator) consumeReader() {
|
||||||
for {
|
for {
|
||||||
message, streamType, readerError := readEvent(g.reader, g.tty)
|
message, streamType, readerError := g.reader.Read()
|
||||||
if message != "" {
|
if message != "" {
|
||||||
logEvent := createEvent(message, streamType)
|
logEvent := createEvent(message, streamType)
|
||||||
logEvent.ContainerID = g.containerID
|
logEvent.ContainerID = g.containerID
|
||||||
@@ -123,50 +115,6 @@ func (g *EventGenerator) peek() *LogEvent {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func readEvent(reader *bufio.Reader, tty bool) (string, StdType, error) {
|
|
||||||
header := []byte{0, 0, 0, 0, 0, 0, 0, 0}
|
|
||||||
buffer := bufPool.Get().(*bytes.Buffer)
|
|
||||||
buffer.Reset()
|
|
||||||
defer bufPool.Put(buffer)
|
|
||||||
var streamType StdType = STDOUT
|
|
||||||
if tty {
|
|
||||||
message, err := reader.ReadString('\n')
|
|
||||||
if err != nil {
|
|
||||||
return message, streamType, err
|
|
||||||
}
|
|
||||||
return message, streamType, nil
|
|
||||||
} else {
|
|
||||||
n, err := io.ReadFull(reader, header)
|
|
||||||
if err != nil {
|
|
||||||
return "", streamType, err
|
|
||||||
}
|
|
||||||
if n != 8 {
|
|
||||||
log.Warn().Bytes("header", header).Msg("short read")
|
|
||||||
message, _ := reader.ReadString('\n')
|
|
||||||
return message, streamType, ErrBadHeader
|
|
||||||
}
|
|
||||||
|
|
||||||
switch header[0] {
|
|
||||||
case 1:
|
|
||||||
streamType = STDOUT
|
|
||||||
case 2:
|
|
||||||
streamType = STDERR
|
|
||||||
default:
|
|
||||||
log.Warn().Bytes("header", header).Msg("unknown stream type")
|
|
||||||
}
|
|
||||||
|
|
||||||
count := binary.BigEndian.Uint32(header[4:])
|
|
||||||
if count == 0 {
|
|
||||||
return "", streamType, nil
|
|
||||||
}
|
|
||||||
_, err = io.CopyN(buffer, reader, int64(count))
|
|
||||||
if err != nil {
|
|
||||||
return "", streamType, err
|
|
||||||
}
|
|
||||||
return buffer.String(), streamType, nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func createEvent(message string, streamType StdType) *LogEvent {
|
func createEvent(message string, streamType StdType) *LogEvent {
|
||||||
h := fnv.New32a()
|
h := fnv.New32a()
|
||||||
h.Write([]byte(message))
|
h.Write([]byte(message))
|
||||||
|
|||||||
@@ -1,12 +1,9 @@
|
|||||||
package container
|
package container
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bufio"
|
|
||||||
"bytes"
|
|
||||||
"context"
|
"context"
|
||||||
"encoding/binary"
|
"io"
|
||||||
"reflect"
|
"reflect"
|
||||||
"strings"
|
|
||||||
"sync"
|
"sync"
|
||||||
"testing"
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
@@ -18,9 +15,8 @@ import (
|
|||||||
|
|
||||||
func TestEventGenerator_Events_tty(t *testing.T) {
|
func TestEventGenerator_Events_tty(t *testing.T) {
|
||||||
input := "example input"
|
input := "example input"
|
||||||
reader := bufio.NewReader(strings.NewReader(input))
|
|
||||||
|
|
||||||
g := NewEventGenerator(context.Background(), reader, Container{Tty: true})
|
g := NewEventGenerator(context.Background(), makeFakeReader(input, STDOUT), Container{Tty: true})
|
||||||
event := <-g.Events
|
event := <-g.Events
|
||||||
|
|
||||||
require.NotNil(t, event, "Expected event to not be nil, but got nil")
|
require.NotNil(t, event, "Expected event to not be nil, but got nil")
|
||||||
@@ -29,9 +25,8 @@ func TestEventGenerator_Events_tty(t *testing.T) {
|
|||||||
|
|
||||||
func TestEventGenerator_Events_non_tty(t *testing.T) {
|
func TestEventGenerator_Events_non_tty(t *testing.T) {
|
||||||
input := "example input"
|
input := "example input"
|
||||||
reader := bytes.NewReader(makeMessage(input, STDOUT))
|
|
||||||
|
|
||||||
g := NewEventGenerator(context.Background(), reader, Container{Tty: false})
|
g := NewEventGenerator(context.Background(), makeFakeReader(input, STDOUT), Container{Tty: false})
|
||||||
event := <-g.Events
|
event := <-g.Events
|
||||||
|
|
||||||
require.NotNil(t, event, "Expected event to not be nil, but got nil")
|
require.NotNil(t, event, "Expected event to not be nil, but got nil")
|
||||||
@@ -40,9 +35,8 @@ func TestEventGenerator_Events_non_tty(t *testing.T) {
|
|||||||
|
|
||||||
func TestEventGenerator_Events_non_tty_close_channel(t *testing.T) {
|
func TestEventGenerator_Events_non_tty_close_channel(t *testing.T) {
|
||||||
input := "example input"
|
input := "example input"
|
||||||
reader := bytes.NewReader(makeMessage(input, STDOUT))
|
|
||||||
|
|
||||||
g := NewEventGenerator(context.Background(), reader, Container{Tty: false})
|
g := NewEventGenerator(context.Background(), makeFakeReader(input, STDOUT), Container{Tty: false})
|
||||||
<-g.Events
|
<-g.Events
|
||||||
_, ok := <-g.Events
|
_, ok := <-g.Events
|
||||||
|
|
||||||
@@ -51,20 +45,31 @@ func TestEventGenerator_Events_non_tty_close_channel(t *testing.T) {
|
|||||||
|
|
||||||
func TestEventGenerator_Events_routines_done(t *testing.T) {
|
func TestEventGenerator_Events_routines_done(t *testing.T) {
|
||||||
input := "example input"
|
input := "example input"
|
||||||
reader := bytes.NewReader(makeMessage(input, STDOUT))
|
|
||||||
|
|
||||||
g := NewEventGenerator(context.Background(), reader, Container{Tty: false})
|
g := NewEventGenerator(context.Background(), makeFakeReader(input, STDOUT), Container{Tty: false})
|
||||||
<-g.Events
|
<-g.Events
|
||||||
assert.False(t, waitTimeout(&g.wg, 1*time.Second), "Expected routines to be done")
|
assert.False(t, waitTimeout(&g.wg, 1*time.Second), "Expected routines to be done")
|
||||||
}
|
}
|
||||||
|
|
||||||
func makeMessage(message string, stream StdType) []byte {
|
type mockLogReader struct {
|
||||||
data := make([]byte, 8)
|
messages []string
|
||||||
binary.BigEndian.PutUint32(data[4:], uint32(len(message)))
|
types []StdType
|
||||||
data[0] = byte(stream / 2)
|
i int
|
||||||
data = append(data, []byte(message)...)
|
}
|
||||||
|
|
||||||
return data
|
func (m *mockLogReader) Read() (string, StdType, error) {
|
||||||
|
if m.i >= len(m.messages) {
|
||||||
|
return "", 0, io.EOF
|
||||||
|
}
|
||||||
|
m.i++
|
||||||
|
return m.messages[m.i-1], m.types[m.i-1], nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func makeFakeReader(message string, stream StdType) LogReader {
|
||||||
|
return &mockLogReader{
|
||||||
|
messages: []string{message},
|
||||||
|
types: []StdType{stream},
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func waitTimeout(wg *sync.WaitGroup, timeout time.Duration) bool {
|
func waitTimeout(wg *sync.WaitGroup, timeout time.Duration) bool {
|
||||||
@@ -156,23 +161,3 @@ func Test_createEvent(t *testing.T) {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
type mockReadCloser struct {
|
|
||||||
bytes []byte
|
|
||||||
}
|
|
||||||
|
|
||||||
func (m mockReadCloser) Read(p []byte) (int, error) {
|
|
||||||
return copy(p, m.bytes), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func Benchmark_readEvent(b *testing.B) {
|
|
||||||
b.ReportAllocs()
|
|
||||||
|
|
||||||
data := makeMessage("2020-05-13T18:55:37.772853839Z {\"key\": \"value\"}\n", STDOUT)
|
|
||||||
|
|
||||||
reader := bufio.NewReader(mockReadCloser{bytes: data})
|
|
||||||
|
|
||||||
for i := 0; i < b.N; i++ {
|
|
||||||
readEvent(reader, true)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -44,10 +44,10 @@ type ContainerEvent struct {
|
|||||||
Time time.Time `json:"time"`
|
Time time.Time `json:"time"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type ContainerFilter map[string][]string
|
type ContainerLabels map[string][]string
|
||||||
|
|
||||||
func ParseContainerFilter(commaValues string) (ContainerFilter, error) {
|
func ParseContainerFilter(commaValues string) (ContainerLabels, error) {
|
||||||
filter := make(ContainerFilter)
|
filter := make(ContainerLabels)
|
||||||
if commaValues == "" {
|
if commaValues == "" {
|
||||||
return filter, nil
|
return filter, nil
|
||||||
}
|
}
|
||||||
@@ -65,7 +65,7 @@ func ParseContainerFilter(commaValues string) (ContainerFilter, error) {
|
|||||||
return filter, nil
|
return filter, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (f ContainerFilter) Exists() bool {
|
func (f ContainerLabels) Exists() bool {
|
||||||
return len(f) > 0
|
return len(f) > 0
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -17,7 +17,6 @@ import (
|
|||||||
docker "github.com/docker/docker/api/types/container"
|
docker "github.com/docker/docker/api/types/container"
|
||||||
"github.com/docker/docker/api/types/events"
|
"github.com/docker/docker/api/types/events"
|
||||||
"github.com/docker/docker/api/types/filters"
|
"github.com/docker/docker/api/types/filters"
|
||||||
"github.com/docker/docker/api/types/swarm"
|
|
||||||
"github.com/docker/docker/api/types/system"
|
"github.com/docker/docker/api/types/system"
|
||||||
"github.com/docker/docker/client"
|
"github.com/docker/docker/client"
|
||||||
|
|
||||||
@@ -37,13 +36,13 @@ type DockerCLI interface {
|
|||||||
Info(ctx context.Context) (system.Info, error)
|
Info(ctx context.Context) (system.Info, error)
|
||||||
}
|
}
|
||||||
|
|
||||||
type httpClient struct {
|
type DockerClient struct {
|
||||||
cli DockerCLI
|
cli DockerCLI
|
||||||
host container.Host
|
host container.Host
|
||||||
info system.Info
|
info system.Info
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewClient(cli DockerCLI, host container.Host) container.Client {
|
func NewClient(cli DockerCLI, host container.Host) *DockerClient {
|
||||||
info, err := cli.Info(context.Background())
|
info, err := cli.Info(context.Background())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Error().Err(err).Msg("Failed to get docker info")
|
log.Error().Err(err).Msg("Failed to get docker info")
|
||||||
@@ -60,7 +59,7 @@ func NewClient(cli DockerCLI, host container.Host) container.Client {
|
|||||||
host.DockerVersion = info.ServerVersion
|
host.DockerVersion = info.ServerVersion
|
||||||
host.Swarm = info.Swarm.NodeID != ""
|
host.Swarm = info.Swarm.NodeID != ""
|
||||||
|
|
||||||
return &httpClient{
|
return &DockerClient{
|
||||||
cli: cli,
|
cli: cli,
|
||||||
host: host,
|
host: host,
|
||||||
info: info,
|
info: info,
|
||||||
@@ -68,7 +67,7 @@ func NewClient(cli DockerCLI, host container.Host) container.Client {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// NewClientWithFilters creates a new instance of Client with docker filters
|
// NewClientWithFilters creates a new instance of Client with docker filters
|
||||||
func NewLocalClient(hostname string) (container.Client, error) {
|
func NewLocalClient(hostname string) (*DockerClient, error) {
|
||||||
cli, err := client.NewClientWithOpts(client.FromEnv, client.WithAPIVersionNegotiation(), client.WithUserAgent("Docker-Client/Dozzle"))
|
cli, err := client.NewClientWithOpts(client.FromEnv, client.WithAPIVersionNegotiation(), client.WithUserAgent("Docker-Client/Dozzle"))
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -93,7 +92,7 @@ func NewLocalClient(hostname string) (container.Client, error) {
|
|||||||
return NewClient(cli, host), nil
|
return NewClient(cli, host), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewRemoteClient(host container.Host) (container.Client, error) {
|
func NewRemoteClient(host container.Host) (*DockerClient, error) {
|
||||||
if host.URL.Scheme != "tcp" {
|
if host.URL.Scheme != "tcp" {
|
||||||
return nil, fmt.Errorf("invalid scheme: %s", host.URL.Scheme)
|
return nil, fmt.Errorf("invalid scheme: %s", host.URL.Scheme)
|
||||||
}
|
}
|
||||||
@@ -123,7 +122,7 @@ func NewRemoteClient(host container.Host) (container.Client, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Finds a container by id, skipping the filters
|
// Finds a container by id, skipping the filters
|
||||||
func (d *httpClient) FindContainer(ctx context.Context, id string) (container.Container, error) {
|
func (d *DockerClient) FindContainer(ctx context.Context, id string) (container.Container, error) {
|
||||||
log.Debug().Str("id", id).Msg("Finding container")
|
log.Debug().Str("id", id).Msg("Finding container")
|
||||||
if json, err := d.cli.ContainerInspect(ctx, id); err == nil {
|
if json, err := d.cli.ContainerInspect(ctx, id); err == nil {
|
||||||
return newContainerFromJSON(json, d.host.ID), nil
|
return newContainerFromJSON(json, d.host.ID), nil
|
||||||
@@ -133,7 +132,7 @@ func (d *httpClient) FindContainer(ctx context.Context, id string) (container.Co
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (d *httpClient) ContainerActions(ctx context.Context, action container.ContainerAction, containerID string) error {
|
func (d *DockerClient) ContainerActions(ctx context.Context, action container.ContainerAction, containerID string) error {
|
||||||
switch action {
|
switch action {
|
||||||
case container.Start:
|
case container.Start:
|
||||||
return d.cli.ContainerStart(ctx, containerID, docker.StartOptions{})
|
return d.cli.ContainerStart(ctx, containerID, docker.StartOptions{})
|
||||||
@@ -146,10 +145,10 @@ func (d *httpClient) ContainerActions(ctx context.Context, action container.Cont
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (d *httpClient) ListContainers(ctx context.Context, filter container.ContainerFilter) ([]container.Container, error) {
|
func (d *DockerClient) ListContainers(ctx context.Context, labels container.ContainerLabels) ([]container.Container, error) {
|
||||||
log.Debug().Interface("filter", filter).Str("host", d.host.Name).Msg("Listing containers")
|
log.Debug().Interface("labels", labels).Str("host", d.host.Name).Msg("Listing containers")
|
||||||
filterArgs := filters.NewArgs()
|
filterArgs := filters.NewArgs()
|
||||||
for key, values := range filter {
|
for key, values := range labels {
|
||||||
for _, value := range values {
|
for _, value := range values {
|
||||||
filterArgs.Add(key, value)
|
filterArgs.Add(key, value)
|
||||||
}
|
}
|
||||||
@@ -175,7 +174,7 @@ func (d *httpClient) ListContainers(ctx context.Context, filter container.Contai
|
|||||||
return containers, nil
|
return containers, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (d *httpClient) ContainerStats(ctx context.Context, id string, stats chan<- container.ContainerStat) error {
|
func (d *DockerClient) ContainerStats(ctx context.Context, id string, stats chan<- container.ContainerStat) error {
|
||||||
response, err := d.cli.ContainerStats(ctx, id, true)
|
response, err := d.cli.ContainerStats(ctx, id, true)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -225,7 +224,7 @@ func (d *httpClient) ContainerStats(ctx context.Context, id string, stats chan<-
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (d *httpClient) ContainerLogs(ctx context.Context, id string, since time.Time, stdType container.StdType) (io.ReadCloser, error) {
|
func (d *DockerClient) ContainerLogs(ctx context.Context, id string, since time.Time, stdType container.StdType) (io.ReadCloser, error) {
|
||||||
log.Debug().Str("id", id).Time("since", since).Stringer("stdType", stdType).Str("host", d.host.Name).Msg("Streaming logs for container")
|
log.Debug().Str("id", id).Time("since", since).Stringer("stdType", stdType).Str("host", d.host.Name).Msg("Streaming logs for container")
|
||||||
|
|
||||||
sinceQuery := since.Add(-50 * time.Millisecond).Format(time.RFC3339Nano)
|
sinceQuery := since.Add(-50 * time.Millisecond).Format(time.RFC3339Nano)
|
||||||
@@ -246,7 +245,7 @@ func (d *httpClient) ContainerLogs(ctx context.Context, id string, since time.Ti
|
|||||||
return reader, nil
|
return reader, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (d *httpClient) ContainerEvents(ctx context.Context, messages chan<- container.ContainerEvent) error {
|
func (d *DockerClient) ContainerEvents(ctx context.Context, messages chan<- container.ContainerEvent) error {
|
||||||
dockerMessages, err := d.cli.Events(ctx, events.ListOptions{})
|
dockerMessages, err := d.cli.Events(ctx, events.ListOptions{})
|
||||||
|
|
||||||
for {
|
for {
|
||||||
@@ -270,7 +269,7 @@ func (d *httpClient) ContainerEvents(ctx context.Context, messages chan<- contai
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (d *httpClient) ContainerLogsBetweenDates(ctx context.Context, id string, from time.Time, to time.Time, stdType container.StdType) (io.ReadCloser, error) {
|
func (d *DockerClient) ContainerLogsBetweenDates(ctx context.Context, id string, from time.Time, to time.Time, stdType container.StdType) (io.ReadCloser, error) {
|
||||||
log.Debug().Str("id", id).Time("from", from).Time("to", to).Stringer("stdType", stdType).Str("host", d.host.Name).Msg("Fetching logs between dates for container")
|
log.Debug().Str("id", id).Time("from", from).Time("to", to).Stringer("stdType", stdType).Str("host", d.host.Name).Msg("Fetching logs between dates for container")
|
||||||
options := docker.LogsOptions{
|
options := docker.LogsOptions{
|
||||||
ShowStdout: stdType&container.STDOUT != 0,
|
ShowStdout: stdType&container.STDOUT != 0,
|
||||||
@@ -288,20 +287,16 @@ func (d *httpClient) ContainerLogsBetweenDates(ctx context.Context, id string, f
|
|||||||
return reader, nil
|
return reader, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (d *httpClient) Ping(ctx context.Context) error {
|
func (d *DockerClient) Ping(ctx context.Context) error {
|
||||||
_, err := d.cli.Ping(ctx)
|
_, err := d.cli.Ping(ctx)
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
func (d *httpClient) Host() container.Host {
|
func (d *DockerClient) Host() container.Host {
|
||||||
log.Debug().Str("host", d.host.Name).Msg("Fetching host")
|
log.Debug().Str("host", d.host.Name).Msg("Fetching host")
|
||||||
return d.host
|
return d.host
|
||||||
}
|
}
|
||||||
|
|
||||||
func (d *httpClient) IsSwarmMode() bool {
|
|
||||||
return d.info.Swarm.LocalNodeState != swarm.LocalNodeStateInactive
|
|
||||||
}
|
|
||||||
|
|
||||||
func newContainer(c types.Container, host string) container.Container {
|
func newContainer(c types.Container, host string) container.Container {
|
||||||
name := "no name"
|
name := "no name"
|
||||||
if c.Labels["dev.dozzle.name"] != "" {
|
if c.Labels["dev.dozzle.name"] != "" {
|
||||||
|
|||||||
@@ -90,9 +90,9 @@ func (m *mockedProxy) ContainerRestart(ctx context.Context, containerID string,
|
|||||||
func Test_dockerClient_ListContainers_null(t *testing.T) {
|
func Test_dockerClient_ListContainers_null(t *testing.T) {
|
||||||
proxy := new(mockedProxy)
|
proxy := new(mockedProxy)
|
||||||
proxy.On("ContainerList", mock.Anything, mock.Anything).Return(nil, nil)
|
proxy.On("ContainerList", mock.Anything, mock.Anything).Return(nil, nil)
|
||||||
client := &httpClient{proxy, container.Host{ID: "localhost"}, system.Info{}}
|
client := &DockerClient{proxy, container.Host{ID: "localhost"}, system.Info{}}
|
||||||
|
|
||||||
list, err := client.ListContainers(context.Background(), container.ContainerFilter{})
|
list, err := client.ListContainers(context.Background(), container.ContainerLabels{})
|
||||||
assert.Empty(t, list, "list should be empty")
|
assert.Empty(t, list, "list should be empty")
|
||||||
require.NoError(t, err, "error should not return an error.")
|
require.NoError(t, err, "error should not return an error.")
|
||||||
|
|
||||||
@@ -102,9 +102,9 @@ func Test_dockerClient_ListContainers_null(t *testing.T) {
|
|||||||
func Test_dockerClient_ListContainers_error(t *testing.T) {
|
func Test_dockerClient_ListContainers_error(t *testing.T) {
|
||||||
proxy := new(mockedProxy)
|
proxy := new(mockedProxy)
|
||||||
proxy.On("ContainerList", mock.Anything, mock.Anything).Return(nil, errors.New("test"))
|
proxy.On("ContainerList", mock.Anything, mock.Anything).Return(nil, errors.New("test"))
|
||||||
client := &httpClient{proxy, container.Host{ID: "localhost"}, system.Info{}}
|
client := &DockerClient{proxy, container.Host{ID: "localhost"}, system.Info{}}
|
||||||
|
|
||||||
list, err := client.ListContainers(context.Background(), container.ContainerFilter{})
|
list, err := client.ListContainers(context.Background(), container.ContainerLabels{})
|
||||||
assert.Nil(t, list, "list should be nil")
|
assert.Nil(t, list, "list should be nil")
|
||||||
require.Error(t, err, "test.")
|
require.Error(t, err, "test.")
|
||||||
|
|
||||||
@@ -125,9 +125,9 @@ func Test_dockerClient_ListContainers_happy(t *testing.T) {
|
|||||||
|
|
||||||
proxy := new(mockedProxy)
|
proxy := new(mockedProxy)
|
||||||
proxy.On("ContainerList", mock.Anything, mock.Anything).Return(containers, nil)
|
proxy.On("ContainerList", mock.Anything, mock.Anything).Return(containers, nil)
|
||||||
client := &httpClient{proxy, container.Host{ID: "localhost"}, system.Info{}}
|
client := &DockerClient{proxy, container.Host{ID: "localhost"}, system.Info{}}
|
||||||
|
|
||||||
list, err := client.ListContainers(context.Background(), container.ContainerFilter{})
|
list, err := client.ListContainers(context.Background(), container.ContainerLabels{})
|
||||||
require.NoError(t, err, "error should not return an error.")
|
require.NoError(t, err, "error should not return an error.")
|
||||||
|
|
||||||
Ids := []string{"1234567890_a", "abcdefghijkl"}
|
Ids := []string{"1234567890_a", "abcdefghijkl"}
|
||||||
@@ -159,7 +159,7 @@ func Test_dockerClient_ContainerLogs_happy(t *testing.T) {
|
|||||||
Since: "2020-12-31T23:59:59.95Z"}
|
Since: "2020-12-31T23:59:59.95Z"}
|
||||||
proxy.On("ContainerLogs", mock.Anything, id, options).Return(reader, nil)
|
proxy.On("ContainerLogs", mock.Anything, id, options).Return(reader, nil)
|
||||||
|
|
||||||
client := &httpClient{proxy, container.Host{ID: "localhost"}, system.Info{}}
|
client := &DockerClient{proxy, container.Host{ID: "localhost"}, system.Info{}}
|
||||||
logReader, _ := client.ContainerLogs(context.Background(), id, since, container.STDALL)
|
logReader, _ := client.ContainerLogs(context.Background(), id, since, container.STDALL)
|
||||||
|
|
||||||
actual, _ := io.ReadAll(logReader)
|
actual, _ := io.ReadAll(logReader)
|
||||||
@@ -173,7 +173,7 @@ func Test_dockerClient_ContainerLogs_error(t *testing.T) {
|
|||||||
|
|
||||||
proxy.On("ContainerLogs", mock.Anything, id, mock.Anything).Return(nil, errors.New("test"))
|
proxy.On("ContainerLogs", mock.Anything, id, mock.Anything).Return(nil, errors.New("test"))
|
||||||
|
|
||||||
client := &httpClient{proxy, container.Host{ID: "localhost"}, system.Info{}}
|
client := &DockerClient{proxy, container.Host{ID: "localhost"}, system.Info{}}
|
||||||
|
|
||||||
reader, err := client.ContainerLogs(context.Background(), id, time.Time{}, container.STDALL)
|
reader, err := client.ContainerLogs(context.Background(), id, time.Time{}, container.STDALL)
|
||||||
|
|
||||||
@@ -189,7 +189,7 @@ func Test_dockerClient_FindContainer_happy(t *testing.T) {
|
|||||||
json := types.ContainerJSON{ContainerJSONBase: &types.ContainerJSONBase{ID: "abcdefghijklmnopqrst", State: state}, Config: &docker.Config{Tty: false}}
|
json := types.ContainerJSON{ContainerJSONBase: &types.ContainerJSONBase{ID: "abcdefghijklmnopqrst", State: state}, Config: &docker.Config{Tty: false}}
|
||||||
proxy.On("ContainerInspect", mock.Anything, "abcdefghijkl").Return(json, nil)
|
proxy.On("ContainerInspect", mock.Anything, "abcdefghijkl").Return(json, nil)
|
||||||
|
|
||||||
client := &httpClient{proxy, container.Host{ID: "localhost"}, system.Info{}}
|
client := &DockerClient{proxy, container.Host{ID: "localhost"}, system.Info{}}
|
||||||
|
|
||||||
container, err := client.FindContainer(context.Background(), "abcdefghijkl")
|
container, err := client.FindContainer(context.Background(), "abcdefghijkl")
|
||||||
require.NoError(t, err, "error should not be thrown")
|
require.NoError(t, err, "error should not be thrown")
|
||||||
@@ -202,7 +202,7 @@ func Test_dockerClient_FindContainer_happy(t *testing.T) {
|
|||||||
func Test_dockerClient_FindContainer_error(t *testing.T) {
|
func Test_dockerClient_FindContainer_error(t *testing.T) {
|
||||||
proxy := new(mockedProxy)
|
proxy := new(mockedProxy)
|
||||||
proxy.On("ContainerInspect", mock.Anything, "not_valid").Return(types.ContainerJSON{}, errors.New("not found"))
|
proxy.On("ContainerInspect", mock.Anything, "not_valid").Return(types.ContainerJSON{}, errors.New("not found"))
|
||||||
client := &httpClient{proxy, container.Host{ID: "localhost"}, system.Info{}}
|
client := &DockerClient{proxy, container.Host{ID: "localhost"}, system.Info{}}
|
||||||
|
|
||||||
_, err := client.FindContainer(context.Background(), "not_valid")
|
_, err := client.FindContainer(context.Background(), "not_valid")
|
||||||
require.Error(t, err, "error should be thrown")
|
require.Error(t, err, "error should be thrown")
|
||||||
@@ -212,7 +212,7 @@ func Test_dockerClient_FindContainer_error(t *testing.T) {
|
|||||||
|
|
||||||
func Test_dockerClient_ContainerActions_happy(t *testing.T) {
|
func Test_dockerClient_ContainerActions_happy(t *testing.T) {
|
||||||
proxy := new(mockedProxy)
|
proxy := new(mockedProxy)
|
||||||
client := &httpClient{proxy, container.Host{ID: "localhost"}, system.Info{}}
|
client := &DockerClient{proxy, container.Host{ID: "localhost"}, system.Info{}}
|
||||||
|
|
||||||
state := &types.ContainerState{Status: "running", StartedAt: time.Now().Format(time.RFC3339Nano)}
|
state := &types.ContainerState{Status: "running", StartedAt: time.Now().Format(time.RFC3339Nano)}
|
||||||
json := types.ContainerJSON{ContainerJSONBase: &types.ContainerJSONBase{ID: "abcdefghijkl", State: state}, Config: &docker.Config{Tty: false}}
|
json := types.ContainerJSON{ContainerJSONBase: &types.ContainerJSONBase{ID: "abcdefghijkl", State: state}, Config: &docker.Config{Tty: false}}
|
||||||
@@ -240,7 +240,7 @@ func Test_dockerClient_ContainerActions_happy(t *testing.T) {
|
|||||||
func Test_dockerClient_ContainerActions_error(t *testing.T) {
|
func Test_dockerClient_ContainerActions_error(t *testing.T) {
|
||||||
|
|
||||||
proxy := new(mockedProxy)
|
proxy := new(mockedProxy)
|
||||||
client := &httpClient{proxy, container.Host{ID: "localhost"}, system.Info{}}
|
client := &DockerClient{proxy, container.Host{ID: "localhost"}, system.Info{}}
|
||||||
proxy.On("ContainerInspect", mock.Anything, "random-id").Return(types.ContainerJSON{}, errors.New("not found"))
|
proxy.On("ContainerInspect", mock.Anything, "random-id").Return(types.ContainerJSON{}, errors.New("not found"))
|
||||||
proxy.On("ContainerStart", mock.Anything, mock.Anything, mock.Anything).Return(errors.New("test"))
|
proxy.On("ContainerStart", mock.Anything, mock.Anything, mock.Anything).Return(errors.New("test"))
|
||||||
proxy.On("ContainerStop", mock.Anything, mock.Anything, mock.Anything).Return(errors.New("test"))
|
proxy.On("ContainerStop", mock.Anything, mock.Anything, mock.Anything).Return(errors.New("test"))
|
||||||
|
|||||||
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 (
|
import (
|
||||||
"context"
|
"context"
|
||||||
@@ -8,35 +8,36 @@ import (
|
|||||||
"sync/atomic"
|
"sync/atomic"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"github.com/amir20/dozzle/internal/container"
|
||||||
"github.com/puzpuzpuz/xsync/v3"
|
"github.com/puzpuzpuz/xsync/v3"
|
||||||
"github.com/rs/zerolog/log"
|
"github.com/rs/zerolog/log"
|
||||||
)
|
)
|
||||||
|
|
||||||
type StatsCollector struct {
|
type DockerStatsCollector struct {
|
||||||
stream chan ContainerStat
|
stream chan container.ContainerStat
|
||||||
subscribers *xsync.MapOf[context.Context, chan<- ContainerStat]
|
subscribers *xsync.MapOf[context.Context, chan<- container.ContainerStat]
|
||||||
client Client
|
client container.Client
|
||||||
cancelers *xsync.MapOf[string, context.CancelFunc]
|
cancelers *xsync.MapOf[string, context.CancelFunc]
|
||||||
stopper context.CancelFunc
|
stopper context.CancelFunc
|
||||||
timer *time.Timer
|
timer *time.Timer
|
||||||
mu sync.Mutex
|
mu sync.Mutex
|
||||||
totalStarted atomic.Int32
|
totalStarted atomic.Int32
|
||||||
filter ContainerFilter
|
labels container.ContainerLabels
|
||||||
}
|
}
|
||||||
|
|
||||||
var timeToStop = 6 * time.Hour
|
var timeToStop = 6 * time.Hour
|
||||||
|
|
||||||
func NewStatsCollector(client Client, filter ContainerFilter) *StatsCollector {
|
func NewDockerStatsCollector(client container.Client, labels container.ContainerLabels) *DockerStatsCollector {
|
||||||
return &StatsCollector{
|
return &DockerStatsCollector{
|
||||||
stream: make(chan ContainerStat),
|
stream: make(chan container.ContainerStat),
|
||||||
subscribers: xsync.NewMapOf[context.Context, chan<- ContainerStat](),
|
subscribers: xsync.NewMapOf[context.Context, chan<- container.ContainerStat](),
|
||||||
client: client,
|
client: client,
|
||||||
cancelers: xsync.NewMapOf[string, context.CancelFunc](),
|
cancelers: xsync.NewMapOf[string, context.CancelFunc](),
|
||||||
filter: filter,
|
labels: labels,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *StatsCollector) Subscribe(ctx context.Context, stats chan<- ContainerStat) {
|
func (c *DockerStatsCollector) Subscribe(ctx context.Context, stats chan<- container.ContainerStat) {
|
||||||
c.subscribers.Store(ctx, stats)
|
c.subscribers.Store(ctx, stats)
|
||||||
go func() {
|
go func() {
|
||||||
<-ctx.Done()
|
<-ctx.Done()
|
||||||
@@ -44,7 +45,7 @@ func (c *StatsCollector) Subscribe(ctx context.Context, stats chan<- ContainerSt
|
|||||||
}()
|
}()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *StatsCollector) forceStop() {
|
func (c *DockerStatsCollector) forceStop() {
|
||||||
c.mu.Lock()
|
c.mu.Lock()
|
||||||
defer c.mu.Unlock()
|
defer c.mu.Unlock()
|
||||||
if c.stopper != nil {
|
if c.stopper != nil {
|
||||||
@@ -54,7 +55,7 @@ func (c *StatsCollector) forceStop() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *StatsCollector) Stop() {
|
func (c *DockerStatsCollector) Stop() {
|
||||||
c.mu.Lock()
|
c.mu.Lock()
|
||||||
defer c.mu.Unlock()
|
defer c.mu.Unlock()
|
||||||
if c.totalStarted.Add(-1) == 0 {
|
if c.totalStarted.Add(-1) == 0 {
|
||||||
@@ -64,7 +65,7 @@ func (c *StatsCollector) Stop() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *StatsCollector) reset() {
|
func (c *DockerStatsCollector) reset() {
|
||||||
c.mu.Lock()
|
c.mu.Lock()
|
||||||
defer c.mu.Unlock()
|
defer c.mu.Unlock()
|
||||||
if c.timer != nil {
|
if c.timer != nil {
|
||||||
@@ -73,7 +74,7 @@ func (c *StatsCollector) reset() {
|
|||||||
c.timer = nil
|
c.timer = nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func streamStats(parent context.Context, sc *StatsCollector, id string) {
|
func streamStats(parent context.Context, sc *DockerStatsCollector, id string) {
|
||||||
ctx, cancel := context.WithCancel(parent)
|
ctx, cancel := context.WithCancel(parent)
|
||||||
sc.cancelers.Store(id, cancel)
|
sc.cancelers.Store(id, cancel)
|
||||||
log.Debug().Str("container", id).Str("host", sc.client.Host().Name).Msg("starting to stream stats")
|
log.Debug().Str("container", id).Str("host", sc.client.Host().Name).Msg("starting to stream stats")
|
||||||
@@ -86,7 +87,7 @@ func streamStats(parent context.Context, sc *StatsCollector, id string) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Start starts the stats collector and blocks until it's stopped. It returns true if the collector was stopped, false if it was already running
|
// Start starts the stats collector and blocks until it's stopped. It returns true if the collector was stopped, false if it was already running
|
||||||
func (sc *StatsCollector) Start(parentCtx context.Context) bool {
|
func (sc *DockerStatsCollector) Start(parentCtx context.Context) bool {
|
||||||
sc.reset()
|
sc.reset()
|
||||||
sc.totalStarted.Add(1)
|
sc.totalStarted.Add(1)
|
||||||
|
|
||||||
@@ -99,8 +100,8 @@ func (sc *StatsCollector) Start(parentCtx context.Context) bool {
|
|||||||
ctx, sc.stopper = context.WithCancel(parentCtx)
|
ctx, sc.stopper = context.WithCancel(parentCtx)
|
||||||
sc.mu.Unlock()
|
sc.mu.Unlock()
|
||||||
|
|
||||||
timeoutCtx, cancel := context.WithTimeout(parentCtx, defaultTimeout)
|
timeoutCtx, cancel := context.WithTimeout(parentCtx, 3*time.Second) // 3 seconds to list containers is hard limit
|
||||||
if containers, err := sc.client.ListContainers(timeoutCtx, sc.filter); err == nil {
|
if containers, err := sc.client.ListContainers(timeoutCtx, sc.labels); err == nil {
|
||||||
for _, c := range containers {
|
for _, c := range containers {
|
||||||
if c.State == "running" {
|
if c.State == "running" {
|
||||||
go streamStats(ctx, sc, c.ID)
|
go streamStats(ctx, sc, c.ID)
|
||||||
@@ -111,7 +112,7 @@ func (sc *StatsCollector) Start(parentCtx context.Context) bool {
|
|||||||
}
|
}
|
||||||
cancel()
|
cancel()
|
||||||
|
|
||||||
events := make(chan ContainerEvent)
|
events := make(chan container.ContainerEvent)
|
||||||
|
|
||||||
go func() {
|
go func() {
|
||||||
log.Debug().Str("host", sc.client.Host().Name).Msg("starting to listen to docker events")
|
log.Debug().Str("host", sc.client.Host().Name).Msg("starting to listen to docker events")
|
||||||
@@ -142,7 +143,7 @@ func (sc *StatsCollector) Start(parentCtx context.Context) bool {
|
|||||||
log.Info().Str("host", sc.client.Host().Name).Msg("stopped container stats collector")
|
log.Info().Str("host", sc.client.Host().Name).Msg("stopped container stats collector")
|
||||||
return true
|
return true
|
||||||
case stat := <-sc.stream:
|
case stat := <-sc.stream:
|
||||||
sc.subscribers.Range(func(c context.Context, stats chan<- ContainerStat) bool {
|
sc.subscribers.Range(func(c context.Context, stats chan<- container.ContainerStat) bool {
|
||||||
select {
|
select {
|
||||||
case stats <- stat:
|
case stats <- stat:
|
||||||
case <-c.Done():
|
case <-c.Done():
|
||||||
@@ -1,16 +1,47 @@
|
|||||||
package container
|
package docker
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
|
"github.com/amir20/dozzle/internal/container"
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
"github.com/stretchr/testify/mock"
|
"github.com/stretchr/testify/mock"
|
||||||
)
|
)
|
||||||
|
|
||||||
func startedCollector(ctx context.Context) *StatsCollector {
|
type mockedClient struct {
|
||||||
|
mock.Mock
|
||||||
|
container.Client
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *mockedClient) ListContainers(ctx context.Context, filter container.ContainerLabels) ([]container.Container, error) {
|
||||||
|
args := m.Called(ctx, filter)
|
||||||
|
return args.Get(0).([]container.Container), args.Error(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *mockedClient) FindContainer(ctx context.Context, id string) (container.Container, error) {
|
||||||
|
args := m.Called(ctx, id)
|
||||||
|
return args.Get(0).(container.Container), args.Error(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *mockedClient) ContainerEvents(ctx context.Context, events chan<- container.ContainerEvent) error {
|
||||||
|
args := m.Called(ctx, events)
|
||||||
|
return args.Error(0)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *mockedClient) ContainerStats(ctx context.Context, id string, stats chan<- container.ContainerStat) error {
|
||||||
|
args := m.Called(ctx, id, stats)
|
||||||
|
return args.Error(0)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *mockedClient) Host() container.Host {
|
||||||
|
args := m.Called()
|
||||||
|
return args.Get(0).(container.Host)
|
||||||
|
}
|
||||||
|
|
||||||
|
func startedCollector(ctx context.Context) *DockerStatsCollector {
|
||||||
client := new(mockedClient)
|
client := new(mockedClient)
|
||||||
client.On("ListContainers", mock.Anything, mock.Anything).Return([]Container{
|
client.On("ListContainers", mock.Anything, mock.Anything).Return([]container.Container{
|
||||||
{
|
{
|
||||||
ID: "1234",
|
ID: "1234",
|
||||||
Name: "test",
|
Name: "test",
|
||||||
@@ -26,17 +57,17 @@ func startedCollector(ctx context.Context) *StatsCollector {
|
|||||||
client.On("ContainerStats", mock.Anything, mock.Anything, mock.AnythingOfType("chan<- container.ContainerStat")).
|
client.On("ContainerStats", mock.Anything, mock.Anything, mock.AnythingOfType("chan<- container.ContainerStat")).
|
||||||
Return(nil).
|
Return(nil).
|
||||||
Run(func(args mock.Arguments) {
|
Run(func(args mock.Arguments) {
|
||||||
stats := args.Get(2).(chan<- ContainerStat)
|
stats := args.Get(2).(chan<- container.ContainerStat)
|
||||||
stats <- ContainerStat{
|
stats <- container.ContainerStat{
|
||||||
ID: "1234",
|
ID: "1234",
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
client.On("Host").Return(Host{
|
client.On("Host").Return(container.Host{
|
||||||
ID: "localhost",
|
ID: "localhost",
|
||||||
})
|
})
|
||||||
|
|
||||||
collector := NewStatsCollector(client, ContainerFilter{})
|
collector := NewDockerStatsCollector(client, container.ContainerLabels{})
|
||||||
stats := make(chan ContainerStat)
|
stats := make(chan container.ContainerStat)
|
||||||
|
|
||||||
collector.Subscribe(ctx, stats)
|
collector.Subscribe(ctx, stats)
|
||||||
|
|
||||||
@@ -14,7 +14,7 @@ func RPCRequest(ctx context.Context, addr string, certs tls.Certificate) error {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatal().Err(err).Msg("Failed to create agent client")
|
log.Fatal().Err(err).Msg("Failed to create agent client")
|
||||||
}
|
}
|
||||||
containers, err := client.ListContainers(ctx, container.ContainerFilter{})
|
containers, err := client.ListContainers(ctx, container.ContainerLabels{})
|
||||||
log.Trace().Int("containers", len(containers)).Msg("Healtcheck RPC request completed")
|
log.Trace().Int("containers", len(containers)).Msg("Healtcheck RPC request completed")
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|||||||
223
internal/k8s/client.go
Normal file
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/container"
|
||||||
"github.com/amir20/dozzle/internal/docker"
|
"github.com/amir20/dozzle/internal/docker"
|
||||||
|
container_support "github.com/amir20/dozzle/internal/support/container"
|
||||||
docker_support "github.com/amir20/dozzle/internal/support/docker"
|
docker_support "github.com/amir20/dozzle/internal/support/docker"
|
||||||
"github.com/rs/zerolog/log"
|
"github.com/rs/zerolog/log"
|
||||||
)
|
)
|
||||||
|
|
||||||
func CreateMultiHostService(embeddedCerts embed.FS, args Args) (container.Client, *docker_support.MultiHostService) {
|
func CreateMultiHostService(embeddedCerts embed.FS, args Args) (container.Client, *docker_support.MultiHostService) {
|
||||||
var clients []docker_support.ClientService
|
var clients []container_support.ClientService
|
||||||
if len(args.RemoteHost) > 0 {
|
if len(args.RemoteHost) > 0 {
|
||||||
log.Info().Msg(`Consider using Dozzle's remote agent to manage remote hosts. See https://dozzle.dev/guide/agent for more information`)
|
log.Info().Msg(`Consider using Dozzle's remote agent to manage remote hosts. See https://dozzle.dev/guide/agent for more information`)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
package docker_support
|
package container_support
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
@@ -22,7 +22,7 @@ func NewAgentService(client *agent.Client) ClientService {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (a *agentService) FindContainer(ctx context.Context, id string, filter container.ContainerFilter) (container.Container, error) {
|
func (a *agentService) FindContainer(ctx context.Context, id string, labels container.ContainerLabels) (container.Container, error) {
|
||||||
return a.client.FindContainer(ctx, id)
|
return a.client.FindContainer(ctx, id)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -38,9 +38,9 @@ func (a *agentService) StreamLogs(ctx context.Context, container container.Conta
|
|||||||
return a.client.StreamContainerLogs(ctx, container.ID, from, stdTypes, events)
|
return a.client.StreamContainerLogs(ctx, container.ID, from, stdTypes, events)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (a *agentService) ListContainers(ctx context.Context, filter container.ContainerFilter) ([]container.Container, error) {
|
func (a *agentService) ListContainers(ctx context.Context, labels container.ContainerLabels) ([]container.Container, error) {
|
||||||
log.Debug().Interface("filter", filter).Msg("Listing containers from agent")
|
log.Debug().Interface("labels", labels).Msg("Listing containers from agent")
|
||||||
return a.client.ListContainers(ctx, filter)
|
return a.client.ListContainers(ctx, labels)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (a *agentService) Host(ctx context.Context) (container.Host, error) {
|
func (a *agentService) Host(ctx context.Context) (container.Host, error) {
|
||||||
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 (
|
import (
|
||||||
"context"
|
"context"
|
||||||
@@ -8,23 +8,30 @@ import (
|
|||||||
"github.com/amir20/dozzle/internal/container"
|
"github.com/amir20/dozzle/internal/container"
|
||||||
)
|
)
|
||||||
|
|
||||||
type containerService struct {
|
type ContainerService struct {
|
||||||
clientService ClientService
|
clientService ClientService
|
||||||
Container container.Container
|
Container container.Container
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *containerService) RawLogs(ctx context.Context, from time.Time, to time.Time, stdTypes container.StdType) (io.ReadCloser, error) {
|
func NewContainerService(clientService ClientService, container container.Container) *ContainerService {
|
||||||
|
return &ContainerService{
|
||||||
|
clientService: clientService,
|
||||||
|
Container: container,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *ContainerService) RawLogs(ctx context.Context, from time.Time, to time.Time, stdTypes container.StdType) (io.ReadCloser, error) {
|
||||||
return c.clientService.RawLogs(ctx, c.Container, from, to, stdTypes)
|
return c.clientService.RawLogs(ctx, c.Container, from, to, stdTypes)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *containerService) LogsBetweenDates(ctx context.Context, from time.Time, to time.Time, stdTypes container.StdType) (<-chan *container.LogEvent, error) {
|
func (c *ContainerService) LogsBetweenDates(ctx context.Context, from time.Time, to time.Time, stdTypes container.StdType) (<-chan *container.LogEvent, error) {
|
||||||
return c.clientService.LogsBetweenDates(ctx, c.Container, from, to, stdTypes)
|
return c.clientService.LogsBetweenDates(ctx, c.Container, from, to, stdTypes)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *containerService) StreamLogs(ctx context.Context, from time.Time, stdTypes container.StdType, events chan<- *container.LogEvent) error {
|
func (c *ContainerService) StreamLogs(ctx context.Context, from time.Time, stdTypes container.StdType, events chan<- *container.LogEvent) error {
|
||||||
return c.clientService.StreamLogs(ctx, c.Container, from, stdTypes, events)
|
return c.clientService.StreamLogs(ctx, c.Container, from, stdTypes, events)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *containerService) Action(ctx context.Context, action container.ContainerAction) error {
|
func (c *ContainerService) Action(ctx context.Context, action container.ContainerAction) error {
|
||||||
return c.clientService.ContainerAction(ctx, c.Container, action)
|
return c.clientService.ContainerAction(ctx, c.Container, action)
|
||||||
}
|
}
|
||||||
@@ -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"
|
"time"
|
||||||
|
|
||||||
"github.com/amir20/dozzle/internal/container"
|
"github.com/amir20/dozzle/internal/container"
|
||||||
|
container_support "github.com/amir20/dozzle/internal/support/container"
|
||||||
"github.com/rs/zerolog/log"
|
"github.com/rs/zerolog/log"
|
||||||
)
|
)
|
||||||
|
|
||||||
type ContainerFilter = func(*container.Container) bool
|
|
||||||
|
|
||||||
type HostUnavailableError struct {
|
type HostUnavailableError struct {
|
||||||
Host container.Host
|
Host container.Host
|
||||||
Err error
|
Err error
|
||||||
@@ -21,9 +20,9 @@ func (h *HostUnavailableError) Error() string {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type ClientManager interface {
|
type ClientManager interface {
|
||||||
Find(id string) (ClientService, bool)
|
Find(id string) (container_support.ClientService, bool)
|
||||||
List() []ClientService
|
List() []container_support.ClientService
|
||||||
RetryAndList() ([]ClientService, []error)
|
RetryAndList() ([]container_support.ClientService, []error)
|
||||||
Subscribe(ctx context.Context, channel chan<- container.Host)
|
Subscribe(ctx context.Context, channel chan<- container.Host)
|
||||||
Hosts(ctx context.Context) []container.Host
|
Hosts(ctx context.Context) []container.Host
|
||||||
LocalClients() []container.Client
|
LocalClients() []container.Client
|
||||||
@@ -43,25 +42,22 @@ func NewMultiHostService(manager ClientManager, timeout time.Duration) *MultiHos
|
|||||||
return m
|
return m
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m *MultiHostService) FindContainer(host string, id string, filter container.ContainerFilter) (*containerService, error) {
|
func (m *MultiHostService) FindContainer(host string, id string, labels container.ContainerLabels) (*container_support.ContainerService, error) {
|
||||||
client, ok := m.manager.Find(host)
|
client, ok := m.manager.Find(host)
|
||||||
if !ok {
|
if !ok {
|
||||||
return nil, fmt.Errorf("host %s not found", host)
|
return nil, fmt.Errorf("host %s not found", host)
|
||||||
}
|
}
|
||||||
ctx, cancel := context.WithTimeout(context.Background(), m.timeout)
|
ctx, cancel := context.WithTimeout(context.Background(), m.timeout)
|
||||||
defer cancel()
|
defer cancel()
|
||||||
container, err := client.FindContainer(ctx, id, filter)
|
container, err := client.FindContainer(ctx, id, labels)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
return &containerService{
|
return container_support.NewContainerService(client, container), nil
|
||||||
clientService: client,
|
|
||||||
Container: container,
|
|
||||||
}, nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m *MultiHostService) ListContainersForHost(host string, filter container.ContainerFilter) ([]container.Container, error) {
|
func (m *MultiHostService) ListContainersForHost(host string, labels container.ContainerLabels) ([]container.Container, error) {
|
||||||
client, ok := m.manager.Find(host)
|
client, ok := m.manager.Find(host)
|
||||||
if !ok {
|
if !ok {
|
||||||
return nil, fmt.Errorf("host %s not found", host)
|
return nil, fmt.Errorf("host %s not found", host)
|
||||||
@@ -69,17 +65,17 @@ func (m *MultiHostService) ListContainersForHost(host string, filter container.C
|
|||||||
ctx, cancel := context.WithTimeout(context.Background(), m.timeout)
|
ctx, cancel := context.WithTimeout(context.Background(), m.timeout)
|
||||||
defer cancel()
|
defer cancel()
|
||||||
|
|
||||||
return client.ListContainers(ctx, filter)
|
return client.ListContainers(ctx, labels)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m *MultiHostService) ListAllContainers(filter container.ContainerFilter) ([]container.Container, []error) {
|
func (m *MultiHostService) ListAllContainers(labels container.ContainerLabels) ([]container.Container, []error) {
|
||||||
containers := make([]container.Container, 0)
|
containers := make([]container.Container, 0)
|
||||||
clients, errors := m.manager.RetryAndList()
|
clients, errors := m.manager.RetryAndList()
|
||||||
|
|
||||||
for _, client := range clients {
|
for _, client := range clients {
|
||||||
ctx, cancel := context.WithTimeout(context.Background(), m.timeout)
|
ctx, cancel := context.WithTimeout(context.Background(), m.timeout)
|
||||||
defer cancel()
|
defer cancel()
|
||||||
list, err := client.ListContainers(ctx, filter)
|
list, err := client.ListContainers(ctx, labels)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
host, _ := client.Host(ctx)
|
host, _ := client.Host(ctx)
|
||||||
log.Debug().Err(err).Str("host", host.Name).Msg("error listing containers")
|
log.Debug().Err(err).Str("host", host.Name).Msg("error listing containers")
|
||||||
@@ -94,8 +90,8 @@ func (m *MultiHostService) ListAllContainers(filter container.ContainerFilter) (
|
|||||||
return containers, errors
|
return containers, errors
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m *MultiHostService) ListAllContainersFiltered(userFilter container.ContainerFilter, filter ContainerFilter) ([]container.Container, []error) {
|
func (m *MultiHostService) ListAllContainersFiltered(userLabels container.ContainerLabels, filter container_support.ContainerFilter) ([]container.Container, []error) {
|
||||||
containers, err := m.ListAllContainers(userFilter)
|
containers, err := m.ListAllContainers(userLabels)
|
||||||
filtered := make([]container.Container, 0, len(containers))
|
filtered := make([]container.Container, 0, len(containers))
|
||||||
for _, container := range containers {
|
for _, container := range containers {
|
||||||
if filter(&container) {
|
if filter(&container) {
|
||||||
@@ -112,7 +108,7 @@ func (m *MultiHostService) SubscribeEventsAndStats(ctx context.Context, events c
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m *MultiHostService) SubscribeContainersStarted(ctx context.Context, containers chan<- container.Container, filter ContainerFilter) {
|
func (m *MultiHostService) SubscribeContainersStarted(ctx context.Context, containers chan<- container.Container, filter container_support.ContainerFilter) {
|
||||||
newContainers := make(chan container.Container)
|
newContainers := make(chan container.Container)
|
||||||
for _, client := range m.manager.List() {
|
for _, client := range m.manager.List() {
|
||||||
client.SubscribeContainersStarted(ctx, newContainers)
|
client.SubscribeContainersStarted(ctx, newContainers)
|
||||||
@@ -135,10 +131,6 @@ func (m *MultiHostService) SubscribeContainersStarted(ctx context.Context, conta
|
|||||||
}()
|
}()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m *MultiHostService) TotalClients() int {
|
|
||||||
return len(m.manager.List())
|
|
||||||
}
|
|
||||||
|
|
||||||
func (m *MultiHostService) Hosts() []container.Host {
|
func (m *MultiHostService) Hosts() []container.Host {
|
||||||
ctx, cancel := context.WithTimeout(context.Background(), m.timeout)
|
ctx, cancel := context.WithTimeout(context.Background(), m.timeout)
|
||||||
defer cancel()
|
defer cancel()
|
||||||
@@ -161,3 +153,7 @@ func (m *MultiHostService) SubscribeAvailableHosts(ctx context.Context, hosts ch
|
|||||||
func (m *MultiHostService) LocalClients() []container.Client {
|
func (m *MultiHostService) LocalClients() []container.Client {
|
||||||
return m.manager.LocalClients()
|
return m.manager.LocalClients()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (m *MultiHostService) TotalClients() int {
|
||||||
|
return len(m.manager.List())
|
||||||
|
}
|
||||||
|
|||||||
@@ -9,6 +9,8 @@ import (
|
|||||||
|
|
||||||
"github.com/amir20/dozzle/internal/agent"
|
"github.com/amir20/dozzle/internal/agent"
|
||||||
"github.com/amir20/dozzle/internal/container"
|
"github.com/amir20/dozzle/internal/container"
|
||||||
|
container_support "github.com/amir20/dozzle/internal/support/container"
|
||||||
|
|
||||||
"github.com/puzpuzpuz/xsync/v3"
|
"github.com/puzpuzpuz/xsync/v3"
|
||||||
"github.com/samber/lo"
|
"github.com/samber/lo"
|
||||||
lop "github.com/samber/lo/parallel"
|
lop "github.com/samber/lo/parallel"
|
||||||
@@ -17,7 +19,7 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
type RetriableClientManager struct {
|
type RetriableClientManager struct {
|
||||||
clients map[string]ClientService
|
clients map[string]container_support.ClientService
|
||||||
failedAgents []string
|
failedAgents []string
|
||||||
certs tls.Certificate
|
certs tls.Certificate
|
||||||
mu sync.RWMutex
|
mu sync.RWMutex
|
||||||
@@ -25,8 +27,8 @@ type RetriableClientManager struct {
|
|||||||
timeout time.Duration
|
timeout time.Duration
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewRetriableClientManager(agents []string, timeout time.Duration, certs tls.Certificate, clients ...ClientService) *RetriableClientManager {
|
func NewRetriableClientManager(agents []string, timeout time.Duration, certs tls.Certificate, clients ...container_support.ClientService) *RetriableClientManager {
|
||||||
clientMap := make(map[string]ClientService)
|
clientMap := make(map[string]container_support.ClientService)
|
||||||
for _, client := range clients {
|
for _, client := range clients {
|
||||||
ctx, cancel := context.WithTimeout(context.Background(), timeout)
|
ctx, cancel := context.WithTimeout(context.Background(), timeout)
|
||||||
defer cancel()
|
defer cancel()
|
||||||
@@ -64,7 +66,7 @@ func NewRetriableClientManager(agents []string, timeout time.Duration, certs tls
|
|||||||
if _, ok := clientMap[host.ID]; ok {
|
if _, ok := clientMap[host.ID]; ok {
|
||||||
log.Warn().Str("host", host.Name).Str("id", host.ID).Msg("duplicate host with same ID found")
|
log.Warn().Str("host", host.Name).Str("id", host.ID).Msg("duplicate host with same ID found")
|
||||||
} else {
|
} else {
|
||||||
clientMap[host.ID] = NewAgentService(agent)
|
clientMap[host.ID] = container_support.NewAgentService(agent)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -86,7 +88,7 @@ func (m *RetriableClientManager) Subscribe(ctx context.Context, channel chan<- c
|
|||||||
}()
|
}()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m *RetriableClientManager) RetryAndList() ([]ClientService, []error) {
|
func (m *RetriableClientManager) RetryAndList() ([]container_support.ClientService, []error) {
|
||||||
m.mu.Lock()
|
m.mu.Lock()
|
||||||
errors := make([]error, 0)
|
errors := make([]error, 0)
|
||||||
if len(m.failedAgents) > 0 {
|
if len(m.failedAgents) > 0 {
|
||||||
@@ -110,7 +112,7 @@ func (m *RetriableClientManager) RetryAndList() ([]ClientService, []error) {
|
|||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
m.clients[host.ID] = NewAgentService(agent)
|
m.clients[host.ID] = container_support.NewAgentService(agent)
|
||||||
m.subscribers.Range(func(ctx context.Context, channel chan<- container.Host) bool {
|
m.subscribers.Range(func(ctx context.Context, channel chan<- container.Host) bool {
|
||||||
host.Available = true
|
host.Available = true
|
||||||
|
|
||||||
@@ -133,14 +135,14 @@ func (m *RetriableClientManager) RetryAndList() ([]ClientService, []error) {
|
|||||||
return m.List(), errors
|
return m.List(), errors
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m *RetriableClientManager) List() []ClientService {
|
func (m *RetriableClientManager) List() []container_support.ClientService {
|
||||||
m.mu.RLock()
|
m.mu.RLock()
|
||||||
defer m.mu.RUnlock()
|
defer m.mu.RUnlock()
|
||||||
|
|
||||||
return lo.Values(m.clients)
|
return lo.Values(m.clients)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m *RetriableClientManager) Find(id string) (ClientService, bool) {
|
func (m *RetriableClientManager) Find(id string) (container_support.ClientService, bool) {
|
||||||
m.mu.RLock()
|
m.mu.RLock()
|
||||||
defer m.mu.RUnlock()
|
defer m.mu.RUnlock()
|
||||||
|
|
||||||
@@ -155,7 +157,7 @@ func (m *RetriableClientManager) String() string {
|
|||||||
func (m *RetriableClientManager) Hosts(ctx context.Context) []container.Host {
|
func (m *RetriableClientManager) Hosts(ctx context.Context) []container.Host {
|
||||||
clients := m.List()
|
clients := m.List()
|
||||||
|
|
||||||
hosts := lop.Map(clients, func(client ClientService, _ int) container.Host {
|
hosts := lop.Map(clients, func(client container_support.ClientService, _ int) container.Host {
|
||||||
host, err := client.Host(ctx)
|
host, err := client.Host(ctx)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Warn().Err(err).Str("host", host.Name).Msg("error fetching host info for client")
|
log.Warn().Err(err).Str("host", host.Name).Msg("error fetching host info for client")
|
||||||
@@ -186,7 +188,7 @@ func (m *RetriableClientManager) LocalClients() []container.Client {
|
|||||||
clients := make([]container.Client, 0)
|
clients := make([]container.Client, 0)
|
||||||
|
|
||||||
for _, service := range services {
|
for _, service := range services {
|
||||||
if clientService, ok := service.(*dockerClientService); ok {
|
if clientService, ok := service.(*DockerClientService); ok {
|
||||||
clients = append(clients, clientService.client)
|
clients = append(clients, clientService.client)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -11,6 +11,9 @@ import (
|
|||||||
|
|
||||||
"github.com/amir20/dozzle/internal/agent"
|
"github.com/amir20/dozzle/internal/agent"
|
||||||
"github.com/amir20/dozzle/internal/container"
|
"github.com/amir20/dozzle/internal/container"
|
||||||
|
"github.com/amir20/dozzle/internal/docker"
|
||||||
|
container_support "github.com/amir20/dozzle/internal/support/container"
|
||||||
|
|
||||||
"github.com/puzpuzpuz/xsync/v3"
|
"github.com/puzpuzpuz/xsync/v3"
|
||||||
"github.com/samber/lo"
|
"github.com/samber/lo"
|
||||||
lop "github.com/samber/lo/parallel"
|
lop "github.com/samber/lo/parallel"
|
||||||
@@ -19,7 +22,7 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
type SwarmClientManager struct {
|
type SwarmClientManager struct {
|
||||||
clients map[string]ClientService
|
clients map[string]container_support.ClientService
|
||||||
certs tls.Certificate
|
certs tls.Certificate
|
||||||
mu sync.RWMutex
|
mu sync.RWMutex
|
||||||
subscribers *xsync.MapOf[context.Context, chan<- container.Host]
|
subscribers *xsync.MapOf[context.Context, chan<- container.Host]
|
||||||
@@ -47,9 +50,9 @@ func localIPs() []string {
|
|||||||
return ips
|
return ips
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewSwarmClientManager(localClient container.Client, certs tls.Certificate, timeout time.Duration, agentManager *RetriableClientManager, filter container.ContainerFilter) *SwarmClientManager {
|
func NewSwarmClientManager(localClient *docker.DockerClient, certs tls.Certificate, timeout time.Duration, agentManager *RetriableClientManager, labels container.ContainerLabels) *SwarmClientManager {
|
||||||
clientMap := make(map[string]ClientService)
|
clientMap := make(map[string]container_support.ClientService)
|
||||||
localService := NewDockerClientService(localClient, filter)
|
localService := NewDockerClientService(localClient, labels)
|
||||||
clientMap[localClient.Host().ID] = localService
|
clientMap[localClient.Host().ID] = localService
|
||||||
|
|
||||||
id, ok := os.LookupEnv("HOSTNAME")
|
id, ok := os.LookupEnv("HOSTNAME")
|
||||||
@@ -90,7 +93,7 @@ func (m *SwarmClientManager) Subscribe(ctx context.Context, channel chan<- conta
|
|||||||
}()
|
}()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m *SwarmClientManager) RetryAndList() ([]ClientService, []error) {
|
func (m *SwarmClientManager) RetryAndList() ([]container_support.ClientService, []error) {
|
||||||
m.mu.Lock()
|
m.mu.Lock()
|
||||||
|
|
||||||
ips, err := net.LookupIP(fmt.Sprintf("tasks.%s", m.name))
|
ips, err := net.LookupIP(fmt.Sprintf("tasks.%s", m.name))
|
||||||
@@ -104,7 +107,7 @@ func (m *SwarmClientManager) RetryAndList() ([]ClientService, []error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
clients := lo.Values(m.clients)
|
clients := lo.Values(m.clients)
|
||||||
endpoints := lo.KeyBy(clients, func(client ClientService) string {
|
endpoints := lo.KeyBy(clients, func(client container_support.ClientService) string {
|
||||||
ctx, cancel := context.WithTimeout(context.Background(), m.timeout)
|
ctx, cancel := context.WithTimeout(context.Background(), m.timeout)
|
||||||
defer cancel()
|
defer cancel()
|
||||||
host, _ := client.Host(ctx)
|
host, _ := client.Host(ctx)
|
||||||
@@ -155,7 +158,7 @@ func (m *SwarmClientManager) RetryAndList() ([]ClientService, []error) {
|
|||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
client := NewAgentService(agent)
|
client := container_support.NewAgentService(agent)
|
||||||
m.clients[host.ID] = client
|
m.clients[host.ID] = client
|
||||||
log.Info().Stringer("ip", ip).Str("id", host.ID).Str("name", host.Name).Msg("added new swarm agent")
|
log.Info().Stringer("ip", ip).Str("id", host.ID).Str("name", host.Name).Msg("added new swarm agent")
|
||||||
|
|
||||||
@@ -182,7 +185,7 @@ func (m *SwarmClientManager) RetryAndList() ([]ClientService, []error) {
|
|||||||
return m.List(), errors
|
return m.List(), errors
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m *SwarmClientManager) List() []ClientService {
|
func (m *SwarmClientManager) List() []container_support.ClientService {
|
||||||
m.mu.RLock()
|
m.mu.RLock()
|
||||||
defer m.mu.RUnlock()
|
defer m.mu.RUnlock()
|
||||||
|
|
||||||
@@ -192,7 +195,7 @@ func (m *SwarmClientManager) List() []ClientService {
|
|||||||
return append(agents, clients...)
|
return append(agents, clients...)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m *SwarmClientManager) Find(id string) (ClientService, bool) {
|
func (m *SwarmClientManager) Find(id string) (container_support.ClientService, bool) {
|
||||||
m.mu.RLock()
|
m.mu.RLock()
|
||||||
defer m.mu.RUnlock()
|
defer m.mu.RUnlock()
|
||||||
|
|
||||||
@@ -210,7 +213,7 @@ func (m *SwarmClientManager) Hosts(ctx context.Context) []container.Host {
|
|||||||
clients := lo.Values(m.clients)
|
clients := lo.Values(m.clients)
|
||||||
m.mu.RUnlock()
|
m.mu.RUnlock()
|
||||||
|
|
||||||
swarmNodes := lop.Map(clients, func(client ClientService, _ int) container.Host {
|
swarmNodes := lop.Map(clients, func(client container_support.ClientService, _ int) container.Host {
|
||||||
host, err := client.Host(ctx)
|
host, err := client.Host(ctx)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Warn().Err(err).Str("id", host.ID).Msg("error getting host from client")
|
log.Warn().Err(err).Str("id", host.ID).Msg("error getting host from client")
|
||||||
|
|||||||
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")
|
action := chi.URLParam(r, "action")
|
||||||
id := chi.URLParam(r, "id")
|
id := chi.URLParam(r, "id")
|
||||||
|
|
||||||
usersFilter := h.config.Filter
|
userLabels := h.config.Labels
|
||||||
if h.config.Authorization.Provider != NONE {
|
if h.config.Authorization.Provider != NONE {
|
||||||
user := auth.UserFromContext(r.Context())
|
user := auth.UserFromContext(r.Context())
|
||||||
if user.ContainerFilter.Exists() {
|
if user.ContainerLabels.Exists() {
|
||||||
usersFilter = user.ContainerFilter
|
userLabels = user.ContainerLabels
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
containerService, err := h.multiHostService.FindContainer(hostKey(r), id, usersFilter)
|
containerService, err := h.hostService.FindContainer(hostKey(r), id, userLabels)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Error().Err(err).Msg("error while trying to find container")
|
log.Error().Err(err).Msg("error while trying to find container")
|
||||||
http.Error(w, err.Error(), http.StatusNotFound)
|
http.Error(w, err.Error(), http.StatusNotFound)
|
||||||
|
|||||||
@@ -9,8 +9,8 @@ import (
|
|||||||
|
|
||||||
func (h *handler) debugStore(w http.ResponseWriter, r *http.Request) {
|
func (h *handler) debugStore(w http.ResponseWriter, r *http.Request) {
|
||||||
respone := make(map[string]interface{})
|
respone := make(map[string]interface{})
|
||||||
respone["hosts"] = h.multiHostService.Hosts()
|
respone["hosts"] = h.hostService.Hosts()
|
||||||
containers, errors := h.multiHostService.ListAllContainers(container.ContainerFilter{})
|
containers, errors := h.hostService.ListAllContainers(container.ContainerLabels{})
|
||||||
respone["containers"] = containers
|
respone["containers"] = containers
|
||||||
respone["errors"] = errors
|
respone["errors"] = errors
|
||||||
|
|
||||||
|
|||||||
@@ -10,7 +10,6 @@ import (
|
|||||||
|
|
||||||
"github.com/amir20/dozzle/internal/auth"
|
"github.com/amir20/dozzle/internal/auth"
|
||||||
"github.com/amir20/dozzle/internal/container"
|
"github.com/amir20/dozzle/internal/container"
|
||||||
"github.com/docker/docker/pkg/stdcopy"
|
|
||||||
"github.com/go-chi/chi/v5"
|
"github.com/go-chi/chi/v5"
|
||||||
"github.com/rs/zerolog/log"
|
"github.com/rs/zerolog/log"
|
||||||
)
|
)
|
||||||
@@ -23,11 +22,11 @@ func (h *handler) downloadLogs(w http.ResponseWriter, r *http.Request) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
usersFilter := h.config.Filter
|
userLabels := h.config.Labels
|
||||||
if h.config.Authorization.Provider != NONE {
|
if h.config.Authorization.Provider != NONE {
|
||||||
user := auth.UserFromContext(r.Context())
|
user := auth.UserFromContext(r.Context())
|
||||||
if user.ContainerFilter.Exists() {
|
if user.ContainerLabels.Exists() {
|
||||||
usersFilter = user.ContainerFilter
|
userLabels = user.ContainerLabels
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -66,7 +65,7 @@ func (h *handler) downloadLogs(w http.ResponseWriter, r *http.Request) {
|
|||||||
|
|
||||||
host := parts[0]
|
host := parts[0]
|
||||||
id := parts[1]
|
id := parts[1]
|
||||||
containerService, err := h.multiHostService.FindContainer(host, id, usersFilter)
|
containerService, err := h.hostService.FindContainer(host, id, userLabels)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Error().Err(err).Msgf("error finding container %s", id)
|
log.Error().Err(err).Msgf("error finding container %s", id)
|
||||||
http.Error(w, fmt.Sprintf("error finding container %s: %v", id, err), http.StatusBadRequest)
|
http.Error(w, fmt.Sprintf("error finding container %s: %v", id, err), http.StatusBadRequest)
|
||||||
@@ -90,19 +89,12 @@ func (h *handler) downloadLogs(w http.ResponseWriter, r *http.Request) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// Copy logs directly to zip entry
|
// Copy logs to zip file
|
||||||
if containerService.Container.Tty {
|
_, err = io.Copy(f, reader)
|
||||||
if _, err := io.Copy(f, reader); err != nil {
|
if err != nil {
|
||||||
log.Error().Err(err).Msgf("error copying logs for container %s", id)
|
log.Error().Err(err).Msgf("error copying logs for container %s", id)
|
||||||
http.Error(w, fmt.Sprintf("error copying logs for container %s: %v", id, err), http.StatusInternalServerError)
|
http.Error(w, fmt.Sprintf("error copying logs for container %s: %v", id, err), http.StatusInternalServerError)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
} else {
|
|
||||||
if _, err := stdcopy.StdCopy(f, f, reader); err != nil {
|
|
||||||
log.Error().Err(err).Msgf("error copying logs for container %s", id)
|
|
||||||
http.Error(w, fmt.Sprintf("error copying logs for container %s: %v", id, err), http.StatusInternalServerError)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -24,18 +24,18 @@ func (h *handler) streamEvents(w http.ResponseWriter, r *http.Request) {
|
|||||||
stats := make(chan container.ContainerStat)
|
stats := make(chan container.ContainerStat)
|
||||||
availableHosts := make(chan container.Host)
|
availableHosts := make(chan container.Host)
|
||||||
|
|
||||||
h.multiHostService.SubscribeEventsAndStats(r.Context(), events, stats)
|
h.hostService.SubscribeEventsAndStats(r.Context(), events, stats)
|
||||||
h.multiHostService.SubscribeAvailableHosts(r.Context(), availableHosts)
|
h.hostService.SubscribeAvailableHosts(r.Context(), availableHosts)
|
||||||
|
|
||||||
usersFilter := h.config.Filter
|
userLabels := h.config.Labels
|
||||||
if h.config.Authorization.Provider != NONE {
|
if h.config.Authorization.Provider != NONE {
|
||||||
user := auth.UserFromContext(r.Context())
|
user := auth.UserFromContext(r.Context())
|
||||||
if user.ContainerFilter.Exists() {
|
if user.ContainerLabels.Exists() {
|
||||||
usersFilter = user.ContainerFilter
|
userLabels = user.ContainerLabels
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
allContainers, errors := h.multiHostService.ListAllContainers(usersFilter)
|
allContainers, errors := h.hostService.ListAllContainers(userLabels)
|
||||||
|
|
||||||
for _, err := range errors {
|
for _, err := range errors {
|
||||||
log.Warn().Err(err).Msg("error listing containers")
|
log.Warn().Err(err).Msg("error listing containers")
|
||||||
@@ -73,7 +73,7 @@ func (h *handler) streamEvents(w http.ResponseWriter, r *http.Request) {
|
|||||||
if event.Name == "start" || event.Name == "rename" {
|
if event.Name == "start" || event.Name == "rename" {
|
||||||
log.Debug().Str("action", event.Name).Str("id", event.ActorID).Msg("container event")
|
log.Debug().Str("action", event.Name).Str("id", event.ActorID).Msg("container event")
|
||||||
|
|
||||||
if containers, err := h.multiHostService.ListContainersForHost(event.Host, usersFilter); err == nil {
|
if containers, err := h.hostService.ListContainersForHost(event.Host, userLabels); err == nil {
|
||||||
if err := sseWriter.Event("containers-changed", containers); err != nil {
|
if err := sseWriter.Event("containers-changed", containers); err != nil {
|
||||||
log.Error().Err(err).Msg("error writing containers to event stream")
|
log.Error().Err(err).Msg("error writing containers to event stream")
|
||||||
return
|
return
|
||||||
@@ -88,7 +88,7 @@ func (h *handler) streamEvents(w http.ResponseWriter, r *http.Request) {
|
|||||||
|
|
||||||
case "update":
|
case "update":
|
||||||
log.Debug().Str("id", event.ActorID).Msg("container updated")
|
log.Debug().Str("id", event.ActorID).Msg("container updated")
|
||||||
if containerService, err := h.multiHostService.FindContainer(event.Host, event.ActorID, usersFilter); err == nil {
|
if containerService, err := h.hostService.FindContainer(event.Host, event.ActorID, userLabels); err == nil {
|
||||||
if err := sseWriter.Event("container-updated", containerService.Container); err != nil {
|
if err := sseWriter.Event("container-updated", containerService.Container); err != nil {
|
||||||
log.Error().Err(err).Msg("error writing event to event stream")
|
log.Error().Err(err).Msg("error writing event to event stream")
|
||||||
return
|
return
|
||||||
@@ -123,7 +123,7 @@ func sendBeaconEvent(h *handler, r *http.Request, runningContainers int) {
|
|||||||
b := types.BeaconEvent{
|
b := types.BeaconEvent{
|
||||||
AuthProvider: string(h.config.Authorization.Provider),
|
AuthProvider: string(h.config.Authorization.Provider),
|
||||||
Browser: r.Header.Get("User-Agent"),
|
Browser: r.Header.Get("User-Agent"),
|
||||||
Clients: h.multiHostService.TotalClients(),
|
Clients: len(h.hostService.Hosts()),
|
||||||
HasActions: h.config.EnableActions,
|
HasActions: h.config.EnableActions,
|
||||||
HasCustomAddress: h.config.Addr != ":8080",
|
HasCustomAddress: h.config.Addr != ":8080",
|
||||||
HasCustomBase: h.config.Base != "/",
|
HasCustomBase: h.config.Base != "/",
|
||||||
@@ -133,9 +133,9 @@ func sendBeaconEvent(h *handler, r *http.Request, runningContainers int) {
|
|||||||
Version: h.config.Version,
|
Version: h.config.Version,
|
||||||
}
|
}
|
||||||
|
|
||||||
local, err := h.multiHostService.LocalHost()
|
local, err := h.hostService.LocalHost()
|
||||||
if err == nil {
|
if err == nil {
|
||||||
b.ServerID = local.ID
|
b.ServerID = local.ID // TODO : fix this for k8s
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := analytics.SendBeacon(b); err != nil {
|
if err := analytics.SendBeacon(b); err != nil {
|
||||||
|
|||||||
@@ -54,7 +54,7 @@ func Test_handler_streamEvents_happy(t *testing.T) {
|
|||||||
})
|
})
|
||||||
|
|
||||||
// This is needed so that the server is initialized for store
|
// This is needed so that the server is initialized for store
|
||||||
manager := docker_support.NewRetriableClientManager(nil, 3*time.Second, tls.Certificate{}, docker_support.NewDockerClientService(mockedClient, container.ContainerFilter{}))
|
manager := docker_support.NewRetriableClientManager(nil, 3*time.Second, tls.Certificate{}, docker_support.NewDockerClientService(mockedClient, container.ContainerLabels{}))
|
||||||
multiHostService := docker_support.NewMultiHostService(manager, 3*time.Second)
|
multiHostService := docker_support.NewMultiHostService(manager, 3*time.Second)
|
||||||
|
|
||||||
server := CreateServer(multiHostService, nil, Config{Base: "/", Authorization: Authorization{Provider: NONE}})
|
server := CreateServer(multiHostService, nil, Config{Base: "/", Authorization: Authorization{Provider: NONE}})
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ import (
|
|||||||
func (h *handler) healthcheck(w http.ResponseWriter, r *http.Request) {
|
func (h *handler) healthcheck(w http.ResponseWriter, r *http.Request) {
|
||||||
log.Debug().Msg("Executing healthcheck")
|
log.Debug().Msg("Executing healthcheck")
|
||||||
|
|
||||||
clients := h.multiHostService.LocalClients()
|
clients := h.hostService.LocalClients()
|
||||||
for _, client := range clients {
|
for _, client := range clients {
|
||||||
if err := client.Ping(r.Context()); err != nil {
|
if err := client.Ping(r.Context()); err != nil {
|
||||||
log.Error().Err(err).Str("host", client.Host().Name).Msg("error pinging host")
|
log.Error().Err(err).Str("host", client.Host().Name).Msg("error pinging host")
|
||||||
|
|||||||
@@ -43,7 +43,7 @@ func (h *handler) executeTemplate(w http.ResponseWriter, req *http.Request) {
|
|||||||
base = h.config.Base
|
base = h.config.Base
|
||||||
}
|
}
|
||||||
|
|
||||||
hosts := h.multiHostService.Hosts()
|
hosts := h.hostService.Hosts()
|
||||||
sort.Slice(hosts, func(i, j int) bool {
|
sort.Slice(hosts, func(i, j int) bool {
|
||||||
return hosts[i].Name < hosts[j].Name
|
return hosts[i].Name < hosts[j].Name
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -18,6 +18,7 @@ import (
|
|||||||
|
|
||||||
"github.com/amir20/dozzle/internal/auth"
|
"github.com/amir20/dozzle/internal/auth"
|
||||||
"github.com/amir20/dozzle/internal/container"
|
"github.com/amir20/dozzle/internal/container"
|
||||||
|
container_support "github.com/amir20/dozzle/internal/support/container"
|
||||||
"github.com/amir20/dozzle/internal/support/search"
|
"github.com/amir20/dozzle/internal/support/search"
|
||||||
support_web "github.com/amir20/dozzle/internal/support/web"
|
support_web "github.com/amir20/dozzle/internal/support/web"
|
||||||
"github.com/amir20/dozzle/internal/utils"
|
"github.com/amir20/dozzle/internal/utils"
|
||||||
@@ -47,15 +48,15 @@ func (h *handler) fetchLogsBetweenDates(w http.ResponseWriter, r *http.Request)
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
usersFilter := h.config.Filter
|
usersLabels := h.config.Labels
|
||||||
if h.config.Authorization.Provider != NONE {
|
if h.config.Authorization.Provider != NONE {
|
||||||
user := auth.UserFromContext(r.Context())
|
user := auth.UserFromContext(r.Context())
|
||||||
if user.ContainerFilter.Exists() {
|
if user.ContainerLabels.Exists() {
|
||||||
usersFilter = user.ContainerFilter
|
usersLabels = user.ContainerLabels
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
containerService, err := h.multiHostService.FindContainer(hostKey(r), id, usersFilter)
|
containerService, err := h.hostService.FindContainer(hostKey(r), id, usersLabels)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
http.Error(w, err.Error(), http.StatusNotFound)
|
http.Error(w, err.Error(), http.StatusNotFound)
|
||||||
return
|
return
|
||||||
@@ -227,7 +228,7 @@ func (h *handler) streamHostLogs(w http.ResponseWriter, r *http.Request) {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
func (h *handler) streamLogsForContainers(w http.ResponseWriter, r *http.Request, containerFilter ContainerFilter) {
|
func (h *handler) streamLogsForContainers(w http.ResponseWriter, r *http.Request, containerFilter container_support.ContainerFilter) {
|
||||||
var stdTypes container.StdType
|
var stdTypes container.StdType
|
||||||
if r.URL.Query().Has("stdout") {
|
if r.URL.Query().Has("stdout") {
|
||||||
stdTypes |= container.STDOUT
|
stdTypes |= container.STDOUT
|
||||||
@@ -248,15 +249,15 @@ func (h *handler) streamLogsForContainers(w http.ResponseWriter, r *http.Request
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
usersFilter := h.config.Filter
|
userLabels := h.config.Labels
|
||||||
if h.config.Authorization.Provider != NONE {
|
if h.config.Authorization.Provider != NONE {
|
||||||
user := auth.UserFromContext(r.Context())
|
user := auth.UserFromContext(r.Context())
|
||||||
if user.ContainerFilter.Exists() {
|
if user.ContainerLabels.Exists() {
|
||||||
usersFilter = user.ContainerFilter
|
userLabels = user.ContainerLabels
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
existingContainers, errs := h.multiHostService.ListAllContainersFiltered(usersFilter, containerFilter)
|
existingContainers, errs := h.hostService.ListAllContainersFiltered(userLabels, containerFilter)
|
||||||
if len(errs) > 0 {
|
if len(errs) > 0 {
|
||||||
log.Warn().Err(errs[0]).Msg("error while listing containers")
|
log.Warn().Err(errs[0]).Msg("error while listing containers")
|
||||||
}
|
}
|
||||||
@@ -290,7 +291,7 @@ func (h *handler) streamLogsForContainers(w http.ResponseWriter, r *http.Request
|
|||||||
events := make([]*container.LogEvent, 0)
|
events := make([]*container.LogEvent, 0)
|
||||||
stillRunning := false
|
stillRunning := false
|
||||||
for _, container := range existingContainers {
|
for _, container := range existingContainers {
|
||||||
containerService, err := h.multiHostService.FindContainer(container.Host, container.ID, usersFilter)
|
containerService, err := h.hostService.FindContainer(container.Host, container.ID, userLabels)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Error().Err(err).Msg("error while finding container")
|
log.Error().Err(err).Msg("error while finding container")
|
||||||
@@ -337,7 +338,7 @@ func (h *handler) streamLogsForContainers(w http.ResponseWriter, r *http.Request
|
|||||||
}
|
}
|
||||||
|
|
||||||
streamLogs := func(c container.Container) {
|
streamLogs := func(c container.Container) {
|
||||||
containerService, err := h.multiHostService.FindContainer(c.Host, c.ID, usersFilter)
|
containerService, err := h.hostService.FindContainer(c.Host, c.ID, userLabels)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Error().Err(err).Msg("error while finding container")
|
log.Error().Err(err).Msg("error while finding container")
|
||||||
return
|
return
|
||||||
@@ -369,7 +370,7 @@ func (h *handler) streamLogsForContainers(w http.ResponseWriter, r *http.Request
|
|||||||
}
|
}
|
||||||
|
|
||||||
newContainers := make(chan container.Container)
|
newContainers := make(chan container.Container)
|
||||||
h.multiHostService.SubscribeContainersStarted(r.Context(), newContainers, containerFilter)
|
h.hostService.SubscribeContainersStarted(r.Context(), newContainers, containerFilter)
|
||||||
|
|
||||||
ticker := time.NewTicker(5 * time.Second)
|
ticker := time.NewTicker(5 * time.Second)
|
||||||
sseWriter.Ping()
|
sseWriter.Ping()
|
||||||
@@ -388,7 +389,7 @@ loop:
|
|||||||
}
|
}
|
||||||
sseWriter.Message(logEvent)
|
sseWriter.Message(logEvent)
|
||||||
case c := <-newContainers:
|
case c := <-newContainers:
|
||||||
if _, err := h.multiHostService.FindContainer(c.Host, c.ID, usersFilter); err == nil {
|
if _, err := h.hostService.FindContainer(c.Host, c.ID, userLabels); err == nil {
|
||||||
events <- &container.ContainerEvent{ActorID: c.ID, Name: "container-started", Host: c.Host}
|
events <- &container.ContainerEvent{ActorID: c.ID, Name: "container-started", Host: c.Host}
|
||||||
go streamLogs(c)
|
go streamLogs(c)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
package web
|
package web
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"context"
|
||||||
"io/fs"
|
"io/fs"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
@@ -9,7 +10,7 @@ import (
|
|||||||
|
|
||||||
"github.com/amir20/dozzle/internal/auth"
|
"github.com/amir20/dozzle/internal/auth"
|
||||||
"github.com/amir20/dozzle/internal/container"
|
"github.com/amir20/dozzle/internal/container"
|
||||||
docker_support "github.com/amir20/dozzle/internal/support/docker"
|
container_support "github.com/amir20/dozzle/internal/support/container"
|
||||||
|
|
||||||
"github.com/go-chi/chi/v5"
|
"github.com/go-chi/chi/v5"
|
||||||
"github.com/go-chi/chi/v5/middleware"
|
"github.com/go-chi/chi/v5/middleware"
|
||||||
@@ -34,7 +35,7 @@ type Config struct {
|
|||||||
Dev bool
|
Dev bool
|
||||||
Authorization Authorization
|
Authorization Authorization
|
||||||
EnableActions bool
|
EnableActions bool
|
||||||
Filter container.ContainerFilter
|
Labels container.ContainerLabels
|
||||||
}
|
}
|
||||||
|
|
||||||
type Authorization struct {
|
type Authorization struct {
|
||||||
@@ -48,20 +49,30 @@ type Authorizer interface {
|
|||||||
CreateToken(string, string) (string, error)
|
CreateToken(string, string) (string, error)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type 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 {
|
type handler struct {
|
||||||
content fs.FS
|
content fs.FS
|
||||||
config *Config
|
config *Config
|
||||||
multiHostService *docker_support.MultiHostService
|
hostService HostService
|
||||||
}
|
}
|
||||||
|
|
||||||
type MultiHostService = docker_support.MultiHostService
|
func CreateServer(hostService HostService, content fs.FS, config Config) *http.Server {
|
||||||
type ContainerFilter = docker_support.ContainerFilter
|
|
||||||
|
|
||||||
func CreateServer(multiHostService *MultiHostService, content fs.FS, config Config) *http.Server {
|
|
||||||
handler := &handler{
|
handler := &handler{
|
||||||
content: content,
|
content: content,
|
||||||
config: &config,
|
config: &config,
|
||||||
multiHostService: multiHostService,
|
hostService: hostService,
|
||||||
}
|
}
|
||||||
|
|
||||||
return &http.Server{Addr: config.Addr, Handler: createRouter(handler)}
|
return &http.Server{Addr: config.Addr, Handler: createRouter(handler)}
|
||||||
|
|||||||
@@ -38,8 +38,8 @@ func (m *MockedClient) ContainerEvents(ctx context.Context, events chan<- contai
|
|||||||
return args.Error(0)
|
return args.Error(0)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m *MockedClient) ListContainers(ctx context.Context, filter container.ContainerFilter) ([]container.Container, error) {
|
func (m *MockedClient) ListContainers(ctx context.Context, labels container.ContainerLabels) ([]container.Container, error) {
|
||||||
args := m.Called(ctx, filter)
|
args := m.Called(ctx, labels)
|
||||||
return args.Get(0).([]container.Container), args.Error(1)
|
return args.Get(0).([]container.Container), args.Error(1)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -86,10 +86,10 @@ func createHandler(client container.Client, content fs.FS, config Config) *chi.M
|
|||||||
content = afero.NewIOFS(fs)
|
content = afero.NewIOFS(fs)
|
||||||
}
|
}
|
||||||
|
|
||||||
manager := docker_support.NewRetriableClientManager(nil, 3*time.Second, tls.Certificate{}, docker_support.NewDockerClientService(client, container.ContainerFilter{}))
|
manager := docker_support.NewRetriableClientManager(nil, 3*time.Second, tls.Certificate{}, docker_support.NewDockerClientService(client, container.ContainerLabels{}))
|
||||||
multiHostService := docker_support.NewMultiHostService(manager, 3*time.Second)
|
multiHostService := docker_support.NewMultiHostService(manager, 3*time.Second)
|
||||||
return createRouter(&handler{
|
return createRouter(&handler{
|
||||||
multiHostService: multiHostService,
|
hostService: multiHostService,
|
||||||
content: content,
|
content: content,
|
||||||
config: &config,
|
config: &config,
|
||||||
})
|
})
|
||||||
|
|||||||
33
main.go
33
main.go
@@ -19,8 +19,10 @@ import (
|
|||||||
"github.com/amir20/dozzle/internal/container"
|
"github.com/amir20/dozzle/internal/container"
|
||||||
"github.com/amir20/dozzle/internal/docker"
|
"github.com/amir20/dozzle/internal/docker"
|
||||||
"github.com/amir20/dozzle/internal/healthcheck"
|
"github.com/amir20/dozzle/internal/healthcheck"
|
||||||
|
"github.com/amir20/dozzle/internal/k8s"
|
||||||
"github.com/amir20/dozzle/internal/support/cli"
|
"github.com/amir20/dozzle/internal/support/cli"
|
||||||
docker_support "github.com/amir20/dozzle/internal/support/docker"
|
docker_support "github.com/amir20/dozzle/internal/support/docker"
|
||||||
|
k8s_support "github.com/amir20/dozzle/internal/support/k8s"
|
||||||
"github.com/amir20/dozzle/internal/web"
|
"github.com/amir20/dozzle/internal/web"
|
||||||
"github.com/rs/zerolog/log"
|
"github.com/rs/zerolog/log"
|
||||||
)
|
)
|
||||||
@@ -160,17 +162,17 @@ func main() {
|
|||||||
|
|
||||||
log.Info().Msgf("Dozzle version %s", args.Version())
|
log.Info().Msgf("Dozzle version %s", args.Version())
|
||||||
|
|
||||||
var multiHostService *docker_support.MultiHostService
|
var hostService web.HostService
|
||||||
if args.Mode == "server" {
|
if args.Mode == "server" {
|
||||||
var localClient container.Client
|
var localClient container.Client
|
||||||
localClient, multiHostService = cli.CreateMultiHostService(certs, args)
|
localClient, multiHostService := cli.CreateMultiHostService(certs, args)
|
||||||
if multiHostService.TotalClients() == 0 {
|
if multiHostService.TotalClients() == 0 {
|
||||||
log.Fatal().Msg("Could not connect to any Docker Engine")
|
log.Fatal().Msg("Could not connect to any Docker Engine")
|
||||||
} else {
|
} else {
|
||||||
log.Info().Int("clients", multiHostService.TotalClients()).Msg("Connected to Docker")
|
log.Info().Int("clients", multiHostService.TotalClients()).Msg("Connected to Docker")
|
||||||
}
|
}
|
||||||
go cli.StartEvent(args, "server", localClient, "")
|
go cli.StartEvent(args, "server", localClient, "")
|
||||||
|
hostService = multiHostService
|
||||||
} else if args.Mode == "swarm" {
|
} else if args.Mode == "swarm" {
|
||||||
localClient, err := docker.NewLocalClient("")
|
localClient, err := docker.NewLocalClient("")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -182,7 +184,7 @@ func main() {
|
|||||||
}
|
}
|
||||||
agentManager := docker_support.NewRetriableClientManager(args.RemoteAgent, args.Timeout, certs)
|
agentManager := docker_support.NewRetriableClientManager(args.RemoteAgent, args.Timeout, certs)
|
||||||
manager := docker_support.NewSwarmClientManager(localClient, certs, args.Timeout, agentManager, args.Filter)
|
manager := docker_support.NewSwarmClientManager(localClient, certs, args.Timeout, agentManager, args.Filter)
|
||||||
multiHostService = docker_support.NewMultiHostService(manager, args.Timeout)
|
hostService = docker_support.NewMultiHostService(manager, args.Timeout)
|
||||||
log.Info().Msg("Starting in swarm mode")
|
log.Info().Msg("Starting in swarm mode")
|
||||||
listener, err := net.Listen("tcp", ":7007")
|
listener, err := net.Listen("tcp", ":7007")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -194,16 +196,29 @@ func main() {
|
|||||||
}
|
}
|
||||||
go cli.StartEvent(args, "swarm", localClient, "")
|
go cli.StartEvent(args, "swarm", localClient, "")
|
||||||
go func() {
|
go func() {
|
||||||
log.Info().Msgf("Dozzle agent version %s", args.Version())
|
log.Info().Msgf("Dozzle agent version in swarm mode %s", args.Version())
|
||||||
if err := server.Serve(listener); err != nil {
|
if err := server.Serve(listener); err != nil {
|
||||||
log.Error().Err(err).Msg("failed to serve")
|
log.Error().Err(err).Msg("failed to serve")
|
||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
|
} else if args.Mode == "k8s" {
|
||||||
|
localClient, err := k8s.NewK8sClient("default")
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal().Err(err).Msg("Could not create k8s client")
|
||||||
|
}
|
||||||
|
|
||||||
|
clusterService, err := k8s_support.NewK8sClusterService(localClient, args.Timeout)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal().Err(err).Msg("Could not create k8s cluster service")
|
||||||
|
}
|
||||||
|
|
||||||
|
go cli.StartEvent(args, "k8s", nil, "")
|
||||||
|
hostService = clusterService
|
||||||
} else {
|
} else {
|
||||||
log.Fatal().Str("mode", args.Mode).Msg("Invalid mode")
|
log.Fatal().Str("mode", args.Mode).Msg("Invalid mode")
|
||||||
}
|
}
|
||||||
|
|
||||||
srv := createServer(args, multiHostService)
|
srv := createServer(args, hostService)
|
||||||
go func() {
|
go func() {
|
||||||
log.Info().Msgf("Accepting connections on %s", args.Addr)
|
log.Info().Msgf("Accepting connections on %s", args.Addr)
|
||||||
if err := srv.ListenAndServe(); err != http.ErrServerClosed {
|
if err := srv.ListenAndServe(); err != http.ErrServerClosed {
|
||||||
@@ -224,7 +239,7 @@ func main() {
|
|||||||
log.Debug().Msg("shut down complete")
|
log.Debug().Msg("shut down complete")
|
||||||
}
|
}
|
||||||
|
|
||||||
func createServer(args cli.Args, multiHostService *docker_support.MultiHostService) *http.Server {
|
func createServer(args cli.Args, hostService web.HostService) *http.Server {
|
||||||
_, dev := os.LookupEnv("DEV")
|
_, dev := os.LookupEnv("DEV")
|
||||||
|
|
||||||
var provider web.AuthProvider = web.NONE
|
var provider web.AuthProvider = web.NONE
|
||||||
@@ -286,7 +301,7 @@ func createServer(args cli.Args, multiHostService *docker_support.MultiHostServi
|
|||||||
TTL: authTTL,
|
TTL: authTTL,
|
||||||
},
|
},
|
||||||
EnableActions: args.EnableActions,
|
EnableActions: args.EnableActions,
|
||||||
Filter: args.Filter,
|
Labels: args.Filter,
|
||||||
}
|
}
|
||||||
|
|
||||||
assets, err := fs.Sub(content, "dist")
|
assets, err := fs.Sub(content, "dist")
|
||||||
@@ -313,5 +328,5 @@ func createServer(args cli.Args, multiHostService *docker_support.MultiHostServi
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return web.CreateServer(multiHostService, assets, config)
|
return web.CreateServer(hostService, assets, config)
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user