Work in progress to show live stats (#671)
Co-authored-by: dependabot-preview[bot] <27856297+dependabot-preview[bot]@users.noreply.github.com>
@@ -31,20 +31,16 @@ Content-Type: text/plain; charset=utf-8
|
|||||||
|
|
||||||
dev
|
dev
|
||||||
|
|
||||||
/* snapshot: Test_handler_listContainers_happy */
|
|
||||||
HTTP/1.1 200 OK
|
|
||||||
Connection: close
|
|
||||||
Content-Type: text/plain; charset=utf-8
|
|
||||||
|
|
||||||
[{"id":"1234567890","names":null,"name":"test","image":"image","imageId":"image_id","command":"command","created":0,"state":"state","status":"status"}]
|
|
||||||
|
|
||||||
/* snapshot: Test_handler_streamEvents_error */
|
/* snapshot: Test_handler_streamEvents_error */
|
||||||
HTTP/1.1 200 OK
|
HTTP/1.1 200 OK
|
||||||
Connection: close
|
Connection: close
|
||||||
Cache-Control: no-cache
|
Cache-Control: no-cache
|
||||||
Connection: keep-alive
|
Connection: keep-alive
|
||||||
Content-Type: text/event-stream
|
Content-Type: text/event-stream
|
||||||
X-Accel-Buffering: no
|
X-Accel-Buffering: no
|
||||||
|
|
||||||
|
event: containers-changed
|
||||||
|
data: []
|
||||||
|
|
||||||
/* snapshot: Test_handler_streamEvents_error_request */
|
/* snapshot: Test_handler_streamEvents_error_request */
|
||||||
HTTP/1.1 200 OK
|
HTTP/1.1 200 OK
|
||||||
@@ -52,7 +48,10 @@ Connection: close
|
|||||||
Cache-Control: no-cache
|
Cache-Control: no-cache
|
||||||
Connection: keep-alive
|
Connection: keep-alive
|
||||||
Content-Type: text/event-stream
|
Content-Type: text/event-stream
|
||||||
X-Accel-Buffering: no
|
X-Accel-Buffering: no
|
||||||
|
|
||||||
|
event: containers-changed
|
||||||
|
data: []
|
||||||
|
|
||||||
/* snapshot: Test_handler_streamEvents_happy */
|
/* snapshot: Test_handler_streamEvents_happy */
|
||||||
HTTP/1.1 200 OK
|
HTTP/1.1 200 OK
|
||||||
@@ -63,7 +62,7 @@ Content-Type: text/event-stream
|
|||||||
X-Accel-Buffering: no
|
X-Accel-Buffering: no
|
||||||
|
|
||||||
event: containers-changed
|
event: containers-changed
|
||||||
data: start
|
data: []
|
||||||
|
|
||||||
/* snapshot: Test_handler_streamLogs_error_finding_container */
|
/* snapshot: Test_handler_streamLogs_error_finding_container */
|
||||||
HTTP/1.1 404 Not Found
|
HTTP/1.1 404 Not Found
|
||||||
|
|||||||
@@ -26,22 +26,22 @@ describe("<App />", () => {
|
|||||||
{ id: "xyz", name: "Test 2" },
|
{ id: "xyz", name: "Test 2" },
|
||||||
];
|
];
|
||||||
},
|
},
|
||||||
};
|
activeContainers() {
|
||||||
|
return [];
|
||||||
const actions = {
|
},
|
||||||
FETCH_CONTAINERS: () => Promise.resolve(),
|
|
||||||
};
|
};
|
||||||
|
|
||||||
store = new Vuex.Store({
|
store = new Vuex.Store({
|
||||||
state,
|
state,
|
||||||
getters,
|
getters,
|
||||||
actions,
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
test("has right title", async () => {
|
test("has right title", async () => {
|
||||||
const wrapper = shallowMount(App, { stubs, store, localVue });
|
const wrapper = shallowMount(App, { stubs, store, localVue });
|
||||||
await wrapper.vm.$nextTick();
|
await wrapper.vm.$nextTick();
|
||||||
|
wrapper.vm.$options.watch.visibleContainers.call(wrapper.vm);
|
||||||
|
|
||||||
expect(wrapper.vm.title).toContain("2 containers");
|
expect(wrapper.vm.title).toContain("2 containers");
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -15,7 +15,6 @@
|
|||||||
<pane v-for="other in activeContainers" :key="other.id" v-if="!isMobile">
|
<pane v-for="other in activeContainers" :key="other.id" v-if="!isMobile">
|
||||||
<log-container
|
<log-container
|
||||||
:id="other.id"
|
:id="other.id"
|
||||||
:title="other.name"
|
|
||||||
show-title
|
show-title
|
||||||
scrollable
|
scrollable
|
||||||
closable
|
closable
|
||||||
@@ -72,10 +71,6 @@ export default {
|
|||||||
titleTemplate: "%s - Dozzle",
|
titleTemplate: "%s - Dozzle",
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
async created() {
|
|
||||||
await this.fetchContainerList();
|
|
||||||
this.title = `${this.visibleContainers.length} containers`;
|
|
||||||
},
|
|
||||||
mounted() {
|
mounted() {
|
||||||
if (this.hasSmallerScrollbars) {
|
if (this.hasSmallerScrollbars) {
|
||||||
document.documentElement.classList.add("has-custom-scrollbars");
|
document.documentElement.classList.add("has-custom-scrollbars");
|
||||||
@@ -100,10 +95,13 @@ export default {
|
|||||||
document.documentElement.removeAttribute("data-theme");
|
document.documentElement.removeAttribute("data-theme");
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
visibleContainers() {
|
||||||
|
this.title = `${this.visibleContainers.length} containers`;
|
||||||
|
},
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
...mapState(["activeContainers", "isMobile", "settings"]),
|
...mapState(["isMobile", "settings"]),
|
||||||
...mapGetters(["visibleContainers"]),
|
...mapGetters(["visibleContainers", "activeContainers"]),
|
||||||
hasSmallerScrollbars() {
|
hasSmallerScrollbars() {
|
||||||
return this.settings.smallerScrollbars;
|
return this.settings.smallerScrollbars;
|
||||||
},
|
},
|
||||||
@@ -113,7 +111,6 @@ export default {
|
|||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
...mapActions({
|
...mapActions({
|
||||||
fetchContainerList: "FETCH_CONTAINERS",
|
|
||||||
removeActiveContainer: "REMOVE_ACTIVE_CONTAINER",
|
removeActiveContainer: "REMOVE_ACTIVE_CONTAINER",
|
||||||
updateSetting: "UPDATE_SETTING",
|
updateSetting: "UPDATE_SETTING",
|
||||||
}),
|
}),
|
||||||
|
|||||||
35
assets/components/ContainerStat.vue
Normal file
@@ -0,0 +1,35 @@
|
|||||||
|
<template>
|
||||||
|
<div class="has-text-weight-light is-size-7 is-uppercase columns is-marginless is-mobile">
|
||||||
|
<div class="column is-narrow">
|
||||||
|
{{ state }}
|
||||||
|
</div>
|
||||||
|
<div class="column is-narrow" v-if="stat.memoryUsage !== null">mem {{ formatBytes(stat.memoryUsage) }}</div>
|
||||||
|
<div class="column is-narrow" v-if="stat.cpu !== null">load {{ stat.cpu }}%</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
export default {
|
||||||
|
props: {
|
||||||
|
stat: Object,
|
||||||
|
state: String,
|
||||||
|
},
|
||||||
|
name: "ContainerStat",
|
||||||
|
methods: {
|
||||||
|
formatBytes(bytes, decimals = 2) {
|
||||||
|
if (bytes === 0) return "0 Bytes";
|
||||||
|
const k = 1024;
|
||||||
|
const dm = decimals < 0 ? 0 : decimals;
|
||||||
|
const sizes = ["Bytes", "KB", "MB", "GB", "TB", "PB", "EB", "ZB", "YB"];
|
||||||
|
const i = Math.floor(Math.log(bytes) / Math.log(k));
|
||||||
|
return parseFloat((bytes / Math.pow(k, i)).toFixed(dm)) + " " + sizes[i];
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
.column {
|
||||||
|
padding-top: 0;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@@ -22,8 +22,6 @@ export default {
|
|||||||
|
|
||||||
<style lang="scss" scoped>
|
<style lang="scss" scoped>
|
||||||
.name {
|
.name {
|
||||||
border-bottom: 1px solid rgba(255, 255, 255, 0.05);
|
|
||||||
background: rgba(0, 0, 0, 0.1);
|
|
||||||
font-weight: bold;
|
font-weight: bold;
|
||||||
font-family: monospace;
|
font-family: monospace;
|
||||||
|
|
||||||
|
|||||||
@@ -1,7 +1,8 @@
|
|||||||
<template>
|
<template>
|
||||||
<scrollable-view :scrollable="scrollable">
|
<scrollable-view :scrollable="scrollable" v-if="container">
|
||||||
<template v-slot:header v-if="showTitle">
|
<template v-slot:header v-if="showTitle">
|
||||||
<container-title :value="title" :closable="closable" @close="$emit('close')"></container-title>
|
<container-title :value="container.name" :closable="closable" @close="$emit('close')"></container-title>
|
||||||
|
<container-stat :stat="container.stat" :state="container.state"></container-stat>
|
||||||
</template>
|
</template>
|
||||||
<template v-slot="{ setLoading }">
|
<template v-slot="{ setLoading }">
|
||||||
<log-viewer-with-source :id="id" @loading-more="setLoading($event)"></log-viewer-with-source>
|
<log-viewer-with-source :id="id" @loading-more="setLoading($event)"></log-viewer-with-source>
|
||||||
@@ -10,18 +11,18 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
|
import { mapGetters } from "vuex";
|
||||||
|
|
||||||
import LogViewerWithSource from "./LogViewerWithSource";
|
import LogViewerWithSource from "./LogViewerWithSource";
|
||||||
import ScrollableView from "./ScrollableView";
|
import ScrollableView from "./ScrollableView";
|
||||||
import ContainerTitle from "./ContainerTitle";
|
import ContainerTitle from "./ContainerTitle";
|
||||||
|
import ContainerStat from "./ContainerStat";
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
props: {
|
props: {
|
||||||
id: {
|
id: {
|
||||||
type: String,
|
type: String,
|
||||||
},
|
},
|
||||||
title: {
|
|
||||||
type: String,
|
|
||||||
},
|
|
||||||
showTitle: {
|
showTitle: {
|
||||||
type: Boolean,
|
type: Boolean,
|
||||||
default: false,
|
default: false,
|
||||||
@@ -40,6 +41,13 @@ export default {
|
|||||||
LogViewerWithSource,
|
LogViewerWithSource,
|
||||||
ScrollableView,
|
ScrollableView,
|
||||||
ContainerTitle,
|
ContainerTitle,
|
||||||
|
ContainerStat,
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
...mapGetters(["allContainersById"]),
|
||||||
|
container() {
|
||||||
|
return this.allContainersById[this.id];
|
||||||
|
},
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
@@ -42,7 +42,7 @@ export default {
|
|||||||
};
|
};
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
...mapGetters(["activeContainersById", "visibleContainers"]),
|
...mapGetters(["visibleContainers"]),
|
||||||
},
|
},
|
||||||
watch: {
|
watch: {
|
||||||
$route(to, from) {
|
$route(to, from) {
|
||||||
|
|||||||
@@ -5,24 +5,20 @@
|
|||||||
</svg>
|
</svg>
|
||||||
<div class="is-overlay columns is-vcentered is-centered has-text-weight-light">
|
<div class="is-overlay columns is-vcentered is-centered has-text-weight-light">
|
||||||
<template v-if="indeterminate">
|
<template v-if="indeterminate">
|
||||||
<div class="column is-narrow is-paddingless is-size-2">
|
<div class="column is-narrow is-paddingless is-size-2">∞</div>
|
||||||
∞
|
|
||||||
</div>
|
|
||||||
</template>
|
</template>
|
||||||
<template v-else>
|
<template v-else>
|
||||||
<span class="column is-narrow is-paddingless is-size-2">
|
<span class="column is-narrow is-paddingless is-size-2">
|
||||||
{{ Math.ceil(scrollProgress * 100) }}
|
{{ Math.ceil(scrollProgress * 100) }}
|
||||||
</span>
|
</span>
|
||||||
<span class="column is-narrow is-paddingless">
|
<span class="column is-narrow is-paddingless"> % </span>
|
||||||
%
|
|
||||||
</span>
|
|
||||||
</template>
|
</template>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import { mapState } from "vuex";
|
import { mapGetters } from "vuex";
|
||||||
import throttle from "lodash.throttle";
|
import throttle from "lodash.throttle";
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
@@ -61,7 +57,7 @@ export default {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
...mapState(["activeContainers"]),
|
...mapGetters(["activeContainers"]),
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
attachEvents() {
|
attachEvents() {
|
||||||
|
|||||||
@@ -85,6 +85,13 @@ section {
|
|||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
|
|
||||||
|
header {
|
||||||
|
position: sticky;
|
||||||
|
top: 0;
|
||||||
|
background: var(--body-background-color);
|
||||||
|
border-bottom: 1px solid rgba(255, 255, 255, 0.05);
|
||||||
|
}
|
||||||
|
|
||||||
&.is-full-height-scrollable {
|
&.is-full-height-scrollable {
|
||||||
height: 100vh;
|
height: 100vh;
|
||||||
min-height: 0;
|
min-height: 0;
|
||||||
|
|||||||
@@ -59,8 +59,13 @@ export default {
|
|||||||
return {};
|
return {};
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
...mapState(["activeContainers"]),
|
...mapGetters(["visibleContainers", "activeContainers"]),
|
||||||
...mapGetters(["activeContainersById", "visibleContainers"]),
|
activeContainersById() {
|
||||||
|
return this.activeContainers.reduce((map, obj) => {
|
||||||
|
map[obj.id] = obj;
|
||||||
|
return map;
|
||||||
|
}, {});
|
||||||
|
},
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
...mapActions({
|
...mapActions({
|
||||||
|
|||||||
@@ -1,11 +1,5 @@
|
|||||||
<template>
|
<template>
|
||||||
<log-container
|
<log-container :id="id" show-title :scrollable="activeContainers.length > 0"> </log-container>
|
||||||
:id="id"
|
|
||||||
:title="title"
|
|
||||||
:show-title="activeContainers.length > 0"
|
|
||||||
:scrollable="activeContainers.length > 0"
|
|
||||||
>
|
|
||||||
</log-container>
|
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
@@ -36,8 +30,7 @@ export default {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
...mapState(["activeContainers"]),
|
...mapGetters(["allContainersById", "activeContainers"]),
|
||||||
...mapGetters(["allContainersById"]),
|
|
||||||
},
|
},
|
||||||
watch: {
|
watch: {
|
||||||
id() {
|
id() {
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ storage.set(DOZZLE_SETTINGS_KEY, { ...DEFAULT_SETTINGS, ...storage.get(DOZZLE_SE
|
|||||||
|
|
||||||
const state = {
|
const state = {
|
||||||
containers: [],
|
containers: [],
|
||||||
activeContainers: [],
|
activeContainerIds: [],
|
||||||
searchFilter: null,
|
searchFilter: null,
|
||||||
isMobile: mql.matches,
|
isMobile: mql.matches,
|
||||||
settings: storage.get(DOZZLE_SETTINGS_KEY),
|
settings: storage.get(DOZZLE_SETTINGS_KEY),
|
||||||
@@ -20,13 +20,26 @@ const state = {
|
|||||||
|
|
||||||
const mutations = {
|
const mutations = {
|
||||||
SET_CONTAINERS(state, containers) {
|
SET_CONTAINERS(state, containers) {
|
||||||
|
const containersById = state.containers.reduce((map, obj) => {
|
||||||
|
map[obj.id] = obj;
|
||||||
|
return map;
|
||||||
|
}, {});
|
||||||
|
|
||||||
|
containers.forEach(
|
||||||
|
(container) =>
|
||||||
|
(container.stat =
|
||||||
|
containersById[container.id] && containersById[container.id].stat
|
||||||
|
? containersById[container.id].stat
|
||||||
|
: { memoryUsage: 0, cpu: 0 })
|
||||||
|
);
|
||||||
|
|
||||||
state.containers = containers;
|
state.containers = containers;
|
||||||
},
|
},
|
||||||
ADD_ACTIVE_CONTAINERS(state, container) {
|
ADD_ACTIVE_CONTAINERS(state, { id }) {
|
||||||
state.activeContainers.push(container);
|
state.activeContainerIds.push(id);
|
||||||
},
|
},
|
||||||
REMOVE_ACTIVE_CONTAINER(state, container) {
|
REMOVE_ACTIVE_CONTAINER(state, { id }) {
|
||||||
state.activeContainers.splice(state.activeContainers.indexOf(container), 1);
|
state.activeContainerIds.splice(state.activeContainerIds.indexOf(id), 1);
|
||||||
},
|
},
|
||||||
SET_SEARCH(state, filter) {
|
SET_SEARCH(state, filter) {
|
||||||
state.searchFilter = filter;
|
state.searchFilter = filter;
|
||||||
@@ -38,6 +51,9 @@ const mutations = {
|
|||||||
state.settings = { ...state.settings, ...newValues };
|
state.settings = { ...state.settings, ...newValues };
|
||||||
storage.set(DOZZLE_SETTINGS_KEY, state.settings);
|
storage.set(DOZZLE_SETTINGS_KEY, state.settings);
|
||||||
},
|
},
|
||||||
|
UPDATE_STAT(state, { container, stat }) {
|
||||||
|
Vue.set(container, "stat", stat);
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
const actions = {
|
const actions = {
|
||||||
@@ -50,21 +66,18 @@ const actions = {
|
|||||||
SET_SEARCH({ commit }, filter) {
|
SET_SEARCH({ commit }, filter) {
|
||||||
commit("SET_SEARCH", filter);
|
commit("SET_SEARCH", filter);
|
||||||
},
|
},
|
||||||
async FETCH_CONTAINERS({ commit }) {
|
|
||||||
const containers = await (await fetch(`${config.base}/api/containers.json`)).json();
|
|
||||||
commit("SET_CONTAINERS", containers);
|
|
||||||
},
|
|
||||||
UPDATE_SETTING({ commit }, setting) {
|
UPDATE_SETTING({ commit }, setting) {
|
||||||
commit("UPDATE_SETTINGS", setting);
|
commit("UPDATE_SETTINGS", setting);
|
||||||
},
|
},
|
||||||
|
UPDATE_STATS({ commit, getters }, stat) {
|
||||||
|
const { allContainersById } = getters;
|
||||||
|
const container = allContainersById[stat.id];
|
||||||
|
if (container) {
|
||||||
|
commit("UPDATE_STAT", { container, stat });
|
||||||
|
}
|
||||||
|
},
|
||||||
};
|
};
|
||||||
const getters = {
|
const getters = {
|
||||||
activeContainersById({ activeContainers }) {
|
|
||||||
return activeContainers.reduce((map, obj) => {
|
|
||||||
map[obj.id] = obj;
|
|
||||||
return map;
|
|
||||||
}, {});
|
|
||||||
},
|
|
||||||
allContainersById({ containers }) {
|
allContainersById({ containers }) {
|
||||||
return containers.reduce((map, obj) => {
|
return containers.reduce((map, obj) => {
|
||||||
map[obj.id] = obj;
|
map[obj.id] = obj;
|
||||||
@@ -75,10 +88,15 @@ const getters = {
|
|||||||
const filter = showAllContainers ? () => true : (c) => c.state === "running";
|
const filter = showAllContainers ? () => true : (c) => c.state === "running";
|
||||||
return containers.filter(filter);
|
return containers.filter(filter);
|
||||||
},
|
},
|
||||||
|
activeContainers({ activeContainerIds }, { allContainersById }) {
|
||||||
|
return activeContainerIds.map((id) => allContainersById[id]);
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
const es = new EventSource(`${config.base}/api/events/stream`);
|
const es = new EventSource(`${config.base}/api/events/stream`);
|
||||||
es.addEventListener("containers-changed", (e) => setTimeout(() => store.dispatch("FETCH_CONTAINERS"), 1000), false);
|
es.addEventListener("containers-changed", (e) => store.commit("SET_CONTAINERS", JSON.parse(e.data)), false);
|
||||||
|
es.addEventListener("container-stat", (e) => store.dispatch("UPDATE_STATS", JSON.parse(e.data)), false);
|
||||||
|
|
||||||
mql.addListener((e) => store.commit("SET_MOBILE_WIDTH", e.matches));
|
mql.addListener((e) => store.commit("SET_MOBILE_WIDTH", e.matches));
|
||||||
|
|
||||||
const store = new Vuex.Store({
|
const store = new Vuex.Store({
|
||||||
|
|||||||
@@ -135,3 +135,7 @@ html.has-custom-scrollbars {
|
|||||||
border-color: transparent;
|
border-color: transparent;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.splitpanes__pane {
|
||||||
|
overflow: unset;
|
||||||
|
}
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import (
|
|||||||
"bytes"
|
"bytes"
|
||||||
"context"
|
"context"
|
||||||
"encoding/binary"
|
"encoding/binary"
|
||||||
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"sort"
|
"sort"
|
||||||
@@ -29,6 +30,7 @@ type dockerProxy interface {
|
|||||||
ContainerLogs(context.Context, string, types.ContainerLogsOptions) (io.ReadCloser, error)
|
ContainerLogs(context.Context, string, types.ContainerLogsOptions) (io.ReadCloser, error)
|
||||||
Events(context.Context, types.EventsOptions) (<-chan events.Message, <-chan error)
|
Events(context.Context, types.EventsOptions) (<-chan events.Message, <-chan error)
|
||||||
ContainerInspect(ctx context.Context, containerID string) (types.ContainerJSON, error)
|
ContainerInspect(ctx context.Context, containerID string) (types.ContainerJSON, error)
|
||||||
|
ContainerStats(ctx context.Context, containerID string, stream bool) (types.ContainerStats, error)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Client is a proxy around the docker client
|
// Client is a proxy around the docker client
|
||||||
@@ -38,6 +40,7 @@ type Client interface {
|
|||||||
ContainerLogs(context.Context, string, int, string) (<-chan string, <-chan error)
|
ContainerLogs(context.Context, string, int, string) (<-chan string, <-chan error)
|
||||||
Events(context.Context) (<-chan events.Message, <-chan error)
|
Events(context.Context) (<-chan events.Message, <-chan error)
|
||||||
ContainerLogsBetweenDates(context.Context, string, time.Time, time.Time) ([]string, error)
|
ContainerLogsBetweenDates(context.Context, string, time.Time, time.Time) ([]string, error)
|
||||||
|
ContainerStats(context.Context, string, chan<- ContainerStat) error
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewClient creates a new instance of Client
|
// NewClient creates a new instance of Client
|
||||||
@@ -146,6 +149,48 @@ func logReader(reader io.ReadCloser, tty bool) func() (string, error) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (d *dockerClient) ContainerStats(ctx context.Context, id string, stats chan<- ContainerStat) error {
|
||||||
|
response, err := d.cli.ContainerStats(ctx, id, true)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
go func() {
|
||||||
|
defer response.Body.Close()
|
||||||
|
decoder := json.NewDecoder(response.Body)
|
||||||
|
var v *types.StatsJSON
|
||||||
|
for {
|
||||||
|
if err := decoder.Decode(&v); err != nil {
|
||||||
|
if err == context.Canceled || err == io.EOF {
|
||||||
|
log.Debugf("stopping stats streaming for container %s", id)
|
||||||
|
break
|
||||||
|
}
|
||||||
|
log.Errorf("decoder for stats api returned an unknown error %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
var (
|
||||||
|
cpuDelta = float64(v.CPUStats.CPUUsage.TotalUsage) - float64(v.PreCPUStats.CPUUsage.TotalUsage)
|
||||||
|
systemDelta = float64(v.CPUStats.SystemUsage) - float64(v.PreCPUStats.SystemUsage)
|
||||||
|
cpuPercent = int64((cpuDelta / systemDelta) * float64(len(v.CPUStats.CPUUsage.PercpuUsage)) * 100)
|
||||||
|
memUsage = int64(v.MemoryStats.Usage - v.MemoryStats.Stats["cache"])
|
||||||
|
memPercent = int64(float64(memUsage) / float64(v.MemoryStats.Limit) * 100)
|
||||||
|
)
|
||||||
|
|
||||||
|
if cpuPercent > 0 || memUsage > 0 {
|
||||||
|
stats <- ContainerStat{
|
||||||
|
ID: id,
|
||||||
|
CPUPercent: cpuPercent,
|
||||||
|
MemoryPercent: memPercent,
|
||||||
|
MemoryUsage: memUsage,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
func (d *dockerClient) ContainerLogs(ctx context.Context, id string, tailSize int, since string) (<-chan string, <-chan error) {
|
func (d *dockerClient) ContainerLogs(ctx context.Context, id string, tailSize int, since string) (<-chan string, <-chan error) {
|
||||||
log.WithField("id", id).WithField("since", since).Debug("Streaming logs for container")
|
log.WithField("id", id).WithField("since", since).Debug("Streaming logs for container")
|
||||||
|
|
||||||
|
|||||||
@@ -40,6 +40,7 @@ func (m *mockedProxy) ContainerLogs(ctx context.Context, id string, options type
|
|||||||
}
|
}
|
||||||
return reader, args.Error(1)
|
return reader, args.Error(1)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m *mockedProxy) ContainerInspect(ctx context.Context, containerID string) (types.ContainerJSON, error) {
|
func (m *mockedProxy) ContainerInspect(ctx context.Context, containerID string) (types.ContainerJSON, error) {
|
||||||
args := m.Called(ctx, containerID)
|
args := m.Called(ctx, containerID)
|
||||||
json, ok := args.Get(0).(types.ContainerJSON)
|
json, ok := args.Get(0).(types.ContainerJSON)
|
||||||
@@ -50,6 +51,10 @@ func (m *mockedProxy) ContainerInspect(ctx context.Context, containerID string)
|
|||||||
return json, args.Error(1)
|
return json, args.Error(1)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (m *mockedProxy) ContainerStats(ctx context.Context, containerID string, stream bool) (types.ContainerStats, error) {
|
||||||
|
return types.ContainerStats{}, nil
|
||||||
|
}
|
||||||
|
|
||||||
func Test_dockerClient_ListContainers_null(t *testing.T) {
|
func Test_dockerClient_ListContainers_null(t *testing.T) {
|
||||||
proxy := new(mockedProxy)
|
proxy := new(mockedProxy)
|
||||||
proxy.On("ContainerList", mock.Anything, mock.Anything).Return(nil, nil)
|
proxy.On("ContainerList", mock.Anything, mock.Anything).Return(nil, nil)
|
||||||
|
|||||||
@@ -12,3 +12,11 @@ type Container struct {
|
|||||||
State string `json:"state"`
|
State string `json:"state"`
|
||||||
Status string `json:"status"`
|
Status string `json:"status"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ContainerStat represent stats instant for a container
|
||||||
|
type ContainerStat struct {
|
||||||
|
ID string `json:"id"`
|
||||||
|
CPUPercent int64 `json:"cpu"`
|
||||||
|
MemoryPercent int64 `json:"memory"`
|
||||||
|
MemoryUsage int64 `json:"memoryUsage"`
|
||||||
|
}
|
||||||
|
|||||||
27
go.mod
@@ -14,40 +14,41 @@ require (
|
|||||||
github.com/Azure/go-ansiterm v0.0.0-20170929234023-d6e3b3328b78 // indirect
|
github.com/Azure/go-ansiterm v0.0.0-20170929234023-d6e3b3328b78 // indirect
|
||||||
github.com/Microsoft/go-winio v0.4.14 // indirect
|
github.com/Microsoft/go-winio v0.4.14 // indirect
|
||||||
github.com/beme/abide v0.0.0-20190723115211-635a09831760
|
github.com/beme/abide v0.0.0-20190723115211-635a09831760
|
||||||
github.com/containerd/containerd v1.3.3 // indirect
|
github.com/containerd/containerd v1.4.0 // indirect
|
||||||
github.com/docker/distribution v2.7.1+incompatible // indirect
|
github.com/docker/distribution v2.7.1+incompatible // indirect
|
||||||
github.com/docker/docker v0.0.0-20190827232753-32688a47f341
|
github.com/docker/docker v0.0.0-20190827232753-32688a47f341
|
||||||
github.com/docker/go-connections v0.4.0 // indirect
|
github.com/docker/go-connections v0.4.0 // indirect
|
||||||
github.com/docker/go-units v0.4.0 // indirect
|
github.com/docker/go-units v0.4.0 // indirect
|
||||||
|
github.com/fsnotify/fsnotify v1.4.9 // indirect
|
||||||
github.com/gobuffalo/envy v1.9.0 // indirect
|
github.com/gobuffalo/envy v1.9.0 // indirect
|
||||||
github.com/gobuffalo/packd v1.0.0 // indirect
|
github.com/gobuffalo/packd v1.0.0 // indirect
|
||||||
github.com/gobuffalo/packr v1.30.1
|
github.com/gobuffalo/packr v1.30.1
|
||||||
github.com/gogo/protobuf v1.3.1 // indirect
|
github.com/gogo/protobuf v1.3.1 // indirect
|
||||||
github.com/golang/protobuf v1.3.4 // indirect
|
github.com/golang/protobuf v1.4.2 // indirect
|
||||||
github.com/google/go-cmp v0.3.1 // indirect
|
|
||||||
github.com/gorilla/mux v1.8.0
|
github.com/gorilla/mux v1.8.0
|
||||||
github.com/magiconair/properties v1.8.2
|
github.com/magiconair/properties v1.8.2
|
||||||
|
github.com/mitchellh/mapstructure v1.3.3 // indirect
|
||||||
github.com/morikuni/aec v0.0.0-20170113033406-39771216ff4c // indirect
|
github.com/morikuni/aec v0.0.0-20170113033406-39771216ff4c // indirect
|
||||||
github.com/opencontainers/go-digest v1.0.0-rc1 // indirect
|
github.com/opencontainers/go-digest v1.0.0 // indirect
|
||||||
github.com/opencontainers/image-spec v1.0.1 // indirect
|
github.com/opencontainers/image-spec v1.0.1 // indirect
|
||||||
github.com/pelletier/go-toml v1.6.0 // indirect
|
github.com/pelletier/go-toml v1.8.0 // indirect
|
||||||
github.com/pkg/errors v0.9.1 // indirect
|
github.com/pkg/errors v0.9.1 // indirect
|
||||||
github.com/rogpeppe/go-internal v1.5.2 // indirect
|
github.com/rogpeppe/go-internal v1.6.2 // indirect
|
||||||
github.com/sergi/go-diff v1.0.0 // indirect
|
github.com/sergi/go-diff v1.0.0 // indirect
|
||||||
github.com/sirupsen/logrus v1.6.0
|
github.com/sirupsen/logrus v1.6.0
|
||||||
github.com/spf13/afero v1.2.2 // indirect
|
github.com/spf13/afero v1.3.5 // indirect
|
||||||
github.com/spf13/cast v1.3.1 // indirect
|
github.com/spf13/cast v1.3.1 // indirect
|
||||||
github.com/spf13/jwalterweatherman v1.1.0 // indirect
|
github.com/spf13/jwalterweatherman v1.1.0 // indirect
|
||||||
github.com/spf13/pflag v1.0.5
|
github.com/spf13/pflag v1.0.5
|
||||||
github.com/spf13/viper v1.7.1
|
github.com/spf13/viper v1.7.1
|
||||||
github.com/stretchr/objx v0.2.0 // indirect
|
github.com/stretchr/objx v0.2.0 // indirect
|
||||||
github.com/stretchr/testify v1.6.1
|
github.com/stretchr/testify v1.6.1
|
||||||
golang.org/x/net v0.0.0-20200226121028-0de0cce0169b // indirect
|
golang.org/x/net v0.0.0-20200904194848-62affa334b73 // indirect
|
||||||
golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae // indirect
|
golang.org/x/sys v0.0.0-20200905004654-be1d3432aa8f // indirect
|
||||||
google.golang.org/genproto v0.0.0-20200226201735-46b91f19d98c // indirect
|
google.golang.org/genproto v0.0.0-20200904004341-0bd0a958aa1d // indirect
|
||||||
google.golang.org/grpc v1.27.1 // indirect
|
google.golang.org/grpc v1.31.1
|
||||||
gopkg.in/ini.v1 v1.52.0 // indirect
|
google.golang.org/protobuf v1.25.0 // indirect
|
||||||
gopkg.in/yaml.v2 v2.2.8 // indirect
|
gopkg.in/ini.v1 v1.61.0 // indirect
|
||||||
gotest.tools v2.2.0+incompatible // indirect
|
gotest.tools v2.2.0+incompatible // indirect
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
62
go.sum
@@ -45,8 +45,11 @@ github.com/bugsnag/panicwrap v0.0.0-20151223152923-e2c28503fcd0/go.mod h1:D/8v3k
|
|||||||
github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
|
github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
|
||||||
github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc=
|
github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc=
|
||||||
github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
|
github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
|
||||||
|
github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc=
|
||||||
github.com/containerd/containerd v1.3.3 h1:LoIzb5y9x5l8VKAlyrbusNPXqBY0+kviRloxFUMFwKc=
|
github.com/containerd/containerd v1.3.3 h1:LoIzb5y9x5l8VKAlyrbusNPXqBY0+kviRloxFUMFwKc=
|
||||||
github.com/containerd/containerd v1.3.3/go.mod h1:bC6axHOhabU15QhwfG7w5PipXdVtMXFTttgp+kVtyUA=
|
github.com/containerd/containerd v1.3.3/go.mod h1:bC6axHOhabU15QhwfG7w5PipXdVtMXFTttgp+kVtyUA=
|
||||||
|
github.com/containerd/containerd v1.4.0 h1:aWJB3lbDEaOxg1mkTBAINY2a+NsoKbAeRYefJPPRY+o=
|
||||||
|
github.com/containerd/containerd v1.4.0/go.mod h1:bC6axHOhabU15QhwfG7w5PipXdVtMXFTttgp+kVtyUA=
|
||||||
github.com/coreos/bbolt v1.3.2/go.mod h1:iRUV2dpdMOn7Bo10OQBFzIJO9kkE559Wcmn+qkEiiKk=
|
github.com/coreos/bbolt v1.3.2/go.mod h1:iRUV2dpdMOn7Bo10OQBFzIJO9kkE559Wcmn+qkEiiKk=
|
||||||
github.com/coreos/etcd v3.3.10+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE=
|
github.com/coreos/etcd v3.3.10+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE=
|
||||||
github.com/coreos/etcd v3.3.13+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE=
|
github.com/coreos/etcd v3.3.13+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE=
|
||||||
@@ -74,11 +77,15 @@ github.com/docker/go-metrics v0.0.0-20180209012529-399ea8c73916/go.mod h1:/u0gXw
|
|||||||
github.com/docker/go-units v0.4.0 h1:3uh0PgVws3nIA0Q+MwDC8yjEPf9zjRfZZWXZYDct3Tw=
|
github.com/docker/go-units v0.4.0 h1:3uh0PgVws3nIA0Q+MwDC8yjEPf9zjRfZZWXZYDct3Tw=
|
||||||
github.com/docker/go-units v0.4.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk=
|
github.com/docker/go-units v0.4.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk=
|
||||||
github.com/docker/libtrust v0.0.0-20150114040149-fa567046d9b1/go.mod h1:cyGadeNEkKy96OOhEzfZl+yxihPEzKnqJwvfuSUqbZE=
|
github.com/docker/libtrust v0.0.0-20150114040149-fa567046d9b1/go.mod h1:cyGadeNEkKy96OOhEzfZl+yxihPEzKnqJwvfuSUqbZE=
|
||||||
|
github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
|
||||||
github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
|
github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
|
||||||
|
github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98=
|
||||||
github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
|
github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
|
||||||
github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4=
|
github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4=
|
||||||
github.com/fsnotify/fsnotify v1.4.7 h1:IXs+QLmnXW2CcXuY+8Mzv/fWEsPGWxqefPtCP5CnV9I=
|
github.com/fsnotify/fsnotify v1.4.7 h1:IXs+QLmnXW2CcXuY+8Mzv/fWEsPGWxqefPtCP5CnV9I=
|
||||||
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
|
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
|
||||||
|
github.com/fsnotify/fsnotify v1.4.9 h1:hsms1Qyu0jgnwNXIxa+/V/PDsU6CfLf6CNO8H7IWoS4=
|
||||||
|
github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ=
|
||||||
github.com/garyburd/redigo v0.0.0-20150301180006-535138d7bcd7/go.mod h1:NR3MbYisc3/PwhQ00EMzDiPmrwpPxAn5GI05/YaO1SY=
|
github.com/garyburd/redigo v0.0.0-20150301180006-535138d7bcd7/go.mod h1:NR3MbYisc3/PwhQ00EMzDiPmrwpPxAn5GI05/YaO1SY=
|
||||||
github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
|
github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
|
||||||
github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU=
|
github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU=
|
||||||
@@ -119,12 +126,22 @@ github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5y
|
|||||||
github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw=
|
github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw=
|
||||||
github.com/golang/protobuf v1.3.4 h1:87PNWwrRvUSnqS4dlcBU/ftvOIBep4sYuBLlh6rX2wk=
|
github.com/golang/protobuf v1.3.4 h1:87PNWwrRvUSnqS4dlcBU/ftvOIBep4sYuBLlh6rX2wk=
|
||||||
github.com/golang/protobuf v1.3.4/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw=
|
github.com/golang/protobuf v1.3.4/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw=
|
||||||
|
github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8=
|
||||||
|
github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA=
|
||||||
|
github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs=
|
||||||
|
github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w=
|
||||||
|
github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0=
|
||||||
|
github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8=
|
||||||
|
github.com/golang/protobuf v1.4.2 h1:+Z5KGCizgyZCbGh1KZqA0fcLLkwbsjIzS4aV2v7wJX0=
|
||||||
|
github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
|
||||||
github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
|
github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
|
||||||
github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
|
github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
|
||||||
github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
|
github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
|
||||||
github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
|
github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
|
||||||
github.com/google/go-cmp v0.3.1 h1:Xye71clBPdm5HgqGwUkwhbynsUJZhDbS20FvLhQ2izg=
|
github.com/google/go-cmp v0.3.1 h1:Xye71clBPdm5HgqGwUkwhbynsUJZhDbS20FvLhQ2izg=
|
||||||
github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
|
github.com/google/go-cmp v0.3.1/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.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||||
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
|
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
|
||||||
github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs=
|
github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs=
|
||||||
github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc=
|
github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc=
|
||||||
@@ -187,6 +204,7 @@ github.com/konsorten/go-windows-terminal-sequences v1.0.2 h1:DB17ag19krx9CFsz4o3
|
|||||||
github.com/konsorten/go-windows-terminal-sequences v1.0.2/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
|
github.com/konsorten/go-windows-terminal-sequences v1.0.2/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
|
||||||
github.com/konsorten/go-windows-terminal-sequences v1.0.3 h1:CE8S1cTafDpPvMhIxNJKvHsGVBgn1xWYf1NbHQhywc8=
|
github.com/konsorten/go-windows-terminal-sequences v1.0.3 h1:CE8S1cTafDpPvMhIxNJKvHsGVBgn1xWYf1NbHQhywc8=
|
||||||
github.com/konsorten/go-windows-terminal-sequences v1.0.3/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
|
github.com/konsorten/go-windows-terminal-sequences v1.0.3/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
|
||||||
|
github.com/kr/fs v0.1.0/go.mod h1:FFnZGqtBN9Gxj7eW1uZ42v5BccTP0vu6NEaFoC2HwRg=
|
||||||
github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc=
|
github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc=
|
||||||
github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI=
|
github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI=
|
||||||
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
|
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
|
||||||
@@ -212,6 +230,8 @@ github.com/mitchellh/iochan v1.0.0/go.mod h1:JwYml1nuB7xOzsp52dPpHFffvOCDupsG0Qu
|
|||||||
github.com/mitchellh/mapstructure v0.0.0-20160808181253-ca63d7c062ee/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y=
|
github.com/mitchellh/mapstructure v0.0.0-20160808181253-ca63d7c062ee/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y=
|
||||||
github.com/mitchellh/mapstructure v1.1.2 h1:fmNYVwqnSfB9mZU6OS2O6GsXM+wcskZDuKQzvN1EDeE=
|
github.com/mitchellh/mapstructure v1.1.2 h1:fmNYVwqnSfB9mZU6OS2O6GsXM+wcskZDuKQzvN1EDeE=
|
||||||
github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y=
|
github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y=
|
||||||
|
github.com/mitchellh/mapstructure v1.3.3 h1:SzB1nHZ2Xi+17FP0zVQBHIZqvwRN9408fJO8h+eeNA8=
|
||||||
|
github.com/mitchellh/mapstructure v1.3.3/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo=
|
||||||
github.com/mitchellh/osext v0.0.0-20151018003038-5e2d6d41470f/go.mod h1:OkQIRizQZAeMln+1tSwduZz7+Af5oFlKirV/MSYes2A=
|
github.com/mitchellh/osext v0.0.0-20151018003038-5e2d6d41470f/go.mod h1:OkQIRizQZAeMln+1tSwduZz7+Af5oFlKirV/MSYes2A=
|
||||||
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
|
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
|
||||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
|
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
|
||||||
@@ -225,6 +245,8 @@ github.com/oklog/ulid v1.3.1/go.mod h1:CirwcVhetQ6Lv90oh/F+FBtV6XMibvdAFo93nm5qn
|
|||||||
github.com/opencontainers/go-digest v0.0.0-20170106003457-a6d0ee40d420/go.mod h1:cMLVZDEM3+U2I4VmLI6N8jQYUd2OVphdqWwCJHrFt2s=
|
github.com/opencontainers/go-digest v0.0.0-20170106003457-a6d0ee40d420/go.mod h1:cMLVZDEM3+U2I4VmLI6N8jQYUd2OVphdqWwCJHrFt2s=
|
||||||
github.com/opencontainers/go-digest v1.0.0-rc1 h1:WzifXhOVOEOuFYOJAW6aQqW0TooG2iki3E3Ii+WN7gQ=
|
github.com/opencontainers/go-digest v1.0.0-rc1 h1:WzifXhOVOEOuFYOJAW6aQqW0TooG2iki3E3Ii+WN7gQ=
|
||||||
github.com/opencontainers/go-digest v1.0.0-rc1/go.mod h1:cMLVZDEM3+U2I4VmLI6N8jQYUd2OVphdqWwCJHrFt2s=
|
github.com/opencontainers/go-digest v1.0.0-rc1/go.mod h1:cMLVZDEM3+U2I4VmLI6N8jQYUd2OVphdqWwCJHrFt2s=
|
||||||
|
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.0.0/go.mod h1:BtxoFyWECRxE4U/7sNtV5W15zMzWCbyJoFRP3s7yZA0=
|
github.com/opencontainers/image-spec v1.0.0/go.mod h1:BtxoFyWECRxE4U/7sNtV5W15zMzWCbyJoFRP3s7yZA0=
|
||||||
github.com/opencontainers/image-spec v1.0.1 h1:JMemWkRwHx4Zj+fVxWoMCFm/8sYGGrUVojFA6h/TRcI=
|
github.com/opencontainers/image-spec v1.0.1 h1:JMemWkRwHx4Zj+fVxWoMCFm/8sYGGrUVojFA6h/TRcI=
|
||||||
github.com/opencontainers/image-spec v1.0.1/go.mod h1:BtxoFyWECRxE4U/7sNtV5W15zMzWCbyJoFRP3s7yZA0=
|
github.com/opencontainers/image-spec v1.0.1/go.mod h1:BtxoFyWECRxE4U/7sNtV5W15zMzWCbyJoFRP3s7yZA0=
|
||||||
@@ -233,12 +255,15 @@ github.com/pelletier/go-toml v1.2.0 h1:T5zMGML61Wp+FlcbWjRDT7yAxhJNAiPPLOFECq181
|
|||||||
github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic=
|
github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic=
|
||||||
github.com/pelletier/go-toml v1.6.0 h1:aetoXYr0Tv7xRU/V4B4IZJ2QcbtMUFoNb3ORp7TzIK4=
|
github.com/pelletier/go-toml v1.6.0 h1:aetoXYr0Tv7xRU/V4B4IZJ2QcbtMUFoNb3ORp7TzIK4=
|
||||||
github.com/pelletier/go-toml v1.6.0/go.mod h1:5N711Q9dKgbdkxHL+MEfF31hpT7l0S0s/t2kKREewys=
|
github.com/pelletier/go-toml v1.6.0/go.mod h1:5N711Q9dKgbdkxHL+MEfF31hpT7l0S0s/t2kKREewys=
|
||||||
|
github.com/pelletier/go-toml v1.8.0 h1:Keo9qb7iRJs2voHvunFtuuYFsbWeOBh8/P9v/kVMFtw=
|
||||||
|
github.com/pelletier/go-toml v1.8.0/go.mod h1:D6yutnOGMveHEPV7VQOuvI/gXY61bv+9bAOTRnLElKs=
|
||||||
github.com/pkg/errors v0.8.0 h1:WdK/asTD0HN+q6hsWO3/vpuAkAr+tw6aNJNDFFf0+qw=
|
github.com/pkg/errors v0.8.0 h1:WdK/asTD0HN+q6hsWO3/vpuAkAr+tw6aNJNDFFf0+qw=
|
||||||
github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||||
github.com/pkg/errors v0.8.1 h1:iURUrRGxPUNPdy5/HRSm+Yj6okJ6UtLINN0Q9M4+h3I=
|
github.com/pkg/errors v0.8.1 h1:iURUrRGxPUNPdy5/HRSm+Yj6okJ6UtLINN0Q9M4+h3I=
|
||||||
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||||
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
|
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
|
||||||
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||||
|
github.com/pkg/sftp v1.10.1/go.mod h1:lYOWFsE0bwd1+KfKJaKeuokY15vzFx25BLbzYYoAxZI=
|
||||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||||
github.com/posener/complete v1.1.1/go.mod h1:em0nMJCgc9GFtwrmVmEMR/ZL6WyhyjMBndrE9hABlRI=
|
github.com/posener/complete v1.1.1/go.mod h1:em0nMJCgc9GFtwrmVmEMR/ZL6WyhyjMBndrE9hABlRI=
|
||||||
@@ -264,6 +289,8 @@ github.com/rogpeppe/go-internal v1.3.2 h1:XU784Pr0wdahMY2bYcyK6N1KuaRAdLtqD4qd8D
|
|||||||
github.com/rogpeppe/go-internal v1.3.2/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc=
|
github.com/rogpeppe/go-internal v1.3.2/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc=
|
||||||
github.com/rogpeppe/go-internal v1.5.2 h1:qLvObTrvO/XRCqmkKxUlOBc48bI3efyDuAZe25QiF0w=
|
github.com/rogpeppe/go-internal v1.5.2 h1:qLvObTrvO/XRCqmkKxUlOBc48bI3efyDuAZe25QiF0w=
|
||||||
github.com/rogpeppe/go-internal v1.5.2/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc=
|
github.com/rogpeppe/go-internal v1.5.2/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc=
|
||||||
|
github.com/rogpeppe/go-internal v1.6.2 h1:aIihoIOHCiLZHxyoNQ+ABL4NKhFTgKLBdMLyEAh98m0=
|
||||||
|
github.com/rogpeppe/go-internal v1.6.2/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc=
|
||||||
github.com/russross/blackfriday v1.5.2/go.mod h1:JO/DiYxRf+HjHt06OyowR9PTA263kcR/rfWxYHBV53g=
|
github.com/russross/blackfriday v1.5.2/go.mod h1:JO/DiYxRf+HjHt06OyowR9PTA263kcR/rfWxYHBV53g=
|
||||||
github.com/ryanuber/columnize v0.0.0-20160712163229-9b3edd62028f/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts=
|
github.com/ryanuber/columnize v0.0.0-20160712163229-9b3edd62028f/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts=
|
||||||
github.com/satori/go.uuid v1.2.0/go.mod h1:dA0hQrYB0VpLJoorglMZABFdXlWrHn1NEOzdhQKdks0=
|
github.com/satori/go.uuid v1.2.0/go.mod h1:dA0hQrYB0VpLJoorglMZABFdXlWrHn1NEOzdhQKdks0=
|
||||||
@@ -289,6 +316,8 @@ github.com/spf13/afero v1.1.2 h1:m8/z1t7/fwjysjQRYbP0RD+bUIF/8tJwPdEZsI83ACI=
|
|||||||
github.com/spf13/afero v1.1.2/go.mod h1:j4pytiNVoe2o6bmDsKpLACNPDBIoEAkihy7loJ1B0CQ=
|
github.com/spf13/afero v1.1.2/go.mod h1:j4pytiNVoe2o6bmDsKpLACNPDBIoEAkihy7loJ1B0CQ=
|
||||||
github.com/spf13/afero v1.2.2 h1:5jhuqJyZCZf2JRofRvN/nIFgIWNzPa3/Vz8mYylgbWc=
|
github.com/spf13/afero v1.2.2 h1:5jhuqJyZCZf2JRofRvN/nIFgIWNzPa3/Vz8mYylgbWc=
|
||||||
github.com/spf13/afero v1.2.2/go.mod h1:9ZxEEn6pIJ8Rxe320qSDBk6AsU0r9pR7Q4OcevTdifk=
|
github.com/spf13/afero v1.2.2/go.mod h1:9ZxEEn6pIJ8Rxe320qSDBk6AsU0r9pR7Q4OcevTdifk=
|
||||||
|
github.com/spf13/afero v1.3.5 h1:AWZ/w4lcfxuh52NVL78p9Eh8j6r1mCTEGSRFBJyIHAE=
|
||||||
|
github.com/spf13/afero v1.3.5/go.mod h1:Ai8FlHk4v/PARR026UzYexafAt9roJ7LcLMAmO6Z93I=
|
||||||
github.com/spf13/cast v1.3.0 h1:oget//CVOEoFewqQxwr0Ej5yjygnqGkvggSE/gB35Q8=
|
github.com/spf13/cast v1.3.0 h1:oget//CVOEoFewqQxwr0Ej5yjygnqGkvggSE/gB35Q8=
|
||||||
github.com/spf13/cast v1.3.0/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE=
|
github.com/spf13/cast v1.3.0/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE=
|
||||||
github.com/spf13/cast v1.3.1 h1:nFm6S0SMdyzrzcmThSipiEubIDy8WEXKNZ0UOgiRpng=
|
github.com/spf13/cast v1.3.1 h1:nFm6S0SMdyzrzcmThSipiEubIDy8WEXKNZ0UOgiRpng=
|
||||||
@@ -348,6 +377,8 @@ golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACk
|
|||||||
golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||||
golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||||
golang.org/x/crypto v0.0.0-20190621222207-cc06ce4a13d4/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
golang.org/x/crypto v0.0.0-20190621222207-cc06ce4a13d4/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||||
|
golang.org/x/crypto v0.0.0-20190820162420-60c769a6c586/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||||
|
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||||
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
||||||
golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
||||||
golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8=
|
golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8=
|
||||||
@@ -386,6 +417,8 @@ golang.org/x/net v0.0.0-20190619014844-b5b0513f8c1b/go.mod h1:z5CRVTTTmAJ677TzLL
|
|||||||
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||||
golang.org/x/net v0.0.0-20200226121028-0de0cce0169b h1:0mm1VjtFUOIlE1SbDlwjYaDxZVDP2S5ou6y0gSgXHu8=
|
golang.org/x/net v0.0.0-20200226121028-0de0cce0169b h1:0mm1VjtFUOIlE1SbDlwjYaDxZVDP2S5ou6y0gSgXHu8=
|
||||||
golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||||
|
golang.org/x/net v0.0.0-20200904194848-62affa334b73 h1:MXfv8rhZWmFeqX3GNZRsd6vOLoaCHjYEX3qkRo3YBUA=
|
||||||
|
golang.org/x/net v0.0.0-20200904194848-62affa334b73/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
|
||||||
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
|
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
|
||||||
golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
||||||
golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
||||||
@@ -414,12 +447,18 @@ golang.org/x/sys v0.0.0-20190515120540-06a5c4944438/go.mod h1:h1NjWce9XRLGQEsW7w
|
|||||||
golang.org/x/sys v0.0.0-20190602015325-4c4f7f33c9ed/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20190602015325-4c4f7f33c9ed/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae h1:/WDfKMnPU+m5M4xB+6x4kaepxRw6jWvR5iDRdvjHgy8=
|
golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae h1:/WDfKMnPU+m5M4xB+6x4kaepxRw6jWvR5iDRdvjHgy8=
|
||||||
golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
golang.org/x/sys v0.0.0-20200905004654-be1d3432aa8f h1:Fqb3ao1hUmOR3GkUOg/Y+BadLwykBIzs5q8Ez2SbHyc=
|
||||||
|
golang.org/x/sys v0.0.0-20200905004654-be1d3432aa8f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||||
golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||||
golang.org/x/text v0.3.2 h1:tW2bmiBqwgJj/UpqtC8EpXEZVYOwU0yG4iWbprSVAcs=
|
golang.org/x/text v0.3.2 h1:tW2bmiBqwgJj/UpqtC8EpXEZVYOwU0yG4iWbprSVAcs=
|
||||||
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
|
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
|
||||||
|
golang.org/x/text v0.3.3 h1:cokOdA+Jmi5PJGXLlLllQSgYigAEfHXJAERHVMaCc2k=
|
||||||
|
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||||
golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||||
golang.org/x/time v0.0.0-20190308202827-9d24e82272b4 h1:SvFZT6jyqRaOeXpc5h/JSfZenJ2O330aBsf7JfSUXmQ=
|
golang.org/x/time v0.0.0-20190308202827-9d24e82272b4 h1:SvFZT6jyqRaOeXpc5h/JSfZenJ2O330aBsf7JfSUXmQ=
|
||||||
golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||||
@@ -444,6 +483,7 @@ golang.org/x/tools v0.0.0-20190911174233-4f2ddba30aff/go.mod h1:b+2E5dAYhXwXZwtn
|
|||||||
golang.org/x/tools v0.0.0-20191012152004-8de300cfc20a/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
golang.org/x/tools v0.0.0-20191012152004-8de300cfc20a/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||||
golang.org/x/tools v0.0.0-20191112195655-aa38f8e97acc/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
golang.org/x/tools v0.0.0-20191112195655-aa38f8e97acc/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||||
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||||
|
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||||
google.golang.org/api v0.0.0-20160322025152-9bf6e6e569ff/go.mod h1:4mhQ8q/RsB7i+udVvVy5NUi08OU8ZlA0gRVgrF7VFY0=
|
google.golang.org/api v0.0.0-20160322025152-9bf6e6e569ff/go.mod h1:4mhQ8q/RsB7i+udVvVy5NUi08OU8ZlA0gRVgrF7VFY0=
|
||||||
google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE=
|
google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE=
|
||||||
google.golang.org/api v0.7.0/go.mod h1:WtwebWUNSVBH/HAw79HIFXZNqEvBhG+Ra+ax0hx3E3M=
|
google.golang.org/api v0.7.0/go.mod h1:WtwebWUNSVBH/HAw79HIFXZNqEvBhG+Ra+ax0hx3E3M=
|
||||||
@@ -467,6 +507,9 @@ google.golang.org/genproto v0.0.0-20190911173649-1774047e7e51/go.mod h1:IbNlFCBr
|
|||||||
google.golang.org/genproto v0.0.0-20191108220845-16a3f7862a1a/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
|
google.golang.org/genproto v0.0.0-20191108220845-16a3f7862a1a/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
|
||||||
google.golang.org/genproto v0.0.0-20200226201735-46b91f19d98c h1:xFOdgVPpeowWAH0MJ5i0XMp+3yWiWamMtN/kx9xThIQ=
|
google.golang.org/genproto v0.0.0-20200226201735-46b91f19d98c h1:xFOdgVPpeowWAH0MJ5i0XMp+3yWiWamMtN/kx9xThIQ=
|
||||||
google.golang.org/genproto v0.0.0-20200226201735-46b91f19d98c/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
|
google.golang.org/genproto v0.0.0-20200226201735-46b91f19d98c/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
|
||||||
|
google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo=
|
||||||
|
google.golang.org/genproto v0.0.0-20200904004341-0bd0a958aa1d h1:92D1fum1bJLKSdr11OJ+54YeCMCGYIygTA7R/YZxH5M=
|
||||||
|
google.golang.org/genproto v0.0.0-20200904004341-0bd0a958aa1d/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
|
||||||
google.golang.org/grpc v0.0.0-20160317175043-d3ddb4469d5a/go.mod h1:yo6s7OP7yaDglbqo1J04qKzAhqBH6lvTonzMVmEdcZw=
|
google.golang.org/grpc v0.0.0-20160317175043-d3ddb4469d5a/go.mod h1:yo6s7OP7yaDglbqo1J04qKzAhqBH6lvTonzMVmEdcZw=
|
||||||
google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
|
google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
|
||||||
google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38=
|
google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38=
|
||||||
@@ -474,9 +517,24 @@ google.golang.org/grpc v1.21.0 h1:G+97AoqBnmZIT91cLG/EkCoK9NSelj64P8bOHHNmGn0=
|
|||||||
google.golang.org/grpc v1.21.0/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM=
|
google.golang.org/grpc v1.21.0/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM=
|
||||||
google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM=
|
google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM=
|
||||||
google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg=
|
google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg=
|
||||||
|
google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY=
|
||||||
google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
|
google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
|
||||||
google.golang.org/grpc v1.27.1 h1:zvIju4sqAGvwKspUQOhwnpcqSbzi7/H6QomNNjTL4sk=
|
google.golang.org/grpc v1.27.1 h1:zvIju4sqAGvwKspUQOhwnpcqSbzi7/H6QomNNjTL4sk=
|
||||||
google.golang.org/grpc v1.27.1/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
|
google.golang.org/grpc v1.27.1/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
|
||||||
|
google.golang.org/grpc v1.31.1 h1:SfXqXS5hkufcdZ/mHtYCh53P2b+92WQq/DZcKLgsFRs=
|
||||||
|
google.golang.org/grpc v1.31.1/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak=
|
||||||
|
google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8=
|
||||||
|
google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0=
|
||||||
|
google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM=
|
||||||
|
google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE=
|
||||||
|
google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo=
|
||||||
|
google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
|
||||||
|
google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
|
||||||
|
google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
|
||||||
|
google.golang.org/protobuf v1.24.0 h1:UhZDfRO8JRQru4/+LlLE0BRKGF8L+PICnvYZmx/fEGA=
|
||||||
|
google.golang.org/protobuf v1.24.0/go.mod h1:r/3tXBNzIEhYS9I1OUVjXDlt8tc493IdKGjtUeSXeh4=
|
||||||
|
google.golang.org/protobuf v1.25.0 h1:Ejskq+SyPohKW+1uil0JJMtmHCgJPJ/qWTxr8qp+R4c=
|
||||||
|
google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c=
|
||||||
gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw=
|
gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw=
|
||||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||||
gopkg.in/check.v1 v1.0.0-20141024133853-64131543e789/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
gopkg.in/check.v1 v1.0.0-20141024133853-64131543e789/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||||
@@ -487,6 +545,8 @@ gopkg.in/ini.v1 v1.51.0 h1:AQvPpx3LzTDM0AjnIRlVFwFFGC+npRopjZxLJj6gdno=
|
|||||||
gopkg.in/ini.v1 v1.51.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
|
gopkg.in/ini.v1 v1.51.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
|
||||||
gopkg.in/ini.v1 v1.52.0 h1:j+Lt/M1oPPejkniCg1TkWE2J3Eh1oZTsHSXzMTzUXn4=
|
gopkg.in/ini.v1 v1.52.0 h1:j+Lt/M1oPPejkniCg1TkWE2J3Eh1oZTsHSXzMTzUXn4=
|
||||||
gopkg.in/ini.v1 v1.52.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
|
gopkg.in/ini.v1 v1.52.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
|
||||||
|
gopkg.in/ini.v1 v1.61.0 h1:LBCdW4FmFYL4s/vDZD1RQYX7oAR6IjujCYgMdbHBR10=
|
||||||
|
gopkg.in/ini.v1 v1.61.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
|
||||||
gopkg.in/resty.v1 v1.12.0/go.mod h1:mDo4pnntr5jdWRML875a/NmxYqAlA73dVijT2AXvQQo=
|
gopkg.in/resty.v1 v1.12.0/go.mod h1:mDo4pnntr5jdWRML875a/NmxYqAlA73dVijT2AXvQQo=
|
||||||
gopkg.in/yaml.v2 v2.0.0-20170812160011-eb3733d160e7/go.mod h1:JAlM8MvJe8wmxCU4Bli9HhUf9+ttbYbLASfIpnQbh74=
|
gopkg.in/yaml.v2 v2.0.0-20170812160011-eb3733d160e7/go.mod h1:JAlM8MvJe8wmxCU4Bli9HhUf9+ttbYbLASfIpnQbh74=
|
||||||
gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||||
@@ -496,6 +556,8 @@ gopkg.in/yaml.v2 v2.2.4 h1:/eiJrUcujPVeJ3xlSWaiNi3uSVmDGBK1pDHUHAnao1I=
|
|||||||
gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||||
gopkg.in/yaml.v2 v2.2.8 h1:obN1ZagJSUGI0Ek/LBmuj4SNLPfIny3KsKFopxRdj10=
|
gopkg.in/yaml.v2 v2.2.8 h1:obN1ZagJSUGI0Ek/LBmuj4SNLPfIny3KsKFopxRdj10=
|
||||||
gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||||
|
gopkg.in/yaml.v2 v2.3.0 h1:clyUAQHOM3G0M3f5vQj7LuJrETvjVot3Z5el9nffUtU=
|
||||||
|
gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||||
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo=
|
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo=
|
||||||
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||||
gotest.tools v2.2.0+incompatible h1:VsBPFP1AI068pPrMxtb/S8Zkgf9xEmTLJjfM+P5UIEo=
|
gotest.tools v2.2.0+incompatible h1:VsBPFP1AI068pPrMxtb/S8Zkgf9xEmTLJjfM+P5UIEo=
|
||||||
|
|||||||
|
Before Width: | Height: | Size: 36 KiB After Width: | Height: | Size: 36 KiB |
|
Before Width: | Height: | Size: 34 KiB After Width: | Height: | Size: 34 KiB |
|
Before Width: | Height: | Size: 63 KiB After Width: | Height: | Size: 67 KiB |
|
Before Width: | Height: | Size: 72 KiB After Width: | Height: | Size: 76 KiB |
|
Before Width: | Height: | Size: 35 KiB After Width: | Height: | Size: 35 KiB |
|
Before Width: | Height: | Size: 65 KiB After Width: | Height: | Size: 69 KiB |
|
Before Width: | Height: | Size: 74 KiB After Width: | Height: | Size: 77 KiB |
31
main_test.go
@@ -70,32 +70,8 @@ func (m *MockedClient) Events(ctx context.Context) (<-chan events.Message, <-cha
|
|||||||
return channel, err
|
return channel, err
|
||||||
}
|
}
|
||||||
|
|
||||||
func Test_handler_listContainers_happy(t *testing.T) {
|
func (m *MockedClient) ContainerStats(context.Context, string, chan<- docker.ContainerStat) error {
|
||||||
req, err := http.NewRequest("GET", "/api/containers.json", nil)
|
return nil
|
||||||
require.NoError(t, err, "NewRequest should not return an error.")
|
|
||||||
|
|
||||||
rr := httptest.NewRecorder()
|
|
||||||
|
|
||||||
mockedClient := new(MockedClient)
|
|
||||||
containers := []docker.Container{
|
|
||||||
{
|
|
||||||
ID: "1234567890",
|
|
||||||
Status: "status",
|
|
||||||
State: "state",
|
|
||||||
Name: "test",
|
|
||||||
Created: 0,
|
|
||||||
Command: "command",
|
|
||||||
ImageID: "image_id",
|
|
||||||
Image: "image",
|
|
||||||
},
|
|
||||||
}
|
|
||||||
mockedClient.On("ListContainers", mock.Anything).Return(containers, nil)
|
|
||||||
|
|
||||||
h := handler{client: mockedClient}
|
|
||||||
handler := http.HandlerFunc(h.listContainers)
|
|
||||||
handler.ServeHTTP(rr, req)
|
|
||||||
abide.AssertHTTPResponse(t, t.Name(), rr.Result())
|
|
||||||
mockedClient.AssertExpectations(t)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func Test_handler_streamLogs_happy(t *testing.T) {
|
func Test_handler_streamLogs_happy(t *testing.T) {
|
||||||
@@ -232,6 +208,7 @@ func Test_handler_streamEvents_happy(t *testing.T) {
|
|||||||
messages := make(chan events.Message)
|
messages := make(chan events.Message)
|
||||||
errChannel := make(chan error)
|
errChannel := make(chan error)
|
||||||
mockedClient.On("Events", mock.Anything).Return(messages, errChannel)
|
mockedClient.On("Events", mock.Anything).Return(messages, errChannel)
|
||||||
|
mockedClient.On("ListContainers").Return([]docker.Container{}, nil)
|
||||||
|
|
||||||
go func() {
|
go func() {
|
||||||
messages <- events.Message{
|
messages <- events.Message{
|
||||||
@@ -258,6 +235,7 @@ func Test_handler_streamEvents_error(t *testing.T) {
|
|||||||
messages := make(chan events.Message)
|
messages := make(chan events.Message)
|
||||||
errChannel := make(chan error)
|
errChannel := make(chan error)
|
||||||
mockedClient.On("Events", mock.Anything).Return(messages, errChannel)
|
mockedClient.On("Events", mock.Anything).Return(messages, errChannel)
|
||||||
|
mockedClient.On("ListContainers").Return([]docker.Container{}, nil)
|
||||||
|
|
||||||
go func() {
|
go func() {
|
||||||
errChannel <- errors.New("fake error")
|
errChannel <- errors.New("fake error")
|
||||||
@@ -281,6 +259,7 @@ func Test_handler_streamEvents_error_request(t *testing.T) {
|
|||||||
messages := make(chan events.Message)
|
messages := make(chan events.Message)
|
||||||
errChannel := make(chan error)
|
errChannel := make(chan error)
|
||||||
mockedClient.On("Events", mock.Anything).Return(messages, errChannel)
|
mockedClient.On("Events", mock.Anything).Return(messages, errChannel)
|
||||||
|
mockedClient.On("ListContainers").Return([]docker.Container{}, nil)
|
||||||
|
|
||||||
ctx, cancel := context.WithCancel(context.Background())
|
ctx, cancel := context.WithCancel(context.Background())
|
||||||
req = req.WithContext(ctx)
|
req = req.WithContext(ctx)
|
||||||
|
|||||||
113
routes.go
@@ -8,8 +8,10 @@ import (
|
|||||||
"net/http"
|
"net/http"
|
||||||
"runtime"
|
"runtime"
|
||||||
"strings"
|
"strings"
|
||||||
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"github.com/amir20/dozzle/docker"
|
||||||
"github.com/gorilla/mux"
|
"github.com/gorilla/mux"
|
||||||
log "github.com/sirupsen/logrus"
|
log "github.com/sirupsen/logrus"
|
||||||
)
|
)
|
||||||
@@ -23,7 +25,6 @@ func createRoutes(base string, h *handler) *mux.Router {
|
|||||||
}))
|
}))
|
||||||
}
|
}
|
||||||
s := r.PathPrefix(base).Subrouter()
|
s := r.PathPrefix(base).Subrouter()
|
||||||
s.HandleFunc("/api/containers.json", h.listContainers)
|
|
||||||
s.HandleFunc("/api/logs/stream", h.streamLogs)
|
s.HandleFunc("/api/logs/stream", h.streamLogs)
|
||||||
s.HandleFunc("/api/logs", h.fetchLogsBetweenDates)
|
s.HandleFunc("/api/logs", h.fetchLogsBetweenDates)
|
||||||
s.HandleFunc("/api/events/stream", h.streamEvents)
|
s.HandleFunc("/api/events/stream", h.streamEvents)
|
||||||
@@ -69,19 +70,6 @@ func (h *handler) index(w http.ResponseWriter, req *http.Request) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (h *handler) listContainers(w http.ResponseWriter, r *http.Request) {
|
|
||||||
containers, err := h.client.ListContainers()
|
|
||||||
if err != nil {
|
|
||||||
http.Error(w, err.Error(), http.StatusInternalServerError)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
err = json.NewEncoder(w).Encode(containers)
|
|
||||||
if err != nil {
|
|
||||||
http.Error(w, err.Error(), http.StatusInternalServerError)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (h *handler) fetchLogsBetweenDates(w http.ResponseWriter, r *http.Request) {
|
func (h *handler) fetchLogsBetweenDates(w http.ResponseWriter, r *http.Request) {
|
||||||
w.Header().Set("Content-Type", "text/plain; charset=UTF-8")
|
w.Header().Set("Content-Type", "text/plain; charset=UTF-8")
|
||||||
|
|
||||||
@@ -166,24 +154,65 @@ func (h *handler) streamEvents(w http.ResponseWriter, r *http.Request) {
|
|||||||
w.Header().Set("X-Accel-Buffering", "no")
|
w.Header().Set("X-Accel-Buffering", "no")
|
||||||
|
|
||||||
ctx := r.Context()
|
ctx := r.Context()
|
||||||
|
|
||||||
messages, err := h.client.Events(ctx)
|
messages, err := h.client.Events(ctx)
|
||||||
|
stats := make(chan docker.ContainerStat)
|
||||||
|
|
||||||
|
runningContainers := map[string]docker.Container{}
|
||||||
|
if containers, err := h.client.ListContainers(); err == nil {
|
||||||
|
for _, c := range containers {
|
||||||
|
if c.State == "running" {
|
||||||
|
h.client.ContainerStats(ctx, c.ID, stats)
|
||||||
|
runningContainers[c.ID] = c
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := sendContainersJSON(h.client, w); err != nil {
|
||||||
|
log.Errorf("Error while encoding containers to stream: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
f.Flush()
|
||||||
|
|
||||||
|
delayer := delayedFunc(time.Second)
|
||||||
|
|
||||||
Loop:
|
Loop:
|
||||||
for {
|
for {
|
||||||
select {
|
select {
|
||||||
|
case stat := <-stats:
|
||||||
|
bytes, _ := json.Marshal(stat)
|
||||||
|
_, err := fmt.Fprintf(w, "event: container-stat\ndata: %s\n\n", string(bytes))
|
||||||
|
if err != nil {
|
||||||
|
log.Debugf("Error while writing to event stream: %v", err)
|
||||||
|
break
|
||||||
|
}
|
||||||
|
f.Flush()
|
||||||
case message, ok := <-messages:
|
case message, ok := <-messages:
|
||||||
if !ok {
|
if !ok {
|
||||||
break Loop
|
break Loop
|
||||||
}
|
}
|
||||||
switch message.Action {
|
switch message.Action {
|
||||||
case "connect", "disconnect", "create", "destroy", "start", "stop":
|
case "start", "connect", "disconnect", "die":
|
||||||
log.Debugf("Triggering docker event: %v", message.Action)
|
log.Debugf("Triggering docker event: %v", message.Action)
|
||||||
_, err := fmt.Fprintf(w, "event: containers-changed\ndata: %s\n\n", message.Action)
|
if message.Action == "start" {
|
||||||
|
log.Debugf("Scanning for new containers")
|
||||||
if err != nil {
|
if containers, err := h.client.ListContainers(); err == nil {
|
||||||
log.Debugf("Error while writing to event stream: %v", err)
|
for _, c := range containers {
|
||||||
break
|
if _, ok = runningContainers[c.ID]; c.State == "running" && !ok {
|
||||||
|
log.Debugf("Found a new container %v", c.ID)
|
||||||
|
h.client.ContainerStats(ctx, c.ID, stats)
|
||||||
|
runningContainers[c.ID] = c
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
delayer(func() {
|
||||||
|
if err := sendContainersJSON(h.client, w); err != nil {
|
||||||
|
log.Errorf("Error while encoding containers to stream: %v", err)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
f.Flush()
|
f.Flush()
|
||||||
default:
|
default:
|
||||||
log.Debugf("Ignoring docker event: %v", message.Action)
|
log.Debugf("Ignoring docker event: %v", message.Action)
|
||||||
@@ -199,3 +228,47 @@ Loop:
|
|||||||
func (h *handler) version(w http.ResponseWriter, r *http.Request) {
|
func (h *handler) version(w http.ResponseWriter, r *http.Request) {
|
||||||
fmt.Fprintln(w, version)
|
fmt.Fprintln(w, version)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func sendContainersJSON(client docker.Client, w http.ResponseWriter) error {
|
||||||
|
if containers, err := client.ListContainers(); err != nil {
|
||||||
|
return err
|
||||||
|
} else {
|
||||||
|
if _, err := fmt.Fprint(w, "event: containers-changed\ndata: "); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := json.NewEncoder(w).Encode(containers); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if _, err := fmt.Fprint(w, "\n\n"); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type delayedFun struct {
|
||||||
|
mu sync.Mutex
|
||||||
|
after time.Duration
|
||||||
|
timer *time.Timer
|
||||||
|
}
|
||||||
|
|
||||||
|
func delayedFunc(after time.Duration) func(f func()) {
|
||||||
|
d := &delayedFun{after: after}
|
||||||
|
|
||||||
|
return func(f func()) {
|
||||||
|
d.add(f)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *delayedFun) add(f func()) {
|
||||||
|
d.mu.Lock()
|
||||||
|
defer d.mu.Unlock()
|
||||||
|
|
||||||
|
if d.timer != nil {
|
||||||
|
d.timer.Stop()
|
||||||
|
}
|
||||||
|
d.timer = time.AfterFunc(d.after, f)
|
||||||
|
}
|
||||||
|
|||||||
@@ -29,7 +29,7 @@
|
|||||||
lodash "^4.17.19"
|
lodash "^4.17.19"
|
||||||
resolve "^1.3.2"
|
resolve "^1.3.2"
|
||||||
semver "^5.4.1"
|
semver "^5.4.1"
|
||||||
source-map "^0.5.0"
|
source-map "^0.6.1"
|
||||||
|
|
||||||
"@babel/generator@^7.11.5", "@babel/generator@^7.11.6":
|
"@babel/generator@^7.11.5", "@babel/generator@^7.11.6":
|
||||||
version "7.11.6"
|
version "7.11.6"
|
||||||
@@ -38,7 +38,7 @@
|
|||||||
dependencies:
|
dependencies:
|
||||||
"@babel/types" "^7.11.5"
|
"@babel/types" "^7.11.5"
|
||||||
jsesc "^2.5.1"
|
jsesc "^2.5.1"
|
||||||
source-map "^0.5.0"
|
source-map "^0.6.1"
|
||||||
|
|
||||||
"@babel/helper-function-name@^7.10.4":
|
"@babel/helper-function-name@^7.10.4":
|
||||||
version "7.10.4"
|
version "7.10.4"
|
||||||
@@ -8903,7 +8903,7 @@ source-map-url@^0.4.0:
|
|||||||
resolved "https://registry.yarnpkg.com/source-map-url/-/source-map-url-0.4.0.tgz#3e935d7ddd73631b97659956d55128e87b5084a3"
|
resolved "https://registry.yarnpkg.com/source-map-url/-/source-map-url-0.4.0.tgz#3e935d7ddd73631b97659956d55128e87b5084a3"
|
||||||
integrity sha1-PpNdfd1zYxuXZZlW1VEo6HtQhKM=
|
integrity sha1-PpNdfd1zYxuXZZlW1VEo6HtQhKM=
|
||||||
|
|
||||||
source-map@^0.5.0, source-map@^0.5.6:
|
source-map@^0.5.6:
|
||||||
version "0.5.7"
|
version "0.5.7"
|
||||||
resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.5.7.tgz#8a039d2d1021d22d1ea14c80d8ea468ba2ef3fcc"
|
resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.5.7.tgz#8a039d2d1021d22d1ea14c80d8ea468ba2ef3fcc"
|
||||||
integrity sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=
|
integrity sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=
|
||||||
|
|||||||