Compare commits
92 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 7cf67eb99a | |||
| 869d5e5002 | |||
| 392dfafc3d | |||
| 21fe5b84f2 | |||
| 65fa04c877 | |||
| 3a352a7320 | |||
| 3bdc9c04f8 | |||
| 6e3c811974 | |||
| ddff9e96a2 | |||
| 1daf813e1b | |||
| 09a169851b | |||
| 1d5ddee1ae | |||
| b7e9871382 | |||
| 9e028b9916 | |||
| 78c0e5fd7f | |||
| 4861503488 | |||
| 115b0e475d | |||
| 6d928815df | |||
| 2a70d0b458 | |||
| 8c9c9a83af | |||
| 571de4084e | |||
| 5abf7e4a9d | |||
| d580e9447b | |||
| 877faaa740 | |||
| 1d4698d77a | |||
| 13da2a4222 | |||
| 209ce312d4 | |||
| 6da36c4c0d | |||
| d9d93a8147 | |||
| eba87fa49e | |||
| 7ae409981c | |||
| 20fb7984d2 | |||
| 17c15b65a2 | |||
| 9ce736a448 | |||
| 10c95124e4 | |||
| 5e02142f77 | |||
| f9585cc656 | |||
| 6da7f6dffd | |||
| 2de739141d | |||
| 585e1edfee | |||
| bf81852003 | |||
| 5f73b41c57 | |||
| 9f7b17f4ec | |||
| 6c292be943 | |||
| 21ae6eda8b | |||
| b732844e7b | |||
| 1b38f37ac2 | |||
| c20ce29f5c | |||
| 47e64bb57a | |||
| b9a5262257 | |||
| 47204b33c4 | |||
| 162f9ea580 | |||
| 2381a8b8a3 | |||
| 8fbbfaf043 | |||
| 3d077b9503 | |||
| 68ea86c55b | |||
| ad5c1c4452 | |||
| 0efd98e999 | |||
| ff72431a72 | |||
| 6d2ed53629 | |||
| 992e1a27ae | |||
| 2378a1f115 | |||
| 3b122564d9 | |||
| a783f7060c | |||
| 76e5c63436 | |||
| 5a17a07b8f | |||
| d1fc9b750b | |||
| 87a59939dd | |||
| 3a159cb89b | |||
| 84ec46c400 | |||
| 0c4ca6d605 | |||
| db5d36c89b | |||
| 8cdacf608a | |||
| 78487a553c | |||
| 8da67482d8 | |||
| 60914f3208 | |||
| 0dc2a391df | |||
| 4cfd3d0cfa | |||
| 9752504097 | |||
| f08f2455de | |||
| b2ea1c592a | |||
| 6f89a1e70b | |||
| 93ce48ebf3 | |||
| 0412581b6f | |||
| 285d0ef0f4 | |||
| be313a68a3 | |||
| 14ddd1c4e8 | |||
| 368646a7a3 | |||
| 691a89660e | |||
| d03844b488 | |||
| 3f860805f5 | |||
| e8eef82b1c |
@@ -90,7 +90,7 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@6524bf65af31da8d45b59e8c27de4bd072b392f5 # v3.8.0
|
||||
uses: docker/setup-buildx-action@f7ce87c1d6bead3e36075b2ce75da1f6cc28aaca # v3.9.0
|
||||
- name: Login to DockerHub
|
||||
uses: docker/login-action@v3.3.0
|
||||
with:
|
||||
@@ -111,12 +111,17 @@ jobs:
|
||||
images: |
|
||||
amir20/dozzle
|
||||
ghcr.io/amir20/dozzle
|
||||
tags: |
|
||||
type=raw,value=latest
|
||||
type=semver,pattern={{version}},prefix=v
|
||||
type=semver,pattern={{major}}.{{minor}},prefix=v
|
||||
type=semver,pattern={{major}},prefix=v
|
||||
- name: Writing certs to file
|
||||
run: |
|
||||
echo "${{ secrets.TTL_KEY }}" > shared_key.pem
|
||||
echo "${{ secrets.TTL_CERT }}" > shared_cert.pem
|
||||
- name: Build and push
|
||||
uses: docker/build-push-action@v6.11.0
|
||||
uses: docker/build-push-action@v6.13.0
|
||||
with:
|
||||
push: true
|
||||
context: .
|
||||
|
||||
@@ -13,7 +13,7 @@ jobs:
|
||||
if: ${{ !github.event.repository.fork && !github.event.pull_request.head.repo.fork && (github.event_name == 'push' || github.event.pull_request.head.repo.full_name == 'amir20/dozzle') }}
|
||||
steps:
|
||||
- name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@6524bf65af31da8d45b59e8c27de4bd072b392f5 # v3.8.0
|
||||
uses: docker/setup-buildx-action@f7ce87c1d6bead3e36075b2ce75da1f6cc28aaca # v3.9.0
|
||||
- name: Login to DockerHub
|
||||
uses: docker/login-action@v3.3.0
|
||||
with:
|
||||
@@ -39,7 +39,7 @@ jobs:
|
||||
echo "${{ secrets.TTL_KEY }}" > shared_key.pem
|
||||
echo "${{ secrets.TTL_CERT }}" > shared_cert.pem
|
||||
- name: Build and push
|
||||
uses: docker/build-push-action@v6.11.0
|
||||
uses: docker/build-push-action@v6.13.0
|
||||
with:
|
||||
context: .
|
||||
push: true
|
||||
|
||||
@@ -77,7 +77,7 @@ jobs:
|
||||
- name: Setup Go
|
||||
uses: actions/setup-go@v5
|
||||
with:
|
||||
go-version: "1.23"
|
||||
go-version: "1.24"
|
||||
check-latest: true
|
||||
- name: Generate dependencies
|
||||
run: make fake_assets shared_key.pem shared_cert.pem
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
# Build assets
|
||||
FROM --platform=$BUILDPLATFORM node:23.6.0-alpine AS node
|
||||
FROM --platform=$BUILDPLATFORM node:23.8.0-alpine AS node
|
||||
|
||||
RUN corepack enable
|
||||
|
||||
@@ -22,7 +22,7 @@ COPY public ./public
|
||||
# Build assets
|
||||
RUN pnpm build
|
||||
|
||||
FROM --platform=$BUILDPLATFORM golang:1.23.4-alpine AS builder
|
||||
FROM --platform=$BUILDPLATFORM golang:1.24-alpine AS builder
|
||||
|
||||
# install gRPC dependencies
|
||||
RUN apk add --no-cache ca-certificates protoc protobuf-dev\
|
||||
|
||||
@@ -162,6 +162,7 @@ declare global {
|
||||
const until: typeof import('@vueuse/core')['until']
|
||||
const useActiveElement: typeof import('@vueuse/core')['useActiveElement']
|
||||
const useAnimate: typeof import('@vueuse/core')['useAnimate']
|
||||
const useAnnouncements: typeof import('./stores/announcements')['useAnnouncements']
|
||||
const useArrayDifference: typeof import('@vueuse/core')['useArrayDifference']
|
||||
const useArrayEvery: typeof import('@vueuse/core')['useArrayEvery']
|
||||
const useArrayFilter: typeof import('@vueuse/core')['useArrayFilter']
|
||||
@@ -192,6 +193,7 @@ declare global {
|
||||
const useContainerActions: typeof import('./composable/containerActions')['useContainerActions']
|
||||
const useContainerStore: typeof import('./stores/container')['useContainerStore']
|
||||
const useContainerStream: typeof import('./composable/eventStreams')['useContainerStream']
|
||||
const useCountdown: typeof import('@vueuse/core')['useCountdown']
|
||||
const useCounter: typeof import('@vueuse/core')['useCounter']
|
||||
const useCssModule: typeof import('vue')['useCssModule']
|
||||
const useCssVar: typeof import('@vueuse/core')['useCssVar']
|
||||
@@ -287,7 +289,6 @@ declare global {
|
||||
const useProfileStorage: typeof import('./composable/profileStorage')['useProfileStorage']
|
||||
const useRafFn: typeof import('@vueuse/core')['useRafFn']
|
||||
const useRefHistory: typeof import('@vueuse/core')['useRefHistory']
|
||||
const useReleases: typeof import('./stores/releases')['useReleases']
|
||||
const useResizeObserver: typeof import('@vueuse/core')['useResizeObserver']
|
||||
const useRoute: typeof import('vue-router')['useRoute']
|
||||
const useRouter: typeof import('vue-router')['useRouter']
|
||||
@@ -551,6 +552,7 @@ declare module 'vue' {
|
||||
readonly until: UnwrapRef<typeof import('@vueuse/core')['until']>
|
||||
readonly useActiveElement: UnwrapRef<typeof import('@vueuse/core')['useActiveElement']>
|
||||
readonly useAnimate: UnwrapRef<typeof import('@vueuse/core')['useAnimate']>
|
||||
readonly useAnnouncements: UnwrapRef<typeof import('./stores/announcements')['useAnnouncements']>
|
||||
readonly useArrayDifference: UnwrapRef<typeof import('@vueuse/core')['useArrayDifference']>
|
||||
readonly useArrayEvery: UnwrapRef<typeof import('@vueuse/core')['useArrayEvery']>
|
||||
readonly useArrayFilter: UnwrapRef<typeof import('@vueuse/core')['useArrayFilter']>
|
||||
@@ -581,6 +583,7 @@ declare module 'vue' {
|
||||
readonly useContainerActions: UnwrapRef<typeof import('./composable/containerActions')['useContainerActions']>
|
||||
readonly useContainerStore: UnwrapRef<typeof import('./stores/container')['useContainerStore']>
|
||||
readonly useContainerStream: UnwrapRef<typeof import('./composable/eventStreams')['useContainerStream']>
|
||||
readonly useCountdown: UnwrapRef<typeof import('@vueuse/core')['useCountdown']>
|
||||
readonly useCounter: UnwrapRef<typeof import('@vueuse/core')['useCounter']>
|
||||
readonly useCssModule: UnwrapRef<typeof import('vue')['useCssModule']>
|
||||
readonly useCssVar: UnwrapRef<typeof import('@vueuse/core')['useCssVar']>
|
||||
@@ -676,7 +679,6 @@ declare module 'vue' {
|
||||
readonly useProfileStorage: UnwrapRef<typeof import('./composable/profileStorage')['useProfileStorage']>
|
||||
readonly useRafFn: UnwrapRef<typeof import('@vueuse/core')['useRafFn']>
|
||||
readonly useRefHistory: UnwrapRef<typeof import('@vueuse/core')['useRefHistory']>
|
||||
readonly useReleases: UnwrapRef<typeof import('./stores/releases')['useReleases']>
|
||||
readonly useResizeObserver: UnwrapRef<typeof import('@vueuse/core')['useResizeObserver']>
|
||||
readonly useRoute: UnwrapRef<typeof import('vue-router')['useRoute']>
|
||||
readonly useRouter: UnwrapRef<typeof import('vue-router')['useRouter']>
|
||||
|
||||
@@ -7,10 +7,12 @@ export {}
|
||||
/* prettier-ignore */
|
||||
declare module 'vue' {
|
||||
export interface GlobalComponents {
|
||||
Announcements: typeof import('./components/Announcements.vue')['default']
|
||||
'Carbon:caretDown': typeof import('~icons/carbon/caret-down')['default']
|
||||
'Carbon:circleSolid': typeof import('~icons/carbon/circle-solid')['default']
|
||||
'Carbon:copyFile': typeof import('~icons/carbon/copy-file')['default']
|
||||
'Carbon:information': typeof import('~icons/carbon/information')['default']
|
||||
'Carbon:logoKubernetes': typeof import('~icons/carbon/logo-kubernetes')['default']
|
||||
'Carbon:macShift': typeof import('~icons/carbon/mac-shift')['default']
|
||||
'Carbon:play': typeof import('~icons/carbon/play')['default']
|
||||
'Carbon:restart': typeof import('~icons/carbon/restart')['default']
|
||||
@@ -39,6 +41,7 @@ declare module 'vue' {
|
||||
EventSource: typeof import('./components/LogViewer/EventSource.vue')['default']
|
||||
FuzzySearchModal: typeof import('./components/FuzzySearchModal.vue')['default']
|
||||
GroupedLog: typeof import('./components/GroupedViewer/GroupedLog.vue')['default']
|
||||
GroupMenu: typeof import('./components/GroupMenu.vue')['default']
|
||||
HostIcon: typeof import('./components/common/HostIcon.vue')['default']
|
||||
HostList: typeof import('./components/HostList.vue')['default']
|
||||
HostLog: typeof import('./components/HostViewer/HostLog.vue')['default']
|
||||
@@ -99,7 +102,6 @@ declare module 'vue' {
|
||||
'Ph:stackSimple': typeof import('~icons/ph/stack-simple')['default']
|
||||
Popup: typeof import('./components/Popup.vue')['default']
|
||||
RandomColorTag: typeof import('./components/LogViewer/RandomColorTag.vue')['default']
|
||||
Releases: typeof import('./components/Releases.vue')['default']
|
||||
RouterLink: typeof import('vue-router')['RouterLink']
|
||||
RouterView: typeof import('vue-router')['RouterView']
|
||||
ScrollableView: typeof import('./components/ScrollableView.vue')['default']
|
||||
@@ -119,6 +121,7 @@ declare module 'vue' {
|
||||
SwarmMenu: typeof import('./components/SwarmMenu.vue')['default']
|
||||
Tag: typeof import('./components/common/Tag.vue')['default']
|
||||
TimedButton: typeof import('./components/common/TimedButton.vue')['default']
|
||||
ToastModal: typeof import('./components/common/ToastModal.vue')['default']
|
||||
Toggle: typeof import('./components/common/Toggle.vue')['default']
|
||||
ViewerWithSource: typeof import('./components/LogViewer/ViewerWithSource.vue')['default']
|
||||
ZigZag: typeof import('./components/LogViewer/ZigZag.vue')['default']
|
||||
|
||||
@@ -0,0 +1,115 @@
|
||||
<template>
|
||||
<Dropdown class="dropdown-end" @closed="releaseSeen = mostRecent?.tag ?? config.version">
|
||||
<template #trigger>
|
||||
<mdi:announcement class="size-6 -rotate-12" />
|
||||
<template v-if="announcements.length > 0 && releaseSeen != mostRecent?.tag">
|
||||
<span class="bg-red absolute top-0 right-px size-2 animate-ping rounded-full opacity-75"></span>
|
||||
<span class="bg-red absolute top-0 right-px size-2 rounded-full"></span>
|
||||
</template>
|
||||
</template>
|
||||
<template #content>
|
||||
<div class="w-72">
|
||||
<ul class="space-y-4 p-2">
|
||||
<li v-for="release in announcements" v-if="announcements.length > 0">
|
||||
<template v-if="release.announcement">
|
||||
<div class="flex items-baseline gap-1">
|
||||
<carbon:information class="text-info self-center" />
|
||||
<a
|
||||
:href="release.htmlUrl"
|
||||
class="link-primary text-lg font-bold"
|
||||
target="_blank"
|
||||
rel="noreferrer noopener"
|
||||
>
|
||||
{{ release.name }}
|
||||
</a>
|
||||
<span class="ml-1 text-xs"><distance-time :date="release.createdAt" /></span>
|
||||
</div>
|
||||
<div class="text-base-content/80 text-sm">
|
||||
{{ release.body }}
|
||||
</div>
|
||||
</template>
|
||||
<template v-else>
|
||||
<div class="flex items-baseline gap-1">
|
||||
<carbon:warning class="stroke-orange self-center" v-if="release.breaking > 0" />
|
||||
<a
|
||||
:href="release.htmlUrl"
|
||||
class="link-primary text-lg font-bold"
|
||||
target="_blank"
|
||||
rel="noreferrer noopener"
|
||||
>
|
||||
{{ release.name }}
|
||||
</a>
|
||||
<span class="ml-1 text-xs"><distance-time :date="release.createdAt" /></span>
|
||||
<Tag class="bg-red ml-auto px-1 py-1 text-xs" v-if="release.latest">
|
||||
{{ $t("releases.latest") }}
|
||||
</Tag>
|
||||
</div>
|
||||
<div class="text-base-content/80 text-sm">
|
||||
{{ summary(release) }}
|
||||
</div>
|
||||
</template>
|
||||
</li>
|
||||
<li v-else>
|
||||
<div class="text-base-content/80 text-sm">
|
||||
{{ $t("releases.no_releases") }}
|
||||
</div>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</template>
|
||||
</Dropdown>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { useAnnouncements } from "@/stores/announcements";
|
||||
|
||||
const { announcements, mostRecent } = useAnnouncements();
|
||||
const { t } = useI18n();
|
||||
|
||||
const releaseSeen = useProfileStorage("releaseSeen", config.version);
|
||||
|
||||
function summary(release: { features: number; bugFixes: number; breaking: number }) {
|
||||
if (release.features > 0 && release.bugFixes > 0 && release.breaking > 0) {
|
||||
return t("releases.three_parts", {
|
||||
first: t("releases.breaking", { count: release.breaking }),
|
||||
second: t("releases.features", { count: release.features }),
|
||||
third: t("releases.bugFixes", { count: release.bugFixes }),
|
||||
});
|
||||
}
|
||||
|
||||
if (release.features > 0 && release.bugFixes > 0) {
|
||||
return t("releases.two_parts", {
|
||||
first: t("releases.features", { count: release.features }),
|
||||
second: t("releases.bugFixes", { count: release.bugFixes }),
|
||||
});
|
||||
}
|
||||
|
||||
if (release.features > 0 && release.breaking > 0) {
|
||||
return t("releases.two_parts", {
|
||||
first: t("releases.features", { count: release.features }),
|
||||
second: t("releases.breaking", { count: release.breaking }),
|
||||
});
|
||||
}
|
||||
|
||||
if (release.bugFixes > 0 && release.breaking > 0) {
|
||||
return t("releases.two_parts", {
|
||||
first: t("releases.bugFixes", { count: release.bugFixes }),
|
||||
second: t("releases.breaking", { count: release.breaking }),
|
||||
});
|
||||
}
|
||||
|
||||
if (release.features > 0) {
|
||||
return t("releases.features", { count: release.features });
|
||||
}
|
||||
|
||||
if (release.bugFixes > 0) {
|
||||
return t("releases.bugFixes", { count: release.bugFixes });
|
||||
}
|
||||
|
||||
if (release.breaking > 0) {
|
||||
return t("releases.breaking", { count: release.breaking });
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped></style>
|
||||
@@ -169,7 +169,7 @@ const downloadParams = computed(() =>
|
||||
|
||||
const downloadUrl = computed(() =>
|
||||
withBase(
|
||||
`/api/containers/${container.host}:${container.id}/download?${new URLSearchParams(downloadParams.value).toString()}`,
|
||||
`/api/containers/${container.host}~${container.id}/download?${new URLSearchParams(downloadParams.value).toString()}`,
|
||||
),
|
||||
);
|
||||
|
||||
|
||||
@@ -0,0 +1,29 @@
|
||||
<template>
|
||||
<ul class="menu w-full p-0 text-[0.95rem]">
|
||||
<li v-if="customGroups.length > 0">
|
||||
<details open>
|
||||
<summary class="text-base-content/80 font-light">
|
||||
<ph:bounding-box-fill />
|
||||
{{ $t("label.group-menu") }}
|
||||
</summary>
|
||||
<ul>
|
||||
<li v-for="group in customGroups" :key="group.name">
|
||||
<router-link :to="{ name: '/group/[name]', params: { name: group.name } }" active-class="menu-active">
|
||||
<ph:stack-simple />
|
||||
<div class="truncate">
|
||||
{{ group.name }}
|
||||
</div>
|
||||
</router-link>
|
||||
</li>
|
||||
</ul>
|
||||
</details>
|
||||
</li>
|
||||
</ul>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
const store = useSwarmStore();
|
||||
|
||||
const { customGroups } = storeToRefs(store);
|
||||
</script>
|
||||
<style scoped></style>
|
||||
@@ -31,7 +31,8 @@
|
||||
</ul>
|
||||
<ul class="flex flex-row flex-wrap gap-x-2 text-sm md:gap-3">
|
||||
<li class="flex items-center gap-1">
|
||||
<octicon:container-24 class="inline-block" /> {{ $t("label.container", hostContainers[host.id]?.length) }}
|
||||
<octicon:container-24 class="inline-block" />
|
||||
{{ $t("label.container", hostContainers[host.id]?.length ?? 0) }}
|
||||
</li>
|
||||
<li class="flex items-center gap-1"><mdi:docker class="inline-block" /> {{ host.dockerVersion }}</li>
|
||||
</ul>
|
||||
@@ -107,7 +108,10 @@ useIntervalFn(
|
||||
stat.totalCPU += container.stat.cpu;
|
||||
stat.totalMem += container.stat.memoryUsage;
|
||||
}
|
||||
weightedStats[host].mostRecent = stat;
|
||||
if (weightedStats[host]) {
|
||||
// TODO fix this init
|
||||
weightedStats[host].mostRecent = stat;
|
||||
}
|
||||
}
|
||||
},
|
||||
1000,
|
||||
|
||||
@@ -1,20 +1,7 @@
|
||||
<template>
|
||||
<div class="flex items-center justify-end gap-4">
|
||||
<slot name="more-items"></slot>
|
||||
<Dropdown class="dropdown-end" @closed="latestTag = latest?.tag ?? config.version">
|
||||
<template #trigger>
|
||||
<mdi:announcement class="size-6 -rotate-12" />
|
||||
<span
|
||||
class="bg-red absolute top-0 right-px size-2 rounded-full"
|
||||
v-if="hasUpdate && latestTag != latest?.tag"
|
||||
></span>
|
||||
</template>
|
||||
<template #content>
|
||||
<div class="w-72">
|
||||
<Releases />
|
||||
</div>
|
||||
</template>
|
||||
</Dropdown>
|
||||
<Announcements />
|
||||
|
||||
<router-link
|
||||
:to="{ name: '/settings' }"
|
||||
@@ -58,7 +45,4 @@ async function logout() {
|
||||
|
||||
location.reload();
|
||||
}
|
||||
|
||||
const { hasUpdate, latest } = useReleases();
|
||||
const latestTag = useProfileStorage("releaseSeen", config.version);
|
||||
</script>
|
||||
|
||||
@@ -2,80 +2,14 @@
|
||||
<LogItem :logEntry>
|
||||
<div class="whitespace-pre-wrap" :data-event="logEntry.event" v-html="logEntry.message"></div>
|
||||
</LogItem>
|
||||
|
||||
<div
|
||||
class="alert alert-info my-4 w-auto flex-none font-sans text-[1rem] md:mx-auto md:w-1/2"
|
||||
v-if="followEligible && showCard"
|
||||
>
|
||||
<carbon:information class="size-6 shrink-0 stroke-current" />
|
||||
<div>
|
||||
<h3 class="text-lg font-bold">{{ $t("alert.similar-container-found.title") }}</h3>
|
||||
{{ $t("alert.similar-container-found.message", { containerId: nextContainer.id }) }}
|
||||
</div>
|
||||
<div>
|
||||
<TimedButton
|
||||
v-if="automaticRedirect"
|
||||
class="btn-primary btn-sm"
|
||||
@finished="redirectNow()"
|
||||
@click="showCard = false"
|
||||
>
|
||||
{{ $t("button.cancel") }}
|
||||
</TimedButton>
|
||||
<router-link
|
||||
:to="{ name: '/container/[id]', params: { id: nextContainer.id } }"
|
||||
class="btn btn-primary btn-sm"
|
||||
v-else
|
||||
>
|
||||
{{ $t("button.redirect") }}
|
||||
</router-link>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
import { ContainerEventLogEntry } from "@/models/LogEntry";
|
||||
const router = useRouter();
|
||||
const { showToast } = useToast();
|
||||
const { t } = useI18n();
|
||||
|
||||
const { logEntry } = defineProps<{
|
||||
logEntry: ContainerEventLogEntry;
|
||||
showContainerName?: boolean;
|
||||
}>();
|
||||
|
||||
const showCard = ref(true);
|
||||
const { containers } = useLoggingContext();
|
||||
const store = useContainerStore();
|
||||
const { containers: allContainers } = storeToRefs(store);
|
||||
|
||||
const nextContainer = computed(
|
||||
() =>
|
||||
[
|
||||
...allContainers.value.filter(
|
||||
(c) => c.created > containers.value[0].created && c.name === containers.value[0].name,
|
||||
),
|
||||
].sort((a, b) => +a.created - +b.created)[0],
|
||||
);
|
||||
|
||||
const followEligible = computed(
|
||||
() =>
|
||||
router.currentRoute.value.name === "/container/[id]" && // we are on a container page
|
||||
logEntry.event === "container-stopped" && // container was stopped
|
||||
containers.value.length === 1 && // only one container
|
||||
Date.now() - +logEntry.date < 5 * 60 * 1000 && // was stopped in the last 5 minutes
|
||||
nextContainer.value !== undefined, // there is a next container
|
||||
);
|
||||
|
||||
function redirectNow() {
|
||||
showToast(
|
||||
{
|
||||
title: t("alert.redirected.title"),
|
||||
message: t("alert.redirected.message", { containerId: nextContainer.value?.id }),
|
||||
type: "info",
|
||||
},
|
||||
{ expire: 5000 },
|
||||
);
|
||||
router.push({ name: "/container/[id]", params: { id: nextContainer.value?.id } });
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
<div class="relative flex w-full items-start gap-x-2 group-[.compact]:items-stretch">
|
||||
<LogStd :std="logEntry.std" class="shrink-0 select-none" v-if="showStd" />
|
||||
<RandomColorTag class="shrink-0 select-none" :value="host.name" v-if="showHostname" />
|
||||
<RandomColorTag class="shrink-0 select-none" :value="container.name" v-if="showContainerName" />
|
||||
<RandomColorTag class="shrink-0 select-none" :value="container.name" v-if="showContainerName" truncateRight />
|
||||
<LogDate :date="logEntry.date" v-if="showTimestamp" class="shrink-0 select-none" />
|
||||
<LogLevel
|
||||
class="flex select-none"
|
||||
|
||||
@@ -97,7 +97,7 @@ const downloadParams = computed(() =>
|
||||
|
||||
const downloadUrl = computed(() =>
|
||||
withBase(
|
||||
`/api/containers/${containers.value.map((c) => c.host + ":" + c.id).join(",")}/download?${new URLSearchParams(downloadParams.value).toString()}`,
|
||||
`/api/containers/${containers.value.map((c) => c.host + "~" + c.id).join(",")}/download?${new URLSearchParams(downloadParams.value).toString()}`,
|
||||
),
|
||||
);
|
||||
</script>
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
<template>
|
||||
<div class="tag grid w-40 overflow-hidden rounded-sm text-center text-sm text-white">
|
||||
<div class="random-color col-start-1 row-start-1 brightness-75"></div>
|
||||
<div class="col-start-1 row-start-1 truncate px-2 brightness-100 [direction:rtl]">{{ value }}</div>
|
||||
<div class="col-start-1 row-start-1 truncate px-2 brightness-100" :class="truncateRight ? '[direction:rtl]' : ''">
|
||||
{{ value }}
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<script lang="ts">
|
||||
@@ -29,8 +31,9 @@ const colors = [
|
||||
] as const;
|
||||
</script>
|
||||
<script lang="ts" setup>
|
||||
const { value } = defineProps<{
|
||||
const { value, truncateRight = false } = defineProps<{
|
||||
value: string;
|
||||
truncateRight?: boolean;
|
||||
}>();
|
||||
|
||||
const color = computed(() => colors[Math.abs(hashCode(value)) % colors.length]);
|
||||
|
||||
@@ -1,74 +0,0 @@
|
||||
<template>
|
||||
<ul class="space-y-4 p-2">
|
||||
<li v-for="release in releases" v-if="releases?.length">
|
||||
<div class="flex items-baseline gap-1">
|
||||
<carbon:warning class="stroke-orange self-center" v-if="release.breaking > 0" />
|
||||
<a :href="release.htmlUrl" class="link-primary text-lg font-bold" target="_blank" rel="noreferrer noopener">
|
||||
{{ release.name }}
|
||||
</a>
|
||||
<span class="ml-1 text-xs"><distance-time :date="new Date(release.createdAt)" /></span>
|
||||
<Tag class="bg-red ml-auto px-1 py-1 text-xs" v-if="release.tag === latest?.tag">
|
||||
{{ $t("releases.latest") }}
|
||||
</Tag>
|
||||
</div>
|
||||
<div class="text-base-content/80 text-sm">
|
||||
{{ summary(release) }}
|
||||
</div>
|
||||
</li>
|
||||
<li v-else>
|
||||
<div class="text-base-content/80 text-sm">
|
||||
{{ $t("releases.no_releases") }}
|
||||
</div>
|
||||
</li>
|
||||
</ul>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
const { releases, latest } = useReleases();
|
||||
const { t } = useI18n();
|
||||
|
||||
function summary(release: { features: number; bugFixes: number; breaking: number }) {
|
||||
if (release.features > 0 && release.bugFixes > 0 && release.breaking > 0) {
|
||||
return t("releases.three_parts", {
|
||||
first: t("releases.breaking", { count: release.breaking }),
|
||||
second: t("releases.features", { count: release.features }),
|
||||
third: t("releases.bugFixes", { count: release.bugFixes }),
|
||||
});
|
||||
}
|
||||
|
||||
if (release.features > 0 && release.bugFixes > 0) {
|
||||
return t("releases.two_parts", {
|
||||
first: t("releases.features", { count: release.features }),
|
||||
second: t("releases.bugFixes", { count: release.bugFixes }),
|
||||
});
|
||||
}
|
||||
|
||||
if (release.features > 0 && release.breaking > 0) {
|
||||
return t("releases.two_parts", {
|
||||
first: t("releases.features", { count: release.features }),
|
||||
second: t("releases.breaking", { count: release.breaking }),
|
||||
});
|
||||
}
|
||||
|
||||
if (release.bugFixes > 0 && release.breaking > 0) {
|
||||
return t("releases.two_parts", {
|
||||
first: t("releases.bugFixes", { count: release.bugFixes }),
|
||||
second: t("releases.breaking", { count: release.breaking }),
|
||||
});
|
||||
}
|
||||
|
||||
if (release.features > 0) {
|
||||
return t("releases.features", { count: release.features });
|
||||
}
|
||||
|
||||
if (release.bugFixes > 0) {
|
||||
return t("releases.bugFixes", { count: release.bugFixes });
|
||||
}
|
||||
|
||||
if (release.breaking > 0) {
|
||||
return t("releases.breaking", { count: release.breaking });
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped></style>
|
||||
@@ -1,10 +1,13 @@
|
||||
<template>
|
||||
<div v-if="ready" data-testid="side-menu" class="flex min-h-0 flex-col">
|
||||
<Carousel v-model="selectedCard" class="flex-1">
|
||||
<CarouselItem title="Hosts and Containers" id="host">
|
||||
<CarouselItem :title="$t('label.host-menu')" id="host">
|
||||
<HostMenu />
|
||||
</CarouselItem>
|
||||
<CarouselItem title="Services and Stacks" v-if="services.length > 0" id="swarm">
|
||||
<CarouselItem :title="$t('label.group-menu')" v-if="customGroups.length > 0" id="group">
|
||||
<GroupMenu />
|
||||
</CarouselItem>
|
||||
<CarouselItem :title="$t('label.swarm-menu')" v-if="services.length > 0" id="swarm">
|
||||
<SwarmMenu />
|
||||
</CarouselItem>
|
||||
</Carousel>
|
||||
@@ -20,16 +23,14 @@ const containerStore = useContainerStore();
|
||||
const { ready } = storeToRefs(containerStore);
|
||||
const route = useRoute();
|
||||
const swarmStore = useSwarmStore();
|
||||
const { services } = storeToRefs(swarmStore);
|
||||
const selectedCard = ref<"host" | "swarm">("host");
|
||||
const { services, customGroups } = storeToRefs(swarmStore);
|
||||
const selectedCard = ref<"host" | "swarm" | "group">("host");
|
||||
|
||||
watch(
|
||||
route,
|
||||
() => {
|
||||
if (route.meta.swarmMode) {
|
||||
selectedCard.value = "swarm";
|
||||
} else if (route.meta.containerMode) {
|
||||
selectedCard.value = "host";
|
||||
if (route.meta.menu && ["host", "swarm", "group"].includes(route.meta.menu as string)) {
|
||||
selectedCard.value = route.meta.menu as "host" | "swarm" | "group";
|
||||
}
|
||||
},
|
||||
{ immediate: true },
|
||||
|
||||
@@ -11,7 +11,7 @@
|
||||
{{ $t("label.container", stack.containers.length) }}
|
||||
</Tag>
|
||||
<Tag class="hidden font-mono max-md:hidden @3xl:block" size="small">
|
||||
{{ $t("label.serivce", stack.services.length) }}
|
||||
{{ $t("label.service", stack.services.length) }}
|
||||
</Tag>
|
||||
</div>
|
||||
<MultiContainerStat class="ml-auto" :containers="stack.containers" />
|
||||
|
||||
@@ -28,14 +28,14 @@
|
||||
</details>
|
||||
</li>
|
||||
|
||||
<li v-if="serivcesWithoutStacks.length > 0">
|
||||
<li v-if="servicesWithoutStacks.length > 0">
|
||||
<details open>
|
||||
<summary class="text-base-content/80 font-light">
|
||||
<ph:circles-four />
|
||||
{{ $t("label.services") }}
|
||||
</summary>
|
||||
<ul>
|
||||
<li v-for="service in serivcesWithoutStacks" :key="service.name">
|
||||
<li v-for="service in servicesWithoutStacks" :key="service.name">
|
||||
<router-link :to="{ name: '/service/[name]', params: { name: service.name } }" active-class="menu-active">
|
||||
<ph:stack-simple />
|
||||
<div class="truncate">
|
||||
@@ -46,37 +46,13 @@
|
||||
</ul>
|
||||
</details>
|
||||
</li>
|
||||
|
||||
<li v-if="customGroups.length > 0">
|
||||
<details open>
|
||||
<summary class="text-base-content/80 font-light">
|
||||
<ph:bounding-box-fill />
|
||||
{{ $t("label.custom-groups") }}
|
||||
</summary>
|
||||
<ul>
|
||||
<li v-for="group in customGroups" :key="group.name">
|
||||
<router-link :to="{ name: '/group/[name]', params: { name: group.name } }" active-class="menu-active">
|
||||
<ph:stack-simple />
|
||||
<div class="truncate">
|
||||
{{ group.name }}
|
||||
</div>
|
||||
</router-link>
|
||||
</li>
|
||||
</ul>
|
||||
</details>
|
||||
</li>
|
||||
</ul>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
const store = useSwarmStore();
|
||||
|
||||
const { stacks, services, customGroups } = storeToRefs(store);
|
||||
const { stacks, services } = storeToRefs(store);
|
||||
|
||||
const serivcesWithoutStacks = computed(() => services.value.filter((service) => !service.stack));
|
||||
const servicesWithoutStacks = computed(() => services.value.filter((service) => !service.stack));
|
||||
</script>
|
||||
<style scoped>
|
||||
.menu {
|
||||
@apply text-[0.95rem];
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -46,6 +46,16 @@ const scrollToItem = (index: number) => {
|
||||
};
|
||||
|
||||
const { pause, resume } = watchPausable(activeId, (v) => {
|
||||
if (activeId.value) {
|
||||
const index = cards.value?.map((c) => c.id).indexOf(activeId.value) ?? -1;
|
||||
if (index !== -1) {
|
||||
console.log("watching", activeId.value);
|
||||
scrollToItem(index);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
watchOnce(cards, () => {
|
||||
if (activeId.value) {
|
||||
const index = cards.value?.map((c) => c.id).indexOf(activeId.value) ?? -1;
|
||||
if (index !== -1) {
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
<mdi:satellite-variant v-if="type == 'agent'" />
|
||||
<ph:globe-simple v-else-if="type == 'remote'" />
|
||||
<mdi:hexagon-multiple v-else-if="type == 'swarm'" />
|
||||
<carbon:logo-kubernetes v-else-if="type == 'k8s'" />
|
||||
<ph:computer-tower v-else />
|
||||
</template>
|
||||
<script setup lang="ts">
|
||||
|
||||
@@ -0,0 +1,42 @@
|
||||
<template>
|
||||
<div class="toast toast-end whitespace-normal max-md:m-0 max-md:w-full">
|
||||
<div
|
||||
class="alert max-w-xl max-md:rounded-none"
|
||||
v-for="{ toast, options: { timed } } in toasts"
|
||||
:key="toast.id"
|
||||
:class="{
|
||||
'alert-error': toast.type === 'error',
|
||||
'alert-info': toast.type === 'info',
|
||||
'alert-warning': toast.type === 'warning',
|
||||
}"
|
||||
>
|
||||
<carbon:information class="size-6 shrink-0 stroke-current" v-if="toast.type === 'info'" />
|
||||
<carbon:warning class="size-6 shrink-0 stroke-current" v-else-if="toast.type === 'error'" />
|
||||
<carbon:warning class="size-6 shrink-0 stroke-current" v-else-if="toast.type === 'warning'" />
|
||||
<div>
|
||||
<h3 class="text-lg font-bold" v-if="toast.title">{{ toast.title }}</h3>
|
||||
<div v-html="toast.message" class="[&>a]:underline"></div>
|
||||
</div>
|
||||
<div>
|
||||
<TimedButton
|
||||
v-if="timed"
|
||||
class="btn-primary btn-sm"
|
||||
:duration="timed"
|
||||
@finished="
|
||||
removeToast(toast.id);
|
||||
toast.action?.handler();
|
||||
"
|
||||
@cancelled="removeToast(toast.id)"
|
||||
>
|
||||
{{ toast.action?.label }}
|
||||
</TimedButton>
|
||||
<button class="btn btn-circle btn-xs" @click="removeToast(toast.id)" v-else>
|
||||
<mdi:close />
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
const { toasts, removeToast } = useToast();
|
||||
</script>
|
||||
@@ -4,36 +4,55 @@ type Toast = {
|
||||
title?: string;
|
||||
message: string;
|
||||
type: "success" | "error" | "warning" | "info";
|
||||
action?: {
|
||||
label: string;
|
||||
handler: () => void;
|
||||
};
|
||||
};
|
||||
|
||||
type ToastOptions = {
|
||||
expire?: number;
|
||||
once?: boolean;
|
||||
timed?: number;
|
||||
};
|
||||
|
||||
const toasts = ref<Toast[]>([]);
|
||||
const toasts = ref<
|
||||
{
|
||||
toast: Toast;
|
||||
options: ToastOptions;
|
||||
}[]
|
||||
>([]);
|
||||
|
||||
const showToast = (
|
||||
toast: Omit<Toast, "id" | "createdAt"> & { id?: string },
|
||||
{ expire = -1, once = false }: ToastOptions = { expire: -1, once: false },
|
||||
{ expire = -1, once = false, timed }: ToastOptions = { expire: -1, once: false },
|
||||
) => {
|
||||
if (once && toasts.value.some((t) => t.id === toast.id)) {
|
||||
if (once && !toast.id) {
|
||||
throw new Error("Toast id is required when once is true");
|
||||
}
|
||||
if (once && toasts.value.some((t) => t.toast.id === toast.id)) {
|
||||
return;
|
||||
}
|
||||
toasts.value.push({
|
||||
|
||||
const toastWithId = {
|
||||
id: Date.now().toString(),
|
||||
createdAt: new Date(),
|
||||
...toast,
|
||||
createdAt: new Date(),
|
||||
};
|
||||
toasts.value.push({
|
||||
toast: toastWithId,
|
||||
options: { expire, once, timed },
|
||||
});
|
||||
|
||||
if (expire > 0) {
|
||||
setTimeout(() => {
|
||||
removeToast(toasts.value[0].id);
|
||||
removeToast(toastWithId.id);
|
||||
}, expire);
|
||||
}
|
||||
};
|
||||
|
||||
const removeToast = (id: Toast["id"]) => {
|
||||
toasts.value = toasts.value.filter((toast) => toast.id !== id);
|
||||
toasts.value = toasts.value.filter((instance) => instance.toast.id !== id);
|
||||
};
|
||||
|
||||
export const useToast = () => {
|
||||
|
||||
@@ -52,29 +52,7 @@
|
||||
<template #fallback> Loading dependencies... </template>
|
||||
</Suspense>
|
||||
</SideDrawer>
|
||||
<div class="toast toast-end whitespace-normal">
|
||||
<div
|
||||
class="alert max-w-xl"
|
||||
v-for="toast in toasts"
|
||||
:key="toast.id"
|
||||
:class="{
|
||||
'alert-error': toast.type === 'error',
|
||||
'alert-info': toast.type === 'info',
|
||||
'alert-warning': toast.type === 'warning',
|
||||
}"
|
||||
>
|
||||
<carbon:information class="size-6 shrink-0 stroke-current" v-if="toast.type === 'info'" />
|
||||
<carbon:warning class="size-6 shrink-0 stroke-current" v-else-if="toast.type === 'error'" />
|
||||
<carbon:warning class="size-6 shrink-0 stroke-current" v-else-if="toast.type === 'warning'" />
|
||||
<div>
|
||||
<h3 class="text-lg font-bold" v-if="toast.title">{{ toast.title }}</h3>
|
||||
<div v-html="toast.message" class="[&>a]:underline"></div>
|
||||
</div>
|
||||
<div>
|
||||
<button class="btn btn-circle btn-xs" @click="removeToast(toast.id)"><mdi:close /></button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<ToastModal />
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
@@ -86,7 +64,6 @@ import SideDrawer from "@/components/common/SideDrawer.vue";
|
||||
const pinnedLogsStore = usePinnedLogsStore();
|
||||
const { pinnedLogs } = storeToRefs(pinnedLogsStore);
|
||||
|
||||
const { toasts, removeToast } = useToast();
|
||||
const drawer = useTemplateRef<InstanceType<typeof SideDrawer>>("drawer") as Ref<InstanceType<typeof SideDrawer>>;
|
||||
const { component: drawerComponent, properties: drawerProperties, width: drawerWidth } = createDrawer(drawer);
|
||||
|
||||
|
||||
@@ -107,7 +107,7 @@
|
||||
@utility menu-active {
|
||||
&,
|
||||
&:active {
|
||||
--color-neutral: var(--color-primary);
|
||||
--menu-active-bg: var(--color-primary);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -11,15 +11,18 @@
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { type Container } from "@/models/Container";
|
||||
const route = useRoute("/container/[id]");
|
||||
const id = toRef(() => route.params.id);
|
||||
|
||||
const containerStore = useContainerStore();
|
||||
const currentContainer = containerStore.currentContainer(id);
|
||||
const { ready } = storeToRefs(containerStore);
|
||||
|
||||
const pinnedLogsStore = usePinnedLogsStore();
|
||||
const { pinnedLogs } = storeToRefs(pinnedLogsStore);
|
||||
const { containers: allContainers } = storeToRefs(containerStore) as unknown as { containers: Ref<Container[]> };
|
||||
const { showToast } = useToast();
|
||||
const { t } = useI18n();
|
||||
const router = useRouter();
|
||||
|
||||
watchEffect(() => {
|
||||
if (ready.value) {
|
||||
@@ -30,8 +33,50 @@ watchEffect(() => {
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
const redirectTrigger = ref(false);
|
||||
watch(currentContainer, () => (redirectTrigger.value = false));
|
||||
|
||||
watchEffect(() => {
|
||||
if (redirectTrigger.value) return;
|
||||
if (!currentContainer.value) return;
|
||||
if (currentContainer.value.state === "running") return;
|
||||
if (Date.now() - +currentContainer.value.finishedAt > 5 * 60 * 1000) return;
|
||||
|
||||
const nextContainer = allContainers.value
|
||||
.filter((c) => c.startedAt > currentContainer.value.startedAt && c.name === currentContainer.value.name)
|
||||
.sort((a, b) => +a.created - +b.created)[0];
|
||||
|
||||
if (!nextContainer) return;
|
||||
|
||||
if (automaticRedirect.value) {
|
||||
redirectTrigger.value = true;
|
||||
showToast(
|
||||
{
|
||||
title: t("alert.similar-container-found.title"),
|
||||
message: t("alert.similar-container-found.message", { containerId: nextContainer.id }),
|
||||
type: "info",
|
||||
action: {
|
||||
label: t("button.cancel"),
|
||||
handler: () => {
|
||||
showToast(
|
||||
{
|
||||
title: t("alert.redirected.title"),
|
||||
message: t("alert.redirected.message", { containerId: nextContainer.id }),
|
||||
type: "info",
|
||||
},
|
||||
{ expire: 5000 },
|
||||
);
|
||||
router.push({ name: "/container/[id]", params: { id: nextContainer.id } });
|
||||
},
|
||||
},
|
||||
},
|
||||
{ timed: 4000 },
|
||||
);
|
||||
}
|
||||
});
|
||||
</script>
|
||||
<route lang="yaml">
|
||||
meta:
|
||||
containerMode: true
|
||||
menu: host
|
||||
</route>
|
||||
|
||||
@@ -22,5 +22,5 @@ watchEffect(() => {
|
||||
</script>
|
||||
<route lang="yaml">
|
||||
meta:
|
||||
swarmMode: true
|
||||
menu: group
|
||||
</route>
|
||||
|
||||
@@ -26,5 +26,5 @@ watchEffect(() => {
|
||||
</script>
|
||||
<route lang="yaml">
|
||||
meta:
|
||||
containerMode: true
|
||||
menu: host
|
||||
</route>
|
||||
|
||||
@@ -22,5 +22,5 @@ watchEffect(() => {
|
||||
</script>
|
||||
<route lang="yaml">
|
||||
meta:
|
||||
containerMode: true
|
||||
menu: host
|
||||
</route>
|
||||
|
||||
@@ -28,5 +28,5 @@ watchEffect(() => {
|
||||
</script>
|
||||
<route lang="yaml">
|
||||
meta:
|
||||
swarmMode: true
|
||||
menu: swarm
|
||||
</route>
|
||||
|
||||
@@ -8,8 +8,8 @@
|
||||
<div>
|
||||
<span v-html="$t('settings.using-version', { version: config.version })"></span>
|
||||
<div
|
||||
v-if="hasUpdate"
|
||||
v-html="$t('settings.update-available', { nextVersion: latest?.name, href: latest?.htmlUrl })"
|
||||
v-if="hasRelease"
|
||||
v-html="$t('settings.update-available', { nextVersion: latestRelease?.name, href: latestRelease?.htmlUrl })"
|
||||
></div>
|
||||
</div>
|
||||
</section>
|
||||
@@ -154,7 +154,7 @@ import { availableLocales, i18n } from "@/modules/i18n";
|
||||
const { t } = useI18n();
|
||||
|
||||
setTitle(t("title.settings"));
|
||||
const { latest, hasUpdate } = useReleases();
|
||||
const { latestRelease, hasRelease } = useAnnouncements();
|
||||
|
||||
const now = new Date();
|
||||
const hoursAgo = (hours: number) => {
|
||||
|
||||
@@ -28,5 +28,5 @@ watchEffect(() => {
|
||||
</script>
|
||||
<route lang="yaml">
|
||||
meta:
|
||||
swarmMode: true
|
||||
menu: swarm
|
||||
</route>
|
||||
|
||||
@@ -0,0 +1,37 @@
|
||||
type Announcement = {
|
||||
name: string;
|
||||
announcement: boolean;
|
||||
createdAt: Date;
|
||||
body: string;
|
||||
tag: string;
|
||||
htmlUrl: string;
|
||||
latest: boolean;
|
||||
mentionsCount: number;
|
||||
features: number;
|
||||
bugFixes: number;
|
||||
breaking: number;
|
||||
};
|
||||
|
||||
const { data: releases } = useFetch(withBase("/api/releases")).get().json<Announcement[]>();
|
||||
|
||||
const otherAnnouncements = [] as Announcement[];
|
||||
|
||||
const announcements = computed(() => {
|
||||
const newReleases =
|
||||
releases.value?.map((release) => ({ ...release, createdAt: new Date(release.createdAt), announcement: false })) ??
|
||||
[];
|
||||
return [...newReleases, ...otherAnnouncements].sort((a, b) => b.createdAt.getTime() - a.createdAt.getTime());
|
||||
});
|
||||
|
||||
const mostRecent = computed(() => announcements.value?.[0]);
|
||||
const latestRelease = computed(() => announcements.value?.find((release) => release.latest && !release.announcement));
|
||||
const hasRelease = computed(() => latestRelease.value !== undefined);
|
||||
|
||||
export function useAnnouncements() {
|
||||
return {
|
||||
mostRecent,
|
||||
announcements,
|
||||
latestRelease,
|
||||
hasRelease,
|
||||
};
|
||||
}
|
||||
@@ -3,7 +3,7 @@ export type Host = {
|
||||
name: string;
|
||||
nCPU: number;
|
||||
memTotal: number;
|
||||
type: "agent" | "local" | "remote" | "swarm";
|
||||
type: "agent" | "local" | "remote" | "swarm" | "k8s";
|
||||
endpoint: string;
|
||||
available: boolean;
|
||||
dockerVersion: string;
|
||||
|
||||
@@ -1,29 +0,0 @@
|
||||
const { data: releases } = useFetch(withBase("/api/releases")).get().json<
|
||||
{
|
||||
name: string;
|
||||
mentionsCount: number;
|
||||
createdAt: string;
|
||||
body: string;
|
||||
tag: string;
|
||||
htmlUrl: string;
|
||||
latest: boolean;
|
||||
features: number;
|
||||
bugFixes: number;
|
||||
breaking: number;
|
||||
}[]
|
||||
>();
|
||||
|
||||
const hasUpdate = computed(() => {
|
||||
if (!releases.value?.length) return false;
|
||||
return releases.value[0].tag !== config.version;
|
||||
});
|
||||
|
||||
const latest = computed(() => releases.value?.find((release) => release.latest));
|
||||
|
||||
export function useReleases() {
|
||||
return {
|
||||
hasUpdate,
|
||||
latest,
|
||||
releases,
|
||||
};
|
||||
}
|
||||
@@ -30,6 +30,7 @@ services:
|
||||
- DOZZLE_FILTER=name=dozzle
|
||||
- DOZZLE_NO_ANALYTICS=1
|
||||
- DOZZLE_HOSTNAME=localhost
|
||||
- DOZZLE_LEVEL=debug
|
||||
ports:
|
||||
- 7070:8080
|
||||
build:
|
||||
@@ -94,7 +95,7 @@ services:
|
||||
|
||||
playwright:
|
||||
container_name: playwright
|
||||
image: mcr.microsoft.com/playwright:v1.49.1-jammy
|
||||
image: mcr.microsoft.com/playwright:v1.50.1-jammy
|
||||
working_dir: /app
|
||||
volumes:
|
||||
- .:/app
|
||||
|
||||
@@ -74,6 +74,7 @@ export default defineConfig({
|
||||
{ text: "Healthcheck", link: "/guide/healthcheck" },
|
||||
{ text: "Remote Hosts", link: "/guide/remote-hosts" },
|
||||
{ text: "Swarm Mode", link: "/guide/swarm-mode" },
|
||||
{ text: "K8s Mode", link: "/guide/k8s" },
|
||||
{ text: "Supported Env Vars", link: "/guide/supported-env-vars" },
|
||||
{ text: "Logging Files on Disk", link: "/guide/log-files-on-disk" },
|
||||
{ text: "SQL Engine", link: "/guide/sql-engine" },
|
||||
|
||||
@@ -0,0 +1,57 @@
|
||||
<template>
|
||||
<div
|
||||
class="banner fixed top-0 right-0 left-0 z-(--vp-z-index-layout-top) flex items-center overflow-hidden bg-[oklch(74%_0.16_232.661)] p-4 font-bold text-gray-800 dark:bg-[oklch(60%_0.126_221.723)] dark:text-white"
|
||||
>
|
||||
<div class="mx-auto flex items-center gap-2 lg:gap-4">
|
||||
<span class="animate-bounce">🚀</span>K8s users, I need your help! I have added K8s support to Dozzle and I need
|
||||
your feedback! 🙏
|
||||
<a
|
||||
href="https://github.com/amir20/dozzle/discussions/3614"
|
||||
target="_blank"
|
||||
class="btn btn-sm btn-primary text-white!"
|
||||
>
|
||||
See the discussion
|
||||
</a>
|
||||
</div>
|
||||
<button class="btn btn-circle btn-ghost btn-xs ml-auto" @click="dismiss">
|
||||
<svg class="swap-on fill-current" xmlns="http://www.w3.org/2000/svg" width="32" height="32" viewBox="0 0 512 512">
|
||||
<polygon
|
||||
points="400 145.49 366.51 112 256 222.51 145.49 112 112 145.49 222.51 256 112 366.51 145.49 400 256 289.49 366.51 400 400 366.51 289.49 256 400 145.49"
|
||||
/>
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
</template>
|
||||
<script setup>
|
||||
import { onMounted } from "vue";
|
||||
const dismiss = () => {
|
||||
localStorage.setItem("banner-dismissed", (Date.now() + 8.64e7 * 1).toString());
|
||||
document.documentElement.classList.add("banner-dismissed");
|
||||
};
|
||||
|
||||
onMounted(() => {
|
||||
if (new Date(+localStorage.getItem("banner-dismissed")) > Date.now()) {
|
||||
document.documentElement.classList.add("banner-dismissed");
|
||||
}
|
||||
});
|
||||
</script>
|
||||
<style>
|
||||
.banner-dismissed {
|
||||
--vp-layout-top-height: 0px !important;
|
||||
}
|
||||
@media (min-width: 375px) {
|
||||
html {
|
||||
--vp-layout-top-height: 100px;
|
||||
}
|
||||
}
|
||||
@media (min-width: 768px) {
|
||||
html {
|
||||
--vp-layout-top-height: 63px;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
<style scoped>
|
||||
.banner-dismissed .banner {
|
||||
display: none;
|
||||
}
|
||||
</style>
|
||||
@@ -1,14 +1,14 @@
|
||||
<template>
|
||||
<div class="stats mt-10">
|
||||
<div class="stats stats-horizontal mt-10">
|
||||
<div class="stat w-40 border-0!">
|
||||
<div class="stat-title">Github Stars</div>
|
||||
<div class="stat-title text-(--vp-c-text-1)">Github Stars</div>
|
||||
<div class="stat-value">
|
||||
<Counter :start="0" :end="data.stars" :duration="1000" :formatter="(value) => (value / 1e3).toFixed(1) + 'K'" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="stat">
|
||||
<div class="stat-title">Docker Pulls</div>
|
||||
<div class="stat-title text-(--vp-c-text-1)">Docker Pulls</div>
|
||||
<div class="stat-value">
|
||||
<Counter :start="0" :end="data.pulls" :duration="1000" :formatter="(value) => (value / 1e6).toFixed(0) + 'M'" />
|
||||
</div>
|
||||
|
||||
@@ -7,6 +7,7 @@ import "./style.css";
|
||||
import HeroVideo from "./components/HeroVideo.vue";
|
||||
import BuyMeCoffee from "./components/BuyMeCoffee.vue";
|
||||
import Stats from "./components/Stats.vue";
|
||||
import Banner from "./components/Banner.vue";
|
||||
|
||||
export default {
|
||||
...DefaultTheme,
|
||||
@@ -15,6 +16,7 @@ export default {
|
||||
"home-hero-image": () => h(HeroVideo),
|
||||
"sidebar-nav-after": () => h(BuyMeCoffee),
|
||||
"home-hero-actions-after": () => h(Stats),
|
||||
"layout-top": () => h(Banner),
|
||||
});
|
||||
},
|
||||
enhanceApp(ctx) {
|
||||
|
||||
@@ -1,6 +1,17 @@
|
||||
@import "tailwindcss";
|
||||
@variant dark (&:where(.dark, .dark *));
|
||||
|
||||
@plugin "daisyui" {
|
||||
include: stat;
|
||||
include: stat, button;
|
||||
themes: light --default;
|
||||
}
|
||||
@plugin "daisyui/theme" {
|
||||
name: "light";
|
||||
default: true;
|
||||
--depth: 0;
|
||||
--noise: 0;
|
||||
--radius-field: 2rem;
|
||||
--color-primary: var(--vp-button-brand-bg);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -7,6 +7,7 @@ export {}
|
||||
/* prettier-ignore */
|
||||
declare module 'vue' {
|
||||
export interface GlobalComponents {
|
||||
Banner: typeof import('./.vitepress/theme/components/Banner.vue')['default']
|
||||
BuyMeCoffee: typeof import('./.vitepress/theme/components/BuyMeCoffee.vue')['default']
|
||||
Counter: typeof import('./.vitepress/theme/components/Counter.vue')['default']
|
||||
HeroVideo: typeof import('./.vitepress/theme/components/HeroVideo.vue')['default']
|
||||
|
||||
@@ -47,9 +47,69 @@ networks:
|
||||
driver: overlay
|
||||
```
|
||||
|
||||
```yaml [k8s-dozzle.yml]
|
||||
# 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"]
|
||||
- apiGroups: ["metrics.k8s.io"]
|
||||
resources: ["pods"]
|
||||
verbs: ["get", "list"]
|
||||
---
|
||||
# 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
|
||||
ports:
|
||||
- containerPort: 8080
|
||||
env:
|
||||
- name: DOZZLE_MODE
|
||||
value: "k8s"
|
||||
```
|
||||
|
||||
:::
|
||||
|
||||
See [swarm mode](/guide/swarm-mode) for more information on running Dozzle in Swarm.
|
||||
See [swarm mode](/guide/swarm-mode) for more information on running Dozzle in Swarm and [Kubernetes](/guide/k8s) for running Dozzle in Kubernetes.
|
||||
|
||||
> [!TIP]
|
||||
> If Docker Hub is blocked in your network, you can use the [GitHub Container Registry](https://ghcr.io/amir20/dozzle:latest) to pull the image. Use `ghcr.io/amir20/dozzle:latest` instead of `amir20/dozzle:latest`.
|
||||
|
||||
@@ -0,0 +1,140 @@
|
||||
---
|
||||
title: Kubernetes Support
|
||||
---
|
||||
|
||||
# Kubernetes Support <Badge type="warning" text="beta" /> <Badge type="tip" text="v8.11.x" />
|
||||
|
||||
Dozzle now supports Kubernetes, allowing you to view logs from your Kubernetes pods. This feature is available in `v8.11` version of Dozzle.
|
||||
|
||||
## Kubernetes Setup
|
||||
|
||||
To set up Dozzle in Kubernetes, you can use the following YAML configuration using `DOZZLE_MODE=k8s`. This configuration includes a deployment and a service to expose Dozzle.
|
||||
|
||||
```yaml
|
||||
# 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"]
|
||||
- apiGroups: ["metrics.k8s.io"]
|
||||
resources: ["pods"]
|
||||
verbs: ["get", "list"]
|
||||
---
|
||||
# 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
|
||||
ports:
|
||||
- containerPort: 8080
|
||||
env:
|
||||
- name: DOZZLE_MODE
|
||||
value: "k8s"
|
||||
---
|
||||
# service.yaml
|
||||
apiVersion: v1
|
||||
kind: Service
|
||||
metadata:
|
||||
name: dozzle-service
|
||||
spec:
|
||||
type: ClusterIP
|
||||
selector:
|
||||
app: dozzle
|
||||
ports:
|
||||
- port: 8080
|
||||
targetPort: 8080
|
||||
protocol: TCP
|
||||
```
|
||||
|
||||
This configuration creates a service account, a cluster role, and a cluster role binding to allow Dozzle to access the necessary Kubernetes resources. It also creates a deployment for Dozzle and exposes it via a service.
|
||||
|
||||
All other features are supported as well, including authentication, filtering, and more. You can use the same environment variables as you would in Docker to configure Dozzle in Kubernetes.
|
||||
|
||||
> [!NOTE]
|
||||
> Dozzle in Kubernetes is a new feature and may have some limitations compared to the Docker version. Please use this [discussion](https://github.com/amir20/dozzle/discussions/3614) to report any issues or suggestions for improvement.
|
||||
|
||||
## Metrics API
|
||||
|
||||
Dozzle relies on the [Kubernetes Metrics API](https://github.com/kubernetes-sigs/metrics-server) to retrieve resource usage information. The API can be installed using the following command:
|
||||
|
||||
```bash
|
||||
kubectl apply -f https://github.com/kubernetes-sigs/metrics-server/releases/latest/download/components.yaml
|
||||
```
|
||||
|
||||
To verify that the API is running, you can run the following command:
|
||||
|
||||
```bash
|
||||
kubectl top pod
|
||||
```
|
||||
|
||||
For now this is required to use Dozzle in Kubernetes.
|
||||
|
||||
## Default Namespace
|
||||
|
||||
By default, Dozzle will monitor all namespaces in the cluster. If you want to restrict Dozzle to a specific namespace, you can set the `DOZZLE_NAMESPACE` environment variable to the name of the namespace.
|
||||
|
||||
```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
|
||||
ports:
|
||||
- containerPort: 8080
|
||||
env:
|
||||
- name: DOZZLE_MODE
|
||||
value: "k8s"
|
||||
- name: DOZZLE_NAMESPACE
|
||||
value: "default"
|
||||
```
|
||||
|
Before Width: | Height: | Size: 19 KiB After Width: | Height: | Size: 19 KiB |
|
Before Width: | Height: | Size: 17 KiB After Width: | Height: | Size: 17 KiB |
|
Before Width: | Height: | Size: 17 KiB After Width: | Height: | Size: 17 KiB |
|
Before Width: | Height: | Size: 16 KiB After Width: | Height: | Size: 16 KiB |
@@ -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
|
||||
@@ -0,0 +1,74 @@
|
||||
# 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"]
|
||||
- apiGroups: ["metrics.k8s.io"]
|
||||
resources: ["pods"]
|
||||
verbs: ["get", "list"]
|
||||
---
|
||||
# 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
|
||||
@@ -2,4 +2,4 @@ users:
|
||||
amir:
|
||||
email: ""
|
||||
name: Amir
|
||||
password: 5e884898da28047151d0e56f8dc6292773603d0d6aabbdd62a11ef721d1542d8
|
||||
password: $2a$11$nXizfBMJrSwfo4ofkS62denLyCKX0X7VsmfTbMyD3thTkmuGNp8.m
|
||||
|
||||
@@ -4,7 +4,7 @@ require (
|
||||
github.com/Microsoft/go-winio v0.6.2 // indirect
|
||||
github.com/alexflint/go-arg v1.5.1
|
||||
github.com/beme/abide v0.0.0-20190723115211-635a09831760
|
||||
github.com/docker/docker v27.4.1+incompatible
|
||||
github.com/docker/docker v27.5.1+incompatible
|
||||
github.com/docker/go-connections v0.5.0 // indirect
|
||||
github.com/docker/go-units v0.5.0 // indirect
|
||||
github.com/dustin/go-humanize v1.0.1
|
||||
@@ -18,24 +18,28 @@ require (
|
||||
github.com/spf13/afero v1.12.0
|
||||
github.com/stretchr/objx v0.5.2 // indirect
|
||||
github.com/stretchr/testify v1.10.0
|
||||
golang.org/x/net v0.33.0 // indirect
|
||||
golang.org/x/sys v0.29.0 // indirect
|
||||
golang.org/x/net v0.35.0 // indirect
|
||||
golang.org/x/sys v0.30.0 // indirect
|
||||
gopkg.in/yaml.v3 v3.0.1
|
||||
)
|
||||
|
||||
require (
|
||||
github.com/PuerkitoBio/goquery v1.10.1
|
||||
github.com/go-chi/chi/v5 v5.2.0
|
||||
github.com/PuerkitoBio/goquery v1.10.2
|
||||
github.com/go-chi/chi/v5 v5.2.1
|
||||
github.com/go-chi/jwtauth/v5 v5.3.2
|
||||
github.com/puzpuzpuz/xsync/v3 v3.4.0
|
||||
github.com/puzpuzpuz/xsync/v3 v3.5.1
|
||||
github.com/rs/zerolog v1.33.0
|
||||
github.com/samber/lo v1.47.0
|
||||
github.com/samber/lo v1.49.1
|
||||
github.com/wk8/go-ordered-map/v2 v2.1.8
|
||||
github.com/yuin/goldmark v1.7.8
|
||||
golang.org/x/crypto v0.32.0
|
||||
golang.org/x/sync v0.10.0
|
||||
google.golang.org/grpc v1.69.2
|
||||
google.golang.org/protobuf v1.36.2
|
||||
golang.org/x/crypto v0.33.0
|
||||
golang.org/x/sync v0.11.0
|
||||
google.golang.org/grpc v1.70.0
|
||||
google.golang.org/protobuf v1.36.5
|
||||
k8s.io/api v0.32.2
|
||||
k8s.io/apimachinery v0.32.2
|
||||
k8s.io/client-go v0.32.2
|
||||
k8s.io/metrics v0.32.2
|
||||
)
|
||||
|
||||
require (
|
||||
@@ -45,14 +49,25 @@ require (
|
||||
github.com/buger/jsonparser v1.1.1 // indirect
|
||||
github.com/cenkalti/backoff/v4 v4.3.0 // indirect
|
||||
github.com/containerd/log v0.1.0 // indirect
|
||||
github.com/davecgh/go-spew v1.1.1 // indirect
|
||||
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect
|
||||
github.com/decred/dcrd/dcrec/secp256k1/v4 v4.3.0 // indirect
|
||||
github.com/distribution/reference v0.6.0 // indirect
|
||||
github.com/emicklei/go-restful/v3 v3.11.0 // indirect
|
||||
github.com/felixge/httpsnoop v1.0.4 // indirect
|
||||
github.com/fxamacker/cbor/v2 v2.7.0 // indirect
|
||||
github.com/go-logr/logr v1.4.2 // indirect
|
||||
github.com/go-logr/stdr v1.2.2 // indirect
|
||||
github.com/go-openapi/jsonpointer v0.21.0 // indirect
|
||||
github.com/go-openapi/jsonreference v0.20.2 // indirect
|
||||
github.com/go-openapi/swag v0.23.0 // indirect
|
||||
github.com/goccy/go-json v0.10.3 // indirect
|
||||
github.com/kr/pretty v0.2.1 // indirect
|
||||
github.com/golang/protobuf v1.5.4 // indirect
|
||||
github.com/google/gnostic-models v0.6.8 // indirect
|
||||
github.com/google/go-cmp v0.6.0 // indirect
|
||||
github.com/google/gofuzz v1.2.0 // indirect
|
||||
github.com/google/uuid v1.6.0 // indirect
|
||||
github.com/josharian/intern v1.0.0 // indirect
|
||||
github.com/json-iterator/go v1.1.12 // indirect
|
||||
github.com/lestrrat-go/blackmagic v1.0.2 // indirect
|
||||
github.com/lestrrat-go/httpcc v1.0.1 // indirect
|
||||
github.com/lestrrat-go/httprc v1.0.6 // indirect
|
||||
@@ -63,18 +78,33 @@ require (
|
||||
github.com/mattn/go-colorable v0.1.13 // indirect
|
||||
github.com/mattn/go-isatty v0.0.20 // indirect
|
||||
github.com/moby/docker-image-spec v1.3.1 // indirect
|
||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
|
||||
github.com/modern-go/reflect2 v1.0.2 // indirect
|
||||
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect
|
||||
github.com/pkg/errors v0.9.1 // indirect
|
||||
github.com/pmezard/go-difflib v1.0.0 // indirect
|
||||
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect
|
||||
github.com/segmentio/asm v1.2.0 // indirect
|
||||
github.com/spf13/pflag v1.0.5 // indirect
|
||||
github.com/x448/float16 v0.8.4 // indirect
|
||||
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.57.0 // indirect
|
||||
go.opentelemetry.io/otel v1.32.0 // indirect
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.22.0 // indirect
|
||||
go.opentelemetry.io/otel/metric v1.32.0 // indirect
|
||||
go.opentelemetry.io/otel/sdk v1.31.0 // indirect
|
||||
go.opentelemetry.io/otel/trace v1.32.0 // indirect
|
||||
golang.org/x/text v0.21.0 // indirect
|
||||
golang.org/x/oauth2 v0.25.0 // indirect
|
||||
golang.org/x/term v0.29.0 // indirect
|
||||
golang.org/x/text v0.22.0 // indirect
|
||||
golang.org/x/time v0.8.0 // indirect
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20241223144023-3abc09e42ca8 // indirect
|
||||
gopkg.in/evanphx/json-patch.v4 v4.12.0 // indirect
|
||||
gopkg.in/inf.v0 v0.9.1 // indirect
|
||||
gotest.tools/v3 v3.0.3 // indirect
|
||||
k8s.io/klog/v2 v2.130.1 // indirect
|
||||
k8s.io/kube-openapi v0.0.0-20241105132330-32ad38e42d3f // indirect
|
||||
k8s.io/utils v0.0.0-20241104100929-3ea5e8cea738 // indirect
|
||||
sigs.k8s.io/json v0.0.0-20241010143419-9aa6b5e7a4b3 // indirect
|
||||
sigs.k8s.io/structured-merge-diff/v4 v4.4.2 // indirect
|
||||
sigs.k8s.io/yaml v1.4.0 // indirect
|
||||
)
|
||||
|
||||
go 1.23.4
|
||||
go 1.24.0
|
||||
|
||||
@@ -2,16 +2,14 @@ github.com/Azure/go-ansiterm v0.0.0-20170929234023-d6e3b3328b78 h1:w+iIsaOQNcT7O
|
||||
github.com/Azure/go-ansiterm v0.0.0-20170929234023-d6e3b3328b78/go.mod h1:LmzpDX56iTiv29bbRTIsUNlaFfuhWRQBWjQdVyAevI8=
|
||||
github.com/Microsoft/go-winio v0.6.2 h1:F2VQgta7ecxGYO8k3ZZz3RS8fVIXVxONVUPlNERoyfY=
|
||||
github.com/Microsoft/go-winio v0.6.2/go.mod h1:yd8OoFMLzJbo9gZq8j5qaps8bJ9aShtEA8Ipt1oGCvU=
|
||||
github.com/PuerkitoBio/goquery v1.10.0 h1:6fiXdLuUvYs2OJSvNRqlNPoBm6YABE226xrbavY5Wv4=
|
||||
github.com/PuerkitoBio/goquery v1.10.0/go.mod h1:TjZZl68Q3eGHNBA8CWaxAN7rOU1EbDz3CWuolcO5Yu4=
|
||||
github.com/PuerkitoBio/goquery v1.10.1 h1:Y8JGYUkXWTGRB6Ars3+j3kN0xg1YqqlwvdTV8WTFQcU=
|
||||
github.com/PuerkitoBio/goquery v1.10.1/go.mod h1:IYiHrOMps66ag56LEH7QYDDupKXyo5A8qrjIx3ZtujY=
|
||||
github.com/PuerkitoBio/goquery v1.10.2 h1:7fh2BdHcG6VFZsK7toXBT/Bh1z5Wmy8Q9MV9HqT2AM8=
|
||||
github.com/PuerkitoBio/goquery v1.10.2/go.mod h1:0guWGjcLu9AYC7C1GHnpysHy056u9aEkUHwhdnePMCU=
|
||||
github.com/alexflint/go-arg v1.5.1 h1:nBuWUCpuRy0snAG+uIJ6N0UvYxpxA0/ghA/AaHxlT8Y=
|
||||
github.com/alexflint/go-arg v1.5.1/go.mod h1:A7vTJzvjoaSTypg4biM5uYNTkJ27SkNTArtYXnlqVO8=
|
||||
github.com/alexflint/go-scalar v1.2.0 h1:WR7JPKkeNpnYIOfHRa7ivM21aWAdHD0gEWHCx+WQBRw=
|
||||
github.com/alexflint/go-scalar v1.2.0/go.mod h1:LoFvNMqS1CPrMVltza4LvnGKhaSpc3oyLEBUZVhhS2o=
|
||||
github.com/andybalholm/cascadia v1.3.2 h1:3Xi6Dw5lHF15JtdcmAHD3i1+T8plmv7BQ/nsViSLyss=
|
||||
github.com/andybalholm/cascadia v1.3.2/go.mod h1:7gtRlve5FxPPgIgX36uWBX58OdBsSS6lUvCFb+h7KvU=
|
||||
github.com/andybalholm/cascadia v1.3.3 h1:AG2YHrzJIm4BZ19iwJ/DAua6Btl3IwJX+VI4kktS1LM=
|
||||
github.com/andybalholm/cascadia v1.3.3/go.mod h1:xNd9bqTn98Ln4DwST8/nG+H0yuB8Hmgu1YHNnWw0GeA=
|
||||
github.com/bahlo/generic-list-go v0.2.0 h1:5sz/EEAK+ls5wF+NeqDpk5+iNdMDXrh3z3nPnH1Wvgk=
|
||||
@@ -25,34 +23,32 @@ github.com/cenkalti/backoff/v4 v4.3.0/go.mod h1:Y3VNntkOUPxTVeUxJ/G5vcM//AlwfmyY
|
||||
github.com/containerd/log v0.1.0 h1:TCJt7ioM2cr/tfR8GPbGf9/VRAX8D2B4PjzCpfX540I=
|
||||
github.com/containerd/log v0.1.0/go.mod h1:VRRf09a7mHDIRezVKTRCrOq78v577GXq3bSa3EhrzVo=
|
||||
github.com/coreos/go-systemd/v22 v22.5.0/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc=
|
||||
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
|
||||
github.com/creack/pty v1.1.11/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
|
||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM=
|
||||
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/decred/dcrd/dcrec/secp256k1/v4 v4.3.0 h1:rpfIENRNNilwHwZeG5+P150SMrnNEcHYvcCuK6dPZSg=
|
||||
github.com/decred/dcrd/dcrec/secp256k1/v4 v4.3.0/go.mod h1:v57UDF4pDQJcEfFUCRop3lJL149eHGSe9Jvczhzjo/0=
|
||||
github.com/distribution/reference v0.6.0 h1:0IXCQ5g4/QMHHkarYzh5l+u8T3t73zM5QvfrDyIgxBk=
|
||||
github.com/distribution/reference v0.6.0/go.mod h1:BbU0aIcezP1/5jX/8MP0YiH4SdvB5Y4f/wlDRiLyi3E=
|
||||
github.com/docker/docker v27.3.1+incompatible h1:KttF0XoteNTicmUtBO0L2tP+J7FGRFTjaEF4k6WdhfI=
|
||||
github.com/docker/docker v27.3.1+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk=
|
||||
github.com/docker/docker v27.4.0+incompatible h1:I9z7sQ5qyzO0BfAb9IMOawRkAGxhYsidKiTMcm0DU+A=
|
||||
github.com/docker/docker v27.4.0+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk=
|
||||
github.com/docker/docker v27.4.1+incompatible h1:ZJvcY7gfwHn1JF48PfbyXg7Jyt9ZCWDW+GGXOIxEwp4=
|
||||
github.com/docker/docker v27.4.1+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk=
|
||||
github.com/docker/docker v27.5.1+incompatible h1:4PYU5dnBYqRQi0294d1FBECqT9ECWeQAIfE8q4YnPY8=
|
||||
github.com/docker/docker v27.5.1+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk=
|
||||
github.com/docker/go-connections v0.5.0 h1:USnMq7hx7gwdVZq1L49hLXaFtUdTADjXGp+uj1Br63c=
|
||||
github.com/docker/go-connections v0.5.0/go.mod h1:ov60Kzw0kKElRwhNs9UlUHAE/F9Fe6GLaXnqyDdmEXc=
|
||||
github.com/docker/go-units v0.5.0 h1:69rxXcBk27SvSaaxTtLh/8llcHD8vYHT7WSdRZ/jvr4=
|
||||
github.com/docker/go-units v0.5.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk=
|
||||
github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY=
|
||||
github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto=
|
||||
github.com/emicklei/go-restful/v3 v3.11.0 h1:rAQeMHw1c7zTmncogyy8VvRZwtkmkZ4FxERmMY4rD+g=
|
||||
github.com/emicklei/go-restful/v3 v3.11.0/go.mod h1:6n3XBCmQQb25CM2LCACGz8ukIrRry+4bhvbpWn3mrbc=
|
||||
github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg=
|
||||
github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U=
|
||||
github.com/go-chi/chi/v5 v5.1.0 h1:acVI1TYaD+hhedDJ3r54HyA6sExp3HfXq7QWEEY/xMw=
|
||||
github.com/go-chi/chi/v5 v5.1.0/go.mod h1:DslCQbL2OYiznFReuXYUmQ2hGd1aDpCnlMNITLSKoi8=
|
||||
github.com/go-chi/chi/v5 v5.2.0 h1:Aj1EtB0qR2Rdo2dG4O94RIU35w2lvQSj6BRA4+qwFL0=
|
||||
github.com/go-chi/chi/v5 v5.2.0/go.mod h1:DslCQbL2OYiznFReuXYUmQ2hGd1aDpCnlMNITLSKoi8=
|
||||
github.com/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/fxamacker/cbor/v2 v2.7.0 h1:iM5WgngdRBanHcxugY4JySA0nk1wZorNOpTgCMedv5E=
|
||||
github.com/fxamacker/cbor/v2 v2.7.0/go.mod h1:pxXPTn3joSm21Gbwsv0w9OSA2y1HFR9qXEeXQVeNoDQ=
|
||||
github.com/go-chi/chi/v5 v5.2.1 h1:KOIHODQj58PmL80G2Eak4WdvUzjSJSm0vG72crDCqb8=
|
||||
github.com/go-chi/chi/v5 v5.2.1/go.mod h1:L2yAIGWB3H+phAw1NxKwWM+7eUH/lU8pOMm5hHcoops=
|
||||
github.com/go-chi/jwtauth/v5 v5.3.2 h1:s+ON3ATyyMs3Me0kqyuua6Rwu+2zqIIkL0GCaMarwvs=
|
||||
github.com/go-chi/jwtauth/v5 v5.3.2/go.mod h1:O4QvPRuZLZghl9WvfVaON+ARfGzpD2PBX/QY5vUz7aQ=
|
||||
github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
|
||||
@@ -60,6 +56,16 @@ github.com/go-logr/logr v1.4.2 h1:6pFjapn8bFcIbiKo3XT4j/BhANplGihG6tvd+8rYgrY=
|
||||
github.com/go-logr/logr v1.4.2/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
|
||||
github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag=
|
||||
github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE=
|
||||
github.com/go-openapi/jsonpointer v0.19.6/go.mod h1:osyAmYz/mB/C3I+WsTTSgw1ONzaLJoLCyoi6/zppojs=
|
||||
github.com/go-openapi/jsonpointer v0.21.0 h1:YgdVicSA9vH5RiHs9TZW5oyafXZFc6+2Vc1rr/O9oNQ=
|
||||
github.com/go-openapi/jsonpointer v0.21.0/go.mod h1:IUyH9l/+uyhIYQ/PXVA41Rexl+kOkAPDdXEYns6fzUY=
|
||||
github.com/go-openapi/jsonreference v0.20.2 h1:3sVjiK66+uXK/6oQ8xgcRKcFgQ5KXa2KvnJRumpMGbE=
|
||||
github.com/go-openapi/jsonreference v0.20.2/go.mod h1:Bl1zwGIM8/wsvqjsOQLJ/SH+En5Ap4rVB5KVcIDZG2k=
|
||||
github.com/go-openapi/swag v0.22.3/go.mod h1:UzaqsxGiab7freDnrUUra0MwWfN/q7tE4j+VcZ0yl14=
|
||||
github.com/go-openapi/swag v0.23.0 h1:vsEVJDUo2hPJ2tu0/Xc+4noaxyEffXNIs3cOULZ+GrE=
|
||||
github.com/go-openapi/swag v0.23.0/go.mod h1:esZ8ITTYEsH1V2trKHjAN8Ai7xHb8RV+YSZ577vPjgQ=
|
||||
github.com/go-task/slim-sprig/v3 v3.0.0 h1:sUs3vkvUymDpBKi3qH1YSqBQk9+9D/8M2mN1vB6EwHI=
|
||||
github.com/go-task/slim-sprig/v3 v3.0.0/go.mod h1:W848ghGpv3Qj3dhTPRyJypKRiqCdHZiAzKg9hl15HA8=
|
||||
github.com/goccy/go-json v0.10.3 h1:KZ5WoDbxAIgm2HNbYckL0se1fHD6rz5j4ywS6ebzDqA=
|
||||
github.com/goccy/go-json v0.10.3/go.mod h1:oq7eo15ShAhp70Anwd5lgX2pLfOS3QCiwU/PULtXL6M=
|
||||
github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=
|
||||
@@ -67,18 +73,32 @@ github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q=
|
||||
github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q=
|
||||
github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek=
|
||||
github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps=
|
||||
github.com/google/gnostic-models v0.6.8 h1:yo/ABAfM5IMRsS1VnXjTBvUb61tFIHozhlYvRgGre9I=
|
||||
github.com/google/gnostic-models v0.6.8/go.mod h1:5n7qKqH0f5wFt+aWF8CW6pZLLNOfYuF5OpfBSENuI8U=
|
||||
github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
|
||||
github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
|
||||
github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
|
||||
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
|
||||
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
|
||||
github.com/google/gofuzz v1.2.0 h1:xRy4A+RhZaiKjJ1bPfwQ8sedCA+YS2YcCHW6ec7JMi0=
|
||||
github.com/google/gofuzz v1.2.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
|
||||
github.com/google/pprof v0.0.0-20241029153458-d1b30febd7db h1:097atOisP2aRj7vFgYQBbFN4U4JNXUNYpxael3UzMyo=
|
||||
github.com/google/pprof v0.0.0-20241029153458-d1b30febd7db/go.mod h1:vavhavw2zAxS5dIdcRluK6cSGGPlZynqzFM8NdvU144=
|
||||
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
|
||||
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||
github.com/grpc-ecosystem/grpc-gateway/v2 v2.16.0 h1:YBftPWNWd4WwGqtY2yeZL2ef8rHAxPBD8KFhJpmcqms=
|
||||
github.com/grpc-ecosystem/grpc-gateway/v2 v2.16.0/go.mod h1:YN5jB8ie0yfIUg6VvR9Kz84aCaG7AsGZnLjhHbUqwPg=
|
||||
github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY=
|
||||
github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y=
|
||||
github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM=
|
||||
github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo=
|
||||
github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8=
|
||||
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
|
||||
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
|
||||
github.com/kr/pretty v0.2.1 h1:Fmg33tUaq4/8ym9TJN1x7sLJnHVwhP33CNkpYV/7rwI=
|
||||
github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
|
||||
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
|
||||
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
|
||||
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
|
||||
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
|
||||
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
|
||||
@@ -95,8 +115,6 @@ github.com/lestrrat-go/jwx/v2 v2.1.3 h1:Ud4lb2QuxRClYAmRleF50KrbKIoM1TddXgBrneT5
|
||||
github.com/lestrrat-go/jwx/v2 v2.1.3/go.mod h1:q6uFgbgZfEmQrfJfrCo90QcQOcXFMfbI/fO0NqRtvZo=
|
||||
github.com/lestrrat-go/option v1.0.1 h1:oAzP2fvZGQKWkvHa1/SAcFolBEca1oN+mQ7eooNBEYU=
|
||||
github.com/lestrrat-go/option v1.0.1/go.mod h1:5ZHFbivi4xwXxhxY9XHDe2FHo6/Z7WWmtT7T5nBBp3I=
|
||||
github.com/magiconair/properties v1.8.7 h1:IeQXZAiQcpL9mgcAe1Nu6cX9LLw6ExEHKjN0VQdvPDY=
|
||||
github.com/magiconair/properties v1.8.7/go.mod h1:Dhd985XPs7jluiymwWYZ0G4Z61jb3vdS329zhj2hYo0=
|
||||
github.com/magiconair/properties v1.8.9 h1:nWcCbLq1N2v/cpNsy5WvQ37Fb+YElfq20WJ/a8RkpQM=
|
||||
github.com/magiconair/properties v1.8.9/go.mod h1:Dhd985XPs7jluiymwWYZ0G4Z61jb3vdS329zhj2hYo0=
|
||||
github.com/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0=
|
||||
@@ -111,8 +129,19 @@ github.com/moby/docker-image-spec v1.3.1 h1:jMKff3w6PgbfSa69GfNg+zN/XLhfXJGnEx3N
|
||||
github.com/moby/docker-image-spec v1.3.1/go.mod h1:eKmb5VW8vQEh/BAr2yvVNvuiJuY6UIocYsFu/DxxRpo=
|
||||
github.com/moby/term v0.0.0-20201216013528-df9cb8a40635 h1:rzf0wL0CHVc8CEsgyygG0Mn9CNCCPZqOPaz8RiiHYQk=
|
||||
github.com/moby/term v0.0.0-20201216013528-df9cb8a40635/go.mod h1:FBS0z0QWA44HXygs7VXDUOGoN/1TV3RuWkLO04am3wc=
|
||||
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
|
||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg=
|
||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
|
||||
github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M=
|
||||
github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
|
||||
github.com/morikuni/aec v1.0.0 h1:nP9CBfwrvYnBRgY6qfDQkygYDmYwOilePFkwzv4dU8A=
|
||||
github.com/morikuni/aec v1.0.0/go.mod h1:BbKIizmSmc5MMPqRYbxO4ZU0S0+P200+tUnFx7PXmsc=
|
||||
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA=
|
||||
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ=
|
||||
github.com/onsi/ginkgo/v2 v2.21.0 h1:7rg/4f3rB88pb5obDgNZrNHrQ4e6WpjonchcpuBRnZM=
|
||||
github.com/onsi/ginkgo/v2 v2.21.0/go.mod h1:7Du3c42kxCUegi0IImZ1wUQzMBVecgIHjR1C+NkhLQo=
|
||||
github.com/onsi/gomega v1.35.1 h1:Cwbd75ZBPxFSuZ6T+rN/WCb/gOc6YgFBXLlZLhC7Ds4=
|
||||
github.com/onsi/gomega v1.35.1/go.mod h1:PvZbdDc8J6XJEpDK4HCuRBm8a6Fzp9/DmhC9C7yFlog=
|
||||
github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8Oi/yOhh5U=
|
||||
github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM=
|
||||
github.com/opencontainers/image-spec v1.1.0 h1:8SG7/vwALn54lVB/0yZ/MMwhFrPYtpEHQb2IpWsCzug=
|
||||
@@ -120,39 +149,49 @@ github.com/opencontainers/image-spec v1.1.0/go.mod h1:W4s4sFTMaBeK1BQLXbG4AdM2sz
|
||||
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
|
||||
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/puzpuzpuz/xsync/v3 v3.4.0 h1:DuVBAdXuGFHv8adVXjWWZ63pJq+NRXOWVXlKDBZ+mJ4=
|
||||
github.com/puzpuzpuz/xsync/v3 v3.4.0/go.mod h1:VjzYrABPabuM4KyBh1Ftq6u8nhwY5tBPKP9jpmh0nnA=
|
||||
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U=
|
||||
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/puzpuzpuz/xsync/v3 v3.5.0 h1:i+cMcpEDY1BkNm7lPDkCtE4oElsYLn+EKF8kAu2vXT4=
|
||||
github.com/puzpuzpuz/xsync/v3 v3.5.0/go.mod h1:VjzYrABPabuM4KyBh1Ftq6u8nhwY5tBPKP9jpmh0nnA=
|
||||
github.com/puzpuzpuz/xsync/v3 v3.5.1 h1:GJYJZwO6IdxN/IKbneznS6yPkVC+c3zyY/j19c++5Fg=
|
||||
github.com/puzpuzpuz/xsync/v3 v3.5.1/go.mod h1:VjzYrABPabuM4KyBh1Ftq6u8nhwY5tBPKP9jpmh0nnA=
|
||||
github.com/rogpeppe/go-internal v1.12.0 h1:exVL4IDcn6na9z1rAb56Vxr+CgyK3nn3O+epU5NdKM8=
|
||||
github.com/rogpeppe/go-internal v1.12.0/go.mod h1:E+RYuTGaKKdloAfM02xzb0FW3Paa99yedzYV+kq4uf4=
|
||||
github.com/rs/xid v1.5.0/go.mod h1:trrq9SKmegXys3aeAKXMUTdJsYXVwGY3RLcfgqegfbg=
|
||||
github.com/rs/zerolog v1.33.0 h1:1cU2KZkvPxNyfgEmhHAz/1A9Bz+llsdYzklWFzgp0r8=
|
||||
github.com/rs/zerolog v1.33.0/go.mod h1:/7mN4D5sKwJLZQ2b/znpjC3/GQWY/xaDXUM0kKWRHss=
|
||||
github.com/samber/lo v1.47.0 h1:z7RynLwP5nbyRscyvcD043DWYoOcYRv3mV8lBeqOCLc=
|
||||
github.com/samber/lo v1.47.0/go.mod h1:RmDH9Ct32Qy3gduHQuKJ3gW1fMHAnE/fAzQuf6He5cU=
|
||||
github.com/samber/lo v1.49.1 h1:4BIFyVfuQSEpluc7Fua+j1NolZHiEHEpaSEKdsH0tew=
|
||||
github.com/samber/lo v1.49.1/go.mod h1:dO6KHFzUKXgP8LDhU0oI8d2hekjXnGOu0DB8Jecxd6o=
|
||||
github.com/segmentio/asm v1.2.0 h1:9BQrFxC+YOHJlTlHGkTrFWf59nbL3XnCoFLTwDCI7ys=
|
||||
github.com/segmentio/asm v1.2.0/go.mod h1:BqMnlJP91P8d+4ibuonYZw9mfnzI9HfxselHZr5aAcs=
|
||||
github.com/sergi/go-diff v1.3.1 h1:xkr+Oxo4BOQKmkn/B9eMK0g5Kg/983T9DqqPHwYqD+8=
|
||||
github.com/sergi/go-diff v1.3.1/go.mod h1:aMJSSKb2lpPvRNec0+w3fl7LP9IOFzdc9Pa4NFbPK1I=
|
||||
github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ=
|
||||
github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ=
|
||||
github.com/spf13/afero v1.11.0 h1:WJQKhtpdm3v2IzqG8VMqrr6Rf3UYpEF239Jy9wNepM8=
|
||||
github.com/spf13/afero v1.11.0/go.mod h1:GH9Y3pIexgf1MTIWtNGyogA5MwRIDXGUr+hbWNoBjkY=
|
||||
github.com/spf13/afero v1.12.0 h1:UcOPyRBYczmFn6yvphxkn9ZEOY65cpwGKb5mL36mrqs=
|
||||
github.com/spf13/afero v1.12.0/go.mod h1:ZTlWwG4/ahT8W7T0WQ5uYmjI9duaLQGy3Q2OAl4sk/4=
|
||||
github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4=
|
||||
github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA=
|
||||
github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
|
||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
|
||||
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
|
||||
github.com/stretchr/objx v0.5.2 h1:xuMeJ0Sdp5ZMRXx/aWO6RZxdr3beISkG5/G/aIRr3pY=
|
||||
github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA=
|
||||
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
|
||||
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
|
||||
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
|
||||
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg=
|
||||
github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
|
||||
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
|
||||
github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
|
||||
github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA=
|
||||
github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
|
||||
github.com/wk8/go-ordered-map/v2 v2.1.8 h1:5h/BUHu93oj4gIdvHHHGsScSTMijfx5PeYkE/fJgbpc=
|
||||
github.com/wk8/go-ordered-map/v2 v2.1.8/go.mod h1:5nJHM5DyteebpVlHnWMV0rPz6Zp7+xBAnxjb1X5vnTw=
|
||||
github.com/x448/float16 v0.8.4 h1:qLwI1I70+NjRFUR3zs1JPUCgaCXSh3SW62uAKT1mSBM=
|
||||
github.com/x448/float16 v0.8.4/go.mod h1:14CWIYCyZA/cWjXOioeEpHeN/83MdbZDRQHoFcYsOfg=
|
||||
github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
||||
github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
||||
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
|
||||
@@ -168,9 +207,10 @@ go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.22.0 h1:FyjCy
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.22.0/go.mod h1:hYwym2nDEeZfG/motx0p7L7J1N1vyzIThemQsb4g2qY=
|
||||
go.opentelemetry.io/otel/metric v1.32.0 h1:xV2umtmNcThh2/a/aCP+h64Xx5wsj8qqnkYZktzNa0M=
|
||||
go.opentelemetry.io/otel/metric v1.32.0/go.mod h1:jH7CIbbK6SH2V2wE16W05BHCtIDzauciCRLoc/SyMv8=
|
||||
go.opentelemetry.io/otel/sdk v1.22.0 h1:6coWHw9xw7EfClIC/+O31R8IY3/+EiRFHevmHafB2Gw=
|
||||
go.opentelemetry.io/otel/sdk v1.22.0/go.mod h1:iu7luyVGYovrRpe2fmj3CVKouQNdTOkxtLzPvPz1DOc=
|
||||
go.opentelemetry.io/otel/sdk v1.31.0/go.mod h1:TfRbMdhvxIIr/B2N2LQW2S5v9m3gOQ/08KsbbO5BPT0=
|
||||
go.opentelemetry.io/otel/sdk v1.32.0 h1:RNxepc9vK59A8XsgZQouW8ue8Gkb4jpWtJm9ge5lEG4=
|
||||
go.opentelemetry.io/otel/sdk v1.32.0/go.mod h1:LqgegDBjKMmb2GC6/PrTnteJG39I8/vJCAP9LlJXEjU=
|
||||
go.opentelemetry.io/otel/sdk/metric v1.32.0 h1:rZvFnvmvawYb0alrYkjraqJq0Z4ZUJAiyYCU9snn1CU=
|
||||
go.opentelemetry.io/otel/sdk/metric v1.32.0/go.mod h1:PWeZlq0zt9YkYAp3gjKZ0eicRYvOh1Gd+X99x6GHpCQ=
|
||||
go.opentelemetry.io/otel/trace v1.32.0 h1:WIC9mYrXf8TmY/EXuULKc8hR17vE+Hjv2cssQDe03fM=
|
||||
go.opentelemetry.io/otel/trace v1.32.0/go.mod h1:+i4rkvCraA+tG6AzwloGaCtkx53Fa+L+V8e9a7YvhT8=
|
||||
go.opentelemetry.io/proto/otlp v1.0.0 h1:T0TX0tmXU8a3CbNXzEKGeU5mIVOdf0oykP+u2lIVU/I=
|
||||
@@ -182,14 +222,9 @@ golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5y
|
||||
golang.org/x/crypto v0.13.0/go.mod h1:y6Z2r+Rw4iayiXXAIxJIDAJ1zMW4yaTpebo8fPOliYc=
|
||||
golang.org/x/crypto v0.19.0/go.mod h1:Iy9bg/ha4yyC70EfRS8jz+B6ybOBKMaSxLj6P6oBDfU=
|
||||
golang.org/x/crypto v0.23.0/go.mod h1:CKFgDieR+mRhux2Lsu27y0fO304Db0wZe70UKqHu0v8=
|
||||
golang.org/x/crypto v0.29.0 h1:L5SG1JTTXupVV3n6sUqMTeWbjAyfPwoda2DLX8J8FrQ=
|
||||
golang.org/x/crypto v0.29.0/go.mod h1:+F4F4N5hv6v38hfeYwTdx20oUvLLc+QfrE9Ax9HtgRg=
|
||||
golang.org/x/crypto v0.30.0 h1:RwoQn3GkWiMkzlX562cLB7OxWvjH1L8xutO2WoJcRoY=
|
||||
golang.org/x/crypto v0.30.0/go.mod h1:kDsLvtWBEx7MV9tJOj9bnXsPbxwJQ6csT/x4KIN4Ssk=
|
||||
golang.org/x/crypto v0.31.0 h1:ihbySMvVjLAeSH1IbfcRTkD/iNscyz8rGzjF/E5hV6U=
|
||||
golang.org/x/crypto v0.31.0/go.mod h1:kDsLvtWBEx7MV9tJOj9bnXsPbxwJQ6csT/x4KIN4Ssk=
|
||||
golang.org/x/crypto v0.32.0 h1:euUpcYgM8WcP71gNpTqQCn6rC2t6ULUPiOzfWaXVVfc=
|
||||
golang.org/x/crypto v0.32.0/go.mod h1:ZnnJkOaASj8g0AjIduWNlq2NRxL0PlBrbKVyZ6V/Ugc=
|
||||
golang.org/x/crypto v0.33.0 h1:IOBPskki6Lysi0lo9qQvbxiQ+FvsCC/YWOecCHAixus=
|
||||
golang.org/x/crypto v0.33.0/go.mod h1:bVdXmD7IV/4GdElGPozy6U7lWdRXA4qyRVGJV57uQ5M=
|
||||
golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||
golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
|
||||
@@ -205,15 +240,16 @@ golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwY
|
||||
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
|
||||
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
|
||||
golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
|
||||
golang.org/x/net v0.9.0/go.mod h1:d48xBJpPfHeWQsugry2m+kC02ZBRGRgulfHnEXEuWns=
|
||||
golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg=
|
||||
golang.org/x/net v0.15.0/go.mod h1:idbUs1IY1+zTqbi8yxTbhexhEEk5ur9LInksu6HrEpk=
|
||||
golang.org/x/net v0.21.0/go.mod h1:bIjVDfnllIU7BJ2DNgfnXvpSvtn8VRwhlsaeUTyUS44=
|
||||
golang.org/x/net v0.25.0/go.mod h1:JkAGAh7GEvH74S6FOH42FLoXpXbE/aqXSrIQjXgsiwM=
|
||||
golang.org/x/net v0.31.0 h1:68CPQngjLL0r2AlUKiSxtQFKvzRVbnzLwMUn5SzcLHo=
|
||||
golang.org/x/net v0.31.0/go.mod h1:P4fl1q7dY2hnZFxEk4pPSkDHF+QqjitcnDjUQyMM+pM=
|
||||
golang.org/x/net v0.33.0 h1:74SYHlV8BIgHIFC/LrYkOGIwL19eTYXQ5wc6TBuO36I=
|
||||
golang.org/x/net v0.33.0/go.mod h1:HXLR5J+9DxmrqMwG9qjGCxZ+zKXxBru04zlTvWlWuN4=
|
||||
golang.org/x/net v0.35.0 h1:T5GQRQb2y08kTAByq9L4/bz8cipCdA8FbRTXewonqY8=
|
||||
golang.org/x/net v0.35.0/go.mod h1:EglIi67kWsHKlRzzVMUD93VMSWGFOMSZgxFjparz1Qk=
|
||||
golang.org/x/oauth2 v0.25.0 h1:CY4y7XT9v0cRI9oupztF8AgiIu99L/ksR/Xp/6jrZ70=
|
||||
golang.org/x/oauth2 v0.25.0/go.mod h1:XYTD2NtWslqkgxebSiOHnXEap4TF09sJSc7H1sXbhtI=
|
||||
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
@@ -222,10 +258,9 @@ golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.3.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y=
|
||||
golang.org/x/sync v0.6.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
|
||||
golang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
|
||||
golang.org/x/sync v0.9.0 h1:fEo0HyrW1GIgZdpbhCRO0PkJajUS5H9IFUztCgEo2jQ=
|
||||
golang.org/x/sync v0.9.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
|
||||
golang.org/x/sync v0.10.0 h1:3NQrjDixjgGwUOCaF8w2+VYHv0Ve/vGYSbdkTa98gmQ=
|
||||
golang.org/x/sync v0.10.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
|
||||
golang.org/x/sync v0.11.0 h1:GGz8+XQP4FvTTrjZPzNKTMFtSXH80RAzG+5ghFPgK9w=
|
||||
golang.org/x/sync v0.11.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
|
||||
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200831180312-196b9ba8737a/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
@@ -237,27 +272,24 @@ golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBc
|
||||
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.7.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/sys v0.20.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/sys v0.27.0 h1:wBqf8DvsY9Y/2P8gAfPDEYNuS30J4lPHJxXSb/nJZ+s=
|
||||
golang.org/x/sys v0.27.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/sys v0.28.0 h1:Fksou7UEQUWlKvIdsqzJmUmCX3cZuD2+P3XyyzwMhlA=
|
||||
golang.org/x/sys v0.28.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/sys v0.29.0 h1:TPYlXGxvx1MGTn2GiZDhnjPA9wZzZeGKHHmKhHYvgaU=
|
||||
golang.org/x/sys v0.29.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/sys v0.30.0 h1:QjkSwP/36a20jFYWkSue1YwXzLmsV5Gfq7Eiy72C1uc=
|
||||
golang.org/x/sys v0.30.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/telemetry v0.0.0-20240228155512-f48c80bd79b2/go.mod h1:TeRTkGYfJXctD9OcfyVLyj2J3IxLnKwHJR8f4D8a3YE=
|
||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
||||
golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
|
||||
golang.org/x/term v0.7.0/go.mod h1:P32HKFT3hSsZrRxla30E9HqToFYAQPCMs/zFMBUFqPY=
|
||||
golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo=
|
||||
golang.org/x/term v0.12.0/go.mod h1:owVbMEjm3cBLCHdkQu9b1opXd4ETQWc3BhuQGKgXgvU=
|
||||
golang.org/x/term v0.17.0/go.mod h1:lLRBjIVuehSbZlaOtGMbcMncT+aqLLLmKrsjNrUguwk=
|
||||
golang.org/x/term v0.20.0/go.mod h1:8UkIAJTvZgivsXaD6/pH6U9ecQzZ45awqEOzuCvwpFY=
|
||||
golang.org/x/term v0.27.0/go.mod h1:iMsnZpn0cago0GOrHO2+Y7u7JPn5AylBrcoWkElMTSM=
|
||||
golang.org/x/term v0.29.0 h1:L6pJp37ocefwRRtYPKSWOWzOtWSxVajvz2ldH/xi3iU=
|
||||
golang.org/x/term v0.29.0/go.mod h1:6bl4lRlvVuDgSf3179VpIxBF0o10JUpXWOnI7nErv7s=
|
||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
|
||||
@@ -266,12 +298,11 @@ golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
|
||||
golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
|
||||
golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
|
||||
golang.org/x/text v0.15.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
|
||||
golang.org/x/text v0.20.0 h1:gK/Kv2otX8gz+wn7Rmb3vT96ZwuoxnQlY+HlJVj7Qug=
|
||||
golang.org/x/text v0.20.0/go.mod h1:D4IsuqiFMhST5bX19pQ9ikHC2GsaKyk/oF+pn3ducp4=
|
||||
golang.org/x/text v0.21.0 h1:zyQAAkrwaneQ066sspRyJaG9VNi/YJ1NfzcGB3hZ/qo=
|
||||
golang.org/x/text v0.21.0/go.mod h1:4IBbMaMmOPCJ8SecivzSH54+73PCFmPWxNTLm+vZkEQ=
|
||||
golang.org/x/time v0.5.0 h1:o7cqy6amK/52YcAKIPlM3a+Fpj35zvRj2TP+e1xFSfk=
|
||||
golang.org/x/time v0.5.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM=
|
||||
golang.org/x/text v0.22.0 h1:bofq7m3/HAFvbF51jz3Q9wLg3jkvSPuiZu/pD1XwgtM=
|
||||
golang.org/x/text v0.22.0/go.mod h1:YRoo4H8PVmsu+E3Ou7cqLVH8oXWIHVoX0jqUWALQhfY=
|
||||
golang.org/x/time v0.8.0 h1:9i3RxcPv3PZnitoVGMPDKZSq1xW1gK1Xy3ArNOGZfEg=
|
||||
golang.org/x/time v0.8.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM=
|
||||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
golang.org/x/tools v0.0.0-20190624222133-a101b041ded4/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
|
||||
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
@@ -281,36 +312,29 @@ golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc
|
||||
golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU=
|
||||
golang.org/x/tools v0.13.0/go.mod h1:HvlwmtVNQAhOuCjW7xxvovg8wbNq7LwfXh/k7wXUl58=
|
||||
golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d/go.mod h1:aiJjzUbINMkxbQROHiO6hDPo2LHcIPhhQsa9DLh0yGk=
|
||||
golang.org/x/tools v0.26.0 h1:v/60pFQmzmT9ExmjDv2gGIfi3OqfKoEP6I5+umXlbnQ=
|
||||
golang.org/x/tools v0.26.0/go.mod h1:TPVVj70c7JJ3WCazhD8OdXcZg/og+b9+tH/KxylGwH0=
|
||||
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
google.golang.org/genproto v0.0.0-20231106174013-bbf56f31fb17 h1:wpZ8pe2x1Q3f2KyT5f8oP/fa9rHAKgFPr/HZdNuS+PQ=
|
||||
google.golang.org/genproto/googleapis/api v0.0.0-20240903143218-8af14fe29dc1 h1:hjSy6tcFQZ171igDaN5QHOw2n6vx40juYbC/x67CEhc=
|
||||
google.golang.org/genproto/googleapis/api v0.0.0-20240903143218-8af14fe29dc1/go.mod h1:qpvKtACPCQhAdu3PyQgV4l3LMXZEtft7y8QcarRsp9I=
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20241118233622-e639e219e697 h1:LWZqQOEjDyONlF1H6afSWpAL/znlREo2tHfLoe+8LMA=
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20241118233622-e639e219e697/go.mod h1:5uTbfoYQed2U9p3KIj2/Zzm02PYhndfdmML0qC3q3FU=
|
||||
google.golang.org/genproto v0.0.0-20241118233622-e639e219e697 h1:ToEetK57OidYuqD4Q5w+vfEnPvPpuTwedCNVohYJfNk=
|
||||
google.golang.org/genproto/googleapis/api v0.0.0-20241209162323-e6fa225c2576 h1:CkkIfIt50+lT6NHAVoRYEyAvQGFM7xEwXUUywFvEb3Q=
|
||||
google.golang.org/genproto/googleapis/api v0.0.0-20241209162323-e6fa225c2576/go.mod h1:1R3kvZ1dtP3+4p4d3G8uJ8rFk/fWlScl38vanWACI08=
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20241223144023-3abc09e42ca8 h1:TqExAhdPaB60Ux47Cn0oLV07rGnxZzIsaRhQaqS666A=
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20241223144023-3abc09e42ca8/go.mod h1:lcTa1sDdWEIHMWlITnIczmw5w60CF9ffkb8Z+DVmmjA=
|
||||
google.golang.org/grpc v1.68.0 h1:aHQeeJbo8zAkAa3pRzrVjZlbz6uSfeOXlJNQM0RAbz0=
|
||||
google.golang.org/grpc v1.68.0/go.mod h1:fmSPC5AsjSBCK54MyHRx48kpOti1/jRfOlwEWywNjWA=
|
||||
google.golang.org/grpc v1.68.1 h1:oI5oTa11+ng8r8XMMN7jAOmWfPZWbYpCFaMUTACxkM0=
|
||||
google.golang.org/grpc v1.68.1/go.mod h1:+q1XYFJjShcqn0QZHvCyeR4CXPA+llXIeUIfIe00waw=
|
||||
google.golang.org/grpc v1.69.0 h1:quSiOM1GJPmPH5XtU+BCoVXcDVJJAzNcoyfC2cCjGkI=
|
||||
google.golang.org/grpc v1.69.0/go.mod h1:vyjdE6jLBI76dgpDojsFGNaHlxdjXN9ghpnd2o7JGZ4=
|
||||
google.golang.org/grpc v1.69.2 h1:U3S9QEtbXC0bYNvRtcoklF3xGtLViumSYxWykJS+7AU=
|
||||
google.golang.org/grpc v1.69.2/go.mod h1:vyjdE6jLBI76dgpDojsFGNaHlxdjXN9ghpnd2o7JGZ4=
|
||||
google.golang.org/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/grpc v1.70.0 h1:pWFv03aZoHzlRKHWicjsZytKAiYCtNS0dHbXnIdq7jQ=
|
||||
google.golang.org/grpc v1.70.0/go.mod h1:ofIJqVKDXx/JiXrwr2IG4/zwdH9txy3IlF40RmcJSQw=
|
||||
google.golang.org/protobuf v1.36.5 h1:tPhr+woSbjfYvY6/GPufUoYizxw1cF/yFoxJ2fmpwlM=
|
||||
google.golang.org/protobuf v1.36.5/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 h1:YR8cESwS4TdDjEe65xsg0ogRM/Nc3DYOhEAlW+xobZo=
|
||||
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
|
||||
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
|
||||
gopkg.in/evanphx/json-patch.v4 v4.12.0 h1:n6jtcsulIzXPJaxegRbvFNNrZDjbij7ny3gmSPG+6V4=
|
||||
gopkg.in/evanphx/json-patch.v4 v4.12.0/go.mod h1:p8EYWUEYMpynmqDbY58zCKCFZw8pRWMG4EsWvDvM72M=
|
||||
gopkg.in/inf.v0 v0.9.1 h1:73M5CoZyi3ZLMOyDlQh031Cx6N9NDJ2Vvfl76EDAgDc=
|
||||
gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw=
|
||||
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
|
||||
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
@@ -319,3 +343,31 @@ gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
gotest.tools/v3 v3.0.2/go.mod h1:3SzNCllyD9/Y+b5r9JIKQ474KzkZyqLqEfYqMsX94Bk=
|
||||
gotest.tools/v3 v3.0.3 h1:4AuOwCGf4lLR9u3YOe2awrHygurzhO/HeQ6laiA6Sx0=
|
||||
gotest.tools/v3 v3.0.3/go.mod h1:Z7Lb0S5l+klDB31fvDQX8ss/FlKDxtlFlw3Oa8Ymbl8=
|
||||
k8s.io/api v0.32.1 h1:f562zw9cy+GvXzXf0CKlVQ7yHJVYzLfL6JAS4kOAaOc=
|
||||
k8s.io/api v0.32.1/go.mod h1:/Yi/BqkuueW1BgpoePYBRdDYfjPF5sgTr5+YqDZra5k=
|
||||
k8s.io/api v0.32.2 h1:bZrMLEkgizC24G9eViHGOPbW+aRo9duEISRIJKfdJuw=
|
||||
k8s.io/api v0.32.2/go.mod h1:hKlhk4x1sJyYnHENsrdCWw31FEmCijNGPJO5WzHiJ6Y=
|
||||
k8s.io/apimachinery v0.32.1 h1:683ENpaCBjma4CYqsmZyhEzrGz6cjn1MY/X2jB2hkZs=
|
||||
k8s.io/apimachinery v0.32.1/go.mod h1:GpHVgxoKlTxClKcteaeuF1Ul/lDVb74KpZcxcmLDElE=
|
||||
k8s.io/apimachinery v0.32.2 h1:yoQBR9ZGkA6Rgmhbp/yuT9/g+4lxtsGYwW6dR6BDPLQ=
|
||||
k8s.io/apimachinery v0.32.2/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/client-go v0.32.2 h1:4dYCD4Nz+9RApM2b/3BtVvBHw54QjMFUl1OLcJG5yOA=
|
||||
k8s.io/client-go v0.32.2/go.mod h1:fpZ4oJXclZ3r2nDOv+Ux3XcJutfrwjKTCHz2H3sww94=
|
||||
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/metrics v0.32.2 h1:7t/rZzTHFrGa9f94XcgLlm3ToAuJtdlHANcJEHlYl9g=
|
||||
k8s.io/metrics v0.32.2/go.mod h1:VL3nJpzcgB6L5nSljkkzoE0nilZhVgcjCfNRgoylaIQ=
|
||||
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=
|
||||
|
||||
@@ -13,7 +13,7 @@ import (
|
||||
"encoding/json"
|
||||
|
||||
"github.com/amir20/dozzle/internal/agent/pb"
|
||||
"github.com/amir20/dozzle/internal/docker"
|
||||
"github.com/amir20/dozzle/internal/container"
|
||||
"github.com/amir20/dozzle/internal/utils"
|
||||
"github.com/rs/zerolog/log"
|
||||
orderedmap "github.com/wk8/go-ordered-map/v2"
|
||||
@@ -89,7 +89,7 @@ func rpcErrToErr(err error) error {
|
||||
}
|
||||
}
|
||||
|
||||
func (c *Client) LogsBetweenDates(ctx context.Context, containerID string, since time.Time, until time.Time, std docker.StdType) (<-chan *docker.LogEvent, error) {
|
||||
func (c *Client) LogsBetweenDates(ctx context.Context, containerID string, since time.Time, until time.Time, std container.StdType) (<-chan *container.LogEvent, error) {
|
||||
stream, err := c.client.LogsBetweenDates(ctx, &pb.LogsBetweenDatesRequest{
|
||||
ContainerId: containerID,
|
||||
Since: timestamppb.New(since),
|
||||
@@ -101,7 +101,7 @@ func (c *Client) LogsBetweenDates(ctx context.Context, containerID string, since
|
||||
return nil, err
|
||||
}
|
||||
|
||||
events := make(chan *docker.LogEvent)
|
||||
events := make(chan *container.LogEvent)
|
||||
|
||||
go func() {
|
||||
sendLogs(stream, events)
|
||||
@@ -111,7 +111,7 @@ func (c *Client) LogsBetweenDates(ctx context.Context, containerID string, since
|
||||
return events, nil
|
||||
}
|
||||
|
||||
func (c *Client) StreamContainerLogs(ctx context.Context, containerID string, since time.Time, std docker.StdType, events chan<- *docker.LogEvent) error {
|
||||
func (c *Client) StreamContainerLogs(ctx context.Context, containerID string, since time.Time, std container.StdType, events chan<- *container.LogEvent) error {
|
||||
stream, err := c.client.StreamLogs(ctx, &pb.StreamLogsRequest{
|
||||
ContainerId: containerID,
|
||||
Since: timestamppb.New(since),
|
||||
@@ -125,7 +125,7 @@ func (c *Client) StreamContainerLogs(ctx context.Context, containerID string, si
|
||||
return sendLogs(stream, events)
|
||||
}
|
||||
|
||||
func sendLogs(stream pb.AgentService_StreamLogsClient, events chan<- *docker.LogEvent) error {
|
||||
func sendLogs(stream pb.AgentService_StreamLogsClient, events chan<- *container.LogEvent) error {
|
||||
for {
|
||||
resp, err := stream.Recv()
|
||||
if err != nil {
|
||||
@@ -151,19 +151,19 @@ func sendLogs(stream pb.AgentService_StreamLogsClient, events chan<- *docker.Log
|
||||
continue
|
||||
}
|
||||
|
||||
events <- &docker.LogEvent{
|
||||
events <- &container.LogEvent{
|
||||
Id: resp.Event.Id,
|
||||
ContainerID: resp.Event.ContainerId,
|
||||
Message: message,
|
||||
Timestamp: resp.Event.Timestamp.AsTime().Unix(),
|
||||
Position: docker.LogPosition(resp.Event.Position),
|
||||
Position: container.LogPosition(resp.Event.Position),
|
||||
Level: resp.Event.Level,
|
||||
Stream: resp.Event.Stream,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (c *Client) StreamRawBytes(ctx context.Context, containerID string, since time.Time, until time.Time, std docker.StdType) (io.ReadCloser, error) {
|
||||
func (c *Client) StreamRawBytes(ctx context.Context, containerID string, since time.Time, until time.Time, std container.StdType) (io.ReadCloser, error) {
|
||||
out, err := c.client.StreamRawBytes(context.Background(), &pb.StreamRawBytesRequest{
|
||||
ContainerId: containerID,
|
||||
Since: timestamppb.New(since),
|
||||
@@ -199,7 +199,7 @@ func (c *Client) StreamRawBytes(ctx context.Context, containerID string, since t
|
||||
return r, nil
|
||||
}
|
||||
|
||||
func (c *Client) StreamStats(ctx context.Context, stats chan<- docker.ContainerStat) error {
|
||||
func (c *Client) StreamStats(ctx context.Context, stats chan<- container.ContainerStat) error {
|
||||
stream, err := c.client.StreamStats(ctx, &pb.StreamStatsRequest{})
|
||||
if err != nil {
|
||||
return err
|
||||
@@ -211,7 +211,7 @@ func (c *Client) StreamStats(ctx context.Context, stats chan<- docker.ContainerS
|
||||
return rpcErrToErr(err)
|
||||
}
|
||||
|
||||
stats <- docker.ContainerStat{
|
||||
stats <- container.ContainerStat{
|
||||
CPUPercent: resp.Stat.CpuPercent,
|
||||
MemoryPercent: resp.Stat.MemoryPercent,
|
||||
MemoryUsage: resp.Stat.MemoryUsage,
|
||||
@@ -220,7 +220,7 @@ func (c *Client) StreamStats(ctx context.Context, stats chan<- docker.ContainerS
|
||||
}
|
||||
}
|
||||
|
||||
func (c *Client) StreamEvents(ctx context.Context, events chan<- docker.ContainerEvent) error {
|
||||
func (c *Client) StreamEvents(ctx context.Context, events chan<- container.ContainerEvent) error {
|
||||
stream, err := c.client.StreamEvents(ctx, &pb.StreamEventsRequest{})
|
||||
if err != nil {
|
||||
return err
|
||||
@@ -232,15 +232,16 @@ func (c *Client) StreamEvents(ctx context.Context, events chan<- docker.Containe
|
||||
return rpcErrToErr(err)
|
||||
}
|
||||
|
||||
events <- docker.ContainerEvent{
|
||||
events <- container.ContainerEvent{
|
||||
ActorID: resp.Event.ActorId,
|
||||
Name: resp.Event.Name,
|
||||
Host: resp.Event.Host,
|
||||
Time: resp.Event.Timestamp.AsTime(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (c *Client) StreamNewContainers(ctx context.Context, containers chan<- docker.Container) error {
|
||||
func (c *Client) StreamNewContainers(ctx context.Context, containers chan<- container.Container) error {
|
||||
stream, err := c.client.StreamContainerStarted(ctx, &pb.StreamContainerStartedRequest{})
|
||||
if err != nil {
|
||||
return err
|
||||
@@ -252,7 +253,7 @@ func (c *Client) StreamNewContainers(ctx context.Context, containers chan<- dock
|
||||
return rpcErrToErr(err)
|
||||
}
|
||||
|
||||
containers <- docker.Container{
|
||||
containers <- container.Container{
|
||||
ID: resp.Container.Id,
|
||||
Name: resp.Container.Name,
|
||||
Image: resp.Container.Image,
|
||||
@@ -270,16 +271,16 @@ func (c *Client) StreamNewContainers(ctx context.Context, containers chan<- dock
|
||||
}
|
||||
}
|
||||
|
||||
func (c *Client) FindContainer(ctx context.Context, containerID string) (docker.Container, error) {
|
||||
func (c *Client) FindContainer(ctx context.Context, containerID string) (container.Container, error) {
|
||||
response, err := c.client.FindContainer(ctx, &pb.FindContainerRequest{ContainerId: containerID})
|
||||
if err != nil {
|
||||
return docker.Container{}, err
|
||||
return container.Container{}, err
|
||||
}
|
||||
|
||||
var stats []docker.ContainerStat
|
||||
var stats []container.ContainerStat
|
||||
|
||||
for _, stat := range response.Container.Stats {
|
||||
stats = append(stats, docker.ContainerStat{
|
||||
stats = append(stats, container.ContainerStat{
|
||||
ID: stat.Id,
|
||||
CPUPercent: stat.CpuPercent,
|
||||
MemoryPercent: stat.MemoryPercent,
|
||||
@@ -287,7 +288,7 @@ func (c *Client) FindContainer(ctx context.Context, containerID string) (docker.
|
||||
})
|
||||
}
|
||||
|
||||
return docker.Container{
|
||||
return container.Container{
|
||||
ID: response.Container.Id,
|
||||
Name: response.Container.Name,
|
||||
Image: response.Container.Image,
|
||||
@@ -305,12 +306,12 @@ func (c *Client) FindContainer(ctx context.Context, containerID string) (docker.
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (c *Client) ListContainers(ctx context.Context, filter docker.ContainerFilter) ([]docker.Container, error) {
|
||||
func (c *Client) ListContainers(ctx context.Context, labels container.ContainerLabels) ([]container.Container, error) {
|
||||
in := &pb.ListContainersRequest{}
|
||||
|
||||
if filter != nil {
|
||||
if labels != nil {
|
||||
in.Filter = make(map[string]*pb.RepeatedString)
|
||||
for k, v := range filter {
|
||||
for k, v := range labels {
|
||||
in.Filter[k] = &pb.RepeatedString{Values: v}
|
||||
}
|
||||
}
|
||||
@@ -320,11 +321,11 @@ func (c *Client) ListContainers(ctx context.Context, filter docker.ContainerFilt
|
||||
return nil, err
|
||||
}
|
||||
|
||||
containers := make([]docker.Container, 0)
|
||||
for _, container := range response.Containers {
|
||||
var stats []docker.ContainerStat
|
||||
for _, stat := range container.Stats {
|
||||
stats = append(stats, docker.ContainerStat{
|
||||
containers := make([]container.Container, 0)
|
||||
for _, c := range response.Containers {
|
||||
var stats []container.ContainerStat
|
||||
for _, stat := range c.Stats {
|
||||
stats = append(stats, container.ContainerStat{
|
||||
ID: stat.Id,
|
||||
CPUPercent: stat.CpuPercent,
|
||||
MemoryPercent: stat.MemoryPercent,
|
||||
@@ -332,20 +333,20 @@ func (c *Client) ListContainers(ctx context.Context, filter docker.ContainerFilt
|
||||
})
|
||||
}
|
||||
|
||||
containers = append(containers, docker.Container{
|
||||
ID: container.Id,
|
||||
Name: container.Name,
|
||||
Image: container.Image,
|
||||
Labels: container.Labels,
|
||||
Group: container.Group,
|
||||
Created: container.Created.AsTime(),
|
||||
State: container.State,
|
||||
Health: container.Health,
|
||||
Host: container.Host,
|
||||
Tty: container.Tty,
|
||||
Command: container.Command,
|
||||
StartedAt: container.Started.AsTime(),
|
||||
FinishedAt: container.Finished.AsTime(),
|
||||
containers = append(containers, container.Container{
|
||||
ID: c.Id,
|
||||
Name: c.Name,
|
||||
Image: c.Image,
|
||||
Labels: c.Labels,
|
||||
Group: c.Group,
|
||||
Created: c.Created.AsTime(),
|
||||
State: c.State,
|
||||
Health: c.Health,
|
||||
Host: c.Host,
|
||||
Tty: c.Tty,
|
||||
Command: c.Command,
|
||||
StartedAt: c.Started.AsTime(),
|
||||
FinishedAt: c.Finished.AsTime(),
|
||||
Stats: utils.RingBufferFrom(300, stats),
|
||||
})
|
||||
}
|
||||
@@ -353,17 +354,17 @@ func (c *Client) ListContainers(ctx context.Context, filter docker.ContainerFilt
|
||||
return containers, nil
|
||||
}
|
||||
|
||||
func (c *Client) Host(ctx context.Context) (docker.Host, error) {
|
||||
func (c *Client) Host(ctx context.Context) (container.Host, error) {
|
||||
info, err := c.client.HostInfo(ctx, &pb.HostInfoRequest{})
|
||||
if err != nil {
|
||||
return docker.Host{
|
||||
return container.Host{
|
||||
Endpoint: c.endpoint,
|
||||
Type: "agent",
|
||||
Available: false,
|
||||
}, err
|
||||
}
|
||||
|
||||
return docker.Host{
|
||||
return container.Host{
|
||||
ID: info.Host.Id,
|
||||
Name: info.Host.Name,
|
||||
NCPU: int(info.Host.CpuCores),
|
||||
@@ -375,16 +376,16 @@ func (c *Client) Host(ctx context.Context) (docker.Host, error) {
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (c *Client) ContainerAction(ctx context.Context, containerId string, action docker.ContainerAction) error {
|
||||
func (c *Client) ContainerAction(ctx context.Context, containerId string, action container.ContainerAction) error {
|
||||
var containerAction pb.ContainerAction
|
||||
switch action {
|
||||
case docker.Start:
|
||||
case container.Start:
|
||||
containerAction = pb.ContainerAction_Start
|
||||
|
||||
case docker.Stop:
|
||||
case container.Stop:
|
||||
containerAction = pb.ContainerAction_Stop
|
||||
|
||||
case docker.Restart:
|
||||
case container.Restart:
|
||||
containerAction = pb.ContainerAction_Restart
|
||||
|
||||
}
|
||||
|
||||
@@ -10,7 +10,7 @@ import (
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/amir20/dozzle/internal/docker"
|
||||
"github.com/amir20/dozzle/internal/container"
|
||||
"github.com/amir20/dozzle/internal/utils"
|
||||
"github.com/docker/docker/api/types/system"
|
||||
"github.com/stretchr/testify/assert"
|
||||
@@ -28,46 +28,46 @@ var client *MockedClient
|
||||
|
||||
type MockedClient struct {
|
||||
mock.Mock
|
||||
docker.Client
|
||||
container.Client
|
||||
}
|
||||
|
||||
func (m *MockedClient) FindContainer(ctx context.Context, id string) (docker.Container, error) {
|
||||
func (m *MockedClient) FindContainer(ctx context.Context, id string) (container.Container, error) {
|
||||
args := m.Called(ctx, id)
|
||||
return args.Get(0).(docker.Container), args.Error(1)
|
||||
return args.Get(0).(container.Container), args.Error(1)
|
||||
}
|
||||
|
||||
func (m *MockedClient) ContainerActions(ctx context.Context, action docker.ContainerAction, containerID string) error {
|
||||
func (m *MockedClient) ContainerActions(ctx context.Context, action container.ContainerAction, containerID string) error {
|
||||
args := m.Called(ctx, action, containerID)
|
||||
return args.Error(0)
|
||||
}
|
||||
|
||||
func (m *MockedClient) ContainerEvents(ctx context.Context, events chan<- docker.ContainerEvent) error {
|
||||
func (m *MockedClient) ContainerEvents(ctx context.Context, events chan<- container.ContainerEvent) error {
|
||||
args := m.Called(ctx, events)
|
||||
return args.Error(0)
|
||||
}
|
||||
|
||||
func (m *MockedClient) ListContainers(ctx context.Context, filter docker.ContainerFilter) ([]docker.Container, error) {
|
||||
func (m *MockedClient) ListContainers(ctx context.Context, filter container.ContainerLabels) ([]container.Container, error) {
|
||||
args := m.Called(ctx, filter)
|
||||
return args.Get(0).([]docker.Container), args.Error(1)
|
||||
return args.Get(0).([]container.Container), args.Error(1)
|
||||
}
|
||||
|
||||
func (m *MockedClient) ContainerLogs(ctx context.Context, id string, since time.Time, stdType docker.StdType) (io.ReadCloser, error) {
|
||||
func (m *MockedClient) ContainerLogs(ctx context.Context, id string, since time.Time, stdType container.StdType) (io.ReadCloser, error) {
|
||||
args := m.Called(ctx, id, since, stdType)
|
||||
return args.Get(0).(io.ReadCloser), args.Error(1)
|
||||
}
|
||||
|
||||
func (m *MockedClient) ContainerStats(context.Context, string, chan<- docker.ContainerStat) error {
|
||||
func (m *MockedClient) ContainerStats(context.Context, string, chan<- container.ContainerStat) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *MockedClient) ContainerLogsBetweenDates(ctx context.Context, id string, from time.Time, to time.Time, stdType docker.StdType) (io.ReadCloser, error) {
|
||||
func (m *MockedClient) ContainerLogsBetweenDates(ctx context.Context, id string, from time.Time, to time.Time, stdType container.StdType) (io.ReadCloser, error) {
|
||||
args := m.Called(ctx, id, from, to, stdType)
|
||||
return args.Get(0).(io.ReadCloser), args.Error(1)
|
||||
}
|
||||
|
||||
func (m *MockedClient) Host() docker.Host {
|
||||
func (m *MockedClient) Host() container.Host {
|
||||
args := m.Called()
|
||||
return args.Get(0).(docker.Host)
|
||||
return args.Get(0).(container.Host)
|
||||
}
|
||||
|
||||
func (m *MockedClient) IsSwarmMode() bool {
|
||||
@@ -92,7 +92,7 @@ func init() {
|
||||
}
|
||||
|
||||
client = &MockedClient{}
|
||||
client.On("ListContainers", mock.Anything, mock.Anything).Return([]docker.Container{
|
||||
client.On("ListContainers", mock.Anything, mock.Anything).Return([]container.Container{
|
||||
{
|
||||
ID: "123456",
|
||||
Name: "test",
|
||||
@@ -101,17 +101,17 @@ func init() {
|
||||
},
|
||||
}, nil)
|
||||
|
||||
client.On("Host").Return(docker.Host{
|
||||
client.On("Host").Return(container.Host{
|
||||
ID: "localhost",
|
||||
Endpoint: "local",
|
||||
Name: "local",
|
||||
})
|
||||
|
||||
client.On("ContainerEvents", mock.Anything, mock.AnythingOfType("chan<- docker.ContainerEvent")).Return(nil).Run(func(args mock.Arguments) {
|
||||
client.On("ContainerEvents", mock.Anything, mock.AnythingOfType("chan<- container.ContainerEvent")).Return(nil).Run(func(args mock.Arguments) {
|
||||
time.Sleep(5 * time.Second)
|
||||
})
|
||||
|
||||
client.On("FindContainer", mock.Anything, "123456").Return(docker.Container{
|
||||
client.On("FindContainer", mock.Anything, "123456").Return(container.Container{
|
||||
ID: "123456",
|
||||
Name: "test",
|
||||
Host: "localhost",
|
||||
@@ -124,13 +124,13 @@ func init() {
|
||||
Labels: map[string]string{
|
||||
"test": "test",
|
||||
},
|
||||
Stats: utils.NewRingBuffer[docker.ContainerStat](300),
|
||||
Stats: utils.NewRingBuffer[container.ContainerStat](300),
|
||||
Created: time.Date(2020, 1, 1, 0, 0, 0, 0, time.UTC),
|
||||
StartedAt: time.Date(2020, 1, 1, 0, 0, 0, 0, time.UTC),
|
||||
FinishedAt: time.Date(2020, 1, 1, 0, 0, 0, 0, time.UTC),
|
||||
}, nil)
|
||||
|
||||
server, _ := NewServer(client, certs, "test", docker.ContainerFilter{})
|
||||
server, _ := NewServer(client, certs, "test", container.ContainerLabels{})
|
||||
|
||||
go server.Serve(lis)
|
||||
}
|
||||
@@ -145,9 +145,9 @@ func TestFindContainer(t *testing.T) {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
container, _ := rpc.FindContainer(context.Background(), "123456")
|
||||
c, _ := rpc.FindContainer(context.Background(), "123456")
|
||||
|
||||
assert.Equal(t, container, docker.Container{
|
||||
assert.Equal(t, c, container.Container{
|
||||
ID: "123456",
|
||||
Name: "test",
|
||||
Host: "localhost",
|
||||
@@ -160,7 +160,7 @@ func TestFindContainer(t *testing.T) {
|
||||
Labels: map[string]string{
|
||||
"test": "test",
|
||||
},
|
||||
Stats: utils.NewRingBuffer[docker.ContainerStat](300),
|
||||
Stats: utils.NewRingBuffer[container.ContainerStat](300),
|
||||
Created: time.Date(2020, 1, 1, 0, 0, 0, 0, time.UTC),
|
||||
StartedAt: time.Date(2020, 1, 1, 0, 0, 0, 0, time.UTC),
|
||||
FinishedAt: time.Date(2020, 1, 1, 0, 0, 0, 0, time.UTC),
|
||||
@@ -173,9 +173,9 @@ func TestListContainers(t *testing.T) {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
containers, _ := rpc.ListContainers(context.Background(), docker.ContainerFilter{})
|
||||
containers, _ := rpc.ListContainers(context.Background(), container.ContainerLabels{})
|
||||
|
||||
assert.Equal(t, containers, []docker.Container{
|
||||
assert.Equal(t, containers, []container.Container{
|
||||
{
|
||||
ID: "123456",
|
||||
Name: "test",
|
||||
@@ -189,7 +189,7 @@ func TestListContainers(t *testing.T) {
|
||||
Labels: map[string]string{
|
||||
"test": "test",
|
||||
},
|
||||
Stats: utils.NewRingBuffer[docker.ContainerStat](300),
|
||||
Stats: utils.NewRingBuffer[container.ContainerStat](300),
|
||||
Created: time.Date(2020, 1, 1, 0, 0, 0, 0, time.UTC),
|
||||
StartedAt: time.Date(2020, 1, 1, 0, 0, 0, 0, time.UTC),
|
||||
FinishedAt: time.Date(2020, 1, 1, 0, 0, 0, 0, time.UTC),
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
// Code generated by protoc-gen-go. DO NOT EDIT.
|
||||
// versions:
|
||||
// protoc-gen-go v1.35.1
|
||||
// protoc v5.29.2
|
||||
// protoc-gen-go v1.36.2
|
||||
// protoc v5.29.3
|
||||
// source: rpc.proto
|
||||
|
||||
package pb
|
||||
@@ -22,11 +22,10 @@ const (
|
||||
)
|
||||
|
||||
type ListContainersRequest struct {
|
||||
state protoimpl.MessageState
|
||||
sizeCache protoimpl.SizeCache
|
||||
state protoimpl.MessageState `protogen:"open.v1"`
|
||||
Filter map[string]*RepeatedString `protobuf:"bytes,1,rep,name=filter,proto3" json:"filter,omitempty" protobuf_key:"bytes,1,opt,name=key" protobuf_val:"bytes,2,opt,name=value"`
|
||||
unknownFields protoimpl.UnknownFields
|
||||
|
||||
Filter map[string]*RepeatedString `protobuf:"bytes,1,rep,name=filter,proto3" json:"filter,omitempty" protobuf_key:"bytes,1,opt,name=key,proto3" protobuf_val:"bytes,2,opt,name=value,proto3"`
|
||||
sizeCache protoimpl.SizeCache
|
||||
}
|
||||
|
||||
func (x *ListContainersRequest) Reset() {
|
||||
@@ -67,11 +66,10 @@ func (x *ListContainersRequest) GetFilter() map[string]*RepeatedString {
|
||||
}
|
||||
|
||||
type RepeatedString struct {
|
||||
state protoimpl.MessageState
|
||||
sizeCache protoimpl.SizeCache
|
||||
state protoimpl.MessageState `protogen:"open.v1"`
|
||||
Values []string `protobuf:"bytes,1,rep,name=values,proto3" json:"values,omitempty"`
|
||||
unknownFields protoimpl.UnknownFields
|
||||
|
||||
Values []string `protobuf:"bytes,1,rep,name=values,proto3" json:"values,omitempty"`
|
||||
sizeCache protoimpl.SizeCache
|
||||
}
|
||||
|
||||
func (x *RepeatedString) Reset() {
|
||||
@@ -112,11 +110,10 @@ func (x *RepeatedString) GetValues() []string {
|
||||
}
|
||||
|
||||
type ListContainersResponse struct {
|
||||
state protoimpl.MessageState
|
||||
sizeCache protoimpl.SizeCache
|
||||
state protoimpl.MessageState `protogen:"open.v1"`
|
||||
Containers []*Container `protobuf:"bytes,1,rep,name=containers,proto3" json:"containers,omitempty"`
|
||||
unknownFields protoimpl.UnknownFields
|
||||
|
||||
Containers []*Container `protobuf:"bytes,1,rep,name=containers,proto3" json:"containers,omitempty"`
|
||||
sizeCache protoimpl.SizeCache
|
||||
}
|
||||
|
||||
func (x *ListContainersResponse) Reset() {
|
||||
@@ -157,12 +154,11 @@ func (x *ListContainersResponse) GetContainers() []*Container {
|
||||
}
|
||||
|
||||
type FindContainerRequest struct {
|
||||
state protoimpl.MessageState
|
||||
sizeCache protoimpl.SizeCache
|
||||
state protoimpl.MessageState `protogen:"open.v1"`
|
||||
ContainerId string `protobuf:"bytes,1,opt,name=containerId,proto3" json:"containerId,omitempty"`
|
||||
Filter map[string]*RepeatedString `protobuf:"bytes,2,rep,name=filter,proto3" json:"filter,omitempty" protobuf_key:"bytes,1,opt,name=key" protobuf_val:"bytes,2,opt,name=value"`
|
||||
unknownFields protoimpl.UnknownFields
|
||||
|
||||
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"`
|
||||
sizeCache protoimpl.SizeCache
|
||||
}
|
||||
|
||||
func (x *FindContainerRequest) Reset() {
|
||||
@@ -210,11 +206,10 @@ func (x *FindContainerRequest) GetFilter() map[string]*RepeatedString {
|
||||
}
|
||||
|
||||
type FindContainerResponse struct {
|
||||
state protoimpl.MessageState
|
||||
sizeCache protoimpl.SizeCache
|
||||
state protoimpl.MessageState `protogen:"open.v1"`
|
||||
Container *Container `protobuf:"bytes,1,opt,name=container,proto3" json:"container,omitempty"`
|
||||
unknownFields protoimpl.UnknownFields
|
||||
|
||||
Container *Container `protobuf:"bytes,1,opt,name=container,proto3" json:"container,omitempty"`
|
||||
sizeCache protoimpl.SizeCache
|
||||
}
|
||||
|
||||
func (x *FindContainerResponse) Reset() {
|
||||
@@ -255,13 +250,12 @@ func (x *FindContainerResponse) GetContainer() *Container {
|
||||
}
|
||||
|
||||
type StreamLogsRequest struct {
|
||||
state protoimpl.MessageState
|
||||
sizeCache protoimpl.SizeCache
|
||||
state protoimpl.MessageState `protogen:"open.v1"`
|
||||
ContainerId string `protobuf:"bytes,1,opt,name=containerId,proto3" json:"containerId,omitempty"`
|
||||
Since *timestamppb.Timestamp `protobuf:"bytes,2,opt,name=since,proto3" json:"since,omitempty"`
|
||||
StreamTypes int32 `protobuf:"varint,3,opt,name=streamTypes,proto3" json:"streamTypes,omitempty"`
|
||||
unknownFields protoimpl.UnknownFields
|
||||
|
||||
ContainerId string `protobuf:"bytes,1,opt,name=containerId,proto3" json:"containerId,omitempty"`
|
||||
Since *timestamppb.Timestamp `protobuf:"bytes,2,opt,name=since,proto3" json:"since,omitempty"`
|
||||
StreamTypes int32 `protobuf:"varint,3,opt,name=streamTypes,proto3" json:"streamTypes,omitempty"`
|
||||
sizeCache protoimpl.SizeCache
|
||||
}
|
||||
|
||||
func (x *StreamLogsRequest) Reset() {
|
||||
@@ -316,11 +310,10 @@ func (x *StreamLogsRequest) GetStreamTypes() int32 {
|
||||
}
|
||||
|
||||
type StreamLogsResponse struct {
|
||||
state protoimpl.MessageState
|
||||
sizeCache protoimpl.SizeCache
|
||||
state protoimpl.MessageState `protogen:"open.v1"`
|
||||
Event *LogEvent `protobuf:"bytes,1,opt,name=event,proto3" json:"event,omitempty"`
|
||||
unknownFields protoimpl.UnknownFields
|
||||
|
||||
Event *LogEvent `protobuf:"bytes,1,opt,name=event,proto3" json:"event,omitempty"`
|
||||
sizeCache protoimpl.SizeCache
|
||||
}
|
||||
|
||||
func (x *StreamLogsResponse) Reset() {
|
||||
@@ -361,14 +354,13 @@ func (x *StreamLogsResponse) GetEvent() *LogEvent {
|
||||
}
|
||||
|
||||
type LogsBetweenDatesRequest struct {
|
||||
state protoimpl.MessageState
|
||||
sizeCache protoimpl.SizeCache
|
||||
state protoimpl.MessageState `protogen:"open.v1"`
|
||||
ContainerId string `protobuf:"bytes,1,opt,name=containerId,proto3" json:"containerId,omitempty"`
|
||||
Since *timestamppb.Timestamp `protobuf:"bytes,2,opt,name=since,proto3" json:"since,omitempty"`
|
||||
Until *timestamppb.Timestamp `protobuf:"bytes,3,opt,name=until,proto3" json:"until,omitempty"`
|
||||
StreamTypes int32 `protobuf:"varint,4,opt,name=streamTypes,proto3" json:"streamTypes,omitempty"`
|
||||
unknownFields protoimpl.UnknownFields
|
||||
|
||||
ContainerId string `protobuf:"bytes,1,opt,name=containerId,proto3" json:"containerId,omitempty"`
|
||||
Since *timestamppb.Timestamp `protobuf:"bytes,2,opt,name=since,proto3" json:"since,omitempty"`
|
||||
Until *timestamppb.Timestamp `protobuf:"bytes,3,opt,name=until,proto3" json:"until,omitempty"`
|
||||
StreamTypes int32 `protobuf:"varint,4,opt,name=streamTypes,proto3" json:"streamTypes,omitempty"`
|
||||
sizeCache protoimpl.SizeCache
|
||||
}
|
||||
|
||||
func (x *LogsBetweenDatesRequest) Reset() {
|
||||
@@ -430,14 +422,13 @@ func (x *LogsBetweenDatesRequest) GetStreamTypes() int32 {
|
||||
}
|
||||
|
||||
type StreamRawBytesRequest struct {
|
||||
state protoimpl.MessageState
|
||||
sizeCache protoimpl.SizeCache
|
||||
state protoimpl.MessageState `protogen:"open.v1"`
|
||||
ContainerId string `protobuf:"bytes,1,opt,name=containerId,proto3" json:"containerId,omitempty"`
|
||||
Since *timestamppb.Timestamp `protobuf:"bytes,2,opt,name=since,proto3" json:"since,omitempty"`
|
||||
Until *timestamppb.Timestamp `protobuf:"bytes,3,opt,name=until,proto3" json:"until,omitempty"`
|
||||
StreamTypes int32 `protobuf:"varint,4,opt,name=streamTypes,proto3" json:"streamTypes,omitempty"`
|
||||
unknownFields protoimpl.UnknownFields
|
||||
|
||||
ContainerId string `protobuf:"bytes,1,opt,name=containerId,proto3" json:"containerId,omitempty"`
|
||||
Since *timestamppb.Timestamp `protobuf:"bytes,2,opt,name=since,proto3" json:"since,omitempty"`
|
||||
Until *timestamppb.Timestamp `protobuf:"bytes,3,opt,name=until,proto3" json:"until,omitempty"`
|
||||
StreamTypes int32 `protobuf:"varint,4,opt,name=streamTypes,proto3" json:"streamTypes,omitempty"`
|
||||
sizeCache protoimpl.SizeCache
|
||||
}
|
||||
|
||||
func (x *StreamRawBytesRequest) Reset() {
|
||||
@@ -499,11 +490,10 @@ func (x *StreamRawBytesRequest) GetStreamTypes() int32 {
|
||||
}
|
||||
|
||||
type StreamRawBytesResponse struct {
|
||||
state protoimpl.MessageState
|
||||
sizeCache protoimpl.SizeCache
|
||||
state protoimpl.MessageState `protogen:"open.v1"`
|
||||
Data []byte `protobuf:"bytes,1,opt,name=data,proto3" json:"data,omitempty"`
|
||||
unknownFields protoimpl.UnknownFields
|
||||
|
||||
Data []byte `protobuf:"bytes,1,opt,name=data,proto3" json:"data,omitempty"`
|
||||
sizeCache protoimpl.SizeCache
|
||||
}
|
||||
|
||||
func (x *StreamRawBytesResponse) Reset() {
|
||||
@@ -544,9 +534,9 @@ func (x *StreamRawBytesResponse) GetData() []byte {
|
||||
}
|
||||
|
||||
type StreamEventsRequest struct {
|
||||
state protoimpl.MessageState
|
||||
sizeCache protoimpl.SizeCache
|
||||
state protoimpl.MessageState `protogen:"open.v1"`
|
||||
unknownFields protoimpl.UnknownFields
|
||||
sizeCache protoimpl.SizeCache
|
||||
}
|
||||
|
||||
func (x *StreamEventsRequest) Reset() {
|
||||
@@ -580,11 +570,10 @@ func (*StreamEventsRequest) Descriptor() ([]byte, []int) {
|
||||
}
|
||||
|
||||
type StreamEventsResponse struct {
|
||||
state protoimpl.MessageState
|
||||
sizeCache protoimpl.SizeCache
|
||||
state protoimpl.MessageState `protogen:"open.v1"`
|
||||
Event *ContainerEvent `protobuf:"bytes,1,opt,name=event,proto3" json:"event,omitempty"`
|
||||
unknownFields protoimpl.UnknownFields
|
||||
|
||||
Event *ContainerEvent `protobuf:"bytes,1,opt,name=event,proto3" json:"event,omitempty"`
|
||||
sizeCache protoimpl.SizeCache
|
||||
}
|
||||
|
||||
func (x *StreamEventsResponse) Reset() {
|
||||
@@ -625,9 +614,9 @@ func (x *StreamEventsResponse) GetEvent() *ContainerEvent {
|
||||
}
|
||||
|
||||
type StreamStatsRequest struct {
|
||||
state protoimpl.MessageState
|
||||
sizeCache protoimpl.SizeCache
|
||||
state protoimpl.MessageState `protogen:"open.v1"`
|
||||
unknownFields protoimpl.UnknownFields
|
||||
sizeCache protoimpl.SizeCache
|
||||
}
|
||||
|
||||
func (x *StreamStatsRequest) Reset() {
|
||||
@@ -661,11 +650,10 @@ func (*StreamStatsRequest) Descriptor() ([]byte, []int) {
|
||||
}
|
||||
|
||||
type StreamStatsResponse struct {
|
||||
state protoimpl.MessageState
|
||||
sizeCache protoimpl.SizeCache
|
||||
state protoimpl.MessageState `protogen:"open.v1"`
|
||||
Stat *ContainerStat `protobuf:"bytes,1,opt,name=stat,proto3" json:"stat,omitempty"`
|
||||
unknownFields protoimpl.UnknownFields
|
||||
|
||||
Stat *ContainerStat `protobuf:"bytes,1,opt,name=stat,proto3" json:"stat,omitempty"`
|
||||
sizeCache protoimpl.SizeCache
|
||||
}
|
||||
|
||||
func (x *StreamStatsResponse) Reset() {
|
||||
@@ -706,9 +694,9 @@ func (x *StreamStatsResponse) GetStat() *ContainerStat {
|
||||
}
|
||||
|
||||
type HostInfoRequest struct {
|
||||
state protoimpl.MessageState
|
||||
sizeCache protoimpl.SizeCache
|
||||
state protoimpl.MessageState `protogen:"open.v1"`
|
||||
unknownFields protoimpl.UnknownFields
|
||||
sizeCache protoimpl.SizeCache
|
||||
}
|
||||
|
||||
func (x *HostInfoRequest) Reset() {
|
||||
@@ -742,11 +730,10 @@ func (*HostInfoRequest) Descriptor() ([]byte, []int) {
|
||||
}
|
||||
|
||||
type HostInfoResponse struct {
|
||||
state protoimpl.MessageState
|
||||
sizeCache protoimpl.SizeCache
|
||||
state protoimpl.MessageState `protogen:"open.v1"`
|
||||
Host *Host `protobuf:"bytes,1,opt,name=host,proto3" json:"host,omitempty"`
|
||||
unknownFields protoimpl.UnknownFields
|
||||
|
||||
Host *Host `protobuf:"bytes,1,opt,name=host,proto3" json:"host,omitempty"`
|
||||
sizeCache protoimpl.SizeCache
|
||||
}
|
||||
|
||||
func (x *HostInfoResponse) Reset() {
|
||||
@@ -787,9 +774,9 @@ func (x *HostInfoResponse) GetHost() *Host {
|
||||
}
|
||||
|
||||
type StreamContainerStartedRequest struct {
|
||||
state protoimpl.MessageState
|
||||
sizeCache protoimpl.SizeCache
|
||||
state protoimpl.MessageState `protogen:"open.v1"`
|
||||
unknownFields protoimpl.UnknownFields
|
||||
sizeCache protoimpl.SizeCache
|
||||
}
|
||||
|
||||
func (x *StreamContainerStartedRequest) Reset() {
|
||||
@@ -823,11 +810,10 @@ func (*StreamContainerStartedRequest) Descriptor() ([]byte, []int) {
|
||||
}
|
||||
|
||||
type StreamContainerStartedResponse struct {
|
||||
state protoimpl.MessageState
|
||||
sizeCache protoimpl.SizeCache
|
||||
state protoimpl.MessageState `protogen:"open.v1"`
|
||||
Container *Container `protobuf:"bytes,1,opt,name=container,proto3" json:"container,omitempty"`
|
||||
unknownFields protoimpl.UnknownFields
|
||||
|
||||
Container *Container `protobuf:"bytes,1,opt,name=container,proto3" json:"container,omitempty"`
|
||||
sizeCache protoimpl.SizeCache
|
||||
}
|
||||
|
||||
func (x *StreamContainerStartedResponse) Reset() {
|
||||
@@ -868,12 +854,11 @@ func (x *StreamContainerStartedResponse) GetContainer() *Container {
|
||||
}
|
||||
|
||||
type ContainerActionRequest struct {
|
||||
state protoimpl.MessageState
|
||||
sizeCache protoimpl.SizeCache
|
||||
state protoimpl.MessageState `protogen:"open.v1"`
|
||||
ContainerId string `protobuf:"bytes,1,opt,name=containerId,proto3" json:"containerId,omitempty"`
|
||||
Action ContainerAction `protobuf:"varint,2,opt,name=action,proto3,enum=protobuf.ContainerAction" json:"action,omitempty"`
|
||||
unknownFields protoimpl.UnknownFields
|
||||
|
||||
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"`
|
||||
sizeCache protoimpl.SizeCache
|
||||
}
|
||||
|
||||
func (x *ContainerActionRequest) Reset() {
|
||||
@@ -921,9 +906,9 @@ func (x *ContainerActionRequest) GetAction() ContainerAction {
|
||||
}
|
||||
|
||||
type ContainerActionResponse struct {
|
||||
state protoimpl.MessageState
|
||||
sizeCache protoimpl.SizeCache
|
||||
state protoimpl.MessageState `protogen:"open.v1"`
|
||||
unknownFields protoimpl.UnknownFields
|
||||
sizeCache protoimpl.SizeCache
|
||||
}
|
||||
|
||||
func (x *ContainerActionResponse) Reset() {
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
// Code generated by protoc-gen-go-grpc. DO NOT EDIT.
|
||||
// versions:
|
||||
// - protoc-gen-go-grpc v1.5.1
|
||||
// - protoc v5.29.2
|
||||
// - protoc v5.29.3
|
||||
// source: rpc.proto
|
||||
|
||||
package pb
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
// Code generated by protoc-gen-go. DO NOT EDIT.
|
||||
// versions:
|
||||
// protoc-gen-go v1.35.1
|
||||
// protoc v5.29.2
|
||||
// protoc-gen-go v1.36.2
|
||||
// protoc v5.29.3
|
||||
// source: types.proto
|
||||
|
||||
package pb
|
||||
@@ -72,26 +72,25 @@ func (ContainerAction) EnumDescriptor() ([]byte, []int) {
|
||||
}
|
||||
|
||||
type Container struct {
|
||||
state protoimpl.MessageState
|
||||
sizeCache protoimpl.SizeCache
|
||||
state protoimpl.MessageState `protogen:"open.v1"`
|
||||
Id string `protobuf:"bytes,1,opt,name=id,proto3" json:"id,omitempty"`
|
||||
Name string `protobuf:"bytes,2,opt,name=name,proto3" json:"name,omitempty"`
|
||||
Image string `protobuf:"bytes,3,opt,name=image,proto3" json:"image,omitempty"`
|
||||
Status string `protobuf:"bytes,4,opt,name=status,proto3" json:"status,omitempty"` // deprecated
|
||||
State string `protobuf:"bytes,5,opt,name=state,proto3" json:"state,omitempty"`
|
||||
ImageId string `protobuf:"bytes,6,opt,name=ImageId,proto3" json:"ImageId,omitempty"` // deprecated
|
||||
Created *timestamppb.Timestamp `protobuf:"bytes,7,opt,name=created,proto3" json:"created,omitempty"`
|
||||
Started *timestamppb.Timestamp `protobuf:"bytes,8,opt,name=started,proto3" json:"started,omitempty"`
|
||||
Health string `protobuf:"bytes,9,opt,name=health,proto3" json:"health,omitempty"`
|
||||
Host string `protobuf:"bytes,10,opt,name=host,proto3" json:"host,omitempty"`
|
||||
Tty bool `protobuf:"varint,11,opt,name=tty,proto3" json:"tty,omitempty"`
|
||||
Labels map[string]string `protobuf:"bytes,12,rep,name=labels,proto3" json:"labels,omitempty" protobuf_key:"bytes,1,opt,name=key" protobuf_val:"bytes,2,opt,name=value"`
|
||||
Stats []*ContainerStat `protobuf:"bytes,13,rep,name=stats,proto3" json:"stats,omitempty"`
|
||||
Group string `protobuf:"bytes,14,opt,name=group,proto3" json:"group,omitempty"`
|
||||
Command string `protobuf:"bytes,15,opt,name=command,proto3" json:"command,omitempty"`
|
||||
Finished *timestamppb.Timestamp `protobuf:"bytes,16,opt,name=finished,proto3" json:"finished,omitempty"`
|
||||
unknownFields protoimpl.UnknownFields
|
||||
|
||||
Id string `protobuf:"bytes,1,opt,name=id,proto3" json:"id,omitempty"`
|
||||
Name string `protobuf:"bytes,2,opt,name=name,proto3" json:"name,omitempty"`
|
||||
Image string `protobuf:"bytes,3,opt,name=image,proto3" json:"image,omitempty"`
|
||||
Status string `protobuf:"bytes,4,opt,name=status,proto3" json:"status,omitempty"` // deprecated
|
||||
State string `protobuf:"bytes,5,opt,name=state,proto3" json:"state,omitempty"`
|
||||
ImageId string `protobuf:"bytes,6,opt,name=ImageId,proto3" json:"ImageId,omitempty"` // deprecated
|
||||
Created *timestamppb.Timestamp `protobuf:"bytes,7,opt,name=created,proto3" json:"created,omitempty"`
|
||||
Started *timestamppb.Timestamp `protobuf:"bytes,8,opt,name=started,proto3" json:"started,omitempty"`
|
||||
Health string `protobuf:"bytes,9,opt,name=health,proto3" json:"health,omitempty"`
|
||||
Host string `protobuf:"bytes,10,opt,name=host,proto3" json:"host,omitempty"`
|
||||
Tty bool `protobuf:"varint,11,opt,name=tty,proto3" json:"tty,omitempty"`
|
||||
Labels map[string]string `protobuf:"bytes,12,rep,name=labels,proto3" json:"labels,omitempty" protobuf_key:"bytes,1,opt,name=key,proto3" protobuf_val:"bytes,2,opt,name=value,proto3"`
|
||||
Stats []*ContainerStat `protobuf:"bytes,13,rep,name=stats,proto3" json:"stats,omitempty"`
|
||||
Group string `protobuf:"bytes,14,opt,name=group,proto3" json:"group,omitempty"`
|
||||
Command string `protobuf:"bytes,15,opt,name=command,proto3" json:"command,omitempty"`
|
||||
Finished *timestamppb.Timestamp `protobuf:"bytes,16,opt,name=finished,proto3" json:"finished,omitempty"`
|
||||
sizeCache protoimpl.SizeCache
|
||||
}
|
||||
|
||||
func (x *Container) Reset() {
|
||||
@@ -237,14 +236,13 @@ func (x *Container) GetFinished() *timestamppb.Timestamp {
|
||||
}
|
||||
|
||||
type ContainerStat struct {
|
||||
state protoimpl.MessageState
|
||||
sizeCache protoimpl.SizeCache
|
||||
state protoimpl.MessageState `protogen:"open.v1"`
|
||||
Id string `protobuf:"bytes,1,opt,name=id,proto3" json:"id,omitempty"`
|
||||
CpuPercent float64 `protobuf:"fixed64,2,opt,name=cpuPercent,proto3" json:"cpuPercent,omitempty"`
|
||||
MemoryUsage float64 `protobuf:"fixed64,3,opt,name=memoryUsage,proto3" json:"memoryUsage,omitempty"`
|
||||
MemoryPercent float64 `protobuf:"fixed64,4,opt,name=memoryPercent,proto3" json:"memoryPercent,omitempty"`
|
||||
unknownFields protoimpl.UnknownFields
|
||||
|
||||
Id string `protobuf:"bytes,1,opt,name=id,proto3" json:"id,omitempty"`
|
||||
CpuPercent float64 `protobuf:"fixed64,2,opt,name=cpuPercent,proto3" json:"cpuPercent,omitempty"`
|
||||
MemoryUsage float64 `protobuf:"fixed64,3,opt,name=memoryUsage,proto3" json:"memoryUsage,omitempty"`
|
||||
MemoryPercent float64 `protobuf:"fixed64,4,opt,name=memoryPercent,proto3" json:"memoryPercent,omitempty"`
|
||||
sizeCache protoimpl.SizeCache
|
||||
}
|
||||
|
||||
func (x *ContainerStat) Reset() {
|
||||
@@ -306,17 +304,16 @@ func (x *ContainerStat) GetMemoryPercent() float64 {
|
||||
}
|
||||
|
||||
type LogEvent struct {
|
||||
state protoimpl.MessageState
|
||||
sizeCache protoimpl.SizeCache
|
||||
state protoimpl.MessageState `protogen:"open.v1"`
|
||||
Id uint32 `protobuf:"varint,1,opt,name=id,proto3" json:"id,omitempty"`
|
||||
ContainerId string `protobuf:"bytes,2,opt,name=containerId,proto3" json:"containerId,omitempty"`
|
||||
Message *anypb.Any `protobuf:"bytes,3,opt,name=message,proto3" json:"message,omitempty"`
|
||||
Timestamp *timestamppb.Timestamp `protobuf:"bytes,4,opt,name=timestamp,proto3" json:"timestamp,omitempty"`
|
||||
Level string `protobuf:"bytes,5,opt,name=level,proto3" json:"level,omitempty"`
|
||||
Stream string `protobuf:"bytes,6,opt,name=stream,proto3" json:"stream,omitempty"`
|
||||
Position string `protobuf:"bytes,7,opt,name=position,proto3" json:"position,omitempty"`
|
||||
unknownFields protoimpl.UnknownFields
|
||||
|
||||
Id uint32 `protobuf:"varint,1,opt,name=id,proto3" json:"id,omitempty"`
|
||||
ContainerId string `protobuf:"bytes,2,opt,name=containerId,proto3" json:"containerId,omitempty"`
|
||||
Message *anypb.Any `protobuf:"bytes,3,opt,name=message,proto3" json:"message,omitempty"`
|
||||
Timestamp *timestamppb.Timestamp `protobuf:"bytes,4,opt,name=timestamp,proto3" json:"timestamp,omitempty"`
|
||||
Level string `protobuf:"bytes,5,opt,name=level,proto3" json:"level,omitempty"`
|
||||
Stream string `protobuf:"bytes,6,opt,name=stream,proto3" json:"stream,omitempty"`
|
||||
Position string `protobuf:"bytes,7,opt,name=position,proto3" json:"position,omitempty"`
|
||||
sizeCache protoimpl.SizeCache
|
||||
}
|
||||
|
||||
func (x *LogEvent) Reset() {
|
||||
@@ -399,11 +396,10 @@ func (x *LogEvent) GetPosition() string {
|
||||
}
|
||||
|
||||
type SimpleMessage struct {
|
||||
state protoimpl.MessageState
|
||||
sizeCache protoimpl.SizeCache
|
||||
state protoimpl.MessageState `protogen:"open.v1"`
|
||||
Message string `protobuf:"bytes,1,opt,name=message,proto3" json:"message,omitempty"`
|
||||
unknownFields protoimpl.UnknownFields
|
||||
|
||||
Message string `protobuf:"bytes,1,opt,name=message,proto3" json:"message,omitempty"`
|
||||
sizeCache protoimpl.SizeCache
|
||||
}
|
||||
|
||||
func (x *SimpleMessage) Reset() {
|
||||
@@ -444,11 +440,10 @@ func (x *SimpleMessage) GetMessage() string {
|
||||
}
|
||||
|
||||
type ComplexMessage struct {
|
||||
state protoimpl.MessageState
|
||||
sizeCache protoimpl.SizeCache
|
||||
state protoimpl.MessageState `protogen:"open.v1"`
|
||||
Data []byte `protobuf:"bytes,1,opt,name=data,proto3" json:"data,omitempty"`
|
||||
unknownFields protoimpl.UnknownFields
|
||||
|
||||
Data []byte `protobuf:"bytes,1,opt,name=data,proto3" json:"data,omitempty"`
|
||||
sizeCache protoimpl.SizeCache
|
||||
}
|
||||
|
||||
func (x *ComplexMessage) Reset() {
|
||||
@@ -489,13 +484,13 @@ func (x *ComplexMessage) GetData() []byte {
|
||||
}
|
||||
|
||||
type ContainerEvent struct {
|
||||
state protoimpl.MessageState
|
||||
sizeCache protoimpl.SizeCache
|
||||
state protoimpl.MessageState `protogen:"open.v1"`
|
||||
ActorId string `protobuf:"bytes,1,opt,name=actorId,proto3" json:"actorId,omitempty"`
|
||||
Name string `protobuf:"bytes,2,opt,name=name,proto3" json:"name,omitempty"`
|
||||
Host string `protobuf:"bytes,3,opt,name=host,proto3" json:"host,omitempty"`
|
||||
Timestamp *timestamppb.Timestamp `protobuf:"bytes,4,opt,name=timestamp,proto3" json:"timestamp,omitempty"`
|
||||
unknownFields protoimpl.UnknownFields
|
||||
|
||||
ActorId string `protobuf:"bytes,1,opt,name=actorId,proto3" json:"actorId,omitempty"`
|
||||
Name string `protobuf:"bytes,2,opt,name=name,proto3" json:"name,omitempty"`
|
||||
Host string `protobuf:"bytes,3,opt,name=host,proto3" json:"host,omitempty"`
|
||||
sizeCache protoimpl.SizeCache
|
||||
}
|
||||
|
||||
func (x *ContainerEvent) Reset() {
|
||||
@@ -549,23 +544,29 @@ func (x *ContainerEvent) GetHost() string {
|
||||
return ""
|
||||
}
|
||||
|
||||
type Host struct {
|
||||
state protoimpl.MessageState
|
||||
sizeCache protoimpl.SizeCache
|
||||
unknownFields protoimpl.UnknownFields
|
||||
func (x *ContainerEvent) GetTimestamp() *timestamppb.Timestamp {
|
||||
if x != nil {
|
||||
return x.Timestamp
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
Id string `protobuf:"bytes,1,opt,name=id,proto3" json:"id,omitempty"`
|
||||
Name string `protobuf:"bytes,2,opt,name=name,proto3" json:"name,omitempty"`
|
||||
NodeAddress string `protobuf:"bytes,3,opt,name=nodeAddress,proto3" json:"nodeAddress,omitempty"`
|
||||
Swarm bool `protobuf:"varint,4,opt,name=swarm,proto3" json:"swarm,omitempty"`
|
||||
Labels map[string]string `protobuf:"bytes,5,rep,name=labels,proto3" json:"labels,omitempty" protobuf_key:"bytes,1,opt,name=key,proto3" protobuf_val:"bytes,2,opt,name=value,proto3"`
|
||||
OperatingSystem string `protobuf:"bytes,6,opt,name=operatingSystem,proto3" json:"operatingSystem,omitempty"`
|
||||
OsVersion string `protobuf:"bytes,7,opt,name=osVersion,proto3" json:"osVersion,omitempty"`
|
||||
OsType string `protobuf:"bytes,8,opt,name=osType,proto3" json:"osType,omitempty"`
|
||||
CpuCores uint32 `protobuf:"varint,9,opt,name=cpuCores,proto3" json:"cpuCores,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"`
|
||||
DockerVersion string `protobuf:"bytes,12,opt,name=dockerVersion,proto3" json:"dockerVersion,omitempty"`
|
||||
type Host struct {
|
||||
state protoimpl.MessageState `protogen:"open.v1"`
|
||||
Id string `protobuf:"bytes,1,opt,name=id,proto3" json:"id,omitempty"`
|
||||
Name string `protobuf:"bytes,2,opt,name=name,proto3" json:"name,omitempty"`
|
||||
NodeAddress string `protobuf:"bytes,3,opt,name=nodeAddress,proto3" json:"nodeAddress,omitempty"`
|
||||
Swarm bool `protobuf:"varint,4,opt,name=swarm,proto3" json:"swarm,omitempty"`
|
||||
Labels map[string]string `protobuf:"bytes,5,rep,name=labels,proto3" json:"labels,omitempty" protobuf_key:"bytes,1,opt,name=key" protobuf_val:"bytes,2,opt,name=value"`
|
||||
OperatingSystem string `protobuf:"bytes,6,opt,name=operatingSystem,proto3" json:"operatingSystem,omitempty"`
|
||||
OsVersion string `protobuf:"bytes,7,opt,name=osVersion,proto3" json:"osVersion,omitempty"`
|
||||
OsType string `protobuf:"bytes,8,opt,name=osType,proto3" json:"osType,omitempty"`
|
||||
CpuCores uint32 `protobuf:"varint,9,opt,name=cpuCores,proto3" json:"cpuCores,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"`
|
||||
DockerVersion string `protobuf:"bytes,12,opt,name=dockerVersion,proto3" json:"dockerVersion,omitempty"`
|
||||
unknownFields protoimpl.UnknownFields
|
||||
sizeCache protoimpl.SizeCache
|
||||
}
|
||||
|
||||
func (x *Host) Reset() {
|
||||
@@ -755,44 +756,48 @@ var file_types_proto_rawDesc = []byte{
|
||||
0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65,
|
||||
0x22, 0x24, 0x0a, 0x0e, 0x43, 0x6f, 0x6d, 0x70, 0x6c, 0x65, 0x78, 0x4d, 0x65, 0x73, 0x73, 0x61,
|
||||
0x67, 0x65, 0x12, 0x12, 0x0a, 0x04, 0x64, 0x61, 0x74, 0x61, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c,
|
||||
0x52, 0x04, 0x64, 0x61, 0x74, 0x61, 0x22, 0x52, 0x0a, 0x0e, 0x43, 0x6f, 0x6e, 0x74, 0x61, 0x69,
|
||||
0x6e, 0x65, 0x72, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x12, 0x18, 0x0a, 0x07, 0x61, 0x63, 0x74, 0x6f,
|
||||
0x72, 0x49, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x61, 0x63, 0x74, 0x6f, 0x72,
|
||||
0x49, 0x64, 0x12, 0x12, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09,
|
||||
0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x12, 0x0a, 0x04, 0x68, 0x6f, 0x73, 0x74, 0x18, 0x03,
|
||||
0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x68, 0x6f, 0x73, 0x74, 0x22, 0xaf, 0x03, 0x0a, 0x04, 0x48,
|
||||
0x6f, 0x73, 0x74, 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52,
|
||||
0x02, 0x69, 0x64, 0x12, 0x12, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28,
|
||||
0x09, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x20, 0x0a, 0x0b, 0x6e, 0x6f, 0x64, 0x65, 0x41,
|
||||
0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, 0x6e, 0x6f,
|
||||
0x64, 0x65, 0x41, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x12, 0x14, 0x0a, 0x05, 0x73, 0x77, 0x61,
|
||||
0x72, 0x6d, 0x18, 0x04, 0x20, 0x01, 0x28, 0x08, 0x52, 0x05, 0x73, 0x77, 0x61, 0x72, 0x6d, 0x12,
|
||||
0x32, 0x0a, 0x06, 0x6c, 0x61, 0x62, 0x65, 0x6c, 0x73, 0x18, 0x05, 0x20, 0x03, 0x28, 0x0b, 0x32,
|
||||
0x1a, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x48, 0x6f, 0x73, 0x74, 0x2e,
|
||||
0x4c, 0x61, 0x62, 0x65, 0x6c, 0x73, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x52, 0x06, 0x6c, 0x61, 0x62,
|
||||
0x65, 0x6c, 0x73, 0x12, 0x28, 0x0a, 0x0f, 0x6f, 0x70, 0x65, 0x72, 0x61, 0x74, 0x69, 0x6e, 0x67,
|
||||
0x53, 0x79, 0x73, 0x74, 0x65, 0x6d, 0x18, 0x06, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0f, 0x6f, 0x70,
|
||||
0x65, 0x72, 0x61, 0x74, 0x69, 0x6e, 0x67, 0x53, 0x79, 0x73, 0x74, 0x65, 0x6d, 0x12, 0x1c, 0x0a,
|
||||
0x09, 0x6f, 0x73, 0x56, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x18, 0x07, 0x20, 0x01, 0x28, 0x09,
|
||||
0x52, 0x09, 0x6f, 0x73, 0x56, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x12, 0x16, 0x0a, 0x06, 0x6f,
|
||||
0x73, 0x54, 0x79, 0x70, 0x65, 0x18, 0x08, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x6f, 0x73, 0x54,
|
||||
0x79, 0x70, 0x65, 0x12, 0x1a, 0x0a, 0x08, 0x63, 0x70, 0x75, 0x43, 0x6f, 0x72, 0x65, 0x73, 0x18,
|
||||
0x09, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x08, 0x63, 0x70, 0x75, 0x43, 0x6f, 0x72, 0x65, 0x73, 0x12,
|
||||
0x16, 0x0a, 0x06, 0x6d, 0x65, 0x6d, 0x6f, 0x72, 0x79, 0x18, 0x0a, 0x20, 0x01, 0x28, 0x04, 0x52,
|
||||
0x06, 0x6d, 0x65, 0x6d, 0x6f, 0x72, 0x79, 0x12, 0x22, 0x0a, 0x0c, 0x61, 0x67, 0x65, 0x6e, 0x74,
|
||||
0x56, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x18, 0x0b, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0c, 0x61,
|
||||
0x67, 0x65, 0x6e, 0x74, 0x56, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x12, 0x24, 0x0a, 0x0d, 0x64,
|
||||
0x6f, 0x63, 0x6b, 0x65, 0x72, 0x56, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x18, 0x0c, 0x20, 0x01,
|
||||
0x28, 0x09, 0x52, 0x0d, 0x64, 0x6f, 0x63, 0x6b, 0x65, 0x72, 0x56, 0x65, 0x72, 0x73, 0x69, 0x6f,
|
||||
0x6e, 0x1a, 0x39, 0x0a, 0x0b, 0x4c, 0x61, 0x62, 0x65, 0x6c, 0x73, 0x45, 0x6e, 0x74, 0x72, 0x79,
|
||||
0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x6b,
|
||||
0x65, 0x79, 0x12, 0x14, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28,
|
||||
0x09, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x3a, 0x02, 0x38, 0x01, 0x2a, 0x33, 0x0a, 0x0f,
|
||||
0x43, 0x6f, 0x6e, 0x74, 0x61, 0x69, 0x6e, 0x65, 0x72, 0x41, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x12,
|
||||
0x09, 0x0a, 0x05, 0x53, 0x74, 0x61, 0x72, 0x74, 0x10, 0x00, 0x12, 0x08, 0x0a, 0x04, 0x53, 0x74,
|
||||
0x6f, 0x70, 0x10, 0x01, 0x12, 0x0b, 0x0a, 0x07, 0x52, 0x65, 0x73, 0x74, 0x61, 0x72, 0x74, 0x10,
|
||||
0x02, 0x42, 0x13, 0x5a, 0x11, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x2f, 0x61, 0x67,
|
||||
0x65, 0x6e, 0x74, 0x2f, 0x70, 0x62, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33,
|
||||
0x52, 0x04, 0x64, 0x61, 0x74, 0x61, 0x22, 0x8c, 0x01, 0x0a, 0x0e, 0x43, 0x6f, 0x6e, 0x74, 0x61,
|
||||
0x69, 0x6e, 0x65, 0x72, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x12, 0x18, 0x0a, 0x07, 0x61, 0x63, 0x74,
|
||||
0x6f, 0x72, 0x49, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x61, 0x63, 0x74, 0x6f,
|
||||
0x72, 0x49, 0x64, 0x12, 0x12, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28,
|
||||
0x09, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x12, 0x0a, 0x04, 0x68, 0x6f, 0x73, 0x74, 0x18,
|
||||
0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x68, 0x6f, 0x73, 0x74, 0x12, 0x38, 0x0a, 0x09, 0x74,
|
||||
0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a,
|
||||
0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66,
|
||||
0x2e, 0x54, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x52, 0x09, 0x74, 0x69, 0x6d, 0x65,
|
||||
0x73, 0x74, 0x61, 0x6d, 0x70, 0x22, 0xaf, 0x03, 0x0a, 0x04, 0x48, 0x6f, 0x73, 0x74, 0x12, 0x0e,
|
||||
0x0a, 0x02, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x02, 0x69, 0x64, 0x12, 0x12,
|
||||
0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6e, 0x61,
|
||||
0x6d, 0x65, 0x12, 0x20, 0x0a, 0x0b, 0x6e, 0x6f, 0x64, 0x65, 0x41, 0x64, 0x64, 0x72, 0x65, 0x73,
|
||||
0x73, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, 0x6e, 0x6f, 0x64, 0x65, 0x41, 0x64, 0x64,
|
||||
0x72, 0x65, 0x73, 0x73, 0x12, 0x14, 0x0a, 0x05, 0x73, 0x77, 0x61, 0x72, 0x6d, 0x18, 0x04, 0x20,
|
||||
0x01, 0x28, 0x08, 0x52, 0x05, 0x73, 0x77, 0x61, 0x72, 0x6d, 0x12, 0x32, 0x0a, 0x06, 0x6c, 0x61,
|
||||
0x62, 0x65, 0x6c, 0x73, 0x18, 0x05, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x70, 0x72, 0x6f,
|
||||
0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x48, 0x6f, 0x73, 0x74, 0x2e, 0x4c, 0x61, 0x62, 0x65, 0x6c,
|
||||
0x73, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x52, 0x06, 0x6c, 0x61, 0x62, 0x65, 0x6c, 0x73, 0x12, 0x28,
|
||||
0x0a, 0x0f, 0x6f, 0x70, 0x65, 0x72, 0x61, 0x74, 0x69, 0x6e, 0x67, 0x53, 0x79, 0x73, 0x74, 0x65,
|
||||
0x6d, 0x18, 0x06, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0f, 0x6f, 0x70, 0x65, 0x72, 0x61, 0x74, 0x69,
|
||||
0x6e, 0x67, 0x53, 0x79, 0x73, 0x74, 0x65, 0x6d, 0x12, 0x1c, 0x0a, 0x09, 0x6f, 0x73, 0x56, 0x65,
|
||||
0x72, 0x73, 0x69, 0x6f, 0x6e, 0x18, 0x07, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x6f, 0x73, 0x56,
|
||||
0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x12, 0x16, 0x0a, 0x06, 0x6f, 0x73, 0x54, 0x79, 0x70, 0x65,
|
||||
0x18, 0x08, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x6f, 0x73, 0x54, 0x79, 0x70, 0x65, 0x12, 0x1a,
|
||||
0x0a, 0x08, 0x63, 0x70, 0x75, 0x43, 0x6f, 0x72, 0x65, 0x73, 0x18, 0x09, 0x20, 0x01, 0x28, 0x0d,
|
||||
0x52, 0x08, 0x63, 0x70, 0x75, 0x43, 0x6f, 0x72, 0x65, 0x73, 0x12, 0x16, 0x0a, 0x06, 0x6d, 0x65,
|
||||
0x6d, 0x6f, 0x72, 0x79, 0x18, 0x0a, 0x20, 0x01, 0x28, 0x04, 0x52, 0x06, 0x6d, 0x65, 0x6d, 0x6f,
|
||||
0x72, 0x79, 0x12, 0x22, 0x0a, 0x0c, 0x61, 0x67, 0x65, 0x6e, 0x74, 0x56, 0x65, 0x72, 0x73, 0x69,
|
||||
0x6f, 0x6e, 0x18, 0x0b, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0c, 0x61, 0x67, 0x65, 0x6e, 0x74, 0x56,
|
||||
0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x12, 0x24, 0x0a, 0x0d, 0x64, 0x6f, 0x63, 0x6b, 0x65, 0x72,
|
||||
0x56, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x18, 0x0c, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0d, 0x64,
|
||||
0x6f, 0x63, 0x6b, 0x65, 0x72, 0x56, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x1a, 0x39, 0x0a, 0x0b,
|
||||
0x4c, 0x61, 0x62, 0x65, 0x6c, 0x73, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x12, 0x10, 0x0a, 0x03, 0x6b,
|
||||
0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x14, 0x0a,
|
||||
0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x76, 0x61,
|
||||
0x6c, 0x75, 0x65, 0x3a, 0x02, 0x38, 0x01, 0x2a, 0x33, 0x0a, 0x0f, 0x43, 0x6f, 0x6e, 0x74, 0x61,
|
||||
0x69, 0x6e, 0x65, 0x72, 0x41, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x09, 0x0a, 0x05, 0x53, 0x74,
|
||||
0x61, 0x72, 0x74, 0x10, 0x00, 0x12, 0x08, 0x0a, 0x04, 0x53, 0x74, 0x6f, 0x70, 0x10, 0x01, 0x12,
|
||||
0x0b, 0x0a, 0x07, 0x52, 0x65, 0x73, 0x74, 0x61, 0x72, 0x74, 0x10, 0x02, 0x42, 0x13, 0x5a, 0x11,
|
||||
0x69, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x2f, 0x61, 0x67, 0x65, 0x6e, 0x74, 0x2f, 0x70,
|
||||
0x62, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33,
|
||||
}
|
||||
|
||||
var (
|
||||
@@ -831,12 +836,13 @@ var file_types_proto_depIdxs = []int32{
|
||||
10, // 4: protobuf.Container.finished:type_name -> google.protobuf.Timestamp
|
||||
11, // 5: protobuf.LogEvent.message:type_name -> google.protobuf.Any
|
||||
10, // 6: protobuf.LogEvent.timestamp:type_name -> google.protobuf.Timestamp
|
||||
9, // 7: protobuf.Host.labels:type_name -> protobuf.Host.LabelsEntry
|
||||
8, // [8:8] is the sub-list for method output_type
|
||||
8, // [8:8] is the sub-list for method input_type
|
||||
8, // [8:8] is the sub-list for extension type_name
|
||||
8, // [8:8] is the sub-list for extension extendee
|
||||
0, // [0:8] is the sub-list for field type_name
|
||||
10, // 7: protobuf.ContainerEvent.timestamp:type_name -> google.protobuf.Timestamp
|
||||
9, // 8: protobuf.Host.labels:type_name -> protobuf.Host.LabelsEntry
|
||||
9, // [9:9] is the sub-list for method output_type
|
||||
9, // [9:9] is the sub-list for method input_type
|
||||
9, // [9:9] is the sub-list for extension type_name
|
||||
9, // [9:9] is the sub-list for extension extendee
|
||||
0, // [0:9] is the sub-list for field type_name
|
||||
}
|
||||
|
||||
func init() { file_types_proto_init() }
|
||||
|
||||
@@ -12,6 +12,7 @@ import (
|
||||
"time"
|
||||
|
||||
"github.com/amir20/dozzle/internal/agent/pb"
|
||||
"github.com/amir20/dozzle/internal/container"
|
||||
"github.com/amir20/dozzle/internal/docker"
|
||||
"github.com/rs/zerolog/log"
|
||||
orderedmap "github.com/wk8/go-ordered-map/v2"
|
||||
@@ -25,19 +26,20 @@ import (
|
||||
)
|
||||
|
||||
type server struct {
|
||||
client docker.Client
|
||||
store *docker.ContainerStore
|
||||
client container.Client
|
||||
store *container.ContainerStore
|
||||
version string
|
||||
|
||||
pb.UnimplementedAgentServiceServer
|
||||
}
|
||||
|
||||
func newServer(client docker.Client, dozzleVersion string, filter docker.ContainerFilter) pb.AgentServiceServer {
|
||||
func newServer(client container.Client, dozzleVersion string, labels container.ContainerLabels) pb.AgentServiceServer {
|
||||
statsCollector := docker.NewDockerStatsCollector(client, labels)
|
||||
return &server{
|
||||
client: client,
|
||||
version: dozzleVersion,
|
||||
|
||||
store: docker.NewContainerStore(context.Background(), client, filter),
|
||||
store: container.NewContainerStore(context.Background(), client, statsCollector, labels),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -47,17 +49,18 @@ func (s *server) StreamLogs(in *pb.StreamLogsRequest, out pb.AgentService_Stream
|
||||
since = in.Since.AsTime()
|
||||
}
|
||||
|
||||
container, err := s.store.FindContainer(in.ContainerId, docker.ContainerFilter{})
|
||||
c, err := s.store.FindContainer(in.ContainerId, container.ContainerLabels{})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
reader, err := s.client.ContainerLogs(out.Context(), in.ContainerId, since, docker.StdType(in.StreamTypes))
|
||||
reader, err := s.client.ContainerLogs(out.Context(), in.ContainerId, since, container.StdType(in.StreamTypes))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
g := docker.NewEventGenerator(out.Context(), reader, container)
|
||||
dockerReader := docker.NewLogReader(reader, c.Tty)
|
||||
g := container.NewEventGenerator(out.Context(), dockerReader, c)
|
||||
|
||||
for event := range g.Events {
|
||||
out.Send(&pb.StreamLogsResponse{
|
||||
@@ -74,17 +77,18 @@ func (s *server) StreamLogs(in *pb.StreamLogsRequest, out pb.AgentService_Stream
|
||||
}
|
||||
|
||||
func (s *server) LogsBetweenDates(in *pb.LogsBetweenDatesRequest, out pb.AgentService_LogsBetweenDatesServer) error {
|
||||
reader, err := s.client.ContainerLogsBetweenDates(out.Context(), in.ContainerId, in.Since.AsTime(), in.Until.AsTime(), docker.StdType(in.StreamTypes))
|
||||
reader, err := s.client.ContainerLogsBetweenDates(out.Context(), in.ContainerId, in.Since.AsTime(), in.Until.AsTime(), container.StdType(in.StreamTypes))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
container, err := s.client.FindContainer(out.Context(), in.ContainerId)
|
||||
c, err := s.client.FindContainer(out.Context(), in.ContainerId)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
g := docker.NewEventGenerator(out.Context(), reader, container)
|
||||
dockerReader := docker.NewLogReader(reader, c.Tty)
|
||||
g := container.NewEventGenerator(out.Context(), dockerReader, c)
|
||||
|
||||
for {
|
||||
select {
|
||||
@@ -101,7 +105,7 @@ func (s *server) LogsBetweenDates(in *pb.LogsBetweenDatesRequest, out pb.AgentSe
|
||||
}
|
||||
|
||||
func (s *server) StreamRawBytes(in *pb.StreamRawBytesRequest, out pb.AgentService_StreamRawBytesServer) error {
|
||||
reader, err := s.client.ContainerLogsBetweenDates(out.Context(), in.ContainerId, in.Since.AsTime(), in.Until.AsTime(), docker.StdType(in.StreamTypes))
|
||||
reader, err := s.client.ContainerLogsBetweenDates(out.Context(), in.ContainerId, in.Since.AsTime(), in.Until.AsTime(), container.StdType(in.StreamTypes))
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
@@ -129,7 +133,7 @@ func (s *server) StreamRawBytes(in *pb.StreamRawBytesRequest, out pb.AgentServic
|
||||
}
|
||||
|
||||
func (s *server) StreamEvents(in *pb.StreamEventsRequest, out pb.AgentService_StreamEventsServer) error {
|
||||
events := make(chan docker.ContainerEvent)
|
||||
events := make(chan container.ContainerEvent)
|
||||
|
||||
s.store.SubscribeEvents(out.Context(), events)
|
||||
|
||||
@@ -138,9 +142,10 @@ func (s *server) StreamEvents(in *pb.StreamEventsRequest, out pb.AgentService_St
|
||||
case event := <-events:
|
||||
out.Send(&pb.StreamEventsResponse{
|
||||
Event: &pb.ContainerEvent{
|
||||
ActorId: event.ActorID,
|
||||
Name: event.Name,
|
||||
Host: event.Host,
|
||||
ActorId: event.ActorID,
|
||||
Name: event.Name,
|
||||
Host: event.Host,
|
||||
Timestamp: timestamppb.New(event.Time),
|
||||
},
|
||||
})
|
||||
case <-out.Context().Done():
|
||||
@@ -150,7 +155,7 @@ func (s *server) StreamEvents(in *pb.StreamEventsRequest, out pb.AgentService_St
|
||||
}
|
||||
|
||||
func (s *server) StreamStats(in *pb.StreamStatsRequest, out pb.AgentService_StreamStatsServer) error {
|
||||
stats := make(chan docker.ContainerStat)
|
||||
stats := make(chan container.ContainerStat)
|
||||
|
||||
s.store.SubscribeStats(out.Context(), stats)
|
||||
|
||||
@@ -172,14 +177,14 @@ func (s *server) StreamStats(in *pb.StreamStatsRequest, out pb.AgentService_Stre
|
||||
}
|
||||
|
||||
func (s *server) FindContainer(ctx context.Context, in *pb.FindContainerRequest) (*pb.FindContainerResponse, error) {
|
||||
filter := make(docker.ContainerFilter)
|
||||
labels := make(container.ContainerLabels)
|
||||
if in.GetFilter() != nil {
|
||||
for k, v := range in.GetFilter() {
|
||||
filter[k] = append(filter[k], v.GetValues()...)
|
||||
labels[k] = append(labels[k], v.GetValues()...)
|
||||
}
|
||||
}
|
||||
|
||||
container, err := s.store.FindContainer(in.ContainerId, filter)
|
||||
container, err := s.store.FindContainer(in.ContainerId, labels)
|
||||
if err != nil {
|
||||
return nil, status.Error(codes.NotFound, err.Error())
|
||||
}
|
||||
@@ -204,14 +209,14 @@ func (s *server) FindContainer(ctx context.Context, in *pb.FindContainerRequest)
|
||||
}
|
||||
|
||||
func (s *server) ListContainers(ctx context.Context, in *pb.ListContainersRequest) (*pb.ListContainersResponse, error) {
|
||||
filter := make(docker.ContainerFilter)
|
||||
labels := make(container.ContainerLabels)
|
||||
if in.GetFilter() != nil {
|
||||
for k, v := range in.GetFilter() {
|
||||
filter[k] = append(filter[k], v.GetValues()...)
|
||||
labels[k] = append(labels[k], v.GetValues()...)
|
||||
}
|
||||
}
|
||||
|
||||
containers, err := s.store.ListContainers(filter)
|
||||
containers, err := s.store.ListContainers(labels)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -267,7 +272,7 @@ func (s *server) HostInfo(ctx context.Context, in *pb.HostInfoRequest) (*pb.Host
|
||||
}
|
||||
|
||||
func (s *server) StreamContainerStarted(in *pb.StreamContainerStartedRequest, out pb.AgentService_StreamContainerStartedServer) error {
|
||||
containers := make(chan docker.Container)
|
||||
containers := make(chan container.Container)
|
||||
|
||||
go s.store.SubscribeNewContainers(out.Context(), containers)
|
||||
|
||||
@@ -297,16 +302,16 @@ func (s *server) StreamContainerStarted(in *pb.StreamContainerStartedRequest, ou
|
||||
}
|
||||
|
||||
func (s *server) ContainerAction(ctx context.Context, in *pb.ContainerActionRequest) (*pb.ContainerActionResponse, error) {
|
||||
var action docker.ContainerAction
|
||||
var action container.ContainerAction
|
||||
switch in.Action {
|
||||
case pb.ContainerAction_Start:
|
||||
action = docker.Start
|
||||
action = container.Start
|
||||
|
||||
case pb.ContainerAction_Stop:
|
||||
action = docker.Stop
|
||||
action = container.Stop
|
||||
|
||||
case pb.ContainerAction_Restart:
|
||||
action = docker.Restart
|
||||
action = container.Restart
|
||||
|
||||
default:
|
||||
return nil, status.Error(codes.InvalidArgument, "invalid action")
|
||||
@@ -321,7 +326,7 @@ func (s *server) ContainerAction(ctx context.Context, in *pb.ContainerActionRequ
|
||||
return &pb.ContainerActionResponse{}, nil
|
||||
}
|
||||
|
||||
func NewServer(client docker.Client, certificates tls.Certificate, dozzleVersion string, filter docker.ContainerFilter) (*grpc.Server, error) {
|
||||
func NewServer(client container.Client, certificates tls.Certificate, dozzleVersion string, labels container.ContainerLabels) (*grpc.Server, error) {
|
||||
caCertPool := x509.NewCertPool()
|
||||
c, err := x509.ParseCertificate(certificates.Certificate[0])
|
||||
if err != nil {
|
||||
@@ -340,12 +345,12 @@ func NewServer(client docker.Client, certificates tls.Certificate, dozzleVersion
|
||||
creds := credentials.NewTLS(tlsConfig)
|
||||
|
||||
grpcServer := grpc.NewServer(grpc.Creds(creds))
|
||||
pb.RegisterAgentServiceServer(grpcServer, newServer(client, dozzleVersion, filter))
|
||||
pb.RegisterAgentServiceServer(grpcServer, newServer(client, dozzleVersion, labels))
|
||||
|
||||
return grpcServer, nil
|
||||
}
|
||||
|
||||
func logEventToPb(event *docker.LogEvent) *pb.LogEvent {
|
||||
func logEventToPb(event *container.LogEvent) *pb.LogEvent {
|
||||
var message *anypb.Any
|
||||
|
||||
if event.Message == nil {
|
||||
|
||||
@@ -7,7 +7,7 @@ import (
|
||||
"net/http"
|
||||
"strings"
|
||||
|
||||
"github.com/amir20/dozzle/internal/docker"
|
||||
"github.com/amir20/dozzle/internal/container"
|
||||
"github.com/rs/zerolog/log"
|
||||
)
|
||||
|
||||
@@ -42,7 +42,7 @@ func NewForwardProxyAuth(userHeader, emailHeader, nameHeader, filterHeader strin
|
||||
func (p *proxyAuthContext) AuthMiddleware(next http.Handler) http.Handler {
|
||||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
if r.Header.Get(p.headerUser) != "" {
|
||||
containerFilter, err := docker.ParseContainerFilter(r.Header.Get(p.headerFilter))
|
||||
containerFilter, err := container.ParseContainerFilter(r.Header.Get(p.headerFilter))
|
||||
if err != nil {
|
||||
log.Fatal().Str("filter", r.Header.Get(p.headerFilter)).Msg("Failed to parse container filter")
|
||||
}
|
||||
|
||||
@@ -11,7 +11,7 @@ import (
|
||||
"os"
|
||||
"time"
|
||||
|
||||
"github.com/amir20/dozzle/internal/docker"
|
||||
"github.com/amir20/dozzle/internal/container"
|
||||
"github.com/go-chi/jwtauth/v5"
|
||||
"github.com/rs/zerolog/log"
|
||||
"golang.org/x/crypto/bcrypt"
|
||||
@@ -19,12 +19,12 @@ import (
|
||||
)
|
||||
|
||||
type User struct {
|
||||
Username string `json:"username" yaml:"-"`
|
||||
Email string `json:"email" yaml:"email"`
|
||||
Name string `json:"name" yaml:"name"`
|
||||
Password string `json:"-" yaml:"password"`
|
||||
Filter string `json:"-" yaml:"filter"`
|
||||
ContainerFilter docker.ContainerFilter `json:"-" yaml:"-"`
|
||||
Username string `json:"username" yaml:"-"`
|
||||
Email string `json:"email" yaml:"email"`
|
||||
Name string `json:"name" yaml:"name"`
|
||||
Password string `json:"-" yaml:"password"`
|
||||
Filter string `json:"-" yaml:"filter"`
|
||||
ContainerLabels container.ContainerLabels `json:"-" yaml:"-"`
|
||||
}
|
||||
|
||||
func (u User) AvatarURL() string {
|
||||
@@ -35,12 +35,12 @@ func (u User) AvatarURL() string {
|
||||
return fmt.Sprintf("https://gravatar.com/avatar/%s?d=https%%3A%%2F%%2Fui-avatars.com%%2Fapi%%2F/%s/128", hashEmail(u.Email), url.QueryEscape(name))
|
||||
}
|
||||
|
||||
func newUser(username, email, name string, filter docker.ContainerFilter) User {
|
||||
func newUser(username, email, name string, labels container.ContainerLabels) User {
|
||||
return User{
|
||||
Username: username,
|
||||
Email: email,
|
||||
Name: name,
|
||||
ContainerFilter: filter,
|
||||
ContainerLabels: labels,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -197,9 +197,9 @@ func UserFromContext(ctx context.Context) *User {
|
||||
}
|
||||
email := claims["email"].(string)
|
||||
name := claims["name"].(string)
|
||||
containerFilter := docker.ContainerFilter{}
|
||||
containerFilter := container.ContainerLabels{}
|
||||
if filter, ok := claims["filter"].(string); ok {
|
||||
containerFilter, err = docker.ParseContainerFilter(filter)
|
||||
containerFilter, err = container.ParseContainerFilter(filter)
|
||||
if err != nil {
|
||||
log.Fatal().Err(err).Str("filter", filter).Msg("Failed to parse container filter")
|
||||
}
|
||||
|
||||
@@ -0,0 +1,41 @@
|
||||
package container
|
||||
|
||||
import (
|
||||
"context"
|
||||
"io"
|
||||
"time"
|
||||
)
|
||||
|
||||
type StdType int
|
||||
|
||||
const (
|
||||
UNKNOWN StdType = 1 << iota
|
||||
STDOUT
|
||||
STDERR
|
||||
)
|
||||
const STDALL = STDOUT | STDERR
|
||||
|
||||
func (s StdType) String() string {
|
||||
switch s {
|
||||
case STDOUT:
|
||||
return "stdout"
|
||||
case STDERR:
|
||||
return "stderr"
|
||||
case STDALL:
|
||||
return "all"
|
||||
default:
|
||||
return "unknown"
|
||||
}
|
||||
}
|
||||
|
||||
type Client interface {
|
||||
ListContainers(context.Context, ContainerLabels) ([]Container, error)
|
||||
FindContainer(context.Context, string) (Container, error)
|
||||
ContainerLogs(context.Context, string, time.Time, StdType) (io.ReadCloser, error)
|
||||
ContainerEvents(context.Context, chan<- ContainerEvent) error
|
||||
ContainerLogsBetweenDates(context.Context, string, time.Time, time.Time, StdType) (io.ReadCloser, error)
|
||||
ContainerStats(context.Context, string, chan<- ContainerStat) error
|
||||
Ping(context.Context) error
|
||||
Host() Host
|
||||
ContainerActions(ctx context.Context, action ContainerAction, containerID string) error
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
package docker
|
||||
package container
|
||||
|
||||
import (
|
||||
"context"
|
||||
@@ -13,32 +13,40 @@ import (
|
||||
"golang.org/x/sync/semaphore"
|
||||
)
|
||||
|
||||
type StatsCollector interface {
|
||||
Start(parentCtx context.Context) bool
|
||||
Subscribe(ctx context.Context, stats chan<- ContainerStat)
|
||||
Stop()
|
||||
}
|
||||
|
||||
type ContainerStore struct {
|
||||
containers *xsync.MapOf[string, *Container]
|
||||
subscribers *xsync.MapOf[context.Context, chan<- ContainerEvent]
|
||||
newContainerSubscribers *xsync.MapOf[context.Context, chan<- Container]
|
||||
client Client
|
||||
statsCollector *StatsCollector
|
||||
statsCollector StatsCollector
|
||||
wg sync.WaitGroup
|
||||
connected atomic.Bool
|
||||
events chan ContainerEvent
|
||||
ctx context.Context
|
||||
filter ContainerFilter
|
||||
labels ContainerLabels
|
||||
}
|
||||
|
||||
func NewContainerStore(ctx context.Context, client Client, filter ContainerFilter) *ContainerStore {
|
||||
log.Debug().Str("host", client.Host().Name).Interface("filter", filter).Msg("initializing container store")
|
||||
const defaultTimeout = 10 * time.Second
|
||||
|
||||
func NewContainerStore(ctx context.Context, client Client, statsCollect StatsCollector, labels ContainerLabels) *ContainerStore {
|
||||
log.Debug().Str("host", client.Host().Name).Interface("labels", labels).Msg("initializing container store")
|
||||
|
||||
s := &ContainerStore{
|
||||
containers: xsync.NewMapOf[string, *Container](),
|
||||
client: client,
|
||||
subscribers: xsync.NewMapOf[context.Context, chan<- ContainerEvent](),
|
||||
newContainerSubscribers: xsync.NewMapOf[context.Context, chan<- Container](),
|
||||
statsCollector: NewStatsCollector(client, filter),
|
||||
statsCollector: statsCollect,
|
||||
wg: sync.WaitGroup{},
|
||||
events: make(chan ContainerEvent),
|
||||
ctx: ctx,
|
||||
filter: filter,
|
||||
labels: labels,
|
||||
}
|
||||
|
||||
s.wg.Add(1)
|
||||
@@ -64,9 +72,9 @@ func (s *ContainerStore) checkConnectivity() error {
|
||||
s.connected.Store(false)
|
||||
}()
|
||||
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 3*time.Second) // 3s is enough to fetch all containers
|
||||
ctx, cancel := context.WithTimeout(context.Background(), defaultTimeout)
|
||||
defer cancel()
|
||||
if containers, err := s.client.ListContainers(ctx, s.filter); err != nil {
|
||||
if containers, err := s.client.ListContainers(ctx, s.labels); err != nil {
|
||||
return err
|
||||
} else {
|
||||
s.containers.Clear()
|
||||
@@ -76,7 +84,7 @@ func (s *ContainerStore) checkConnectivity() error {
|
||||
}
|
||||
|
||||
running := lo.Filter(containers, func(item Container, index int) bool {
|
||||
return item.State != "exited"
|
||||
return item.State != "exited" && !item.FullyLoaded
|
||||
})
|
||||
|
||||
sem := semaphore.NewWeighted(maxFetchParallelism)
|
||||
@@ -88,7 +96,7 @@ func (s *ContainerStore) checkConnectivity() error {
|
||||
}
|
||||
go func(c Container, i int) {
|
||||
defer sem.Release(1)
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 2*time.Second) // 2s is hardcoded timeout for fetching container
|
||||
ctx, cancel := context.WithTimeout(context.Background(), defaultTimeout)
|
||||
defer cancel()
|
||||
if container, err := s.client.FindContainer(ctx, c.ID); err == nil {
|
||||
s.containers.Store(c.ID, &container)
|
||||
@@ -107,38 +115,44 @@ func (s *ContainerStore) checkConnectivity() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *ContainerStore) ListContainers(filter ContainerFilter) ([]Container, error) {
|
||||
func (s *ContainerStore) ListContainers(labels ContainerLabels) ([]Container, error) {
|
||||
s.wg.Wait()
|
||||
|
||||
if err := s.checkConnectivity(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
validContainers, err := s.client.ListContainers(s.ctx, filter)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
validIDMap := lo.KeyBy(validContainers, func(item Container) string {
|
||||
return item.ID
|
||||
})
|
||||
|
||||
containers := make([]Container, 0)
|
||||
s.containers.Range(func(_ string, c *Container) bool {
|
||||
if _, ok := validIDMap[c.ID]; ok {
|
||||
containers = append(containers, *c)
|
||||
if labels.Exists() {
|
||||
validContainers, err := s.client.ListContainers(s.ctx, labels)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return true
|
||||
})
|
||||
|
||||
validIDMap := lo.KeyBy(validContainers, func(item Container) string {
|
||||
return item.ID
|
||||
})
|
||||
|
||||
s.containers.Range(func(_ string, c *Container) bool {
|
||||
if _, ok := validIDMap[c.ID]; ok {
|
||||
containers = append(containers, *c)
|
||||
}
|
||||
return true
|
||||
})
|
||||
} else {
|
||||
s.containers.Range(func(_ string, c *Container) bool {
|
||||
containers = append(containers, *c)
|
||||
return true
|
||||
})
|
||||
}
|
||||
|
||||
return containers, nil
|
||||
}
|
||||
|
||||
func (s *ContainerStore) FindContainer(id string, filter ContainerFilter) (Container, error) {
|
||||
func (s *ContainerStore) FindContainer(id string, labels ContainerLabels) (Container, error) {
|
||||
s.wg.Wait()
|
||||
|
||||
if filter.Exists() {
|
||||
validContainers, err := s.client.ListContainers(s.ctx, filter)
|
||||
if labels.Exists() {
|
||||
validContainers, err := s.client.ListContainers(s.ctx, labels)
|
||||
if err != nil {
|
||||
return Container{}, err
|
||||
}
|
||||
@@ -154,31 +168,38 @@ func (s *ContainerStore) FindContainer(id string, filter ContainerFilter) (Conta
|
||||
}
|
||||
|
||||
if container, ok := s.containers.Load(id); ok {
|
||||
if container.StartedAt.IsZero() {
|
||||
log.Debug().Str("id", id).Msg("container doesn't have detailed information, fetching it")
|
||||
if !container.FullyLoaded {
|
||||
log.Debug().Str("id", id).Msg("container is not fully loaded, fetching it")
|
||||
if newContainer, ok := s.containers.Compute(id, func(c *Container, loaded bool) (*Container, bool) {
|
||||
if loaded {
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 3*time.Second)
|
||||
ctx, cancel := context.WithTimeout(context.Background(), defaultTimeout)
|
||||
defer cancel()
|
||||
if newContainer, err := s.client.FindContainer(ctx, id); err == nil {
|
||||
return &newContainer, false
|
||||
} else {
|
||||
log.Error().Err(err).Msg("failed to fetch container")
|
||||
return c, false
|
||||
}
|
||||
}
|
||||
return c, false
|
||||
}); ok {
|
||||
event := ContainerEvent{
|
||||
Name: "update",
|
||||
Host: s.client.Host().ID,
|
||||
ActorID: id,
|
||||
}
|
||||
s.subscribers.Range(func(c context.Context, events chan<- ContainerEvent) bool {
|
||||
select {
|
||||
case events <- event:
|
||||
case <-c.Done():
|
||||
s.subscribers.Delete(c)
|
||||
go func() {
|
||||
event := ContainerEvent{
|
||||
Name: "update",
|
||||
Host: newContainer.Host,
|
||||
ActorID: id,
|
||||
Container: newContainer,
|
||||
}
|
||||
return true
|
||||
})
|
||||
|
||||
s.subscribers.Range(func(c context.Context, events chan<- ContainerEvent) bool {
|
||||
select {
|
||||
case events <- event:
|
||||
case <-c.Done():
|
||||
s.subscribers.Delete(c)
|
||||
}
|
||||
return true
|
||||
})
|
||||
}()
|
||||
return *newContainer, nil
|
||||
}
|
||||
}
|
||||
@@ -234,13 +255,37 @@ func (s *ContainerStore) init() {
|
||||
for {
|
||||
select {
|
||||
case event := <-s.events:
|
||||
log.Trace().Str("event", event.Name).Str("id", event.ActorID).Msg("received container event")
|
||||
log.Debug().Str("event", event.Name).Str("id", event.ActorID).Msg("received container event")
|
||||
switch event.Name {
|
||||
case "start":
|
||||
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.filter)
|
||||
list, _ := s.client.ListContainers(ctx, s.labels)
|
||||
|
||||
// make sure the container is in the list of containers when using filter
|
||||
valid := lo.ContainsBy(list, func(item Container) bool {
|
||||
return item.ID == container.ID
|
||||
})
|
||||
|
||||
if valid {
|
||||
s.containers.Store(container.ID, &container)
|
||||
s.newContainerSubscribers.Range(func(c context.Context, containers chan<- Container) bool {
|
||||
select {
|
||||
case containers <- container:
|
||||
case <-c.Done():
|
||||
}
|
||||
return true
|
||||
})
|
||||
}
|
||||
}
|
||||
cancel()
|
||||
|
||||
case "start":
|
||||
ctx, cancel := context.WithTimeout(context.Background(), defaultTimeout)
|
||||
|
||||
if container, err := s.client.FindContainer(ctx, event.ActorID); err == nil {
|
||||
list, _ := s.client.ListContainers(ctx, s.labels)
|
||||
|
||||
// make sure the container is in the list of containers when using filter
|
||||
valid := lo.ContainsBy(list, func(item Container) bool {
|
||||
@@ -248,7 +293,6 @@ func (s *ContainerStore) init() {
|
||||
})
|
||||
|
||||
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 {
|
||||
@@ -264,6 +308,42 @@ func (s *ContainerStore) init() {
|
||||
log.Debug().Str("id", event.ActorID).Msg("container destroyed")
|
||||
s.containers.Delete(event.ActorID)
|
||||
|
||||
case "update":
|
||||
started := false
|
||||
updatedContainer, _ := s.containers.Compute(event.ActorID, func(c *Container, loaded bool) (*Container, bool) {
|
||||
if loaded {
|
||||
newContainer := event.Container
|
||||
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
|
||||
c.Host = newContainer.Host
|
||||
return c, false
|
||||
} else {
|
||||
return c, true
|
||||
}
|
||||
})
|
||||
|
||||
if started {
|
||||
s.subscribers.Range(func(ctx context.Context, events chan<- ContainerEvent) bool {
|
||||
select {
|
||||
case events <- ContainerEvent{
|
||||
Name: "start",
|
||||
ActorID: updatedContainer.ID,
|
||||
Host: updatedContainer.Host,
|
||||
}:
|
||||
case <-ctx.Done():
|
||||
s.subscribers.Delete(ctx)
|
||||
}
|
||||
return true
|
||||
})
|
||||
}
|
||||
|
||||
case "die":
|
||||
s.containers.Compute(event.ActorID, func(c *Container, loaded bool) (*Container, bool) {
|
||||
if loaded {
|
||||
@@ -1,4 +1,4 @@
|
||||
package docker
|
||||
package container
|
||||
|
||||
import (
|
||||
"context"
|
||||
@@ -14,7 +14,7 @@ type mockedClient struct {
|
||||
Client
|
||||
}
|
||||
|
||||
func (m *mockedClient) ListContainers(ctx context.Context, filter ContainerFilter) ([]Container, error) {
|
||||
func (m *mockedClient) ListContainers(ctx context.Context, filter ContainerLabels) ([]Container, error) {
|
||||
args := m.Called(ctx, filter)
|
||||
return args.Get(0).([]Container), args.Error(1)
|
||||
}
|
||||
@@ -48,7 +48,7 @@ func TestContainerStore_List(t *testing.T) {
|
||||
Name: "test",
|
||||
},
|
||||
}, nil)
|
||||
client.On("ContainerEvents", mock.Anything, mock.AnythingOfType("chan<- docker.ContainerEvent")).Return(nil).Run(func(args mock.Arguments) {
|
||||
client.On("ContainerEvents", mock.Anything, mock.AnythingOfType("chan<- container.ContainerEvent")).Return(nil).Run(func(args mock.Arguments) {
|
||||
ctx := args.Get(0).(context.Context)
|
||||
<-ctx.Done()
|
||||
})
|
||||
@@ -63,11 +63,9 @@ func TestContainerStore_List(t *testing.T) {
|
||||
Stats: utils.NewRingBuffer[ContainerStat](300),
|
||||
}, nil)
|
||||
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
t.Cleanup(cancel)
|
||||
|
||||
store := NewContainerStore(ctx, client, ContainerFilter{})
|
||||
containers, _ := store.ListContainers(ContainerFilter{})
|
||||
collector := &fakeStatsCollector{}
|
||||
store := NewContainerStore(t.Context(), client, collector, ContainerLabels{})
|
||||
containers, _ := store.ListContainers(ContainerLabels{})
|
||||
|
||||
assert.Equal(t, containers[0].ID, "1234")
|
||||
}
|
||||
@@ -83,7 +81,7 @@ func TestContainerStore_die(t *testing.T) {
|
||||
},
|
||||
}, nil)
|
||||
|
||||
client.On("ContainerEvents", mock.Anything, mock.AnythingOfType("chan<- docker.ContainerEvent")).Return(nil).
|
||||
client.On("ContainerEvents", mock.Anything, mock.AnythingOfType("chan<- container.ContainerEvent")).Return(nil).
|
||||
Run(func(args mock.Arguments) {
|
||||
ctx := args.Get(0).(context.Context)
|
||||
events := args.Get(1).(chan<- ContainerEvent)
|
||||
@@ -98,7 +96,7 @@ func TestContainerStore_die(t *testing.T) {
|
||||
ID: "localhost",
|
||||
})
|
||||
|
||||
client.On("ContainerStats", mock.Anything, "1234", mock.AnythingOfType("chan<- docker.ContainerStat")).Return(nil)
|
||||
client.On("ContainerStats", mock.Anything, "1234", mock.AnythingOfType("chan<- container.ContainerStat")).Return(nil)
|
||||
|
||||
client.On("FindContainer", mock.Anything, "1234").Return(Container{
|
||||
ID: "1234",
|
||||
@@ -107,15 +105,19 @@ func TestContainerStore_die(t *testing.T) {
|
||||
Stats: utils.NewRingBuffer[ContainerStat](300),
|
||||
}, nil)
|
||||
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
t.Cleanup(cancel)
|
||||
store := NewContainerStore(ctx, client, ContainerFilter{})
|
||||
store := NewContainerStore(t.Context(), client, &fakeStatsCollector{}, ContainerLabels{})
|
||||
|
||||
// Wait until we get the event
|
||||
events := make(chan ContainerEvent)
|
||||
store.SubscribeEvents(ctx, events)
|
||||
store.SubscribeEvents(t.Context(), events)
|
||||
<-events
|
||||
|
||||
containers, _ := store.ListContainers(ContainerFilter{})
|
||||
containers, _ := store.ListContainers(ContainerLabels{})
|
||||
assert.Equal(t, containers[0].State, "exited")
|
||||
}
|
||||
|
||||
type fakeStatsCollector struct{}
|
||||
|
||||
func (f *fakeStatsCollector) Subscribe(_ context.Context, _ chan<- ContainerStat) {}
|
||||
func (f *fakeStatsCollector) Start(_ context.Context) bool { return true }
|
||||
func (f *fakeStatsCollector) Stop() {}
|
||||
@@ -1,4 +1,4 @@
|
||||
package docker
|
||||
package container
|
||||
|
||||
import (
|
||||
"html"
|
||||
@@ -30,7 +30,6 @@ func escape(logEvent *LogEvent) {
|
||||
}
|
||||
|
||||
func escapeAnyMap(orderedMap *orderedmap.OrderedMap[string, any]) {
|
||||
|
||||
for pair := orderedMap.Oldest(); pair != nil; pair = pair.Next() {
|
||||
switch value := pair.Value.(type) {
|
||||
case string:
|
||||
@@ -1,14 +1,10 @@
|
||||
package docker
|
||||
package container
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"bytes"
|
||||
"context"
|
||||
"encoding/binary"
|
||||
"errors"
|
||||
"fmt"
|
||||
"hash/fnv"
|
||||
"io"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
@@ -23,30 +19,26 @@ import (
|
||||
type EventGenerator struct {
|
||||
Events chan *LogEvent
|
||||
Errors chan error
|
||||
reader *bufio.Reader
|
||||
reader LogReader
|
||||
next *LogEvent
|
||||
buffer chan *LogEvent
|
||||
tty bool
|
||||
wg sync.WaitGroup
|
||||
containerID string
|
||||
ctx context.Context
|
||||
}
|
||||
|
||||
var bufPool = sync.Pool{
|
||||
New: func() any {
|
||||
return new(bytes.Buffer)
|
||||
},
|
||||
}
|
||||
|
||||
var ErrBadHeader = fmt.Errorf("dozzle/docker: unable to read header")
|
||||
|
||||
func NewEventGenerator(ctx context.Context, reader io.Reader, container Container) *EventGenerator {
|
||||
type LogReader interface {
|
||||
Read() (string, StdType, error)
|
||||
}
|
||||
|
||||
func NewEventGenerator(ctx context.Context, reader LogReader, container Container) *EventGenerator {
|
||||
generator := &EventGenerator{
|
||||
reader: bufio.NewReader(reader),
|
||||
reader: reader,
|
||||
buffer: make(chan *LogEvent, 100),
|
||||
Errors: make(chan error, 1),
|
||||
Events: make(chan *LogEvent),
|
||||
tty: container.Tty,
|
||||
containerID: container.ID,
|
||||
ctx: ctx,
|
||||
}
|
||||
@@ -90,7 +82,7 @@ loop:
|
||||
|
||||
func (g *EventGenerator) consumeReader() {
|
||||
for {
|
||||
message, streamType, readerError := readEvent(g.reader, g.tty)
|
||||
message, streamType, readerError := g.reader.Read()
|
||||
if message != "" {
|
||||
logEvent := createEvent(message, streamType)
|
||||
logEvent.ContainerID = g.containerID
|
||||
@@ -123,50 +115,6 @@ func (g *EventGenerator) peek() *LogEvent {
|
||||
}
|
||||
}
|
||||
|
||||
func readEvent(reader *bufio.Reader, tty bool) (string, StdType, error) {
|
||||
header := []byte{0, 0, 0, 0, 0, 0, 0, 0}
|
||||
buffer := bufPool.Get().(*bytes.Buffer)
|
||||
buffer.Reset()
|
||||
defer bufPool.Put(buffer)
|
||||
var streamType StdType = STDOUT
|
||||
if tty {
|
||||
message, err := reader.ReadString('\n')
|
||||
if err != nil {
|
||||
return message, streamType, err
|
||||
}
|
||||
return message, streamType, nil
|
||||
} else {
|
||||
n, err := io.ReadFull(reader, header)
|
||||
if err != nil {
|
||||
return "", streamType, err
|
||||
}
|
||||
if n != 8 {
|
||||
log.Warn().Bytes("header", header).Msg("short read")
|
||||
message, _ := reader.ReadString('\n')
|
||||
return message, streamType, ErrBadHeader
|
||||
}
|
||||
|
||||
switch header[0] {
|
||||
case 1:
|
||||
streamType = STDOUT
|
||||
case 2:
|
||||
streamType = STDERR
|
||||
default:
|
||||
log.Warn().Bytes("header", header).Msg("unknown stream type")
|
||||
}
|
||||
|
||||
count := binary.BigEndian.Uint32(header[4:])
|
||||
if count == 0 {
|
||||
return "", streamType, nil
|
||||
}
|
||||
_, err = io.CopyN(buffer, reader, int64(count))
|
||||
if err != nil {
|
||||
return "", streamType, err
|
||||
}
|
||||
return buffer.String(), streamType, nil
|
||||
}
|
||||
}
|
||||
|
||||
func createEvent(message string, streamType StdType) *LogEvent {
|
||||
h := fnv.New32a()
|
||||
h.Write([]byte(message))
|
||||
@@ -1,12 +1,9 @@
|
||||
package docker
|
||||
package container
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"bytes"
|
||||
"context"
|
||||
"encoding/binary"
|
||||
"io"
|
||||
"reflect"
|
||||
"strings"
|
||||
"sync"
|
||||
"testing"
|
||||
"time"
|
||||
@@ -18,9 +15,8 @@ import (
|
||||
|
||||
func TestEventGenerator_Events_tty(t *testing.T) {
|
||||
input := "example input"
|
||||
reader := bufio.NewReader(strings.NewReader(input))
|
||||
|
||||
g := NewEventGenerator(context.Background(), reader, Container{Tty: true})
|
||||
g := NewEventGenerator(context.Background(), makeFakeReader(input, STDOUT), Container{Tty: true})
|
||||
event := <-g.Events
|
||||
|
||||
require.NotNil(t, event, "Expected event to not be nil, but got nil")
|
||||
@@ -29,9 +25,8 @@ func TestEventGenerator_Events_tty(t *testing.T) {
|
||||
|
||||
func TestEventGenerator_Events_non_tty(t *testing.T) {
|
||||
input := "example input"
|
||||
reader := bytes.NewReader(makeMessage(input, STDOUT))
|
||||
|
||||
g := NewEventGenerator(context.Background(), reader, Container{Tty: false})
|
||||
g := NewEventGenerator(context.Background(), makeFakeReader(input, STDOUT), Container{Tty: false})
|
||||
event := <-g.Events
|
||||
|
||||
require.NotNil(t, event, "Expected event to not be nil, but got nil")
|
||||
@@ -40,9 +35,8 @@ func TestEventGenerator_Events_non_tty(t *testing.T) {
|
||||
|
||||
func TestEventGenerator_Events_non_tty_close_channel(t *testing.T) {
|
||||
input := "example input"
|
||||
reader := bytes.NewReader(makeMessage(input, STDOUT))
|
||||
|
||||
g := NewEventGenerator(context.Background(), reader, Container{Tty: false})
|
||||
g := NewEventGenerator(context.Background(), makeFakeReader(input, STDOUT), Container{Tty: false})
|
||||
<-g.Events
|
||||
_, ok := <-g.Events
|
||||
|
||||
@@ -51,20 +45,31 @@ func TestEventGenerator_Events_non_tty_close_channel(t *testing.T) {
|
||||
|
||||
func TestEventGenerator_Events_routines_done(t *testing.T) {
|
||||
input := "example input"
|
||||
reader := bytes.NewReader(makeMessage(input, STDOUT))
|
||||
|
||||
g := NewEventGenerator(context.Background(), reader, Container{Tty: false})
|
||||
g := NewEventGenerator(context.Background(), makeFakeReader(input, STDOUT), Container{Tty: false})
|
||||
<-g.Events
|
||||
assert.False(t, waitTimeout(&g.wg, 1*time.Second), "Expected routines to be done")
|
||||
}
|
||||
|
||||
func makeMessage(message string, stream StdType) []byte {
|
||||
data := make([]byte, 8)
|
||||
binary.BigEndian.PutUint32(data[4:], uint32(len(message)))
|
||||
data[0] = byte(stream / 2)
|
||||
data = append(data, []byte(message)...)
|
||||
type mockLogReader struct {
|
||||
messages []string
|
||||
types []StdType
|
||||
i int
|
||||
}
|
||||
|
||||
return data
|
||||
func (m *mockLogReader) Read() (string, StdType, error) {
|
||||
if m.i >= len(m.messages) {
|
||||
return "", 0, io.EOF
|
||||
}
|
||||
m.i++
|
||||
return m.messages[m.i-1], m.types[m.i-1], nil
|
||||
}
|
||||
|
||||
func makeFakeReader(message string, stream StdType) LogReader {
|
||||
return &mockLogReader{
|
||||
messages: []string{message},
|
||||
types: []StdType{stream},
|
||||
}
|
||||
}
|
||||
|
||||
func waitTimeout(wg *sync.WaitGroup, timeout time.Duration) bool {
|
||||
@@ -156,23 +161,3 @@ func Test_createEvent(t *testing.T) {
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
type mockReadCloser struct {
|
||||
bytes []byte
|
||||
}
|
||||
|
||||
func (m mockReadCloser) Read(p []byte) (int, error) {
|
||||
return copy(p, m.bytes), nil
|
||||
}
|
||||
|
||||
func Benchmark_readEvent(b *testing.B) {
|
||||
b.ReportAllocs()
|
||||
|
||||
data := makeMessage("2020-05-13T18:55:37.772853839Z {\"key\": \"value\"}\n", STDOUT)
|
||||
|
||||
reader := bufio.NewReader(mockReadCloser{bytes: data})
|
||||
|
||||
for i := 0; i < b.N; i++ {
|
||||
readEvent(reader, true)
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
package docker
|
||||
package container
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
@@ -25,6 +25,7 @@ type Host struct {
|
||||
AgentVersion string `json:"agentVersion,omitempty"`
|
||||
Type string `json:"type"`
|
||||
Available bool `json:"available"`
|
||||
Swarm bool `json:"-"`
|
||||
}
|
||||
|
||||
func (h Host) String() string {
|
||||
@@ -1,4 +1,4 @@
|
||||
package docker
|
||||
package container
|
||||
|
||||
import (
|
||||
"regexp"
|
||||
@@ -1,4 +1,4 @@
|
||||
package docker
|
||||
package container
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
@@ -1,4 +1,4 @@
|
||||
package docker
|
||||
package container
|
||||
|
||||
import (
|
||||
"errors"
|
||||
@@ -1,4 +1,4 @@
|
||||
package docker
|
||||
package container
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
@@ -1,4 +1,4 @@
|
||||
package docker
|
||||
package container
|
||||
|
||||
import (
|
||||
"regexp"
|
||||
@@ -1,4 +1,4 @@
|
||||
package docker
|
||||
package container
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
@@ -7,25 +7,25 @@ import (
|
||||
"time"
|
||||
|
||||
"github.com/amir20/dozzle/internal/utils"
|
||||
"github.com/docker/docker/api/types/filters"
|
||||
)
|
||||
|
||||
// Container represents an internal representation of docker containers
|
||||
type Container struct {
|
||||
ID string `json:"id"`
|
||||
Name string `json:"name"`
|
||||
Image string `json:"image"`
|
||||
Command string `json:"command"`
|
||||
Created time.Time `json:"created"`
|
||||
StartedAt time.Time `json:"startedAt"`
|
||||
FinishedAt time.Time `json:"finishedAt"`
|
||||
State string `json:"state"`
|
||||
Health string `json:"health,omitempty"`
|
||||
Host string `json:"host,omitempty"`
|
||||
Tty bool `json:"-"`
|
||||
Labels map[string]string `json:"labels,omitempty"`
|
||||
Stats *utils.RingBuffer[ContainerStat] `json:"stats,omitempty"`
|
||||
Group string `json:"group,omitempty"`
|
||||
ID string `json:"id"`
|
||||
Name string `json:"name"`
|
||||
Image string `json:"image"`
|
||||
Command string `json:"command"`
|
||||
Created time.Time `json:"created"`
|
||||
StartedAt time.Time `json:"startedAt"`
|
||||
FinishedAt time.Time `json:"finishedAt"`
|
||||
State string `json:"state"`
|
||||
Health string `json:"health,omitempty"`
|
||||
Host string `json:"host,omitempty"`
|
||||
Tty bool `json:"-"`
|
||||
Labels map[string]string `json:"labels,omitempty"`
|
||||
Stats *utils.RingBuffer[ContainerStat] `json:"stats,omitempty"`
|
||||
Group string `json:"group,omitempty"`
|
||||
FullyLoaded bool `json:"-,omitempty"`
|
||||
}
|
||||
|
||||
// ContainerStat represent stats instant for a container
|
||||
@@ -43,12 +43,13 @@ type ContainerEvent struct {
|
||||
ActorID string `json:"actorId"`
|
||||
ActorAttributes map[string]string `json:"actorAttributes,omitempty"`
|
||||
Time time.Time `json:"time"`
|
||||
Container *Container `json:"-"`
|
||||
}
|
||||
|
||||
type ContainerFilter map[string][]string
|
||||
type ContainerLabels map[string][]string
|
||||
|
||||
func ParseContainerFilter(commaValues string) (ContainerFilter, error) {
|
||||
filter := make(ContainerFilter)
|
||||
func ParseContainerFilter(commaValues string) (ContainerLabels, error) {
|
||||
filter := make(ContainerLabels)
|
||||
if commaValues == "" {
|
||||
return filter, nil
|
||||
}
|
||||
@@ -66,21 +67,10 @@ func ParseContainerFilter(commaValues string) (ContainerFilter, error) {
|
||||
return filter, nil
|
||||
}
|
||||
|
||||
func (f ContainerFilter) Exists() bool {
|
||||
func (f ContainerLabels) Exists() bool {
|
||||
return len(f) > 0
|
||||
}
|
||||
|
||||
func (f ContainerFilter) asArgs() filters.Args {
|
||||
filterArgs := filters.NewArgs()
|
||||
for key, values := range f {
|
||||
for _, value := range values {
|
||||
filterArgs.Add(key, value)
|
||||
}
|
||||
}
|
||||
|
||||
return filterArgs
|
||||
}
|
||||
|
||||
type LogPosition string
|
||||
|
||||
const (
|
||||
@@ -11,112 +11,76 @@ import (
|
||||
|
||||
"encoding/json"
|
||||
|
||||
"github.com/amir20/dozzle/internal/container"
|
||||
"github.com/amir20/dozzle/internal/utils"
|
||||
"github.com/docker/docker/api/types"
|
||||
"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/swarm"
|
||||
"github.com/docker/docker/api/types/filters"
|
||||
"github.com/docker/docker/api/types/system"
|
||||
"github.com/docker/docker/client"
|
||||
|
||||
"github.com/rs/zerolog/log"
|
||||
)
|
||||
|
||||
type StdType int
|
||||
|
||||
const (
|
||||
UNKNOWN StdType = 1 << iota
|
||||
STDOUT
|
||||
STDERR
|
||||
)
|
||||
const STDALL = STDOUT | STDERR
|
||||
|
||||
func (s StdType) String() string {
|
||||
switch s {
|
||||
case STDOUT:
|
||||
return "stdout"
|
||||
case STDERR:
|
||||
return "stderr"
|
||||
case STDALL:
|
||||
return "all"
|
||||
default:
|
||||
return "unknown"
|
||||
}
|
||||
}
|
||||
|
||||
type DockerCLI interface {
|
||||
ContainerList(context.Context, container.ListOptions) ([]types.Container, error)
|
||||
ContainerLogs(context.Context, string, container.LogsOptions) (io.ReadCloser, error)
|
||||
ContainerList(context.Context, docker.ListOptions) ([]types.Container, error)
|
||||
ContainerLogs(context.Context, string, docker.LogsOptions) (io.ReadCloser, error)
|
||||
Events(context.Context, events.ListOptions) (<-chan events.Message, <-chan error)
|
||||
ContainerInspect(ctx context.Context, containerID string) (types.ContainerJSON, error)
|
||||
ContainerStats(ctx context.Context, containerID string, stream bool) (container.StatsResponseReader, error)
|
||||
ContainerStats(ctx context.Context, containerID string, stream bool) (docker.StatsResponseReader, error)
|
||||
Ping(ctx context.Context) (types.Ping, error)
|
||||
ContainerStart(ctx context.Context, containerID string, options container.StartOptions) error
|
||||
ContainerStop(ctx context.Context, containerID string, options container.StopOptions) error
|
||||
ContainerRestart(ctx context.Context, containerID string, options container.StopOptions) error
|
||||
ContainerStart(ctx context.Context, containerID string, options docker.StartOptions) error
|
||||
ContainerStop(ctx context.Context, containerID string, options docker.StopOptions) error
|
||||
ContainerRestart(ctx context.Context, containerID string, options docker.StopOptions) error
|
||||
Info(ctx context.Context) (system.Info, error)
|
||||
}
|
||||
|
||||
type Client interface {
|
||||
ListContainers(context.Context, ContainerFilter) ([]Container, error)
|
||||
FindContainer(context.Context, string) (Container, error)
|
||||
ContainerLogs(context.Context, string, time.Time, StdType) (io.ReadCloser, error)
|
||||
ContainerEvents(context.Context, chan<- ContainerEvent) error
|
||||
ContainerLogsBetweenDates(context.Context, string, time.Time, time.Time, StdType) (io.ReadCloser, error)
|
||||
ContainerStats(context.Context, string, chan<- ContainerStat) error
|
||||
Ping(context.Context) (types.Ping, error)
|
||||
Host() Host
|
||||
ContainerActions(ctx context.Context, action ContainerAction, containerID string) error
|
||||
IsSwarmMode() bool
|
||||
SystemInfo() system.Info
|
||||
}
|
||||
|
||||
type httpClient struct {
|
||||
type DockerClient struct {
|
||||
cli DockerCLI
|
||||
host Host
|
||||
host container.Host
|
||||
info system.Info
|
||||
}
|
||||
|
||||
func NewClient(cli DockerCLI, host Host) Client {
|
||||
func NewClient(cli DockerCLI, host container.Host) *DockerClient {
|
||||
info, err := cli.Info(context.Background())
|
||||
if err != nil {
|
||||
log.Error().Err(err).Msg("Failed to get docker info")
|
||||
}
|
||||
|
||||
host.NCPU = info.NCPU
|
||||
host.MemTotal = info.MemTotal
|
||||
host.DockerVersion = info.ServerVersion
|
||||
|
||||
return &httpClient{
|
||||
cli: cli,
|
||||
host: host,
|
||||
info: info,
|
||||
}
|
||||
}
|
||||
|
||||
// NewClientWithFilters creates a new instance of Client with docker filters
|
||||
func NewLocalClient(hostname string) (Client, error) {
|
||||
cli, err := client.NewClientWithOpts(client.FromEnv, client.WithAPIVersionNegotiation())
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
info, err := cli.Info(context.Background())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
id := info.ID
|
||||
if info.Swarm.NodeID != "" {
|
||||
id = info.Swarm.NodeID
|
||||
}
|
||||
|
||||
host := Host{
|
||||
ID: id,
|
||||
host.ID = id
|
||||
host.NCPU = info.NCPU
|
||||
host.MemTotal = info.MemTotal
|
||||
host.DockerVersion = info.ServerVersion
|
||||
host.Swarm = info.Swarm.NodeID != ""
|
||||
|
||||
return &DockerClient{
|
||||
cli: cli,
|
||||
host: host,
|
||||
info: info,
|
||||
}
|
||||
}
|
||||
|
||||
// NewClientWithFilters creates a new instance of Client with docker filters
|
||||
func NewLocalClient(hostname string) (*DockerClient, error) {
|
||||
cli, err := client.NewClientWithOpts(client.FromEnv, client.WithAPIVersionNegotiation(), client.WithUserAgent("Docker-Client/Dozzle"))
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
info, err := cli.Info(context.Background())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
host := container.Host{
|
||||
Name: info.Name,
|
||||
MemTotal: info.MemTotal,
|
||||
NCPU: info.NCPU,
|
||||
Endpoint: "local",
|
||||
Type: "local",
|
||||
}
|
||||
@@ -128,7 +92,7 @@ func NewLocalClient(hostname string) (Client, error) {
|
||||
return NewClient(cli, host), nil
|
||||
}
|
||||
|
||||
func NewRemoteClient(host Host) (Client, error) {
|
||||
func NewRemoteClient(host container.Host) (*DockerClient, error) {
|
||||
if host.URL.Scheme != "tcp" {
|
||||
return nil, fmt.Errorf("invalid scheme: %s", host.URL.Scheme)
|
||||
}
|
||||
@@ -144,7 +108,7 @@ func NewRemoteClient(host Host) (Client, error) {
|
||||
log.Debug().Msg("Not using TLS for remote client")
|
||||
}
|
||||
|
||||
opts = append(opts, client.WithAPIVersionNegotiation())
|
||||
opts = append(opts, client.WithAPIVersionNegotiation(), client.WithUserAgent("Docker-Client/Dozzle"))
|
||||
|
||||
cli, err := client.NewClientWithOpts(opts...)
|
||||
|
||||
@@ -158,33 +122,39 @@ func NewRemoteClient(host Host) (Client, error) {
|
||||
}
|
||||
|
||||
// Finds a container by id, skipping the filters
|
||||
func (d *httpClient) FindContainer(ctx context.Context, id string) (Container, error) {
|
||||
func (d *DockerClient) FindContainer(ctx context.Context, id string) (container.Container, error) {
|
||||
log.Debug().Str("id", id).Msg("Finding container")
|
||||
if json, err := d.cli.ContainerInspect(ctx, id); err == nil {
|
||||
return newContainerFromJSON(json, d.host.ID), nil
|
||||
} else {
|
||||
return Container{}, err
|
||||
return container.Container{}, err
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func (d *httpClient) ContainerActions(ctx context.Context, action ContainerAction, containerID string) error {
|
||||
func (d *DockerClient) ContainerActions(ctx context.Context, action container.ContainerAction, containerID string) error {
|
||||
switch action {
|
||||
case Start:
|
||||
return d.cli.ContainerStart(ctx, containerID, container.StartOptions{})
|
||||
case Stop:
|
||||
return d.cli.ContainerStop(ctx, containerID, container.StopOptions{})
|
||||
case Restart:
|
||||
return d.cli.ContainerRestart(ctx, containerID, container.StopOptions{})
|
||||
case container.Start:
|
||||
return d.cli.ContainerStart(ctx, containerID, docker.StartOptions{})
|
||||
case container.Stop:
|
||||
return d.cli.ContainerStop(ctx, containerID, docker.StopOptions{})
|
||||
case container.Restart:
|
||||
return d.cli.ContainerRestart(ctx, containerID, docker.StopOptions{})
|
||||
default:
|
||||
return fmt.Errorf("unknown action: %s", action)
|
||||
}
|
||||
}
|
||||
|
||||
func (d *httpClient) ListContainers(ctx context.Context, filter ContainerFilter) ([]Container, error) {
|
||||
log.Debug().Interface("filter", filter).Str("host", d.host.Name).Msg("Listing containers")
|
||||
containerListOptions := container.ListOptions{
|
||||
Filters: filter.asArgs(),
|
||||
func (d *DockerClient) ListContainers(ctx context.Context, labels container.ContainerLabels) ([]container.Container, error) {
|
||||
log.Debug().Interface("labels", labels).Str("host", d.host.Name).Msg("Listing containers")
|
||||
filterArgs := filters.NewArgs()
|
||||
for key, values := range labels {
|
||||
for _, value := range values {
|
||||
filterArgs.Add(key, value)
|
||||
}
|
||||
}
|
||||
containerListOptions := docker.ListOptions{
|
||||
Filters: filterArgs,
|
||||
All: true,
|
||||
}
|
||||
list, err := d.cli.ContainerList(ctx, containerListOptions)
|
||||
@@ -192,7 +162,7 @@ func (d *httpClient) ListContainers(ctx context.Context, filter ContainerFilter)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var containers = make([]Container, 0, len(list))
|
||||
var containers = make([]container.Container, 0, len(list))
|
||||
for _, c := range list {
|
||||
containers = append(containers, newContainer(c, d.host.ID))
|
||||
}
|
||||
@@ -204,7 +174,7 @@ func (d *httpClient) ListContainers(ctx context.Context, filter ContainerFilter)
|
||||
return containers, nil
|
||||
}
|
||||
|
||||
func (d *httpClient) ContainerStats(ctx context.Context, id string, stats chan<- ContainerStat) error {
|
||||
func (d *DockerClient) ContainerStats(ctx context.Context, id string, stats chan<- container.ContainerStat) error {
|
||||
response, err := d.cli.ContainerStats(ctx, id, true)
|
||||
|
||||
if err != nil {
|
||||
@@ -213,7 +183,7 @@ func (d *httpClient) ContainerStats(ctx context.Context, id string, stats chan<-
|
||||
|
||||
defer response.Body.Close()
|
||||
decoder := json.NewDecoder(response.Body)
|
||||
var v *container.StatsResponse
|
||||
var v *docker.StatsResponse
|
||||
for {
|
||||
if err := decoder.Decode(&v); err != nil {
|
||||
return err
|
||||
@@ -243,7 +213,7 @@ func (d *httpClient) ContainerStats(ctx context.Context, id string, stats chan<-
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
return nil
|
||||
case stats <- ContainerStat{
|
||||
case stats <- container.ContainerStat{
|
||||
ID: id,
|
||||
CPUPercent: cpuPercent,
|
||||
MemoryPercent: memPercent,
|
||||
@@ -254,13 +224,13 @@ func (d *httpClient) ContainerStats(ctx context.Context, id string, stats chan<-
|
||||
}
|
||||
}
|
||||
|
||||
func (d *httpClient) ContainerLogs(ctx context.Context, id string, since time.Time, stdType StdType) (io.ReadCloser, error) {
|
||||
func (d *DockerClient) ContainerLogs(ctx context.Context, id string, since time.Time, stdType container.StdType) (io.ReadCloser, error) {
|
||||
log.Debug().Str("id", id).Time("since", since).Stringer("stdType", stdType).Str("host", d.host.Name).Msg("Streaming logs for container")
|
||||
|
||||
sinceQuery := since.Add(-50 * time.Millisecond).Format(time.RFC3339Nano)
|
||||
options := container.LogsOptions{
|
||||
ShowStdout: stdType&STDOUT != 0,
|
||||
ShowStderr: stdType&STDERR != 0,
|
||||
options := docker.LogsOptions{
|
||||
ShowStdout: stdType&container.STDOUT != 0,
|
||||
ShowStderr: stdType&container.STDERR != 0,
|
||||
Follow: true,
|
||||
Tail: strconv.Itoa(100),
|
||||
Timestamps: true,
|
||||
@@ -275,7 +245,7 @@ func (d *httpClient) ContainerLogs(ctx context.Context, id string, since time.Ti
|
||||
return reader, nil
|
||||
}
|
||||
|
||||
func (d *httpClient) ContainerEvents(ctx context.Context, messages chan<- ContainerEvent) error {
|
||||
func (d *DockerClient) ContainerEvents(ctx context.Context, messages chan<- container.ContainerEvent) error {
|
||||
dockerMessages, err := d.cli.Events(ctx, events.ListOptions{})
|
||||
|
||||
for {
|
||||
@@ -287,22 +257,23 @@ func (d *httpClient) ContainerEvents(ctx context.Context, messages chan<- Contai
|
||||
|
||||
case message := <-dockerMessages:
|
||||
if message.Type == events.ContainerEventType && len(message.Actor.ID) > 0 {
|
||||
messages <- ContainerEvent{
|
||||
messages <- container.ContainerEvent{
|
||||
ActorID: message.Actor.ID[:12],
|
||||
Name: string(message.Action),
|
||||
Host: d.host.ID,
|
||||
ActorAttributes: message.Actor.Attributes,
|
||||
Time: time.Now(),
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (d *httpClient) ContainerLogsBetweenDates(ctx context.Context, id string, from time.Time, to time.Time, stdType StdType) (io.ReadCloser, error) {
|
||||
func (d *DockerClient) ContainerLogsBetweenDates(ctx context.Context, id string, from time.Time, to time.Time, stdType container.StdType) (io.ReadCloser, error) {
|
||||
log.Debug().Str("id", id).Time("from", from).Time("to", to).Stringer("stdType", stdType).Str("host", d.host.Name).Msg("Fetching logs between dates for container")
|
||||
options := container.LogsOptions{
|
||||
ShowStdout: stdType&STDOUT != 0,
|
||||
ShowStderr: stdType&STDERR != 0,
|
||||
options := docker.LogsOptions{
|
||||
ShowStdout: stdType&container.STDOUT != 0,
|
||||
ShowStderr: stdType&container.STDERR != 0,
|
||||
Timestamps: true,
|
||||
Since: from.Format(time.RFC3339Nano),
|
||||
Until: to.Format(time.RFC3339Nano),
|
||||
@@ -316,24 +287,17 @@ func (d *httpClient) ContainerLogsBetweenDates(ctx context.Context, id string, f
|
||||
return reader, nil
|
||||
}
|
||||
|
||||
func (d *httpClient) Ping(ctx context.Context) (types.Ping, error) {
|
||||
return d.cli.Ping(ctx)
|
||||
func (d *DockerClient) Ping(ctx context.Context) error {
|
||||
_, err := d.cli.Ping(ctx)
|
||||
return err
|
||||
}
|
||||
|
||||
func (d *httpClient) Host() Host {
|
||||
func (d *DockerClient) Host() container.Host {
|
||||
log.Debug().Str("host", d.host.Name).Msg("Fetching host")
|
||||
return d.host
|
||||
}
|
||||
|
||||
func (d *httpClient) IsSwarmMode() bool {
|
||||
return d.info.Swarm.LocalNodeState != swarm.LocalNodeStateInactive
|
||||
}
|
||||
|
||||
func (d *httpClient) SystemInfo() system.Info {
|
||||
return d.info
|
||||
}
|
||||
|
||||
func newContainer(c types.Container, host string) Container {
|
||||
func newContainer(c types.Container, host string) container.Container {
|
||||
name := "no name"
|
||||
if c.Labels["dev.dozzle.name"] != "" {
|
||||
name = c.Labels["dev.dozzle.name"]
|
||||
@@ -345,7 +309,7 @@ func newContainer(c types.Container, host string) Container {
|
||||
if c.Labels["dev.dozzle.group"] != "" {
|
||||
group = c.Labels["dev.dozzle.group"]
|
||||
}
|
||||
return Container{
|
||||
return container.Container{
|
||||
ID: c.ID[:12],
|
||||
Name: name,
|
||||
Image: c.Image,
|
||||
@@ -354,12 +318,12 @@ func newContainer(c types.Container, host string) Container {
|
||||
State: c.State,
|
||||
Host: host,
|
||||
Labels: c.Labels,
|
||||
Stats: utils.NewRingBuffer[ContainerStat](300), // 300 seconds of stats
|
||||
Stats: utils.NewRingBuffer[container.ContainerStat](300), // 300 seconds of stats
|
||||
Group: group,
|
||||
}
|
||||
}
|
||||
|
||||
func newContainerFromJSON(c types.ContainerJSON, host string) Container {
|
||||
func newContainerFromJSON(c types.ContainerJSON, host string) container.Container {
|
||||
name := "no name"
|
||||
if c.Config.Labels["dev.dozzle.name"] != "" {
|
||||
name = c.Config.Labels["dev.dozzle.name"]
|
||||
@@ -372,17 +336,18 @@ func newContainerFromJSON(c types.ContainerJSON, host string) Container {
|
||||
group = c.Config.Labels["dev.dozzle.group"]
|
||||
}
|
||||
|
||||
container := Container{
|
||||
ID: c.ID[:12],
|
||||
Name: name,
|
||||
Image: c.Config.Image,
|
||||
Command: strings.Join(c.Config.Entrypoint, " ") + " " + strings.Join(c.Config.Cmd, " "),
|
||||
State: c.State.Status,
|
||||
Host: host,
|
||||
Labels: c.Config.Labels,
|
||||
Stats: utils.NewRingBuffer[ContainerStat](300), // 300 seconds of stats
|
||||
Group: group,
|
||||
Tty: c.Config.Tty,
|
||||
container := container.Container{
|
||||
ID: c.ID[:12],
|
||||
Name: name,
|
||||
Image: c.Config.Image,
|
||||
Command: strings.Join(c.Config.Entrypoint, " ") + " " + strings.Join(c.Config.Cmd, " "),
|
||||
State: c.State.Status,
|
||||
Host: host,
|
||||
Labels: c.Config.Labels,
|
||||
Stats: utils.NewRingBuffer[container.ContainerStat](300), // 300 seconds of stats
|
||||
Group: group,
|
||||
Tty: c.Config.Tty,
|
||||
FullyLoaded: true,
|
||||
}
|
||||
|
||||
if createdAt, err := time.Parse(time.RFC3339Nano, c.Created); err == nil {
|
||||
|
||||
@@ -10,8 +10,9 @@ import (
|
||||
|
||||
"testing"
|
||||
|
||||
"github.com/amir20/dozzle/internal/container"
|
||||
"github.com/docker/docker/api/types"
|
||||
"github.com/docker/docker/api/types/container"
|
||||
docker "github.com/docker/docker/api/types/container"
|
||||
"github.com/docker/docker/api/types/system"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/mock"
|
||||
@@ -23,7 +24,7 @@ type mockedProxy struct {
|
||||
DockerCLI
|
||||
}
|
||||
|
||||
func (m *mockedProxy) ContainerList(context.Context, container.ListOptions) ([]types.Container, error) {
|
||||
func (m *mockedProxy) ContainerList(context.Context, docker.ListOptions) ([]types.Container, error) {
|
||||
args := m.Called()
|
||||
containers, ok := args.Get(0).([]types.Container)
|
||||
if !ok && args.Get(0) != nil {
|
||||
@@ -33,7 +34,7 @@ func (m *mockedProxy) ContainerList(context.Context, container.ListOptions) ([]t
|
||||
|
||||
}
|
||||
|
||||
func (m *mockedProxy) ContainerLogs(ctx context.Context, id string, options container.LogsOptions) (io.ReadCloser, error) {
|
||||
func (m *mockedProxy) ContainerLogs(ctx context.Context, id string, options docker.LogsOptions) (io.ReadCloser, error) {
|
||||
args := m.Called(ctx, id, options)
|
||||
reader, ok := args.Get(0).(io.ReadCloser)
|
||||
if !ok && args.Get(0) != nil {
|
||||
@@ -47,11 +48,11 @@ func (m *mockedProxy) ContainerInspect(ctx context.Context, containerID string)
|
||||
return args.Get(0).(types.ContainerJSON), args.Error(1)
|
||||
}
|
||||
|
||||
func (m *mockedProxy) ContainerStats(ctx context.Context, containerID string, stream bool) (container.StatsResponseReader, error) {
|
||||
return container.StatsResponseReader{}, nil
|
||||
func (m *mockedProxy) ContainerStats(ctx context.Context, containerID string, stream bool) (docker.StatsResponseReader, error) {
|
||||
return docker.StatsResponseReader{}, nil
|
||||
}
|
||||
|
||||
func (m *mockedProxy) ContainerStart(ctx context.Context, containerID string, options container.StartOptions) error {
|
||||
func (m *mockedProxy) ContainerStart(ctx context.Context, containerID string, options docker.StartOptions) error {
|
||||
|
||||
args := m.Called(ctx, containerID, options)
|
||||
err := args.Get(0)
|
||||
@@ -63,7 +64,7 @@ func (m *mockedProxy) ContainerStart(ctx context.Context, containerID string, op
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *mockedProxy) ContainerStop(ctx context.Context, containerID string, options container.StopOptions) error {
|
||||
func (m *mockedProxy) ContainerStop(ctx context.Context, containerID string, options docker.StopOptions) error {
|
||||
args := m.Called(ctx, containerID, options)
|
||||
err := args.Get(0)
|
||||
|
||||
@@ -74,7 +75,7 @@ func (m *mockedProxy) ContainerStop(ctx context.Context, containerID string, opt
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *mockedProxy) ContainerRestart(ctx context.Context, containerID string, options container.StopOptions) error {
|
||||
func (m *mockedProxy) ContainerRestart(ctx context.Context, containerID string, options docker.StopOptions) error {
|
||||
|
||||
args := m.Called(ctx, containerID, options)
|
||||
err := args.Get(0)
|
||||
@@ -89,9 +90,9 @@ func (m *mockedProxy) ContainerRestart(ctx context.Context, containerID string,
|
||||
func Test_dockerClient_ListContainers_null(t *testing.T) {
|
||||
proxy := new(mockedProxy)
|
||||
proxy.On("ContainerList", mock.Anything, mock.Anything).Return(nil, nil)
|
||||
client := &httpClient{proxy, Host{ID: "localhost"}, system.Info{}}
|
||||
client := &DockerClient{proxy, container.Host{ID: "localhost"}, system.Info{}}
|
||||
|
||||
list, err := client.ListContainers(context.Background(), ContainerFilter{})
|
||||
list, err := client.ListContainers(context.Background(), container.ContainerLabels{})
|
||||
assert.Empty(t, list, "list should be empty")
|
||||
require.NoError(t, err, "error should not return an error.")
|
||||
|
||||
@@ -101,9 +102,9 @@ func Test_dockerClient_ListContainers_null(t *testing.T) {
|
||||
func Test_dockerClient_ListContainers_error(t *testing.T) {
|
||||
proxy := new(mockedProxy)
|
||||
proxy.On("ContainerList", mock.Anything, mock.Anything).Return(nil, errors.New("test"))
|
||||
client := &httpClient{proxy, Host{ID: "localhost"}, system.Info{}}
|
||||
client := &DockerClient{proxy, container.Host{ID: "localhost"}, system.Info{}}
|
||||
|
||||
list, err := client.ListContainers(context.Background(), ContainerFilter{})
|
||||
list, err := client.ListContainers(context.Background(), container.ContainerLabels{})
|
||||
assert.Nil(t, list, "list should be nil")
|
||||
require.Error(t, err, "test.")
|
||||
|
||||
@@ -124,9 +125,9 @@ func Test_dockerClient_ListContainers_happy(t *testing.T) {
|
||||
|
||||
proxy := new(mockedProxy)
|
||||
proxy.On("ContainerList", mock.Anything, mock.Anything).Return(containers, nil)
|
||||
client := &httpClient{proxy, Host{ID: "localhost"}, system.Info{}}
|
||||
client := &DockerClient{proxy, container.Host{ID: "localhost"}, system.Info{}}
|
||||
|
||||
list, err := client.ListContainers(context.Background(), ContainerFilter{})
|
||||
list, err := client.ListContainers(context.Background(), container.ContainerLabels{})
|
||||
require.NoError(t, err, "error should not return an error.")
|
||||
|
||||
Ids := []string{"1234567890_a", "abcdefghijkl"}
|
||||
@@ -149,7 +150,7 @@ func Test_dockerClient_ContainerLogs_happy(t *testing.T) {
|
||||
|
||||
reader := io.NopCloser(bytes.NewReader(b))
|
||||
since := time.Date(2021, time.January, 1, 0, 0, 0, 0, time.UTC)
|
||||
options := container.LogsOptions{
|
||||
options := docker.LogsOptions{
|
||||
ShowStdout: true,
|
||||
ShowStderr: true,
|
||||
Follow: true,
|
||||
@@ -158,8 +159,8 @@ func Test_dockerClient_ContainerLogs_happy(t *testing.T) {
|
||||
Since: "2020-12-31T23:59:59.95Z"}
|
||||
proxy.On("ContainerLogs", mock.Anything, id, options).Return(reader, nil)
|
||||
|
||||
client := &httpClient{proxy, Host{ID: "localhost"}, system.Info{}}
|
||||
logReader, _ := client.ContainerLogs(context.Background(), id, since, STDALL)
|
||||
client := &DockerClient{proxy, container.Host{ID: "localhost"}, system.Info{}}
|
||||
logReader, _ := client.ContainerLogs(context.Background(), id, since, container.STDALL)
|
||||
|
||||
actual, _ := io.ReadAll(logReader)
|
||||
assert.Equal(t, string(b), string(actual), "message doesn't match expected")
|
||||
@@ -172,9 +173,9 @@ func Test_dockerClient_ContainerLogs_error(t *testing.T) {
|
||||
|
||||
proxy.On("ContainerLogs", mock.Anything, id, mock.Anything).Return(nil, errors.New("test"))
|
||||
|
||||
client := &httpClient{proxy, Host{ID: "localhost"}, system.Info{}}
|
||||
client := &DockerClient{proxy, container.Host{ID: "localhost"}, system.Info{}}
|
||||
|
||||
reader, err := client.ContainerLogs(context.Background(), id, time.Time{}, STDALL)
|
||||
reader, err := client.ContainerLogs(context.Background(), id, time.Time{}, container.STDALL)
|
||||
|
||||
assert.Nil(t, reader, "reader should be nil")
|
||||
assert.Error(t, err, "error should have been returned")
|
||||
@@ -185,10 +186,10 @@ func Test_dockerClient_FindContainer_happy(t *testing.T) {
|
||||
proxy := new(mockedProxy)
|
||||
|
||||
state := &types.ContainerState{Status: "running", StartedAt: time.Now().Format(time.RFC3339Nano)}
|
||||
json := types.ContainerJSON{ContainerJSONBase: &types.ContainerJSONBase{ID: "abcdefghijklmnopqrst", State: state}, Config: &container.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)
|
||||
|
||||
client := &httpClient{proxy, Host{ID: "localhost"}, system.Info{}}
|
||||
client := &DockerClient{proxy, container.Host{ID: "localhost"}, system.Info{}}
|
||||
|
||||
container, err := client.FindContainer(context.Background(), "abcdefghijkl")
|
||||
require.NoError(t, err, "error should not be thrown")
|
||||
@@ -201,7 +202,7 @@ func Test_dockerClient_FindContainer_happy(t *testing.T) {
|
||||
func Test_dockerClient_FindContainer_error(t *testing.T) {
|
||||
proxy := new(mockedProxy)
|
||||
proxy.On("ContainerInspect", mock.Anything, "not_valid").Return(types.ContainerJSON{}, errors.New("not found"))
|
||||
client := &httpClient{proxy, Host{ID: "localhost"}, system.Info{}}
|
||||
client := &DockerClient{proxy, container.Host{ID: "localhost"}, system.Info{}}
|
||||
|
||||
_, err := client.FindContainer(context.Background(), "not_valid")
|
||||
require.Error(t, err, "error should be thrown")
|
||||
@@ -211,24 +212,24 @@ func Test_dockerClient_FindContainer_error(t *testing.T) {
|
||||
|
||||
func Test_dockerClient_ContainerActions_happy(t *testing.T) {
|
||||
proxy := new(mockedProxy)
|
||||
client := &httpClient{proxy, Host{ID: "localhost"}, system.Info{}}
|
||||
client := &DockerClient{proxy, container.Host{ID: "localhost"}, system.Info{}}
|
||||
|
||||
state := &types.ContainerState{Status: "running", StartedAt: time.Now().Format(time.RFC3339Nano)}
|
||||
json := types.ContainerJSON{ContainerJSONBase: &types.ContainerJSONBase{ID: "abcdefghijkl", State: state}, Config: &container.Config{Tty: false}}
|
||||
json := types.ContainerJSON{ContainerJSONBase: &types.ContainerJSONBase{ID: "abcdefghijkl", State: state}, Config: &docker.Config{Tty: false}}
|
||||
|
||||
proxy.On("ContainerInspect", mock.Anything, "abcdefghijkl").Return(json, nil)
|
||||
proxy.On("ContainerStart", mock.Anything, "abcdefghijkl", mock.Anything).Return(nil)
|
||||
proxy.On("ContainerStop", mock.Anything, "abcdefghijkl", mock.Anything).Return(nil)
|
||||
proxy.On("ContainerRestart", mock.Anything, "abcdefghijkl", mock.Anything).Return(nil)
|
||||
|
||||
container, err := client.FindContainer(context.Background(), "abcdefghijkl")
|
||||
c, err := client.FindContainer(context.Background(), "abcdefghijkl")
|
||||
require.NoError(t, err, "error should not be thrown")
|
||||
|
||||
assert.Equal(t, container.ID, "abcdefghijkl")
|
||||
assert.Equal(t, c.ID, "abcdefghijkl")
|
||||
|
||||
actions := []string{"start", "stop", "restart"}
|
||||
for _, action := range actions {
|
||||
err := client.ContainerActions(context.Background(), ContainerAction(action), container.ID)
|
||||
err := client.ContainerActions(context.Background(), container.ContainerAction(action), c.ID)
|
||||
require.NoError(t, err, "error should not be thrown")
|
||||
assert.Equal(t, err, nil)
|
||||
}
|
||||
@@ -239,18 +240,18 @@ func Test_dockerClient_ContainerActions_happy(t *testing.T) {
|
||||
func Test_dockerClient_ContainerActions_error(t *testing.T) {
|
||||
|
||||
proxy := new(mockedProxy)
|
||||
client := &httpClient{proxy, Host{ID: "localhost"}, system.Info{}}
|
||||
client := &DockerClient{proxy, container.Host{ID: "localhost"}, system.Info{}}
|
||||
proxy.On("ContainerInspect", mock.Anything, "random-id").Return(types.ContainerJSON{}, errors.New("not found"))
|
||||
proxy.On("ContainerStart", mock.Anything, mock.Anything, mock.Anything).Return(errors.New("test"))
|
||||
proxy.On("ContainerStop", mock.Anything, mock.Anything, mock.Anything).Return(errors.New("test"))
|
||||
proxy.On("ContainerRestart", mock.Anything, mock.Anything, mock.Anything).Return(errors.New("test"))
|
||||
|
||||
container, err := client.FindContainer(context.Background(), "random-id")
|
||||
c, err := client.FindContainer(context.Background(), "random-id")
|
||||
require.Error(t, err, "error should be thrown")
|
||||
|
||||
actions := []string{"start", "stop", "restart"}
|
||||
for _, action := range actions {
|
||||
err := client.ContainerActions(context.Background(), ContainerAction(action), container.ID)
|
||||
err := client.ContainerActions(context.Background(), container.ContainerAction(action), c.ID)
|
||||
require.Error(t, err, "error should be thrown")
|
||||
assert.Error(t, err, "error should have been returned")
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
@@ -8,35 +8,36 @@ import (
|
||||
"sync/atomic"
|
||||
"time"
|
||||
|
||||
"github.com/amir20/dozzle/internal/container"
|
||||
"github.com/puzpuzpuz/xsync/v3"
|
||||
"github.com/rs/zerolog/log"
|
||||
)
|
||||
|
||||
type StatsCollector struct {
|
||||
stream chan ContainerStat
|
||||
subscribers *xsync.MapOf[context.Context, chan<- ContainerStat]
|
||||
client Client
|
||||
type DockerStatsCollector struct {
|
||||
stream chan container.ContainerStat
|
||||
subscribers *xsync.MapOf[context.Context, chan<- container.ContainerStat]
|
||||
client container.Client
|
||||
cancelers *xsync.MapOf[string, context.CancelFunc]
|
||||
stopper context.CancelFunc
|
||||
timer *time.Timer
|
||||
mu sync.Mutex
|
||||
totalStarted atomic.Int32
|
||||
filter ContainerFilter
|
||||
labels container.ContainerLabels
|
||||
}
|
||||
|
||||
var timeToStop = 6 * time.Hour
|
||||
|
||||
func NewStatsCollector(client Client, filter ContainerFilter) *StatsCollector {
|
||||
return &StatsCollector{
|
||||
stream: make(chan ContainerStat),
|
||||
subscribers: xsync.NewMapOf[context.Context, chan<- ContainerStat](),
|
||||
func NewDockerStatsCollector(client container.Client, labels container.ContainerLabels) *DockerStatsCollector {
|
||||
return &DockerStatsCollector{
|
||||
stream: make(chan container.ContainerStat),
|
||||
subscribers: xsync.NewMapOf[context.Context, chan<- container.ContainerStat](),
|
||||
client: client,
|
||||
cancelers: xsync.NewMapOf[string, context.CancelFunc](),
|
||||
filter: filter,
|
||||
labels: labels,
|
||||
}
|
||||
}
|
||||
|
||||
func (c *StatsCollector) Subscribe(ctx context.Context, stats chan<- ContainerStat) {
|
||||
func (c *DockerStatsCollector) Subscribe(ctx context.Context, stats chan<- container.ContainerStat) {
|
||||
c.subscribers.Store(ctx, stats)
|
||||
go func() {
|
||||
<-ctx.Done()
|
||||
@@ -44,7 +45,7 @@ func (c *StatsCollector) Subscribe(ctx context.Context, stats chan<- ContainerSt
|
||||
}()
|
||||
}
|
||||
|
||||
func (c *StatsCollector) forceStop() {
|
||||
func (c *DockerStatsCollector) forceStop() {
|
||||
c.mu.Lock()
|
||||
defer c.mu.Unlock()
|
||||
if c.stopper != nil {
|
||||
@@ -54,7 +55,7 @@ func (c *StatsCollector) forceStop() {
|
||||
}
|
||||
}
|
||||
|
||||
func (c *StatsCollector) Stop() {
|
||||
func (c *DockerStatsCollector) Stop() {
|
||||
c.mu.Lock()
|
||||
defer c.mu.Unlock()
|
||||
if c.totalStarted.Add(-1) == 0 {
|
||||
@@ -64,7 +65,7 @@ func (c *StatsCollector) Stop() {
|
||||
}
|
||||
}
|
||||
|
||||
func (c *StatsCollector) reset() {
|
||||
func (c *DockerStatsCollector) reset() {
|
||||
c.mu.Lock()
|
||||
defer c.mu.Unlock()
|
||||
if c.timer != nil {
|
||||
@@ -73,7 +74,7 @@ func (c *StatsCollector) reset() {
|
||||
c.timer = nil
|
||||
}
|
||||
|
||||
func streamStats(parent context.Context, sc *StatsCollector, id string) {
|
||||
func streamStats(parent context.Context, sc *DockerStatsCollector, id string) {
|
||||
ctx, cancel := context.WithCancel(parent)
|
||||
sc.cancelers.Store(id, cancel)
|
||||
log.Debug().Str("container", id).Str("host", sc.client.Host().Name).Msg("starting to stream stats")
|
||||
@@ -86,7 +87,7 @@ func streamStats(parent context.Context, sc *StatsCollector, id string) {
|
||||
}
|
||||
|
||||
// Start starts the stats collector and blocks until it's stopped. It returns true if the collector was stopped, false if it was already running
|
||||
func (sc *StatsCollector) Start(parentCtx context.Context) bool {
|
||||
func (sc *DockerStatsCollector) Start(parentCtx context.Context) bool {
|
||||
sc.reset()
|
||||
sc.totalStarted.Add(1)
|
||||
|
||||
@@ -100,7 +101,7 @@ func (sc *StatsCollector) Start(parentCtx context.Context) bool {
|
||||
sc.mu.Unlock()
|
||||
|
||||
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 {
|
||||
if c.State == "running" {
|
||||
go streamStats(ctx, sc, c.ID)
|
||||
@@ -111,7 +112,7 @@ func (sc *StatsCollector) Start(parentCtx context.Context) bool {
|
||||
}
|
||||
cancel()
|
||||
|
||||
events := make(chan ContainerEvent)
|
||||
events := make(chan container.ContainerEvent)
|
||||
|
||||
go func() {
|
||||
log.Debug().Str("host", sc.client.Host().Name).Msg("starting to listen to docker events")
|
||||
@@ -142,7 +143,7 @@ func (sc *StatsCollector) Start(parentCtx context.Context) bool {
|
||||
log.Info().Str("host", sc.client.Host().Name).Msg("stopped container stats collector")
|
||||
return true
|
||||
case stat := <-sc.stream:
|
||||
sc.subscribers.Range(func(c context.Context, stats chan<- ContainerStat) bool {
|
||||
sc.subscribers.Range(func(c context.Context, stats chan<- container.ContainerStat) bool {
|
||||
select {
|
||||
case stats <- stat:
|
||||
case <-c.Done():
|
||||
|
||||
@@ -4,39 +4,70 @@ import (
|
||||
"context"
|
||||
"testing"
|
||||
|
||||
"github.com/amir20/dozzle/internal/container"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/mock"
|
||||
)
|
||||
|
||||
func startedCollector(ctx context.Context) *StatsCollector {
|
||||
type mockedClient struct {
|
||||
mock.Mock
|
||||
container.Client
|
||||
}
|
||||
|
||||
func (m *mockedClient) ListContainers(ctx context.Context, filter container.ContainerLabels) ([]container.Container, error) {
|
||||
args := m.Called(ctx, filter)
|
||||
return args.Get(0).([]container.Container), args.Error(1)
|
||||
}
|
||||
|
||||
func (m *mockedClient) FindContainer(ctx context.Context, id string) (container.Container, error) {
|
||||
args := m.Called(ctx, id)
|
||||
return args.Get(0).(container.Container), args.Error(1)
|
||||
}
|
||||
|
||||
func (m *mockedClient) ContainerEvents(ctx context.Context, events chan<- container.ContainerEvent) error {
|
||||
args := m.Called(ctx, events)
|
||||
return args.Error(0)
|
||||
}
|
||||
|
||||
func (m *mockedClient) ContainerStats(ctx context.Context, id string, stats chan<- container.ContainerStat) error {
|
||||
args := m.Called(ctx, id, stats)
|
||||
return args.Error(0)
|
||||
}
|
||||
|
||||
func (m *mockedClient) Host() container.Host {
|
||||
args := m.Called()
|
||||
return args.Get(0).(container.Host)
|
||||
}
|
||||
|
||||
func startedCollector(ctx context.Context) *DockerStatsCollector {
|
||||
client := new(mockedClient)
|
||||
client.On("ListContainers", mock.Anything, mock.Anything).Return([]Container{
|
||||
client.On("ListContainers", mock.Anything, mock.Anything).Return([]container.Container{
|
||||
{
|
||||
ID: "1234",
|
||||
Name: "test",
|
||||
State: "running",
|
||||
},
|
||||
}, nil)
|
||||
client.On("ContainerEvents", mock.Anything, mock.AnythingOfType("chan<- docker.ContainerEvent")).
|
||||
client.On("ContainerEvents", mock.Anything, mock.AnythingOfType("chan<- container.ContainerEvent")).
|
||||
Return(nil).
|
||||
Run(func(args mock.Arguments) {
|
||||
ctx := args.Get(0).(context.Context)
|
||||
<-ctx.Done()
|
||||
})
|
||||
client.On("ContainerStats", mock.Anything, mock.Anything, mock.AnythingOfType("chan<- docker.ContainerStat")).
|
||||
client.On("ContainerStats", mock.Anything, mock.Anything, mock.AnythingOfType("chan<- container.ContainerStat")).
|
||||
Return(nil).
|
||||
Run(func(args mock.Arguments) {
|
||||
stats := args.Get(2).(chan<- ContainerStat)
|
||||
stats <- ContainerStat{
|
||||
stats := args.Get(2).(chan<- container.ContainerStat)
|
||||
stats <- container.ContainerStat{
|
||||
ID: "1234",
|
||||
}
|
||||
})
|
||||
client.On("Host").Return(Host{
|
||||
client.On("Host").Return(container.Host{
|
||||
ID: "localhost",
|
||||
})
|
||||
|
||||
collector := NewStatsCollector(client, ContainerFilter{})
|
||||
stats := make(chan ContainerStat)
|
||||
collector := NewDockerStatsCollector(client, container.ContainerLabels{})
|
||||
stats := make(chan container.ContainerStat)
|
||||
|
||||
collector.Subscribe(ctx, stats)
|
||||
|
||||
|
||||
@@ -5,7 +5,7 @@ import (
|
||||
"crypto/tls"
|
||||
|
||||
"github.com/amir20/dozzle/internal/agent"
|
||||
"github.com/amir20/dozzle/internal/docker"
|
||||
"github.com/amir20/dozzle/internal/container"
|
||||
"github.com/rs/zerolog/log"
|
||||
)
|
||||
|
||||
@@ -14,7 +14,7 @@ func RPCRequest(ctx context.Context, addr string, certs tls.Certificate) error {
|
||||
if err != nil {
|
||||
log.Fatal().Err(err).Msg("Failed to create agent client")
|
||||
}
|
||||
containers, err := client.ListContainers(ctx, docker.ContainerFilter{})
|
||||
containers, err := client.ListContainers(ctx, container.ContainerLabels{})
|
||||
log.Trace().Int("containers", len(containers)).Msg("Healtcheck RPC request completed")
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -0,0 +1,236 @@
|
||||
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
|
||||
host container.Host
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
nodes, err := 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")
|
||||
}
|
||||
node := nodes.Items[0]
|
||||
|
||||
return &K8sClient{
|
||||
Clientset: clientset,
|
||||
namespace: namespace,
|
||||
config: config,
|
||||
host: container.Host{
|
||||
ID: node.Status.NodeInfo.MachineID,
|
||||
Name: node.Name,
|
||||
},
|
||||
}, nil
|
||||
}
|
||||
|
||||
func podToContainers(pod *corev1.Pod) []container.Container {
|
||||
started := time.Time{}
|
||||
if pod.Status.StartTime != nil {
|
||||
started = pod.Status.StartTime.Time
|
||||
}
|
||||
var containers []container.Container
|
||||
for _, c := range pod.Spec.Containers {
|
||||
containers = append(containers, container.Container{
|
||||
ID: pod.Namespace + ":" + 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),
|
||||
FullyLoaded: true,
|
||||
})
|
||||
}
|
||||
return containers
|
||||
}
|
||||
|
||||
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 {
|
||||
containers = append(containers, podToContainers(&pod)...)
|
||||
}
|
||||
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")
|
||||
namespace, podName, containerName := parsePodContainerID(id)
|
||||
|
||||
pod, err := k.Clientset.CoreV1().Pods(namespace).Get(ctx, podName, metav1.GetOptions{})
|
||||
if err != nil {
|
||||
return container.Container{}, err
|
||||
}
|
||||
|
||||
for _, c := range podToContainers(pod) {
|
||||
if c.ID == id {
|
||||
return c, 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) {
|
||||
namespace, 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(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) {
|
||||
namespace, podName, containerName := parsePodContainerID(id)
|
||||
opts := &corev1.PodLogOptions{
|
||||
Container: containerName,
|
||||
Follow: false,
|
||||
Timestamps: true,
|
||||
SinceTime: &metav1.Time{Time: start},
|
||||
}
|
||||
|
||||
return k.Clientset.CoreV1().Pods(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"
|
||||
}
|
||||
|
||||
for _, c := range podToContainers(pod) {
|
||||
ch <- container.ContainerEvent{
|
||||
Name: name,
|
||||
ActorID: c.ID,
|
||||
Host: pod.Spec.NodeName,
|
||||
Time: time.Now(),
|
||||
Container: &c,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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("default").List(ctx, metav1.ListOptions{Limit: 1})
|
||||
return err
|
||||
}
|
||||
|
||||
func (k *K8sClient) Host() container.Host {
|
||||
return k.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, string) {
|
||||
parts := strings.Split(id, ":")
|
||||
return parts[0], parts[1], parts[2]
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -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.Namespace + ":" + 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
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -2,12 +2,12 @@ package cli
|
||||
|
||||
import (
|
||||
"github.com/amir20/dozzle/internal/analytics"
|
||||
"github.com/amir20/dozzle/internal/docker"
|
||||
"github.com/amir20/dozzle/internal/container"
|
||||
"github.com/amir20/dozzle/types"
|
||||
"github.com/rs/zerolog/log"
|
||||
)
|
||||
|
||||
func StartEvent(args Args, mode string, client docker.Client, subCommand string) {
|
||||
func StartEvent(args Args, mode string, client container.Client, subCommand string) {
|
||||
if args.NoAnalytics {
|
||||
return
|
||||
}
|
||||
@@ -29,7 +29,7 @@ func StartEvent(args Args, mode string, client docker.Client, subCommand string)
|
||||
host := client.Host()
|
||||
event.ServerID = host.ID
|
||||
event.ServerVersion = host.DockerVersion
|
||||
event.IsSwarmMode = client.SystemInfo().Swarm.NodeID != ""
|
||||
event.IsSwarmMode = host.Swarm
|
||||
} else {
|
||||
event.ServerID = "n/a"
|
||||
}
|
||||
|
||||
@@ -27,8 +27,9 @@ type Args struct {
|
||||
RemoteAgent []string `arg:"env:DOZZLE_REMOTE_AGENT,--remote-agent,separate" help:"list of agents to connect remotely"`
|
||||
NoAnalytics bool `arg:"--no-analytics,env:DOZZLE_NO_ANALYTICS" help:"disables anonymous analytics"`
|
||||
Mode string `arg:"env:DOZZLE_MODE" default:"server" help:"sets the mode to run in (server, swarm)"`
|
||||
TimeoutString string `arg:"--timeout,env:DOZZLE_TIMEOUT" default:"3s" help:"sets the timeout for docker client"`
|
||||
TimeoutString string `arg:"--timeout,env:DOZZLE_TIMEOUT" default:"10s" help:"sets the timeout for docker client"`
|
||||
Timeout time.Duration `arg:"-"`
|
||||
Namespace string `arg:"env:DOZZLE_NAMESPACE" default:"" help:"sets the namespace to use in k8s"`
|
||||
Healthcheck *HealthcheckCmd `arg:"subcommand:healthcheck" help:"checks if the server is running"`
|
||||
Generate *GenerateCmd `arg:"subcommand:generate" help:"generates a configuration file for simple auth"`
|
||||
Agent *AgentCmd `arg:"subcommand:agent" help:"starts the agent"`
|
||||
|
||||
@@ -4,19 +4,21 @@ import (
|
||||
"context"
|
||||
"embed"
|
||||
|
||||
"github.com/amir20/dozzle/internal/container"
|
||||
"github.com/amir20/dozzle/internal/docker"
|
||||
container_support "github.com/amir20/dozzle/internal/support/container"
|
||||
docker_support "github.com/amir20/dozzle/internal/support/docker"
|
||||
"github.com/rs/zerolog/log"
|
||||
)
|
||||
|
||||
func CreateMultiHostService(embeddedCerts embed.FS, args Args) (docker.Client, *docker_support.MultiHostService) {
|
||||
var clients []docker_support.ClientService
|
||||
func CreateMultiHostService(embeddedCerts embed.FS, args Args) *docker_support.MultiHostService {
|
||||
var clients []container_support.ClientService
|
||||
if len(args.RemoteHost) > 0 {
|
||||
log.Info().Msg(`Consider using Dozzle's remote agent to manage remote hosts. See https://dozzle.dev/guide/agent for more information`)
|
||||
}
|
||||
|
||||
for _, remoteHost := range args.RemoteHost {
|
||||
host, err := docker.ParseConnection(remoteHost)
|
||||
host, err := container.ParseConnection(remoteHost)
|
||||
if err != nil {
|
||||
log.Fatal().Err(err).Interface("host", remoteHost).Msg("Could not parse remote host")
|
||||
}
|
||||
@@ -46,6 +48,7 @@ func CreateMultiHostService(embeddedCerts embed.FS, args Args) (docker.Client, *
|
||||
log.Debug().Msg("Adding local Docker Engine")
|
||||
clients = append(clients, docker_support.NewDockerClientService(localClient, args.Filter))
|
||||
}
|
||||
go StartEvent(args, "server", localClient, "")
|
||||
}
|
||||
|
||||
certs, err := ReadCertificates(embeddedCerts)
|
||||
@@ -54,5 +57,5 @@ func CreateMultiHostService(embeddedCerts embed.FS, args Args) (docker.Client, *
|
||||
}
|
||||
|
||||
clientManager := docker_support.NewRetriableClientManager(args.RemoteAgent, args.Timeout, certs, clients...)
|
||||
return localClient, docker_support.NewMultiHostService(clientManager, args.Timeout)
|
||||
return docker_support.NewMultiHostService(clientManager, args.Timeout)
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
package docker_support
|
||||
package container_support
|
||||
|
||||
import (
|
||||
"context"
|
||||
@@ -7,13 +7,13 @@ import (
|
||||
"time"
|
||||
|
||||
"github.com/amir20/dozzle/internal/agent"
|
||||
"github.com/amir20/dozzle/internal/docker"
|
||||
"github.com/amir20/dozzle/internal/container"
|
||||
"github.com/rs/zerolog/log"
|
||||
)
|
||||
|
||||
type agentService struct {
|
||||
client *agent.Client
|
||||
host docker.Host
|
||||
host container.Host
|
||||
}
|
||||
|
||||
func NewAgentService(client *agent.Client) ClientService {
|
||||
@@ -22,28 +22,28 @@ func NewAgentService(client *agent.Client) ClientService {
|
||||
}
|
||||
}
|
||||
|
||||
func (a *agentService) FindContainer(ctx context.Context, id string, filter docker.ContainerFilter) (docker.Container, error) {
|
||||
func (a *agentService) FindContainer(ctx context.Context, id string, labels container.ContainerLabels) (container.Container, error) {
|
||||
return a.client.FindContainer(ctx, id)
|
||||
}
|
||||
|
||||
func (a *agentService) RawLogs(ctx context.Context, container docker.Container, from time.Time, to time.Time, stdTypes docker.StdType) (io.ReadCloser, error) {
|
||||
func (a *agentService) RawLogs(ctx context.Context, container container.Container, from time.Time, to time.Time, stdTypes container.StdType) (io.ReadCloser, error) {
|
||||
return a.client.StreamRawBytes(ctx, container.ID, from, to, stdTypes)
|
||||
}
|
||||
|
||||
func (a *agentService) LogsBetweenDates(ctx context.Context, container docker.Container, from time.Time, to time.Time, stdTypes docker.StdType) (<-chan *docker.LogEvent, error) {
|
||||
func (a *agentService) LogsBetweenDates(ctx context.Context, container container.Container, from time.Time, to time.Time, stdTypes container.StdType) (<-chan *container.LogEvent, error) {
|
||||
return a.client.LogsBetweenDates(ctx, container.ID, from, to, stdTypes)
|
||||
}
|
||||
|
||||
func (a *agentService) StreamLogs(ctx context.Context, container docker.Container, from time.Time, stdTypes docker.StdType, events chan<- *docker.LogEvent) error {
|
||||
func (a *agentService) StreamLogs(ctx context.Context, container container.Container, from time.Time, stdTypes container.StdType, events chan<- *container.LogEvent) error {
|
||||
return a.client.StreamContainerLogs(ctx, container.ID, from, stdTypes, events)
|
||||
}
|
||||
|
||||
func (a *agentService) ListContainers(ctx context.Context, filter docker.ContainerFilter) ([]docker.Container, error) {
|
||||
log.Debug().Interface("filter", filter).Msg("Listing containers from agent")
|
||||
return a.client.ListContainers(ctx, filter)
|
||||
func (a *agentService) ListContainers(ctx context.Context, labels container.ContainerLabels) ([]container.Container, error) {
|
||||
log.Debug().Interface("labels", labels).Msg("Listing containers from agent")
|
||||
return a.client.ListContainers(ctx, labels)
|
||||
}
|
||||
|
||||
func (a *agentService) Host(ctx context.Context) (docker.Host, error) {
|
||||
func (a *agentService) Host(ctx context.Context) (container.Host, error) {
|
||||
host, err := a.client.Host(ctx)
|
||||
if err != nil {
|
||||
host := a.host
|
||||
@@ -55,18 +55,18 @@ func (a *agentService) Host(ctx context.Context) (docker.Host, error) {
|
||||
return a.host, err
|
||||
}
|
||||
|
||||
func (a *agentService) SubscribeStats(ctx context.Context, stats chan<- docker.ContainerStat) {
|
||||
func (a *agentService) SubscribeStats(ctx context.Context, stats chan<- container.ContainerStat) {
|
||||
go a.client.StreamStats(ctx, stats)
|
||||
}
|
||||
|
||||
func (a *agentService) SubscribeEvents(ctx context.Context, events chan<- docker.ContainerEvent) {
|
||||
func (a *agentService) SubscribeEvents(ctx context.Context, events chan<- container.ContainerEvent) {
|
||||
go a.client.StreamEvents(ctx, events)
|
||||
}
|
||||
|
||||
func (d *agentService) SubscribeContainersStarted(ctx context.Context, containers chan<- docker.Container) {
|
||||
func (d *agentService) SubscribeContainersStarted(ctx context.Context, containers chan<- container.Container) {
|
||||
go d.client.StreamNewContainers(ctx, containers)
|
||||
}
|
||||
|
||||
func (a *agentService) ContainerAction(ctx context.Context, container docker.Container, action docker.ContainerAction) error {
|
||||
func (a *agentService) ContainerAction(ctx context.Context, container container.Container, action container.ContainerAction) error {
|
||||
return a.client.ContainerAction(ctx, container.ID, action)
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
package container_support
|
||||
|
||||
import (
|
||||
"context"
|
||||
"io"
|
||||
"time"
|
||||
|
||||
"github.com/amir20/dozzle/internal/container"
|
||||
)
|
||||
|
||||
type ContainerService struct {
|
||||
clientService ClientService
|
||||
Container container.Container
|
||||
}
|
||||
|
||||
func NewContainerService(clientService ClientService, container container.Container) *ContainerService {
|
||||
return &ContainerService{
|
||||
clientService: clientService,
|
||||
Container: container,
|
||||
}
|
||||
}
|
||||
|
||||
func (c *ContainerService) RawLogs(ctx context.Context, from time.Time, to time.Time, stdTypes container.StdType) (io.ReadCloser, error) {
|
||||
return c.clientService.RawLogs(ctx, c.Container, from, to, stdTypes)
|
||||
}
|
||||
|
||||
func (c *ContainerService) LogsBetweenDates(ctx context.Context, from time.Time, to time.Time, stdTypes container.StdType) (<-chan *container.LogEvent, error) {
|
||||
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 {
|
||||
return c.clientService.StreamLogs(ctx, c.Container, from, stdTypes, events)
|
||||
}
|
||||
|
||||
func (c *ContainerService) Action(ctx context.Context, action container.ContainerAction) error {
|
||||
return c.clientService.ContainerAction(ctx, c.Container, action)
|
||||
}
|
||||
@@ -1,99 +0,0 @@
|
||||
package docker_support
|
||||
|
||||
import (
|
||||
"context"
|
||||
"io"
|
||||
"time"
|
||||
|
||||
"github.com/amir20/dozzle/internal/docker"
|
||||
)
|
||||
|
||||
type ClientService interface {
|
||||
FindContainer(ctx context.Context, id string, filter docker.ContainerFilter) (docker.Container, error)
|
||||
ListContainers(ctx context.Context, filter docker.ContainerFilter) ([]docker.Container, error)
|
||||
Host(ctx context.Context) (docker.Host, error)
|
||||
ContainerAction(ctx context.Context, container docker.Container, action docker.ContainerAction) error
|
||||
LogsBetweenDates(ctx context.Context, container docker.Container, from time.Time, to time.Time, stdTypes docker.StdType) (<-chan *docker.LogEvent, error)
|
||||
RawLogs(ctx context.Context, container docker.Container, from time.Time, to time.Time, stdTypes docker.StdType) (io.ReadCloser, error)
|
||||
|
||||
// Subscriptions
|
||||
SubscribeStats(ctx context.Context, stats chan<- docker.ContainerStat)
|
||||
SubscribeEvents(ctx context.Context, events chan<- docker.ContainerEvent)
|
||||
SubscribeContainersStarted(ctx context.Context, containers chan<- docker.Container)
|
||||
|
||||
// Blocking streaming functions that should be used in a goroutine
|
||||
StreamLogs(ctx context.Context, container docker.Container, from time.Time, stdTypes docker.StdType, events chan<- *docker.LogEvent) error
|
||||
}
|
||||
|
||||
type dockerClientService struct {
|
||||
client docker.Client
|
||||
store *docker.ContainerStore
|
||||
}
|
||||
|
||||
func NewDockerClientService(client docker.Client, filter docker.ContainerFilter) ClientService {
|
||||
return &dockerClientService{
|
||||
client: client,
|
||||
store: docker.NewContainerStore(context.Background(), client, filter),
|
||||
}
|
||||
}
|
||||
|
||||
func (d *dockerClientService) RawLogs(ctx context.Context, container docker.Container, from time.Time, to time.Time, stdTypes docker.StdType) (io.ReadCloser, error) {
|
||||
return d.client.ContainerLogsBetweenDates(ctx, container.ID, from, to, stdTypes)
|
||||
}
|
||||
|
||||
func (d *dockerClientService) LogsBetweenDates(ctx context.Context, container docker.Container, from time.Time, to time.Time, stdTypes docker.StdType) (<-chan *docker.LogEvent, error) {
|
||||
reader, err := d.client.ContainerLogsBetweenDates(ctx, container.ID, from, to, stdTypes)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
g := docker.NewEventGenerator(ctx, reader, container)
|
||||
return g.Events, nil
|
||||
}
|
||||
|
||||
func (d *dockerClientService) StreamLogs(ctx context.Context, container docker.Container, from time.Time, stdTypes docker.StdType, events chan<- *docker.LogEvent) error {
|
||||
reader, err := d.client.ContainerLogs(ctx, container.ID, from, stdTypes)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
g := docker.NewEventGenerator(ctx, reader, container)
|
||||
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 docker.ContainerFilter) (docker.Container, error) {
|
||||
return d.store.FindContainer(id, filter)
|
||||
}
|
||||
|
||||
func (d *dockerClientService) ContainerAction(ctx context.Context, container docker.Container, action docker.ContainerAction) error {
|
||||
return d.client.ContainerActions(ctx, action, container.ID)
|
||||
}
|
||||
|
||||
func (d *dockerClientService) ListContainers(ctx context.Context, filter docker.ContainerFilter) ([]docker.Container, error) {
|
||||
return d.store.ListContainers(filter)
|
||||
}
|
||||
|
||||
func (d *dockerClientService) Host(ctx context.Context) (docker.Host, error) {
|
||||
return d.client.Host(), nil
|
||||
}
|
||||
|
||||
func (d *dockerClientService) SubscribeStats(ctx context.Context, stats chan<- docker.ContainerStat) {
|
||||
d.store.SubscribeStats(ctx, stats)
|
||||
}
|
||||
|
||||
func (d *dockerClientService) SubscribeEvents(ctx context.Context, events chan<- docker.ContainerEvent) {
|
||||
d.store.SubscribeEvents(ctx, events)
|
||||
}
|
||||
|
||||
func (d *dockerClientService) SubscribeContainersStarted(ctx context.Context, containers chan<- docker.Container) {
|
||||
d.store.SubscribeNewContainers(ctx, containers)
|
||||
}
|
||||
@@ -1,30 +0,0 @@
|
||||
package docker_support
|
||||
|
||||
import (
|
||||
"context"
|
||||
"io"
|
||||
"time"
|
||||
|
||||
"github.com/amir20/dozzle/internal/docker"
|
||||
)
|
||||
|
||||
type containerService struct {
|
||||
clientService ClientService
|
||||
Container docker.Container
|
||||
}
|
||||
|
||||
func (c *containerService) RawLogs(ctx context.Context, from time.Time, to time.Time, stdTypes docker.StdType) (io.ReadCloser, error) {
|
||||
return c.clientService.RawLogs(ctx, c.Container, from, to, stdTypes)
|
||||
}
|
||||
|
||||
func (c *containerService) LogsBetweenDates(ctx context.Context, from time.Time, to time.Time, stdTypes docker.StdType) (<-chan *docker.LogEvent, error) {
|
||||
return c.clientService.LogsBetweenDates(ctx, c.Container, from, to, stdTypes)
|
||||
}
|
||||
|
||||
func (c *containerService) StreamLogs(ctx context.Context, from time.Time, stdTypes docker.StdType, events chan<- *docker.LogEvent) error {
|
||||
return c.clientService.StreamLogs(ctx, c.Container, from, stdTypes, events)
|
||||
}
|
||||
|
||||
func (c *containerService) Action(ctx context.Context, action docker.ContainerAction) error {
|
||||
return c.clientService.ContainerAction(ctx, c.Container, action)
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
@@ -5,14 +5,13 @@ import (
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"github.com/amir20/dozzle/internal/docker"
|
||||
"github.com/amir20/dozzle/internal/container"
|
||||
container_support "github.com/amir20/dozzle/internal/support/container"
|
||||
"github.com/rs/zerolog/log"
|
||||
)
|
||||
|
||||
type ContainerFilter = func(*docker.Container) bool
|
||||
|
||||
type HostUnavailableError struct {
|
||||
Host docker.Host
|
||||
Host container.Host
|
||||
Err error
|
||||
}
|
||||
|
||||
@@ -21,12 +20,12 @@ func (h *HostUnavailableError) Error() string {
|
||||
}
|
||||
|
||||
type ClientManager interface {
|
||||
Find(id string) (ClientService, bool)
|
||||
List() []ClientService
|
||||
RetryAndList() ([]ClientService, []error)
|
||||
Subscribe(ctx context.Context, channel chan<- docker.Host)
|
||||
Hosts(ctx context.Context) []docker.Host
|
||||
LocalClients() []docker.Client
|
||||
Find(id string) (container_support.ClientService, bool)
|
||||
List() []container_support.ClientService
|
||||
RetryAndList() ([]container_support.ClientService, []error)
|
||||
Subscribe(ctx context.Context, channel chan<- container.Host)
|
||||
Hosts(ctx context.Context) []container.Host
|
||||
LocalClients() []container.Client
|
||||
}
|
||||
|
||||
type MultiHostService struct {
|
||||
@@ -43,25 +42,22 @@ func NewMultiHostService(manager ClientManager, timeout time.Duration) *MultiHos
|
||||
return m
|
||||
}
|
||||
|
||||
func (m *MultiHostService) FindContainer(host string, id string, filter docker.ContainerFilter) (*containerService, error) {
|
||||
func (m *MultiHostService) FindContainer(host string, id string, labels container.ContainerLabels) (*container_support.ContainerService, error) {
|
||||
client, ok := m.manager.Find(host)
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("host %s not found", host)
|
||||
}
|
||||
ctx, cancel := context.WithTimeout(context.Background(), m.timeout)
|
||||
defer cancel()
|
||||
container, err := client.FindContainer(ctx, id, filter)
|
||||
container, err := client.FindContainer(ctx, id, labels)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &containerService{
|
||||
clientService: client,
|
||||
Container: container,
|
||||
}, nil
|
||||
return container_support.NewContainerService(client, container), nil
|
||||
}
|
||||
|
||||
func (m *MultiHostService) ListContainersForHost(host string, filter docker.ContainerFilter) ([]docker.Container, error) {
|
||||
func (m *MultiHostService) ListContainersForHost(host string, labels container.ContainerLabels) ([]container.Container, error) {
|
||||
client, ok := m.manager.Find(host)
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("host %s not found", host)
|
||||
@@ -69,17 +65,17 @@ func (m *MultiHostService) ListContainersForHost(host string, filter docker.Cont
|
||||
ctx, cancel := context.WithTimeout(context.Background(), m.timeout)
|
||||
defer cancel()
|
||||
|
||||
return client.ListContainers(ctx, filter)
|
||||
return client.ListContainers(ctx, labels)
|
||||
}
|
||||
|
||||
func (m *MultiHostService) ListAllContainers(filter docker.ContainerFilter) ([]docker.Container, []error) {
|
||||
containers := make([]docker.Container, 0)
|
||||
func (m *MultiHostService) ListAllContainers(labels container.ContainerLabels) ([]container.Container, []error) {
|
||||
containers := make([]container.Container, 0)
|
||||
clients, errors := m.manager.RetryAndList()
|
||||
|
||||
for _, client := range clients {
|
||||
ctx, cancel := context.WithTimeout(context.Background(), m.timeout)
|
||||
defer cancel()
|
||||
list, err := client.ListContainers(ctx, filter)
|
||||
list, err := client.ListContainers(ctx, labels)
|
||||
if err != nil {
|
||||
host, _ := client.Host(ctx)
|
||||
log.Debug().Err(err).Str("host", host.Name).Msg("error listing containers")
|
||||
@@ -94,9 +90,9 @@ func (m *MultiHostService) ListAllContainers(filter docker.ContainerFilter) ([]d
|
||||
return containers, errors
|
||||
}
|
||||
|
||||
func (m *MultiHostService) ListAllContainersFiltered(userFilter docker.ContainerFilter, filter ContainerFilter) ([]docker.Container, []error) {
|
||||
containers, err := m.ListAllContainers(userFilter)
|
||||
filtered := make([]docker.Container, 0, len(containers))
|
||||
func (m *MultiHostService) ListAllContainersFiltered(userLabels container.ContainerLabels, filter container_support.ContainerFilter) ([]container.Container, []error) {
|
||||
containers, err := m.ListAllContainers(userLabels)
|
||||
filtered := make([]container.Container, 0, len(containers))
|
||||
for _, container := range containers {
|
||||
if filter(&container) {
|
||||
filtered = append(filtered, container)
|
||||
@@ -105,15 +101,15 @@ func (m *MultiHostService) ListAllContainersFiltered(userFilter docker.Container
|
||||
return filtered, err
|
||||
}
|
||||
|
||||
func (m *MultiHostService) SubscribeEventsAndStats(ctx context.Context, events chan<- docker.ContainerEvent, stats chan<- docker.ContainerStat) {
|
||||
func (m *MultiHostService) SubscribeEventsAndStats(ctx context.Context, events chan<- container.ContainerEvent, stats chan<- container.ContainerStat) {
|
||||
for _, client := range m.manager.List() {
|
||||
client.SubscribeEvents(ctx, events)
|
||||
client.SubscribeStats(ctx, stats)
|
||||
}
|
||||
}
|
||||
|
||||
func (m *MultiHostService) SubscribeContainersStarted(ctx context.Context, containers chan<- docker.Container, filter ContainerFilter) {
|
||||
newContainers := make(chan docker.Container)
|
||||
func (m *MultiHostService) SubscribeContainersStarted(ctx context.Context, containers chan<- container.Container, filter container_support.ContainerFilter) {
|
||||
newContainers := make(chan container.Container)
|
||||
for _, client := range m.manager.List() {
|
||||
client.SubscribeContainersStarted(ctx, newContainers)
|
||||
}
|
||||
@@ -135,29 +131,29 @@ func (m *MultiHostService) SubscribeContainersStarted(ctx context.Context, conta
|
||||
}()
|
||||
}
|
||||
|
||||
func (m *MultiHostService) TotalClients() int {
|
||||
return len(m.manager.List())
|
||||
}
|
||||
|
||||
func (m *MultiHostService) Hosts() []docker.Host {
|
||||
func (m *MultiHostService) Hosts() []container.Host {
|
||||
ctx, cancel := context.WithTimeout(context.Background(), m.timeout)
|
||||
defer cancel()
|
||||
return m.manager.Hosts(ctx)
|
||||
}
|
||||
|
||||
func (m *MultiHostService) LocalHost() (docker.Host, error) {
|
||||
func (m *MultiHostService) LocalHost() (container.Host, error) {
|
||||
for _, host := range m.Hosts() {
|
||||
if host.Type == "local" {
|
||||
return host, nil
|
||||
}
|
||||
}
|
||||
return docker.Host{}, fmt.Errorf("local host not found")
|
||||
return container.Host{}, fmt.Errorf("local host not found")
|
||||
}
|
||||
|
||||
func (m *MultiHostService) SubscribeAvailableHosts(ctx context.Context, hosts chan<- docker.Host) {
|
||||
func (m *MultiHostService) SubscribeAvailableHosts(ctx context.Context, hosts chan<- container.Host) {
|
||||
m.manager.Subscribe(ctx, hosts)
|
||||
}
|
||||
|
||||
func (m *MultiHostService) LocalClients() []docker.Client {
|
||||
func (m *MultiHostService) LocalClients() []container.Client {
|
||||
return m.manager.LocalClients()
|
||||
}
|
||||
|
||||
func (m *MultiHostService) TotalClients() int {
|
||||
return len(m.manager.List())
|
||||
}
|
||||
|
||||
@@ -8,7 +8,9 @@ import (
|
||||
"time"
|
||||
|
||||
"github.com/amir20/dozzle/internal/agent"
|
||||
"github.com/amir20/dozzle/internal/docker"
|
||||
"github.com/amir20/dozzle/internal/container"
|
||||
container_support "github.com/amir20/dozzle/internal/support/container"
|
||||
|
||||
"github.com/puzpuzpuz/xsync/v3"
|
||||
"github.com/samber/lo"
|
||||
lop "github.com/samber/lo/parallel"
|
||||
@@ -17,16 +19,16 @@ import (
|
||||
)
|
||||
|
||||
type RetriableClientManager struct {
|
||||
clients map[string]ClientService
|
||||
clients map[string]container_support.ClientService
|
||||
failedAgents []string
|
||||
certs tls.Certificate
|
||||
mu sync.RWMutex
|
||||
subscribers *xsync.MapOf[context.Context, chan<- docker.Host]
|
||||
subscribers *xsync.MapOf[context.Context, chan<- container.Host]
|
||||
timeout time.Duration
|
||||
}
|
||||
|
||||
func NewRetriableClientManager(agents []string, timeout time.Duration, certs tls.Certificate, clients ...ClientService) *RetriableClientManager {
|
||||
clientMap := make(map[string]ClientService)
|
||||
func NewRetriableClientManager(agents []string, timeout time.Duration, certs tls.Certificate, clients ...container_support.ClientService) *RetriableClientManager {
|
||||
clientMap := make(map[string]container_support.ClientService)
|
||||
for _, client := range clients {
|
||||
ctx, cancel := context.WithTimeout(context.Background(), timeout)
|
||||
defer cancel()
|
||||
@@ -64,7 +66,7 @@ func NewRetriableClientManager(agents []string, timeout time.Duration, certs tls
|
||||
if _, ok := clientMap[host.ID]; ok {
|
||||
log.Warn().Str("host", host.Name).Str("id", host.ID).Msg("duplicate host with same ID found")
|
||||
} else {
|
||||
clientMap[host.ID] = NewAgentService(agent)
|
||||
clientMap[host.ID] = container_support.NewAgentService(agent)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -72,12 +74,12 @@ func NewRetriableClientManager(agents []string, timeout time.Duration, certs tls
|
||||
clients: clientMap,
|
||||
failedAgents: failed,
|
||||
certs: certs,
|
||||
subscribers: xsync.NewMapOf[context.Context, chan<- docker.Host](),
|
||||
subscribers: xsync.NewMapOf[context.Context, chan<- container.Host](),
|
||||
timeout: timeout,
|
||||
}
|
||||
}
|
||||
|
||||
func (m *RetriableClientManager) Subscribe(ctx context.Context, channel chan<- docker.Host) {
|
||||
func (m *RetriableClientManager) Subscribe(ctx context.Context, channel chan<- container.Host) {
|
||||
m.subscribers.Store(ctx, channel)
|
||||
|
||||
go func() {
|
||||
@@ -86,7 +88,7 @@ func (m *RetriableClientManager) Subscribe(ctx context.Context, channel chan<- d
|
||||
}()
|
||||
}
|
||||
|
||||
func (m *RetriableClientManager) RetryAndList() ([]ClientService, []error) {
|
||||
func (m *RetriableClientManager) RetryAndList() ([]container_support.ClientService, []error) {
|
||||
m.mu.Lock()
|
||||
errors := make([]error, 0)
|
||||
if len(m.failedAgents) > 0 {
|
||||
@@ -110,12 +112,12 @@ func (m *RetriableClientManager) RetryAndList() ([]ClientService, []error) {
|
||||
continue
|
||||
}
|
||||
|
||||
m.clients[host.ID] = NewAgentService(agent)
|
||||
m.subscribers.Range(func(ctx context.Context, channel chan<- docker.Host) bool {
|
||||
m.clients[host.ID] = container_support.NewAgentService(agent)
|
||||
m.subscribers.Range(func(ctx context.Context, channel chan<- container.Host) bool {
|
||||
host.Available = true
|
||||
|
||||
// We don't want to block the subscribers in event.go
|
||||
go func(host docker.Host) {
|
||||
go func(host container.Host) {
|
||||
select {
|
||||
case channel <- host:
|
||||
case <-ctx.Done():
|
||||
@@ -133,14 +135,14 @@ func (m *RetriableClientManager) RetryAndList() ([]ClientService, []error) {
|
||||
return m.List(), errors
|
||||
}
|
||||
|
||||
func (m *RetriableClientManager) List() []ClientService {
|
||||
func (m *RetriableClientManager) List() []container_support.ClientService {
|
||||
m.mu.RLock()
|
||||
defer m.mu.RUnlock()
|
||||
|
||||
return lo.Values(m.clients)
|
||||
}
|
||||
|
||||
func (m *RetriableClientManager) Find(id string) (ClientService, bool) {
|
||||
func (m *RetriableClientManager) Find(id string) (container_support.ClientService, bool) {
|
||||
m.mu.RLock()
|
||||
defer m.mu.RUnlock()
|
||||
|
||||
@@ -152,10 +154,10 @@ func (m *RetriableClientManager) String() string {
|
||||
return fmt.Sprintf("RetriableClientManager{clients: %d, failedAgents: %d}", len(m.clients), len(m.failedAgents))
|
||||
}
|
||||
|
||||
func (m *RetriableClientManager) Hosts(ctx context.Context) []docker.Host {
|
||||
func (m *RetriableClientManager) Hosts(ctx context.Context) []container.Host {
|
||||
clients := m.List()
|
||||
|
||||
hosts := lop.Map(clients, func(client ClientService, _ int) docker.Host {
|
||||
hosts := lop.Map(clients, func(client container_support.ClientService, _ int) container.Host {
|
||||
host, err := client.Host(ctx)
|
||||
if err != nil {
|
||||
log.Warn().Err(err).Str("host", host.Name).Msg("error fetching host info for client")
|
||||
@@ -168,7 +170,7 @@ func (m *RetriableClientManager) Hosts(ctx context.Context) []docker.Host {
|
||||
})
|
||||
|
||||
for _, endpoint := range m.failedAgents {
|
||||
hosts = append(hosts, docker.Host{
|
||||
hosts = append(hosts, container.Host{
|
||||
ID: endpoint,
|
||||
Name: endpoint,
|
||||
Endpoint: endpoint,
|
||||
@@ -180,13 +182,13 @@ func (m *RetriableClientManager) Hosts(ctx context.Context) []docker.Host {
|
||||
return hosts
|
||||
}
|
||||
|
||||
func (m *RetriableClientManager) LocalClients() []docker.Client {
|
||||
func (m *RetriableClientManager) LocalClients() []container.Client {
|
||||
services := m.List()
|
||||
|
||||
clients := make([]docker.Client, 0)
|
||||
clients := make([]container.Client, 0)
|
||||
|
||||
for _, service := range services {
|
||||
if clientService, ok := service.(*dockerClientService); ok {
|
||||
if clientService, ok := service.(*DockerClientService); ok {
|
||||
clients = append(clients, clientService.client)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -10,7 +10,10 @@ import (
|
||||
"time"
|
||||
|
||||
"github.com/amir20/dozzle/internal/agent"
|
||||
"github.com/amir20/dozzle/internal/container"
|
||||
"github.com/amir20/dozzle/internal/docker"
|
||||
container_support "github.com/amir20/dozzle/internal/support/container"
|
||||
|
||||
"github.com/puzpuzpuz/xsync/v3"
|
||||
"github.com/samber/lo"
|
||||
lop "github.com/samber/lo/parallel"
|
||||
@@ -19,11 +22,11 @@ import (
|
||||
)
|
||||
|
||||
type SwarmClientManager struct {
|
||||
clients map[string]ClientService
|
||||
clients map[string]container_support.ClientService
|
||||
certs tls.Certificate
|
||||
mu sync.RWMutex
|
||||
subscribers *xsync.MapOf[context.Context, chan<- docker.Host]
|
||||
localClient docker.Client
|
||||
subscribers *xsync.MapOf[context.Context, chan<- container.Host]
|
||||
localClient container.Client
|
||||
localIPs []string
|
||||
name string
|
||||
timeout time.Duration
|
||||
@@ -47,9 +50,9 @@ func localIPs() []string {
|
||||
return ips
|
||||
}
|
||||
|
||||
func NewSwarmClientManager(localClient docker.Client, certs tls.Certificate, timeout time.Duration, agentManager *RetriableClientManager, filter docker.ContainerFilter) *SwarmClientManager {
|
||||
clientMap := make(map[string]ClientService)
|
||||
localService := NewDockerClientService(localClient, filter)
|
||||
func NewSwarmClientManager(localClient *docker.DockerClient, certs tls.Certificate, timeout time.Duration, agentManager *RetriableClientManager, labels container.ContainerLabels) *SwarmClientManager {
|
||||
clientMap := make(map[string]container_support.ClientService)
|
||||
localService := NewDockerClientService(localClient, labels)
|
||||
clientMap[localClient.Host().ID] = localService
|
||||
|
||||
id, ok := os.LookupEnv("HOSTNAME")
|
||||
@@ -59,12 +62,12 @@ func NewSwarmClientManager(localClient docker.Client, certs tls.Certificate, tim
|
||||
|
||||
ctx, cancel := context.WithTimeout(context.Background(), timeout)
|
||||
defer cancel()
|
||||
container, err := localClient.FindContainer(ctx, id)
|
||||
c, err := localClient.FindContainer(ctx, id)
|
||||
if err != nil {
|
||||
log.Fatal().Err(err).Msg("error finding own container when looking for swarm service name")
|
||||
}
|
||||
|
||||
serviceName := container.Labels["com.docker.swarm.service.name"]
|
||||
serviceName := c.Labels["com.docker.swarm.service.name"]
|
||||
|
||||
log.Debug().Str("service", serviceName).Msg("found swarm service name")
|
||||
|
||||
@@ -72,7 +75,7 @@ func NewSwarmClientManager(localClient docker.Client, certs tls.Certificate, tim
|
||||
localClient: localClient,
|
||||
clients: clientMap,
|
||||
certs: certs,
|
||||
subscribers: xsync.NewMapOf[context.Context, chan<- docker.Host](),
|
||||
subscribers: xsync.NewMapOf[context.Context, chan<- container.Host](),
|
||||
localIPs: localIPs(),
|
||||
name: serviceName,
|
||||
timeout: timeout,
|
||||
@@ -80,7 +83,7 @@ func NewSwarmClientManager(localClient docker.Client, certs tls.Certificate, tim
|
||||
}
|
||||
}
|
||||
|
||||
func (m *SwarmClientManager) Subscribe(ctx context.Context, channel chan<- docker.Host) {
|
||||
func (m *SwarmClientManager) Subscribe(ctx context.Context, channel chan<- container.Host) {
|
||||
m.subscribers.Store(ctx, channel)
|
||||
m.agentManager.Subscribe(ctx, channel)
|
||||
|
||||
@@ -90,7 +93,7 @@ func (m *SwarmClientManager) Subscribe(ctx context.Context, channel chan<- docke
|
||||
}()
|
||||
}
|
||||
|
||||
func (m *SwarmClientManager) RetryAndList() ([]ClientService, []error) {
|
||||
func (m *SwarmClientManager) RetryAndList() ([]container_support.ClientService, []error) {
|
||||
m.mu.Lock()
|
||||
|
||||
ips, err := net.LookupIP(fmt.Sprintf("tasks.%s", m.name))
|
||||
@@ -104,7 +107,7 @@ func (m *SwarmClientManager) RetryAndList() ([]ClientService, []error) {
|
||||
}
|
||||
|
||||
clients := lo.Values(m.clients)
|
||||
endpoints := lo.KeyBy(clients, func(client ClientService) string {
|
||||
endpoints := lo.KeyBy(clients, func(client container_support.ClientService) string {
|
||||
ctx, cancel := context.WithTimeout(context.Background(), m.timeout)
|
||||
defer cancel()
|
||||
host, _ := client.Host(ctx)
|
||||
@@ -155,11 +158,11 @@ func (m *SwarmClientManager) RetryAndList() ([]ClientService, []error) {
|
||||
continue
|
||||
}
|
||||
|
||||
client := NewAgentService(agent)
|
||||
client := container_support.NewAgentService(agent)
|
||||
m.clients[host.ID] = client
|
||||
log.Info().Stringer("ip", ip).Str("id", host.ID).Str("name", host.Name).Msg("added new swarm agent")
|
||||
|
||||
m.subscribers.Range(func(ctx context.Context, channel chan<- docker.Host) bool {
|
||||
m.subscribers.Range(func(ctx context.Context, channel chan<- container.Host) bool {
|
||||
host.Available = true
|
||||
host.Type = "swarm"
|
||||
|
||||
@@ -182,7 +185,7 @@ func (m *SwarmClientManager) RetryAndList() ([]ClientService, []error) {
|
||||
return m.List(), errors
|
||||
}
|
||||
|
||||
func (m *SwarmClientManager) List() []ClientService {
|
||||
func (m *SwarmClientManager) List() []container_support.ClientService {
|
||||
m.mu.RLock()
|
||||
defer m.mu.RUnlock()
|
||||
|
||||
@@ -192,7 +195,7 @@ func (m *SwarmClientManager) List() []ClientService {
|
||||
return append(agents, clients...)
|
||||
}
|
||||
|
||||
func (m *SwarmClientManager) Find(id string) (ClientService, bool) {
|
||||
func (m *SwarmClientManager) Find(id string) (container_support.ClientService, bool) {
|
||||
m.mu.RLock()
|
||||
defer m.mu.RUnlock()
|
||||
|
||||
@@ -205,12 +208,12 @@ func (m *SwarmClientManager) Find(id string) (ClientService, bool) {
|
||||
return client, ok
|
||||
}
|
||||
|
||||
func (m *SwarmClientManager) Hosts(ctx context.Context) []docker.Host {
|
||||
func (m *SwarmClientManager) Hosts(ctx context.Context) []container.Host {
|
||||
m.mu.RLock()
|
||||
clients := lo.Values(m.clients)
|
||||
m.mu.RUnlock()
|
||||
|
||||
swarmNodes := lop.Map(clients, func(client ClientService, _ int) docker.Host {
|
||||
swarmNodes := lop.Map(clients, func(client container_support.ClientService, _ int) container.Host {
|
||||
host, err := client.Host(ctx)
|
||||
if err != nil {
|
||||
log.Warn().Err(err).Str("id", host.ID).Msg("error getting host from client")
|
||||
@@ -230,6 +233,6 @@ func (m *SwarmClientManager) String() string {
|
||||
return fmt.Sprintf("SwarmClientManager{clients: %d}", len(m.clients))
|
||||
}
|
||||
|
||||
func (m *SwarmClientManager) LocalClients() []docker.Client {
|
||||
return []docker.Client{m.localClient}
|
||||
func (m *SwarmClientManager) LocalClients() []container.Client {
|
||||
return []container.Client{m.localClient}
|
||||
}
|
||||
|
||||
@@ -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 m.client.client.Host(), nil
|
||||
}
|
||||
|
||||
func (m *K8sClusterService) SubscribeAvailableHosts(ctx context.Context, hosts chan<- container.Host) {
|
||||
}
|
||||
|
||||
func (m *K8sClusterService) LocalClients() []container.Client {
|
||||
return []container.Client{m.client.client}
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
@@ -5,7 +5,7 @@ import (
|
||||
"regexp"
|
||||
"strings"
|
||||
|
||||
"github.com/amir20/dozzle/internal/docker"
|
||||
"github.com/amir20/dozzle/internal/container"
|
||||
"github.com/rs/zerolog/log"
|
||||
orderedmap "github.com/wk8/go-ordered-map/v2"
|
||||
)
|
||||
@@ -26,7 +26,7 @@ func ParseRegex(search string) (*regexp.Regexp, error) {
|
||||
return re, nil
|
||||
}
|
||||
|
||||
func Search(re *regexp.Regexp, logEvent *docker.LogEvent) bool {
|
||||
func Search(re *regexp.Regexp, logEvent *container.LogEvent) bool {
|
||||
switch value := logEvent.Message.(type) {
|
||||
case string:
|
||||
if re.MatchString(value) {
|
||||
|
||||
@@ -6,7 +6,7 @@ import (
|
||||
"time"
|
||||
|
||||
"github.com/amir20/dozzle/internal/auth"
|
||||
"github.com/amir20/dozzle/internal/docker"
|
||||
"github.com/amir20/dozzle/internal/container"
|
||||
"github.com/go-chi/chi/v5"
|
||||
"github.com/rs/zerolog/log"
|
||||
)
|
||||
@@ -15,22 +15,22 @@ func (h *handler) containerActions(w http.ResponseWriter, r *http.Request) {
|
||||
action := chi.URLParam(r, "action")
|
||||
id := chi.URLParam(r, "id")
|
||||
|
||||
usersFilter := h.config.Filter
|
||||
userLabels := h.config.Labels
|
||||
if h.config.Authorization.Provider != NONE {
|
||||
user := auth.UserFromContext(r.Context())
|
||||
if user.ContainerFilter.Exists() {
|
||||
usersFilter = user.ContainerFilter
|
||||
if user.ContainerLabels.Exists() {
|
||||
userLabels = user.ContainerLabels
|
||||
}
|
||||
}
|
||||
|
||||
containerService, err := h.multiHostService.FindContainer(hostKey(r), id, usersFilter)
|
||||
containerService, err := h.hostService.FindContainer(hostKey(r), id, userLabels)
|
||||
if err != nil {
|
||||
log.Error().Err(err).Msg("error while trying to find container")
|
||||
http.Error(w, err.Error(), http.StatusNotFound)
|
||||
return
|
||||
}
|
||||
|
||||
parsedAction, err := docker.ParseContainerAction(action)
|
||||
parsedAction, err := container.ParseContainerAction(action)
|
||||
if err != nil {
|
||||
log.Error().Err(err).Msg("error while trying to parse action")
|
||||
http.Error(w, err.Error(), http.StatusBadRequest)
|
||||
|
||||