mirror of
https://github.com/kubeshark/kubeshark.git
synced 2026-03-06 04:32:12 +00:00
Compare commits
158 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
d34dacbbe2 | ||
|
|
0595df8b87 | ||
|
|
ebbe6458a8 | ||
|
|
7f2021c312 | ||
|
|
824945141a | ||
|
|
0244f12167 | ||
|
|
60533a9591 | ||
|
|
90f0f603c7 | ||
|
|
683d199774 | ||
|
|
fa632b49a7 | ||
|
|
04579eb03c | ||
|
|
dea223bfe1 | ||
|
|
06c8056443 | ||
|
|
d18f1f8316 | ||
|
|
f9202900ee | ||
|
|
9e34662511 | ||
|
|
1e726e381b | ||
|
|
69a9deab4b | ||
|
|
f9396e01ca | ||
|
|
2d5b170406 | ||
|
|
dc59fb6931 | ||
|
|
793bb97e51 | ||
|
|
ceb8d714e3 | ||
|
|
8d8310ee02 | ||
|
|
0824524d62 | ||
|
|
71eff5ea04 | ||
|
|
50e404f51e | ||
|
|
ffa34039b1 | ||
|
|
d888706e1e | ||
|
|
1ef17542dd | ||
|
|
0566f63d72 | ||
|
|
6d49339e29 | ||
|
|
58f0de4d4e | ||
|
|
f175480f65 | ||
|
|
6dd2bf705b | ||
|
|
f64ee23c74 | ||
|
|
803681a239 | ||
|
|
a2150b4a78 | ||
|
|
ac358be877 | ||
|
|
2996c1a4bc | ||
|
|
e42c4f8648 | ||
|
|
7d5ed601df | ||
|
|
30651c0f75 | ||
|
|
594f1b973a | ||
|
|
77ced2a46d | ||
|
|
b195ed9905 | ||
|
|
59ef3a4244 | ||
|
|
360a4ea562 | ||
|
|
e883358cd6 | ||
|
|
efb1a0b58b | ||
|
|
eb67f76e2b | ||
|
|
5b9c134ab2 | ||
|
|
8db12a4b1a | ||
|
|
7fb85df3ac | ||
|
|
edc3d04d59 | ||
|
|
679bf35ce3 | ||
|
|
17e1ccf9ef | ||
|
|
80e97e7f7e | ||
|
|
5c86e20c92 | ||
|
|
4a030c02f7 | ||
|
|
ea5054866d | ||
|
|
a11e8f730e | ||
|
|
1e66ebd8b3 | ||
|
|
3be0c9ecd9 | ||
|
|
ef8314b554 | ||
|
|
b57cb0e615 | ||
|
|
1d24188a02 | ||
|
|
9d9f64098e | ||
|
|
fbf3d1729e | ||
|
|
eb02ecda20 | ||
|
|
dc62195a8f | ||
|
|
38b58dba69 | ||
|
|
765feafbcc | ||
|
|
0a622b5017 | ||
|
|
a0a9d74662 | ||
|
|
5e7ef0fbb9 | ||
|
|
1d6c176c7f | ||
|
|
3b9f5ee32f | ||
|
|
1619df2d5e | ||
|
|
21b91ea6e4 | ||
|
|
79a8ee37f9 | ||
|
|
6e14fa95a1 | ||
|
|
cd34892943 | ||
|
|
62b17c1822 | ||
|
|
cef0e01cf6 | ||
|
|
6e279bfca5 | ||
|
|
4e7bc05ecf | ||
|
|
8886590ea2 | ||
|
|
8400e9e903 | ||
|
|
eaa120cad8 | ||
|
|
35f9e16e7c | ||
|
|
b29b15cf6c | ||
|
|
8fab07494c | ||
|
|
79816ae337 | ||
|
|
728b5b5d1c | ||
|
|
c4048e5c8e | ||
|
|
bd71e9a122 | ||
|
|
4a053734d9 | ||
|
|
5b439d8316 | ||
|
|
400774555a | ||
|
|
7cd6d123d1 | ||
|
|
90c9d8b0d0 | ||
|
|
96f47116f0 | ||
|
|
78456d7987 | ||
|
|
115692dbfc | ||
|
|
f809ed5eeb | ||
|
|
603206f2cb | ||
|
|
6aa38f071f | ||
|
|
d684dee7a4 | ||
|
|
e3049fb5a5 | ||
|
|
ec18d96b45 | ||
|
|
f03df50def | ||
|
|
b2f091746a | ||
|
|
60431b2836 | ||
|
|
77e01da6e8 | ||
|
|
18c1473bd9 | ||
|
|
1638e1be3b | ||
|
|
01a1cd8434 | ||
|
|
466214c4b5 | ||
|
|
de1295e29d | ||
|
|
7de3338752 | ||
|
|
3db6d5a5ea | ||
|
|
65ba0952b4 | ||
|
|
97798cb5b7 | ||
|
|
b1df4b69ae | ||
|
|
b3dcff2cd5 | ||
|
|
09702697ad | ||
|
|
7abf8b83e3 | ||
|
|
12d873d344 | ||
|
|
672accba0c | ||
|
|
566eab3527 | ||
|
|
0f52533cd8 | ||
|
|
eef58496b5 | ||
|
|
1137f9386b | ||
|
|
93714ab902 | ||
|
|
fc03ba2eda | ||
|
|
3662fbcdf6 | ||
|
|
b762e3c194 | ||
|
|
35ef211477 | ||
|
|
feb386ba1f | ||
|
|
ed4a818a53 | ||
|
|
fa733025dc | ||
|
|
5f603e3291 | ||
|
|
b84c698c1a | ||
|
|
c59aadb221 | ||
|
|
6aaee4b519 | ||
|
|
6f47ad862e | ||
|
|
f18f3da99c | ||
|
|
3e32c889d9 | ||
|
|
f604a3a35d | ||
|
|
5d205b5082 | ||
|
|
756f5f5720 | ||
|
|
9a1c17cc61 | ||
|
|
64253cd919 | ||
|
|
accad7c058 | ||
|
|
485bc7fd2b | ||
|
|
bc3efc6d4c | ||
|
|
135b1a5e1e |
19
.github/workflows/publish-cli.yml
vendored
19
.github/workflows/publish-cli.yml
vendored
@@ -1,19 +0,0 @@
|
|||||||
name: public-cli
|
|
||||||
on:
|
|
||||||
push:
|
|
||||||
branches:
|
|
||||||
- 'develop'
|
|
||||||
- 'main'
|
|
||||||
jobs:
|
|
||||||
docker:
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
steps:
|
|
||||||
- name: Checkout
|
|
||||||
uses: actions/checkout@v2
|
|
||||||
- name: Set up Cloud SDK
|
|
||||||
uses: google-github-actions/setup-gcloud@master
|
|
||||||
with:
|
|
||||||
service_account_key: ${{ secrets.GCR_JSON_KEY }}
|
|
||||||
export_default_credentials: true
|
|
||||||
- name: Build and Push CLI
|
|
||||||
run: make push-cli
|
|
||||||
39
.github/workflows/publish-docker.yml
vendored
39
.github/workflows/publish-docker.yml
vendored
@@ -1,39 +0,0 @@
|
|||||||
name: publish-docker
|
|
||||||
on:
|
|
||||||
push:
|
|
||||||
branches:
|
|
||||||
- 'develop'
|
|
||||||
- 'main'
|
|
||||||
jobs:
|
|
||||||
docker:
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
steps:
|
|
||||||
- name: Checkout
|
|
||||||
uses: actions/checkout@v2
|
|
||||||
- name: Get base image name
|
|
||||||
shell: bash
|
|
||||||
run: echo "##[set-output name=image;]$(echo gcr.io/up9-docker-hub/mizu/${GITHUB_REF#refs/heads/})"
|
|
||||||
id: base_image_step
|
|
||||||
- name: Docker meta
|
|
||||||
id: meta
|
|
||||||
uses: crazy-max/ghaction-docker-meta@v2
|
|
||||||
with:
|
|
||||||
images: ${{ steps.base_image_step.outputs.image }}
|
|
||||||
tags: |
|
|
||||||
type=sha
|
|
||||||
type=raw,${{ github.sha }}
|
|
||||||
type=raw,latest
|
|
||||||
- name: Login to DockerHub
|
|
||||||
uses: docker/login-action@v1
|
|
||||||
with:
|
|
||||||
registry: gcr.io
|
|
||||||
username: _json_key
|
|
||||||
password: ${{ secrets.GCR_JSON_KEY }}
|
|
||||||
- name: Build and push
|
|
||||||
uses: docker/build-push-action@v2
|
|
||||||
with:
|
|
||||||
context: .
|
|
||||||
push: true
|
|
||||||
tags: ${{ steps.meta.outputs.tags }}
|
|
||||||
labels: ${{ steps.meta.outputs.labels }}
|
|
||||||
|
|
||||||
81
.github/workflows/publish.yml
vendored
Normal file
81
.github/workflows/publish.yml
vendored
Normal file
@@ -0,0 +1,81 @@
|
|||||||
|
name: publish
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
branches:
|
||||||
|
- 'develop'
|
||||||
|
- 'main'
|
||||||
|
jobs:
|
||||||
|
docker:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- name: Checkout
|
||||||
|
uses: actions/checkout@v2
|
||||||
|
- name: Set up Cloud SDK
|
||||||
|
uses: google-github-actions/setup-gcloud@master
|
||||||
|
with:
|
||||||
|
service_account_key: ${{ secrets.GCR_JSON_KEY }}
|
||||||
|
export_default_credentials: true
|
||||||
|
- uses: haya14busa/action-cond@v1
|
||||||
|
id: condval
|
||||||
|
with:
|
||||||
|
cond: ${{ github.ref == 'refs/heads/main' }}
|
||||||
|
if_true: "minor"
|
||||||
|
if_false: "patch"
|
||||||
|
- name: Auto Increment Semver Action
|
||||||
|
uses: MCKanpolat/auto-semver-action@1.0.5
|
||||||
|
id: versioning
|
||||||
|
with:
|
||||||
|
releaseType: ${{ steps.condval.outputs.value }}
|
||||||
|
github_token: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
- name: Get version parameters
|
||||||
|
shell: bash
|
||||||
|
run: |
|
||||||
|
echo "##[set-output name=build_timestamp;]$(echo $(date +%s))"
|
||||||
|
echo "##[set-output name=branch;]$(echo ${GITHUB_REF#refs/heads/})"
|
||||||
|
id: version_parameters
|
||||||
|
- name: Get base image name
|
||||||
|
shell: bash
|
||||||
|
run: echo "##[set-output name=image;]$(echo gcr.io/up9-docker-hub/mizu/${GITHUB_REF#refs/heads/})"
|
||||||
|
id: base_image_step
|
||||||
|
- name: Docker meta
|
||||||
|
id: meta
|
||||||
|
uses: crazy-max/ghaction-docker-meta@v2
|
||||||
|
with:
|
||||||
|
images: ${{ steps.base_image_step.outputs.image }}
|
||||||
|
tags: |
|
||||||
|
type=sha
|
||||||
|
type=raw,${{ github.sha }}
|
||||||
|
type=raw,${{ steps.versioning.outputs.version }}
|
||||||
|
- name: Login to DockerHub
|
||||||
|
uses: docker/login-action@v1
|
||||||
|
with:
|
||||||
|
registry: gcr.io
|
||||||
|
username: _json_key
|
||||||
|
password: ${{ secrets.GCR_JSON_KEY }}
|
||||||
|
- name: Build and push
|
||||||
|
uses: docker/build-push-action@v2
|
||||||
|
with:
|
||||||
|
context: .
|
||||||
|
push: true
|
||||||
|
tags: ${{ steps.meta.outputs.tags }}
|
||||||
|
labels: ${{ steps.meta.outputs.labels }}
|
||||||
|
build-args: |
|
||||||
|
SEM_VER=${{ steps.versioning.outputs.version }}
|
||||||
|
BUILD_TIMESTAMP=${{ steps.version_parameters.outputs.build_timestamp }}
|
||||||
|
GIT_BRANCH=${{ steps.version_parameters.outputs.branch }}
|
||||||
|
COMMIT_HASH=${{ github.sha }}
|
||||||
|
- name: Build and Push CLI
|
||||||
|
run: make push-cli SEM_VER='${{ steps.versioning.outputs.version }}' BUILD_TIMESTAMP='${{ steps.version_parameters.outputs.build_timestamp }}'
|
||||||
|
- shell: bash
|
||||||
|
run: |
|
||||||
|
echo '${{ steps.versioning.outputs.version }}' >> cli/bin/version.txt
|
||||||
|
- name: publish
|
||||||
|
uses: ncipollo/release-action@v1
|
||||||
|
with:
|
||||||
|
token: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
artifacts: "cli/bin/*"
|
||||||
|
commit: ${{ steps.version_parameters.outputs.branch }}
|
||||||
|
tag: ${{ steps.versioning.outputs.version }}
|
||||||
|
prerelease: ${{ github.ref != 'refs/heads/main' }}
|
||||||
|
bodyFile: 'cli/bin/README.md'
|
||||||
|
|
||||||
30
Dockerfile
30
Dockerfile
@@ -13,19 +13,30 @@ ENV CGO_ENABLED=1 GOOS=linux GOARCH=amd64
|
|||||||
|
|
||||||
RUN apk add libpcap-dev gcc g++ make
|
RUN apk add libpcap-dev gcc g++ make
|
||||||
|
|
||||||
# Move to api working directory (/api-build).
|
# Move to agent working directory (/agent-build).
|
||||||
WORKDIR /app/api-build
|
WORKDIR /app/agent-build
|
||||||
|
|
||||||
COPY api/go.mod api/go.sum ./
|
COPY agent/go.mod agent/go.sum ./
|
||||||
COPY shared/go.mod shared/go.mod ../shared/
|
COPY shared/go.mod shared/go.mod ../shared/
|
||||||
|
COPY tap/go.mod tap/go.mod ../tap/
|
||||||
RUN go mod download
|
RUN go mod download
|
||||||
# cheap trick to make the build faster (As long as go.mod wasn't changes)
|
# cheap trick to make the build faster (As long as go.mod wasn't changes)
|
||||||
RUN go list -f '{{.Path}}@{{.Version}}' -m all | sed 1d | grep -e 'go-cache' -e 'sqlite' | xargs go get
|
RUN go list -f '{{.Path}}@{{.Version}}' -m all | sed 1d | grep -e 'go-cache' -e 'sqlite' | xargs go get
|
||||||
|
|
||||||
# Copy and build api code
|
ARG COMMIT_HASH
|
||||||
|
ARG GIT_BRANCH
|
||||||
|
ARG BUILD_TIMESTAMP
|
||||||
|
ARG SEM_VER
|
||||||
|
|
||||||
|
# Copy and build agent code
|
||||||
COPY shared ../shared
|
COPY shared ../shared
|
||||||
COPY api .
|
COPY tap ../tap
|
||||||
RUN go build -ldflags="-s -w" -o mizuagent .
|
COPY agent .
|
||||||
|
RUN go build -ldflags="-s -w \
|
||||||
|
-X 'mizuserver/pkg/version.GitCommitHash=${COMMIT_HASH}' \
|
||||||
|
-X 'mizuserver/pkg/version.Branch=${GIT_BRANCH}' \
|
||||||
|
-X 'mizuserver/pkg/version.BuildTimestamp=${BUILD_TIMESTAMP}' \
|
||||||
|
-X 'mizuserver/pkg/version.SemVer=${SEM_VER}'" -o mizuagent .
|
||||||
|
|
||||||
|
|
||||||
FROM alpine:3.13.5
|
FROM alpine:3.13.5
|
||||||
@@ -34,10 +45,13 @@ RUN apk add bash libpcap-dev tcpdump
|
|||||||
WORKDIR /app
|
WORKDIR /app
|
||||||
|
|
||||||
# Copy binary and config files from /build to root folder of scratch container.
|
# Copy binary and config files from /build to root folder of scratch container.
|
||||||
COPY --from=builder ["/app/api-build/mizuagent", "."]
|
COPY --from=builder ["/app/agent-build/mizuagent", "."]
|
||||||
COPY --from=site-build ["/app/ui-build/build", "site"]
|
COPY --from=site-build ["/app/ui-build/build", "site"]
|
||||||
|
|
||||||
COPY api/start.sh .
|
COPY agent/start.sh .
|
||||||
|
|
||||||
|
# gin-gonic runs in debug mode without this
|
||||||
|
ENV GIN_MODE=release
|
||||||
|
|
||||||
# this script runs both apiserver and passivetapper and exits either if one of them exits, preventing a scenario where the container runs without one process
|
# this script runs both apiserver and passivetapper and exits either if one of them exits, preventing a scenario where the container runs without one process
|
||||||
ENTRYPOINT "/app/mizuagent"
|
ENTRYPOINT "/app/mizuagent"
|
||||||
|
|||||||
42
Makefile
42
Makefile
@@ -8,7 +8,7 @@ SHELL=/bin/bash
|
|||||||
# HELP
|
# HELP
|
||||||
# This will output the help for each task
|
# This will output the help for each task
|
||||||
# thanks to https://marmelab.com/blog/2016/02/29/auto-documented-makefile.html
|
# thanks to https://marmelab.com/blog/2016/02/29/auto-documented-makefile.html
|
||||||
.PHONY: help ui api cli tap docker
|
.PHONY: help ui agent cli tap docker
|
||||||
|
|
||||||
help: ## This help.
|
help: ## This help.
|
||||||
@awk 'BEGIN {FS = ":.*?## "} /^[a-zA-Z_-]+:.*?## / {printf "\033[36m%-30s\033[0m %s\n", $$1, $$2}' $(MAKEFILE_LIST)
|
@awk 'BEGIN {FS = ":.*?## "} /^[a-zA-Z_-]+:.*?## / {printf "\033[36m%-30s\033[0m %s\n", $$1, $$2}' $(MAKEFILE_LIST)
|
||||||
@@ -19,34 +19,30 @@ help: ## This help.
|
|||||||
TS_SUFFIX="$(shell date '+%s')"
|
TS_SUFFIX="$(shell date '+%s')"
|
||||||
GIT_BRANCH="$(shell git branch | grep \* | cut -d ' ' -f2 | tr '[:upper:]' '[:lower:]' | tr '/' '_')"
|
GIT_BRANCH="$(shell git branch | grep \* | cut -d ' ' -f2 | tr '[:upper:]' '[:lower:]' | tr '/' '_')"
|
||||||
BUCKET_PATH=static.up9.io/mizu/$(GIT_BRANCH)
|
BUCKET_PATH=static.up9.io/mizu/$(GIT_BRANCH)
|
||||||
|
export SEM_VER?=0.0.0
|
||||||
|
|
||||||
ui: ## build UI
|
ui: ## Build UI.
|
||||||
@(cd ui; npm i ; npm run build; )
|
@(cd ui; npm i ; npm run build; )
|
||||||
@ls -l ui/build
|
@ls -l ui/build
|
||||||
|
|
||||||
cli: # build CLI
|
cli: ## Build CLI.
|
||||||
@echo "building cli"; cd cli && $(MAKE) build
|
@echo "building cli"; cd cli && $(MAKE) build
|
||||||
|
|
||||||
api: ## build API server
|
agent: ## Build agent.
|
||||||
@(echo "building API server .." )
|
@(echo "building mizu agent .." )
|
||||||
@(cd api; go build -o build/apiserver main.go)
|
@(cd agent; go build -o build/mizuagent main.go)
|
||||||
@ls -l api/build
|
@ls -l agent/build
|
||||||
|
|
||||||
#tap: ## build tap binary
|
docker: ## Build and publish agent docker image.
|
||||||
# @(cd tap; go build -o build/tap ./src)
|
$(MAKE) push-docker
|
||||||
# @ls -l tap/build
|
|
||||||
|
|
||||||
docker: ## build Docker image
|
push: push-docker push-cli ## Build and publish agent docker image & CLI.
|
||||||
@(echo "building docker image" )
|
|
||||||
./build-push-featurebranch.sh
|
|
||||||
|
|
||||||
push: push-docker push-cli ## build and publish Mizu docker image & CLI
|
push-docker: ## Build and publish agent docker image.
|
||||||
|
|
||||||
push-docker:
|
|
||||||
@echo "publishing Docker image .. "
|
@echo "publishing Docker image .. "
|
||||||
./build-push-featurebranch.sh
|
./build-push-featurebranch.sh
|
||||||
|
|
||||||
push-cli:
|
push-cli: ## Build and publish CLI.
|
||||||
@echo "publishing CLI .. "
|
@echo "publishing CLI .. "
|
||||||
@cd cli; $(MAKE) build-all
|
@cd cli; $(MAKE) build-all
|
||||||
@echo "publishing file ${OUTPUT_FILE} .."
|
@echo "publishing file ${OUTPUT_FILE} .."
|
||||||
@@ -55,17 +51,17 @@ push-cli:
|
|||||||
gsutil setmeta -r -h "Cache-Control:public, max-age=30" gs://${BUCKET_PATH}/\*
|
gsutil setmeta -r -h "Cache-Control:public, max-age=30" gs://${BUCKET_PATH}/\*
|
||||||
|
|
||||||
|
|
||||||
clean: clean-ui clean-api clean-cli clean-docker ## Clean all build artifacts
|
clean: clean-ui clean-agent clean-cli clean-docker ## Clean all build artifacts.
|
||||||
|
|
||||||
clean-ui:
|
clean-ui: ## Clean UI.
|
||||||
@(rm -rf ui/build ; echo "UI cleanup done" )
|
@(rm -rf ui/build ; echo "UI cleanup done" )
|
||||||
|
|
||||||
clean-api:
|
clean-agent: ## Clean agent.
|
||||||
@(rm -rf api/build ; echo "api cleanup done" )
|
@(rm -rf agent/build ; echo "agent cleanup done" )
|
||||||
|
|
||||||
clean-cli:
|
clean-cli: ## Clean CLI.
|
||||||
@(cd cli; make clean ; echo "CLI cleanup done" )
|
@(cd cli; make clean ; echo "CLI cleanup done" )
|
||||||
|
|
||||||
clean-docker:
|
clean-docker:
|
||||||
@(echo "DOCKER cleanup - NOT IMPLEMENTED YET " )
|
@(echo "DOCKER cleanup - NOT IMPLEMENTED YET " )
|
||||||
|
|
||||||
|
|||||||
406
README.md
406
README.md
@@ -1,18 +1,404 @@
|
|||||||
# 水 mizu
|

|
||||||
standalone web app traffic viewer for Kubernetes
|
# The API Traffic Viewer for Kubernetes
|
||||||
|
|
||||||
|
A simple-yet-powerful API traffic viewer for Kubernetes to help you troubleshoot and debug your microservices. Think TCPDump and Chrome Dev Tools combined.
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
## Features
|
||||||
|
|
||||||
|
- Simple and powerful CLI
|
||||||
|
- Real time view of all HTTP requests, REST and gRPC API calls
|
||||||
|
- No installation or code instrumentation
|
||||||
|
- Works completely on premises (on-prem)
|
||||||
|
|
||||||
## Download
|
## Download
|
||||||
|
|
||||||
Download `mizu` for your platform as
|
Download Mizu for your platform and operating system
|
||||||
|
|
||||||
* for MacOS - `curl -o mizu https://static.up9.com/mizu/mizu-darwin-amd64 && chmod 755 mizu`
|
### Latest Stable Release
|
||||||
* for Linux - `curl -o mizu https://static.up9.com/mizu/mizu-linux-amd64 && chmod 755 mizu`
|
|
||||||
|
|
||||||
## Run
|
* for MacOS - Intel
|
||||||
|
```
|
||||||
|
curl -Lo mizu \
|
||||||
|
https://github.com/up9inc/mizu/releases/latest/download/mizu_darwin_amd64 \
|
||||||
|
&& chmod 755 mizu
|
||||||
|
```
|
||||||
|
|
||||||
|
* for Linux - Intel 64bit
|
||||||
|
```
|
||||||
|
curl -Lo mizu \
|
||||||
|
https://github.com/up9inc/mizu/releases/latest/download/mizu_linux_amd64 \
|
||||||
|
&& chmod 755 mizu
|
||||||
|
```
|
||||||
|
|
||||||
|
SHA256 checksums are available on the [Releases](https://github.com/up9inc/mizu/releases) page.
|
||||||
|
|
||||||
|
### Development (unstable) Build
|
||||||
|
Pick one from the [Releases](https://github.com/up9inc/mizu/releases) page.
|
||||||
|
|
||||||
|
## Prerequisites
|
||||||
|
1. Set `KUBECONFIG` environment variable to your Kubernetes configuration. If this is not set, Mizu assumes that configuration is at `${HOME}/.kube/config`
|
||||||
|
2. Mizu needs following permissions on your Kubernetes cluster to run
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
- apiGroups:
|
||||||
|
- ""
|
||||||
|
resources:
|
||||||
|
- pods
|
||||||
|
verbs:
|
||||||
|
- list
|
||||||
|
- watch
|
||||||
|
- create
|
||||||
|
- delete
|
||||||
|
- apiGroups:
|
||||||
|
- ""
|
||||||
|
resources:
|
||||||
|
- services
|
||||||
|
verbs:
|
||||||
|
- create
|
||||||
|
- delete
|
||||||
|
- apiGroups:
|
||||||
|
- apps
|
||||||
|
resources:
|
||||||
|
- daemonsets
|
||||||
|
verbs:
|
||||||
|
- create
|
||||||
|
- patch
|
||||||
|
- delete
|
||||||
|
- apiGroups:
|
||||||
|
- ""
|
||||||
|
resources:
|
||||||
|
- namespaces
|
||||||
|
verbs:
|
||||||
|
- get
|
||||||
|
- list
|
||||||
|
- watch
|
||||||
|
- create
|
||||||
|
- delete
|
||||||
|
- apiGroups:
|
||||||
|
- ""
|
||||||
|
resources:
|
||||||
|
- services/proxy
|
||||||
|
verbs:
|
||||||
|
- get
|
||||||
|
```
|
||||||
|
|
||||||
|
3. Optionally, for resolving traffic IP to Kubernetes service name, Mizu needs below permissions
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
- apiGroups:
|
||||||
|
- ""
|
||||||
|
resources:
|
||||||
|
- pods
|
||||||
|
verbs:
|
||||||
|
- get
|
||||||
|
- list
|
||||||
|
- watch
|
||||||
|
- create
|
||||||
|
- delete
|
||||||
|
- apiGroups:
|
||||||
|
- ""
|
||||||
|
resources:
|
||||||
|
- services
|
||||||
|
verbs:
|
||||||
|
- get
|
||||||
|
- list
|
||||||
|
- watch
|
||||||
|
- create
|
||||||
|
- delete
|
||||||
|
- apiGroups:
|
||||||
|
- apps
|
||||||
|
resources:
|
||||||
|
- daemonsets
|
||||||
|
verbs:
|
||||||
|
- create
|
||||||
|
- patch
|
||||||
|
- delete
|
||||||
|
- apiGroups:
|
||||||
|
- ""
|
||||||
|
resources:
|
||||||
|
- namespaces
|
||||||
|
verbs:
|
||||||
|
- get
|
||||||
|
- list
|
||||||
|
- watch
|
||||||
|
- create
|
||||||
|
- delete
|
||||||
|
- apiGroups:
|
||||||
|
- ""
|
||||||
|
resources:
|
||||||
|
- services/proxy
|
||||||
|
verbs:
|
||||||
|
- get
|
||||||
|
- apiGroups:
|
||||||
|
- ""
|
||||||
|
resources:
|
||||||
|
- serviceaccounts
|
||||||
|
verbs:
|
||||||
|
- get
|
||||||
|
- create
|
||||||
|
- delete
|
||||||
|
- apiGroups:
|
||||||
|
- rbac.authorization.k8s.io
|
||||||
|
resources:
|
||||||
|
- clusterroles
|
||||||
|
verbs:
|
||||||
|
- get
|
||||||
|
- create
|
||||||
|
- delete
|
||||||
|
- apiGroups:
|
||||||
|
- rbac.authorization.k8s.io
|
||||||
|
resources:
|
||||||
|
- clusterrolebindings
|
||||||
|
verbs:
|
||||||
|
- get
|
||||||
|
- create
|
||||||
|
- delete
|
||||||
|
- apiGroups:
|
||||||
|
- rbac.authorization.k8s.io
|
||||||
|
resources:
|
||||||
|
- roles
|
||||||
|
verbs:
|
||||||
|
- get
|
||||||
|
- create
|
||||||
|
- delete
|
||||||
|
- apiGroups:
|
||||||
|
- rbac.authorization.k8s.io
|
||||||
|
resources:
|
||||||
|
- rolebindings
|
||||||
|
verbs:
|
||||||
|
- get
|
||||||
|
- create
|
||||||
|
- delete
|
||||||
|
- apiGroups:
|
||||||
|
- apps
|
||||||
|
- extensions
|
||||||
|
resources:
|
||||||
|
- pods
|
||||||
|
verbs:
|
||||||
|
- get
|
||||||
|
- list
|
||||||
|
- watch
|
||||||
|
- apiGroups:
|
||||||
|
- apps
|
||||||
|
- extensions
|
||||||
|
resources:
|
||||||
|
- services
|
||||||
|
verbs:
|
||||||
|
- get
|
||||||
|
- list
|
||||||
|
- watch
|
||||||
|
- apiGroups:
|
||||||
|
- ""
|
||||||
|
- apps
|
||||||
|
- extensions
|
||||||
|
resources:
|
||||||
|
- endpoints
|
||||||
|
verbs:
|
||||||
|
- get
|
||||||
|
- list
|
||||||
|
- watch
|
||||||
|
```
|
||||||
|
|
||||||
|
4. Optionally, in order to use the policy rules validation feature, Mizu requires the following additional permissions:
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
- apiGroups:
|
||||||
|
- ""
|
||||||
|
resources:
|
||||||
|
- configmaps
|
||||||
|
verbs:
|
||||||
|
- get
|
||||||
|
- create
|
||||||
|
- delete
|
||||||
|
```
|
||||||
|
|
||||||
|
5. Alternatively, in order to restrict Mizu to one namespace only (by setting `agent.namespace` in the config file), Mizu needs the following permissions in that namespace:
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
- apiGroups:
|
||||||
|
- ""
|
||||||
|
resources:
|
||||||
|
- pods
|
||||||
|
verbs:
|
||||||
|
- get
|
||||||
|
- list
|
||||||
|
- watch
|
||||||
|
- create
|
||||||
|
- delete
|
||||||
|
- apiGroups:
|
||||||
|
- ""
|
||||||
|
resources:
|
||||||
|
- services
|
||||||
|
verbs:
|
||||||
|
- get
|
||||||
|
- create
|
||||||
|
- delete
|
||||||
|
- apiGroups:
|
||||||
|
- apps
|
||||||
|
resources:
|
||||||
|
- daemonsets
|
||||||
|
verbs:
|
||||||
|
- get
|
||||||
|
- create
|
||||||
|
- patch
|
||||||
|
- delete
|
||||||
|
- apiGroups:
|
||||||
|
- ""
|
||||||
|
resources:
|
||||||
|
- services/proxy
|
||||||
|
verbs:
|
||||||
|
- get
|
||||||
|
```
|
||||||
|
|
||||||
|
6. To restrict Mizu to one namespace while also resolving IPs, Mizu needs the following permissions in that namespace:
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
- apiGroups:
|
||||||
|
- ""
|
||||||
|
resources:
|
||||||
|
- pods
|
||||||
|
verbs:
|
||||||
|
- get
|
||||||
|
- list
|
||||||
|
- watch
|
||||||
|
- create
|
||||||
|
- delete
|
||||||
|
- apiGroups:
|
||||||
|
- ""
|
||||||
|
resources:
|
||||||
|
- services
|
||||||
|
verbs:
|
||||||
|
- get
|
||||||
|
- list
|
||||||
|
- watch
|
||||||
|
- create
|
||||||
|
- delete
|
||||||
|
- apiGroups:
|
||||||
|
- apps
|
||||||
|
resources:
|
||||||
|
- daemonsets
|
||||||
|
verbs:
|
||||||
|
- get
|
||||||
|
- create
|
||||||
|
- patch
|
||||||
|
- delete
|
||||||
|
- apiGroups:
|
||||||
|
- ""
|
||||||
|
resources:
|
||||||
|
- services/proxy
|
||||||
|
verbs:
|
||||||
|
- get
|
||||||
|
- apiGroups:
|
||||||
|
- ""
|
||||||
|
resources:
|
||||||
|
- serviceaccounts
|
||||||
|
verbs:
|
||||||
|
- get
|
||||||
|
- create
|
||||||
|
- delete
|
||||||
|
- apiGroups:
|
||||||
|
- rbac.authorization.k8s.io
|
||||||
|
resources:
|
||||||
|
- roles
|
||||||
|
verbs:
|
||||||
|
- get
|
||||||
|
- create
|
||||||
|
- delete
|
||||||
|
- apiGroups:
|
||||||
|
- rbac.authorization.k8s.io
|
||||||
|
resources:
|
||||||
|
- rolebindings
|
||||||
|
verbs:
|
||||||
|
- get
|
||||||
|
- create
|
||||||
|
- delete
|
||||||
|
- apiGroups:
|
||||||
|
- apps
|
||||||
|
- extensions
|
||||||
|
resources:
|
||||||
|
- pods
|
||||||
|
verbs:
|
||||||
|
- get
|
||||||
|
- list
|
||||||
|
- watch
|
||||||
|
- apiGroups:
|
||||||
|
- apps
|
||||||
|
- extensions
|
||||||
|
resources:
|
||||||
|
- services
|
||||||
|
verbs:
|
||||||
|
- get
|
||||||
|
- list
|
||||||
|
- watch
|
||||||
|
- apiGroups:
|
||||||
|
- ""
|
||||||
|
- apps
|
||||||
|
- extensions
|
||||||
|
resources:
|
||||||
|
- endpoints
|
||||||
|
verbs:
|
||||||
|
- get
|
||||||
|
- list
|
||||||
|
- watch
|
||||||
|
```
|
||||||
|
|
||||||
|
See `examples/roles` for example `clusterroles`.
|
||||||
|
|
||||||
|
## How to Run
|
||||||
|
|
||||||
|
1. Find pods you'd like to tap to in your Kubernetes cluster
|
||||||
|
2. Run `mizu tap PODNAME` or `mizu tap REGEX`
|
||||||
|
3. Open browser on `http://localhost:8899/mizu` **or** as instructed in the CLI ..
|
||||||
|
4. Watch the API traffic flowing ..
|
||||||
|
5. Type ^C to stop
|
||||||
|
|
||||||
|
## Examples
|
||||||
|
|
||||||
|
Run `mizu help` for usage options
|
||||||
|
|
||||||
|
|
||||||
|
To tap specific pod -
|
||||||
|
```
|
||||||
|
$ kubectl get pods
|
||||||
|
NAME READY STATUS RESTARTS AGE
|
||||||
|
front-end-649fc5fd6-kqbtn 2/2 Running 0 7m
|
||||||
|
..
|
||||||
|
|
||||||
1. Find pod you'd like to tap to in your Kubernetes cluster
|
$ mizu tap front-end-649fc5fd6-kqbtn
|
||||||
2. Run `mizu --pod podname`
|
+front-end-649fc5fd6-kqbtn
|
||||||
3. Open browser on `http://localhost:8899` as instructed ..
|
Web interface is now available at http://localhost:8899
|
||||||
4. Watch the WebAPI traffic flowing ..
|
^C
|
||||||
|
```
|
||||||
|
|
||||||
|
To tap multiple pods using regex -
|
||||||
|
```
|
||||||
|
$ kubectl get pods
|
||||||
|
NAME READY STATUS RESTARTS AGE
|
||||||
|
carts-66c77f5fbb-fq65r 2/2 Running 0 20m
|
||||||
|
catalogue-5f4cb7cf5-7zrmn 2/2 Running 0 20m
|
||||||
|
front-end-649fc5fd6-kqbtn 2/2 Running 0 20m
|
||||||
|
..
|
||||||
|
|
||||||
|
$ mizu tap "^ca.*"
|
||||||
|
+carts-66c77f5fbb-fq65r
|
||||||
|
+catalogue-5f4cb7cf5-7zrmn
|
||||||
|
Web interface is now available at http://localhost:8899
|
||||||
|
^C
|
||||||
|
```
|
||||||
|
|
||||||
|
## Advanced Usage
|
||||||
|
|
||||||
|
### Namespace-Restricted Mode
|
||||||
|
|
||||||
|
Some users have permission to only manage resources in one particular namespace assigned to them.
|
||||||
|
By default `mizu tap` creates a new namespace `mizu` for all of its Kubernetes resources. In order to instead install
|
||||||
|
Mizu in an existing namespace, set the `mizu-resources-namespace` config option.
|
||||||
|
|
||||||
|
If `mizu-resources-namespace` is set to a value other than the default `mizu`, Mizu will operate in a
|
||||||
|
Namespace-Restricted mode. It will only tap pods in `mizu-resources-namespace`. This way Mizu only requires permissions
|
||||||
|
to the namespace set by `mizu-resources-namespace`. The user must set the tapped namespace to the same namespace by
|
||||||
|
using the `--namespace` flag or by setting `tap.namespaces` in the config file.
|
||||||
|
|
||||||
|
Setting `mizu-resources-namespace=mizu` resets Mizu to its default behavior.
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
# mizu API server
|
# mizu agent
|
||||||
API server for MIZU
|
Agent for MIZU (API server and tapper)
|
||||||
Basic APIs:
|
Basic APIs:
|
||||||
* /fetch - retrieve traffic data
|
* /fetch - retrieve traffic data
|
||||||
* /stats - retrieve statistics of collected data
|
* /stats - retrieve statistics of collected data
|
||||||
@@ -14,7 +14,7 @@ Basic APIs:
|
|||||||
|
|
||||||
### Connecting
|
### Connecting
|
||||||
1. Start mizu using the cli with the debug image `mizu tap --mizu-image gcr.io/up9-docker-hub/mizu/debug:latest {tapped_pod_name}`
|
1. Start mizu using the cli with the debug image `mizu tap --mizu-image gcr.io/up9-docker-hub/mizu/debug:latest {tapped_pod_name}`
|
||||||
2. Forward the debug port using `kubectl port-forward -n default mizu-collector 2345:2345`
|
2. Forward the debug port using `kubectl port-forward -n default mizu-api-server 2345:2345`
|
||||||
3. Run the run/debug configuration you've created earlier in Intellij.
|
3. Run the run/debug configuration you've created earlier in Intellij.
|
||||||
|
|
||||||
<small>Do note that dlv won't start the api until a debugger connects to it.</small>
|
<small>Do note that dlv won't start the api until a debugger connects to it.</small>
|
||||||
@@ -3,23 +3,23 @@ module mizuserver
|
|||||||
go 1.16
|
go 1.16
|
||||||
|
|
||||||
require (
|
require (
|
||||||
github.com/antoniodipinto/ikisocket v0.0.0-20210417133349-f1502512d69a
|
|
||||||
github.com/beevik/etree v1.1.0
|
github.com/beevik/etree v1.1.0
|
||||||
github.com/djherbis/atime v1.0.0
|
github.com/djherbis/atime v1.0.0
|
||||||
github.com/fasthttp/websocket v1.4.3-beta.1 // indirect
|
github.com/fsnotify/fsnotify v1.4.9
|
||||||
|
github.com/gin-contrib/static v0.0.1
|
||||||
|
github.com/gin-gonic/gin v1.7.2
|
||||||
github.com/go-playground/locales v0.13.0
|
github.com/go-playground/locales v0.13.0
|
||||||
github.com/go-playground/universal-translator v0.17.0
|
github.com/go-playground/universal-translator v0.17.0
|
||||||
github.com/go-playground/validator/v10 v10.5.0
|
github.com/go-playground/validator/v10 v10.5.0
|
||||||
github.com/gofiber/fiber/v2 v2.8.0
|
|
||||||
github.com/google/gopacket v1.1.19
|
|
||||||
github.com/google/martian v2.1.0+incompatible
|
github.com/google/martian v2.1.0+incompatible
|
||||||
github.com/gorilla/websocket v1.4.2
|
github.com/gorilla/websocket v1.4.2
|
||||||
github.com/leodido/go-urn v1.2.1 // indirect
|
|
||||||
github.com/orcaman/concurrent-map v0.0.0-20210106121528-16402b402231
|
github.com/orcaman/concurrent-map v0.0.0-20210106121528-16402b402231
|
||||||
github.com/patrickmn/go-cache v2.1.0+incompatible
|
github.com/patrickmn/go-cache v2.1.0+incompatible
|
||||||
|
github.com/romana/rlog v0.0.0-20171115192701-f018bc92e7d7
|
||||||
github.com/up9inc/mizu/shared v0.0.0
|
github.com/up9inc/mizu/shared v0.0.0
|
||||||
|
github.com/up9inc/mizu/tap v0.0.0
|
||||||
|
github.com/yalp/jsonpath v0.0.0-20180802001716-5cc68e5049a0
|
||||||
go.mongodb.org/mongo-driver v1.5.1
|
go.mongodb.org/mongo-driver v1.5.1
|
||||||
golang.org/x/net v0.0.0-20210421230115-4e50805a0758
|
|
||||||
gorm.io/driver/sqlite v1.1.4
|
gorm.io/driver/sqlite v1.1.4
|
||||||
gorm.io/gorm v1.21.8
|
gorm.io/gorm v1.21.8
|
||||||
k8s.io/api v0.21.0
|
k8s.io/api v0.21.0
|
||||||
@@ -28,3 +28,5 @@ require (
|
|||||||
)
|
)
|
||||||
|
|
||||||
replace github.com/up9inc/mizu/shared v0.0.0 => ../shared
|
replace github.com/up9inc/mizu/shared v0.0.0 => ../shared
|
||||||
|
|
||||||
|
replace github.com/up9inc/mizu/tap v0.0.0 => ../tap
|
||||||
@@ -41,15 +41,12 @@ github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym
|
|||||||
github.com/NYTimes/gziphandler v0.0.0-20170623195520-56545f4a5d46/go.mod h1:3wb06e3pkSAbeQ52E9H9iFoQsEEwGN64994WTCIhntQ=
|
github.com/NYTimes/gziphandler v0.0.0-20170623195520-56545f4a5d46/go.mod h1:3wb06e3pkSAbeQ52E9H9iFoQsEEwGN64994WTCIhntQ=
|
||||||
github.com/PuerkitoBio/purell v1.1.1/go.mod h1:c11w/QuzBsJSee3cPx9rAFu61PvFxuPbtSwDGJws/X0=
|
github.com/PuerkitoBio/purell v1.1.1/go.mod h1:c11w/QuzBsJSee3cPx9rAFu61PvFxuPbtSwDGJws/X0=
|
||||||
github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578/go.mod h1:uGdkoq3SwY9Y+13GIhn11/XLaGBb4BfwItxLd5jeuXE=
|
github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578/go.mod h1:uGdkoq3SwY9Y+13GIhn11/XLaGBb4BfwItxLd5jeuXE=
|
||||||
github.com/andybalholm/brotli v1.0.0/go.mod h1:loMXtMfwqflxFJPmdbJO0a3KNoPuLBgiu3qAvBg8x/Y=
|
|
||||||
github.com/andybalholm/brotli v1.0.1 h1:KqhlKozYbRtJvsPrrEeXcO+N2l6NYT5A2QAFmSULpEc=
|
|
||||||
github.com/andybalholm/brotli v1.0.1/go.mod h1:loMXtMfwqflxFJPmdbJO0a3KNoPuLBgiu3qAvBg8x/Y=
|
|
||||||
github.com/antoniodipinto/ikisocket v0.0.0-20210417133349-f1502512d69a h1:76llBleIE3fkdqaJFDzdirtiYhQPdIQem8H8r2iwA1Q=
|
|
||||||
github.com/antoniodipinto/ikisocket v0.0.0-20210417133349-f1502512d69a/go.mod h1:QvDfsDQDmGxUsvEeWabVZ5pp2FMXpOkwQV0L6SE6cp0=
|
|
||||||
github.com/asaskevich/govalidator v0.0.0-20190424111038-f61b66f89f4a/go.mod h1:lB+ZfQJz7igIIfQNfa7Ml4HSf2uFQQRzpGGRXenZAgY=
|
github.com/asaskevich/govalidator v0.0.0-20190424111038-f61b66f89f4a/go.mod h1:lB+ZfQJz7igIIfQNfa7Ml4HSf2uFQQRzpGGRXenZAgY=
|
||||||
github.com/aws/aws-sdk-go v1.34.28/go.mod h1:H7NKnBqNVzoTJpGfLrQkkD+ytBA93eiDYi/+8rV9s48=
|
github.com/aws/aws-sdk-go v1.34.28/go.mod h1:H7NKnBqNVzoTJpGfLrQkkD+ytBA93eiDYi/+8rV9s48=
|
||||||
github.com/beevik/etree v1.1.0 h1:T0xke/WvNtMoCqgzPhkX2r4rjY3GDZFi+FjpRZY2Jbs=
|
github.com/beevik/etree v1.1.0 h1:T0xke/WvNtMoCqgzPhkX2r4rjY3GDZFi+FjpRZY2Jbs=
|
||||||
github.com/beevik/etree v1.1.0/go.mod h1:r8Aw8JqVegEf0w2fDnATrX9VpkMcyFeM0FhwO62wh+A=
|
github.com/beevik/etree v1.1.0/go.mod h1:r8Aw8JqVegEf0w2fDnATrX9VpkMcyFeM0FhwO62wh+A=
|
||||||
|
github.com/bradleyfalzon/tlsx v0.0.0-20170624122154-28fd0e59bac4 h1:NJOOlc6ZJjix0A1rAU+nxruZtR8KboG1848yqpIUo4M=
|
||||||
|
github.com/bradleyfalzon/tlsx v0.0.0-20170624122154-28fd0e59bac4/go.mod h1:DQPxZS994Ld1Y8uwnJT+dRL04XPD0cElP/pHH/zEBHM=
|
||||||
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/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI=
|
github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI=
|
||||||
github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI=
|
github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI=
|
||||||
@@ -61,18 +58,26 @@ github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c
|
|||||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||||
github.com/djherbis/atime v1.0.0 h1:ySLvBAM0EvOGaX7TI4dAM5lWj+RdJUCKtGSEHN8SGBg=
|
github.com/djherbis/atime v1.0.0 h1:ySLvBAM0EvOGaX7TI4dAM5lWj+RdJUCKtGSEHN8SGBg=
|
||||||
github.com/djherbis/atime v1.0.0/go.mod h1:5W+KBIuTwVGcqjIfaTwt+KSYX1o6uep8dtevevQP/f8=
|
github.com/djherbis/atime v1.0.0/go.mod h1:5W+KBIuTwVGcqjIfaTwt+KSYX1o6uep8dtevevQP/f8=
|
||||||
|
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/docopt/docopt-go v0.0.0-20180111231733-ee0de3bc6815/go.mod h1:WwZ+bS3ebgob9U8Nd0kOddGdZWjyMGR8Wziv+TBNwSE=
|
github.com/docopt/docopt-go v0.0.0-20180111231733-ee0de3bc6815/go.mod h1:WwZ+bS3ebgob9U8Nd0kOddGdZWjyMGR8Wziv+TBNwSE=
|
||||||
github.com/elazarl/goproxy v0.0.0-20180725130230-947c36da3153/go.mod h1:/Zj4wYkgs4iZTTu3o/KG3Itv/qCCa8VVMlb3i9OVuzc=
|
github.com/elazarl/goproxy v0.0.0-20180725130230-947c36da3153/go.mod h1:/Zj4wYkgs4iZTTu3o/KG3Itv/qCCa8VVMlb3i9OVuzc=
|
||||||
github.com/emicklei/go-restful v0.0.0-20170410110728-ff4f55a20633/go.mod h1:otzb+WCGbkyDHkqmQmT5YD2WR4BBwUdeQoFo8l/7tVs=
|
github.com/emicklei/go-restful v0.0.0-20170410110728-ff4f55a20633/go.mod h1:otzb+WCGbkyDHkqmQmT5YD2WR4BBwUdeQoFo8l/7tVs=
|
||||||
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/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/evanphx/json-patch v4.9.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk=
|
github.com/evanphx/json-patch v4.9.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk=
|
||||||
github.com/fasthttp/websocket v1.4.2/go.mod h1:smsv/h4PBEBaU0XDTY5UwJTpZv69fQ0FfcLJr21mA6Y=
|
|
||||||
github.com/fasthttp/websocket v1.4.3-beta.1 h1:stc4P2aoxYKsdmbe1AJ5mAm73Fxc1NOgrZpPftvZIXQ=
|
|
||||||
github.com/fasthttp/websocket v1.4.3-beta.1/go.mod h1:JGrgLaT02bL9NuJkZbHN8mVV2tkCJZQh7yJ5/XCXO2g=
|
|
||||||
github.com/form3tech-oss/jwt-go v3.2.2+incompatible h1:TcekIExNqud5crz4xD2pavyTgWiPvpYe4Xau31I0PRk=
|
github.com/form3tech-oss/jwt-go v3.2.2+incompatible h1:TcekIExNqud5crz4xD2pavyTgWiPvpYe4Xau31I0PRk=
|
||||||
github.com/form3tech-oss/jwt-go v3.2.2+incompatible/go.mod h1:pbq4aXjuKjdthFRnoDwaVPLA+WlJuPGy+QneDUgJi2k=
|
github.com/form3tech-oss/jwt-go v3.2.2+incompatible/go.mod h1:pbq4aXjuKjdthFRnoDwaVPLA+WlJuPGy+QneDUgJi2k=
|
||||||
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/gin-contrib/sse v0.1.0 h1:Y/yl/+YNO8GZSjAhjMsSuLt29uWRFHdHYUb5lYOV9qE=
|
||||||
|
github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI=
|
||||||
|
github.com/gin-contrib/static v0.0.1 h1:JVxuvHPuUfkoul12N7dtQw7KRn/pSMq7Ue1Va9Swm1U=
|
||||||
|
github.com/gin-contrib/static v0.0.1/go.mod h1:CSxeF+wep05e0kCOsqWdAWbSszmc31zTIbD8TvWl7Hs=
|
||||||
|
github.com/gin-gonic/gin v1.6.3/go.mod h1:75u5sXoLsGZoRN5Sgbi1eraJ4GU3++wFwWzhwvtwp4M=
|
||||||
|
github.com/gin-gonic/gin v1.7.2 h1:Tg03T9yM2xa8j6I3Z3oqLaQRSmKvxPd6g/2HJ6zICFA=
|
||||||
|
github.com/gin-gonic/gin v1.7.2/go.mod h1:jD2toBW3GZUr5UMcdrwQA10I7RuaFOl/SGeDjXkfUtY=
|
||||||
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=
|
||||||
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
|
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
|
||||||
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
|
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
|
||||||
@@ -92,6 +97,8 @@ github.com/go-playground/locales v0.13.0 h1:HyWk6mgj5qFqCT5fjGBuRArbVDfE4hi8+e8c
|
|||||||
github.com/go-playground/locales v0.13.0/go.mod h1:taPMhCMXrRLJO55olJkUXHZBHCxTMfnGwq/HNwmWNS8=
|
github.com/go-playground/locales v0.13.0/go.mod h1:taPMhCMXrRLJO55olJkUXHZBHCxTMfnGwq/HNwmWNS8=
|
||||||
github.com/go-playground/universal-translator v0.17.0 h1:icxd5fm+REJzpZx7ZfpaD876Lmtgy7VtROAbHHXk8no=
|
github.com/go-playground/universal-translator v0.17.0 h1:icxd5fm+REJzpZx7ZfpaD876Lmtgy7VtROAbHHXk8no=
|
||||||
github.com/go-playground/universal-translator v0.17.0/go.mod h1:UkSxE5sNxxRwHyU+Scu5vgOQjsIJAF8j9muTVoKLVtA=
|
github.com/go-playground/universal-translator v0.17.0/go.mod h1:UkSxE5sNxxRwHyU+Scu5vgOQjsIJAF8j9muTVoKLVtA=
|
||||||
|
github.com/go-playground/validator/v10 v10.2.0/go.mod h1:uOYAAleCW8F/7oMFd6aG0GOhaH6EGOAJShg8Id5JGkI=
|
||||||
|
github.com/go-playground/validator/v10 v10.4.1/go.mod h1:nlOn6nFhuKACm19sB/8EGNn9GlaMV7XkbRSipzJ0Ii4=
|
||||||
github.com/go-playground/validator/v10 v10.5.0 h1:X9rflw/KmpACwT8zdrm1upefpvdy6ur8d1kWyq6sg3E=
|
github.com/go-playground/validator/v10 v10.5.0 h1:X9rflw/KmpACwT8zdrm1upefpvdy6ur8d1kWyq6sg3E=
|
||||||
github.com/go-playground/validator/v10 v10.5.0/go.mod h1:xm76BBt941f7yWdGnI2DVPFFg1UK3YY04qifoXU3lOk=
|
github.com/go-playground/validator/v10 v10.5.0/go.mod h1:xm76BBt941f7yWdGnI2DVPFFg1UK3YY04qifoXU3lOk=
|
||||||
github.com/go-sql-driver/mysql v1.5.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg=
|
github.com/go-sql-driver/mysql v1.5.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg=
|
||||||
@@ -120,12 +127,6 @@ github.com/gobuffalo/packd v0.1.0/go.mod h1:M2Juc+hhDXf/PnmBANFCqx4DM3wRbgDvnVWe
|
|||||||
github.com/gobuffalo/packr/v2 v2.0.9/go.mod h1:emmyGweYTm6Kdper+iywB6YK5YzuKchGtJQZ0Odn4pQ=
|
github.com/gobuffalo/packr/v2 v2.0.9/go.mod h1:emmyGweYTm6Kdper+iywB6YK5YzuKchGtJQZ0Odn4pQ=
|
||||||
github.com/gobuffalo/packr/v2 v2.2.0/go.mod h1:CaAwI0GPIAv+5wKLtv8Afwl+Cm78K/I/VCm/3ptBN+0=
|
github.com/gobuffalo/packr/v2 v2.2.0/go.mod h1:CaAwI0GPIAv+5wKLtv8Afwl+Cm78K/I/VCm/3ptBN+0=
|
||||||
github.com/gobuffalo/syncx v0.0.0-20190224160051-33c29581e754/go.mod h1:HhnNqWY95UYwwW3uSASeV7vtgYkT2t16hJgV3AEPUpw=
|
github.com/gobuffalo/syncx v0.0.0-20190224160051-33c29581e754/go.mod h1:HhnNqWY95UYwwW3uSASeV7vtgYkT2t16hJgV3AEPUpw=
|
||||||
github.com/gofiber/fiber/v2 v2.1.3/go.mod h1:MMiSv1HrDkN8Pv7NeVDYK+T/lwXOEKAvPBbLvJPCEfA=
|
|
||||||
github.com/gofiber/fiber/v2 v2.7.1/go.mod h1:f8BRRIMjMdRyt2qmJ/0Sea3j3rwwfufPrh9WNBRiVZ0=
|
|
||||||
github.com/gofiber/fiber/v2 v2.8.0 h1:BdWvZmg/WY/Vjtjm38aXOp1Lks1BhuyS2b7lSWSPAzk=
|
|
||||||
github.com/gofiber/fiber/v2 v2.8.0/go.mod h1:Ah3IJikrKNRepl/HuVawppS25X7FWohwfCSRn7kJG28=
|
|
||||||
github.com/gofiber/websocket/v2 v2.0.3 h1:nqPGHB4LQhxKX5KJUjayOd2xiiENieS/dn6TPfCL8uk=
|
|
||||||
github.com/gofiber/websocket/v2 v2.0.3/go.mod h1:/OTEImCxORKE5unw0dWqJYovid6vZF+wB1W0aaMKs2M=
|
|
||||||
github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q=
|
github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q=
|
||||||
github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q=
|
github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q=
|
||||||
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
|
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
|
||||||
@@ -186,7 +187,6 @@ github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ
|
|||||||
github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
|
github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
|
||||||
github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=
|
github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=
|
||||||
github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc=
|
github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc=
|
||||||
github.com/imdario/mergo v0.3.5 h1:JboBksRwiiAJWvIYJVo46AfV+IAIKZpfrSzVKj42R4Q=
|
|
||||||
github.com/imdario/mergo v0.3.5/go.mod h1:2EnlNZ0deacrJVfApfmtdGgDfMuh/nq6Ok1EcJh5FfA=
|
github.com/imdario/mergo v0.3.5/go.mod h1:2EnlNZ0deacrJVfApfmtdGgDfMuh/nq6Ok1EcJh5FfA=
|
||||||
github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8=
|
github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8=
|
||||||
github.com/jinzhu/inflection v1.0.0 h1:K317FqzuhWc8YvSVlFMCCUb36O/S9MCKRDI7QkRKD/E=
|
github.com/jinzhu/inflection v1.0.0 h1:K317FqzuhWc8YvSVlFMCCUb36O/S9MCKRDI7QkRKD/E=
|
||||||
@@ -198,6 +198,7 @@ github.com/jmespath/go-jmespath v0.4.0/go.mod h1:T8mJZnbsbmF+m6zOOFylbeCJqk5+pHW
|
|||||||
github.com/jmespath/go-jmespath/internal/testify v1.5.1/go.mod h1:L3OGu8Wl2/fWfCI6z80xFu9LTZmf1ZRjMHUOPmWr69U=
|
github.com/jmespath/go-jmespath/internal/testify v1.5.1/go.mod h1:L3OGu8Wl2/fWfCI6z80xFu9LTZmf1ZRjMHUOPmWr69U=
|
||||||
github.com/joho/godotenv v1.3.0/go.mod h1:7hK45KPybAkOC6peb+G5yklZfMxEjkZhHbwpqxOKXbg=
|
github.com/joho/godotenv v1.3.0/go.mod h1:7hK45KPybAkOC6peb+G5yklZfMxEjkZhHbwpqxOKXbg=
|
||||||
github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU=
|
github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU=
|
||||||
|
github.com/json-iterator/go v1.1.9/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
|
||||||
github.com/json-iterator/go v1.1.10 h1:Kz6Cvnvv2wGdaG/V8yMvfkmNiXq9Ya2KUv4rouJJr68=
|
github.com/json-iterator/go v1.1.10 h1:Kz6Cvnvv2wGdaG/V8yMvfkmNiXq9Ya2KUv4rouJJr68=
|
||||||
github.com/json-iterator/go v1.1.10/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
|
github.com/json-iterator/go v1.1.10/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
|
||||||
github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU=
|
github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU=
|
||||||
@@ -206,13 +207,7 @@ github.com/karrick/godirwalk v1.8.0/go.mod h1:H5KPZjojv4lE+QYImBI8xVtrBRgYrIVsaR
|
|||||||
github.com/karrick/godirwalk v1.10.3/go.mod h1:RoGL9dQei4vP9ilrpETWE8CLOZ1kiN0LhBygSwrAsHA=
|
github.com/karrick/godirwalk v1.10.3/go.mod h1:RoGL9dQei4vP9ilrpETWE8CLOZ1kiN0LhBygSwrAsHA=
|
||||||
github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8=
|
github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8=
|
||||||
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
|
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
|
||||||
github.com/klauspost/compress v1.8.2/go.mod h1:RyIbtBH6LamlWaDj8nUwkbUhJ87Yi3uG0guNDohfE1A=
|
|
||||||
github.com/klauspost/compress v1.9.5/go.mod h1:RyIbtBH6LamlWaDj8nUwkbUhJ87Yi3uG0guNDohfE1A=
|
github.com/klauspost/compress v1.9.5/go.mod h1:RyIbtBH6LamlWaDj8nUwkbUhJ87Yi3uG0guNDohfE1A=
|
||||||
github.com/klauspost/compress v1.10.7/go.mod h1:aoV0uJVorq1K+umq18yTdKaF57EivdYsUV+/s2qKfXs=
|
|
||||||
github.com/klauspost/compress v1.11.8/go.mod h1:aoV0uJVorq1K+umq18yTdKaF57EivdYsUV+/s2qKfXs=
|
|
||||||
github.com/klauspost/compress v1.11.13 h1:eSvu8Tmq6j2psUJqJrLcWH6K3w5Dwc+qipbaA6eVEN4=
|
|
||||||
github.com/klauspost/compress v1.11.13/go.mod h1:aoV0uJVorq1K+umq18yTdKaF57EivdYsUV+/s2qKfXs=
|
|
||||||
github.com/klauspost/cpuid v1.2.1/go.mod h1:Pj4uuM528wm8OyEC2QMXAi2YiTZ96dNQPGgoMS4s3ek=
|
|
||||||
github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
|
github.com/konsorten/go-windows-terminal-sequences v1.0.1/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.2/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
|
||||||
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
|
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
|
||||||
@@ -222,13 +217,14 @@ github.com/kr/pty v1.1.5/go.mod h1:9r2w37qlBe7rQ6e1fg1S/9xpWHSnaqNdHD3WcMdbPDA=
|
|||||||
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
|
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
|
||||||
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
|
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
|
||||||
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
|
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
|
||||||
|
github.com/leodido/go-urn v1.2.0 h1:hpXL4XnriNwQ/ABnpepYM/1vCLWNDfUNts8dX3xTG6Y=
|
||||||
github.com/leodido/go-urn v1.2.0/go.mod h1:+8+nEpDfqqsY+g338gtMEUOtuK+4dEMhiQEgxpxOKII=
|
github.com/leodido/go-urn v1.2.0/go.mod h1:+8+nEpDfqqsY+g338gtMEUOtuK+4dEMhiQEgxpxOKII=
|
||||||
github.com/leodido/go-urn v1.2.1 h1:BqpAaACuzVSgi/VLzGZIobT2z4v53pjosyNd9Yv6n/w=
|
|
||||||
github.com/leodido/go-urn v1.2.1/go.mod h1:zt4jvISO2HfUBqxjfIshjdMTYS56ZS/qv49ictyFfxY=
|
|
||||||
github.com/mailru/easyjson v0.0.0-20190614124828-94de47d64c63/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc=
|
github.com/mailru/easyjson v0.0.0-20190614124828-94de47d64c63/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc=
|
||||||
github.com/mailru/easyjson v0.0.0-20190626092158-b2ccc519800e/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc=
|
github.com/mailru/easyjson v0.0.0-20190626092158-b2ccc519800e/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc=
|
||||||
github.com/markbates/oncer v0.0.0-20181203154359-bf2de49a0be2/go.mod h1:Ld9puTsIW75CHf65OeIOkyKbteujpZVXDpWK6YGZbxE=
|
github.com/markbates/oncer v0.0.0-20181203154359-bf2de49a0be2/go.mod h1:Ld9puTsIW75CHf65OeIOkyKbteujpZVXDpWK6YGZbxE=
|
||||||
github.com/markbates/safe v1.0.1/go.mod h1:nAqgmRi7cY2nqMc92/bSEeQA+R4OheNU2T1kNSCBdG0=
|
github.com/markbates/safe v1.0.1/go.mod h1:nAqgmRi7cY2nqMc92/bSEeQA+R4OheNU2T1kNSCBdG0=
|
||||||
|
github.com/mattn/go-isatty v0.0.12 h1:wuysRhFDzyxgEmMf5xjvJ2M9dZoWAXNNr5LSBS7uHXY=
|
||||||
|
github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU=
|
||||||
github.com/mattn/go-sqlite3 v1.14.5 h1:1IdxlwTNazvbKJQSxoJ5/9ECbEeaTTyeU7sEAZ5KKTQ=
|
github.com/mattn/go-sqlite3 v1.14.5 h1:1IdxlwTNazvbKJQSxoJ5/9ECbEeaTTyeU7sEAZ5KKTQ=
|
||||||
github.com/mattn/go-sqlite3 v1.14.5/go.mod h1:WVKg1VTActs4Qso6iwGbiFih2UIHo0ENGwNd0Lj+XmI=
|
github.com/mattn/go-sqlite3 v1.14.5/go.mod h1:WVKg1VTActs4Qso6iwGbiFih2UIHo0ENGwNd0Lj+XmI=
|
||||||
github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y=
|
github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y=
|
||||||
@@ -264,9 +260,8 @@ github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:
|
|||||||
github.com/rogpeppe/go-internal v1.1.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
|
github.com/rogpeppe/go-internal v1.1.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
|
||||||
github.com/rogpeppe/go-internal v1.2.2/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
|
github.com/rogpeppe/go-internal v1.2.2/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
|
||||||
github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
|
github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
|
||||||
github.com/savsgio/gotils v0.0.0-20200117113501-90175b0fbe3f/go.mod h1:lHhJedqxCoHN+zMtwGNTXWmF0u9Jt363FYRhV6g0CdY=
|
github.com/romana/rlog v0.0.0-20171115192701-f018bc92e7d7 h1:jkvpcEatpwuMF5O5LVxTnehj6YZ/aEZN4NWD/Xml4pI=
|
||||||
github.com/savsgio/gotils v0.0.0-20200616100644-13ff1fd2c28c h1:KKqhycXW1WVNkX7r4ekTV2gFkbhdyihlWD8c0/FiWmk=
|
github.com/romana/rlog v0.0.0-20171115192701-f018bc92e7d7/go.mod h1:KTrHyWpO1sevuXPZwyeZc72ddWRFqNSKDFl7uVWKpg0=
|
||||||
github.com/savsgio/gotils v0.0.0-20200616100644-13ff1fd2c28c/go.mod h1:TWNAOTaVzGOXq8RbEvHnhzA/A2sLZzgn0m6URjnukY8=
|
|
||||||
github.com/sirupsen/logrus v1.4.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo=
|
github.com/sirupsen/logrus v1.4.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo=
|
||||||
github.com/sirupsen/logrus v1.4.1/go.mod h1:ni0Sbl8bgC9z8RoU9G6nDWqqs/fq4eDPysMBDgk/93Q=
|
github.com/sirupsen/logrus v1.4.1/go.mod h1:ni0Sbl8bgC9z8RoU9G6nDWqqs/fq4eDPysMBDgk/93Q=
|
||||||
github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE=
|
github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE=
|
||||||
@@ -278,28 +273,22 @@ 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/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
|
||||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||||
github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||||
github.com/stretchr/objx v0.2.0 h1:Hbg2NidpLE8veEBkEZTL3CvlkUIVzuU9jDplZO54c48=
|
|
||||||
github.com/stretchr/objx v0.2.0/go.mod h1:qt09Ya8vawLte6SNmTgCsAVtYtaKzEcn8ATUoHMkEqE=
|
github.com/stretchr/objx v0.2.0/go.mod h1:qt09Ya8vawLte6SNmTgCsAVtYtaKzEcn8ATUoHMkEqE=
|
||||||
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
|
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
|
||||||
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
|
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
|
||||||
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
|
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
|
||||||
|
github.com/stretchr/testify v1.6.1 h1:hDPOHmpOpP40lSULcqw7IrRb/u7w6RpDC9399XyoNd0=
|
||||||
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||||
github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY=
|
|
||||||
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
|
||||||
github.com/tidwall/pretty v1.0.0/go.mod h1:XNkn88O1ChpSDQmQeStsy+sBenx6DDtFZJxhVysOjyk=
|
github.com/tidwall/pretty v1.0.0/go.mod h1:XNkn88O1ChpSDQmQeStsy+sBenx6DDtFZJxhVysOjyk=
|
||||||
github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw=
|
github.com/ugorji/go v1.1.7 h1:/68gy2h+1mWMrwZFeD1kQialdSzAb432dtpeJ42ovdo=
|
||||||
github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc=
|
github.com/ugorji/go v1.1.7/go.mod h1:kZn38zHttfInRq0xu/PH0az30d+z6vm202qpg1oXVMw=
|
||||||
github.com/valyala/fasthttp v1.9.0/go.mod h1:FstJa9V+Pj9vQ7OJie2qMHdwemEDaDiSdBnvPM1Su9w=
|
github.com/ugorji/go/codec v1.1.7 h1:2SvQaVZ1ouYrrKKwoSk2pzd4A9evlKJb9oTL+OaLUSs=
|
||||||
github.com/valyala/fasthttp v1.15.1/go.mod h1:YOKImeEosDdBPnxc0gy7INqi3m1zK6A+xl6TwOBhHCA=
|
github.com/ugorji/go/codec v1.1.7/go.mod h1:Ax+UKWsSmolVDwsd+7N3ZtXu+yMGCf907BLYF3GoBXY=
|
||||||
github.com/valyala/fasthttp v1.16.0/go.mod h1:YOKImeEosDdBPnxc0gy7INqi3m1zK6A+xl6TwOBhHCA=
|
|
||||||
github.com/valyala/fasthttp v1.18.0/go.mod h1:jjraHZVbKOXftJfsOYoAjaeygpj5hr8ermTRJNroD7A=
|
|
||||||
github.com/valyala/fasthttp v1.23.0 h1:0ufwSD9BhWa6f8HWdmdq4FHQ23peRo3Ng/Qs8m5NcFs=
|
|
||||||
github.com/valyala/fasthttp v1.23.0/go.mod h1:0mw2RjXGOzxf4NL2jni3gUQ7LfjjUSiG5sskOUUSEpU=
|
|
||||||
github.com/valyala/tcplisten v0.0.0-20161114210144-ceec8f93295a h1:0R4NLDRDZX6JcmhJgXi5E4b8Wg84ihbmUKp/GvSPEzc=
|
|
||||||
github.com/valyala/tcplisten v0.0.0-20161114210144-ceec8f93295a/go.mod h1:v3UYOV9WzVtRmSR+PDvWpU/qWl4Wa5LApYYX4ZtKbio=
|
|
||||||
github.com/xdg-go/pbkdf2 v1.0.0/go.mod h1:jrpuAogTd400dnrH08LKmI/xc1MbPOebTwRqcT5RDeI=
|
github.com/xdg-go/pbkdf2 v1.0.0/go.mod h1:jrpuAogTd400dnrH08LKmI/xc1MbPOebTwRqcT5RDeI=
|
||||||
github.com/xdg-go/scram v1.0.2/go.mod h1:1WAq6h33pAW+iRreB34OORO2Nf7qel3VV3fjBj+hCSs=
|
github.com/xdg-go/scram v1.0.2/go.mod h1:1WAq6h33pAW+iRreB34OORO2Nf7qel3VV3fjBj+hCSs=
|
||||||
github.com/xdg-go/stringprep v1.0.2/go.mod h1:8F9zXuvzgwmyT5DUm4GUfZGDdT3W+LCvS6+da4O5kxM=
|
github.com/xdg-go/stringprep v1.0.2/go.mod h1:8F9zXuvzgwmyT5DUm4GUfZGDdT3W+LCvS6+da4O5kxM=
|
||||||
|
github.com/yalp/jsonpath v0.0.0-20180802001716-5cc68e5049a0 h1:6fRhSjgLCkTD3JnJxvaJ4Sj+TYblw757bqYgZaOq5ZY=
|
||||||
|
github.com/yalp/jsonpath v0.0.0-20180802001716-5cc68e5049a0/go.mod h1:/LWChgwKmvncFJFHJ7Gvn9wZArjbV5/FppcK2fKk/tI=
|
||||||
github.com/youmark/pkcs8 v0.0.0-20181117223130-1be2e3e5546d/go.mod h1:rHwXgn7JulP+udvsHwJoVG1YGAP6VLg4y9I5dyZdqmA=
|
github.com/youmark/pkcs8 v0.0.0-20181117223130-1be2e3e5546d/go.mod h1:rHwXgn7JulP+udvsHwJoVG1YGAP6VLg4y9I5dyZdqmA=
|
||||||
github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
||||||
github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
||||||
@@ -372,11 +361,8 @@ golang.org/x/net v0.0.0-20200222125558-5a598a2470a0/go.mod h1:z5CRVTTTmAJ677TzLL
|
|||||||
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-20200301022130-244492dfa37a/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
golang.org/x/net v0.0.0-20200301022130-244492dfa37a/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||||
golang.org/x/net v0.0.0-20200324143707-d3edc9973b7e/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
|
golang.org/x/net v0.0.0-20200324143707-d3edc9973b7e/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
|
||||||
golang.org/x/net v0.0.0-20200602114024-627f9648deb9/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
|
|
||||||
golang.org/x/net v0.0.0-20201016165138-7b1cca2348c0/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
|
|
||||||
golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
|
golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
|
||||||
golang.org/x/net v0.0.0-20210224082022-3d97a244fca7/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
|
golang.org/x/net v0.0.0-20210224082022-3d97a244fca7/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
|
||||||
golang.org/x/net v0.0.0-20210226101413-39120d07d75e/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
|
|
||||||
golang.org/x/net v0.0.0-20210421230115-4e50805a0758 h1:aEpZnXcAmXkd6AvLb2OPt+EN1Zu/8Ne3pCqPjja5PXY=
|
golang.org/x/net v0.0.0-20210421230115-4e50805a0758 h1:aEpZnXcAmXkd6AvLb2OPt+EN1Zu/8Ne3pCqPjja5PXY=
|
||||||
golang.org/x/net v0.0.0-20210421230115-4e50805a0758/go.mod h1:72T/g9IO56b78aLF+1Kcs5dz7/ng1VjMUvfKvpfy+jM=
|
golang.org/x/net v0.0.0-20210421230115-4e50805a0758/go.mod h1:72T/g9IO56b78aLF+1Kcs5dz7/ng1VjMUvfKvpfy+jM=
|
||||||
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=
|
||||||
@@ -410,21 +396,20 @@ golang.org/x/sys v0.0.0-20190616124812-15dcb6c0061f/go.mod h1:h1NjWce9XRLGQEsW7w
|
|||||||
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-20190726091711-fc99dfbffb4e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-20191001151750-bb3f8db39f24/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20191001151750-bb3f8db39f24/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-20191228213918-04cbcbbfeed8/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20191228213918-04cbcbbfeed8/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-20200113162924-86b910548bc1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20200113162924-86b910548bc1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-20200122134326-e047566fdf82/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20200122134326-e047566fdf82/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-20200202164722-d101bd2416d5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20200202164722-d101bd2416d5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-20200212091648-12a6c2dcc1e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20200212091648-12a6c2dcc1e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
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-20200302150141-5c8b2ff67527/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20200302150141-5c8b2ff67527/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-20200602225109-6fdc65e7d980/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
|
||||||
golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-20201101102859-da207088b7d1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
|
||||||
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-20201210223839-7e3030f88018/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
|
||||||
golang.org/x/sys v0.0.0-20210225134936-a50acf3fe073/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20210225134936-a50acf3fe073/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-20210420072515-93ed5bcd2bfe h1:WdX7u8s3yOigWAhHEaDl8r9G+4XwFQEQFtBMYyN+kXQ=
|
golang.org/x/sys v0.0.0-20210420072515-93ed5bcd2bfe h1:WdX7u8s3yOigWAhHEaDl8r9G+4XwFQEQFtBMYyN+kXQ=
|
||||||
golang.org/x/sys v0.0.0-20210420072515-93ed5bcd2bfe/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20210420072515-93ed5bcd2bfe/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
226
agent/main.go
Normal file
226
agent/main.go
Normal file
@@ -0,0 +1,226 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"flag"
|
||||||
|
"fmt"
|
||||||
|
"github.com/gin-contrib/static"
|
||||||
|
"github.com/gin-gonic/gin"
|
||||||
|
"github.com/gorilla/websocket"
|
||||||
|
"github.com/romana/rlog"
|
||||||
|
"github.com/up9inc/mizu/shared"
|
||||||
|
"github.com/up9inc/mizu/tap"
|
||||||
|
"mizuserver/pkg/api"
|
||||||
|
"mizuserver/pkg/models"
|
||||||
|
"mizuserver/pkg/routes"
|
||||||
|
"mizuserver/pkg/sensitiveDataFiltering"
|
||||||
|
"mizuserver/pkg/utils"
|
||||||
|
"net/http"
|
||||||
|
"os"
|
||||||
|
"os/signal"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
var tapperMode = flag.Bool("tap", false, "Run in tapper mode without API")
|
||||||
|
var apiServerMode = flag.Bool("api-server", false, "Run in API server mode with API")
|
||||||
|
var standaloneMode = flag.Bool("standalone", false, "Run in standalone tapper and API mode")
|
||||||
|
var apiServerAddress = flag.String("api-server-address", "", "Address of mizu API server")
|
||||||
|
var namespace = flag.String("namespace", "", "Resolve IPs if they belong to resources in this namespace (default is all)")
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
flag.Parse()
|
||||||
|
hostMode := os.Getenv(shared.HostModeEnvVar) == "1"
|
||||||
|
tapOpts := &tap.TapOpts{HostMode: hostMode}
|
||||||
|
|
||||||
|
if !*tapperMode && !*apiServerMode && !*standaloneMode {
|
||||||
|
panic("One of the flags --tap, --api or --standalone must be provided")
|
||||||
|
}
|
||||||
|
|
||||||
|
if *standaloneMode {
|
||||||
|
api.StartResolving(*namespace)
|
||||||
|
|
||||||
|
harOutputChannel, outboundLinkOutputChannel := tap.StartPassiveTapper(tapOpts)
|
||||||
|
filteredHarChannel := make(chan *tap.OutputChannelItem)
|
||||||
|
|
||||||
|
go filterHarItems(harOutputChannel, filteredHarChannel, getTrafficFilteringOptions())
|
||||||
|
go api.StartReadingEntries(filteredHarChannel, nil)
|
||||||
|
go api.StartReadingOutbound(outboundLinkOutputChannel)
|
||||||
|
|
||||||
|
hostApi(nil)
|
||||||
|
} else if *tapperMode {
|
||||||
|
if *apiServerAddress == "" {
|
||||||
|
panic("API server address must be provided with --api-server-address when using --tap")
|
||||||
|
}
|
||||||
|
|
||||||
|
tapTargets := getTapTargets()
|
||||||
|
if tapTargets != nil {
|
||||||
|
tap.SetFilterAuthorities(tapTargets)
|
||||||
|
rlog.Infof("Filtering for the following authorities: %v", tap.GetFilterIPs())
|
||||||
|
}
|
||||||
|
|
||||||
|
harOutputChannel, outboundLinkOutputChannel := tap.StartPassiveTapper(tapOpts)
|
||||||
|
|
||||||
|
socketConnection, err := shared.ConnectToSocketServer(*apiServerAddress, shared.DEFAULT_SOCKET_RETRIES, shared.DEFAULT_SOCKET_RETRY_SLEEP_TIME, false)
|
||||||
|
if err != nil {
|
||||||
|
panic(fmt.Sprintf("Error connecting to socket server at %s %v", *apiServerAddress, err))
|
||||||
|
}
|
||||||
|
|
||||||
|
go pipeTapChannelToSocket(socketConnection, harOutputChannel)
|
||||||
|
go pipeOutboundLinksChannelToSocket(socketConnection, outboundLinkOutputChannel)
|
||||||
|
} else if *apiServerMode {
|
||||||
|
api.StartResolving(*namespace)
|
||||||
|
|
||||||
|
socketHarOutChannel := make(chan *tap.OutputChannelItem, 1000)
|
||||||
|
filteredHarChannel := make(chan *tap.OutputChannelItem)
|
||||||
|
|
||||||
|
go filterHarItems(socketHarOutChannel, filteredHarChannel, getTrafficFilteringOptions())
|
||||||
|
go api.StartReadingEntries(filteredHarChannel, nil)
|
||||||
|
|
||||||
|
hostApi(socketHarOutChannel)
|
||||||
|
}
|
||||||
|
|
||||||
|
signalChan := make(chan os.Signal, 1)
|
||||||
|
signal.Notify(signalChan, os.Interrupt)
|
||||||
|
<-signalChan
|
||||||
|
|
||||||
|
rlog.Info("Exiting")
|
||||||
|
}
|
||||||
|
|
||||||
|
func hostApi(socketHarOutputChannel chan<- *tap.OutputChannelItem) {
|
||||||
|
app := gin.Default()
|
||||||
|
|
||||||
|
app.GET("/echo", func(c *gin.Context) {
|
||||||
|
c.String(http.StatusOK, "Here is Mizu agent")
|
||||||
|
})
|
||||||
|
|
||||||
|
eventHandlers := api.RoutesEventHandlers{
|
||||||
|
SocketHarOutChannel: socketHarOutputChannel,
|
||||||
|
}
|
||||||
|
|
||||||
|
app.Use(static.ServeRoot("/", "./site"))
|
||||||
|
app.Use(CORSMiddleware()) // This has to be called after the static middleware, does not work if its called before
|
||||||
|
|
||||||
|
api.WebSocketRoutes(app, &eventHandlers)
|
||||||
|
routes.EntriesRoutes(app)
|
||||||
|
routes.MetadataRoutes(app)
|
||||||
|
routes.StatusRoutes(app)
|
||||||
|
routes.NotFoundRoute(app)
|
||||||
|
|
||||||
|
utils.StartServer(app)
|
||||||
|
}
|
||||||
|
|
||||||
|
func CORSMiddleware() gin.HandlerFunc {
|
||||||
|
return func(c *gin.Context) {
|
||||||
|
c.Writer.Header().Set("Access-Control-Allow-Origin", "*")
|
||||||
|
c.Writer.Header().Set("Access-Control-Allow-Credentials", "true")
|
||||||
|
c.Writer.Header().Set("Access-Control-Allow-Headers", "Content-Type, Content-Length, Accept-Encoding, X-CSRF-Token, Authorization, accept, origin, Cache-Control, X-Requested-With")
|
||||||
|
c.Writer.Header().Set("Access-Control-Allow-Methods", "POST, OPTIONS, GET, PUT")
|
||||||
|
|
||||||
|
if c.Request.Method == "OPTIONS" {
|
||||||
|
c.AbortWithStatus(204)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
c.Next()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func getTapTargets() []string {
|
||||||
|
nodeName := os.Getenv(shared.NodeNameEnvVar)
|
||||||
|
var tappedAddressesPerNodeDict map[string][]string
|
||||||
|
err := json.Unmarshal([]byte(os.Getenv(shared.TappedAddressesPerNodeDictEnvVar)), &tappedAddressesPerNodeDict)
|
||||||
|
if err != nil {
|
||||||
|
panic(fmt.Sprintf("env var %s's value of %s is invalid! must be map[string][]string %v", shared.TappedAddressesPerNodeDictEnvVar, tappedAddressesPerNodeDict, err))
|
||||||
|
}
|
||||||
|
return tappedAddressesPerNodeDict[nodeName]
|
||||||
|
}
|
||||||
|
|
||||||
|
func getTrafficFilteringOptions() *shared.TrafficFilteringOptions {
|
||||||
|
filteringOptionsJson := os.Getenv(shared.MizuFilteringOptionsEnvVar)
|
||||||
|
if filteringOptionsJson == "" {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
var filteringOptions shared.TrafficFilteringOptions
|
||||||
|
err := json.Unmarshal([]byte(filteringOptionsJson), &filteringOptions)
|
||||||
|
if err != nil {
|
||||||
|
panic(fmt.Sprintf("env var %s's value of %s is invalid! json must match the shared.TrafficFilteringOptions struct %v", shared.MizuFilteringOptionsEnvVar, filteringOptionsJson, err))
|
||||||
|
}
|
||||||
|
|
||||||
|
return &filteringOptions
|
||||||
|
}
|
||||||
|
|
||||||
|
var userAgentsToFilter = []string{"kube-probe", "prometheus"}
|
||||||
|
|
||||||
|
func filterHarItems(inChannel <-chan *tap.OutputChannelItem, outChannel chan *tap.OutputChannelItem, filterOptions *shared.TrafficFilteringOptions) {
|
||||||
|
for message := range inChannel {
|
||||||
|
if message.ConnectionInfo.IsOutgoing && api.CheckIsServiceIP(message.ConnectionInfo.ServerIP) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
// TODO: move this to tappers https://up9.atlassian.net/browse/TRA-3441
|
||||||
|
if filterOptions.HideHealthChecks && isHealthCheckByUserAgent(message) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if !filterOptions.DisableRedaction {
|
||||||
|
sensitiveDataFiltering.FilterSensitiveInfoFromHarRequest(message, filterOptions)
|
||||||
|
}
|
||||||
|
|
||||||
|
outChannel <- message
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func isHealthCheckByUserAgent(message *tap.OutputChannelItem) bool {
|
||||||
|
for _, header := range message.HarEntry.Request.Headers {
|
||||||
|
if strings.ToLower(header.Name) == "user-agent" {
|
||||||
|
for _, userAgent := range userAgentsToFilter {
|
||||||
|
if strings.Contains(strings.ToLower(header.Value), userAgent) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
func pipeTapChannelToSocket(connection *websocket.Conn, messageDataChannel <-chan *tap.OutputChannelItem) {
|
||||||
|
if connection == nil {
|
||||||
|
panic("Websocket connection is nil")
|
||||||
|
}
|
||||||
|
|
||||||
|
if messageDataChannel == nil {
|
||||||
|
panic("Channel of captured messages is nil")
|
||||||
|
}
|
||||||
|
|
||||||
|
for messageData := range messageDataChannel {
|
||||||
|
marshaledData, err := models.CreateWebsocketTappedEntryMessage(messageData)
|
||||||
|
if err != nil {
|
||||||
|
rlog.Infof("error converting message to json %s, (%v,%+v)\n", err, err, err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
err = connection.WriteMessage(websocket.TextMessage, marshaledData)
|
||||||
|
if err != nil {
|
||||||
|
rlog.Infof("error sending message through socket server %s, (%v,%+v)\n", err, err, err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func pipeOutboundLinksChannelToSocket(connection *websocket.Conn, outboundLinkChannel <-chan *tap.OutboundLink) {
|
||||||
|
for outboundLink := range outboundLinkChannel {
|
||||||
|
if outboundLink.SuggestedProtocol == tap.TLSProtocol {
|
||||||
|
marshaledData, err := models.CreateWebsocketOutboundLinkMessage(outboundLink)
|
||||||
|
if err != nil {
|
||||||
|
rlog.Infof("Error converting outbound link to json %s, (%v,%+v)", err, err, err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
err = connection.WriteMessage(websocket.TextMessage, marshaledData)
|
||||||
|
if err != nil {
|
||||||
|
rlog.Infof("error sending outbound link message through socket server %s, (%v,%+v)", err, err, err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
202
agent/pkg/api/main.go
Normal file
202
agent/pkg/api/main.go
Normal file
@@ -0,0 +1,202 @@
|
|||||||
|
package api
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bufio"
|
||||||
|
"context"
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"mizuserver/pkg/holder"
|
||||||
|
"net/url"
|
||||||
|
"os"
|
||||||
|
"path"
|
||||||
|
"sort"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/google/martian/har"
|
||||||
|
"github.com/romana/rlog"
|
||||||
|
"github.com/up9inc/mizu/tap"
|
||||||
|
"go.mongodb.org/mongo-driver/bson/primitive"
|
||||||
|
|
||||||
|
"mizuserver/pkg/database"
|
||||||
|
"mizuserver/pkg/models"
|
||||||
|
"mizuserver/pkg/resolver"
|
||||||
|
"mizuserver/pkg/utils"
|
||||||
|
)
|
||||||
|
|
||||||
|
var k8sResolver *resolver.Resolver
|
||||||
|
|
||||||
|
func StartResolving(namespace string) {
|
||||||
|
errOut := make(chan error, 100)
|
||||||
|
res, err := resolver.NewFromInCluster(errOut, namespace)
|
||||||
|
if err != nil {
|
||||||
|
rlog.Infof("error creating k8s resolver %s", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
ctx := context.Background()
|
||||||
|
res.Start(ctx)
|
||||||
|
go func() {
|
||||||
|
for {
|
||||||
|
select {
|
||||||
|
case err := <-errOut:
|
||||||
|
rlog.Infof("name resolving error %s", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
k8sResolver = res
|
||||||
|
holder.SetResolver(res)
|
||||||
|
}
|
||||||
|
|
||||||
|
func StartReadingEntries(harChannel <-chan *tap.OutputChannelItem, workingDir *string) {
|
||||||
|
if workingDir != nil && *workingDir != "" {
|
||||||
|
startReadingFiles(*workingDir)
|
||||||
|
} else {
|
||||||
|
startReadingChannel(harChannel)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func startReadingFiles(workingDir string) {
|
||||||
|
err := os.MkdirAll(workingDir, os.ModePerm)
|
||||||
|
utils.CheckErr(err)
|
||||||
|
|
||||||
|
for true {
|
||||||
|
dir, _ := os.Open(workingDir)
|
||||||
|
dirFiles, _ := dir.Readdir(-1)
|
||||||
|
|
||||||
|
var harFiles []os.FileInfo
|
||||||
|
for _, fileInfo := range dirFiles {
|
||||||
|
if strings.HasSuffix(fileInfo.Name(), ".har") {
|
||||||
|
harFiles = append(harFiles, fileInfo)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
sort.Sort(utils.ByModTime(harFiles))
|
||||||
|
|
||||||
|
if len(harFiles) == 0 {
|
||||||
|
rlog.Infof("Waiting for new files\n")
|
||||||
|
time.Sleep(3 * time.Second)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
fileInfo := harFiles[0]
|
||||||
|
inputFilePath := path.Join(workingDir, fileInfo.Name())
|
||||||
|
file, err := os.Open(inputFilePath)
|
||||||
|
utils.CheckErr(err)
|
||||||
|
|
||||||
|
var inputHar har.HAR
|
||||||
|
decErr := json.NewDecoder(bufio.NewReader(file)).Decode(&inputHar)
|
||||||
|
utils.CheckErr(decErr)
|
||||||
|
|
||||||
|
for _, entry := range inputHar.Log.Entries {
|
||||||
|
time.Sleep(time.Millisecond * 250)
|
||||||
|
connectionInfo := &tap.ConnectionInfo{
|
||||||
|
ClientIP: fileInfo.Name(),
|
||||||
|
ClientPort: "",
|
||||||
|
ServerIP: "",
|
||||||
|
ServerPort: "",
|
||||||
|
IsOutgoing: false,
|
||||||
|
}
|
||||||
|
saveHarToDb(entry, connectionInfo)
|
||||||
|
}
|
||||||
|
rmErr := os.Remove(inputFilePath)
|
||||||
|
utils.CheckErr(rmErr)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func startReadingChannel(outputItems <-chan *tap.OutputChannelItem) {
|
||||||
|
if outputItems == nil {
|
||||||
|
panic("Channel of captured messages is nil")
|
||||||
|
}
|
||||||
|
|
||||||
|
for item := range outputItems {
|
||||||
|
saveHarToDb(item.HarEntry, item.ConnectionInfo)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func StartReadingOutbound(outboundLinkChannel <-chan *tap.OutboundLink) {
|
||||||
|
// tcpStreamFactory will block on write to channel. Empty channel to unblock.
|
||||||
|
// TODO: Make write to channel optional.
|
||||||
|
for range outboundLinkChannel {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func saveHarToDb(entry *har.Entry, connectionInfo *tap.ConnectionInfo) {
|
||||||
|
entryBytes, _ := json.Marshal(entry)
|
||||||
|
serviceName, urlPath := getServiceNameFromUrl(entry.Request.URL)
|
||||||
|
entryId := primitive.NewObjectID().Hex()
|
||||||
|
var (
|
||||||
|
resolvedSource string
|
||||||
|
resolvedDestination string
|
||||||
|
)
|
||||||
|
if k8sResolver != nil {
|
||||||
|
unresolvedSource := connectionInfo.ClientIP
|
||||||
|
resolvedSource = k8sResolver.Resolve(unresolvedSource)
|
||||||
|
if resolvedSource == "" {
|
||||||
|
rlog.Debugf("Cannot find resolved name to source: %s\n", unresolvedSource)
|
||||||
|
if os.Getenv("SKIP_NOT_RESOLVED_SOURCE") == "1" {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
unresolvedDestination := fmt.Sprintf("%s:%s", connectionInfo.ServerIP, connectionInfo.ServerPort)
|
||||||
|
resolvedDestination = k8sResolver.Resolve(unresolvedDestination)
|
||||||
|
if resolvedDestination == "" {
|
||||||
|
rlog.Debugf("Cannot find resolved name to dest: %s\n", unresolvedDestination)
|
||||||
|
if os.Getenv("SKIP_NOT_RESOLVED_DEST") == "1" {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
mizuEntry := models.MizuEntry{
|
||||||
|
EntryId: entryId,
|
||||||
|
Entry: string(entryBytes), // simple way to store it and not convert to bytes
|
||||||
|
Service: serviceName,
|
||||||
|
Url: entry.Request.URL,
|
||||||
|
Path: urlPath,
|
||||||
|
Method: entry.Request.Method,
|
||||||
|
Status: entry.Response.Status,
|
||||||
|
RequestSenderIp: connectionInfo.ClientIP,
|
||||||
|
Timestamp: entry.StartedDateTime.UnixNano() / int64(time.Millisecond),
|
||||||
|
ResolvedSource: resolvedSource,
|
||||||
|
ResolvedDestination: resolvedDestination,
|
||||||
|
IsOutgoing: connectionInfo.IsOutgoing,
|
||||||
|
}
|
||||||
|
mizuEntry.EstimatedSizeBytes = getEstimatedEntrySizeBytes(mizuEntry)
|
||||||
|
database.CreateEntry(&mizuEntry)
|
||||||
|
|
||||||
|
baseEntry := models.BaseEntryDetails{}
|
||||||
|
if err := models.GetEntry(&mizuEntry, &baseEntry); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
baseEntry.Rules = models.RunValidationRulesState(*entry, serviceName)
|
||||||
|
baseEntry.Latency = entry.Timings.Receive
|
||||||
|
baseEntryBytes, _ := models.CreateBaseEntryWebSocketMessage(&baseEntry)
|
||||||
|
BroadcastToBrowserClients(baseEntryBytes)
|
||||||
|
}
|
||||||
|
|
||||||
|
func getServiceNameFromUrl(inputUrl string) (string, string) {
|
||||||
|
parsed, err := url.Parse(inputUrl)
|
||||||
|
utils.CheckErr(err)
|
||||||
|
return fmt.Sprintf("%s://%s", parsed.Scheme, parsed.Host), parsed.Path
|
||||||
|
}
|
||||||
|
|
||||||
|
func CheckIsServiceIP(address string) bool {
|
||||||
|
return k8sResolver.CheckIsServiceIP(address)
|
||||||
|
}
|
||||||
|
|
||||||
|
// gives a rough estimate of the size this will take up in the db, good enough for maintaining db size limit accurately
|
||||||
|
func getEstimatedEntrySizeBytes(mizuEntry models.MizuEntry) int {
|
||||||
|
sizeBytes := len(mizuEntry.Entry)
|
||||||
|
sizeBytes += len(mizuEntry.EntryId)
|
||||||
|
sizeBytes += len(mizuEntry.Service)
|
||||||
|
sizeBytes += len(mizuEntry.Url)
|
||||||
|
sizeBytes += len(mizuEntry.Method)
|
||||||
|
sizeBytes += len(mizuEntry.RequestSenderIp)
|
||||||
|
sizeBytes += len(mizuEntry.ResolvedDestination)
|
||||||
|
sizeBytes += len(mizuEntry.ResolvedSource)
|
||||||
|
sizeBytes += 8 // Status bytes (sqlite integer is always 8 bytes)
|
||||||
|
sizeBytes += 8 // Timestamp bytes
|
||||||
|
sizeBytes += 8 // SizeBytes bytes
|
||||||
|
sizeBytes += 1 // IsOutgoing bytes
|
||||||
|
|
||||||
|
return sizeBytes
|
||||||
|
}
|
||||||
118
agent/pkg/api/socket_routes.go
Normal file
118
agent/pkg/api/socket_routes.go
Normal file
@@ -0,0 +1,118 @@
|
|||||||
|
package api
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"github.com/gin-gonic/gin"
|
||||||
|
"github.com/gorilla/websocket"
|
||||||
|
"github.com/romana/rlog"
|
||||||
|
"github.com/up9inc/mizu/shared/debounce"
|
||||||
|
"net/http"
|
||||||
|
"sync"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
type EventHandlers interface {
|
||||||
|
WebSocketConnect(socketId int, isTapper bool)
|
||||||
|
WebSocketDisconnect(socketId int, isTapper bool)
|
||||||
|
WebSocketMessage(socketId int, message []byte)
|
||||||
|
}
|
||||||
|
|
||||||
|
type SocketConnection struct {
|
||||||
|
connection *websocket.Conn
|
||||||
|
lock *sync.Mutex
|
||||||
|
eventHandlers EventHandlers
|
||||||
|
isTapper bool
|
||||||
|
}
|
||||||
|
|
||||||
|
var websocketUpgrader = websocket.Upgrader{
|
||||||
|
ReadBufferSize: 1024,
|
||||||
|
WriteBufferSize: 1024,
|
||||||
|
}
|
||||||
|
|
||||||
|
var websocketIdsLock = sync.Mutex{}
|
||||||
|
var connectedWebsockets map[int]*SocketConnection
|
||||||
|
var connectedWebsocketIdCounter = 0
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
websocketUpgrader.CheckOrigin = func(r *http.Request) bool { return true } // like cors for web socket
|
||||||
|
connectedWebsockets = make(map[int]*SocketConnection, 0)
|
||||||
|
}
|
||||||
|
|
||||||
|
func WebSocketRoutes(app *gin.Engine, eventHandlers EventHandlers) {
|
||||||
|
app.GET("/ws", func(c *gin.Context) {
|
||||||
|
websocketHandler(c.Writer, c.Request, eventHandlers, false)
|
||||||
|
})
|
||||||
|
app.GET("/wsTapper", func(c *gin.Context) {
|
||||||
|
websocketHandler(c.Writer, c.Request, eventHandlers, true)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func websocketHandler(w http.ResponseWriter, r *http.Request, eventHandlers EventHandlers, isTapper bool) {
|
||||||
|
conn, err := websocketUpgrader.Upgrade(w, r, nil)
|
||||||
|
if err != nil {
|
||||||
|
rlog.Errorf("Failed to set websocket upgrade: %v", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
websocketIdsLock.Lock()
|
||||||
|
|
||||||
|
connectedWebsocketIdCounter++
|
||||||
|
socketId := connectedWebsocketIdCounter
|
||||||
|
connectedWebsockets[socketId] = &SocketConnection{connection: conn, lock: &sync.Mutex{}, eventHandlers: eventHandlers, isTapper: isTapper}
|
||||||
|
|
||||||
|
websocketIdsLock.Unlock()
|
||||||
|
|
||||||
|
defer func() {
|
||||||
|
socketCleanup(socketId, connectedWebsockets[socketId])
|
||||||
|
}()
|
||||||
|
|
||||||
|
eventHandlers.WebSocketConnect(socketId, isTapper)
|
||||||
|
|
||||||
|
for {
|
||||||
|
_, msg, err := conn.ReadMessage()
|
||||||
|
if err != nil {
|
||||||
|
rlog.Errorf("Error reading message, socket id: %d, error: %v", socketId, err)
|
||||||
|
break
|
||||||
|
}
|
||||||
|
eventHandlers.WebSocketMessage(socketId, msg)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func socketCleanup(socketId int, socketConnection *SocketConnection) {
|
||||||
|
err := socketConnection.connection.Close()
|
||||||
|
if err != nil {
|
||||||
|
rlog.Errorf("Error closing socket connection for socket id %d: %v\n", socketId, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
websocketIdsLock.Lock()
|
||||||
|
connectedWebsockets[socketId] = nil
|
||||||
|
websocketIdsLock.Unlock()
|
||||||
|
|
||||||
|
socketConnection.eventHandlers.WebSocketDisconnect(socketId, socketConnection.isTapper)
|
||||||
|
}
|
||||||
|
|
||||||
|
var db = debounce.NewDebouncer(time.Second*5, func() {
|
||||||
|
rlog.Error("Successfully sent to socket")
|
||||||
|
})
|
||||||
|
|
||||||
|
func SendToSocket(socketId int, message []byte) error {
|
||||||
|
socketObj := connectedWebsockets[socketId]
|
||||||
|
if socketObj == nil {
|
||||||
|
return errors.New("Socket is disconnected")
|
||||||
|
}
|
||||||
|
|
||||||
|
var sent = false
|
||||||
|
time.AfterFunc(time.Second*5, func() {
|
||||||
|
if !sent {
|
||||||
|
rlog.Error("Socket timed out")
|
||||||
|
socketCleanup(socketId, socketObj)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
socketObj.lock.Lock() // gorilla socket panics from concurrent writes to a single socket
|
||||||
|
err := socketObj.connection.WriteMessage(1, message)
|
||||||
|
socketObj.lock.Unlock()
|
||||||
|
|
||||||
|
sent = true
|
||||||
|
return err
|
||||||
|
}
|
||||||
130
agent/pkg/api/socket_server_handlers.go
Normal file
130
agent/pkg/api/socket_server_handlers.go
Normal file
@@ -0,0 +1,130 @@
|
|||||||
|
package api
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"mizuserver/pkg/models"
|
||||||
|
"mizuserver/pkg/providers"
|
||||||
|
"mizuserver/pkg/up9"
|
||||||
|
"sync"
|
||||||
|
|
||||||
|
"github.com/romana/rlog"
|
||||||
|
"github.com/up9inc/mizu/shared"
|
||||||
|
"github.com/up9inc/mizu/tap"
|
||||||
|
)
|
||||||
|
|
||||||
|
var browserClientSocketUUIDs = make([]int, 0)
|
||||||
|
var socketListLock = sync.Mutex{}
|
||||||
|
|
||||||
|
type RoutesEventHandlers struct {
|
||||||
|
EventHandlers
|
||||||
|
SocketHarOutChannel chan<- *tap.OutputChannelItem
|
||||||
|
}
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
go up9.UpdateAnalyzeStatus(BroadcastToBrowserClients)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *RoutesEventHandlers) WebSocketConnect(socketId int, isTapper bool) {
|
||||||
|
if isTapper {
|
||||||
|
rlog.Infof("Websocket event - Tapper connected, socket ID: %d", socketId)
|
||||||
|
} else {
|
||||||
|
rlog.Infof("Websocket event - Browser socket connected, socket ID: %d", socketId)
|
||||||
|
socketListLock.Lock()
|
||||||
|
browserClientSocketUUIDs = append(browserClientSocketUUIDs, socketId)
|
||||||
|
socketListLock.Unlock()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *RoutesEventHandlers) WebSocketDisconnect(socketId int, isTapper bool) {
|
||||||
|
if isTapper {
|
||||||
|
rlog.Infof("Websocket event - Tapper disconnected, socket ID: %d", socketId)
|
||||||
|
} else {
|
||||||
|
rlog.Infof("Websocket event - Browser socket disconnected, socket ID: %d", socketId)
|
||||||
|
socketListLock.Lock()
|
||||||
|
removeSocketUUIDFromBrowserSlice(socketId)
|
||||||
|
socketListLock.Unlock()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func BroadcastToBrowserClients(message []byte) {
|
||||||
|
for _, socketId := range browserClientSocketUUIDs {
|
||||||
|
go func(socketId int) {
|
||||||
|
err := SendToSocket(socketId, message)
|
||||||
|
if err != nil {
|
||||||
|
rlog.Errorf("error sending message to socket ID %d: %v", socketId, err)
|
||||||
|
}
|
||||||
|
}(socketId)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *RoutesEventHandlers) WebSocketMessage(_ int, message []byte) {
|
||||||
|
var socketMessageBase shared.WebSocketMessageMetadata
|
||||||
|
err := json.Unmarshal(message, &socketMessageBase)
|
||||||
|
if err != nil {
|
||||||
|
rlog.Infof("Could not unmarshal websocket message %v\n", err)
|
||||||
|
} else {
|
||||||
|
switch socketMessageBase.MessageType {
|
||||||
|
case shared.WebSocketMessageTypeTappedEntry:
|
||||||
|
var tappedEntryMessage models.WebSocketTappedEntryMessage
|
||||||
|
err := json.Unmarshal(message, &tappedEntryMessage)
|
||||||
|
if err != nil {
|
||||||
|
rlog.Infof("Could not unmarshal message of message type %s %v\n", socketMessageBase.MessageType, err)
|
||||||
|
} else {
|
||||||
|
h.SocketHarOutChannel <- tappedEntryMessage.Data
|
||||||
|
}
|
||||||
|
case shared.WebSocketMessageTypeUpdateStatus:
|
||||||
|
var statusMessage shared.WebSocketStatusMessage
|
||||||
|
err := json.Unmarshal(message, &statusMessage)
|
||||||
|
if err != nil {
|
||||||
|
rlog.Infof("Could not unmarshal message of message type %s %v\n", socketMessageBase.MessageType, err)
|
||||||
|
} else {
|
||||||
|
providers.TapStatus.Pods = statusMessage.TappingStatus.Pods
|
||||||
|
BroadcastToBrowserClients(message)
|
||||||
|
}
|
||||||
|
case shared.WebsocketMessageTypeOutboundLink:
|
||||||
|
var outboundLinkMessage models.WebsocketOutboundLinkMessage
|
||||||
|
err := json.Unmarshal(message, &outboundLinkMessage)
|
||||||
|
if err != nil {
|
||||||
|
rlog.Infof("Could not unmarshal message of message type %s %v\n", socketMessageBase.MessageType, err)
|
||||||
|
} else {
|
||||||
|
handleTLSLink(outboundLinkMessage)
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
rlog.Infof("Received socket message of type %s for which no handlers are defined", socketMessageBase.MessageType)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func handleTLSLink(outboundLinkMessage models.WebsocketOutboundLinkMessage) {
|
||||||
|
resolvedName := k8sResolver.Resolve(outboundLinkMessage.Data.DstIP)
|
||||||
|
if resolvedName != "" {
|
||||||
|
outboundLinkMessage.Data.DstIP = resolvedName
|
||||||
|
} else if outboundLinkMessage.Data.SuggestedResolvedName != "" {
|
||||||
|
outboundLinkMessage.Data.DstIP = outboundLinkMessage.Data.SuggestedResolvedName
|
||||||
|
}
|
||||||
|
cacheKey := fmt.Sprintf("%s -> %s:%d", outboundLinkMessage.Data.Src, outboundLinkMessage.Data.DstIP, outboundLinkMessage.Data.DstPort)
|
||||||
|
_, isInCache := providers.RecentTLSLinks.Get(cacheKey)
|
||||||
|
if isInCache {
|
||||||
|
return
|
||||||
|
} else {
|
||||||
|
providers.RecentTLSLinks.SetDefault(cacheKey, outboundLinkMessage.Data)
|
||||||
|
}
|
||||||
|
marshaledMessage, err := json.Marshal(outboundLinkMessage)
|
||||||
|
if err != nil {
|
||||||
|
rlog.Errorf("Error marshaling outbound link message for broadcasting: %v", err)
|
||||||
|
} else {
|
||||||
|
rlog.Errorf("Broadcasting outboundlink message %s", string(marshaledMessage))
|
||||||
|
BroadcastToBrowserClients(marshaledMessage)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func removeSocketUUIDFromBrowserSlice(uuidToRemove int) {
|
||||||
|
newUUIDSlice := make([]int, 0, len(browserClientSocketUUIDs))
|
||||||
|
for _, uuid := range browserClientSocketUUIDs {
|
||||||
|
if uuid != uuidToRemove {
|
||||||
|
newUUIDSlice = append(newUUIDSlice, uuid)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
browserClientSocketUUIDs = newUUIDSlice
|
||||||
|
}
|
||||||
264
agent/pkg/controllers/entries_controller.go
Normal file
264
agent/pkg/controllers/entries_controller.go
Normal file
@@ -0,0 +1,264 @@
|
|||||||
|
package controllers
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"mizuserver/pkg/database"
|
||||||
|
"mizuserver/pkg/models"
|
||||||
|
"mizuserver/pkg/providers"
|
||||||
|
"mizuserver/pkg/up9"
|
||||||
|
"mizuserver/pkg/utils"
|
||||||
|
"mizuserver/pkg/validation"
|
||||||
|
"net/http"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/gin-gonic/gin"
|
||||||
|
"github.com/google/martian/har"
|
||||||
|
"github.com/romana/rlog"
|
||||||
|
)
|
||||||
|
|
||||||
|
func GetEntries(c *gin.Context) {
|
||||||
|
entriesFilter := &models.EntriesFilter{}
|
||||||
|
|
||||||
|
if err := c.BindQuery(entriesFilter); err != nil {
|
||||||
|
c.JSON(http.StatusBadRequest, err)
|
||||||
|
}
|
||||||
|
err := validation.Validate(entriesFilter)
|
||||||
|
if err != nil {
|
||||||
|
c.JSON(http.StatusBadRequest, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
order := database.OperatorToOrderMapping[entriesFilter.Operator]
|
||||||
|
operatorSymbol := database.OperatorToSymbolMapping[entriesFilter.Operator]
|
||||||
|
var entries []models.MizuEntry
|
||||||
|
database.GetEntriesTable().
|
||||||
|
Order(fmt.Sprintf("timestamp %s", order)).
|
||||||
|
Where(fmt.Sprintf("timestamp %s %v", operatorSymbol, entriesFilter.Timestamp)).
|
||||||
|
Omit("entry"). // remove the "big" entry field
|
||||||
|
Limit(entriesFilter.Limit).
|
||||||
|
Find(&entries)
|
||||||
|
|
||||||
|
if len(entries) > 0 && order == database.OrderDesc {
|
||||||
|
// the entries always order from oldest to newest so we should revers
|
||||||
|
utils.ReverseSlice(entries)
|
||||||
|
}
|
||||||
|
|
||||||
|
baseEntries := make([]models.BaseEntryDetails, 0)
|
||||||
|
for _, data := range entries {
|
||||||
|
harEntry := models.BaseEntryDetails{}
|
||||||
|
if err := models.GetEntry(&data, &harEntry); err != nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
baseEntries = append(baseEntries, harEntry)
|
||||||
|
}
|
||||||
|
|
||||||
|
c.JSON(http.StatusOK, baseEntries)
|
||||||
|
}
|
||||||
|
|
||||||
|
func GetHARs(c *gin.Context) {
|
||||||
|
entriesFilter := &models.HarFetchRequestBody{}
|
||||||
|
order := database.OrderDesc
|
||||||
|
if err := c.BindQuery(entriesFilter); err != nil {
|
||||||
|
c.JSON(http.StatusBadRequest, err)
|
||||||
|
}
|
||||||
|
err := validation.Validate(entriesFilter)
|
||||||
|
if err != nil {
|
||||||
|
c.JSON(http.StatusBadRequest, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
var timestampFrom, timestampTo int64
|
||||||
|
|
||||||
|
if entriesFilter.From < 0 {
|
||||||
|
timestampFrom = 0
|
||||||
|
} else {
|
||||||
|
timestampFrom = entriesFilter.From
|
||||||
|
}
|
||||||
|
if entriesFilter.To <= 0 {
|
||||||
|
timestampTo = time.Now().UnixNano() / int64(time.Millisecond)
|
||||||
|
} else {
|
||||||
|
timestampTo = entriesFilter.To
|
||||||
|
}
|
||||||
|
|
||||||
|
var entries []models.MizuEntry
|
||||||
|
database.GetEntriesTable().
|
||||||
|
Where(fmt.Sprintf("timestamp BETWEEN %v AND %v", timestampFrom, timestampTo)).
|
||||||
|
Order(fmt.Sprintf("timestamp %s", order)).
|
||||||
|
Find(&entries)
|
||||||
|
|
||||||
|
if len(entries) > 0 {
|
||||||
|
// the entries always order from oldest to newest so we should revers
|
||||||
|
utils.ReverseSlice(entries)
|
||||||
|
}
|
||||||
|
|
||||||
|
harsObject := map[string]*models.ExtendedHAR{}
|
||||||
|
|
||||||
|
for _, entryData := range entries {
|
||||||
|
var harEntry har.Entry
|
||||||
|
_ = json.Unmarshal([]byte(entryData.Entry), &harEntry)
|
||||||
|
if entryData.ResolvedDestination != "" {
|
||||||
|
harEntry.Request.URL = utils.SetHostname(harEntry.Request.URL, entryData.ResolvedDestination)
|
||||||
|
}
|
||||||
|
|
||||||
|
var fileName string
|
||||||
|
sourceOfEntry := entryData.ResolvedSource
|
||||||
|
if sourceOfEntry != "" {
|
||||||
|
// naively assumes the proper service source is http
|
||||||
|
sourceOfEntry = fmt.Sprintf("http://%s", sourceOfEntry)
|
||||||
|
//replace / from the file name cause they end up creating a corrupted folder
|
||||||
|
fileName = fmt.Sprintf("%s.har", strings.ReplaceAll(sourceOfEntry, "/", "_"))
|
||||||
|
} else {
|
||||||
|
fileName = "unknown_source.har"
|
||||||
|
}
|
||||||
|
if harOfSource, ok := harsObject[fileName]; ok {
|
||||||
|
harOfSource.Log.Entries = append(harOfSource.Log.Entries, &harEntry)
|
||||||
|
} else {
|
||||||
|
var entriesHar []*har.Entry
|
||||||
|
entriesHar = append(entriesHar, &harEntry)
|
||||||
|
harsObject[fileName] = &models.ExtendedHAR{
|
||||||
|
Log: &models.ExtendedLog{
|
||||||
|
Version: "1.2",
|
||||||
|
Creator: &models.ExtendedCreator{
|
||||||
|
Creator: &har.Creator{
|
||||||
|
Name: "mizu",
|
||||||
|
Version: "0.0.2",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Entries: entriesHar,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
// leave undefined when no source is present, otherwise modeler assumes source is empty string ""
|
||||||
|
if sourceOfEntry != "" {
|
||||||
|
harsObject[fileName].Log.Creator.Source = &sourceOfEntry
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
retObj := map[string][]byte{}
|
||||||
|
for k, v := range harsObject {
|
||||||
|
bytesData, _ := json.Marshal(v)
|
||||||
|
retObj[k] = bytesData
|
||||||
|
}
|
||||||
|
buffer := utils.ZipData(retObj)
|
||||||
|
c.Data(http.StatusOK, "application/octet-stream", buffer.Bytes())
|
||||||
|
}
|
||||||
|
|
||||||
|
func UploadEntries(c *gin.Context) {
|
||||||
|
rlog.Infof("Upload entries - started\n")
|
||||||
|
|
||||||
|
uploadRequestBody := &models.UploadEntriesRequestBody{}
|
||||||
|
if err := c.BindQuery(uploadRequestBody); err != nil {
|
||||||
|
c.JSON(http.StatusBadRequest, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if err := validation.Validate(uploadRequestBody); err != nil {
|
||||||
|
c.JSON(http.StatusBadRequest, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if up9.GetAnalyzeInfo().IsAnalyzing {
|
||||||
|
c.String(http.StatusBadRequest, "Cannot analyze, mizu is already analyzing")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
rlog.Infof("Upload entries - creating token. dest %s\n", uploadRequestBody.Dest)
|
||||||
|
token, err := up9.CreateAnonymousToken(uploadRequestBody.Dest)
|
||||||
|
if err != nil {
|
||||||
|
c.String(http.StatusServiceUnavailable, "Cannot analyze, mizu is already analyzing")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
rlog.Infof("Upload entries - uploading. token: %s model: %s\n", token.Token, token.Model)
|
||||||
|
go up9.UploadEntriesImpl(token.Token, token.Model, uploadRequestBody.Dest, uploadRequestBody.SleepIntervalSec)
|
||||||
|
c.String(http.StatusOK, "OK")
|
||||||
|
}
|
||||||
|
|
||||||
|
func GetFullEntries(c *gin.Context) {
|
||||||
|
entriesFilter := &models.HarFetchRequestBody{}
|
||||||
|
if err := c.BindQuery(entriesFilter); err != nil {
|
||||||
|
c.JSON(http.StatusBadRequest, err)
|
||||||
|
}
|
||||||
|
err := validation.Validate(entriesFilter)
|
||||||
|
if err != nil {
|
||||||
|
c.JSON(http.StatusBadRequest, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
var timestampFrom, timestampTo int64
|
||||||
|
|
||||||
|
if entriesFilter.From < 0 {
|
||||||
|
timestampFrom = 0
|
||||||
|
} else {
|
||||||
|
timestampFrom = entriesFilter.From
|
||||||
|
}
|
||||||
|
if entriesFilter.To <= 0 {
|
||||||
|
timestampTo = time.Now().UnixNano() / int64(time.Millisecond)
|
||||||
|
} else {
|
||||||
|
timestampTo = entriesFilter.To
|
||||||
|
}
|
||||||
|
|
||||||
|
entriesArray := database.GetEntriesFromDb(timestampFrom, timestampTo)
|
||||||
|
result := make([]models.FullEntryDetails, 0)
|
||||||
|
for _, data := range entriesArray {
|
||||||
|
harEntry := models.FullEntryDetails{}
|
||||||
|
if err := models.GetEntry(&data, &harEntry); err != nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
result = append(result, harEntry)
|
||||||
|
}
|
||||||
|
c.JSON(http.StatusOK, result)
|
||||||
|
}
|
||||||
|
|
||||||
|
func GetEntry(c *gin.Context) {
|
||||||
|
var entryData models.MizuEntry
|
||||||
|
database.GetEntriesTable().
|
||||||
|
Where(map[string]string{"entryId": c.Param("entryId")}).
|
||||||
|
First(&entryData)
|
||||||
|
|
||||||
|
fullEntry := models.FullEntryDetails{}
|
||||||
|
if err := models.GetEntry(&entryData, &fullEntry); err != nil {
|
||||||
|
c.JSON(http.StatusInternalServerError, map[string]interface{}{
|
||||||
|
"error": true,
|
||||||
|
"msg": "Can't get entry details",
|
||||||
|
})
|
||||||
|
}
|
||||||
|
fullEntryWithPolicy := models.FullEntryWithPolicy{}
|
||||||
|
if err := models.GetEntry(&entryData, &fullEntryWithPolicy); err != nil {
|
||||||
|
c.JSON(http.StatusInternalServerError, map[string]interface{}{
|
||||||
|
"error": true,
|
||||||
|
"msg": "Can't get entry details",
|
||||||
|
})
|
||||||
|
}
|
||||||
|
c.JSON(http.StatusOK, fullEntryWithPolicy)
|
||||||
|
}
|
||||||
|
|
||||||
|
func DeleteAllEntries(c *gin.Context) {
|
||||||
|
database.GetEntriesTable().
|
||||||
|
Where("1 = 1").
|
||||||
|
Delete(&models.MizuEntry{})
|
||||||
|
|
||||||
|
c.JSON(http.StatusOK, map[string]string{
|
||||||
|
"msg": "Success",
|
||||||
|
})
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
func GetGeneralStats(c *gin.Context) {
|
||||||
|
sqlQuery := "SELECT count(*) as count, min(timestamp) as min, max(timestamp) as max from mizu_entries"
|
||||||
|
var result struct {
|
||||||
|
Count int
|
||||||
|
Min int
|
||||||
|
Max int
|
||||||
|
}
|
||||||
|
database.GetEntriesTable().Raw(sqlQuery).Scan(&result)
|
||||||
|
c.JSON(http.StatusOK, result)
|
||||||
|
}
|
||||||
|
|
||||||
|
func GetTappingStatus(c *gin.Context) {
|
||||||
|
c.JSON(http.StatusOK, providers.TapStatus)
|
||||||
|
}
|
||||||
|
|
||||||
|
func AnalyzeInformation(c *gin.Context) {
|
||||||
|
c.JSON(http.StatusOK, up9.GetAnalyzeInfo())
|
||||||
|
}
|
||||||
|
|
||||||
|
func GetRecentTLSLinks(c *gin.Context) {
|
||||||
|
c.JSON(http.StatusOK, providers.GetAllRecentTLSAddresses())
|
||||||
|
}
|
||||||
13
agent/pkg/controllers/metadata_controller.go
Normal file
13
agent/pkg/controllers/metadata_controller.go
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
package controllers
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/gin-gonic/gin"
|
||||||
|
"github.com/up9inc/mizu/shared"
|
||||||
|
"mizuserver/pkg/version"
|
||||||
|
"net/http"
|
||||||
|
)
|
||||||
|
|
||||||
|
func GetVersion(c *gin.Context) {
|
||||||
|
resp := shared.VersionResponse{SemVer: version.SemVer}
|
||||||
|
c.JSON(http.StatusOK, resp)
|
||||||
|
}
|
||||||
12
agent/pkg/controllers/resolving_controller.go
Normal file
12
agent/pkg/controllers/resolving_controller.go
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
package controllers
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/gin-gonic/gin"
|
||||||
|
"mizuserver/pkg/holder"
|
||||||
|
"net/http"
|
||||||
|
)
|
||||||
|
|
||||||
|
func GetCurrentResolvingInformation(c *gin.Context) {
|
||||||
|
c.JSON(http.StatusOK, holder.GetResolver().GetMap())
|
||||||
|
}
|
||||||
|
|
||||||
32
agent/pkg/controllers/status_controller.go
Normal file
32
agent/pkg/controllers/status_controller.go
Normal file
@@ -0,0 +1,32 @@
|
|||||||
|
package controllers
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"github.com/gin-gonic/gin"
|
||||||
|
"github.com/romana/rlog"
|
||||||
|
"github.com/up9inc/mizu/shared"
|
||||||
|
"mizuserver/pkg/api"
|
||||||
|
"mizuserver/pkg/providers"
|
||||||
|
"mizuserver/pkg/validation"
|
||||||
|
"net/http"
|
||||||
|
)
|
||||||
|
|
||||||
|
func PostTappedPods(c *gin.Context) {
|
||||||
|
tapStatus := &shared.TapStatus{}
|
||||||
|
if err := c.Bind(tapStatus); err != nil {
|
||||||
|
c.JSON(http.StatusBadRequest, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if err := validation.Validate(tapStatus); err != nil {
|
||||||
|
c.JSON(http.StatusBadRequest, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
rlog.Infof("[Status] POST request: %d tapped pods", len(tapStatus.Pods))
|
||||||
|
providers.TapStatus.Pods = tapStatus.Pods
|
||||||
|
message := shared.CreateWebSocketStatusMessage(*tapStatus)
|
||||||
|
if jsonBytes, err := json.Marshal(message); err != nil {
|
||||||
|
rlog.Errorf("Could not Marshal message %v\n", err)
|
||||||
|
} else {
|
||||||
|
api.BroadcastToBrowserClients(jsonBytes)
|
||||||
|
}
|
||||||
|
}
|
||||||
73
agent/pkg/database/main.go
Normal file
73
agent/pkg/database/main.go
Normal file
@@ -0,0 +1,73 @@
|
|||||||
|
package database
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"gorm.io/driver/sqlite"
|
||||||
|
"gorm.io/gorm"
|
||||||
|
"gorm.io/gorm/logger"
|
||||||
|
"mizuserver/pkg/models"
|
||||||
|
"mizuserver/pkg/utils"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
DBPath = "./entries.db"
|
||||||
|
OrderDesc = "desc"
|
||||||
|
OrderAsc = "asc"
|
||||||
|
LT = "lt"
|
||||||
|
GT = "gt"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
DB *gorm.DB
|
||||||
|
IsDBLocked = false
|
||||||
|
OperatorToSymbolMapping = map[string]string{
|
||||||
|
LT: "<",
|
||||||
|
GT: ">",
|
||||||
|
}
|
||||||
|
OperatorToOrderMapping = map[string]string{
|
||||||
|
LT: OrderDesc,
|
||||||
|
GT: OrderAsc,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
DB = initDataBase(DBPath)
|
||||||
|
go StartEnforcingDatabaseSize()
|
||||||
|
}
|
||||||
|
|
||||||
|
func GetEntriesTable() *gorm.DB {
|
||||||
|
return DB.Table("mizu_entries")
|
||||||
|
}
|
||||||
|
|
||||||
|
func CreateEntry(entry *models.MizuEntry) {
|
||||||
|
if IsDBLocked {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
GetEntriesTable().Create(entry)
|
||||||
|
}
|
||||||
|
|
||||||
|
func initDataBase(databasePath string) *gorm.DB {
|
||||||
|
temp, _ := gorm.Open(sqlite.Open(databasePath), &gorm.Config{
|
||||||
|
Logger: &utils.TruncatingLogger{LogLevel: logger.Warn, SlowThreshold: 500 * time.Millisecond},
|
||||||
|
})
|
||||||
|
_ = temp.AutoMigrate(&models.MizuEntry{}) // this will ensure table is created
|
||||||
|
return temp
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
func GetEntriesFromDb(timestampFrom int64, timestampTo int64) []models.MizuEntry {
|
||||||
|
order := OrderDesc
|
||||||
|
var entries []models.MizuEntry
|
||||||
|
GetEntriesTable().
|
||||||
|
Where(fmt.Sprintf("timestamp BETWEEN %v AND %v", timestampFrom, timestampTo)).
|
||||||
|
Order(fmt.Sprintf("timestamp %s", order)).
|
||||||
|
Find(&entries)
|
||||||
|
|
||||||
|
if len(entries) > 0 {
|
||||||
|
// the entries always order from oldest to newest so we should revers
|
||||||
|
utils.ReverseSlice(entries)
|
||||||
|
}
|
||||||
|
return entries
|
||||||
|
}
|
||||||
|
|
||||||
121
agent/pkg/database/size_enforcer.go
Normal file
121
agent/pkg/database/size_enforcer.go
Normal file
@@ -0,0 +1,121 @@
|
|||||||
|
package database
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/fsnotify/fsnotify"
|
||||||
|
"github.com/romana/rlog"
|
||||||
|
"github.com/up9inc/mizu/shared"
|
||||||
|
"github.com/up9inc/mizu/shared/debounce"
|
||||||
|
"github.com/up9inc/mizu/shared/units"
|
||||||
|
"log"
|
||||||
|
"mizuserver/pkg/models"
|
||||||
|
"os"
|
||||||
|
"strconv"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
const percentageOfMaxSizeBytesToPrune = 15
|
||||||
|
const defaultMaxDatabaseSizeBytes int64 = 200 * 1000 * 1000
|
||||||
|
|
||||||
|
func StartEnforcingDatabaseSize() {
|
||||||
|
watcher, err := fsnotify.NewWatcher()
|
||||||
|
if err != nil {
|
||||||
|
log.Fatalf("Error creating filesystem watcher for db size enforcement: %v\n", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
maxEntriesDBByteSize, err := getMaxEntriesDBByteSize()
|
||||||
|
if err != nil {
|
||||||
|
log.Fatalf("Error parsing max db size: %v\n", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
checkFileSizeDebouncer := debounce.NewDebouncer(5*time.Second, func() {
|
||||||
|
checkFileSize(maxEntriesDBByteSize)
|
||||||
|
})
|
||||||
|
|
||||||
|
go func() {
|
||||||
|
for {
|
||||||
|
select {
|
||||||
|
case event, ok := <-watcher.Events:
|
||||||
|
if !ok {
|
||||||
|
return // closed channel
|
||||||
|
}
|
||||||
|
if event.Op == fsnotify.Write {
|
||||||
|
checkFileSizeDebouncer.SetOn()
|
||||||
|
}
|
||||||
|
case err, ok := <-watcher.Errors:
|
||||||
|
if !ok {
|
||||||
|
return // closed channel
|
||||||
|
}
|
||||||
|
rlog.Errorf("filesystem watcher encountered error:%v", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
err = watcher.Add(DBPath)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatalf("Error adding %s to filesystem watcher for db size enforcement: %v\n", DBPath, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func getMaxEntriesDBByteSize() (int64, error) {
|
||||||
|
maxEntriesDBByteSize := defaultMaxDatabaseSizeBytes
|
||||||
|
var err error
|
||||||
|
|
||||||
|
maxEntriesDBSizeByteSEnvVarValue := os.Getenv(shared.MaxEntriesDBSizeBytesEnvVar)
|
||||||
|
if maxEntriesDBSizeByteSEnvVarValue != "" {
|
||||||
|
maxEntriesDBByteSize, err = strconv.ParseInt(maxEntriesDBSizeByteSEnvVarValue, 10, 64)
|
||||||
|
}
|
||||||
|
return maxEntriesDBByteSize, err
|
||||||
|
}
|
||||||
|
|
||||||
|
func checkFileSize(maxSizeBytes int64) {
|
||||||
|
fileStat, err := os.Stat(DBPath)
|
||||||
|
if err != nil {
|
||||||
|
rlog.Errorf("Error checking %s file size: %v", DBPath, err)
|
||||||
|
} else {
|
||||||
|
if fileStat.Size() > maxSizeBytes {
|
||||||
|
pruneOldEntries(fileStat.Size())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func pruneOldEntries(currentFileSize int64) {
|
||||||
|
// sqlite locks the database while delete or VACUUM are running and sqlite is terrible at handling its own db lock while a lot of inserts are attempted, we prevent a significant bottleneck by handling the db lock ourselves here
|
||||||
|
IsDBLocked = true
|
||||||
|
defer func() { IsDBLocked = false }()
|
||||||
|
|
||||||
|
amountOfBytesToTrim := currentFileSize / (100 / percentageOfMaxSizeBytesToPrune)
|
||||||
|
|
||||||
|
rows, err := GetEntriesTable().Limit(10000).Order("id").Rows()
|
||||||
|
if err != nil {
|
||||||
|
rlog.Errorf("Error getting 10000 first db rows: %v", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
entryIdsToRemove := make([]uint, 0)
|
||||||
|
bytesToBeRemoved := int64(0)
|
||||||
|
for rows.Next() {
|
||||||
|
if bytesToBeRemoved >= amountOfBytesToTrim {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
var entry models.MizuEntry
|
||||||
|
err = DB.ScanRows(rows, &entry)
|
||||||
|
if err != nil {
|
||||||
|
rlog.Errorf("Error scanning db row: %v", err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
entryIdsToRemove = append(entryIdsToRemove, entry.ID)
|
||||||
|
bytesToBeRemoved += int64(entry.EstimatedSizeBytes)
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(entryIdsToRemove) > 0 {
|
||||||
|
GetEntriesTable().Where(entryIdsToRemove).Delete(models.MizuEntry{})
|
||||||
|
// VACUUM causes sqlite to shrink the db file after rows have been deleted, the db file will not shrink without this
|
||||||
|
DB.Exec("VACUUM")
|
||||||
|
rlog.Errorf("Removed %d rows and cleared %s", len(entryIdsToRemove), units.BytesToHumanReadable(bytesToBeRemoved))
|
||||||
|
} else {
|
||||||
|
rlog.Error("Found no rows to remove when pruning")
|
||||||
|
}
|
||||||
|
}
|
||||||
14
agent/pkg/holder/main.go
Normal file
14
agent/pkg/holder/main.go
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
package holder
|
||||||
|
|
||||||
|
import "mizuserver/pkg/resolver"
|
||||||
|
|
||||||
|
var k8sResolver *resolver.Resolver
|
||||||
|
|
||||||
|
func SetResolver(param *resolver.Resolver) {
|
||||||
|
k8sResolver = param
|
||||||
|
}
|
||||||
|
|
||||||
|
func GetResolver() *resolver.Resolver {
|
||||||
|
return k8sResolver
|
||||||
|
}
|
||||||
|
|
||||||
224
agent/pkg/models/models.go
Normal file
224
agent/pkg/models/models.go
Normal file
@@ -0,0 +1,224 @@
|
|||||||
|
package models
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
|
||||||
|
"mizuserver/pkg/rules"
|
||||||
|
"mizuserver/pkg/utils"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/google/martian/har"
|
||||||
|
"github.com/up9inc/mizu/shared"
|
||||||
|
"github.com/up9inc/mizu/tap"
|
||||||
|
)
|
||||||
|
|
||||||
|
type DataUnmarshaler interface {
|
||||||
|
UnmarshalData(*MizuEntry) error
|
||||||
|
}
|
||||||
|
|
||||||
|
func GetEntry(r *MizuEntry, v DataUnmarshaler) error {
|
||||||
|
return v.UnmarshalData(r)
|
||||||
|
}
|
||||||
|
|
||||||
|
type MizuEntry struct {
|
||||||
|
ID uint `gorm:"primarykey"`
|
||||||
|
CreatedAt time.Time
|
||||||
|
UpdatedAt time.Time
|
||||||
|
Entry string `json:"entry,omitempty" gorm:"column:entry"`
|
||||||
|
EntryId string `json:"entryId" gorm:"column:entryId"`
|
||||||
|
Url string `json:"url" gorm:"column:url"`
|
||||||
|
Method string `json:"method" gorm:"column:method"`
|
||||||
|
Status int `json:"status" gorm:"column:status"`
|
||||||
|
RequestSenderIp string `json:"requestSenderIp" gorm:"column:requestSenderIp"`
|
||||||
|
Service string `json:"service" gorm:"column:service"`
|
||||||
|
Timestamp int64 `json:"timestamp" gorm:"column:timestamp"`
|
||||||
|
Path string `json:"path" gorm:"column:path"`
|
||||||
|
ResolvedSource string `json:"resolvedSource,omitempty" gorm:"column:resolvedSource"`
|
||||||
|
ResolvedDestination string `json:"resolvedDestination,omitempty" gorm:"column:resolvedDestination"`
|
||||||
|
IsOutgoing bool `json:"isOutgoing,omitempty" gorm:"column:isOutgoing"`
|
||||||
|
EstimatedSizeBytes int `json:"-" gorm:"column:estimatedSizeBytes"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type BaseEntryDetails struct {
|
||||||
|
Id string `json:"id,omitempty"`
|
||||||
|
Url string `json:"url,omitempty"`
|
||||||
|
RequestSenderIp string `json:"requestSenderIp,omitempty"`
|
||||||
|
Service string `json:"service,omitempty"`
|
||||||
|
Path string `json:"path,omitempty"`
|
||||||
|
StatusCode int `json:"statusCode,omitempty"`
|
||||||
|
Method string `json:"method,omitempty"`
|
||||||
|
Timestamp int64 `json:"timestamp,omitempty"`
|
||||||
|
IsOutgoing bool `json:"isOutgoing,omitempty"`
|
||||||
|
Latency int64 `json:"latency,omitempty"`
|
||||||
|
Rules ApplicableRules `json:"rules,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type ApplicableRules struct {
|
||||||
|
Latency int64 `json:"latency,omitempty"`
|
||||||
|
Status bool `json:"status,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewApplicableRules(status bool, latency int64) ApplicableRules {
|
||||||
|
ar := ApplicableRules{}
|
||||||
|
ar.Status = status
|
||||||
|
ar.Latency = latency
|
||||||
|
return ar
|
||||||
|
}
|
||||||
|
|
||||||
|
type FullEntryDetails struct {
|
||||||
|
har.Entry
|
||||||
|
}
|
||||||
|
|
||||||
|
type FullEntryDetailsExtra struct {
|
||||||
|
har.Entry
|
||||||
|
}
|
||||||
|
|
||||||
|
func (bed *BaseEntryDetails) UnmarshalData(entry *MizuEntry) error {
|
||||||
|
entryUrl := entry.Url
|
||||||
|
service := entry.Service
|
||||||
|
if entry.ResolvedDestination != "" {
|
||||||
|
entryUrl = utils.SetHostname(entryUrl, entry.ResolvedDestination)
|
||||||
|
service = utils.SetHostname(service, entry.ResolvedDestination)
|
||||||
|
}
|
||||||
|
bed.Id = entry.EntryId
|
||||||
|
bed.Url = entryUrl
|
||||||
|
bed.Service = service
|
||||||
|
bed.Path = entry.Path
|
||||||
|
bed.StatusCode = entry.Status
|
||||||
|
bed.Method = entry.Method
|
||||||
|
bed.Timestamp = entry.Timestamp
|
||||||
|
bed.RequestSenderIp = entry.RequestSenderIp
|
||||||
|
bed.IsOutgoing = entry.IsOutgoing
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (fed *FullEntryDetails) UnmarshalData(entry *MizuEntry) error {
|
||||||
|
if err := json.Unmarshal([]byte(entry.Entry), &fed.Entry); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if entry.ResolvedDestination != "" {
|
||||||
|
fed.Entry.Request.URL = utils.SetHostname(fed.Entry.Request.URL, entry.ResolvedDestination)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (fedex *FullEntryDetailsExtra) UnmarshalData(entry *MizuEntry) error {
|
||||||
|
if err := json.Unmarshal([]byte(entry.Entry), &fedex.Entry); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if entry.ResolvedSource != "" {
|
||||||
|
fedex.Entry.Request.Headers = append(fedex.Request.Headers, har.Header{Name: "x-mizu-source", Value: entry.ResolvedSource})
|
||||||
|
}
|
||||||
|
if entry.ResolvedDestination != "" {
|
||||||
|
fedex.Entry.Request.Headers = append(fedex.Request.Headers, har.Header{Name: "x-mizu-destination", Value: entry.ResolvedDestination})
|
||||||
|
fedex.Entry.Request.URL = utils.SetHostname(fedex.Entry.Request.URL, entry.ResolvedDestination)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type EntriesFilter struct {
|
||||||
|
Limit int `query:"limit" validate:"required,min=1,max=200"`
|
||||||
|
Operator string `query:"operator" validate:"required,oneof='lt' 'gt'"`
|
||||||
|
Timestamp int64 `query:"timestamp" validate:"required,min=1"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type UploadEntriesRequestBody struct {
|
||||||
|
Dest string `form:"dest"`
|
||||||
|
SleepIntervalSec int `form:"interval"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type HarFetchRequestBody struct {
|
||||||
|
From int64 `query:"from"`
|
||||||
|
To int64 `query:"to"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type WebSocketEntryMessage struct {
|
||||||
|
*shared.WebSocketMessageMetadata
|
||||||
|
Data *BaseEntryDetails `json:"data,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type WebSocketTappedEntryMessage struct {
|
||||||
|
*shared.WebSocketMessageMetadata
|
||||||
|
Data *tap.OutputChannelItem
|
||||||
|
}
|
||||||
|
|
||||||
|
type WebsocketOutboundLinkMessage struct {
|
||||||
|
*shared.WebSocketMessageMetadata
|
||||||
|
Data *tap.OutboundLink
|
||||||
|
}
|
||||||
|
|
||||||
|
func CreateBaseEntryWebSocketMessage(base *BaseEntryDetails) ([]byte, error) {
|
||||||
|
message := &WebSocketEntryMessage{
|
||||||
|
WebSocketMessageMetadata: &shared.WebSocketMessageMetadata{
|
||||||
|
MessageType: shared.WebSocketMessageTypeEntry,
|
||||||
|
},
|
||||||
|
Data: base,
|
||||||
|
}
|
||||||
|
return json.Marshal(message)
|
||||||
|
}
|
||||||
|
|
||||||
|
func CreateWebsocketTappedEntryMessage(base *tap.OutputChannelItem) ([]byte, error) {
|
||||||
|
message := &WebSocketTappedEntryMessage{
|
||||||
|
WebSocketMessageMetadata: &shared.WebSocketMessageMetadata{
|
||||||
|
MessageType: shared.WebSocketMessageTypeTappedEntry,
|
||||||
|
},
|
||||||
|
Data: base,
|
||||||
|
}
|
||||||
|
return json.Marshal(message)
|
||||||
|
}
|
||||||
|
|
||||||
|
func CreateWebsocketOutboundLinkMessage(base *tap.OutboundLink) ([]byte, error) {
|
||||||
|
message := &WebsocketOutboundLinkMessage{
|
||||||
|
WebSocketMessageMetadata: &shared.WebSocketMessageMetadata{
|
||||||
|
MessageType: shared.WebsocketMessageTypeOutboundLink,
|
||||||
|
},
|
||||||
|
Data: base,
|
||||||
|
}
|
||||||
|
return json.Marshal(message)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ExtendedHAR is the top level object of a HAR log.
|
||||||
|
type ExtendedHAR struct {
|
||||||
|
Log *ExtendedLog `json:"log"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// ExtendedLog is the HAR HTTP request and response log.
|
||||||
|
type ExtendedLog struct {
|
||||||
|
// Version number of the HAR format.
|
||||||
|
Version string `json:"version"`
|
||||||
|
// Creator holds information about the log creator application.
|
||||||
|
Creator *ExtendedCreator `json:"creator"`
|
||||||
|
// Entries is a list containing requests and responses.
|
||||||
|
Entries []*har.Entry `json:"entries"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type ExtendedCreator struct {
|
||||||
|
*har.Creator
|
||||||
|
Source *string `json:"_source"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type FullEntryWithPolicy struct {
|
||||||
|
RulesMatched []rules.RulesMatched `json:"rulesMatched,omitempty"`
|
||||||
|
Entry har.Entry `json:"entry"`
|
||||||
|
Service string `json:"service"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (fewp *FullEntryWithPolicy) UnmarshalData(entry *MizuEntry) error {
|
||||||
|
if err := json.Unmarshal([]byte(entry.Entry), &fewp.Entry); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
_, resultPolicyToSend := rules.MatchRequestPolicy(fewp.Entry, entry.Service)
|
||||||
|
fewp.RulesMatched = resultPolicyToSend
|
||||||
|
fewp.Service = entry.Service
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func RunValidationRulesState(harEntry har.Entry, service string) ApplicableRules {
|
||||||
|
numberOfRules, resultPolicyToSend := rules.MatchRequestPolicy(harEntry, service)
|
||||||
|
statusPolicyToSend, latency := rules.PassedValidationRules(resultPolicyToSend, numberOfRules)
|
||||||
|
ar := NewApplicableRules(statusPolicyToSend, latency)
|
||||||
|
return ar
|
||||||
|
}
|
||||||
28
agent/pkg/providers/status_provider.go
Normal file
28
agent/pkg/providers/status_provider.go
Normal file
@@ -0,0 +1,28 @@
|
|||||||
|
package providers
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/patrickmn/go-cache"
|
||||||
|
"github.com/up9inc/mizu/shared"
|
||||||
|
"github.com/up9inc/mizu/tap"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
const tlsLinkRetainmentTime = time.Minute * 15
|
||||||
|
|
||||||
|
var (
|
||||||
|
TapStatus shared.TapStatus
|
||||||
|
RecentTLSLinks = cache.New(tlsLinkRetainmentTime, tlsLinkRetainmentTime)
|
||||||
|
)
|
||||||
|
|
||||||
|
func GetAllRecentTLSAddresses() []string {
|
||||||
|
recentTLSLinks := make([]string, 0)
|
||||||
|
|
||||||
|
for _, outboundLinkItem := range RecentTLSLinks.Items() {
|
||||||
|
outboundLink, castOk := outboundLinkItem.Object.(*tap.OutboundLink)
|
||||||
|
if castOk {
|
||||||
|
recentTLSLinks = append(recentTLSLinks, outboundLink.DstIP)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return recentTLSLinks
|
||||||
|
}
|
||||||
@@ -32,7 +32,7 @@ Now you will be able to import `github.com/up9inc/mizu/resolver` in any `.go` fi
|
|||||||
errOut := make(chan error, 100)
|
errOut := make(chan error, 100)
|
||||||
k8sResolver, err := resolver.NewFromOutOfCluster("", errOut)
|
k8sResolver, err := resolver.NewFromOutOfCluster("", errOut)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Printf("error creating k8s resolver %s", err)
|
rlog.Errorf("error creating k8s resolver %s", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
ctx, cancel := context.WithCancel(context.Background())
|
ctx, cancel := context.WithCancel(context.Background())
|
||||||
@@ -40,15 +40,15 @@ k8sResolver.Start(ctx)
|
|||||||
|
|
||||||
resolvedName := k8sResolver.Resolve("10.107.251.91") // will always return `nil` in real scenarios as the internal map takes a moment to populate after `Start` is called
|
resolvedName := k8sResolver.Resolve("10.107.251.91") // will always return `nil` in real scenarios as the internal map takes a moment to populate after `Start` is called
|
||||||
if resolvedName != nil {
|
if resolvedName != nil {
|
||||||
fmt.Printf("resolved 10.107.251.91=%s", *resolvedName)
|
rlog.Errorf("resolved 10.107.251.91=%s", *resolvedName)
|
||||||
} else {
|
} else {
|
||||||
fmt.Printf("Could not find a resolved name for 10.107.251.91")
|
rlog.Error("Could not find a resolved name for 10.107.251.91")
|
||||||
}
|
}
|
||||||
|
|
||||||
for {
|
for {
|
||||||
select {
|
select {
|
||||||
case err := <- errOut:
|
case err := <- errOut:
|
||||||
fmt.Printf("name resolving error %s", err)
|
rlog.Errorf("name resolving error %s", err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
23
agent/pkg/resolver/loader.go
Normal file
23
agent/pkg/resolver/loader.go
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
package resolver
|
||||||
|
|
||||||
|
import (
|
||||||
|
cmap "github.com/orcaman/concurrent-map"
|
||||||
|
"k8s.io/client-go/kubernetes"
|
||||||
|
_ "k8s.io/client-go/plugin/pkg/client/auth/azure"
|
||||||
|
_ "k8s.io/client-go/plugin/pkg/client/auth/gcp"
|
||||||
|
_ "k8s.io/client-go/plugin/pkg/client/auth/oidc"
|
||||||
|
_ "k8s.io/client-go/plugin/pkg/client/auth/openstack"
|
||||||
|
restclient "k8s.io/client-go/rest"
|
||||||
|
)
|
||||||
|
|
||||||
|
func NewFromInCluster(errOut chan error, namesapce string) (*Resolver, error) {
|
||||||
|
config, err := restclient.InClusterConfig()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
clientset, err := kubernetes.NewForConfig(config)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return &Resolver{clientConfig: config, clientSet: clientset, nameMap: cmap.New(), serviceMap: cmap.New(), errOut: errOut, namespace: namesapce}, nil
|
||||||
|
}
|
||||||
192
agent/pkg/resolver/resolver.go
Normal file
192
agent/pkg/resolver/resolver.go
Normal file
@@ -0,0 +1,192 @@
|
|||||||
|
package resolver
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"github.com/romana/rlog"
|
||||||
|
k8serrors "k8s.io/apimachinery/pkg/api/errors"
|
||||||
|
|
||||||
|
"github.com/orcaman/concurrent-map"
|
||||||
|
corev1 "k8s.io/api/core/v1"
|
||||||
|
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||||
|
"k8s.io/apimachinery/pkg/watch"
|
||||||
|
"k8s.io/client-go/kubernetes"
|
||||||
|
restclient "k8s.io/client-go/rest"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
kubClientNullString = "None"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Resolver struct {
|
||||||
|
clientConfig *restclient.Config
|
||||||
|
clientSet *kubernetes.Clientset
|
||||||
|
nameMap cmap.ConcurrentMap
|
||||||
|
serviceMap cmap.ConcurrentMap
|
||||||
|
isStarted bool
|
||||||
|
errOut chan error
|
||||||
|
namespace string
|
||||||
|
}
|
||||||
|
|
||||||
|
func (resolver *Resolver) Start(ctx context.Context) {
|
||||||
|
if !resolver.isStarted {
|
||||||
|
resolver.isStarted = true
|
||||||
|
|
||||||
|
go resolver.infiniteErrorHandleRetryFunc(ctx, resolver.watchServices)
|
||||||
|
go resolver.infiniteErrorHandleRetryFunc(ctx, resolver.watchEndpoints)
|
||||||
|
go resolver.infiniteErrorHandleRetryFunc(ctx, resolver.watchPods)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (resolver *Resolver) Resolve(name string) string {
|
||||||
|
resolvedName, isFound := resolver.nameMap.Get(name)
|
||||||
|
if !isFound {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
return resolvedName.(string)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (resolver *Resolver) GetMap() cmap.ConcurrentMap {
|
||||||
|
return resolver.nameMap
|
||||||
|
}
|
||||||
|
|
||||||
|
func (resolver *Resolver) CheckIsServiceIP(address string) bool {
|
||||||
|
_, isFound := resolver.serviceMap.Get(address)
|
||||||
|
return isFound
|
||||||
|
}
|
||||||
|
|
||||||
|
func (resolver *Resolver) watchPods(ctx context.Context) error {
|
||||||
|
// empty namespace makes the client watch all namespaces
|
||||||
|
watcher, err := resolver.clientSet.CoreV1().Pods(resolver.namespace).Watch(ctx, metav1.ListOptions{Watch: true})
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
for {
|
||||||
|
select {
|
||||||
|
case event := <-watcher.ResultChan():
|
||||||
|
if event.Object == nil {
|
||||||
|
return errors.New("error in kubectl pod watch")
|
||||||
|
}
|
||||||
|
if event.Type == watch.Deleted {
|
||||||
|
pod := event.Object.(*corev1.Pod)
|
||||||
|
resolver.saveResolvedName(pod.Status.PodIP, "", event.Type)
|
||||||
|
}
|
||||||
|
case <-ctx.Done():
|
||||||
|
watcher.Stop()
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (resolver *Resolver) watchEndpoints(ctx context.Context) error {
|
||||||
|
// empty namespace makes the client watch all namespaces
|
||||||
|
watcher, err := resolver.clientSet.CoreV1().Endpoints(resolver.namespace).Watch(ctx, metav1.ListOptions{Watch: true})
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
for {
|
||||||
|
select {
|
||||||
|
case event := <-watcher.ResultChan():
|
||||||
|
if event.Object == nil {
|
||||||
|
return errors.New("error in kubectl endpoint watch")
|
||||||
|
}
|
||||||
|
endpoint := event.Object.(*corev1.Endpoints)
|
||||||
|
serviceHostname := fmt.Sprintf("%s.%s", endpoint.Name, endpoint.Namespace)
|
||||||
|
if endpoint.Subsets != nil {
|
||||||
|
for _, subset := range endpoint.Subsets {
|
||||||
|
var ports []int32
|
||||||
|
if subset.Ports != nil {
|
||||||
|
for _, portMapping := range subset.Ports {
|
||||||
|
if portMapping.Port > 0 {
|
||||||
|
ports = append(ports, portMapping.Port)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if subset.Addresses != nil {
|
||||||
|
for _, address := range subset.Addresses {
|
||||||
|
resolver.saveResolvedName(address.IP, serviceHostname, event.Type)
|
||||||
|
for _, port := range ports {
|
||||||
|
ipWithPort := fmt.Sprintf("%s:%d", address.IP, port)
|
||||||
|
resolver.saveResolvedName(ipWithPort, serviceHostname, event.Type)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
case <-ctx.Done():
|
||||||
|
watcher.Stop()
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (resolver *Resolver) watchServices(ctx context.Context) error {
|
||||||
|
// empty namespace makes the client watch all namespaces
|
||||||
|
watcher, err := resolver.clientSet.CoreV1().Services(resolver.namespace).Watch(ctx, metav1.ListOptions{Watch: true})
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
for {
|
||||||
|
select {
|
||||||
|
case event := <-watcher.ResultChan():
|
||||||
|
if event.Object == nil {
|
||||||
|
return errors.New("error in kubectl service watch")
|
||||||
|
}
|
||||||
|
|
||||||
|
service := event.Object.(*corev1.Service)
|
||||||
|
serviceHostname := fmt.Sprintf("%s.%s", service.Name, service.Namespace)
|
||||||
|
if service.Spec.ClusterIP != "" && service.Spec.ClusterIP != kubClientNullString {
|
||||||
|
resolver.saveResolvedName(service.Spec.ClusterIP, serviceHostname, event.Type)
|
||||||
|
resolver.saveServiceIP(service.Spec.ClusterIP, serviceHostname, event.Type)
|
||||||
|
}
|
||||||
|
if service.Status.LoadBalancer.Ingress != nil {
|
||||||
|
for _, ingress := range service.Status.LoadBalancer.Ingress {
|
||||||
|
resolver.saveResolvedName(ingress.IP, serviceHostname, event.Type)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
case <-ctx.Done():
|
||||||
|
watcher.Stop()
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (resolver *Resolver) saveResolvedName(key string, resolved string, eventType watch.EventType) {
|
||||||
|
if eventType == watch.Deleted {
|
||||||
|
resolver.nameMap.Remove(key)
|
||||||
|
rlog.Infof("setting %s=nil\n", key)
|
||||||
|
} else {
|
||||||
|
resolver.nameMap.Set(key, resolved)
|
||||||
|
rlog.Infof("setting %s=%s\n", key, resolved)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (resolver *Resolver) saveServiceIP(key string, resolved string, eventType watch.EventType) {
|
||||||
|
if eventType == watch.Deleted {
|
||||||
|
resolver.serviceMap.Remove(key)
|
||||||
|
} else {
|
||||||
|
resolver.serviceMap.Set(key, resolved)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (resolver *Resolver) infiniteErrorHandleRetryFunc(ctx context.Context, fun func(ctx context.Context) error) {
|
||||||
|
for {
|
||||||
|
err := fun(ctx)
|
||||||
|
if err != nil {
|
||||||
|
resolver.errOut <- err
|
||||||
|
|
||||||
|
var statusError *k8serrors.StatusError
|
||||||
|
if errors.As(err, &statusError) {
|
||||||
|
if statusError.ErrStatus.Reason == metav1.StatusReasonForbidden {
|
||||||
|
rlog.Infof("Resolver loop encountered permission error, aborting event listening - %v\n", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if ctx.Err() != nil { // context was cancelled or errored
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
26
agent/pkg/routes/entries_routes.go
Normal file
26
agent/pkg/routes/entries_routes.go
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
package routes
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/gin-gonic/gin"
|
||||||
|
"mizuserver/pkg/controllers"
|
||||||
|
)
|
||||||
|
|
||||||
|
// EntriesRoutes defines the group of har entries routes.
|
||||||
|
func EntriesRoutes(ginApp *gin.Engine) {
|
||||||
|
routeGroup := ginApp.Group("/api")
|
||||||
|
|
||||||
|
routeGroup.GET("/entries", controllers.GetEntries) // get entries (base/thin entries)
|
||||||
|
routeGroup.GET("/entries/:entryId", controllers.GetEntry) // get single (full) entry
|
||||||
|
routeGroup.GET("/exportEntries", controllers.GetFullEntries)
|
||||||
|
routeGroup.GET("/uploadEntries", controllers.UploadEntries)
|
||||||
|
routeGroup.GET("/resolving", controllers.GetCurrentResolvingInformation)
|
||||||
|
|
||||||
|
routeGroup.GET("/har", controllers.GetHARs)
|
||||||
|
|
||||||
|
routeGroup.GET("/resetDB", controllers.DeleteAllEntries) // get single (full) entry
|
||||||
|
routeGroup.GET("/generalStats", controllers.GetGeneralStats) // get general stats about entries in DB
|
||||||
|
|
||||||
|
routeGroup.GET("/tapStatus", controllers.GetTappingStatus) // get tapping status
|
||||||
|
routeGroup.GET("/analyzeStatus", controllers.AnalyzeInformation)
|
||||||
|
routeGroup.GET("/recentTLSLinks", controllers.GetRecentTLSLinks)
|
||||||
|
}
|
||||||
13
agent/pkg/routes/metadata_routes.go
Normal file
13
agent/pkg/routes/metadata_routes.go
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
package routes
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/gin-gonic/gin"
|
||||||
|
"mizuserver/pkg/controllers"
|
||||||
|
)
|
||||||
|
|
||||||
|
// MetadataRoutes defines the group of metadata routes.
|
||||||
|
func MetadataRoutes(app *gin.Engine) {
|
||||||
|
routeGroup := app.Group("/metadata")
|
||||||
|
|
||||||
|
routeGroup.GET("/version", controllers.GetVersion)
|
||||||
|
}
|
||||||
18
agent/pkg/routes/not_found_route.go
Normal file
18
agent/pkg/routes/not_found_route.go
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
package routes
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/gin-gonic/gin"
|
||||||
|
"net/http"
|
||||||
|
)
|
||||||
|
|
||||||
|
// NotFoundRoute defines the 404 Error route.
|
||||||
|
func NotFoundRoute(app *gin.Engine) {
|
||||||
|
app.Use(
|
||||||
|
func(c *gin.Context) {
|
||||||
|
c.JSON(http.StatusNotFound, map[string]interface{}{
|
||||||
|
"error": true,
|
||||||
|
"msg": "sorry, endpoint is not found",
|
||||||
|
})
|
||||||
|
},
|
||||||
|
)
|
||||||
|
}
|
||||||
12
agent/pkg/routes/status_routes.go
Normal file
12
agent/pkg/routes/status_routes.go
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
package routes
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/gin-gonic/gin"
|
||||||
|
"mizuserver/pkg/controllers"
|
||||||
|
)
|
||||||
|
|
||||||
|
func StatusRoutes(ginApp *gin.Engine) {
|
||||||
|
routeGroup := ginApp.Group("/status")
|
||||||
|
|
||||||
|
routeGroup.POST("/tappedPods", controllers.PostTappedPods)
|
||||||
|
}
|
||||||
110
agent/pkg/rules/models.go
Normal file
110
agent/pkg/rules/models.go
Normal file
@@ -0,0 +1,110 @@
|
|||||||
|
package rules
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"reflect"
|
||||||
|
"regexp"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/google/martian/har"
|
||||||
|
"github.com/up9inc/mizu/shared"
|
||||||
|
jsonpath "github.com/yalp/jsonpath"
|
||||||
|
)
|
||||||
|
|
||||||
|
type RulesMatched struct {
|
||||||
|
Matched bool `json:"matched"`
|
||||||
|
Rule shared.RulePolicy `json:"rule"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func appendRulesMatched(rulesMatched []RulesMatched, matched bool, rule shared.RulePolicy) []RulesMatched {
|
||||||
|
return append(rulesMatched, RulesMatched{Matched: matched, Rule: rule})
|
||||||
|
}
|
||||||
|
|
||||||
|
func ValidatePath(URLFromRule string, URL string) bool {
|
||||||
|
if URLFromRule != "" {
|
||||||
|
matchPath, err := regexp.MatchString(URLFromRule, URL)
|
||||||
|
if err != nil || !matchPath {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
func ValidateService(serviceFromRule string, service string) bool {
|
||||||
|
if serviceFromRule != "" {
|
||||||
|
matchService, err := regexp.MatchString(serviceFromRule, service)
|
||||||
|
if err != nil || !matchService {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
func MatchRequestPolicy(harEntry har.Entry, service string) (int, []RulesMatched) {
|
||||||
|
enforcePolicy, _ := shared.DecodeEnforcePolicy(fmt.Sprintf("%s/%s", shared.RulePolicyPath, shared.RulePolicyFileName))
|
||||||
|
var resultPolicyToSend []RulesMatched
|
||||||
|
for _, rule := range enforcePolicy.Rules {
|
||||||
|
if !ValidatePath(rule.Path, harEntry.Request.URL) || !ValidateService(rule.Service, service) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if rule.Type == "json" {
|
||||||
|
var bodyJsonMap interface{}
|
||||||
|
if err := json.Unmarshal(harEntry.Response.Content.Text, &bodyJsonMap); err != nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
out, err := jsonpath.Read(bodyJsonMap, rule.Key)
|
||||||
|
if err != nil || out == nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
var matchValue bool
|
||||||
|
if reflect.TypeOf(out).Kind() == reflect.String {
|
||||||
|
matchValue, err = regexp.MatchString(rule.Value, out.(string))
|
||||||
|
if err != nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
val := fmt.Sprint(out)
|
||||||
|
matchValue, err = regexp.MatchString(rule.Value, val)
|
||||||
|
if err != nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
resultPolicyToSend = appendRulesMatched(resultPolicyToSend, matchValue, rule)
|
||||||
|
} else if rule.Type == "header" {
|
||||||
|
for j := range harEntry.Response.Headers {
|
||||||
|
matchKey, err := regexp.MatchString(rule.Key, harEntry.Response.Headers[j].Name)
|
||||||
|
if err != nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if matchKey {
|
||||||
|
matchValue, err := regexp.MatchString(rule.Value, harEntry.Response.Headers[j].Value)
|
||||||
|
if err != nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
resultPolicyToSend = appendRulesMatched(resultPolicyToSend, matchValue, rule)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
resultPolicyToSend = appendRulesMatched(resultPolicyToSend, true, rule)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return len(enforcePolicy.Rules), resultPolicyToSend
|
||||||
|
}
|
||||||
|
|
||||||
|
func PassedValidationRules(rulesMatched []RulesMatched, numberOfRules int) (bool, int64) {
|
||||||
|
if len(rulesMatched) == 0 {
|
||||||
|
return false, 0
|
||||||
|
}
|
||||||
|
for _, rule := range rulesMatched {
|
||||||
|
if rule.Matched == false {
|
||||||
|
return false, -1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for _, rule := range rulesMatched {
|
||||||
|
if strings.ToLower(rule.Rule.Type) == "latency" {
|
||||||
|
return true, rule.Rule.Latency
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true, -1
|
||||||
|
}
|
||||||
@@ -5,7 +5,7 @@ import (
|
|||||||
"encoding/xml"
|
"encoding/xml"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"mizuserver/pkg/tap"
|
"github.com/up9inc/mizu/tap"
|
||||||
"net/url"
|
"net/url"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
@@ -15,8 +15,8 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
func FilterSensitiveInfoFromHarRequest(harOutputItem *tap.OutputChannelItem, options *shared.TrafficFilteringOptions) {
|
func FilterSensitiveInfoFromHarRequest(harOutputItem *tap.OutputChannelItem, options *shared.TrafficFilteringOptions) {
|
||||||
filterHarHeaders(harOutputItem.HarEntry.Request.Headers)
|
harOutputItem.HarEntry.Request.Headers = filterHarHeaders(harOutputItem.HarEntry.Request.Headers)
|
||||||
filterHarHeaders(harOutputItem.HarEntry.Response.Headers)
|
harOutputItem.HarEntry.Response.Headers = filterHarHeaders(harOutputItem.HarEntry.Response.Headers)
|
||||||
|
|
||||||
harOutputItem.HarEntry.Request.Cookies = make([]har.Cookie, 0, 0)
|
harOutputItem.HarEntry.Request.Cookies = make([]har.Cookie, 0, 0)
|
||||||
harOutputItem.HarEntry.Response.Cookies = make([]har.Cookie, 0, 0)
|
harOutputItem.HarEntry.Response.Cookies = make([]har.Cookie, 0, 0)
|
||||||
@@ -44,12 +44,19 @@ func FilterSensitiveInfoFromHarRequest(harOutputItem *tap.OutputChannelItem, opt
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func filterHarHeaders(headers []har.Header) {
|
func filterHarHeaders(headers []har.Header) []har.Header {
|
||||||
|
newHeaders := make([]har.Header, 0)
|
||||||
for i, header := range headers {
|
for i, header := range headers {
|
||||||
if isFieldNameSensitive(header.Name) {
|
if strings.ToLower(header.Name) == "cookie" {
|
||||||
|
continue
|
||||||
|
} else if isFieldNameSensitive(header.Name) {
|
||||||
|
newHeaders = append(newHeaders, har.Header{Name: header.Name, Value: maskedFieldPlaceholderValue})
|
||||||
headers[i].Value = maskedFieldPlaceholderValue
|
headers[i].Value = maskedFieldPlaceholderValue
|
||||||
|
} else {
|
||||||
|
newHeaders = append(newHeaders, header)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
return newHeaders
|
||||||
}
|
}
|
||||||
|
|
||||||
func getContentTypeHeaderValue(headers []har.Header) string {
|
func getContentTypeHeaderValue(headers []har.Header) string {
|
||||||
201
agent/pkg/up9/main.go
Normal file
201
agent/pkg/up9/main.go
Normal file
@@ -0,0 +1,201 @@
|
|||||||
|
package up9
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"compress/zlib"
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"github.com/romana/rlog"
|
||||||
|
"github.com/up9inc/mizu/shared"
|
||||||
|
"io/ioutil"
|
||||||
|
"log"
|
||||||
|
"mizuserver/pkg/database"
|
||||||
|
"mizuserver/pkg/models"
|
||||||
|
"net/http"
|
||||||
|
"net/url"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
AnalyzeCheckSleepTime = 5 * time.Second
|
||||||
|
)
|
||||||
|
|
||||||
|
type GuestToken struct {
|
||||||
|
Token string `json:"token"`
|
||||||
|
Model string `json:"model"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type ModelStatus struct {
|
||||||
|
LastMajorGeneration float64 `json:"lastMajorGeneration"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func getGuestToken(url string, target *GuestToken) error {
|
||||||
|
resp, err := http.Get(url)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer resp.Body.Close()
|
||||||
|
rlog.Infof("Got token from the server, starting to json decode... status code: %v", resp.StatusCode)
|
||||||
|
return json.NewDecoder(resp.Body).Decode(target)
|
||||||
|
}
|
||||||
|
|
||||||
|
func CreateAnonymousToken(envPrefix string) (*GuestToken, error) {
|
||||||
|
tokenUrl := fmt.Sprintf("https://trcc.%s/anonymous/token", envPrefix)
|
||||||
|
if strings.HasPrefix(envPrefix, "http") {
|
||||||
|
tokenUrl = fmt.Sprintf("%s/api/token", envPrefix)
|
||||||
|
}
|
||||||
|
token := &GuestToken{}
|
||||||
|
if err := getGuestToken(tokenUrl, token); err != nil {
|
||||||
|
rlog.Infof("Failed to get token, %s", err)
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return token, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func GetRemoteUrl(analyzeDestination string, analyzeToken string) string {
|
||||||
|
return fmt.Sprintf("https://%s/share/%s", analyzeDestination, analyzeToken)
|
||||||
|
}
|
||||||
|
|
||||||
|
func CheckIfModelReady(analyzeDestination string, analyzeModel string, analyzeToken string) bool {
|
||||||
|
statusUrl, _ := url.Parse(fmt.Sprintf("https://trcc.%s/models/%s/status", analyzeDestination, analyzeModel))
|
||||||
|
req := &http.Request{
|
||||||
|
Method: http.MethodGet,
|
||||||
|
URL: statusUrl,
|
||||||
|
Header: map[string][]string{
|
||||||
|
"Content-Type": {"application/json"},
|
||||||
|
"Guest-Auth": {analyzeToken},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
statusResp, err := http.DefaultClient.Do(req)
|
||||||
|
if err != nil {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
defer statusResp.Body.Close()
|
||||||
|
|
||||||
|
target := &ModelStatus{}
|
||||||
|
_ = json.NewDecoder(statusResp.Body).Decode(&target)
|
||||||
|
|
||||||
|
return target.LastMajorGeneration > 0
|
||||||
|
}
|
||||||
|
|
||||||
|
func GetTrafficDumpUrl(analyzeDestination string, analyzeModel string) *url.URL {
|
||||||
|
strUrl := fmt.Sprintf("https://traffic.%s/dumpTrafficBulk/%s", analyzeDestination, analyzeModel)
|
||||||
|
if strings.HasPrefix(analyzeDestination, "http") {
|
||||||
|
strUrl = fmt.Sprintf("%s/api/workspace/dumpTrafficBulk", analyzeDestination)
|
||||||
|
}
|
||||||
|
postUrl, _ := url.Parse(strUrl)
|
||||||
|
return postUrl
|
||||||
|
}
|
||||||
|
|
||||||
|
type AnalyzeInformation struct {
|
||||||
|
IsAnalyzing bool
|
||||||
|
SentCount int
|
||||||
|
AnalyzedModel string
|
||||||
|
AnalyzeToken string
|
||||||
|
AnalyzeDestination string
|
||||||
|
}
|
||||||
|
|
||||||
|
func (info *AnalyzeInformation) Reset() {
|
||||||
|
info.IsAnalyzing = false
|
||||||
|
info.AnalyzedModel = ""
|
||||||
|
info.AnalyzeToken = ""
|
||||||
|
info.AnalyzeDestination = ""
|
||||||
|
info.SentCount = 0
|
||||||
|
}
|
||||||
|
|
||||||
|
var analyzeInformation = &AnalyzeInformation{}
|
||||||
|
|
||||||
|
func GetAnalyzeInfo() *shared.AnalyzeStatus {
|
||||||
|
return &shared.AnalyzeStatus{
|
||||||
|
IsAnalyzing: analyzeInformation.IsAnalyzing,
|
||||||
|
RemoteUrl: GetRemoteUrl(analyzeInformation.AnalyzeDestination, analyzeInformation.AnalyzeToken),
|
||||||
|
IsRemoteReady: CheckIfModelReady(analyzeInformation.AnalyzeDestination, analyzeInformation.AnalyzedModel, analyzeInformation.AnalyzeToken),
|
||||||
|
SentCount: analyzeInformation.SentCount,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func UploadEntriesImpl(token string, model string, envPrefix string, sleepIntervalSec int) {
|
||||||
|
analyzeInformation.IsAnalyzing = true
|
||||||
|
analyzeInformation.AnalyzedModel = model
|
||||||
|
analyzeInformation.AnalyzeToken = token
|
||||||
|
analyzeInformation.AnalyzeDestination = envPrefix
|
||||||
|
analyzeInformation.SentCount = 0
|
||||||
|
|
||||||
|
sleepTime := time.Second * time.Duration(sleepIntervalSec)
|
||||||
|
|
||||||
|
var timestampFrom int64 = 0
|
||||||
|
|
||||||
|
for {
|
||||||
|
timestampTo := time.Now().UnixNano() / int64(time.Millisecond)
|
||||||
|
rlog.Infof("Getting entries from %v, to %v\n", timestampFrom, timestampTo)
|
||||||
|
entriesArray := database.GetEntriesFromDb(timestampFrom, timestampTo)
|
||||||
|
|
||||||
|
if len(entriesArray) > 0 {
|
||||||
|
|
||||||
|
fullEntriesExtra := make([]models.FullEntryDetailsExtra, 0)
|
||||||
|
for _, data := range entriesArray {
|
||||||
|
harEntry := models.FullEntryDetailsExtra{}
|
||||||
|
if err := models.GetEntry(&data, &harEntry); err != nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
fullEntriesExtra = append(fullEntriesExtra, harEntry)
|
||||||
|
}
|
||||||
|
rlog.Infof("About to upload %v entries\n", len(fullEntriesExtra))
|
||||||
|
|
||||||
|
body, jMarshalErr := json.Marshal(fullEntriesExtra)
|
||||||
|
if jMarshalErr != nil {
|
||||||
|
analyzeInformation.Reset()
|
||||||
|
rlog.Infof("Stopping analyzing")
|
||||||
|
log.Fatal(jMarshalErr)
|
||||||
|
}
|
||||||
|
|
||||||
|
var in bytes.Buffer
|
||||||
|
w := zlib.NewWriter(&in)
|
||||||
|
_, _ = w.Write(body)
|
||||||
|
_ = w.Close()
|
||||||
|
reqBody := ioutil.NopCloser(bytes.NewReader(in.Bytes()))
|
||||||
|
|
||||||
|
req := &http.Request{
|
||||||
|
Method: http.MethodPost,
|
||||||
|
URL: GetTrafficDumpUrl(envPrefix, model),
|
||||||
|
Header: map[string][]string{
|
||||||
|
"Content-Encoding": {"deflate"},
|
||||||
|
"Content-Type": {"application/octet-stream"},
|
||||||
|
"Guest-Auth": {token},
|
||||||
|
},
|
||||||
|
Body: reqBody,
|
||||||
|
}
|
||||||
|
|
||||||
|
if _, postErr := http.DefaultClient.Do(req); postErr != nil {
|
||||||
|
analyzeInformation.Reset()
|
||||||
|
rlog.Info("Stopping analyzing")
|
||||||
|
log.Fatal(postErr)
|
||||||
|
}
|
||||||
|
analyzeInformation.SentCount += len(entriesArray)
|
||||||
|
rlog.Infof("Finish uploading %v entries to %s\n", len(entriesArray), GetTrafficDumpUrl(envPrefix, model))
|
||||||
|
|
||||||
|
} else {
|
||||||
|
rlog.Infof("Nothing to upload")
|
||||||
|
}
|
||||||
|
|
||||||
|
rlog.Infof("Sleeping for %v...\n", sleepTime)
|
||||||
|
time.Sleep(sleepTime)
|
||||||
|
timestampFrom = timestampTo
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func UpdateAnalyzeStatus(callback func(data []byte)) {
|
||||||
|
for {
|
||||||
|
if !analyzeInformation.IsAnalyzing {
|
||||||
|
time.Sleep(AnalyzeCheckSleepTime)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
analyzeStatus := GetAnalyzeInfo()
|
||||||
|
socketMessage := shared.CreateWebSocketMessageTypeAnalyzeStatus(*analyzeStatus)
|
||||||
|
|
||||||
|
jsonMessage, _ := json.Marshal(socketMessage)
|
||||||
|
callback(jsonMessage)
|
||||||
|
time.Sleep(AnalyzeCheckSleepTime)
|
||||||
|
}
|
||||||
|
}
|
||||||
59
agent/pkg/utils/truncating_logger.go
Normal file
59
agent/pkg/utils/truncating_logger.go
Normal file
@@ -0,0 +1,59 @@
|
|||||||
|
package utils
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
"github.com/romana/rlog"
|
||||||
|
"gorm.io/gorm/logger"
|
||||||
|
"gorm.io/gorm/utils"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
// TruncatingLogger implements the gorm logger.Interface interface. Its purpose is to act as gorm's logger while truncating logs to a max of 50 characters to minimise the performance impact
|
||||||
|
type TruncatingLogger struct {
|
||||||
|
LogLevel logger.LogLevel
|
||||||
|
SlowThreshold time.Duration
|
||||||
|
}
|
||||||
|
|
||||||
|
func (truncatingLogger *TruncatingLogger) LogMode(logLevel logger.LogLevel) logger.Interface {
|
||||||
|
truncatingLogger.LogLevel = logLevel
|
||||||
|
return truncatingLogger
|
||||||
|
}
|
||||||
|
|
||||||
|
func (truncatingLogger *TruncatingLogger) Info(_ context.Context, message string, __ ...interface{}) {
|
||||||
|
if truncatingLogger.LogLevel < logger.Info {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
rlog.Errorf("gorm info: %.150s", message)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (truncatingLogger *TruncatingLogger) Warn(_ context.Context, message string, __ ...interface{}) {
|
||||||
|
if truncatingLogger.LogLevel < logger.Warn {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
rlog.Errorf("gorm warning: %.150s", message)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (truncatingLogger *TruncatingLogger) Error(_ context.Context, message string, __ ...interface{}) {
|
||||||
|
if truncatingLogger.LogLevel < logger.Error {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
rlog.Errorf("gorm error: %.150s", message)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (truncatingLogger *TruncatingLogger) Trace(ctx context.Context, begin time.Time, fc func() (string, int64), err error) {
|
||||||
|
if truncatingLogger.LogLevel == logger.Silent {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
elapsed := time.Since(begin)
|
||||||
|
if err != nil {
|
||||||
|
sql, rows := fc() // copied into every condition as this is a potentially heavy operation best done only when necessary
|
||||||
|
truncatingLogger.Error(ctx, fmt.Sprintf("Error in %s: %v - elapsed: %fs affected rows: %d, sql: %s", utils.FileWithLineNum(), err, elapsed.Seconds(), rows, sql))
|
||||||
|
} else if truncatingLogger.LogLevel >= logger.Warn && elapsed > truncatingLogger.SlowThreshold {
|
||||||
|
sql, rows := fc()
|
||||||
|
truncatingLogger.Warn(ctx, fmt.Sprintf("Slow sql query - elapse: %fs rows: %d, sql: %s", elapsed.Seconds(), rows, sql))
|
||||||
|
} else if truncatingLogger.LogLevel >= logger.Info {
|
||||||
|
sql, rows := fc()
|
||||||
|
truncatingLogger.Info(ctx, fmt.Sprintf("Sql query - elapse: %fs rows: %d, sql: %s", elapsed.Seconds(), rows, sql))
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,34 +1,42 @@
|
|||||||
package utils
|
package utils
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/json"
|
"context"
|
||||||
"fmt"
|
"github.com/gin-gonic/gin"
|
||||||
"github.com/gofiber/fiber/v2"
|
"github.com/romana/rlog"
|
||||||
"log"
|
"log"
|
||||||
"mizuserver/pkg/models"
|
"net/http"
|
||||||
"net/url"
|
"net/url"
|
||||||
"os"
|
"os"
|
||||||
"os/signal"
|
"os/signal"
|
||||||
"reflect"
|
"reflect"
|
||||||
"syscall"
|
"syscall"
|
||||||
|
"time"
|
||||||
)
|
)
|
||||||
|
|
||||||
// StartServer starts the server with a graceful shutdown
|
// StartServer starts the server with a graceful shutdown
|
||||||
func StartServer(app *fiber.App) {
|
func StartServer(app *gin.Engine) {
|
||||||
signals := make(chan os.Signal, 2)
|
signals := make(chan os.Signal, 2)
|
||||||
signal.Notify(signals,
|
signal.Notify(signals,
|
||||||
os.Interrupt, // this catch ctrl + c
|
os.Interrupt, // this catch ctrl + c
|
||||||
syscall.SIGTSTP, // this catch ctrl + z
|
syscall.SIGTSTP, // this catch ctrl + z
|
||||||
)
|
)
|
||||||
|
|
||||||
|
srv := &http.Server{
|
||||||
|
Addr: ":8080",
|
||||||
|
Handler: app,
|
||||||
|
}
|
||||||
|
|
||||||
go func() {
|
go func() {
|
||||||
_ = <-signals
|
_ = <-signals
|
||||||
fmt.Println("Shutting down...")
|
rlog.Infof("Shutting down...")
|
||||||
_ = app.Shutdown()
|
ctx, _ := context.WithTimeout(context.Background(), 5*time.Second)
|
||||||
|
_ = srv.Shutdown(ctx)
|
||||||
|
os.Exit(0)
|
||||||
}()
|
}()
|
||||||
|
|
||||||
// Run server.
|
// Run server.
|
||||||
if err := app.Listen(":8899"); err != nil {
|
if err := app.Run(":8899"); err != nil {
|
||||||
log.Printf("Oops... Server is not running! Reason: %v", err)
|
log.Printf("Oops... Server is not running! Reason: %v", err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -61,27 +69,3 @@ func SetHostname(address, newHostname string) string {
|
|||||||
return replacedUrl.String()
|
return replacedUrl.String()
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func GetResolvedBaseEntry(entry models.MizuEntry) models.BaseEntryDetails {
|
|
||||||
entryUrl := entry.Url
|
|
||||||
service := entry.Service
|
|
||||||
if entry.ResolvedDestination != "" {
|
|
||||||
entryUrl = SetHostname(entryUrl, entry.ResolvedDestination)
|
|
||||||
service = SetHostname(service, entry.ResolvedDestination)
|
|
||||||
}
|
|
||||||
return models.BaseEntryDetails{
|
|
||||||
Id: entry.EntryId,
|
|
||||||
Url: entryUrl,
|
|
||||||
Service: service,
|
|
||||||
Path: entry.Path,
|
|
||||||
StatusCode: entry.Status,
|
|
||||||
Method: entry.Method,
|
|
||||||
Timestamp: entry.Timestamp,
|
|
||||||
RequestSenderIp: entry.RequestSenderIp,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func GetBytesFromStruct(v interface{}) []byte{
|
|
||||||
a, _ := json.Marshal(v)
|
|
||||||
return a
|
|
||||||
}
|
|
||||||
8
agent/pkg/version/consts.go
Normal file
8
agent/pkg/version/consts.go
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
package version
|
||||||
|
|
||||||
|
var (
|
||||||
|
SemVer = "0.0.1"
|
||||||
|
Branch = "develop"
|
||||||
|
GitCommitHash = "" // this var is overridden using ldflags in makefile when building
|
||||||
|
BuildTimestamp = "" // this var is overridden using ldflags in makefile when building
|
||||||
|
)
|
||||||
147
api/main.go
147
api/main.go
@@ -1,147 +0,0 @@
|
|||||||
package main
|
|
||||||
|
|
||||||
import (
|
|
||||||
"encoding/json"
|
|
||||||
"flag"
|
|
||||||
"fmt"
|
|
||||||
"github.com/gofiber/fiber/v2"
|
|
||||||
"github.com/gorilla/websocket"
|
|
||||||
"github.com/up9inc/mizu/shared"
|
|
||||||
"mizuserver/pkg/api"
|
|
||||||
"mizuserver/pkg/middleware"
|
|
||||||
"mizuserver/pkg/models"
|
|
||||||
"mizuserver/pkg/routes"
|
|
||||||
"mizuserver/pkg/sensitiveDataFiltering"
|
|
||||||
"mizuserver/pkg/tap"
|
|
||||||
"mizuserver/pkg/utils"
|
|
||||||
"os"
|
|
||||||
"os/signal"
|
|
||||||
)
|
|
||||||
|
|
||||||
var shouldTap = flag.Bool("tap", false, "Run in tapper mode without API")
|
|
||||||
var aggregator = flag.Bool("aggregator", false, "Run in aggregator mode with API")
|
|
||||||
var standalone = flag.Bool("standalone", false, "Run in standalone tapper and API mode")
|
|
||||||
var aggregatorAddress = flag.String("aggregator-address", "", "Address of mizu collector for tapping")
|
|
||||||
|
|
||||||
|
|
||||||
func main() {
|
|
||||||
flag.Parse()
|
|
||||||
|
|
||||||
if !*shouldTap && !*aggregator && !*standalone{
|
|
||||||
panic("One of the flags --tap, --api or --standalone must be provided")
|
|
||||||
}
|
|
||||||
|
|
||||||
if *standalone {
|
|
||||||
harOutputChannel := tap.StartPassiveTapper()
|
|
||||||
filteredHarChannel := make(chan *tap.OutputChannelItem)
|
|
||||||
go filterHarHeaders(harOutputChannel, filteredHarChannel, getTrafficFilteringOptions())
|
|
||||||
go api.StartReadingEntries(filteredHarChannel, nil)
|
|
||||||
hostApi(nil)
|
|
||||||
} else if *shouldTap {
|
|
||||||
if *aggregatorAddress == "" {
|
|
||||||
panic("Aggregator address must be provided with --aggregator-address when using --tap")
|
|
||||||
}
|
|
||||||
|
|
||||||
tapTargets := getTapTargets()
|
|
||||||
if tapTargets != nil {
|
|
||||||
tap.HostAppAddresses = tapTargets
|
|
||||||
fmt.Println("Filtering for the following addresses:", tap.HostAppAddresses)
|
|
||||||
}
|
|
||||||
|
|
||||||
harOutputChannel := tap.StartPassiveTapper()
|
|
||||||
socketConnection, err := shared.ConnectToSocketServer(*aggregatorAddress, shared.DEFAULT_SOCKET_RETRIES, shared.DEFAULT_SOCKET_RETRY_SLEEP_TIME, false)
|
|
||||||
if err != nil {
|
|
||||||
panic(fmt.Sprintf("Error connecting to socket server at %s %v", *aggregatorAddress, err))
|
|
||||||
}
|
|
||||||
go pipeChannelToSocket(socketConnection, harOutputChannel)
|
|
||||||
} else if *aggregator {
|
|
||||||
socketHarOutChannel := make(chan *tap.OutputChannelItem, 1000)
|
|
||||||
filteredHarChannel := make(chan *tap.OutputChannelItem)
|
|
||||||
go api.StartReadingEntries(filteredHarChannel, nil)
|
|
||||||
go filterHarHeaders(socketHarOutChannel, filteredHarChannel, getTrafficFilteringOptions())
|
|
||||||
hostApi(socketHarOutChannel)
|
|
||||||
}
|
|
||||||
|
|
||||||
signalChan := make(chan os.Signal, 1)
|
|
||||||
signal.Notify(signalChan, os.Interrupt)
|
|
||||||
<-signalChan
|
|
||||||
|
|
||||||
fmt.Println("Exiting")
|
|
||||||
}
|
|
||||||
|
|
||||||
func hostApi(socketHarOutputChannel chan<- *tap.OutputChannelItem) {
|
|
||||||
app := fiber.New()
|
|
||||||
|
|
||||||
|
|
||||||
middleware.FiberMiddleware(app) // Register Fiber's middleware for app.
|
|
||||||
app.Static("/", "./site")
|
|
||||||
|
|
||||||
//Simple route to know server is running
|
|
||||||
app.Get("/echo", func(c *fiber.Ctx) error {
|
|
||||||
return c.SendString("Hello, World 👋!")
|
|
||||||
})
|
|
||||||
eventHandlers := api.RoutesEventHandlers{
|
|
||||||
SocketHarOutChannel: socketHarOutputChannel,
|
|
||||||
}
|
|
||||||
routes.WebSocketRoutes(app, &eventHandlers)
|
|
||||||
routes.EntriesRoutes(app)
|
|
||||||
routes.NotFoundRoute(app)
|
|
||||||
|
|
||||||
utils.StartServer(app)
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
func getTapTargets() []string {
|
|
||||||
nodeName := os.Getenv(shared.NodeNameEnvVar)
|
|
||||||
var tappedAddressesPerNodeDict map[string][]string
|
|
||||||
err := json.Unmarshal([]byte(os.Getenv(shared.TappedAddressesPerNodeDictEnvVar)), &tappedAddressesPerNodeDict)
|
|
||||||
if err != nil {
|
|
||||||
panic(fmt.Sprintf("env var %s's value of %s is invalid! must be map[string][]string %v", shared.TappedAddressesPerNodeDictEnvVar, tappedAddressesPerNodeDict, err))
|
|
||||||
}
|
|
||||||
return tappedAddressesPerNodeDict[nodeName]
|
|
||||||
}
|
|
||||||
|
|
||||||
func getTrafficFilteringOptions() *shared.TrafficFilteringOptions {
|
|
||||||
filteringOptionsJson := os.Getenv(shared.MizuFilteringOptionsEnvVar)
|
|
||||||
if filteringOptionsJson == "" {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
var filteringOptions shared.TrafficFilteringOptions
|
|
||||||
err := json.Unmarshal([]byte(filteringOptionsJson), &filteringOptions)
|
|
||||||
if err != nil {
|
|
||||||
panic(fmt.Sprintf("env var %s's value of %s is invalid! json must match the shared.TrafficFilteringOptions struct %v", shared.MizuFilteringOptionsEnvVar, filteringOptionsJson, err))
|
|
||||||
}
|
|
||||||
|
|
||||||
return &filteringOptions
|
|
||||||
}
|
|
||||||
|
|
||||||
func filterHarHeaders(inChannel <- chan *tap.OutputChannelItem, outChannel chan *tap.OutputChannelItem, filterOptions *shared.TrafficFilteringOptions) {
|
|
||||||
for message := range inChannel {
|
|
||||||
sensitiveDataFiltering.FilterSensitiveInfoFromHarRequest(message, filterOptions)
|
|
||||||
outChannel <- message
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func pipeChannelToSocket(connection *websocket.Conn, messageDataChannel <-chan *tap.OutputChannelItem) {
|
|
||||||
if connection == nil {
|
|
||||||
panic("Websocket connection is nil")
|
|
||||||
}
|
|
||||||
|
|
||||||
if messageDataChannel == nil {
|
|
||||||
panic("Channel of captured messages is nil")
|
|
||||||
}
|
|
||||||
|
|
||||||
for messageData := range messageDataChannel {
|
|
||||||
marshaledData, err := models.CreateWebsocketTappedEntryMessage(messageData)
|
|
||||||
if err != nil {
|
|
||||||
fmt.Printf("error converting message to json %s, (%v,%+v)\n", err, err, err)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
err = connection.WriteMessage(websocket.TextMessage, marshaledData)
|
|
||||||
if err != nil {
|
|
||||||
fmt.Printf("error sending message through socket server %s, (%v,%+v)\n", err, err, err)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,131 +0,0 @@
|
|||||||
package api
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bufio"
|
|
||||||
"context"
|
|
||||||
"encoding/json"
|
|
||||||
"fmt"
|
|
||||||
"github.com/google/martian/har"
|
|
||||||
"go.mongodb.org/mongo-driver/bson/primitive"
|
|
||||||
"mizuserver/pkg/database"
|
|
||||||
"mizuserver/pkg/models"
|
|
||||||
"mizuserver/pkg/resolver"
|
|
||||||
"mizuserver/pkg/tap"
|
|
||||||
"mizuserver/pkg/utils"
|
|
||||||
"net/url"
|
|
||||||
"os"
|
|
||||||
"path"
|
|
||||||
"sort"
|
|
||||||
"time"
|
|
||||||
)
|
|
||||||
|
|
||||||
var k8sResolver *resolver.Resolver
|
|
||||||
|
|
||||||
func init() {
|
|
||||||
errOut := make(chan error, 100)
|
|
||||||
res, err := resolver.NewFromInCluster(errOut)
|
|
||||||
if err != nil {
|
|
||||||
fmt.Printf("error creating k8s resolver %s", err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
ctx := context.Background()
|
|
||||||
res.Start(ctx)
|
|
||||||
go func() {
|
|
||||||
for {
|
|
||||||
select {
|
|
||||||
case err := <-errOut:
|
|
||||||
fmt.Printf("name resolving error %s", err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
|
|
||||||
k8sResolver = res
|
|
||||||
}
|
|
||||||
|
|
||||||
func StartReadingEntries(harChannel <-chan *tap.OutputChannelItem, workingDir *string) {
|
|
||||||
if workingDir != nil && *workingDir != "" {
|
|
||||||
startReadingFiles(*workingDir)
|
|
||||||
} else {
|
|
||||||
startReadingChannel(harChannel)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func startReadingFiles(workingDir string) {
|
|
||||||
err := os.MkdirAll(workingDir, os.ModePerm)
|
|
||||||
utils.CheckErr(err)
|
|
||||||
|
|
||||||
for true {
|
|
||||||
dir, _ := os.Open(workingDir)
|
|
||||||
dirFiles, _ := dir.Readdir(-1)
|
|
||||||
sort.Sort(utils.ByModTime(dirFiles))
|
|
||||||
|
|
||||||
if len(dirFiles) == 0 {
|
|
||||||
fmt.Printf("Waiting for new files\n")
|
|
||||||
time.Sleep(3 * time.Second)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
fileInfo := dirFiles[0]
|
|
||||||
inputFilePath := path.Join(workingDir, fileInfo.Name())
|
|
||||||
file, err := os.Open(inputFilePath)
|
|
||||||
utils.CheckErr(err)
|
|
||||||
|
|
||||||
var inputHar har.HAR
|
|
||||||
decErr := json.NewDecoder(bufio.NewReader(file)).Decode(&inputHar)
|
|
||||||
utils.CheckErr(decErr)
|
|
||||||
|
|
||||||
for _, entry := range inputHar.Log.Entries {
|
|
||||||
time.Sleep(time.Millisecond * 250)
|
|
||||||
saveHarToDb(entry, fileInfo.Name())
|
|
||||||
}
|
|
||||||
rmErr := os.Remove(inputFilePath)
|
|
||||||
utils.CheckErr(rmErr)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func startReadingChannel(outputItems <-chan *tap.OutputChannelItem) {
|
|
||||||
if outputItems == nil {
|
|
||||||
panic("Channel of captured messages is nil")
|
|
||||||
}
|
|
||||||
|
|
||||||
for item := range outputItems {
|
|
||||||
saveHarToDb(item.HarEntry, item.RequestSenderIp)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func saveHarToDb(entry *har.Entry, sender string) {
|
|
||||||
entryBytes, _ := json.Marshal(entry)
|
|
||||||
serviceName, urlPath, serviceHostName := getServiceNameFromUrl(entry.Request.URL)
|
|
||||||
entryId := primitive.NewObjectID().Hex()
|
|
||||||
var (
|
|
||||||
resolvedSource string
|
|
||||||
resolvedDestination string
|
|
||||||
)
|
|
||||||
if k8sResolver != nil {
|
|
||||||
resolvedSource = k8sResolver.Resolve(sender)
|
|
||||||
resolvedDestination = k8sResolver.Resolve(serviceHostName)
|
|
||||||
}
|
|
||||||
mizuEntry := models.MizuEntry{
|
|
||||||
EntryId: entryId,
|
|
||||||
Entry: string(entryBytes), // simple way to store it and not convert to bytes
|
|
||||||
Service: serviceName,
|
|
||||||
Url: entry.Request.URL,
|
|
||||||
Path: urlPath,
|
|
||||||
Method: entry.Request.Method,
|
|
||||||
Status: entry.Response.Status,
|
|
||||||
RequestSenderIp: sender,
|
|
||||||
Timestamp: entry.StartedDateTime.UnixNano() / int64(time.Millisecond),
|
|
||||||
ResolvedSource: resolvedSource,
|
|
||||||
ResolvedDestination: resolvedDestination,
|
|
||||||
}
|
|
||||||
database.GetEntriesTable().Create(&mizuEntry)
|
|
||||||
|
|
||||||
baseEntry := utils.GetResolvedBaseEntry(mizuEntry)
|
|
||||||
baseEntryBytes, _ := models.CreateBaseEntryWebSocketMessage(&baseEntry)
|
|
||||||
broadcastToBrowserClients(baseEntryBytes)
|
|
||||||
}
|
|
||||||
|
|
||||||
func getServiceNameFromUrl(inputUrl string) (string, string, string) {
|
|
||||||
parsed, err := url.Parse(inputUrl)
|
|
||||||
utils.CheckErr(err)
|
|
||||||
return fmt.Sprintf("%s://%s", parsed.Scheme, parsed.Host), parsed.Path, parsed.Host
|
|
||||||
}
|
|
||||||
@@ -1,96 +0,0 @@
|
|||||||
package api
|
|
||||||
|
|
||||||
import (
|
|
||||||
"encoding/json"
|
|
||||||
"fmt"
|
|
||||||
"github.com/antoniodipinto/ikisocket"
|
|
||||||
"github.com/up9inc/mizu/shared"
|
|
||||||
"mizuserver/pkg/controllers"
|
|
||||||
"mizuserver/pkg/models"
|
|
||||||
"mizuserver/pkg/routes"
|
|
||||||
"mizuserver/pkg/tap"
|
|
||||||
)
|
|
||||||
|
|
||||||
var browserClientSocketUUIDs = make([]string, 0)
|
|
||||||
|
|
||||||
type RoutesEventHandlers struct {
|
|
||||||
routes.EventHandlers
|
|
||||||
SocketHarOutChannel chan<- *tap.OutputChannelItem
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
func (h *RoutesEventHandlers) WebSocketConnect(ep *ikisocket.EventPayload) {
|
|
||||||
if ep.Kws.GetAttribute("is_tapper") == true {
|
|
||||||
fmt.Println(fmt.Sprintf("Websocket Connection event - Tapper connected: %s", ep.SocketUUID))
|
|
||||||
} else {
|
|
||||||
fmt.Println(fmt.Sprintf("Websocket Connection event - Browser socket connected: %s", ep.SocketUUID))
|
|
||||||
browserClientSocketUUIDs = append(browserClientSocketUUIDs, ep.SocketUUID)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (h *RoutesEventHandlers) WebSocketDisconnect(ep *ikisocket.EventPayload) {
|
|
||||||
if ep.Kws.GetAttribute("is_tapper") == true {
|
|
||||||
fmt.Println(fmt.Sprintf("Disconnection event - Tapper connected: %s", ep.SocketUUID))
|
|
||||||
} else {
|
|
||||||
fmt.Println(fmt.Sprintf("Disconnection event - Browser socket connected: %s", ep.SocketUUID))
|
|
||||||
removeSocketUUIDFromBrowserSlice(ep.SocketUUID)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func broadcastToBrowserClients(message []byte) {
|
|
||||||
ikisocket.EmitToList(browserClientSocketUUIDs, message)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (h *RoutesEventHandlers) WebSocketClose(ep *ikisocket.EventPayload) {
|
|
||||||
if ep.Kws.GetAttribute("is_tapper") == true {
|
|
||||||
fmt.Println(fmt.Sprintf("Websocket Close event - Tapper connected: %s", ep.SocketUUID))
|
|
||||||
} else {
|
|
||||||
fmt.Println(fmt.Sprintf("Websocket Close event - Browser socket connected: %s", ep.SocketUUID))
|
|
||||||
removeSocketUUIDFromBrowserSlice(ep.SocketUUID)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (h *RoutesEventHandlers) WebSocketError(ep *ikisocket.EventPayload) {
|
|
||||||
fmt.Println(fmt.Sprintf("Socket error - Socket uuid : %s %v", ep.SocketUUID, ep.Error))
|
|
||||||
}
|
|
||||||
|
|
||||||
func (h *RoutesEventHandlers) WebSocketMessage(ep *ikisocket.EventPayload) {
|
|
||||||
var socketMessageBase shared.WebSocketMessageMetadata
|
|
||||||
err := json.Unmarshal(ep.Data, &socketMessageBase)
|
|
||||||
if err != nil {
|
|
||||||
fmt.Printf("Could not unmarshal websocket message %v\n", err)
|
|
||||||
} else {
|
|
||||||
switch socketMessageBase.MessageType {
|
|
||||||
case shared.WebSocketMessageTypeTappedEntry:
|
|
||||||
var tappedEntryMessage models.WebSocketTappedEntryMessage
|
|
||||||
err := json.Unmarshal(ep.Data, &tappedEntryMessage)
|
|
||||||
if err != nil {
|
|
||||||
fmt.Printf("Could not unmarshal message of message type %s %v\n", socketMessageBase.MessageType, err)
|
|
||||||
} else {
|
|
||||||
h.SocketHarOutChannel <- tappedEntryMessage.Data
|
|
||||||
}
|
|
||||||
case shared.WebSocketMessageTypeUpdateStatus:
|
|
||||||
var statusMessage shared.WebSocketStatusMessage
|
|
||||||
err := json.Unmarshal(ep.Data, &statusMessage)
|
|
||||||
if err != nil {
|
|
||||||
fmt.Printf("Could not unmarshal message of message type %s %v\n", socketMessageBase.MessageType, err)
|
|
||||||
} else {
|
|
||||||
controllers.TapStatus = statusMessage.TappingStatus
|
|
||||||
broadcastToBrowserClients(ep.Data)
|
|
||||||
}
|
|
||||||
default:
|
|
||||||
fmt.Printf("Received socket message of type %s for which no handlers are defined", socketMessageBase.MessageType)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
func removeSocketUUIDFromBrowserSlice(uuidToRemove string) {
|
|
||||||
newUUIDSlice := make([]string, 0, len(browserClientSocketUUIDs))
|
|
||||||
for _, uuid := range browserClientSocketUUIDs {
|
|
||||||
if uuid != uuidToRemove {
|
|
||||||
newUUIDSlice = append(newUUIDSlice, uuid)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
browserClientSocketUUIDs = newUUIDSlice
|
|
||||||
}
|
|
||||||
@@ -1,166 +0,0 @@
|
|||||||
package controllers
|
|
||||||
|
|
||||||
import (
|
|
||||||
"encoding/json"
|
|
||||||
"fmt"
|
|
||||||
"github.com/gofiber/fiber/v2"
|
|
||||||
"github.com/google/martian/har"
|
|
||||||
"mizuserver/pkg/database"
|
|
||||||
"mizuserver/pkg/models"
|
|
||||||
"mizuserver/pkg/utils"
|
|
||||||
"mizuserver/pkg/validation"
|
|
||||||
)
|
|
||||||
|
|
||||||
const (
|
|
||||||
OrderDesc = "desc"
|
|
||||||
OrderAsc = "asc"
|
|
||||||
LT = "lt"
|
|
||||||
GT = "gt"
|
|
||||||
)
|
|
||||||
|
|
||||||
var (
|
|
||||||
operatorToSymbolMapping = map[string]string{
|
|
||||||
LT: "<",
|
|
||||||
GT: ">",
|
|
||||||
}
|
|
||||||
operatorToOrderMapping = map[string]string{
|
|
||||||
LT: OrderDesc,
|
|
||||||
GT: OrderAsc,
|
|
||||||
}
|
|
||||||
)
|
|
||||||
|
|
||||||
func GetEntries(c *fiber.Ctx) error {
|
|
||||||
entriesFilter := &models.EntriesFilter{}
|
|
||||||
|
|
||||||
if err := c.QueryParser(entriesFilter); err != nil {
|
|
||||||
return c.Status(fiber.StatusBadRequest).JSON(err)
|
|
||||||
}
|
|
||||||
err := validation.Validate(entriesFilter)
|
|
||||||
if err != nil {
|
|
||||||
return c.Status(fiber.StatusBadRequest).JSON(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
order := operatorToOrderMapping[entriesFilter.Operator]
|
|
||||||
operatorSymbol := operatorToSymbolMapping[entriesFilter.Operator]
|
|
||||||
var entries []models.MizuEntry
|
|
||||||
database.GetEntriesTable().
|
|
||||||
Order(fmt.Sprintf("timestamp %s", order)).
|
|
||||||
Where(fmt.Sprintf("timestamp %s %v", operatorSymbol, entriesFilter.Timestamp)).
|
|
||||||
Omit("entry"). // remove the "big" entry field
|
|
||||||
Limit(entriesFilter.Limit).
|
|
||||||
Find(&entries)
|
|
||||||
|
|
||||||
if len(entries) > 0 && order == OrderDesc {
|
|
||||||
// the entries always order from oldest to newest so we should revers
|
|
||||||
utils.ReverseSlice(entries)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Convert to base entries
|
|
||||||
baseEntries := make([]models.BaseEntryDetails, 0, entriesFilter.Limit)
|
|
||||||
for _, entry := range entries {
|
|
||||||
baseEntries = append(baseEntries, utils.GetResolvedBaseEntry(entry))
|
|
||||||
}
|
|
||||||
|
|
||||||
return c.Status(fiber.StatusOK).JSON(baseEntries)
|
|
||||||
}
|
|
||||||
|
|
||||||
func GetHARs(c *fiber.Ctx) error {
|
|
||||||
entriesFilter := &models.HarFetchRequestBody{}
|
|
||||||
order := OrderDesc
|
|
||||||
if err := c.QueryParser(entriesFilter); err != nil {
|
|
||||||
return c.Status(fiber.StatusBadRequest).JSON(err)
|
|
||||||
}
|
|
||||||
err := validation.Validate(entriesFilter)
|
|
||||||
if err != nil {
|
|
||||||
return c.Status(fiber.StatusBadRequest).JSON(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
var entries []models.MizuEntry
|
|
||||||
database.GetEntriesTable().
|
|
||||||
Order(fmt.Sprintf("timestamp %s", order)).
|
|
||||||
// Where(fmt.Sprintf("timestamp %s %v", operatorSymbol, entriesFilter.Timestamp)).
|
|
||||||
Limit(1000).
|
|
||||||
Find(&entries)
|
|
||||||
|
|
||||||
if len(entries) > 0 {
|
|
||||||
// the entries always order from oldest to newest so we should revers
|
|
||||||
utils.ReverseSlice(entries)
|
|
||||||
}
|
|
||||||
|
|
||||||
harsObject := map[string]*models.ExtendedHAR{}
|
|
||||||
|
|
||||||
for _, entryData := range entries {
|
|
||||||
var harEntry har.Entry
|
|
||||||
_ = json.Unmarshal([]byte(entryData.Entry), &harEntry)
|
|
||||||
|
|
||||||
sourceOfEntry := entryData.ResolvedSource
|
|
||||||
fileName := fmt.Sprintf("%s.har", sourceOfEntry)
|
|
||||||
if harOfSource, ok := harsObject[fileName]; ok {
|
|
||||||
harOfSource.Log.Entries = append(harOfSource.Log.Entries, &harEntry)
|
|
||||||
} else {
|
|
||||||
var entriesHar []*har.Entry
|
|
||||||
entriesHar = append(entriesHar, &harEntry)
|
|
||||||
harsObject[fileName] = &models.ExtendedHAR{
|
|
||||||
Log: &models.ExtendedLog{
|
|
||||||
Version: "1.2",
|
|
||||||
Creator: &models.ExtendedCreator{
|
|
||||||
Creator: &har.Creator{
|
|
||||||
Name: "mizu",
|
|
||||||
Version: "0.0.2",
|
|
||||||
},
|
|
||||||
Source: sourceOfEntry,
|
|
||||||
},
|
|
||||||
Entries: entriesHar,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
retObj := map[string][]byte{}
|
|
||||||
for k, v := range harsObject {
|
|
||||||
bytesData, _ := json.Marshal(v)
|
|
||||||
retObj[k] = bytesData
|
|
||||||
}
|
|
||||||
buffer := utils.ZipData(retObj)
|
|
||||||
return c.Status(fiber.StatusOK).SendStream(buffer)
|
|
||||||
}
|
|
||||||
|
|
||||||
func GetEntry(c *fiber.Ctx) error {
|
|
||||||
var entryData models.EntryData
|
|
||||||
database.GetEntriesTable().
|
|
||||||
Select("entry", "resolvedDestination").
|
|
||||||
Where(map[string]string{"entryId": c.Params("entryId")}).
|
|
||||||
First(&entryData)
|
|
||||||
|
|
||||||
var fullEntry har.Entry
|
|
||||||
unmarshallErr := json.Unmarshal([]byte(entryData.Entry), &fullEntry)
|
|
||||||
utils.CheckErr(unmarshallErr)
|
|
||||||
|
|
||||||
if entryData.ResolvedDestination != "" {
|
|
||||||
fullEntry.Request.URL = utils.SetHostname(fullEntry.Request.URL, entryData.ResolvedDestination)
|
|
||||||
}
|
|
||||||
|
|
||||||
return c.Status(fiber.StatusOK).JSON(fullEntry)
|
|
||||||
}
|
|
||||||
|
|
||||||
func DeleteAllEntries(c *fiber.Ctx) error {
|
|
||||||
database.GetEntriesTable().
|
|
||||||
Where("1 = 1").
|
|
||||||
Delete(&models.MizuEntry{})
|
|
||||||
|
|
||||||
return c.Status(fiber.StatusOK).JSON(fiber.Map{
|
|
||||||
"msg": "Success",
|
|
||||||
})
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
func GetGeneralStats(c *fiber.Ctx) error {
|
|
||||||
sqlQuery := "SELECT count(*) as count, min(timestamp) as min, max(timestamp) as max from mizu_entries"
|
|
||||||
var result struct {
|
|
||||||
Count int
|
|
||||||
Min int
|
|
||||||
Max int
|
|
||||||
}
|
|
||||||
database.GetEntriesTable().Raw(sqlQuery).Scan(&result)
|
|
||||||
return c.Status(fiber.StatusOK).JSON(&result)
|
|
||||||
}
|
|
||||||
@@ -1,12 +0,0 @@
|
|||||||
package controllers
|
|
||||||
|
|
||||||
import (
|
|
||||||
"github.com/gofiber/fiber/v2"
|
|
||||||
"github.com/up9inc/mizu/shared"
|
|
||||||
)
|
|
||||||
|
|
||||||
var TapStatus shared.TapStatus
|
|
||||||
|
|
||||||
func GetTappingStatus(c *fiber.Ctx) error {
|
|
||||||
return c.Status(fiber.StatusOK).JSON(TapStatus)
|
|
||||||
}
|
|
||||||
@@ -1,25 +0,0 @@
|
|||||||
package database
|
|
||||||
|
|
||||||
import (
|
|
||||||
"gorm.io/driver/sqlite"
|
|
||||||
"gorm.io/gorm"
|
|
||||||
"mizuserver/pkg/models"
|
|
||||||
)
|
|
||||||
|
|
||||||
const (
|
|
||||||
DBPath = "./entries.db"
|
|
||||||
)
|
|
||||||
|
|
||||||
var (
|
|
||||||
DB = initDataBase(DBPath)
|
|
||||||
)
|
|
||||||
|
|
||||||
func GetEntriesTable() *gorm.DB {
|
|
||||||
return DB.Table("mizu_entries")
|
|
||||||
}
|
|
||||||
|
|
||||||
func initDataBase(databasePath string) *gorm.DB {
|
|
||||||
temp, _ := gorm.Open(sqlite.Open(databasePath), &gorm.Config{})
|
|
||||||
_ = temp.AutoMigrate(&models.MizuEntry{}) // this will ensure table is created
|
|
||||||
return temp
|
|
||||||
}
|
|
||||||
@@ -1,18 +0,0 @@
|
|||||||
package middleware
|
|
||||||
|
|
||||||
import (
|
|
||||||
"github.com/gofiber/fiber/v2"
|
|
||||||
"github.com/gofiber/fiber/v2/middleware/cors"
|
|
||||||
"github.com/gofiber/fiber/v2/middleware/logger"
|
|
||||||
)
|
|
||||||
|
|
||||||
// FiberMiddleware provide Fiber's built-in middlewares.
|
|
||||||
// See: https://docs.gofiber.io/api/middleware
|
|
||||||
func FiberMiddleware(a *fiber.App) {
|
|
||||||
a.Use(
|
|
||||||
// Add CORS to each route.
|
|
||||||
cors.New(),
|
|
||||||
// Add simple logger.
|
|
||||||
logger.New(),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
@@ -1,104 +0,0 @@
|
|||||||
package models
|
|
||||||
|
|
||||||
import (
|
|
||||||
"encoding/json"
|
|
||||||
"github.com/google/martian/har"
|
|
||||||
"github.com/up9inc/mizu/shared"
|
|
||||||
"mizuserver/pkg/tap"
|
|
||||||
"time"
|
|
||||||
)
|
|
||||||
|
|
||||||
type MizuEntry struct {
|
|
||||||
ID uint `gorm:"primarykey"`
|
|
||||||
CreatedAt time.Time
|
|
||||||
UpdatedAt time.Time
|
|
||||||
Entry string `json:"entry,omitempty" gorm:"column:entry"`
|
|
||||||
EntryId string `json:"entryId" gorm:"column:entryId"`
|
|
||||||
Url string `json:"url" gorm:"column:url"`
|
|
||||||
Method string `json:"method" gorm:"column:method"`
|
|
||||||
Status int `json:"status" gorm:"column:status"`
|
|
||||||
RequestSenderIp string `json:"requestSenderIp" gorm:"column:requestSenderIp"`
|
|
||||||
Service string `json:"service" gorm:"column:service"`
|
|
||||||
Timestamp int64 `json:"timestamp" gorm:"column:timestamp"`
|
|
||||||
Path string `json:"path" gorm:"column:path"`
|
|
||||||
ResolvedSource string `json:"resolvedSource,omitempty" gorm:"column:resolvedSource"`
|
|
||||||
ResolvedDestination string `json:"resolvedDestination,omitempty" gorm:"column:resolvedDestination"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type BaseEntryDetails struct {
|
|
||||||
Id string `json:"id,omitempty"`
|
|
||||||
Url string `json:"url,omitempty"`
|
|
||||||
RequestSenderIp string `json:"requestSenderIp,omitempty"`
|
|
||||||
Service string `json:"service,omitempty"`
|
|
||||||
Path string `json:"path,omitempty"`
|
|
||||||
StatusCode int `json:"statusCode,omitempty"`
|
|
||||||
Method string `json:"method,omitempty"`
|
|
||||||
Timestamp int64 `json:"timestamp,omitempty"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type EntryData struct {
|
|
||||||
Entry string `json:"entry,omitempty"`
|
|
||||||
ResolvedDestination string `json:"resolvedDestination,omitempty" gorm:"column:resolvedDestination"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type EntriesFilter struct {
|
|
||||||
Limit int `query:"limit" validate:"required,min=1,max=200"`
|
|
||||||
Operator string `query:"operator" validate:"required,oneof='lt' 'gt'"`
|
|
||||||
Timestamp int64 `query:"timestamp" validate:"required,min=1"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type HarFetchRequestBody struct {
|
|
||||||
Limit int `query:"limit"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type WebSocketEntryMessage struct {
|
|
||||||
*shared.WebSocketMessageMetadata
|
|
||||||
Data *BaseEntryDetails `json:"data,omitempty"`
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
type WebSocketTappedEntryMessage struct {
|
|
||||||
*shared.WebSocketMessageMetadata
|
|
||||||
Data *tap.OutputChannelItem
|
|
||||||
}
|
|
||||||
|
|
||||||
func CreateBaseEntryWebSocketMessage(base *BaseEntryDetails) ([]byte, error) {
|
|
||||||
message := &WebSocketEntryMessage{
|
|
||||||
WebSocketMessageMetadata: &shared.WebSocketMessageMetadata{
|
|
||||||
MessageType: shared.WebSocketMessageTypeEntry,
|
|
||||||
},
|
|
||||||
Data: base,
|
|
||||||
}
|
|
||||||
return json.Marshal(message)
|
|
||||||
}
|
|
||||||
|
|
||||||
func CreateWebsocketTappedEntryMessage(base *tap.OutputChannelItem) ([]byte, error) {
|
|
||||||
message := &WebSocketTappedEntryMessage{
|
|
||||||
WebSocketMessageMetadata: &shared.WebSocketMessageMetadata{
|
|
||||||
MessageType: shared.WebSocketMessageTypeTappedEntry,
|
|
||||||
},
|
|
||||||
Data: base,
|
|
||||||
}
|
|
||||||
return json.Marshal(message)
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
// ExtendedHAR is the top level object of a HAR log.
|
|
||||||
type ExtendedHAR struct {
|
|
||||||
Log *ExtendedLog `json:"log"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// ExtendedLog is the HAR HTTP request and response log.
|
|
||||||
type ExtendedLog struct {
|
|
||||||
// Version number of the HAR format.
|
|
||||||
Version string `json:"version"`
|
|
||||||
// Creator holds information about the log creator application.
|
|
||||||
Creator *ExtendedCreator `json:"creator"`
|
|
||||||
// Entries is a list containing requests and responses.
|
|
||||||
Entries []*har.Entry `json:"entries"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type ExtendedCreator struct {
|
|
||||||
*har.Creator
|
|
||||||
Source string `json:"_source"`
|
|
||||||
}
|
|
||||||
@@ -1,61 +0,0 @@
|
|||||||
package resolver
|
|
||||||
|
|
||||||
import (
|
|
||||||
"k8s.io/client-go/kubernetes"
|
|
||||||
_ "k8s.io/client-go/plugin/pkg/client/auth/azure"
|
|
||||||
_ "k8s.io/client-go/plugin/pkg/client/auth/gcp"
|
|
||||||
_ "k8s.io/client-go/plugin/pkg/client/auth/oidc"
|
|
||||||
_ "k8s.io/client-go/plugin/pkg/client/auth/openstack"
|
|
||||||
restclient "k8s.io/client-go/rest"
|
|
||||||
"k8s.io/client-go/tools/clientcmd"
|
|
||||||
"k8s.io/client-go/util/homedir"
|
|
||||||
"path/filepath"
|
|
||||||
)
|
|
||||||
|
|
||||||
func NewFromInCluster(errOut chan error) (*Resolver, error) {
|
|
||||||
config, err := restclient.InClusterConfig()
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
clientset, err := kubernetes.NewForConfig(config)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
return &Resolver{clientConfig: config, clientSet: clientset, nameMap: make(map[string]string), errOut: errOut}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewFromOutOfCluster(kubeConfigPath string, errOut chan error) (*Resolver, error) {
|
|
||||||
if kubeConfigPath == "" {
|
|
||||||
home := homedir.HomeDir()
|
|
||||||
kubeConfigPath = filepath.Join(home, ".kube", "config")
|
|
||||||
}
|
|
||||||
|
|
||||||
configPathList := filepath.SplitList(kubeConfigPath)
|
|
||||||
configLoadingRules := &clientcmd.ClientConfigLoadingRules{}
|
|
||||||
if len(configPathList) <= 1 {
|
|
||||||
configLoadingRules.ExplicitPath = kubeConfigPath
|
|
||||||
} else {
|
|
||||||
configLoadingRules.Precedence = configPathList
|
|
||||||
}
|
|
||||||
contextName := ""
|
|
||||||
clientConfigLoader := clientcmd.NewNonInteractiveDeferredLoadingClientConfig(
|
|
||||||
configLoadingRules,
|
|
||||||
&clientcmd.ConfigOverrides{
|
|
||||||
CurrentContext: contextName,
|
|
||||||
},
|
|
||||||
)
|
|
||||||
clientConfig, err := clientConfigLoader.ClientConfig()
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
clientset, err := kubernetes.NewForConfig(clientConfig)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return &Resolver{clientConfig: clientConfig, clientSet: clientset, nameMap: make(map[string]string), errOut: errOut}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewFromExisting(clientConfig *restclient.Config, clientSet *kubernetes.Clientset, errOut chan error) *Resolver {
|
|
||||||
return &Resolver{clientConfig: clientConfig, clientSet: clientSet, nameMap: make(map[string]string), errOut: errOut}
|
|
||||||
}
|
|
||||||
@@ -1,169 +0,0 @@
|
|||||||
package resolver
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"errors"
|
|
||||||
"fmt"
|
|
||||||
k8serrors "k8s.io/apimachinery/pkg/api/errors"
|
|
||||||
|
|
||||||
corev1 "k8s.io/api/core/v1"
|
|
||||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
|
||||||
"k8s.io/apimachinery/pkg/watch"
|
|
||||||
"k8s.io/client-go/kubernetes"
|
|
||||||
restclient "k8s.io/client-go/rest"
|
|
||||||
)
|
|
||||||
const (
|
|
||||||
kubClientNullString = "None"
|
|
||||||
)
|
|
||||||
|
|
||||||
type Resolver struct {
|
|
||||||
clientConfig *restclient.Config
|
|
||||||
clientSet *kubernetes.Clientset
|
|
||||||
nameMap map[string]string
|
|
||||||
isStarted bool
|
|
||||||
errOut chan error
|
|
||||||
}
|
|
||||||
|
|
||||||
func (resolver *Resolver) Start(ctx context.Context) {
|
|
||||||
if !resolver.isStarted {
|
|
||||||
resolver.isStarted = true
|
|
||||||
go resolver.infiniteErrorHandleRetryFunc(ctx, resolver.watchServices)
|
|
||||||
go resolver.infiniteErrorHandleRetryFunc(ctx, resolver.watchEndpoints)
|
|
||||||
go resolver.infiniteErrorHandleRetryFunc(ctx, resolver.watchPods)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (resolver *Resolver) Resolve(name string) string {
|
|
||||||
resolvedName, isFound := resolver.nameMap[name]
|
|
||||||
if !isFound {
|
|
||||||
return ""
|
|
||||||
}
|
|
||||||
return resolvedName
|
|
||||||
}
|
|
||||||
|
|
||||||
func (resolver *Resolver) watchPods(ctx context.Context) error {
|
|
||||||
// empty namespace makes the client watch all namespaces
|
|
||||||
watcher, err := resolver.clientSet.CoreV1().Pods("").Watch(ctx, metav1.ListOptions{Watch: true})
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
for {
|
|
||||||
select {
|
|
||||||
case event := <- watcher.ResultChan():
|
|
||||||
if event.Object == nil {
|
|
||||||
return errors.New("error in kubectl pod watch")
|
|
||||||
}
|
|
||||||
if event.Type == watch.Deleted {
|
|
||||||
pod := event.Object.(*corev1.Pod)
|
|
||||||
resolver.saveResolvedName(pod.Status.PodIP, "", event.Type)
|
|
||||||
}
|
|
||||||
case <- ctx.Done():
|
|
||||||
watcher.Stop()
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (resolver *Resolver) watchEndpoints(ctx context.Context) error {
|
|
||||||
// empty namespace makes the client watch all namespaces
|
|
||||||
watcher, err := resolver.clientSet.CoreV1().Endpoints("").Watch(ctx, metav1.ListOptions{Watch: true})
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
for {
|
|
||||||
select {
|
|
||||||
case event := <- watcher.ResultChan():
|
|
||||||
if event.Object == nil {
|
|
||||||
return errors.New("error in kubectl endpoint watch")
|
|
||||||
}
|
|
||||||
endpoint := event.Object.(*corev1.Endpoints)
|
|
||||||
serviceHostname := fmt.Sprintf("%s.%s", endpoint.Name, endpoint.Namespace)
|
|
||||||
if endpoint.Subsets != nil {
|
|
||||||
for _, subset := range endpoint.Subsets {
|
|
||||||
var ports []int32
|
|
||||||
if subset.Ports != nil {
|
|
||||||
for _, portMapping := range subset.Ports {
|
|
||||||
if portMapping.Port > 0 {
|
|
||||||
ports = append(ports, portMapping.Port)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if subset.Addresses != nil {
|
|
||||||
for _, address := range subset.Addresses {
|
|
||||||
resolver.saveResolvedName(address.IP, serviceHostname, event.Type)
|
|
||||||
for _, port := range ports {
|
|
||||||
ipWithPort := fmt.Sprintf("%s:%d", address.IP, port)
|
|
||||||
resolver.saveResolvedName(ipWithPort, serviceHostname, event.Type)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
case <- ctx.Done():
|
|
||||||
watcher.Stop()
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (resolver *Resolver) watchServices(ctx context.Context) error {
|
|
||||||
// empty namespace makes the client watch all namespaces
|
|
||||||
watcher, err := resolver.clientSet.CoreV1().Services("").Watch(ctx, metav1.ListOptions{Watch: true})
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
for {
|
|
||||||
select {
|
|
||||||
case event := <- watcher.ResultChan():
|
|
||||||
if event.Object == nil {
|
|
||||||
return errors.New("error in kubectl service watch")
|
|
||||||
}
|
|
||||||
|
|
||||||
service := event.Object.(*corev1.Service)
|
|
||||||
serviceHostname := fmt.Sprintf("%s.%s", service.Name, service.Namespace)
|
|
||||||
if service.Spec.ClusterIP != "" && service.Spec.ClusterIP != kubClientNullString {
|
|
||||||
resolver.saveResolvedName(service.Spec.ClusterIP, serviceHostname, event.Type)
|
|
||||||
}
|
|
||||||
if service.Status.LoadBalancer.Ingress != nil {
|
|
||||||
for _, ingress := range service.Status.LoadBalancer.Ingress {
|
|
||||||
resolver.saveResolvedName(ingress.IP, serviceHostname, event.Type)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
case <- ctx.Done():
|
|
||||||
watcher.Stop()
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (resolver *Resolver) saveResolvedName(key string, resolved string, eventType watch.EventType) {
|
|
||||||
if eventType == watch.Deleted {
|
|
||||||
delete(resolver.nameMap, key)
|
|
||||||
// fmt.Printf("setting %s=nil\n", key)
|
|
||||||
} else {
|
|
||||||
resolver.nameMap[key] = resolved
|
|
||||||
// fmt.Printf("setting %s=%s\n", key, resolved)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (resolver *Resolver) infiniteErrorHandleRetryFunc(ctx context.Context, fun func(ctx context.Context) error) {
|
|
||||||
for {
|
|
||||||
err := fun(ctx)
|
|
||||||
if err != nil {
|
|
||||||
resolver.errOut <- err
|
|
||||||
|
|
||||||
var statusError *k8serrors.StatusError
|
|
||||||
if errors.As(err, &statusError) {
|
|
||||||
if statusError.ErrStatus.Reason == metav1.StatusReasonForbidden {
|
|
||||||
fmt.Printf("Resolver loop encountered permission error, aborting event listening - %v\n", err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if ctx.Err() != nil { // context was cancelled or errored
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@@ -1,15 +0,0 @@
|
|||||||
package routes
|
|
||||||
|
|
||||||
import "github.com/gofiber/fiber/v2"
|
|
||||||
|
|
||||||
// NotFoundRoute func for describe 404 Error route.
|
|
||||||
func NotFoundRoute(fiberApp *fiber.App) {
|
|
||||||
fiberApp.Use(
|
|
||||||
func(c *fiber.Ctx) error {
|
|
||||||
return c.Status(fiber.StatusNotFound).JSON(fiber.Map{
|
|
||||||
"error": true,
|
|
||||||
"msg": "sorry, endpoint is not found",
|
|
||||||
})
|
|
||||||
},
|
|
||||||
)
|
|
||||||
}
|
|
||||||
@@ -1,20 +0,0 @@
|
|||||||
package routes
|
|
||||||
|
|
||||||
import (
|
|
||||||
"github.com/gofiber/fiber/v2"
|
|
||||||
"mizuserver/pkg/controllers"
|
|
||||||
)
|
|
||||||
|
|
||||||
// EntriesRoutes func for describe group of public routes.
|
|
||||||
func EntriesRoutes(fiberApp *fiber.App) {
|
|
||||||
routeGroup := fiberApp.Group("/api")
|
|
||||||
|
|
||||||
routeGroup.Get("/entries", controllers.GetEntries) // get entries (base/thin entries)
|
|
||||||
routeGroup.Get("/entries/:entryId", controllers.GetEntry) // get single (full) entry
|
|
||||||
|
|
||||||
routeGroup.Get("/har", controllers.GetHARs)
|
|
||||||
routeGroup.Get("/resetDB", controllers.DeleteAllEntries) // get single (full) entry
|
|
||||||
routeGroup.Get("/generalStats", controllers.GetGeneralStats) // get general stats about entries in DB
|
|
||||||
|
|
||||||
routeGroup.Get("/tapStatus", controllers.GetTappingStatus) // get tapping status
|
|
||||||
}
|
|
||||||
@@ -1,31 +0,0 @@
|
|||||||
package routes
|
|
||||||
|
|
||||||
import (
|
|
||||||
"github.com/antoniodipinto/ikisocket"
|
|
||||||
"github.com/gofiber/fiber/v2"
|
|
||||||
)
|
|
||||||
|
|
||||||
type EventHandlers interface {
|
|
||||||
WebSocketConnect(ep *ikisocket.EventPayload)
|
|
||||||
WebSocketDisconnect(ep *ikisocket.EventPayload)
|
|
||||||
WebSocketClose(ep *ikisocket.EventPayload)
|
|
||||||
WebSocketError(ep *ikisocket.EventPayload)
|
|
||||||
WebSocketMessage(ep *ikisocket.EventPayload)
|
|
||||||
}
|
|
||||||
|
|
||||||
func WebSocketRoutes(app *fiber.App, eventHandlers EventHandlers) {
|
|
||||||
app.Get("/ws", ikisocket.New(func(kws *ikisocket.Websocket) {
|
|
||||||
kws.SetAttribute("is_tapper", false)
|
|
||||||
}))
|
|
||||||
|
|
||||||
app.Get("/wsTapper", ikisocket.New(func(kws *ikisocket.Websocket) {
|
|
||||||
// Tapper clients are handled differently, they don't need to receive new message broadcasts.
|
|
||||||
kws.SetAttribute("is_tapper", true)
|
|
||||||
}))
|
|
||||||
|
|
||||||
ikisocket.On(ikisocket.EventMessage, eventHandlers.WebSocketMessage)
|
|
||||||
ikisocket.On(ikisocket.EventConnect, eventHandlers.WebSocketConnect)
|
|
||||||
ikisocket.On(ikisocket.EventDisconnect, eventHandlers.WebSocketDisconnect)
|
|
||||||
ikisocket.On(ikisocket.EventClose, eventHandlers.WebSocketClose) // This event is called when the server disconnects the user actively with .Close() method
|
|
||||||
ikisocket.On(ikisocket.EventError, eventHandlers.WebSocketError) // On error event
|
|
||||||
}
|
|
||||||
@@ -1,209 +0,0 @@
|
|||||||
package tap
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"net/http"
|
|
||||||
"strconv"
|
|
||||||
"strings"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/orcaman/concurrent-map"
|
|
||||||
)
|
|
||||||
|
|
||||||
type requestResponsePair struct {
|
|
||||||
Request httpMessage `json:"request"`
|
|
||||||
Response httpMessage `json:"response"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type envoyMessageWrapper struct {
|
|
||||||
HttpBufferedTrace requestResponsePair `json:"http_buffered_trace"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type headerKeyVal struct {
|
|
||||||
Key string `json:"key"`
|
|
||||||
Value string `json:"value"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type messageBody struct {
|
|
||||||
Truncated bool `json:"truncated"`
|
|
||||||
AsBytes string `json:"as_bytes"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type httpMessage struct {
|
|
||||||
IsRequest bool
|
|
||||||
Headers []headerKeyVal `json:"headers"`
|
|
||||||
HTTPVersion string `json:"httpVersion"`
|
|
||||||
Body messageBody `json:"body"`
|
|
||||||
captureTime time.Time
|
|
||||||
orig interface {}
|
|
||||||
requestSenderIp string
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
// Key is {client_addr}:{client_port}->{dest_addr}:{dest_port}
|
|
||||||
type requestResponseMatcher struct {
|
|
||||||
openMessagesMap cmap.ConcurrentMap
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
func createResponseRequestMatcher() requestResponseMatcher {
|
|
||||||
newMatcher := &requestResponseMatcher{openMessagesMap: cmap.New()}
|
|
||||||
return *newMatcher
|
|
||||||
}
|
|
||||||
|
|
||||||
func (matcher *requestResponseMatcher) registerRequest(ident string, request *http.Request, captureTime time.Time, body string, isHTTP2 bool) *envoyMessageWrapper {
|
|
||||||
split := splitIdent(ident)
|
|
||||||
key := genKey(split)
|
|
||||||
|
|
||||||
messageExtraHeaders := []headerKeyVal{
|
|
||||||
{Key: "x-up9-source", Value: split[0]},
|
|
||||||
{Key: "x-up9-destination", Value: split[1] + ":" + split[3]},
|
|
||||||
}
|
|
||||||
|
|
||||||
requestHTTPMessage := requestToMessage(request, captureTime, body, &messageExtraHeaders, isHTTP2, split[0])
|
|
||||||
|
|
||||||
if response, found := matcher.openMessagesMap.Pop(key); found {
|
|
||||||
// Type assertion always succeeds because all of the map's values are of httpMessage type
|
|
||||||
responseHTTPMessage := response.(*httpMessage)
|
|
||||||
if responseHTTPMessage.IsRequest {
|
|
||||||
SilentError("Request-Duplicate", "Got duplicate request with same identifier\n")
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
Debug("Matched open Response for %s\n", key)
|
|
||||||
return matcher.preparePair(&requestHTTPMessage, responseHTTPMessage)
|
|
||||||
}
|
|
||||||
|
|
||||||
matcher.openMessagesMap.Set(key, &requestHTTPMessage)
|
|
||||||
Debug("Registered open Request for %s\n", key)
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (matcher *requestResponseMatcher) registerResponse(ident string, response *http.Response, captureTime time.Time, body string, isHTTP2 bool) *envoyMessageWrapper {
|
|
||||||
split := splitIdent(ident)
|
|
||||||
key := genKey(split)
|
|
||||||
|
|
||||||
responseHTTPMessage := responseToMessage(response, captureTime, body, isHTTP2)
|
|
||||||
|
|
||||||
if request, found := matcher.openMessagesMap.Pop(key); found {
|
|
||||||
// Type assertion always succeeds because all of the map's values are of httpMessage type
|
|
||||||
requestHTTPMessage := request.(*httpMessage)
|
|
||||||
if !requestHTTPMessage.IsRequest {
|
|
||||||
SilentError("Response-Duplicate", "Got duplicate response with same identifier\n")
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
Debug("Matched open Request for %s\n", key)
|
|
||||||
return matcher.preparePair(requestHTTPMessage, &responseHTTPMessage)
|
|
||||||
}
|
|
||||||
|
|
||||||
matcher.openMessagesMap.Set(key, &responseHTTPMessage)
|
|
||||||
Debug("Registered open Response for %s\n", key)
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (matcher *requestResponseMatcher) preparePair(requestHTTPMessage *httpMessage, responseHTTPMessage *httpMessage) *envoyMessageWrapper {
|
|
||||||
matcher.addDuration(requestHTTPMessage, responseHTTPMessage)
|
|
||||||
|
|
||||||
return &envoyMessageWrapper{
|
|
||||||
HttpBufferedTrace: requestResponsePair{
|
|
||||||
Request: *requestHTTPMessage,
|
|
||||||
Response: *responseHTTPMessage,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func requestToMessage(request *http.Request, captureTime time.Time, body string, messageExtraHeaders *[]headerKeyVal, isHTTP2 bool, requestSenderIp string) httpMessage {
|
|
||||||
messageHeaders := make([]headerKeyVal, 0)
|
|
||||||
|
|
||||||
for key, value := range request.Header {
|
|
||||||
messageHeaders = append(messageHeaders, headerKeyVal{Key: key, Value: value[0]})
|
|
||||||
}
|
|
||||||
|
|
||||||
if !isHTTP2 {
|
|
||||||
messageHeaders = append(messageHeaders, headerKeyVal{Key: ":method", Value: request.Method})
|
|
||||||
messageHeaders = append(messageHeaders, headerKeyVal{Key: ":path", Value: request.RequestURI})
|
|
||||||
messageHeaders = append(messageHeaders, headerKeyVal{Key: ":authority", Value: request.Host})
|
|
||||||
messageHeaders = append(messageHeaders, headerKeyVal{Key: ":scheme", Value: "http"})
|
|
||||||
}
|
|
||||||
|
|
||||||
messageHeaders = append(messageHeaders, headerKeyVal{Key: "x-request-start", Value: fmt.Sprintf("%.3f", float64(captureTime.UnixNano()) / float64(1000000000))})
|
|
||||||
|
|
||||||
messageHeaders = append(messageHeaders, *messageExtraHeaders...)
|
|
||||||
|
|
||||||
httpVersion := request.Proto
|
|
||||||
|
|
||||||
requestBody := messageBody{Truncated: false, AsBytes: body}
|
|
||||||
|
|
||||||
return httpMessage{
|
|
||||||
IsRequest: true,
|
|
||||||
Headers: messageHeaders,
|
|
||||||
HTTPVersion: httpVersion,
|
|
||||||
Body: requestBody,
|
|
||||||
captureTime: captureTime,
|
|
||||||
orig: request,
|
|
||||||
requestSenderIp: requestSenderIp,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func responseToMessage(response *http.Response, captureTime time.Time, body string, isHTTP2 bool) httpMessage {
|
|
||||||
messageHeaders := make([]headerKeyVal, 0)
|
|
||||||
|
|
||||||
for key, value := range response.Header {
|
|
||||||
messageHeaders = append(messageHeaders, headerKeyVal{Key: key, Value: value[0]})
|
|
||||||
}
|
|
||||||
|
|
||||||
if !isHTTP2 {
|
|
||||||
messageHeaders = append(messageHeaders, headerKeyVal{Key: ":status", Value: strconv.Itoa(response.StatusCode)})
|
|
||||||
}
|
|
||||||
|
|
||||||
httpVersion := response.Proto
|
|
||||||
|
|
||||||
requestBody := messageBody{Truncated: false, AsBytes: body}
|
|
||||||
|
|
||||||
return httpMessage{
|
|
||||||
IsRequest: false,
|
|
||||||
Headers: messageHeaders,
|
|
||||||
HTTPVersion: httpVersion,
|
|
||||||
Body: requestBody,
|
|
||||||
captureTime: captureTime,
|
|
||||||
orig: response,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (matcher *requestResponseMatcher) addDuration(requestHTTPMessage *httpMessage, responseHTTPMessage *httpMessage) {
|
|
||||||
durationMs := float64(responseHTTPMessage.captureTime.UnixNano() / 1000000) - float64(requestHTTPMessage.captureTime.UnixNano() / 1000000)
|
|
||||||
if durationMs < 1 {
|
|
||||||
durationMs = 1
|
|
||||||
}
|
|
||||||
|
|
||||||
responseHTTPMessage.Headers = append(responseHTTPMessage.Headers, headerKeyVal{Key: "x-up9-duration-ms", Value: fmt.Sprintf("%.0f", durationMs)})
|
|
||||||
}
|
|
||||||
|
|
||||||
func splitIdent(ident string) []string {
|
|
||||||
ident = strings.Replace(ident, "->", " ", -1)
|
|
||||||
return strings.Split(ident, " ")
|
|
||||||
}
|
|
||||||
|
|
||||||
func genKey(split []string) string {
|
|
||||||
key := fmt.Sprintf("%s:%s->%s:%s,%s", split[0], split[2], split[1], split[3], split[4])
|
|
||||||
return key
|
|
||||||
}
|
|
||||||
|
|
||||||
func (matcher *requestResponseMatcher) deleteOlderThan(t time.Time) int {
|
|
||||||
keysToPop := make([]string, 0)
|
|
||||||
for item := range matcher.openMessagesMap.IterBuffered() {
|
|
||||||
// Map only contains values of type httpMessage
|
|
||||||
message, _ := item.Val.(*httpMessage)
|
|
||||||
|
|
||||||
if message.captureTime.Before(t) {
|
|
||||||
keysToPop = append(keysToPop, item.Key)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
numDeleted := len(keysToPop)
|
|
||||||
|
|
||||||
for _, key := range keysToPop {
|
|
||||||
_, _ = matcher.openMessagesMap.Pop(key)
|
|
||||||
}
|
|
||||||
|
|
||||||
return numDeleted
|
|
||||||
}
|
|
||||||
@@ -1,35 +0,0 @@
|
|||||||
package tap
|
|
||||||
|
|
||||||
import (
|
|
||||||
"sync"
|
|
||||||
)
|
|
||||||
|
|
||||||
type AppStats struct {
|
|
||||||
matchedMessages int
|
|
||||||
}
|
|
||||||
|
|
||||||
type StatsTracker struct {
|
|
||||||
stats AppStats
|
|
||||||
statsMutex sync.Mutex
|
|
||||||
}
|
|
||||||
|
|
||||||
func (st *StatsTracker) incMatchedMessages() {
|
|
||||||
st.statsMutex.Lock()
|
|
||||||
st.stats.matchedMessages++
|
|
||||||
st.statsMutex.Unlock()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (st *StatsTracker) dumpStats() AppStats {
|
|
||||||
st.statsMutex.Lock()
|
|
||||||
|
|
||||||
stats := AppStats{
|
|
||||||
matchedMessages: st.stats.matchedMessages,
|
|
||||||
}
|
|
||||||
|
|
||||||
st.stats.matchedMessages = 0
|
|
||||||
|
|
||||||
st.statsMutex.Unlock()
|
|
||||||
|
|
||||||
return stats
|
|
||||||
}
|
|
||||||
|
|
||||||
@@ -1,239 +0,0 @@
|
|||||||
package tap
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bytes"
|
|
||||||
"encoding/json"
|
|
||||||
"fmt"
|
|
||||||
"log"
|
|
||||||
"net/http"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/gorilla/websocket"
|
|
||||||
"github.com/patrickmn/go-cache"
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
const (
|
|
||||||
// Time allowed to write a message to the peer.
|
|
||||||
writeWait = 10 * time.Second
|
|
||||||
|
|
||||||
// Time allowed to read the next pong message from the peer.
|
|
||||||
pongWait = 60 * time.Second
|
|
||||||
|
|
||||||
// Send pings to peer with this period. Must be less than pongWait.
|
|
||||||
pingPeriod = (pongWait * 9) / 10
|
|
||||||
|
|
||||||
// Maximum message size allowed from peer.
|
|
||||||
maxMessageSize = 512
|
|
||||||
)
|
|
||||||
|
|
||||||
var (
|
|
||||||
newline = []byte{'\n'}
|
|
||||||
space = []byte{' '}
|
|
||||||
hub *Hub
|
|
||||||
outboundSocketNotifyExpiringCache = cache.New(outboundThrottleCacheExpiryPeriod, outboundThrottleCacheExpiryPeriod)
|
|
||||||
)
|
|
||||||
|
|
||||||
var upgrader = websocket.Upgrader{
|
|
||||||
ReadBufferSize: 1024,
|
|
||||||
WriteBufferSize: 1024,
|
|
||||||
CheckOrigin: func (_ *http.Request) bool { return true },
|
|
||||||
}
|
|
||||||
|
|
||||||
// Client is a middleman between the websocket connection and the hub.
|
|
||||||
type Client struct {
|
|
||||||
hub *Hub
|
|
||||||
|
|
||||||
// The websocket connection.
|
|
||||||
conn *websocket.Conn
|
|
||||||
|
|
||||||
// Buffered channel of outbound messages.
|
|
||||||
send chan []byte
|
|
||||||
}
|
|
||||||
|
|
||||||
type OutBoundLinkMessage struct {
|
|
||||||
SourceIP string `json:"sourceIP"`
|
|
||||||
IP string `json:"ip"`
|
|
||||||
Port int `json:"port"`
|
|
||||||
Type string `json:"type"`
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
// readPump pumps messages from the websocket connection to the hub.
|
|
||||||
//
|
|
||||||
// The application runs readPump in a per-connection goroutine. The application
|
|
||||||
// ensures that there is at most one reader on a connection by executing all
|
|
||||||
// reads from this goroutine.
|
|
||||||
func (c *Client) readPump() {
|
|
||||||
defer func() {
|
|
||||||
c.hub.unregister <- c
|
|
||||||
c.conn.Close()
|
|
||||||
}()
|
|
||||||
c.conn.SetReadLimit(maxMessageSize)
|
|
||||||
c.conn.SetReadDeadline(time.Now().Add(pongWait))
|
|
||||||
c.conn.SetPongHandler(func(string) error { c.conn.SetReadDeadline(time.Now().Add(pongWait)); return nil })
|
|
||||||
for {
|
|
||||||
_, message, err := c.conn.ReadMessage()
|
|
||||||
if err != nil {
|
|
||||||
if websocket.IsUnexpectedCloseError(err, websocket.CloseGoingAway, websocket.CloseAbnormalClosure) {
|
|
||||||
log.Printf("error: %v", err)
|
|
||||||
}
|
|
||||||
break
|
|
||||||
}
|
|
||||||
message = bytes.TrimSpace(bytes.Replace(message, newline, space, -1))
|
|
||||||
c.hub.onMessageCallback(message)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// writePump pumps messages from the hub to the websocket connection.
|
|
||||||
//
|
|
||||||
// A goroutine running writePump is started for each connection. The
|
|
||||||
// application ensures that there is at most one writer to a connection by
|
|
||||||
// executing all writes from this goroutine.
|
|
||||||
func (c *Client) writePump() {
|
|
||||||
ticker := time.NewTicker(pingPeriod)
|
|
||||||
defer func() {
|
|
||||||
ticker.Stop()
|
|
||||||
c.conn.Close()
|
|
||||||
}()
|
|
||||||
for {
|
|
||||||
select {
|
|
||||||
case message, ok := <-c.send:
|
|
||||||
c.conn.SetWriteDeadline(time.Now().Add(writeWait))
|
|
||||||
if !ok {
|
|
||||||
// The hub closed the channel.
|
|
||||||
c.conn.WriteMessage(websocket.CloseMessage, []byte{})
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
w, err := c.conn.NextWriter(websocket.TextMessage)
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
w.Write(message)
|
|
||||||
|
|
||||||
|
|
||||||
if err := w.Close(); err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
case <-ticker.C:
|
|
||||||
c.conn.SetWriteDeadline(time.Now().Add(writeWait))
|
|
||||||
if err := c.conn.WriteMessage(websocket.PingMessage, nil); err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
type Hub struct {
|
|
||||||
// Registered clients.
|
|
||||||
clients map[*Client]bool
|
|
||||||
|
|
||||||
// Inbound messages from the clients.
|
|
||||||
broadcast chan []byte
|
|
||||||
|
|
||||||
// Register requests from the clients.
|
|
||||||
register chan *Client
|
|
||||||
|
|
||||||
// Unregister requests from clients.
|
|
||||||
unregister chan *Client
|
|
||||||
|
|
||||||
// Handle messages from client
|
|
||||||
onMessageCallback func([]byte)
|
|
||||||
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
func newHub(onMessageCallback func([]byte)) *Hub {
|
|
||||||
return &Hub{
|
|
||||||
broadcast: make(chan []byte),
|
|
||||||
register: make(chan *Client),
|
|
||||||
unregister: make(chan *Client),
|
|
||||||
clients: make(map[*Client]bool),
|
|
||||||
onMessageCallback: onMessageCallback,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (h *Hub) run() {
|
|
||||||
for {
|
|
||||||
select {
|
|
||||||
case client := <-h.register:
|
|
||||||
h.clients[client] = true
|
|
||||||
case client := <-h.unregister:
|
|
||||||
if _, ok := h.clients[client]; ok {
|
|
||||||
delete(h.clients, client)
|
|
||||||
close(client.send)
|
|
||||||
}
|
|
||||||
case message := <-h.broadcast:
|
|
||||||
// matched messages counter is incremented in this thread instead of in multiple http reader
|
|
||||||
// threads in order to reduce contention.
|
|
||||||
statsTracker.incMatchedMessages()
|
|
||||||
|
|
||||||
for client := range h.clients {
|
|
||||||
select {
|
|
||||||
case client.send <- message:
|
|
||||||
default:
|
|
||||||
close(client.send)
|
|
||||||
delete(h.clients, client)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
// serveWs handles websocket requests from the peer.
|
|
||||||
func serveWs(hub *Hub, w http.ResponseWriter, r *http.Request) {
|
|
||||||
conn, err := upgrader.Upgrade(w, r, nil)
|
|
||||||
if err != nil {
|
|
||||||
log.Println(err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
client := &Client{hub: hub, conn: conn, send: make(chan []byte, 256)}
|
|
||||||
client.hub.register <- client
|
|
||||||
|
|
||||||
// Allow collection of memory referenced by the caller by doing all work in
|
|
||||||
// new goroutines.
|
|
||||||
go client.writePump()
|
|
||||||
go client.readPump()
|
|
||||||
}
|
|
||||||
|
|
||||||
func startOutputServer(port string, messageCallback func([]byte)) {
|
|
||||||
hub = newHub(messageCallback)
|
|
||||||
go hub.run()
|
|
||||||
http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
|
|
||||||
serveWs(hub, w, r)
|
|
||||||
})
|
|
||||||
err := http.ListenAndServe("0.0.0.0:" + port, nil)
|
|
||||||
if err != nil {
|
|
||||||
log.Fatal("Output server error: ", err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func broadcastReqResPair(reqResJson []byte) {
|
|
||||||
hub.broadcast <- reqResJson
|
|
||||||
}
|
|
||||||
|
|
||||||
func broadcastOutboundLink(srcIP string, dstIP string, dstPort int) {
|
|
||||||
cacheKey := fmt.Sprintf("%s -> %s:%d", srcIP, dstIP, dstPort)
|
|
||||||
_, isInCache := outboundSocketNotifyExpiringCache.Get(cacheKey)
|
|
||||||
if isInCache {
|
|
||||||
return
|
|
||||||
} else {
|
|
||||||
outboundSocketNotifyExpiringCache.SetDefault(cacheKey, true)
|
|
||||||
}
|
|
||||||
|
|
||||||
socketMessage := OutBoundLinkMessage{
|
|
||||||
SourceIP: srcIP,
|
|
||||||
IP: dstIP,
|
|
||||||
Port: dstPort,
|
|
||||||
Type: "outboundSocketDetected",
|
|
||||||
}
|
|
||||||
|
|
||||||
jsonStr, err := json.Marshal(socketMessage)
|
|
||||||
if err != nil {
|
|
||||||
log.Printf("error marshalling outbound socket detection object: %v", err)
|
|
||||||
} else {
|
|
||||||
hub.broadcast <- jsonStr
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,117 +0,0 @@
|
|||||||
package tap
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"sync"
|
|
||||||
|
|
||||||
"github.com/google/gopacket"
|
|
||||||
"github.com/google/gopacket/layers" // pulls in all layers decoders
|
|
||||||
"github.com/google/gopacket/reassembly"
|
|
||||||
)
|
|
||||||
|
|
||||||
/*
|
|
||||||
* The TCP factory: returns a new Stream
|
|
||||||
* Implements gopacket.reassembly.StreamFactory interface (New)
|
|
||||||
* Generates a new tcp stream for each new tcp connection. Closes the stream when the connection closes.
|
|
||||||
*/
|
|
||||||
type tcpStreamFactory struct {
|
|
||||||
wg sync.WaitGroup
|
|
||||||
doHTTP bool
|
|
||||||
harWriter *HarWriter
|
|
||||||
}
|
|
||||||
|
|
||||||
func (factory *tcpStreamFactory) New(net, transport gopacket.Flow, tcp *layers.TCP, ac reassembly.AssemblerContext) reassembly.Stream {
|
|
||||||
Debug("* NEW: %s %s\n", net, transport)
|
|
||||||
fsmOptions := reassembly.TCPSimpleFSMOptions{
|
|
||||||
SupportMissingEstablishment: *allowmissinginit,
|
|
||||||
}
|
|
||||||
Debug("Current App Ports: %v\n", appPorts)
|
|
||||||
dstIp := net.Dst().String()
|
|
||||||
dstPort := int(tcp.DstPort)
|
|
||||||
|
|
||||||
if factory.shouldNotifyOnOutboundLink(dstIp, dstPort) {
|
|
||||||
broadcastOutboundLink(net.Src().String(), dstIp, dstPort)
|
|
||||||
}
|
|
||||||
isHTTP := factory.shouldTap(dstIp, dstPort)
|
|
||||||
stream := &tcpStream{
|
|
||||||
net: net,
|
|
||||||
transport: transport,
|
|
||||||
isDNS: tcp.SrcPort == 53 || tcp.DstPort == 53,
|
|
||||||
isHTTP: isHTTP && factory.doHTTP,
|
|
||||||
reversed: tcp.SrcPort == 80,
|
|
||||||
tcpstate: reassembly.NewTCPSimpleFSM(fsmOptions),
|
|
||||||
ident: fmt.Sprintf("%s:%s", net, transport),
|
|
||||||
optchecker: reassembly.NewTCPOptionCheck(),
|
|
||||||
}
|
|
||||||
if stream.isHTTP {
|
|
||||||
stream.client = httpReader{
|
|
||||||
msgQueue: make(chan httpReaderDataMsg),
|
|
||||||
ident: fmt.Sprintf("%s %s", net, transport),
|
|
||||||
tcpID: tcpID{
|
|
||||||
srcIP: net.Src().String(),
|
|
||||||
dstIP: net.Dst().String(),
|
|
||||||
srcPort: transport.Src().String(),
|
|
||||||
dstPort: transport.Dst().String(),
|
|
||||||
},
|
|
||||||
hexdump: *hexdump,
|
|
||||||
parent: stream,
|
|
||||||
isClient: true,
|
|
||||||
harWriter: factory.harWriter,
|
|
||||||
}
|
|
||||||
stream.server = httpReader{
|
|
||||||
msgQueue: make(chan httpReaderDataMsg),
|
|
||||||
ident: fmt.Sprintf("%s %s", net.Reverse(), transport.Reverse()),
|
|
||||||
tcpID: tcpID{
|
|
||||||
srcIP: net.Dst().String(),
|
|
||||||
dstIP: net.Src().String(),
|
|
||||||
srcPort: transport.Dst().String(),
|
|
||||||
dstPort: transport.Src().String(),
|
|
||||||
},
|
|
||||||
hexdump: *hexdump,
|
|
||||||
parent: stream,
|
|
||||||
harWriter: factory.harWriter,
|
|
||||||
}
|
|
||||||
factory.wg.Add(2)
|
|
||||||
// Start reading from channels stream.client.bytes and stream.server.bytes
|
|
||||||
go stream.client.run(&factory.wg)
|
|
||||||
go stream.server.run(&factory.wg)
|
|
||||||
}
|
|
||||||
return stream
|
|
||||||
}
|
|
||||||
|
|
||||||
func (factory *tcpStreamFactory) WaitGoRoutines() {
|
|
||||||
factory.wg.Wait()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (factory *tcpStreamFactory) shouldTap(dstIP string, dstPort int) bool {
|
|
||||||
if hostMode {
|
|
||||||
if inArrayString(HostAppAddresses, fmt.Sprintf("%s:%d", dstIP, dstPort)) == true {
|
|
||||||
return true
|
|
||||||
} else if inArrayString(HostAppAddresses, dstIP) == true {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
return false
|
|
||||||
} else {
|
|
||||||
isTappedPort := dstPort == 80 || (appPorts != nil && (inArrayInt(appPorts, dstPort)))
|
|
||||||
if !isTappedPort {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
if !*anydirection {
|
|
||||||
isDirectedHere := inArrayString(ownIps, dstIP)
|
|
||||||
if !isDirectedHere {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (factory *tcpStreamFactory) shouldNotifyOnOutboundLink(dstIP string, dstPort int) bool {
|
|
||||||
if inArrayInt(remoteOnlyOutboundPorts, dstPort) {
|
|
||||||
isDirectedHere := inArrayString(ownIps, dstIP)
|
|
||||||
return !isDirectedHere && !isPrivateIP(dstIP)
|
|
||||||
}
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
BIN
assets/mizu-example.png
Normal file
BIN
assets/mizu-example.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 811 KiB |
24
assets/mizu-logo.svg
Normal file
24
assets/mizu-logo.svg
Normal file
File diff suppressed because one or more lines are too long
|
After Width: | Height: | Size: 44 KiB |
BIN
assets/mizu-ui.png
Normal file
BIN
assets/mizu-ui.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 491 KiB |
@@ -5,7 +5,9 @@ SERVER_NAME=mizu
|
|||||||
GCP_PROJECT=up9-docker-hub
|
GCP_PROJECT=up9-docker-hub
|
||||||
REPOSITORY=gcr.io/$GCP_PROJECT
|
REPOSITORY=gcr.io/$GCP_PROJECT
|
||||||
GIT_BRANCH=$(git branch | grep \* | cut -d ' ' -f2 | tr '[:upper:]' '[:lower:]')
|
GIT_BRANCH=$(git branch | grep \* | cut -d ' ' -f2 | tr '[:upper:]' '[:lower:]')
|
||||||
DOCKER_TAGGED_BUILD=$REPOSITORY/$SERVER_NAME/$GIT_BRANCH:latest
|
SEM_VER=${SEM_VER=0.0.0}
|
||||||
|
DOCKER_REPO=$REPOSITORY/$SERVER_NAME/$GIT_BRANCH
|
||||||
|
DOCKER_TAGGED_BUILDS=("$DOCKER_REPO:latest" "$DOCKER_REPO:$SEM_VER")
|
||||||
|
|
||||||
if [ "$GIT_BRANCH" = 'develop' -o "$GIT_BRANCH" = 'master' -o "$GIT_BRANCH" = 'main' ]
|
if [ "$GIT_BRANCH" = 'develop' -o "$GIT_BRANCH" = 'master' -o "$GIT_BRANCH" = 'main' ]
|
||||||
then
|
then
|
||||||
@@ -13,8 +15,12 @@ then
|
|||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
echo "building $DOCKER_TAGGED_BUILD"
|
echo "building ${DOCKER_TAGGED_BUILDS[@]}"
|
||||||
docker build -t "$DOCKER_TAGGED_BUILD" .
|
DOCKER_TAGS_ARGS=$(echo ${DOCKER_TAGGED_BUILDS[@]/#/-t }) # "-t FIRST_TAG -t SECOND_TAG ..."
|
||||||
|
docker build $DOCKER_TAGS_ARGS --build-arg SEM_VER=${SEM_VER} --build-arg BUILD_TIMESTAMP=${BUILD_TIMESTAMP} --build-arg GIT_BRANCH=${GIT_BRANCH} --build-arg COMMIT_HASH=${COMMIT_HASH} .
|
||||||
|
|
||||||
echo pushing to "$REPOSITORY"
|
for DOCKER_TAG in "${DOCKER_TAGGED_BUILDS[@]}"
|
||||||
docker push "$DOCKER_TAGGED_BUILD"
|
do
|
||||||
|
echo pushing "$DOCKER_TAG"
|
||||||
|
docker push "$DOCKER_TAG"
|
||||||
|
done
|
||||||
|
|||||||
1
cli/.gitignore
vendored
Normal file
1
cli/.gitignore
vendored
Normal file
@@ -0,0 +1 @@
|
|||||||
|
bin
|
||||||
21
cli/Makefile
21
cli/Makefile
@@ -1,6 +1,9 @@
|
|||||||
FOLDER=$(GOOS).$(GOARCH)
|
SUFFIX=$(GOOS)_$(GOARCH)
|
||||||
COMMIT_HASH=$(shell git rev-parse HEAD)
|
COMMIT_HASH=$(shell git rev-parse HEAD)
|
||||||
GIT_BRANCH=$(shell git branch --show-current | tr '[:upper:]' '[:lower:]')
|
GIT_BRANCH=$(shell git branch --show-current | tr '[:upper:]' '[:lower:]')
|
||||||
|
GIT_VERSION=$(shell git branch --show-current | tr '[:upper:]' '[:lower:]')
|
||||||
|
BUILD_TIMESTAMP=$(shell date +%s)
|
||||||
|
export SEM_VER?=0.0.0
|
||||||
|
|
||||||
.PHONY: help
|
.PHONY: help
|
||||||
.DEFAULT_GOAL := help
|
.DEFAULT_GOAL := help
|
||||||
@@ -11,22 +14,28 @@ help: ## This help.
|
|||||||
install:
|
install:
|
||||||
go install mizu.go
|
go install mizu.go
|
||||||
|
|
||||||
build: ## build mizu CLI binary (select platform via GOOS / GOARCH env variables)
|
build: ## Build mizu CLI binary (select platform via GOOS / GOARCH env variables).
|
||||||
go build -ldflags="-X 'github.com/up9inc/mizu/cli/mizu.GitCommitHash=$(COMMIT_HASH)' -X 'github.com/up9inc/mizu/cli/mizu.Branch=$(GIT_BRANCH)'" -o bin/$(FOLDER)/mizu mizu.go
|
go build -ldflags="-X 'github.com/up9inc/mizu/cli/mizu.GitCommitHash=$(COMMIT_HASH)' \
|
||||||
|
-X 'github.com/up9inc/mizu/cli/mizu.Branch=$(GIT_BRANCH)' \
|
||||||
|
-X 'github.com/up9inc/mizu/cli/mizu.BuildTimestamp=$(BUILD_TIMESTAMP)' \
|
||||||
|
-X 'github.com/up9inc/mizu/cli/mizu.SemVer=$(SEM_VER)'" \
|
||||||
|
-o bin/mizu_$(SUFFIX) mizu.go
|
||||||
|
(cd bin && shasum -a 256 mizu_${SUFFIX} > mizu_${SUFFIX}.sha256)
|
||||||
|
|
||||||
build-all: ## build for all supported platforms
|
build-all: ## Build for all supported platforms.
|
||||||
@echo "Compiling for every OS and Platform"
|
@echo "Compiling for every OS and Platform"
|
||||||
|
@mkdir -p bin && echo "SHA256 checksums available for compiled binaries \n\nRun \`shasum -a 256 -c mizu_OS_ARCH.sha256\` to verify\n\n" > bin/README.md
|
||||||
@$(MAKE) build GOOS=darwin GOARCH=amd64
|
@$(MAKE) build GOOS=darwin GOARCH=amd64
|
||||||
@$(MAKE) build GOOS=linux GOARCH=amd64
|
@$(MAKE) build GOOS=linux GOARCH=amd64
|
||||||
|
@# $(MAKE) build GOOS=darwin GOARCH=arm64
|
||||||
@# $(MAKE) GOOS=windows GOARCH=amd64
|
@# $(MAKE) GOOS=windows GOARCH=amd64
|
||||||
@# $(MAKE) GOOS=linux GOARCH=386
|
@# $(MAKE) GOOS=linux GOARCH=386
|
||||||
@# $(MAKE) GOOS=windows GOARCH=386
|
@# $(MAKE) GOOS=windows GOARCH=386
|
||||||
@# $(MAKE) GOOS=darwin GOARCH=arm64
|
|
||||||
@# $(MAKE) GOOS=linux GOARCH=arm64
|
@# $(MAKE) GOOS=linux GOARCH=arm64
|
||||||
@# $(MAKE) GOOS=windows GOARCH=arm64
|
@# $(MAKE) GOOS=windows GOARCH=arm64
|
||||||
@echo "---------"
|
@echo "---------"
|
||||||
@find ./bin -ls
|
@find ./bin -ls
|
||||||
|
|
||||||
clean: ## clean all build artifacts
|
clean: ## Clean all build artifacts.
|
||||||
go clean
|
go clean
|
||||||
rm -rf ./bin/*
|
rm -rf ./bin/*
|
||||||
|
|||||||
40
cli/cmd/config.go
Normal file
40
cli/cmd/config.go
Normal file
@@ -0,0 +1,40 @@
|
|||||||
|
package cmd
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"github.com/spf13/cobra"
|
||||||
|
"github.com/up9inc/mizu/cli/mizu"
|
||||||
|
"github.com/up9inc/mizu/cli/uiUtils"
|
||||||
|
"io/ioutil"
|
||||||
|
)
|
||||||
|
|
||||||
|
var regenerateFile bool
|
||||||
|
|
||||||
|
var configCmd = &cobra.Command{
|
||||||
|
Use: "config",
|
||||||
|
Short: "Generate config with default values",
|
||||||
|
RunE: func(cmd *cobra.Command, args []string) error {
|
||||||
|
template, err := mizu.GetConfigWithDefaults()
|
||||||
|
if err != nil {
|
||||||
|
mizu.Log.Errorf("Failed generating config with defaults %v", err)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
if regenerateFile {
|
||||||
|
data := []byte(template)
|
||||||
|
if err := ioutil.WriteFile(mizu.GetConfigFilePath(), data, 0644); err != nil {
|
||||||
|
mizu.Log.Errorf("Failed writing config %v", err)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
mizu.Log.Infof(fmt.Sprintf("Template File written to %s", fmt.Sprintf(uiUtils.Purple, mizu.GetConfigFilePath())))
|
||||||
|
} else {
|
||||||
|
mizu.Log.Debugf("Writing template config.\n%v", template)
|
||||||
|
fmt.Printf("%v", template)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
rootCmd.AddCommand(configCmd)
|
||||||
|
configCmd.Flags().BoolVarP(®enerateFile, "regenerate", "r", false, fmt.Sprintf("Regenerate the config file with default values %s", mizu.GetConfigFilePath()))
|
||||||
|
}
|
||||||
@@ -1,21 +1,23 @@
|
|||||||
package cmd
|
package cmd
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"github.com/creasty/defaults"
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
|
"github.com/up9inc/mizu/cli/mizu"
|
||||||
|
"github.com/up9inc/mizu/cli/mizu/configStructs"
|
||||||
)
|
)
|
||||||
|
|
||||||
type MizuFetchOptions struct {
|
|
||||||
Limit uint16
|
|
||||||
Directory string
|
|
||||||
}
|
|
||||||
|
|
||||||
var mizuFetchOptions = MizuFetchOptions{}
|
|
||||||
|
|
||||||
var fetchCmd = &cobra.Command{
|
var fetchCmd = &cobra.Command{
|
||||||
Use: "fetch",
|
Use: "fetch",
|
||||||
Short: "Download recorded traffic to files",
|
Short: "Download recorded traffic to files",
|
||||||
RunE: func(cmd *cobra.Command, args []string) error {
|
RunE: func(cmd *cobra.Command, args []string) error {
|
||||||
RunMizuFetch(&mizuFetchOptions)
|
go mizu.ReportRun("fetch", mizu.Config.Fetch)
|
||||||
|
if isCompatible, err := mizu.CheckVersionCompatibility(mizu.Config.Fetch.MizuPort); err != nil {
|
||||||
|
return err
|
||||||
|
} else if !isCompatible {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
RunMizuFetch()
|
||||||
return nil
|
return nil
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
@@ -23,6 +25,11 @@ var fetchCmd = &cobra.Command{
|
|||||||
func init() {
|
func init() {
|
||||||
rootCmd.AddCommand(fetchCmd)
|
rootCmd.AddCommand(fetchCmd)
|
||||||
|
|
||||||
fetchCmd.Flags().Uint16VarP(&mizuFetchOptions.Limit, "limit", "l", 1000, "Provide a custom limit for entries to fetch")
|
defaultFetchConfig := configStructs.FetchConfig{}
|
||||||
fetchCmd.Flags().StringVarP(&mizuFetchOptions.Directory, "directory", "d", ".", "Provide a custom directory for fetched entries")
|
defaults.Set(&defaultFetchConfig)
|
||||||
|
|
||||||
|
fetchCmd.Flags().StringP(configStructs.DirectoryFetchName, "d", defaultFetchConfig.Directory, "Provide a custom directory for fetched entries")
|
||||||
|
fetchCmd.Flags().Int(configStructs.FromTimestampFetchName, defaultFetchConfig.FromTimestamp, "Custom start timestamp for fetched entries")
|
||||||
|
fetchCmd.Flags().Int(configStructs.ToTimestampFetchName, defaultFetchConfig.ToTimestamp, "Custom end timestamp fetched entries")
|
||||||
|
fetchCmd.Flags().Uint16P(configStructs.MizuPortFetchName, "p", defaultFetchConfig.MizuPort, "Custom port for mizu")
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,6 +4,8 @@ import (
|
|||||||
"archive/zip"
|
"archive/zip"
|
||||||
"bytes"
|
"bytes"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"github.com/up9inc/mizu/cli/kubernetes"
|
||||||
|
"github.com/up9inc/mizu/cli/mizu"
|
||||||
"io"
|
"io"
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
"log"
|
"log"
|
||||||
@@ -13,8 +15,9 @@ import (
|
|||||||
"strings"
|
"strings"
|
||||||
)
|
)
|
||||||
|
|
||||||
func RunMizuFetch(fetch *MizuFetchOptions) {
|
func RunMizuFetch() {
|
||||||
resp, err := http.Get(fmt.Sprintf("http://localhost:8899/api/har?limit=%v", fetch.Limit))
|
mizuProxiedUrl := kubernetes.GetMizuApiServerProxiedHostAndPath(mizu.Config.Fetch.MizuPort)
|
||||||
|
resp, err := http.Get(fmt.Sprintf("http://%s/api/har?from=%v&to=%v", mizuProxiedUrl, mizu.Config.Fetch.FromTimestamp, mizu.Config.Fetch.ToTimestamp))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatal(err)
|
log.Fatal(err)
|
||||||
}
|
}
|
||||||
@@ -30,8 +33,8 @@ func RunMizuFetch(fetch *MizuFetchOptions) {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatal(err)
|
log.Fatal(err)
|
||||||
}
|
}
|
||||||
_ = Unzip(zipReader, fetch.Directory)
|
|
||||||
|
|
||||||
|
_ = Unzip(zipReader, mizu.Config.Fetch.Directory)
|
||||||
}
|
}
|
||||||
|
|
||||||
func Unzip(reader *zip.Reader, dest string) error {
|
func Unzip(reader *zip.Reader, dest string) error {
|
||||||
@@ -53,7 +56,7 @@ func Unzip(reader *zip.Reader, dest string) error {
|
|||||||
path := filepath.Join(dest, f.Name)
|
path := filepath.Join(dest, f.Name)
|
||||||
|
|
||||||
// Check for ZipSlip (Directory traversal)
|
// Check for ZipSlip (Directory traversal)
|
||||||
if !strings.HasPrefix(path, filepath.Clean(dest) + string(os.PathSeparator)) {
|
if !strings.HasPrefix(path, filepath.Clean(dest)+string(os.PathSeparator)) {
|
||||||
return fmt.Errorf("illegal file path: %s", path)
|
return fmt.Errorf("illegal file path: %s", path)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -61,7 +64,7 @@ func Unzip(reader *zip.Reader, dest string) error {
|
|||||||
_ = os.MkdirAll(path, f.Mode())
|
_ = os.MkdirAll(path, f.Mode())
|
||||||
} else {
|
} else {
|
||||||
_ = os.MkdirAll(filepath.Dir(path), f.Mode())
|
_ = os.MkdirAll(filepath.Dir(path), f.Mode())
|
||||||
fmt.Print("writing HAR file [ ", path, " ] .. ")
|
mizu.Log.Infof("writing HAR file [ %v ]", path)
|
||||||
f, err := os.OpenFile(path, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, f.Mode())
|
f, err := os.OpenFile(path, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, f.Mode())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
@@ -70,7 +73,7 @@ func Unzip(reader *zip.Reader, dest string) error {
|
|||||||
if err := f.Close(); err != nil {
|
if err := f.Close(); err != nil {
|
||||||
panic(err)
|
panic(err)
|
||||||
}
|
}
|
||||||
fmt.Println(" done")
|
mizu.Log.Info(" done")
|
||||||
}()
|
}()
|
||||||
|
|
||||||
_, err = io.Copy(f, rc)
|
_, err = io.Copy(f, rc)
|
||||||
@@ -90,5 +93,3 @@ func Unzip(reader *zip.Reader, dest string) error {
|
|||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
46
cli/cmd/logs.go
Normal file
46
cli/cmd/logs.go
Normal file
@@ -0,0 +1,46 @@
|
|||||||
|
package cmd
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"github.com/spf13/cobra"
|
||||||
|
"github.com/up9inc/mizu/cli/fsUtils"
|
||||||
|
"github.com/up9inc/mizu/cli/kubernetes"
|
||||||
|
"github.com/up9inc/mizu/cli/mizu"
|
||||||
|
"os"
|
||||||
|
"path"
|
||||||
|
)
|
||||||
|
|
||||||
|
var filePath string
|
||||||
|
|
||||||
|
var logsCmd = &cobra.Command{
|
||||||
|
Use: "logs",
|
||||||
|
Short: "Create a zip file with logs for Github issue or troubleshoot",
|
||||||
|
RunE: func(cmd *cobra.Command, args []string) error {
|
||||||
|
kubernetesProvider, err := kubernetes.NewProvider(mizu.Config.View.KubeConfigPath)
|
||||||
|
if err != nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
ctx, _ := context.WithCancel(context.Background())
|
||||||
|
|
||||||
|
if filePath == "" {
|
||||||
|
pwd, err := os.Getwd()
|
||||||
|
if err != nil {
|
||||||
|
mizu.Log.Errorf("Failed to get PWD, %v (try using `mizu logs -f <full path dest zip file>)`", err)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
filePath = path.Join(pwd, "mizu_logs.zip")
|
||||||
|
}
|
||||||
|
mizu.Log.Debugf("Using file path %s", filePath)
|
||||||
|
|
||||||
|
if err := fsUtils.DumpLogs(kubernetesProvider, ctx, filePath); err != nil {
|
||||||
|
mizu.Log.Errorf("Failed dump logs %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
rootCmd.AddCommand(logsCmd)
|
||||||
|
logsCmd.Flags().StringVarP(&filePath, "file", "f", "", "Path for zip file (default current <pwd>\\mizu_logs.zip)")
|
||||||
|
}
|
||||||
@@ -1,7 +1,10 @@
|
|||||||
package cmd
|
package cmd
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"fmt"
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
|
"github.com/up9inc/mizu/cli/fsUtils"
|
||||||
|
"github.com/up9inc/mizu/cli/mizu"
|
||||||
)
|
)
|
||||||
|
|
||||||
var rootCmd = &cobra.Command{
|
var rootCmd = &cobra.Command{
|
||||||
@@ -9,6 +12,21 @@ var rootCmd = &cobra.Command{
|
|||||||
Short: "A web traffic viewer for kubernetes",
|
Short: "A web traffic viewer for kubernetes",
|
||||||
Long: `A web traffic viewer for kubernetes
|
Long: `A web traffic viewer for kubernetes
|
||||||
Further info is available at https://github.com/up9inc/mizu`,
|
Further info is available at https://github.com/up9inc/mizu`,
|
||||||
|
PersistentPreRunE: func(cmd *cobra.Command, args []string) error {
|
||||||
|
if err := fsUtils.EnsureDir(mizu.GetMizuFolderPath()); err != nil {
|
||||||
|
mizu.Log.Errorf("Failed to use mizu folder, %v", err)
|
||||||
|
}
|
||||||
|
mizu.InitLogger()
|
||||||
|
if err := mizu.InitConfig(cmd); err != nil {
|
||||||
|
mizu.Log.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
rootCmd.PersistentFlags().StringSlice(mizu.SetCommandName, []string{}, fmt.Sprintf("Override values using --%s", mizu.SetCommandName))
|
||||||
}
|
}
|
||||||
|
|
||||||
// Execute adds all child commands to the root command and sets flags appropriately.
|
// Execute adds all child commands to the root command and sets flags appropriately.
|
||||||
|
|||||||
@@ -2,44 +2,53 @@ package cmd
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"os"
|
||||||
"github.com/up9inc/mizu/cli/mizu"
|
|
||||||
"regexp"
|
|
||||||
|
|
||||||
|
"github.com/creasty/defaults"
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
|
"github.com/up9inc/mizu/cli/errormessage"
|
||||||
|
"github.com/up9inc/mizu/cli/mizu"
|
||||||
|
"github.com/up9inc/mizu/cli/mizu/configStructs"
|
||||||
|
"github.com/up9inc/mizu/cli/uiUtils"
|
||||||
)
|
)
|
||||||
|
|
||||||
type MizuTapOptions struct {
|
const analysisMessageToConfirm = `NOTE: running mizu with --analysis flag will upload recorded traffic for further analysis and enriched presentation options.`
|
||||||
GuiPort uint16
|
|
||||||
Namespace string
|
|
||||||
AllNamespaces bool
|
|
||||||
KubeConfigPath string
|
|
||||||
MizuImage string
|
|
||||||
MizuPodPort uint16
|
|
||||||
PlainTextFilterRegexes []string
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
var mizuTapOptions = &MizuTapOptions{}
|
|
||||||
|
|
||||||
var tapCmd = &cobra.Command{
|
var tapCmd = &cobra.Command{
|
||||||
Use: "tap [POD REGEX]",
|
Use: "tap [POD REGEX]",
|
||||||
Short: "Record ingoing traffic of a kubernetes pod",
|
Short: "Record ingoing traffic of a kubernetes pod",
|
||||||
Long: `Record the ingoing traffic of a kubernetes pod.
|
Long: `Record the ingoing traffic of a kubernetes pod.
|
||||||
Supported protocols are HTTP and gRPC.`,
|
Supported protocols are HTTP and gRPC.`,
|
||||||
RunE: func(cmd *cobra.Command, args []string) error {
|
RunE: func(cmd *cobra.Command, args []string) error {
|
||||||
if len(args) == 0 {
|
go mizu.ReportRun("tap", mizu.Config.Tap)
|
||||||
return errors.New("POD REGEX argument is required")
|
RunMizuTap()
|
||||||
|
return nil
|
||||||
|
},
|
||||||
|
PreRunE: func(cmd *cobra.Command, args []string) error {
|
||||||
|
if len(args) == 1 {
|
||||||
|
mizu.Config.Tap.PodRegexStr = args[0]
|
||||||
} else if len(args) > 1 {
|
} else if len(args) > 1 {
|
||||||
return errors.New("unexpected number of arguments")
|
return errors.New("unexpected number of arguments")
|
||||||
}
|
}
|
||||||
|
|
||||||
regex, err := regexp.Compile(args[0])
|
if err := mizu.Config.Validate(); err != nil {
|
||||||
if err != nil {
|
return errormessage.FormatError(err)
|
||||||
return errors.New(fmt.Sprintf("%s is not a valid regex %s", args[0], err))
|
}
|
||||||
|
|
||||||
|
if err := mizu.Config.Tap.Validate(); err != nil {
|
||||||
|
return errormessage.FormatError(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
mizu.Log.Infof("Mizu will store up to %s of traffic, old traffic will be cleared once the limit is reached.", mizu.Config.Tap.HumanMaxEntriesDBSize)
|
||||||
|
|
||||||
|
if mizu.Config.Tap.Analysis {
|
||||||
|
mizu.Log.Infof(analysisMessageToConfirm)
|
||||||
|
if !uiUtils.AskForConfirmation("Would you like to proceed [Y/n]: ") {
|
||||||
|
mizu.Log.Infof("You can always run mizu without analysis, aborting")
|
||||||
|
os.Exit(0)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
RunMizuTap(regex, mizuTapOptions)
|
|
||||||
return nil
|
return nil
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
@@ -47,11 +56,18 @@ var tapCmd = &cobra.Command{
|
|||||||
func init() {
|
func init() {
|
||||||
rootCmd.AddCommand(tapCmd)
|
rootCmd.AddCommand(tapCmd)
|
||||||
|
|
||||||
tapCmd.Flags().Uint16VarP(&mizuTapOptions.GuiPort, "gui-port", "p", 8899, "Provide a custom port for the web interface webserver")
|
defaultTapConfig := configStructs.TapConfig{}
|
||||||
tapCmd.Flags().StringVarP(&mizuTapOptions.Namespace, "namespace", "n", "", "Namespace selector")
|
defaults.Set(&defaultTapConfig)
|
||||||
tapCmd.Flags().BoolVarP(&mizuTapOptions.AllNamespaces, "all-namespaces", "A", false, "Tap all namespaces")
|
|
||||||
tapCmd.Flags().StringVarP(&mizuTapOptions.KubeConfigPath, "kube-config", "k", "", "Path to kube-config file")
|
tapCmd.Flags().Uint16P(configStructs.GuiPortTapName, "p", defaultTapConfig.GuiPort, "Provide a custom port for the web interface webserver")
|
||||||
tapCmd.Flags().StringVarP(&mizuTapOptions.MizuImage, "mizu-image", "", fmt.Sprintf("gcr.io/up9-docker-hub/mizu/%s:latest", mizu.Branch), "Custom image for mizu collector")
|
tapCmd.Flags().StringArrayP(configStructs.NamespacesTapName, "n", defaultTapConfig.Namespaces, "Namespaces selector")
|
||||||
tapCmd.Flags().Uint16VarP(&mizuTapOptions.MizuPodPort, "mizu-port", "", 8899, "Port which mizu cli will attempt to forward from the mizu collector pod")
|
tapCmd.Flags().Bool(configStructs.AnalysisTapName, defaultTapConfig.Analysis, "Uploads traffic to UP9 for further analysis (Beta)")
|
||||||
tapCmd.Flags().StringArrayVarP(&mizuTapOptions.PlainTextFilterRegexes, "regex-masking", "r", nil, "List of regex expressions that are used to filter matching values from text/plain http bodies")
|
tapCmd.Flags().BoolP(configStructs.AllNamespacesTapName, "A", defaultTapConfig.AllNamespaces, "Tap all namespaces")
|
||||||
|
tapCmd.Flags().StringArrayP(configStructs.PlainTextFilterRegexesTapName, "r", defaultTapConfig.PlainTextFilterRegexes, "List of regex expressions that are used to filter matching values from text/plain http bodies")
|
||||||
|
tapCmd.Flags().Bool(configStructs.HideHealthChecksTapName, defaultTapConfig.HideHealthChecks, "hides requests with kube-probe or prometheus user-agent headers")
|
||||||
|
tapCmd.Flags().Bool(configStructs.DisableRedactionTapName, defaultTapConfig.DisableRedaction, "Disables redaction of potentially sensitive request/response headers and body values")
|
||||||
|
tapCmd.Flags().String(configStructs.HumanMaxEntriesDBSizeTapName, defaultTapConfig.HumanMaxEntriesDBSize, "override the default max entries db size of 200mb")
|
||||||
|
tapCmd.Flags().String(configStructs.DirectionTapName, defaultTapConfig.Direction, "Record traffic that goes in this direction (relative to the tapped pod): in/any")
|
||||||
|
tapCmd.Flags().Bool(configStructs.DryRunTapName, defaultTapConfig.DryRun, "Preview of all pods matching the regex, without tapping them")
|
||||||
|
tapCmd.Flags().String(configStructs.EnforcePolicyFile, defaultTapConfig.EnforcePolicyFile, "Yaml file with policy rules")
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,169 +1,380 @@
|
|||||||
package cmd
|
package cmd
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"bytes"
|
||||||
"context"
|
"context"
|
||||||
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
"github.com/up9inc/mizu/shared"
|
"github.com/up9inc/mizu/cli/fsUtils"
|
||||||
|
"github.com/up9inc/mizu/cli/goUtils"
|
||||||
|
"github.com/up9inc/mizu/cli/mizu/configStructs"
|
||||||
|
"net/http"
|
||||||
|
"net/url"
|
||||||
"os"
|
"os"
|
||||||
"os/signal"
|
"os/signal"
|
||||||
|
"path"
|
||||||
"regexp"
|
"regexp"
|
||||||
|
"strings"
|
||||||
"syscall"
|
"syscall"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
core "k8s.io/api/core/v1"
|
"github.com/up9inc/mizu/cli/errormessage"
|
||||||
|
|
||||||
"github.com/up9inc/mizu/cli/debounce"
|
|
||||||
"github.com/up9inc/mizu/cli/kubernetes"
|
"github.com/up9inc/mizu/cli/kubernetes"
|
||||||
"github.com/up9inc/mizu/cli/mizu"
|
"github.com/up9inc/mizu/cli/mizu"
|
||||||
|
"github.com/up9inc/mizu/cli/uiUtils"
|
||||||
|
"github.com/up9inc/mizu/shared"
|
||||||
|
"github.com/up9inc/mizu/shared/debounce"
|
||||||
|
yaml "gopkg.in/yaml.v3"
|
||||||
|
core "k8s.io/api/core/v1"
|
||||||
|
"k8s.io/apimachinery/pkg/util/wait"
|
||||||
)
|
)
|
||||||
|
|
||||||
var mizuServiceAccountExists bool
|
|
||||||
var aggregatorService *core.Service
|
|
||||||
|
|
||||||
const (
|
const (
|
||||||
|
cleanupTimeout = time.Minute
|
||||||
updateTappersDelay = 5 * time.Second
|
updateTappersDelay = 5 * time.Second
|
||||||
)
|
)
|
||||||
|
|
||||||
var currentlyTappedPods []core.Pod
|
type tapState struct {
|
||||||
|
apiServerService *core.Service
|
||||||
|
currentlyTappedPods []core.Pod
|
||||||
|
mizuServiceAccountExists bool
|
||||||
|
doNotRemoveConfigMap bool
|
||||||
|
}
|
||||||
|
|
||||||
func RunMizuTap(podRegexQuery *regexp.Regexp, tappingOptions *MizuTapOptions) {
|
var state tapState
|
||||||
mizuApiFilteringOptions, err := getMizuApiFilteringOptions(tappingOptions)
|
|
||||||
|
func RunMizuTap() {
|
||||||
|
mizuApiFilteringOptions, err := getMizuApiFilteringOptions()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
mizu.Log.Errorf(uiUtils.Error, fmt.Sprintf("Error parsing regex-masking: %v", errormessage.FormatError(err)))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
var mizuValidationRules string
|
||||||
|
if mizu.Config.Tap.EnforcePolicyFile != "" {
|
||||||
|
mizuValidationRules, err = readValidationRules(mizu.Config.Tap.EnforcePolicyFile)
|
||||||
|
if err != nil {
|
||||||
|
mizu.Log.Errorf(uiUtils.Error, fmt.Sprintf("Error reading policy file: %v", errormessage.FormatError(err)))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
kubernetesProvider, err := kubernetes.NewProvider(mizu.Config.KubeConfigPath)
|
||||||
|
if err != nil {
|
||||||
|
mizu.Log.Error(err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
kubernetesProvider := kubernetes.NewProvider(tappingOptions.KubeConfigPath)
|
|
||||||
|
|
||||||
defer cleanUpMizuResources(kubernetesProvider)
|
|
||||||
ctx, cancel := context.WithCancel(context.Background())
|
ctx, cancel := context.WithCancel(context.Background())
|
||||||
defer cancel() // cancel will be called when this function exits
|
defer cancel() // cancel will be called when this function exits
|
||||||
|
|
||||||
if matchingPods, err := kubernetesProvider.GetAllPodsMatchingRegex(ctx, podRegexQuery); err != nil {
|
targetNamespaces := getNamespaces(kubernetesProvider)
|
||||||
return
|
|
||||||
|
var namespacesStr string
|
||||||
|
if targetNamespaces[0] != mizu.K8sAllNamespaces {
|
||||||
|
namespacesStr = fmt.Sprintf("namespaces \"%s\"", strings.Join(targetNamespaces, "\", \""))
|
||||||
} else {
|
} else {
|
||||||
currentlyTappedPods = matchingPods
|
namespacesStr = "all namespaces"
|
||||||
}
|
}
|
||||||
|
mizu.CheckNewerVersion()
|
||||||
|
mizu.Log.Infof("Tapping pods in %s", namespacesStr)
|
||||||
|
|
||||||
nodeToTappedPodIPMap, err := getNodeHostToTappedPodIpsMap(currentlyTappedPods)
|
if err, _ := updateCurrentlyTappedPods(kubernetesProvider, ctx, targetNamespaces); err != nil {
|
||||||
if err != nil {
|
mizu.Log.Errorf(uiUtils.Error, fmt.Sprintf("Error getting pods by regex: %v", errormessage.FormatError(err)))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := createMizuResources(ctx, kubernetesProvider, nodeToTappedPodIPMap, tappingOptions, mizuApiFilteringOptions); err != nil {
|
if len(state.currentlyTappedPods) == 0 {
|
||||||
|
var suggestionStr string
|
||||||
|
if targetNamespaces[0] != mizu.K8sAllNamespaces {
|
||||||
|
suggestionStr = ". Select a different namespace with -n or tap all namespaces with -A"
|
||||||
|
}
|
||||||
|
mizu.Log.Warningf(uiUtils.Warning, fmt.Sprintf("Did not find any pods matching the regex argument%s", suggestionStr))
|
||||||
|
}
|
||||||
|
|
||||||
|
if mizu.Config.Tap.DryRun {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
go portForwardApiPod(ctx, kubernetesProvider, cancel, tappingOptions) // TODO convert this to job for built in pod ttl or have the running app handle this
|
nodeToTappedPodIPMap := getNodeHostToTappedPodIpsMap(state.currentlyTappedPods)
|
||||||
go watchPodsForTapping(ctx, kubernetesProvider, cancel, podRegexQuery, tappingOptions)
|
|
||||||
go syncApiStatus(ctx, cancel, tappingOptions)
|
defer cleanUpMizuResources(kubernetesProvider)
|
||||||
|
if err := createMizuResources(ctx, kubernetesProvider, nodeToTappedPodIPMap, mizuApiFilteringOptions, mizuValidationRules); err != nil {
|
||||||
|
mizu.Log.Errorf(uiUtils.Error, fmt.Sprintf("Error creating resources: %v", errormessage.FormatError(err)))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
go goUtils.HandleExcWrapper(createProxyToApiServerPod, ctx, kubernetesProvider, cancel)
|
||||||
|
go goUtils.HandleExcWrapper(watchPodsForTapping, ctx, kubernetesProvider, targetNamespaces, cancel)
|
||||||
|
|
||||||
//block until exit signal or error
|
//block until exit signal or error
|
||||||
waitForFinish(ctx, cancel)
|
waitForFinish(ctx, cancel)
|
||||||
|
|
||||||
// TODO handle incoming traffic from tapper using a channel
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func createMizuResources(ctx context.Context, kubernetesProvider *kubernetes.Provider, nodeToTappedPodIPMap map[string][]string, tappingOptions *MizuTapOptions, mizuApiFilteringOptions *shared.TrafficFilteringOptions) error {
|
func readValidationRules(file string) (string, error) {
|
||||||
if err := createMizuAggregator(ctx, kubernetesProvider, tappingOptions, mizuApiFilteringOptions); err != nil {
|
rules, err := shared.DecodeEnforcePolicy(file)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
newContent, _ := yaml.Marshal(&rules)
|
||||||
|
return string(newContent), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func createMizuResources(ctx context.Context, kubernetesProvider *kubernetes.Provider, nodeToTappedPodIPMap map[string][]string, mizuApiFilteringOptions *shared.TrafficFilteringOptions, mizuValidationRules string) error {
|
||||||
|
if !mizu.Config.IsNsRestrictedMode() {
|
||||||
|
if err := createMizuNamespace(ctx, kubernetesProvider); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := createMizuApiServer(ctx, kubernetesProvider, mizuApiFilteringOptions); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := createMizuTappers(ctx, kubernetesProvider, nodeToTappedPodIPMap, tappingOptions); err != nil {
|
if err := updateMizuTappers(ctx, kubernetesProvider, nodeToTappedPodIPMap); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if err := createMizuConfigmap(ctx, kubernetesProvider, mizuValidationRules); err != nil {
|
||||||
|
mizu.Log.Warningf(uiUtils.Warning, fmt.Sprintf("Failed to create resources required for policy validation. Mizu will not validate policy rules. error: %v\n", errormessage.FormatError(err)))
|
||||||
|
state.doNotRemoveConfigMap = true
|
||||||
|
} else if mizuValidationRules == "" {
|
||||||
|
state.doNotRemoveConfigMap = true
|
||||||
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func createMizuAggregator(ctx context.Context, kubernetesProvider *kubernetes.Provider, tappingOptions *MizuTapOptions, mizuApiFilteringOptions *shared.TrafficFilteringOptions) error {
|
func createMizuConfigmap(ctx context.Context, kubernetesProvider *kubernetes.Provider, data string) error {
|
||||||
|
err := kubernetesProvider.CreateConfigMap(ctx, mizu.Config.MizuResourcesNamespace, mizu.ConfigMapName, data)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
func createMizuNamespace(ctx context.Context, kubernetesProvider *kubernetes.Provider) error {
|
||||||
|
_, err := kubernetesProvider.CreateNamespace(ctx, mizu.Config.MizuResourcesNamespace)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
func createMizuApiServer(ctx context.Context, kubernetesProvider *kubernetes.Provider, mizuApiFilteringOptions *shared.TrafficFilteringOptions) error {
|
||||||
var err error
|
var err error
|
||||||
|
|
||||||
mizuServiceAccountExists = createRBACIfNecessary(ctx, kubernetesProvider)
|
state.mizuServiceAccountExists, err = createRBACIfNecessary(ctx, kubernetesProvider)
|
||||||
_, err = kubernetesProvider.CreateMizuAggregatorPod(ctx, mizu.ResourcesNamespace, mizu.AggregatorPodName, tappingOptions.MizuImage, mizuServiceAccountExists, mizuApiFilteringOptions)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Printf("Error creating mizu collector pod: %v\n", err)
|
mizu.Log.Warningf(uiUtils.Warning, fmt.Sprintf("Failed to ensure the resources required for IP resolving. Mizu will not resolve target IPs to names. error: %v", errormessage.FormatError(err)))
|
||||||
return err
|
|
||||||
}
|
}
|
||||||
|
|
||||||
aggregatorService, err = kubernetesProvider.CreateService(ctx, mizu.ResourcesNamespace, mizu.AggregatorPodName, mizu.AggregatorPodName)
|
var serviceAccountName string
|
||||||
|
if state.mizuServiceAccountExists {
|
||||||
|
serviceAccountName = mizu.ServiceAccountName
|
||||||
|
} else {
|
||||||
|
serviceAccountName = ""
|
||||||
|
}
|
||||||
|
|
||||||
|
opts := &kubernetes.ApiServerOptions{
|
||||||
|
Namespace: mizu.Config.MizuResourcesNamespace,
|
||||||
|
PodName: mizu.ApiServerPodName,
|
||||||
|
PodImage: mizu.Config.AgentImage,
|
||||||
|
ServiceAccountName: serviceAccountName,
|
||||||
|
IsNamespaceRestricted: mizu.Config.IsNsRestrictedMode(),
|
||||||
|
MizuApiFilteringOptions: mizuApiFilteringOptions,
|
||||||
|
MaxEntriesDBSizeBytes: mizu.Config.Tap.MaxEntriesDBSizeBytes(),
|
||||||
|
}
|
||||||
|
_, err = kubernetesProvider.CreateMizuApiServerPod(ctx, opts)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Printf("Error creating mizu collector service: %v\n", err)
|
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
mizu.Log.Debugf("Successfully created API server pod: %s", mizu.ApiServerPodName)
|
||||||
|
|
||||||
|
state.apiServerService, err = kubernetesProvider.CreateService(ctx, mizu.Config.MizuResourcesNamespace, mizu.ApiServerPodName, mizu.ApiServerPodName)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
mizu.Log.Debugf("Successfully created service: %s", mizu.ApiServerPodName)
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func getMizuApiFilteringOptions(tappingOptions *MizuTapOptions) (*shared.TrafficFilteringOptions, error) {
|
func getMizuApiFilteringOptions() (*shared.TrafficFilteringOptions, error) {
|
||||||
if tappingOptions.PlainTextFilterRegexes == nil || len(tappingOptions.PlainTextFilterRegexes) == 0 {
|
var compiledRegexSlice []*shared.SerializableRegexp
|
||||||
return nil, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
compiledRegexSlice := make([]*shared.SerializableRegexp, 0)
|
if mizu.Config.Tap.PlainTextFilterRegexes != nil && len(mizu.Config.Tap.PlainTextFilterRegexes) > 0 {
|
||||||
for _, regexStr := range tappingOptions.PlainTextFilterRegexes {
|
compiledRegexSlice = make([]*shared.SerializableRegexp, 0)
|
||||||
compiledRegex, err := shared.CompileRegexToSerializableRegexp(regexStr)
|
for _, regexStr := range mizu.Config.Tap.PlainTextFilterRegexes {
|
||||||
if err != nil {
|
compiledRegex, err := shared.CompileRegexToSerializableRegexp(regexStr)
|
||||||
fmt.Printf("Regex %s is invalid: %v", regexStr, err)
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
|
}
|
||||||
|
compiledRegexSlice = append(compiledRegexSlice, compiledRegex)
|
||||||
}
|
}
|
||||||
compiledRegexSlice = append(compiledRegexSlice, compiledRegex)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return &shared.TrafficFilteringOptions{PlainTextMaskingRegexes: compiledRegexSlice}, nil
|
return &shared.TrafficFilteringOptions{PlainTextMaskingRegexes: compiledRegexSlice, HideHealthChecks: mizu.Config.Tap.HideHealthChecks, DisableRedaction: mizu.Config.Tap.DisableRedaction}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func createMizuTappers(ctx context.Context, kubernetesProvider *kubernetes.Provider, nodeToTappedPodIPMap map[string][]string, tappingOptions *MizuTapOptions) error {
|
func updateMizuTappers(ctx context.Context, kubernetesProvider *kubernetes.Provider, nodeToTappedPodIPMap map[string][]string) error {
|
||||||
if err := kubernetesProvider.ApplyMizuTapperDaemonSet(
|
if len(nodeToTappedPodIPMap) > 0 {
|
||||||
ctx,
|
var serviceAccountName string
|
||||||
mizu.ResourcesNamespace,
|
if state.mizuServiceAccountExists {
|
||||||
mizu.TapperDaemonSetName,
|
serviceAccountName = mizu.ServiceAccountName
|
||||||
tappingOptions.MizuImage,
|
} else {
|
||||||
mizu.TapperPodName,
|
serviceAccountName = ""
|
||||||
fmt.Sprintf("%s.%s.svc.cluster.local", aggregatorService.Name, aggregatorService.Namespace),
|
}
|
||||||
nodeToTappedPodIPMap,
|
|
||||||
mizuServiceAccountExists,
|
if err := kubernetesProvider.ApplyMizuTapperDaemonSet(
|
||||||
); err != nil {
|
ctx,
|
||||||
fmt.Printf("Error creating mizu tapper daemonset: %v\n", err)
|
mizu.Config.MizuResourcesNamespace,
|
||||||
return err
|
mizu.TapperDaemonSetName,
|
||||||
|
mizu.Config.AgentImage,
|
||||||
|
mizu.TapperPodName,
|
||||||
|
fmt.Sprintf("%s.%s.svc.cluster.local", state.apiServerService.Name, state.apiServerService.Namespace),
|
||||||
|
nodeToTappedPodIPMap,
|
||||||
|
serviceAccountName,
|
||||||
|
mizu.Config.Tap.TapOutgoing(),
|
||||||
|
); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
mizu.Log.Debugf("Successfully created %v tappers", len(nodeToTappedPodIPMap))
|
||||||
|
} else {
|
||||||
|
if err := kubernetesProvider.RemoveDaemonSet(ctx, mizu.Config.MizuResourcesNamespace, mizu.TapperDaemonSetName); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func cleanUpMizuResources(kubernetesProvider *kubernetes.Provider) {
|
func cleanUpMizuResources(kubernetesProvider *kubernetes.Provider) {
|
||||||
fmt.Printf("\nRemoving mizu resources\n")
|
|
||||||
|
|
||||||
removalCtx, _ := context.WithTimeout(context.Background(), 5*time.Second)
|
removalCtx, cancel := context.WithTimeout(context.Background(), cleanupTimeout)
|
||||||
if err := kubernetesProvider.RemovePod(removalCtx, mizu.ResourcesNamespace, mizu.AggregatorPodName); err != nil {
|
defer cancel()
|
||||||
fmt.Printf("Error removing Pod %s in namespace %s: %s (%v,%+v)\n", mizu.AggregatorPodName, mizu.ResourcesNamespace, err, err, err)
|
|
||||||
|
if mizu.Config.DumpLogs {
|
||||||
|
mizuDir := mizu.GetMizuFolderPath()
|
||||||
|
filePath = path.Join(mizuDir, fmt.Sprintf("mizu_logs_%s.zip", time.Now().Format("2006_01_02__15_04_05")))
|
||||||
|
if err := fsUtils.DumpLogs(kubernetesProvider, removalCtx, filePath); err != nil {
|
||||||
|
mizu.Log.Errorf("Failed dump logs %v", err)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if err := kubernetesProvider.RemoveService(removalCtx, mizu.ResourcesNamespace, mizu.AggregatorPodName); err != nil {
|
|
||||||
fmt.Printf("Error removing Service %s in namespace %s: %s (%v,%+v)\n", mizu.AggregatorPodName, mizu.ResourcesNamespace, err, err, err)
|
mizu.Log.Infof("\nRemoving mizu resources\n")
|
||||||
|
|
||||||
|
if !mizu.Config.IsNsRestrictedMode() {
|
||||||
|
if err := kubernetesProvider.RemoveNamespace(removalCtx, mizu.Config.MizuResourcesNamespace); err != nil {
|
||||||
|
mizu.Log.Errorf(uiUtils.Error, fmt.Sprintf("Error removing Namespace %s: %v", mizu.Config.MizuResourcesNamespace, errormessage.FormatError(err)))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if err := kubernetesProvider.RemovePod(removalCtx, mizu.Config.MizuResourcesNamespace, mizu.ApiServerPodName); err != nil {
|
||||||
|
mizu.Log.Errorf(uiUtils.Error, fmt.Sprintf("Error removing Pod %s in namespace %s: %v", mizu.ApiServerPodName, mizu.Config.MizuResourcesNamespace, errormessage.FormatError(err)))
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := kubernetesProvider.RemoveService(removalCtx, mizu.Config.MizuResourcesNamespace, mizu.ApiServerPodName); err != nil {
|
||||||
|
mizu.Log.Errorf(uiUtils.Error, fmt.Sprintf("Error removing Service %s in namespace %s: %v", mizu.ApiServerPodName, mizu.Config.MizuResourcesNamespace, errormessage.FormatError(err)))
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := kubernetesProvider.RemoveDaemonSet(removalCtx, mizu.Config.MizuResourcesNamespace, mizu.TapperDaemonSetName); err != nil {
|
||||||
|
mizu.Log.Errorf(uiUtils.Error, fmt.Sprintf("Error removing DaemonSet %s in namespace %s: %v", mizu.TapperDaemonSetName, mizu.Config.MizuResourcesNamespace, errormessage.FormatError(err)))
|
||||||
|
}
|
||||||
|
|
||||||
|
if !state.doNotRemoveConfigMap {
|
||||||
|
if err := kubernetesProvider.RemoveConfigMap(removalCtx, mizu.Config.MizuResourcesNamespace, mizu.ConfigMapName); err != nil {
|
||||||
|
mizu.Log.Errorf(uiUtils.Error, fmt.Sprintf("Error removing ConfigMap %s in namespace %s: %v", mizu.ConfigMapName, mizu.Config.MizuResourcesNamespace, errormessage.FormatError(err)))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
if err := kubernetesProvider.RemoveDaemonSet(removalCtx, mizu.ResourcesNamespace, mizu.TapperDaemonSetName); err != nil {
|
|
||||||
fmt.Printf("Error removing DaemonSet %s in namespace %s: %s (%v,%+v)\n", mizu.TapperDaemonSetName, mizu.ResourcesNamespace, err, err, err)
|
if state.mizuServiceAccountExists {
|
||||||
|
if !mizu.Config.IsNsRestrictedMode() {
|
||||||
|
if err := kubernetesProvider.RemoveNonNamespacedResources(removalCtx, mizu.ClusterRoleName, mizu.ClusterRoleBindingName); err != nil {
|
||||||
|
mizu.Log.Errorf(uiUtils.Error, fmt.Sprintf("Error removing non-namespaced resources: %v", errormessage.FormatError(err)))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if err := kubernetesProvider.RemoveServicAccount(removalCtx, mizu.Config.MizuResourcesNamespace, mizu.ServiceAccountName); err != nil {
|
||||||
|
mizu.Log.Errorf(uiUtils.Error, fmt.Sprintf("Error removing Service Account %s in namespace %s: %v", mizu.ServiceAccountName, mizu.Config.MizuResourcesNamespace, errormessage.FormatError(err)))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := kubernetesProvider.RemoveRole(removalCtx, mizu.Config.MizuResourcesNamespace, mizu.RoleName); err != nil {
|
||||||
|
mizu.Log.Errorf(uiUtils.Error, fmt.Sprintf("Error removing Role %s in namespace %s: %v", mizu.RoleName, mizu.Config.MizuResourcesNamespace, errormessage.FormatError(err)))
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := kubernetesProvider.RemoveRoleBinding(removalCtx, mizu.Config.MizuResourcesNamespace, mizu.RoleBindingName); err != nil {
|
||||||
|
mizu.Log.Errorf(uiUtils.Error, fmt.Sprintf("Error removing RoleBinding %s in namespace %s: %v", mizu.RoleBindingName, mizu.Config.MizuResourcesNamespace, errormessage.FormatError(err)))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if !mizu.Config.IsNsRestrictedMode() {
|
||||||
|
waitUntilNamespaceDeleted(removalCtx, cancel, kubernetesProvider)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func watchPodsForTapping(ctx context.Context, kubernetesProvider *kubernetes.Provider, cancel context.CancelFunc, podRegex *regexp.Regexp, tappingOptions *MizuTapOptions) {
|
func waitUntilNamespaceDeleted(ctx context.Context, cancel context.CancelFunc, kubernetesProvider *kubernetes.Provider) {
|
||||||
added, modified, removed, errorChan := kubernetes.FilteredWatch(ctx, kubernetesProvider.GetPodWatcher(ctx, getNamespace(tappingOptions, kubernetesProvider)), podRegex)
|
// Call cancel if a terminating signal was received. Allows user to skip the wait.
|
||||||
|
go func() {
|
||||||
|
waitForFinish(ctx, cancel)
|
||||||
|
}()
|
||||||
|
|
||||||
|
if err := kubernetesProvider.WaitUtilNamespaceDeleted(ctx, mizu.Config.MizuResourcesNamespace); err != nil {
|
||||||
|
switch {
|
||||||
|
case ctx.Err() == context.Canceled:
|
||||||
|
// Do nothing. User interrupted the wait.
|
||||||
|
case err == wait.ErrWaitTimeout:
|
||||||
|
mizu.Log.Errorf(uiUtils.Error, fmt.Sprintf("Timeout while removing Namespace %s", mizu.Config.MizuResourcesNamespace))
|
||||||
|
default:
|
||||||
|
mizu.Log.Errorf(uiUtils.Error, fmt.Sprintf("Error while waiting for Namespace %s to be deleted: %v", mizu.Config.MizuResourcesNamespace, errormessage.FormatError(err)))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func reportTappedPods() {
|
||||||
|
mizuProxiedUrl := kubernetes.GetMizuApiServerProxiedHostAndPath(mizu.Config.Tap.GuiPort)
|
||||||
|
tappedPodsUrl := fmt.Sprintf("http://%s/status/tappedPods", mizuProxiedUrl)
|
||||||
|
|
||||||
|
podInfos := make([]shared.PodInfo, 0)
|
||||||
|
for _, pod := range state.currentlyTappedPods {
|
||||||
|
podInfos = append(podInfos, shared.PodInfo{Name: pod.Name, Namespace: pod.Namespace})
|
||||||
|
}
|
||||||
|
tapStatus := shared.TapStatus{Pods: podInfos}
|
||||||
|
|
||||||
|
if jsonValue, err := json.Marshal(tapStatus); err != nil {
|
||||||
|
mizu.Log.Debugf("[ERROR] failed Marshal the tapped pods %v", err)
|
||||||
|
} else {
|
||||||
|
if response, err := http.Post(tappedPodsUrl, "application/json", bytes.NewBuffer(jsonValue)); err != nil {
|
||||||
|
mizu.Log.Debugf("[ERROR] failed sending to API server the tapped pods %v", err)
|
||||||
|
} else if response.StatusCode != 200 {
|
||||||
|
mizu.Log.Debugf("[ERROR] failed sending to API server the tapped pods, response status code %v", response.StatusCode)
|
||||||
|
} else {
|
||||||
|
mizu.Log.Debugf("Reported to server API about %d taped pods successfully", len(podInfos))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func watchPodsForTapping(ctx context.Context, kubernetesProvider *kubernetes.Provider, targetNamespaces []string, cancel context.CancelFunc) {
|
||||||
|
added, modified, removed, errorChan := kubernetes.FilteredWatch(ctx, kubernetesProvider, targetNamespaces, mizu.Config.Tap.PodRegex())
|
||||||
|
|
||||||
restartTappers := func() {
|
restartTappers := func() {
|
||||||
if matchingPods, err := kubernetesProvider.GetAllPodsMatchingRegex(ctx, podRegex); err != nil {
|
err, changeFound := updateCurrentlyTappedPods(kubernetesProvider, ctx, targetNamespaces)
|
||||||
fmt.Printf("Error getting pods by regex: %s (%v,%+v)\n", err, err, err)
|
|
||||||
cancel()
|
|
||||||
} else {
|
|
||||||
currentlyTappedPods = matchingPods
|
|
||||||
}
|
|
||||||
|
|
||||||
nodeToTappedPodIPMap, err := getNodeHostToTappedPodIpsMap(currentlyTappedPods)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Printf("Error building node to ips map: %s (%v,%+v)\n", err, err, err)
|
mizu.Log.Errorf(uiUtils.Error, fmt.Sprintf("Failed to update currently tapped pods: %v", err))
|
||||||
cancel()
|
cancel()
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := createMizuTappers(ctx, kubernetesProvider, nodeToTappedPodIPMap, tappingOptions); err != nil {
|
if !changeFound {
|
||||||
fmt.Printf("Error updating daemonset: %s (%v,%+v)\n", err, err, err)
|
mizu.Log.Debugf("Nothing changed update tappers not needed")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
reportTappedPods()
|
||||||
|
|
||||||
|
nodeToTappedPodIPMap := getNodeHostToTappedPodIpsMap(state.currentlyTappedPods)
|
||||||
|
if err != nil {
|
||||||
|
mizu.Log.Errorf(uiUtils.Error, fmt.Sprintf("Error building node to ips map: %v", errormessage.FormatError(err)))
|
||||||
|
cancel()
|
||||||
|
}
|
||||||
|
if err := updateMizuTappers(ctx, kubernetesProvider, nodeToTappedPodIPMap); err != nil {
|
||||||
|
mizu.Log.Errorf(uiUtils.Error, fmt.Sprintf("Error updating daemonset: %v", errormessage.FormatError(err)))
|
||||||
cancel()
|
cancel()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -171,98 +382,187 @@ func watchPodsForTapping(ctx context.Context, kubernetesProvider *kubernetes.Pro
|
|||||||
|
|
||||||
for {
|
for {
|
||||||
select {
|
select {
|
||||||
case newTarget := <-added:
|
case pod := <-added:
|
||||||
fmt.Printf("+%s\n", newTarget.Name)
|
mizu.Log.Debugf("Added matching pod %s, ns: %s", pod.Name, pod.Namespace)
|
||||||
|
|
||||||
case removedTarget := <-removed:
|
|
||||||
fmt.Printf("-%s\n", removedTarget.Name)
|
|
||||||
restartTappersDebouncer.SetOn()
|
restartTappersDebouncer.SetOn()
|
||||||
|
case pod := <-removed:
|
||||||
case modifiedTarget := <-modified:
|
mizu.Log.Debugf("Removed matching pod %s, ns: %s", pod.Name, pod.Namespace)
|
||||||
|
restartTappersDebouncer.SetOn()
|
||||||
|
case pod := <-modified:
|
||||||
|
mizu.Log.Debugf("Modified matching pod %s, ns: %s, phase: %s, ip: %s", pod.Name, pod.Namespace, pod.Status.Phase, pod.Status.PodIP)
|
||||||
// Act only if the modified pod has already obtained an IP address.
|
// Act only if the modified pod has already obtained an IP address.
|
||||||
// After filtering for IPs, on a normal pod restart this includes the following events:
|
// After filtering for IPs, on a normal pod restart this includes the following events:
|
||||||
// - Pod deletion
|
// - Pod deletion
|
||||||
// - Pod reaches start state
|
// - Pod reaches start state
|
||||||
// - Pod reaches ready state
|
// - Pod reaches ready state
|
||||||
// Ready/unready transitions might also trigger this event.
|
// Ready/unready transitions might also trigger this event.
|
||||||
if modifiedTarget.Status.PodIP != "" {
|
if pod.Status.PodIP != "" {
|
||||||
restartTappersDebouncer.SetOn()
|
restartTappersDebouncer.SetOn()
|
||||||
}
|
}
|
||||||
|
|
||||||
case <-errorChan:
|
case err := <-errorChan:
|
||||||
|
mizu.Log.Debugf("Watching pods loop, got error %v, stopping `restart tappers debouncer`", err)
|
||||||
|
restartTappersDebouncer.Cancel()
|
||||||
// TODO: Does this also perform cleanup?
|
// TODO: Does this also perform cleanup?
|
||||||
cancel()
|
cancel()
|
||||||
|
|
||||||
case <-ctx.Done():
|
case <-ctx.Done():
|
||||||
|
mizu.Log.Debugf("Watching pods loop, context done, stopping `restart tappers debouncer`")
|
||||||
|
restartTappersDebouncer.Cancel()
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func portForwardApiPod(ctx context.Context, kubernetesProvider *kubernetes.Provider, cancel context.CancelFunc, tappingOptions *MizuTapOptions) {
|
func updateCurrentlyTappedPods(kubernetesProvider *kubernetes.Provider, ctx context.Context, targetNamespaces []string) (error, bool) {
|
||||||
podExactRegex := regexp.MustCompile(fmt.Sprintf("^%s$", mizu.AggregatorPodName))
|
changeFound := false
|
||||||
added, modified, removed, errorChan := kubernetes.FilteredWatch(ctx, kubernetesProvider.GetPodWatcher(ctx, mizu.ResourcesNamespace), podExactRegex)
|
if matchingPods, err := kubernetesProvider.ListAllRunningPodsMatchingRegex(ctx, mizu.Config.Tap.PodRegex(), targetNamespaces); err != nil {
|
||||||
|
return err, false
|
||||||
|
} else {
|
||||||
|
podsToTap := excludeMizuPods(matchingPods)
|
||||||
|
addedPods, removedPods := getPodArrayDiff(state.currentlyTappedPods, podsToTap)
|
||||||
|
for _, addedPod := range addedPods {
|
||||||
|
changeFound = true
|
||||||
|
mizu.Log.Infof(uiUtils.Green, fmt.Sprintf("+%s", addedPod.Name))
|
||||||
|
}
|
||||||
|
for _, removedPod := range removedPods {
|
||||||
|
changeFound = true
|
||||||
|
mizu.Log.Infof(uiUtils.Red, fmt.Sprintf("-%s", removedPod.Name))
|
||||||
|
}
|
||||||
|
state.currentlyTappedPods = podsToTap
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil, changeFound
|
||||||
|
}
|
||||||
|
|
||||||
|
func excludeMizuPods(pods []core.Pod) []core.Pod {
|
||||||
|
mizuPrefixRegex := regexp.MustCompile("^" + mizu.MizuResourcesPrefix)
|
||||||
|
|
||||||
|
nonMizuPods := make([]core.Pod, 0)
|
||||||
|
for _, pod := range pods {
|
||||||
|
if !mizuPrefixRegex.MatchString(pod.Name) {
|
||||||
|
nonMizuPods = append(nonMizuPods, pod)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nonMizuPods
|
||||||
|
}
|
||||||
|
|
||||||
|
func getPodArrayDiff(oldPods []core.Pod, newPods []core.Pod) (added []core.Pod, removed []core.Pod) {
|
||||||
|
added = getMissingPods(newPods, oldPods)
|
||||||
|
removed = getMissingPods(oldPods, newPods)
|
||||||
|
|
||||||
|
return added, removed
|
||||||
|
}
|
||||||
|
|
||||||
|
//returns pods present in pods1 array and missing in pods2 array
|
||||||
|
func getMissingPods(pods1 []core.Pod, pods2 []core.Pod) []core.Pod {
|
||||||
|
missingPods := make([]core.Pod, 0)
|
||||||
|
for _, pod1 := range pods1 {
|
||||||
|
var found = false
|
||||||
|
for _, pod2 := range pods2 {
|
||||||
|
if pod1.UID == pod2.UID {
|
||||||
|
found = true
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if !found {
|
||||||
|
missingPods = append(missingPods, pod1)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return missingPods
|
||||||
|
}
|
||||||
|
|
||||||
|
func createProxyToApiServerPod(ctx context.Context, kubernetesProvider *kubernetes.Provider, cancel context.CancelFunc) {
|
||||||
|
podExactRegex := regexp.MustCompile(fmt.Sprintf("^%s$", mizu.ApiServerPodName))
|
||||||
|
added, modified, removed, errorChan := kubernetes.FilteredWatch(ctx, kubernetesProvider, []string{mizu.Config.MizuResourcesNamespace}, podExactRegex)
|
||||||
isPodReady := false
|
isPodReady := false
|
||||||
var portForward *kubernetes.PortForward
|
timeAfter := time.After(25 * time.Second)
|
||||||
for {
|
for {
|
||||||
select {
|
select {
|
||||||
|
case <-ctx.Done():
|
||||||
|
mizu.Log.Debugf("Watching API Server pod loop, ctx done")
|
||||||
|
return
|
||||||
case <-added:
|
case <-added:
|
||||||
|
mizu.Log.Debugf("Watching API Server pod loop, added")
|
||||||
continue
|
continue
|
||||||
case <-removed:
|
case <-removed:
|
||||||
fmt.Printf("%s removed\n", mizu.AggregatorPodName)
|
mizu.Log.Infof("%s removed", mizu.ApiServerPodName)
|
||||||
cancel()
|
cancel()
|
||||||
return
|
return
|
||||||
case modifiedPod := <-modified:
|
case modifiedPod := <-modified:
|
||||||
if modifiedPod.Status.Phase == "Running" && !isPodReady {
|
if modifiedPod == nil {
|
||||||
isPodReady = true
|
mizu.Log.Debugf("Watching API Server pod loop, modifiedPod with nil")
|
||||||
var err error
|
continue
|
||||||
portForward, err = kubernetes.NewPortForward(kubernetesProvider, mizu.ResourcesNamespace, mizu.AggregatorPodName, tappingOptions.GuiPort, tappingOptions.MizuPodPort, cancel)
|
|
||||||
fmt.Printf("Web interface is now available at http://localhost:%d\n", tappingOptions.GuiPort)
|
|
||||||
if err != nil {
|
|
||||||
fmt.Printf("error forwarding port to pod %s\n", err)
|
|
||||||
cancel()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
mizu.Log.Debugf("Watching API Server pod loop, modified: %v", modifiedPod.Status.Phase)
|
||||||
case <-time.After(25 * time.Second):
|
if modifiedPod.Status.Phase == core.PodRunning && !isPodReady {
|
||||||
|
isPodReady = true
|
||||||
|
go startProxyReportErrorIfAny(kubernetesProvider, cancel)
|
||||||
|
mizu.Log.Infof("Mizu is available at http://%s\n", kubernetes.GetMizuApiServerProxiedHostAndPath(mizu.Config.Tap.GuiPort))
|
||||||
|
time.Sleep(time.Second * 5) // Waiting to be sure the proxy is ready
|
||||||
|
requestForAnalysis()
|
||||||
|
reportTappedPods()
|
||||||
|
}
|
||||||
|
case <-timeAfter:
|
||||||
if !isPodReady {
|
if !isPodReady {
|
||||||
fmt.Printf("error: %s pod was not ready in time", mizu.AggregatorPodName)
|
mizu.Log.Errorf(uiUtils.Error, "Mizu API server was not ready in time")
|
||||||
cancel()
|
cancel()
|
||||||
}
|
}
|
||||||
|
|
||||||
case <-errorChan:
|
case <-errorChan:
|
||||||
|
mizu.Log.Debugf("[ERROR] Agent creation, watching %v namespace", mizu.Config.MizuResourcesNamespace)
|
||||||
cancel()
|
cancel()
|
||||||
|
|
||||||
case <-ctx.Done():
|
|
||||||
if portForward != nil {
|
|
||||||
portForward.Stop()
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func createRBACIfNecessary(ctx context.Context, kubernetesProvider *kubernetes.Provider) bool {
|
func startProxyReportErrorIfAny(kubernetesProvider *kubernetes.Provider, cancel context.CancelFunc) {
|
||||||
mizuRBACExists, err := kubernetesProvider.DoesMizuRBACExist(ctx, mizu.ResourcesNamespace)
|
err := kubernetes.StartProxy(kubernetesProvider, mizu.Config.Tap.GuiPort, mizu.Config.MizuResourcesNamespace, mizu.ApiServerPodName)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Printf("warning: could not ensure mizu rbac resources exist %v\n", err)
|
mizu.Log.Errorf(uiUtils.Error, fmt.Sprintf("Error occured while running k8s proxy %v\n"+
|
||||||
return false
|
"Try setting different port by using --%s", errormessage.FormatError(err), configStructs.GuiPortTapName))
|
||||||
|
cancel()
|
||||||
}
|
}
|
||||||
if !mizuRBACExists {
|
|
||||||
var versionString = mizu.Version
|
|
||||||
if mizu.GitCommitHash != "" {
|
|
||||||
versionString += "-" + mizu.GitCommitHash
|
|
||||||
}
|
|
||||||
err := kubernetesProvider.CreateMizuRBAC(ctx, mizu.ResourcesNamespace, versionString)
|
|
||||||
if err != nil {
|
|
||||||
fmt.Printf("warning: could not create mizu rbac resources %v\n", err)
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return true
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func getNodeHostToTappedPodIpsMap(tappedPods []core.Pod) (map[string][]string, error) {
|
func requestForAnalysis() {
|
||||||
|
if !mizu.Config.Tap.Analysis {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
mizuProxiedUrl := kubernetes.GetMizuApiServerProxiedHostAndPath(mizu.Config.Tap.GuiPort)
|
||||||
|
urlPath := fmt.Sprintf("http://%s/api/uploadEntries?dest=%s&interval=%v", mizuProxiedUrl, url.QueryEscape(mizu.Config.Tap.AnalysisDestination), mizu.Config.Tap.SleepIntervalSec)
|
||||||
|
u, parseErr := url.ParseRequestURI(urlPath)
|
||||||
|
if parseErr != nil {
|
||||||
|
mizu.Log.Fatal("Failed parsing the URL (consider changing the analysis dest URL), err: %v", parseErr)
|
||||||
|
}
|
||||||
|
|
||||||
|
mizu.Log.Debugf("Sending get request to %v", u.String())
|
||||||
|
if response, requestErr := http.Get(u.String()); requestErr != nil {
|
||||||
|
mizu.Log.Errorf("Failed to notify agent for analysis, err: %v", requestErr)
|
||||||
|
} else if response.StatusCode != 200 {
|
||||||
|
mizu.Log.Errorf("Failed to notify agent for analysis, status code: %v", response.StatusCode)
|
||||||
|
} else {
|
||||||
|
mizu.Log.Infof(uiUtils.Purple, "Traffic is uploading to UP9 for further analysis")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func createRBACIfNecessary(ctx context.Context, kubernetesProvider *kubernetes.Provider) (bool, error) {
|
||||||
|
if !mizu.Config.IsNsRestrictedMode() {
|
||||||
|
err := kubernetesProvider.CreateMizuRBAC(ctx, mizu.Config.MizuResourcesNamespace, mizu.ServiceAccountName, mizu.ClusterRoleName, mizu.ClusterRoleBindingName, mizu.RBACVersion)
|
||||||
|
if err != nil {
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
err := kubernetesProvider.CreateMizuRBACNamespaceRestricted(ctx, mizu.Config.MizuResourcesNamespace, mizu.ServiceAccountName, mizu.RoleName, mizu.RoleBindingName, mizu.RBACVersion)
|
||||||
|
if err != nil {
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func getNodeHostToTappedPodIpsMap(tappedPods []core.Pod) map[string][]string {
|
||||||
nodeToTappedPodIPMap := make(map[string][]string, 0)
|
nodeToTappedPodIPMap := make(map[string][]string, 0)
|
||||||
for _, pod := range tappedPods {
|
for _, pod := range tappedPods {
|
||||||
existingList := nodeToTappedPodIPMap[pod.Spec.NodeName]
|
existingList := nodeToTappedPodIPMap[pod.Spec.NodeName]
|
||||||
@@ -272,7 +572,7 @@ func getNodeHostToTappedPodIpsMap(tappedPods []core.Pod) (map[string][]string, e
|
|||||||
nodeToTappedPodIPMap[pod.Spec.NodeName] = append(nodeToTappedPodIPMap[pod.Spec.NodeName], pod.Status.PodIP)
|
nodeToTappedPodIPMap[pod.Spec.NodeName] = append(nodeToTappedPodIPMap[pod.Spec.NodeName], pod.Status.PodIP)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return nodeToTappedPodIPMap, nil
|
return nodeToTappedPodIPMap
|
||||||
}
|
}
|
||||||
|
|
||||||
func waitForFinish(ctx context.Context, cancel context.CancelFunc) {
|
func waitForFinish(ctx context.Context, cancel context.CancelFunc) {
|
||||||
@@ -288,34 +588,12 @@ func waitForFinish(ctx context.Context, cancel context.CancelFunc) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func syncApiStatus(ctx context.Context, cancel context.CancelFunc, tappingOptions *MizuTapOptions) {
|
func getNamespaces(kubernetesProvider *kubernetes.Provider) []string {
|
||||||
controlSocket, err := mizu.CreateControlSocket(fmt.Sprintf("ws://localhost:%d/ws", tappingOptions.GuiPort))
|
if mizu.Config.Tap.AllNamespaces {
|
||||||
if err != nil {
|
return []string{mizu.K8sAllNamespaces}
|
||||||
fmt.Printf("error establishing control socket connection %s\n", err)
|
} else if len(mizu.Config.Tap.Namespaces) > 0 {
|
||||||
cancel()
|
return mizu.Config.Tap.Namespaces
|
||||||
}
|
|
||||||
|
|
||||||
for {
|
|
||||||
select {
|
|
||||||
case <-ctx.Done():
|
|
||||||
return
|
|
||||||
default:
|
|
||||||
err = controlSocket.SendNewTappedPodsListMessage(currentlyTappedPods)
|
|
||||||
if err != nil {
|
|
||||||
fmt.Printf("error Sending message via control socket %s\n", err)
|
|
||||||
}
|
|
||||||
time.Sleep(10 * time.Second)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
func getNamespace(tappingOptions *MizuTapOptions, kubernetesProvider *kubernetes.Provider) string {
|
|
||||||
if tappingOptions.AllNamespaces {
|
|
||||||
return mizu.K8sAllNamespaces
|
|
||||||
} else if len(tappingOptions.Namespace) > 0 {
|
|
||||||
return tappingOptions.Namespace
|
|
||||||
} else {
|
} else {
|
||||||
return kubernetesProvider.CurrentNamespace()
|
return []string{kubernetesProvider.CurrentNamespace()}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,21 +1,38 @@
|
|||||||
package cmd
|
package cmd
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"strconv"
|
||||||
"github.com/up9inc/mizu/cli/mizu"
|
"time"
|
||||||
|
|
||||||
|
"github.com/creasty/defaults"
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
|
"github.com/up9inc/mizu/cli/mizu"
|
||||||
|
"github.com/up9inc/mizu/cli/mizu/configStructs"
|
||||||
)
|
)
|
||||||
|
|
||||||
var versionCmd = &cobra.Command{
|
var versionCmd = &cobra.Command{
|
||||||
Use: "version",
|
Use: "version",
|
||||||
Short: "Print version info",
|
Short: "Print version info",
|
||||||
RunE: func(cmd *cobra.Command, args []string) error {
|
RunE: func(cmd *cobra.Command, args []string) error {
|
||||||
fmt.Printf("%s (%s) %s\n", mizu.Version, mizu.Branch, mizu.GitCommitHash)
|
go mizu.ReportRun("version", mizu.Config.Version)
|
||||||
|
if mizu.Config.Version.DebugInfo {
|
||||||
|
timeStampInt, _ := strconv.ParseInt(mizu.BuildTimestamp, 10, 0)
|
||||||
|
mizu.Log.Infof("Version: %s \nBranch: %s (%s)", mizu.SemVer, mizu.Branch, mizu.GitCommitHash)
|
||||||
|
mizu.Log.Infof("Build Time: %s (%s)", mizu.BuildTimestamp, time.Unix(timeStampInt, 0))
|
||||||
|
|
||||||
|
} else {
|
||||||
|
mizu.Log.Infof("Version: %s (%s)", mizu.SemVer, mizu.Branch)
|
||||||
|
}
|
||||||
return nil
|
return nil
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
rootCmd.AddCommand(versionCmd)
|
rootCmd.AddCommand(versionCmd)
|
||||||
|
|
||||||
|
defaultVersionConfig := configStructs.VersionConfig{}
|
||||||
|
defaults.Set(&defaultVersionConfig)
|
||||||
|
|
||||||
|
versionCmd.Flags().BoolP(configStructs.DebugInfoVersionName, "d", defaultVersionConfig.DebugInfo, "Provide all information about version")
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,13 +1,17 @@
|
|||||||
package cmd
|
package cmd
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"github.com/creasty/defaults"
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
|
"github.com/up9inc/mizu/cli/mizu"
|
||||||
|
"github.com/up9inc/mizu/cli/mizu/configStructs"
|
||||||
)
|
)
|
||||||
|
|
||||||
var viewCmd = &cobra.Command{
|
var viewCmd = &cobra.Command{
|
||||||
Use: "view",
|
Use: "view",
|
||||||
Short: "Open GUI in browser",
|
Short: "Open GUI in browser",
|
||||||
RunE: func(cmd *cobra.Command, args []string) error {
|
RunE: func(cmd *cobra.Command, args []string) error {
|
||||||
|
go mizu.ReportRun("view", mizu.Config.View)
|
||||||
runMizuView()
|
runMizuView()
|
||||||
return nil
|
return nil
|
||||||
},
|
},
|
||||||
@@ -15,4 +19,10 @@ var viewCmd = &cobra.Command{
|
|||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
rootCmd.AddCommand(viewCmd)
|
rootCmd.AddCommand(viewCmd)
|
||||||
|
|
||||||
|
defaultViewConfig := configStructs.ViewConfig{}
|
||||||
|
defaults.Set(&defaultViewConfig)
|
||||||
|
|
||||||
|
viewCmd.Flags().Uint16P(configStructs.GuiPortViewName, "p", defaultViewConfig.GuiPort, "Provide a custom port for the web interface webserver")
|
||||||
|
viewCmd.Flags().StringP(configStructs.KubeConfigPathViewName, "k", defaultViewConfig.KubeConfigPath, "Path to kube-config file")
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,25 +9,46 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
func runMizuView() {
|
func runMizuView() {
|
||||||
kubernetesProvider := kubernetes.NewProvider("")
|
kubernetesProvider, err := kubernetes.NewProvider(mizu.Config.View.KubeConfigPath)
|
||||||
|
if err != nil {
|
||||||
|
mizu.Log.Error(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
ctx, cancel := context.WithCancel(context.Background())
|
ctx, cancel := context.WithCancel(context.Background())
|
||||||
defer cancel()
|
defer cancel()
|
||||||
|
|
||||||
exists, err := kubernetesProvider.DoesServicesExist(ctx, mizu.ResourcesNamespace, mizu.AggregatorPodName)
|
exists, err := kubernetesProvider.DoesServicesExist(ctx, mizu.Config.MizuResourcesNamespace, mizu.ApiServerPodName)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
panic(err)
|
mizu.Log.Errorf("Failed to found mizu service %v", err)
|
||||||
|
cancel()
|
||||||
|
return
|
||||||
}
|
}
|
||||||
if !exists {
|
if !exists {
|
||||||
fmt.Printf("The %s service not found\n", mizu.AggregatorPodName)
|
mizu.Log.Infof("%s service not found, you should run `mizu tap` command first", mizu.ApiServerPodName)
|
||||||
|
cancel()
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
_, err = http.Get("http://localhost:8899/")
|
mizuProxiedUrl := kubernetes.GetMizuApiServerProxiedHostAndPath(mizu.Config.View.GuiPort)
|
||||||
|
_, err = http.Get(fmt.Sprintf("http://%s/", mizuProxiedUrl))
|
||||||
if err == nil {
|
if err == nil {
|
||||||
fmt.Printf("Found a running service %s and open port 8899\n", mizu.AggregatorPodName)
|
mizu.Log.Infof("Found a running service %s and open port %d", mizu.ApiServerPodName, mizu.Config.View.GuiPort)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
fmt.Printf("Found service %s, creating port forwarding to 8899\n", mizu.AggregatorPodName)
|
mizu.Log.Debugf("Found service %s, creating k8s proxy", mizu.ApiServerPodName)
|
||||||
portForwardApiPod(ctx, kubernetesProvider, cancel, &MizuTapOptions{GuiPort: 8899, MizuPodPort: 8899})
|
|
||||||
|
go startProxyReportErrorIfAny(kubernetesProvider, cancel)
|
||||||
|
|
||||||
|
mizu.Log.Infof("Mizu is available at http://%s\n", kubernetes.GetMizuApiServerProxiedHostAndPath(mizu.Config.View.GuiPort))
|
||||||
|
if isCompatible, err := mizu.CheckVersionCompatibility(mizu.Config.View.GuiPort); err != nil {
|
||||||
|
mizu.Log.Errorf("Failed to check versions compatibility %v", err)
|
||||||
|
cancel()
|
||||||
|
return
|
||||||
|
} else if !isCompatible {
|
||||||
|
cancel()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
waitForFinish(ctx, cancel)
|
||||||
}
|
}
|
||||||
|
|||||||
38
cli/errormessage/errormessage.go
Normal file
38
cli/errormessage/errormessage.go
Normal file
@@ -0,0 +1,38 @@
|
|||||||
|
package errormessage
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"github.com/up9inc/mizu/cli/mizu"
|
||||||
|
|
||||||
|
regexpsyntax "regexp/syntax"
|
||||||
|
|
||||||
|
k8serrors "k8s.io/apimachinery/pkg/api/errors"
|
||||||
|
)
|
||||||
|
|
||||||
|
// formatError wraps error with a detailed message that is meant for the user.
|
||||||
|
// While the errors are meant to be displayed, they are not meant to be exported as classes outsite of CLI.
|
||||||
|
func FormatError(err error) error {
|
||||||
|
var errorNew error
|
||||||
|
if k8serrors.IsForbidden(err) {
|
||||||
|
errorNew = fmt.Errorf("insufficient permissions: %w. "+
|
||||||
|
"supply the required permission or control Mizu's access to namespaces by setting %s "+
|
||||||
|
"in the config file or setting the tapped namespace with --%s %s=<NAMEPSACE>",
|
||||||
|
err,
|
||||||
|
mizu.MizuResourcesNamespaceConfigName,
|
||||||
|
mizu.SetCommandName,
|
||||||
|
mizu.MizuResourcesNamespaceConfigName)
|
||||||
|
} else if syntaxError, isSyntaxError := asRegexSyntaxError(err); isSyntaxError {
|
||||||
|
errorNew = fmt.Errorf("regex %s is invalid: %w", syntaxError.Expr, err)
|
||||||
|
} else {
|
||||||
|
errorNew = err
|
||||||
|
}
|
||||||
|
|
||||||
|
return errorNew
|
||||||
|
}
|
||||||
|
|
||||||
|
func asRegexSyntaxError(err error) (*regexpsyntax.Error, bool) {
|
||||||
|
var syntaxError *regexpsyntax.Error
|
||||||
|
return syntaxError, errors.As(err, &syntaxError)
|
||||||
|
}
|
||||||
26
cli/fsUtils/dirUtils.go
Normal file
26
cli/fsUtils/dirUtils.go
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
package fsUtils
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
)
|
||||||
|
|
||||||
|
func EnsureDir(dirName string) error {
|
||||||
|
err := os.Mkdir(dirName, 0700)
|
||||||
|
if err == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
if os.IsExist(err) {
|
||||||
|
// check that the existing path is a directory
|
||||||
|
info, err := os.Stat(dirName)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if !info.IsDir() {
|
||||||
|
return errors.New(fmt.Sprintf("path exists but is not a directory: %s", dirName))
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return err
|
||||||
|
}
|
||||||
58
cli/fsUtils/mizuLogsUtils.go
Normal file
58
cli/fsUtils/mizuLogsUtils.go
Normal file
@@ -0,0 +1,58 @@
|
|||||||
|
package fsUtils
|
||||||
|
|
||||||
|
import (
|
||||||
|
"archive/zip"
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
"github.com/up9inc/mizu/cli/kubernetes"
|
||||||
|
"github.com/up9inc/mizu/cli/mizu"
|
||||||
|
"os"
|
||||||
|
"regexp"
|
||||||
|
)
|
||||||
|
|
||||||
|
func DumpLogs(provider *kubernetes.Provider, ctx context.Context, filePath string) error {
|
||||||
|
podExactRegex := regexp.MustCompile("^" + mizu.MizuResourcesPrefix)
|
||||||
|
pods, err := provider.ListAllPodsMatchingRegex(ctx, podExactRegex, []string{mizu.Config.MizuResourcesNamespace})
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(pods) == 0 {
|
||||||
|
return fmt.Errorf("no mizu pods found in namespace %s", mizu.Config.MizuResourcesNamespace)
|
||||||
|
}
|
||||||
|
|
||||||
|
newZipFile, err := os.Create(filePath)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer newZipFile.Close()
|
||||||
|
zipWriter := zip.NewWriter(newZipFile)
|
||||||
|
defer zipWriter.Close()
|
||||||
|
|
||||||
|
for _, pod := range pods {
|
||||||
|
logs, err := provider.GetPodLogs(pod.Namespace, pod.Name, ctx)
|
||||||
|
if err != nil {
|
||||||
|
mizu.Log.Errorf("Failed to get logs, %v", err)
|
||||||
|
continue
|
||||||
|
} else {
|
||||||
|
mizu.Log.Debugf("Successfully read log length %d for pod: %s.%s", len(logs), pod.Namespace, pod.Name)
|
||||||
|
}
|
||||||
|
if err := AddStrToZip(zipWriter, logs, fmt.Sprintf("%s.%s.log", pod.Namespace, pod.Name)); err != nil {
|
||||||
|
mizu.Log.Errorf("Failed write logs, %v", err)
|
||||||
|
} else {
|
||||||
|
mizu.Log.Infof("Successfully added log length %d from pod: %s.%s", len(logs), pod.Namespace, pod.Name)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if err := AddFileToZip(zipWriter, mizu.GetConfigFilePath()); err != nil {
|
||||||
|
mizu.Log.Debugf("Failed write file, %v", err)
|
||||||
|
} else {
|
||||||
|
mizu.Log.Infof("Successfully added file %s", mizu.GetConfigFilePath())
|
||||||
|
}
|
||||||
|
if err := AddFileToZip(zipWriter, mizu.GetLogFilePath()); err != nil {
|
||||||
|
mizu.Log.Debugf("Failed write file, %v", err)
|
||||||
|
} else {
|
||||||
|
mizu.Log.Infof("Successfully added file %s", mizu.GetLogFilePath())
|
||||||
|
}
|
||||||
|
mizu.Log.Infof("You can find the zip with all logs in %s\n", filePath)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
55
cli/fsUtils/zipUtils.go
Normal file
55
cli/fsUtils/zipUtils.go
Normal file
@@ -0,0 +1,55 @@
|
|||||||
|
package fsUtils
|
||||||
|
|
||||||
|
import (
|
||||||
|
"archive/zip"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
)
|
||||||
|
|
||||||
|
func AddFileToZip(zipWriter *zip.Writer, filename string) error {
|
||||||
|
|
||||||
|
fileToZip, err := os.Open(filename)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to open file %s, %w", filename, err)
|
||||||
|
}
|
||||||
|
defer fileToZip.Close()
|
||||||
|
|
||||||
|
// Get the file information
|
||||||
|
info, err := fileToZip.Stat()
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to get file information %s, %w", filename, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
header, err := zip.FileInfoHeader(info)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Using FileInfoHeader() above only uses the basename of the file. If we want
|
||||||
|
// to preserve the folder structure we can overwrite this with the full path.
|
||||||
|
header.Name = filepath.Base(filename)
|
||||||
|
|
||||||
|
// Change to deflate to gain better compression
|
||||||
|
// see http://golang.org/pkg/archive/zip/#pkg-constants
|
||||||
|
header.Method = zip.Deflate
|
||||||
|
|
||||||
|
writer, err := zipWriter.CreateHeader(header)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to create header in zip for %s, %w", filename, err)
|
||||||
|
}
|
||||||
|
_, err = io.Copy(writer, fileToZip)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
func AddStrToZip(writer *zip.Writer, logs string, fileName string) error {
|
||||||
|
if zipFile, err := writer.Create(fileName); err != nil {
|
||||||
|
return fmt.Errorf("couldn't create a log file inside zip for %s, %w", fileName, err)
|
||||||
|
} else {
|
||||||
|
if _, err = zipFile.Write([]byte(logs)); err != nil {
|
||||||
|
return fmt.Errorf("couldn't write logs to zip file: %s, %w", fileName, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
12
cli/go.mod
12
cli/go.mod
@@ -3,12 +3,18 @@ module github.com/up9inc/mizu/cli
|
|||||||
go 1.16
|
go 1.16
|
||||||
|
|
||||||
require (
|
require (
|
||||||
|
github.com/creasty/defaults v1.5.1
|
||||||
|
github.com/google/go-github/v37 v37.0.0
|
||||||
github.com/gorilla/websocket v1.4.2
|
github.com/gorilla/websocket v1.4.2
|
||||||
|
github.com/op/go-logging v0.0.0-20160315200505-970db520ece7
|
||||||
github.com/spf13/cobra v1.1.3
|
github.com/spf13/cobra v1.1.3
|
||||||
|
github.com/spf13/pflag v1.0.5
|
||||||
github.com/up9inc/mizu/shared v0.0.0
|
github.com/up9inc/mizu/shared v0.0.0
|
||||||
k8s.io/api v0.21.0
|
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b
|
||||||
k8s.io/apimachinery v0.21.0
|
k8s.io/api v0.21.2
|
||||||
k8s.io/client-go v0.21.0
|
k8s.io/apimachinery v0.21.2
|
||||||
|
k8s.io/client-go v0.21.2
|
||||||
|
k8s.io/kubectl v0.21.2
|
||||||
)
|
)
|
||||||
|
|
||||||
replace github.com/up9inc/mizu/shared v0.0.0 => ../shared
|
replace github.com/up9inc/mizu/shared v0.0.0 => ../shared
|
||||||
|
|||||||
192
cli/go.sum
192
cli/go.sum
@@ -23,6 +23,7 @@ cloud.google.com/go/storage v1.0.0/go.mod h1:IhtSnM/ZTZV8YYJWCY8RULGVqBDmpoyjwiy
|
|||||||
cloud.google.com/go/storage v1.5.0/go.mod h1:tpKbwo567HUNpVclU5sGELwQWBDZ8gh0ZeosJ0Rtdos=
|
cloud.google.com/go/storage v1.5.0/go.mod h1:tpKbwo567HUNpVclU5sGELwQWBDZ8gh0ZeosJ0Rtdos=
|
||||||
cloud.google.com/go/storage v1.6.0/go.mod h1:N7U0C8pVQ/+NIKOBQyamJIeKQKkZ+mxpohlUTyfDhBk=
|
cloud.google.com/go/storage v1.6.0/go.mod h1:N7U0C8pVQ/+NIKOBQyamJIeKQKkZ+mxpohlUTyfDhBk=
|
||||||
dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU=
|
dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU=
|
||||||
|
github.com/Azure/go-ansiterm v0.0.0-20170929234023-d6e3b3328b78/go.mod h1:LmzpDX56iTiv29bbRTIsUNlaFfuhWRQBWjQdVyAevI8=
|
||||||
github.com/Azure/go-autorest v14.2.0+incompatible h1:V5VMDjClD3GiElqLWO7mz2MxNAK/vTfRHdAubSIPRgs=
|
github.com/Azure/go-autorest v14.2.0+incompatible h1:V5VMDjClD3GiElqLWO7mz2MxNAK/vTfRHdAubSIPRgs=
|
||||||
github.com/Azure/go-autorest v14.2.0+incompatible/go.mod h1:r+4oMnoxhatjLLJ6zxSWATqVooLgysK6ZNox3g/xq24=
|
github.com/Azure/go-autorest v14.2.0+incompatible/go.mod h1:r+4oMnoxhatjLLJ6zxSWATqVooLgysK6ZNox3g/xq24=
|
||||||
github.com/Azure/go-autorest/autorest v0.11.12 h1:gI8ytXbxMfI+IVbI9mP2JGCTXIuhHLgRlvQ9X4PsnHE=
|
github.com/Azure/go-autorest/autorest v0.11.12 h1:gI8ytXbxMfI+IVbI9mP2JGCTXIuhHLgRlvQ9X4PsnHE=
|
||||||
@@ -39,67 +40,135 @@ github.com/Azure/go-autorest/tracing v0.6.0 h1:TYi4+3m5t6K48TGI9AUdb+IzbnSxvnvUM
|
|||||||
github.com/Azure/go-autorest/tracing v0.6.0/go.mod h1:+vhtPC754Xsa23ID7GlGsrdKBpUA79WCAKPPZVC2DeU=
|
github.com/Azure/go-autorest/tracing v0.6.0/go.mod h1:+vhtPC754Xsa23ID7GlGsrdKBpUA79WCAKPPZVC2DeU=
|
||||||
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
|
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
|
||||||
github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo=
|
github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo=
|
||||||
|
github.com/MakeNowJust/heredoc v0.0.0-20170808103936-bb23615498cd/go.mod h1:64YHyfSL2R96J44Nlwm39UHepQbyR5q10x7iYa1ks2E=
|
||||||
github.com/NYTimes/gziphandler v0.0.0-20170623195520-56545f4a5d46/go.mod h1:3wb06e3pkSAbeQ52E9H9iFoQsEEwGN64994WTCIhntQ=
|
github.com/NYTimes/gziphandler v0.0.0-20170623195520-56545f4a5d46/go.mod h1:3wb06e3pkSAbeQ52E9H9iFoQsEEwGN64994WTCIhntQ=
|
||||||
github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU=
|
github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU=
|
||||||
|
github.com/PuerkitoBio/purell v1.1.0/go.mod h1:c11w/QuzBsJSee3cPx9rAFu61PvFxuPbtSwDGJws/X0=
|
||||||
github.com/PuerkitoBio/purell v1.1.1/go.mod h1:c11w/QuzBsJSee3cPx9rAFu61PvFxuPbtSwDGJws/X0=
|
github.com/PuerkitoBio/purell v1.1.1/go.mod h1:c11w/QuzBsJSee3cPx9rAFu61PvFxuPbtSwDGJws/X0=
|
||||||
github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578/go.mod h1:uGdkoq3SwY9Y+13GIhn11/XLaGBb4BfwItxLd5jeuXE=
|
github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578/go.mod h1:uGdkoq3SwY9Y+13GIhn11/XLaGBb4BfwItxLd5jeuXE=
|
||||||
|
github.com/agnivade/levenshtein v1.0.1/go.mod h1:CURSv5d9Uaml+FovSIICkLbAUZ9S4RqaHDIsdSBg7lM=
|
||||||
github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
|
github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
|
||||||
|
github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
|
||||||
github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
|
github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
|
||||||
|
github.com/alecthomas/units v0.0.0-20190717042225-c3de453c63f4/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
|
||||||
|
github.com/andreyvit/diff v0.0.0-20170406064948-c7f18ee00883/go.mod h1:rCTlJbsFo29Kk6CurOXKm700vrz8f0KW0JNfpkRJY/8=
|
||||||
github.com/armon/circbuf v0.0.0-20150827004946-bbbad097214e/go.mod h1:3U/XgcO3hCbHZ8TKRvWD2dDTCfh9M9ya+I9JpbB7O8o=
|
github.com/armon/circbuf v0.0.0-20150827004946-bbbad097214e/go.mod h1:3U/XgcO3hCbHZ8TKRvWD2dDTCfh9M9ya+I9JpbB7O8o=
|
||||||
|
github.com/armon/consul-api v0.0.0-20180202201655-eb2c6b5be1b6/go.mod h1:grANhF5doyWs3UAsr3K4I6qtAmlQcZDesFNEHPZAzj8=
|
||||||
github.com/armon/go-metrics v0.0.0-20180917152333-f0300d1749da/go.mod h1:Q73ZrmVTwzkszR9V5SSuryQ31EELlFMUz1kKyl939pY=
|
github.com/armon/go-metrics v0.0.0-20180917152333-f0300d1749da/go.mod h1:Q73ZrmVTwzkszR9V5SSuryQ31EELlFMUz1kKyl939pY=
|
||||||
github.com/armon/go-radix v0.0.0-20180808171621-7fddfc383310/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8=
|
github.com/armon/go-radix v0.0.0-20180808171621-7fddfc383310/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8=
|
||||||
|
github.com/asaskevich/govalidator v0.0.0-20180720115003-f9ffefc3facf/go.mod h1:lB+ZfQJz7igIIfQNfa7Ml4HSf2uFQQRzpGGRXenZAgY=
|
||||||
github.com/asaskevich/govalidator v0.0.0-20190424111038-f61b66f89f4a/go.mod h1:lB+ZfQJz7igIIfQNfa7Ml4HSf2uFQQRzpGGRXenZAgY=
|
github.com/asaskevich/govalidator v0.0.0-20190424111038-f61b66f89f4a/go.mod h1:lB+ZfQJz7igIIfQNfa7Ml4HSf2uFQQRzpGGRXenZAgY=
|
||||||
github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q=
|
github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q=
|
||||||
github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8=
|
github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8=
|
||||||
|
github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw=
|
||||||
github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs=
|
github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs=
|
||||||
github.com/bketelsen/crypt v0.0.3-0.20200106085610-5cbc8cc4026c/go.mod h1:MKsuJmJgSg28kpZDP6UIiPt0e0Oz0kqKNGyRaWEPv84=
|
github.com/bketelsen/crypt v0.0.3-0.20200106085610-5cbc8cc4026c/go.mod h1:MKsuJmJgSg28kpZDP6UIiPt0e0Oz0kqKNGyRaWEPv84=
|
||||||
|
github.com/blang/semver v3.5.1+incompatible/go.mod h1:kRBLl5iJ+tD4TcOOxsy/0fnwebNt5EWlYSAyrTnjyyk=
|
||||||
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/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
|
||||||
|
github.com/chai2010/gettext-go v0.0.0-20160711120539-c6fed771bfd5/go.mod h1:/iP1qXHoty45bqomnu2LM+VVyAEdWN+vtSHGlQgyxbw=
|
||||||
github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI=
|
github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI=
|
||||||
github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI=
|
github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI=
|
||||||
github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU=
|
github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU=
|
||||||
github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
|
github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
|
||||||
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.13+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE=
|
github.com/coreos/etcd v3.3.13+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE=
|
||||||
|
github.com/coreos/go-semver v0.2.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk=
|
||||||
github.com/coreos/go-semver v0.3.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk=
|
github.com/coreos/go-semver v0.3.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk=
|
||||||
github.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4=
|
github.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4=
|
||||||
github.com/coreos/pkg v0.0.0-20180928190104-399ea9e2e55f/go.mod h1:E3G3o1h8I7cfcXa63jLwjI0eiQQMgzzUDFVpN/nH/eA=
|
github.com/coreos/pkg v0.0.0-20180928190104-399ea9e2e55f/go.mod h1:E3G3o1h8I7cfcXa63jLwjI0eiQQMgzzUDFVpN/nH/eA=
|
||||||
github.com/cpuguy83/go-md2man/v2 v2.0.0/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU=
|
github.com/cpuguy83/go-md2man/v2 v2.0.0/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU=
|
||||||
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
|
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/creasty/defaults v1.5.1 h1:j8WexcS3d/t4ZmllX4GEkl4wIB/trOr035ajcLHCISM=
|
||||||
|
github.com/creasty/defaults v1.5.1/go.mod h1:FPZ+Y0WNrbqOVw+c6av63eyHUAl6pMHZwqLPvXUZGfY=
|
||||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||||
|
github.com/daviddengcn/go-colortext v0.0.0-20160507010035-511bcaf42ccd/go.mod h1:dv4zxwHi5C/8AeI+4gX4dCWOIvNi7I6JCSX0HvlKPgE=
|
||||||
github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ=
|
github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ=
|
||||||
github.com/dgryski/go-sip13 v0.0.0-20181026042036-e10d5fee7954/go.mod h1:vAd38F8PWV+bWy6jNmig1y/TA+kYO4g3RSRF0IAv0no=
|
github.com/dgryski/go-sip13 v0.0.0-20181026042036-e10d5fee7954/go.mod h1:vAd38F8PWV+bWy6jNmig1y/TA+kYO4g3RSRF0IAv0no=
|
||||||
|
github.com/docker/distribution v2.7.1+incompatible/go.mod h1:J2gT2udsDAN96Uj4KfcMRqY0/ypR+oyYUYmja8H+y+w=
|
||||||
|
github.com/docker/go-units v0.3.3/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk=
|
||||||
|
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/docopt/docopt-go v0.0.0-20180111231733-ee0de3bc6815/go.mod h1:WwZ+bS3ebgob9U8Nd0kOddGdZWjyMGR8Wziv+TBNwSE=
|
github.com/docopt/docopt-go v0.0.0-20180111231733-ee0de3bc6815/go.mod h1:WwZ+bS3ebgob9U8Nd0kOddGdZWjyMGR8Wziv+TBNwSE=
|
||||||
github.com/elazarl/goproxy v0.0.0-20180725130230-947c36da3153 h1:yUdfgN0XgIJw7foRItutHYUIhlcKzcSf5vDpdhQAKTc=
|
|
||||||
github.com/elazarl/goproxy v0.0.0-20180725130230-947c36da3153/go.mod h1:/Zj4wYkgs4iZTTu3o/KG3Itv/qCCa8VVMlb3i9OVuzc=
|
github.com/elazarl/goproxy v0.0.0-20180725130230-947c36da3153/go.mod h1:/Zj4wYkgs4iZTTu3o/KG3Itv/qCCa8VVMlb3i9OVuzc=
|
||||||
github.com/emicklei/go-restful v0.0.0-20170410110728-ff4f55a20633/go.mod h1:otzb+WCGbkyDHkqmQmT5YD2WR4BBwUdeQoFo8l/7tVs=
|
github.com/emicklei/go-restful v0.0.0-20170410110728-ff4f55a20633/go.mod h1:otzb+WCGbkyDHkqmQmT5YD2WR4BBwUdeQoFo8l/7tVs=
|
||||||
|
github.com/emicklei/go-restful v2.9.5+incompatible/go.mod h1:otzb+WCGbkyDHkqmQmT5YD2WR4BBwUdeQoFo8l/7tVs=
|
||||||
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/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/evanphx/json-patch v4.5.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk=
|
||||||
|
github.com/evanphx/json-patch v4.9.0+incompatible h1:kLcOMZeuLAJvL2BPWLMIj5oaZQobrkAqrL+WFZwQses=
|
||||||
github.com/evanphx/json-patch v4.9.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk=
|
github.com/evanphx/json-patch v4.9.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk=
|
||||||
|
github.com/exponent-io/jsonpath v0.0.0-20151013193312-d6023ce2651d/go.mod h1:ZZMPRZwes7CROmyNKgQzC3XPs6L/G2EJLHddWejkmf4=
|
||||||
|
github.com/fatih/camelcase v1.0.0/go.mod h1:yN2Sb0lFhZJUdVvtELVWefmrXpuZESvPmqwoZc+/fpc=
|
||||||
github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4=
|
github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4=
|
||||||
github.com/form3tech-oss/jwt-go v3.2.2+incompatible h1:TcekIExNqud5crz4xD2pavyTgWiPvpYe4Xau31I0PRk=
|
github.com/form3tech-oss/jwt-go v3.2.2+incompatible h1:TcekIExNqud5crz4xD2pavyTgWiPvpYe4Xau31I0PRk=
|
||||||
github.com/form3tech-oss/jwt-go v3.2.2+incompatible/go.mod h1:pbq4aXjuKjdthFRnoDwaVPLA+WlJuPGy+QneDUgJi2k=
|
github.com/form3tech-oss/jwt-go v3.2.2+incompatible/go.mod h1:pbq4aXjuKjdthFRnoDwaVPLA+WlJuPGy+QneDUgJi2k=
|
||||||
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/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ=
|
||||||
|
github.com/fvbommel/sortorder v1.0.1/go.mod h1:uk88iVf1ovNn1iLfgUVU2F9o5eO30ui720w+kxuqRs0=
|
||||||
github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
|
github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
|
||||||
|
github.com/globalsign/mgo v0.0.0-20180905125535-1ca0a4f7cbcb/go.mod h1:xkRDCp4j0OGD1HRkm4kmhM+pmpv3AKq5SU7GMg4oO/Q=
|
||||||
|
github.com/globalsign/mgo v0.0.0-20181015135952-eeefdecb41b8/go.mod h1:xkRDCp4j0OGD1HRkm4kmhM+pmpv3AKq5SU7GMg4oO/Q=
|
||||||
|
github.com/go-errors/errors v1.0.1/go.mod h1:f4zRHt4oKfwPJE5k8C9vpYG+aDHdBFUsgrm6/TyX73Q=
|
||||||
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=
|
||||||
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
|
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
|
||||||
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
|
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
|
||||||
github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
|
github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
|
||||||
|
github.com/go-kit/kit v0.9.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
|
||||||
github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE=
|
github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE=
|
||||||
github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk=
|
github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk=
|
||||||
github.com/go-logr/logr v0.1.0/go.mod h1:ixOQHD9gLJUVQQ2ZOR7zLEifBX6tGkNJF4QyIY7sIas=
|
github.com/go-logr/logr v0.1.0/go.mod h1:ixOQHD9gLJUVQQ2ZOR7zLEifBX6tGkNJF4QyIY7sIas=
|
||||||
|
github.com/go-logr/logr v0.2.0/go.mod h1:z6/tIYblkpsD+a4lm/fGIIU9mZ+XfAiaFtq7xTgseGU=
|
||||||
github.com/go-logr/logr v0.4.0 h1:K7/B1jt6fIBQVd4Owv2MqGQClcgf0R266+7C/QjRcLc=
|
github.com/go-logr/logr v0.4.0 h1:K7/B1jt6fIBQVd4Owv2MqGQClcgf0R266+7C/QjRcLc=
|
||||||
github.com/go-logr/logr v0.4.0/go.mod h1:z6/tIYblkpsD+a4lm/fGIIU9mZ+XfAiaFtq7xTgseGU=
|
github.com/go-logr/logr v0.4.0/go.mod h1:z6/tIYblkpsD+a4lm/fGIIU9mZ+XfAiaFtq7xTgseGU=
|
||||||
|
github.com/go-openapi/analysis v0.0.0-20180825180245-b006789cd277/go.mod h1:k70tL6pCuVxPJOHXQ+wIac1FUrvNkHolPie/cLEU6hI=
|
||||||
|
github.com/go-openapi/analysis v0.17.0/go.mod h1:IowGgpVeD0vNm45So8nr+IcQ3pxVtpRoBWb8PVZO0ik=
|
||||||
|
github.com/go-openapi/analysis v0.18.0/go.mod h1:IowGgpVeD0vNm45So8nr+IcQ3pxVtpRoBWb8PVZO0ik=
|
||||||
|
github.com/go-openapi/analysis v0.19.2/go.mod h1:3P1osvZa9jKjb8ed2TPng3f0i/UY9snX6gxi44djMjk=
|
||||||
|
github.com/go-openapi/analysis v0.19.5/go.mod h1:hkEAkxagaIvIP7VTn8ygJNkd4kAYON2rCu0v0ObL0AU=
|
||||||
|
github.com/go-openapi/errors v0.17.0/go.mod h1:LcZQpmvG4wyF5j4IhA73wkLFQg+QJXOQHVjmcZxhka0=
|
||||||
|
github.com/go-openapi/errors v0.18.0/go.mod h1:LcZQpmvG4wyF5j4IhA73wkLFQg+QJXOQHVjmcZxhka0=
|
||||||
|
github.com/go-openapi/errors v0.19.2/go.mod h1:qX0BLWsyaKfvhluLejVpVNwNRdXZhEbTA4kxxpKBC94=
|
||||||
|
github.com/go-openapi/jsonpointer v0.17.0/go.mod h1:cOnomiV+CVVwFLk0A/MExoFMjwdsUdVpsRhURCKh+3M=
|
||||||
|
github.com/go-openapi/jsonpointer v0.18.0/go.mod h1:cOnomiV+CVVwFLk0A/MExoFMjwdsUdVpsRhURCKh+3M=
|
||||||
github.com/go-openapi/jsonpointer v0.19.2/go.mod h1:3akKfEdA7DF1sugOqz1dVQHBcuDBPKZGEoHC/NkiQRg=
|
github.com/go-openapi/jsonpointer v0.19.2/go.mod h1:3akKfEdA7DF1sugOqz1dVQHBcuDBPKZGEoHC/NkiQRg=
|
||||||
github.com/go-openapi/jsonpointer v0.19.3/go.mod h1:Pl9vOtqEWErmShwVjC8pYs9cog34VGT37dQOVbmoatg=
|
github.com/go-openapi/jsonpointer v0.19.3/go.mod h1:Pl9vOtqEWErmShwVjC8pYs9cog34VGT37dQOVbmoatg=
|
||||||
|
github.com/go-openapi/jsonreference v0.17.0/go.mod h1:g4xxGn04lDIRh0GJb5QlpE3HfopLOL6uZrK/VgnsK9I=
|
||||||
|
github.com/go-openapi/jsonreference v0.18.0/go.mod h1:g4xxGn04lDIRh0GJb5QlpE3HfopLOL6uZrK/VgnsK9I=
|
||||||
github.com/go-openapi/jsonreference v0.19.2/go.mod h1:jMjeRr2HHw6nAVajTXJ4eiUwohSTlpa0o73RUL1owJc=
|
github.com/go-openapi/jsonreference v0.19.2/go.mod h1:jMjeRr2HHw6nAVajTXJ4eiUwohSTlpa0o73RUL1owJc=
|
||||||
github.com/go-openapi/jsonreference v0.19.3/go.mod h1:rjx6GuL8TTa9VaixXglHmQmIL98+wF9xc8zWvFonSJ8=
|
github.com/go-openapi/jsonreference v0.19.3/go.mod h1:rjx6GuL8TTa9VaixXglHmQmIL98+wF9xc8zWvFonSJ8=
|
||||||
|
github.com/go-openapi/loads v0.17.0/go.mod h1:72tmFy5wsWx89uEVddd0RjRWPZm92WRLhf7AC+0+OOU=
|
||||||
|
github.com/go-openapi/loads v0.18.0/go.mod h1:72tmFy5wsWx89uEVddd0RjRWPZm92WRLhf7AC+0+OOU=
|
||||||
|
github.com/go-openapi/loads v0.19.0/go.mod h1:72tmFy5wsWx89uEVddd0RjRWPZm92WRLhf7AC+0+OOU=
|
||||||
|
github.com/go-openapi/loads v0.19.2/go.mod h1:QAskZPMX5V0C2gvfkGZzJlINuP7Hx/4+ix5jWFxsNPs=
|
||||||
|
github.com/go-openapi/loads v0.19.4/go.mod h1:zZVHonKd8DXyxyw4yfnVjPzBjIQcLt0CCsn0N0ZrQsk=
|
||||||
|
github.com/go-openapi/runtime v0.0.0-20180920151709-4f900dc2ade9/go.mod h1:6v9a6LTXWQCdL8k1AO3cvqx5OtZY/Y9wKTgaoP6YRfA=
|
||||||
|
github.com/go-openapi/runtime v0.19.0/go.mod h1:OwNfisksmmaZse4+gpV3Ne9AyMOlP1lt4sK4FXt0O64=
|
||||||
|
github.com/go-openapi/runtime v0.19.4/go.mod h1:X277bwSUBxVlCYR3r7xgZZGKVvBd/29gLDlFGtJ8NL4=
|
||||||
|
github.com/go-openapi/spec v0.17.0/go.mod h1:XkF/MOi14NmjsfZ8VtAKf8pIlbZzyoTvZsdfssdxcBI=
|
||||||
|
github.com/go-openapi/spec v0.18.0/go.mod h1:XkF/MOi14NmjsfZ8VtAKf8pIlbZzyoTvZsdfssdxcBI=
|
||||||
|
github.com/go-openapi/spec v0.19.2/go.mod h1:sCxk3jxKgioEJikev4fgkNmwS+3kuYdJtcsZsD5zxMY=
|
||||||
github.com/go-openapi/spec v0.19.3/go.mod h1:FpwSN1ksY1eteniUU7X0N/BgJ7a4WvBFVA8Lj9mJglo=
|
github.com/go-openapi/spec v0.19.3/go.mod h1:FpwSN1ksY1eteniUU7X0N/BgJ7a4WvBFVA8Lj9mJglo=
|
||||||
|
github.com/go-openapi/spec v0.19.5/go.mod h1:Hm2Jr4jv8G1ciIAo+frC/Ft+rR2kQDh8JHKHb3gWUSk=
|
||||||
|
github.com/go-openapi/strfmt v0.17.0/go.mod h1:P82hnJI0CXkErkXi8IKjPbNBM6lV6+5pLP5l494TcyU=
|
||||||
|
github.com/go-openapi/strfmt v0.18.0/go.mod h1:P82hnJI0CXkErkXi8IKjPbNBM6lV6+5pLP5l494TcyU=
|
||||||
|
github.com/go-openapi/strfmt v0.19.0/go.mod h1:+uW+93UVvGGq2qGaZxdDeJqSAqBqBdl+ZPMF/cC8nDY=
|
||||||
|
github.com/go-openapi/strfmt v0.19.3/go.mod h1:0yX7dbo8mKIvc3XSKp7MNfxw4JytCfCD6+bY1AVL9LU=
|
||||||
|
github.com/go-openapi/strfmt v0.19.5/go.mod h1:eftuHTlB/dI8Uq8JJOyRlieZf+WkkxUuk0dgdHXr2Qk=
|
||||||
|
github.com/go-openapi/swag v0.17.0/go.mod h1:AByQ+nYG6gQg71GINrmuDXCPWdL640yX49/kXLo40Tg=
|
||||||
|
github.com/go-openapi/swag v0.18.0/go.mod h1:AByQ+nYG6gQg71GINrmuDXCPWdL640yX49/kXLo40Tg=
|
||||||
github.com/go-openapi/swag v0.19.2/go.mod h1:POnQmlKehdgb5mhVOsnJFsivZCEZ/vjK9gh66Z9tfKk=
|
github.com/go-openapi/swag v0.19.2/go.mod h1:POnQmlKehdgb5mhVOsnJFsivZCEZ/vjK9gh66Z9tfKk=
|
||||||
github.com/go-openapi/swag v0.19.5/go.mod h1:POnQmlKehdgb5mhVOsnJFsivZCEZ/vjK9gh66Z9tfKk=
|
github.com/go-openapi/swag v0.19.5/go.mod h1:POnQmlKehdgb5mhVOsnJFsivZCEZ/vjK9gh66Z9tfKk=
|
||||||
|
github.com/go-openapi/validate v0.18.0/go.mod h1:Uh4HdOzKt19xGIGm1qHf/ofbX1YQ4Y+MYsct2VUrAJ4=
|
||||||
|
github.com/go-openapi/validate v0.19.2/go.mod h1:1tRCw7m3jtI8eNWEEliiAqUIcBztB2KDnRCRMUi7GTA=
|
||||||
|
github.com/go-openapi/validate v0.19.8/go.mod h1:8DJv2CVJQ6kGNpFW6eV9N3JviE1C85nY1c2z52x1Gk4=
|
||||||
github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY=
|
github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY=
|
||||||
|
github.com/gobuffalo/here v0.6.0/go.mod h1:wAG085dHOYqUpf+Ap+WOdrPTp5IYcDAs/x7PLa8Y5fM=
|
||||||
github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
|
github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
|
||||||
github.com/gogo/protobuf v1.2.1/go.mod h1:hp+jE20tsWTFYpLwKvXlhS1hjn+gTNwPg2I6zVXpSg4=
|
github.com/gogo/protobuf v1.2.1/go.mod h1:hp+jE20tsWTFYpLwKvXlhS1hjn+gTNwPg2I6zVXpSg4=
|
||||||
github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q=
|
github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q=
|
||||||
@@ -108,6 +177,7 @@ github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfU
|
|||||||
github.com/golang/groupcache v0.0.0-20190129154638-5b532d6fd5ef/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
|
github.com/golang/groupcache v0.0.0-20190129154638-5b532d6fd5ef/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
|
||||||
github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
|
github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
|
||||||
github.com/golang/groupcache v0.0.0-20191227052852-215e87163ea7/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
|
github.com/golang/groupcache v0.0.0-20191227052852-215e87163ea7/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
|
||||||
|
github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e h1:1r7pUrabqp18hOBcwBwiTsbnFeTZHV9eER/QT5JVZxY=
|
||||||
github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
|
github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
|
||||||
github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
|
github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
|
||||||
github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
|
github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
|
||||||
@@ -125,8 +195,10 @@ github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrU
|
|||||||
github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w=
|
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.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0=
|
||||||
github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8=
|
github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8=
|
||||||
|
github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
|
||||||
github.com/golang/protobuf v1.4.3 h1:JjCZWpVbqXDqFVmTfYWEVTMIYrL/NPdPSCHPJ0T/raM=
|
github.com/golang/protobuf v1.4.3 h1:JjCZWpVbqXDqFVmTfYWEVTMIYrL/NPdPSCHPJ0T/raM=
|
||||||
github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
|
github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
|
||||||
|
github.com/golangplus/testing v0.0.0-20180327235837-af21d9c3145e/go.mod h1:0AA//k/eakGydO4jKRoRL2j92ZKSzTgj9tclaCrvXHk=
|
||||||
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=
|
||||||
@@ -134,11 +206,18 @@ github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMyw
|
|||||||
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.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/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||||
github.com/google/go-cmp v0.5.2 h1:X2ev0eStA3AbceY54o37/0PQ/UWqKEiiO2dKL5OPaFM=
|
|
||||||
github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||||
|
github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||||
|
github.com/google/go-cmp v0.5.6 h1:BKbKCqvP6I+rmFHt06ZmyQtvB8xAkWdhFyr0ZUNZcxQ=
|
||||||
|
github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||||
|
github.com/google/go-github/v37 v37.0.0 h1:rCspN8/6kB1BAJWZfuafvHhyfIo5fkAulaP/3bOQ/tM=
|
||||||
|
github.com/google/go-github/v37 v37.0.0/go.mod h1:LM7in3NmXDrX58GbEHy7FtNLbI2JijX93RnMKvWG3m4=
|
||||||
|
github.com/google/go-querystring v1.0.0 h1:Xkwi/a1rcvNg1PPYe5vI8GbeBY/jrVuDX5ASuANWTrk=
|
||||||
|
github.com/google/go-querystring v1.0.0/go.mod h1:odCYkC5MyYFN7vkCjXpyrEuKhc/BUO6wN/zVPAxq5ck=
|
||||||
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/gofuzz v1.1.0 h1:Hsa8mG0dQ46ij8Sl2AYJDUv1oA9/d6Vk+3LG99Oe02g=
|
github.com/google/gofuzz v1.1.0 h1:Hsa8mG0dQ46ij8Sl2AYJDUv1oA9/d6Vk+3LG99Oe02g=
|
||||||
github.com/google/gofuzz v1.1.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
|
github.com/google/gofuzz v1.1.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
|
||||||
|
github.com/google/martian v2.1.0+incompatible h1:/CP5g8u/VJHijgedC/Legn3BAbAaWPgecwXBIDzw5no=
|
||||||
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=
|
||||||
github.com/google/pprof v0.0.0-20190515194954-54271f7e092f/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc=
|
github.com/google/pprof v0.0.0-20190515194954-54271f7e092f/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc=
|
||||||
@@ -146,13 +225,17 @@ github.com/google/pprof v0.0.0-20191218002539-d4f498aebedc/go.mod h1:ZgVRPoUq/hf
|
|||||||
github.com/google/pprof v0.0.0-20200212024743-f11f1df84d12/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
|
github.com/google/pprof v0.0.0-20200212024743-f11f1df84d12/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
|
||||||
github.com/google/pprof v0.0.0-20200229191704-1ebb73c60ed3/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
|
github.com/google/pprof v0.0.0-20200229191704-1ebb73c60ed3/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
|
||||||
github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI=
|
github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI=
|
||||||
|
github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510/go.mod h1:pupxD2MaaD3pAXIBCelhxNneeOaAeabZDe5s4K6zSpQ=
|
||||||
|
github.com/google/uuid v1.0.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||||
github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||||
|
github.com/google/uuid v1.1.2 h1:EVhdT+1Kseyi1/pUmXKaFxYsDNy9RQYkMWRH68J/W7Y=
|
||||||
github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||||
github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg=
|
github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg=
|
||||||
github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk=
|
github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk=
|
||||||
github.com/googleapis/gnostic v0.4.1 h1:DLJCy1n/vrD4HPjOvYcT8aYQXpPIzoRZONaYwyycI+I=
|
github.com/googleapis/gnostic v0.4.1 h1:DLJCy1n/vrD4HPjOvYcT8aYQXpPIzoRZONaYwyycI+I=
|
||||||
github.com/googleapis/gnostic v0.4.1/go.mod h1:LRhVm6pbyptWbWbuZ38d1eyptfvIytN3ir6b65WBswg=
|
github.com/googleapis/gnostic v0.4.1/go.mod h1:LRhVm6pbyptWbWbuZ38d1eyptfvIytN3ir6b65WBswg=
|
||||||
github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=
|
github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=
|
||||||
|
github.com/gorilla/websocket v1.4.0/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ=
|
||||||
github.com/gorilla/websocket v1.4.2 h1:+/TMaTYc4QFitKJxsQ7Yye35DkWvkdLcvGKqM+x0Ufc=
|
github.com/gorilla/websocket v1.4.2 h1:+/TMaTYc4QFitKJxsQ7Yye35DkWvkdLcvGKqM+x0Ufc=
|
||||||
github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
|
github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
|
||||||
github.com/gregjones/httpcache v0.0.0-20180305231024-9cad4c3443a7/go.mod h1:FecbI9+v66THATjSRHfNgh1IVFe/9kFxbXtjV0ctIMA=
|
github.com/gregjones/httpcache v0.0.0-20180305231024-9cad4c3443a7/go.mod h1:FecbI9+v66THATjSRHfNgh1IVFe/9kFxbXtjV0ctIMA=
|
||||||
@@ -173,6 +256,7 @@ github.com/hashicorp/go-uuid v1.0.0/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/b
|
|||||||
github.com/hashicorp/go-uuid v1.0.1/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro=
|
github.com/hashicorp/go-uuid v1.0.1/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro=
|
||||||
github.com/hashicorp/go.net v0.0.1/go.mod h1:hjKkEWcCURg++eb33jQU7oqQcI9XDCnUzHA0oac0k90=
|
github.com/hashicorp/go.net v0.0.1/go.mod h1:hjKkEWcCURg++eb33jQU7oqQcI9XDCnUzHA0oac0k90=
|
||||||
github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
|
github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
|
||||||
|
github.com/hashicorp/golang-lru v0.5.1 h1:0hERBMJE1eitiLkihrMvRVBYAkpHzc/J3QdDN+dAcgU=
|
||||||
github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
|
github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
|
||||||
github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ=
|
github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ=
|
||||||
github.com/hashicorp/logutils v1.0.0/go.mod h1:QIAnNjmIWmVIIkWDTG1z5v++HQmx9WQRO+LraFDTW64=
|
github.com/hashicorp/logutils v1.0.0/go.mod h1:QIAnNjmIWmVIIkWDTG1z5v++HQmx9WQRO+LraFDTW64=
|
||||||
@@ -205,66 +289,96 @@ github.com/kr/pty v1.1.5/go.mod h1:9r2w37qlBe7rQ6e1fg1S/9xpWHSnaqNdHD3WcMdbPDA=
|
|||||||
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
|
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
|
||||||
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
|
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
|
||||||
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
|
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
|
||||||
|
github.com/liggitt/tabwriter v0.0.0-20181228230101-89fcab3d43de/go.mod h1:zAbeS9B/r2mtpb6U+EI2rYA5OAXxsYw6wTamcNW+zcE=
|
||||||
|
github.com/lithammer/dedent v1.1.0/go.mod h1:jrXYCQtgg0nJiN+StA2KgR7w6CiQNv9Fd/Z9BP0jIOc=
|
||||||
|
github.com/magiconair/properties v1.8.0/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ=
|
||||||
github.com/magiconair/properties v1.8.1/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ=
|
github.com/magiconair/properties v1.8.1/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ=
|
||||||
|
github.com/mailru/easyjson v0.0.0-20180823135443-60711f1a8329/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc=
|
||||||
|
github.com/mailru/easyjson v0.0.0-20190312143242-1de009706dbe/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc=
|
||||||
github.com/mailru/easyjson v0.0.0-20190614124828-94de47d64c63/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc=
|
github.com/mailru/easyjson v0.0.0-20190614124828-94de47d64c63/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc=
|
||||||
github.com/mailru/easyjson v0.0.0-20190626092158-b2ccc519800e/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc=
|
github.com/mailru/easyjson v0.0.0-20190626092158-b2ccc519800e/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc=
|
||||||
|
github.com/mailru/easyjson v0.7.0/go.mod h1:KAzv3t3aY1NaHWoQz1+4F1ccyAH66Jk7yos7ldAVICs=
|
||||||
|
github.com/markbates/pkger v0.17.1/go.mod h1:0JoVlrol20BSywW79rN3kdFFsE5xYM+rSCQDXbLhiuI=
|
||||||
github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU=
|
github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU=
|
||||||
github.com/mattn/go-isatty v0.0.3/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4=
|
github.com/mattn/go-isatty v0.0.3/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4=
|
||||||
|
github.com/mattn/go-runewidth v0.0.7/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI=
|
||||||
github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0=
|
github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0=
|
||||||
|
github.com/matttproud/golang_protobuf_extensions v1.0.2-0.20181231171920-c182affec369/go.mod h1:BSXmuO+STAnVfrANrmjBb36TMTDstsz7MSK+HVaYKv4=
|
||||||
github.com/miekg/dns v1.0.14/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg=
|
github.com/miekg/dns v1.0.14/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg=
|
||||||
github.com/mitchellh/cli v1.0.0/go.mod h1:hNIlj7HEI86fIcpObd7a0FcrxTWetlwJDGcceTlRvqc=
|
github.com/mitchellh/cli v1.0.0/go.mod h1:hNIlj7HEI86fIcpObd7a0FcrxTWetlwJDGcceTlRvqc=
|
||||||
github.com/mitchellh/go-homedir v1.0.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0=
|
github.com/mitchellh/go-homedir v1.0.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0=
|
||||||
github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0=
|
github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0=
|
||||||
github.com/mitchellh/go-testing-interface v1.0.0/go.mod h1:kRemZodwjscx+RGhAo8eIhFbs2+BFgRtFPeD/KE+zxI=
|
github.com/mitchellh/go-testing-interface v1.0.0/go.mod h1:kRemZodwjscx+RGhAo8eIhFbs2+BFgRtFPeD/KE+zxI=
|
||||||
|
github.com/mitchellh/go-wordwrap v1.0.0/go.mod h1:ZXFpozHsX6DPmq2I0TCekCxypsnAUbP2oI0UX1GXzOo=
|
||||||
github.com/mitchellh/gox v0.4.0/go.mod h1:Sd9lOJ0+aimLBi73mGofS1ycjY8lL3uZM3JPS42BGNg=
|
github.com/mitchellh/gox v0.4.0/go.mod h1:Sd9lOJ0+aimLBi73mGofS1ycjY8lL3uZM3JPS42BGNg=
|
||||||
github.com/mitchellh/iochan v1.0.0/go.mod h1:JwYml1nuB7xOzsp52dPpHFffvOCDupsG0QubkSMEySY=
|
github.com/mitchellh/iochan v1.0.0/go.mod h1:JwYml1nuB7xOzsp52dPpHFffvOCDupsG0QubkSMEySY=
|
||||||
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/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y=
|
github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y=
|
||||||
github.com/moby/spdystream v0.2.0 h1:cjW1zVyyoiM0T7b6UoySUFqzXMoqRckQtXwGPiBhOM8=
|
|
||||||
github.com/moby/spdystream v0.2.0/go.mod h1:f7i0iNDQJ059oMTcWxx8MA/zKFIuD/lY+0GqbN2Wy8c=
|
github.com/moby/spdystream v0.2.0/go.mod h1:f7i0iNDQJ059oMTcWxx8MA/zKFIuD/lY+0GqbN2Wy8c=
|
||||||
|
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-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 h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg=
|
||||||
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=
|
||||||
github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
|
github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
|
||||||
github.com/modern-go/reflect2 v1.0.1 h1:9f412s+6RmYXLWZSEzVVgPGK7C2PphHj5RJrvfx9AWI=
|
github.com/modern-go/reflect2 v1.0.1 h1:9f412s+6RmYXLWZSEzVVgPGK7C2PphHj5RJrvfx9AWI=
|
||||||
github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
|
github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
|
||||||
|
github.com/monochromegane/go-gitignore v0.0.0-20200626010858-205db1a8cc00/go.mod h1:Pm3mSP3c5uWn86xMLZ5Sa7JB9GsEZySvHYXCTK4E9q4=
|
||||||
github.com/munnerz/goautoneg v0.0.0-20120707110453-a547fc61f48d/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ=
|
github.com/munnerz/goautoneg v0.0.0-20120707110453-a547fc61f48d/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ=
|
||||||
github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U=
|
github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U=
|
||||||
|
github.com/mxk/go-flowrate v0.0.0-20140419014527-cca7078d478f h1:y5//uYreIhSUg3J1GEMiLbxo1LJaP8RfCpH6pymGZus=
|
||||||
github.com/mxk/go-flowrate v0.0.0-20140419014527-cca7078d478f/go.mod h1:ZdcZmHo+o7JKHSa8/e818NopupXU1YMK5fe1lsApnBw=
|
github.com/mxk/go-flowrate v0.0.0-20140419014527-cca7078d478f/go.mod h1:ZdcZmHo+o7JKHSa8/e818NopupXU1YMK5fe1lsApnBw=
|
||||||
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e h1:fD57ERR4JtEqsWbfPhv4DMiApHyliiK5xCTNVSPiaAs=
|
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e h1:fD57ERR4JtEqsWbfPhv4DMiApHyliiK5xCTNVSPiaAs=
|
||||||
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno=
|
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno=
|
||||||
github.com/oklog/ulid v1.3.1/go.mod h1:CirwcVhetQ6Lv90oh/F+FBtV6XMibvdAFo93nm5qn4U=
|
github.com/oklog/ulid v1.3.1/go.mod h1:CirwcVhetQ6Lv90oh/F+FBtV6XMibvdAFo93nm5qn4U=
|
||||||
|
github.com/olekukonko/tablewriter v0.0.4/go.mod h1:zq6QwlOf5SlnkVbMSr5EoBv3636FWnp+qbPhuoO21uA=
|
||||||
github.com/onsi/ginkgo v0.0.0-20170829012221-11459a886d9c/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
|
github.com/onsi/ginkgo v0.0.0-20170829012221-11459a886d9c/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
|
||||||
github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
|
github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
|
||||||
github.com/onsi/ginkgo v1.11.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
|
github.com/onsi/ginkgo v1.11.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
|
||||||
github.com/onsi/gomega v0.0.0-20170829124025-dcabb60a477c/go.mod h1:C1qb7wdrVGGVU+Z6iS04AVkA3Q65CEZX59MT0QO5uiA=
|
github.com/onsi/gomega v0.0.0-20170829124025-dcabb60a477c/go.mod h1:C1qb7wdrVGGVU+Z6iS04AVkA3Q65CEZX59MT0QO5uiA=
|
||||||
github.com/onsi/gomega v1.7.0/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY=
|
github.com/onsi/gomega v1.7.0/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY=
|
||||||
|
github.com/op/go-logging v0.0.0-20160315200505-970db520ece7 h1:lDH9UUVJtmYCjyT0CI4q8xvlXPxeZ0gYCVvWbmPlp88=
|
||||||
|
github.com/op/go-logging v0.0.0-20160315200505-970db520ece7/go.mod h1:HzydrMdWErDVzsI23lYNej1Htcns9BCg93Dk0bBINWk=
|
||||||
|
github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM=
|
||||||
github.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc=
|
github.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc=
|
||||||
|
github.com/pborman/uuid v1.2.0/go.mod h1:X/NO0urCmaxf9VXbdlT7C2Yzkj2IKimNn4k+gtPdI/k=
|
||||||
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/peterbourgon/diskv v2.0.1+incompatible/go.mod h1:uqqh8zWWbv1HBMNONnaR/tNboyR3/BZd58JJSHlUSCU=
|
github.com/peterbourgon/diskv v2.0.1+incompatible/go.mod h1:uqqh8zWWbv1HBMNONnaR/tNboyR3/BZd58JJSHlUSCU=
|
||||||
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/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/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
github.com/pmezard/go-difflib v1.0.0 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=
|
||||||
github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw=
|
github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw=
|
||||||
github.com/prometheus/client_golang v0.9.3/go.mod h1:/TN21ttK/J9q6uSwhBd54HahCDft0ttaMvbicHlPoso=
|
github.com/prometheus/client_golang v0.9.3/go.mod h1:/TN21ttK/J9q6uSwhBd54HahCDft0ttaMvbicHlPoso=
|
||||||
|
github.com/prometheus/client_golang v1.0.0/go.mod h1:db9x61etRT2tGnBNRi70OPL5FsnadC4Ky3P0J6CfImo=
|
||||||
|
github.com/prometheus/client_golang v1.7.1/go.mod h1:PY5Wy2awLA44sXw4AOSfFBetzPP4j5+D6mVACh+pe2M=
|
||||||
github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo=
|
github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo=
|
||||||
github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
|
github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
|
||||||
github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
|
github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
|
||||||
|
github.com/prometheus/client_model v0.2.0/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
|
||||||
github.com/prometheus/common v0.0.0-20181113130724-41aa239b4cce/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro=
|
github.com/prometheus/common v0.0.0-20181113130724-41aa239b4cce/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro=
|
||||||
github.com/prometheus/common v0.4.0/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4=
|
github.com/prometheus/common v0.4.0/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4=
|
||||||
|
github.com/prometheus/common v0.4.1/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4=
|
||||||
|
github.com/prometheus/common v0.10.0/go.mod h1:Tlit/dnDKsSWFlCLTWaA1cyBgKHSMdTB80sz/V91rCo=
|
||||||
github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk=
|
github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk=
|
||||||
github.com/prometheus/procfs v0.0.0-20190507164030-5867b95ac084/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA=
|
github.com/prometheus/procfs v0.0.0-20190507164030-5867b95ac084/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA=
|
||||||
|
github.com/prometheus/procfs v0.0.2/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA=
|
||||||
|
github.com/prometheus/procfs v0.1.3/go.mod h1:lV6e/gmhEcM9IjHGsFOCxxuZ+z1YqCvr4OA4YeYWdaU=
|
||||||
|
github.com/prometheus/procfs v0.2.0/go.mod h1:lV6e/gmhEcM9IjHGsFOCxxuZ+z1YqCvr4OA4YeYWdaU=
|
||||||
github.com/prometheus/tsdb v0.7.1/go.mod h1:qhTCs0VvXwvX/y3TZrWD7rabWM+ijKTux40TwIPHuXU=
|
github.com/prometheus/tsdb v0.7.1/go.mod h1:qhTCs0VvXwvX/y3TZrWD7rabWM+ijKTux40TwIPHuXU=
|
||||||
github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod h1:XWv6SoW27p1b0cqNHllgS5HIMJraePCO15w5zCzIWYg=
|
github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod h1:XWv6SoW27p1b0cqNHllgS5HIMJraePCO15w5zCzIWYg=
|
||||||
github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
|
github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
|
||||||
|
github.com/russross/blackfriday v1.5.2/go.mod h1:JO/DiYxRf+HjHt06OyowR9PTA263kcR/rfWxYHBV53g=
|
||||||
github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
|
github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
|
||||||
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/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg65j358z/aeFdxmN0P9QXhEzd20vsDc=
|
github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg65j358z/aeFdxmN0P9QXhEzd20vsDc=
|
||||||
|
github.com/sergi/go-diff v1.0.0/go.mod h1:0CfEIISq7TuYL3j771MWULgwwjU+GofnZX9QAmXWZgo=
|
||||||
|
github.com/sergi/go-diff v1.1.0/go.mod h1:STckp+ISIX8hZLjrqAeVduY0gWCT9IjLuqbuNXdaHfM=
|
||||||
github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc=
|
github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc=
|
||||||
github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo=
|
github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo=
|
||||||
|
github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE=
|
||||||
github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc=
|
github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc=
|
||||||
github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA=
|
github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA=
|
||||||
github.com/soheilhy/cmux v0.1.4/go.mod h1:IM3LyeVVIOuxMH7sFAkER9+bJ4dT7Ms6E4xg4kGIyLM=
|
github.com/soheilhy/cmux v0.1.4/go.mod h1:IM3LyeVVIOuxMH7sFAkER9+bJ4dT7Ms6E4xg4kGIyLM=
|
||||||
@@ -272,6 +386,8 @@ github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasO
|
|||||||
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/go.mod h1:9ZxEEn6pIJ8Rxe320qSDBk6AsU0r9pR7Q4OcevTdifk=
|
github.com/spf13/afero v1.2.2/go.mod h1:9ZxEEn6pIJ8Rxe320qSDBk6AsU0r9pR7Q4OcevTdifk=
|
||||||
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/cobra v1.0.0/go.mod h1:/6GTrnGXV9HjY+aR4k0oJ5tcvakLuG6EuKReYlHNrgE=
|
||||||
|
github.com/spf13/cobra v1.1.1/go.mod h1:WnodtKOvamDL/PwE2M4iKs8aMDBZ5Q5klgD3qfVJQMI=
|
||||||
github.com/spf13/cobra v1.1.3 h1:xghbfqPkxzxP3C/f3n5DdpAbdKLj4ZE4BWQI362l53M=
|
github.com/spf13/cobra v1.1.3 h1:xghbfqPkxzxP3C/f3n5DdpAbdKLj4ZE4BWQI362l53M=
|
||||||
github.com/spf13/cobra v1.1.3/go.mod h1:pGADOWyqRD/YMrPZigI/zbliZ2wVD/23d+is3pSWzOo=
|
github.com/spf13/cobra v1.1.3/go.mod h1:pGADOWyqRD/YMrPZigI/zbliZ2wVD/23d+is3pSWzOo=
|
||||||
github.com/spf13/jwalterweatherman v1.0.0/go.mod h1:cQK4TGJAtQXfYWX+Ddv3mKDzgVb68N+wFjFa4jdeBTo=
|
github.com/spf13/jwalterweatherman v1.0.0/go.mod h1:cQK4TGJAtQXfYWX+Ddv3mKDzgVb68N+wFjFa4jdeBTo=
|
||||||
@@ -279,6 +395,7 @@ github.com/spf13/pflag v0.0.0-20170130214245-9ff6c6923cff/go.mod h1:DYY7MBk1bdzu
|
|||||||
github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4=
|
github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4=
|
||||||
github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA=
|
github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA=
|
||||||
github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
|
github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
|
||||||
|
github.com/spf13/viper v1.4.0/go.mod h1:PTJ7Z/lr49W6bUbkmS1V3by4uWynFiR9p7+dSq/yZzE=
|
||||||
github.com/spf13/viper v1.7.0/go.mod h1:8WkrPz2fc9jxqZNCJI/76HCieCp4Q8HaLFoCha5qpdg=
|
github.com/spf13/viper v1.7.0/go.mod h1:8WkrPz2fc9jxqZNCJI/76HCieCp4Q8HaLFoCha5qpdg=
|
||||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||||
github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||||
@@ -289,24 +406,37 @@ github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81P
|
|||||||
github.com/stretchr/testify v1.6.1 h1:hDPOHmpOpP40lSULcqw7IrRb/u7w6RpDC9399XyoNd0=
|
github.com/stretchr/testify v1.6.1 h1:hDPOHmpOpP40lSULcqw7IrRb/u7w6RpDC9399XyoNd0=
|
||||||
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||||
github.com/subosito/gotenv v1.2.0/go.mod h1:N0PQaV/YGNqwC0u51sEeR/aUtSLEXKX9iv69rRypqCw=
|
github.com/subosito/gotenv v1.2.0/go.mod h1:N0PQaV/YGNqwC0u51sEeR/aUtSLEXKX9iv69rRypqCw=
|
||||||
|
github.com/tidwall/pretty v1.0.0/go.mod h1:XNkn88O1ChpSDQmQeStsy+sBenx6DDtFZJxhVysOjyk=
|
||||||
github.com/tmc/grpc-websocket-proxy v0.0.0-20190109142713-0ad062ec5ee5/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U=
|
github.com/tmc/grpc-websocket-proxy v0.0.0-20190109142713-0ad062ec5ee5/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U=
|
||||||
|
github.com/ugorji/go v1.1.4/go.mod h1:uQMGLiO92mf5W77hV/PUCpI3pbzQx3CRekS0kk+RGrc=
|
||||||
|
github.com/vektah/gqlparser v1.1.2/go.mod h1:1ycwN7Ij5njmMkPPAOaRFY4rET2Enx7IkVv3vaXspKw=
|
||||||
github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU=
|
github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU=
|
||||||
|
github.com/yalp/jsonpath v0.0.0-20180802001716-5cc68e5049a0 h1:6fRhSjgLCkTD3JnJxvaJ4Sj+TYblw757bqYgZaOq5ZY=
|
||||||
|
github.com/yalp/jsonpath v0.0.0-20180802001716-5cc68e5049a0/go.mod h1:/LWChgwKmvncFJFHJ7Gvn9wZArjbV5/FppcK2fKk/tI=
|
||||||
|
github.com/xlab/treeprint v0.0.0-20181112141820-a009c3971eca/go.mod h1:ce1O1j6UtZfjr22oyGxGLbauSBp2YVXpARAosm7dHBg=
|
||||||
|
github.com/xordataexchange/crypt v0.0.3-0.20170626215501-b2862e3d0a77/go.mod h1:aYKd//L2LvnjZzWKhF00oedf4jCCReLcmhLdhm1A27Q=
|
||||||
github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
||||||
github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
||||||
go.etcd.io/bbolt v1.3.2/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU=
|
go.etcd.io/bbolt v1.3.2/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU=
|
||||||
|
go.mongodb.org/mongo-driver v1.0.3/go.mod h1:u7ryQJ+DOzQmeO7zB6MHyr8jkEQvC8vH7qLUO4lqsUM=
|
||||||
|
go.mongodb.org/mongo-driver v1.1.1/go.mod h1:u7ryQJ+DOzQmeO7zB6MHyr8jkEQvC8vH7qLUO4lqsUM=
|
||||||
|
go.mongodb.org/mongo-driver v1.1.2/go.mod h1:u7ryQJ+DOzQmeO7zB6MHyr8jkEQvC8vH7qLUO4lqsUM=
|
||||||
go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU=
|
go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU=
|
||||||
go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8=
|
go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8=
|
||||||
go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
|
go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
|
||||||
go.opencensus.io v0.22.3/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
|
go.opencensus.io v0.22.3/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
|
||||||
|
go.starlark.net v0.0.0-20200306205701-8dd3e2ee1dd5/go.mod h1:nmDLcffg48OtT/PSW0Hg7FvpRQsQh5OSqIylirxKC7o=
|
||||||
go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE=
|
go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE=
|
||||||
go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0=
|
go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0=
|
||||||
go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q=
|
go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q=
|
||||||
golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
|
golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
|
||||||
golang.org/x/crypto v0.0.0-20181029021203-45a5f77698d3/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
|
golang.org/x/crypto v0.0.0-20181029021203-45a5f77698d3/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
|
||||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||||
|
golang.org/x/crypto v0.0.0-20190320223903-b7391e95e576/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||||
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-20190611184440-5c40567a22f8/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
golang.org/x/crypto v0.0.0-20190611184440-5c40567a22f8/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||||
|
golang.org/x/crypto v0.0.0-20190617133340-57b3e21c3d56/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||||
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||||
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||||
golang.org/x/crypto v0.0.0-20201002170205-7f63de1d35b0/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
golang.org/x/crypto v0.0.0-20201002170205-7f63de1d35b0/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||||
@@ -342,9 +472,11 @@ golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzB
|
|||||||
golang.org/x/mod v0.1.1-0.20191107180719-034126e5016b/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=
|
golang.org/x/mod v0.1.1-0.20191107180719-034126e5016b/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=
|
||||||
golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
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.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||||
|
golang.org/x/mod v0.3.1-0.20200828183125-ce943fd02449/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||||
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||||
golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||||
golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||||
|
golang.org/x/net v0.0.0-20181005035420-146acd28ed58/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||||
golang.org/x/net v0.0.0-20181023162649-9b4f9f5ad519/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
golang.org/x/net v0.0.0-20181023162649-9b4f9f5ad519/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||||
golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||||
golang.org/x/net v0.0.0-20181201002055-351d144fa1fc/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
golang.org/x/net v0.0.0-20181201002055-351d144fa1fc/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||||
@@ -352,9 +484,11 @@ golang.org/x/net v0.0.0-20181220203305-927f97764cc3/go.mod h1:mL1N/T3taQHkDXs73r
|
|||||||
golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||||
golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||||
golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||||
|
golang.org/x/net v0.0.0-20190320064053-1272bf9dcd53/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||||
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||||
golang.org/x/net v0.0.0-20190501004415-9ce7a6920f09/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
golang.org/x/net v0.0.0-20190501004415-9ce7a6920f09/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||||
golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||||
|
golang.org/x/net v0.0.0-20190522155817-f3200d17e092/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks=
|
||||||
golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks=
|
golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks=
|
||||||
golang.org/x/net v0.0.0-20190613194153-d28f0bde5980/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
golang.org/x/net v0.0.0-20190613194153-d28f0bde5980/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-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||||
@@ -368,6 +502,7 @@ golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLL
|
|||||||
golang.org/x/net v0.0.0-20200301022130-244492dfa37a/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
golang.org/x/net v0.0.0-20200301022130-244492dfa37a/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||||
golang.org/x/net v0.0.0-20200324143707-d3edc9973b7e/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
|
golang.org/x/net v0.0.0-20200324143707-d3edc9973b7e/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
|
||||||
golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
|
golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
|
||||||
|
golang.org/x/net v0.0.0-20201110031124-69a78807bb2b/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
|
||||||
golang.org/x/net v0.0.0-20210224082022-3d97a244fca7 h1:OgUuv8lsRpBibGNbSizVwKWlysjaNzmC9gYMhPVfqFM=
|
golang.org/x/net v0.0.0-20210224082022-3d97a244fca7 h1:OgUuv8lsRpBibGNbSizVwKWlysjaNzmC9gYMhPVfqFM=
|
||||||
golang.org/x/net v0.0.0-20210224082022-3d97a244fca7/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
|
golang.org/x/net v0.0.0-20210224082022-3d97a244fca7/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
|
||||||
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=
|
||||||
@@ -392,7 +527,9 @@ golang.org/x/sys v0.0.0-20181107165924-66b7b1311ac8/go.mod h1:STP8DvDyc/dI5b8T5h
|
|||||||
golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||||
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||||
golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
golang.org/x/sys v0.0.0-20190321052220-f7bb7a8bee54/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/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=
|
||||||
@@ -400,9 +537,12 @@ golang.org/x/sys v0.0.0-20190616124812-15dcb6c0061f/go.mod h1:h1NjWce9XRLGQEsW7w
|
|||||||
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-20190726091711-fc99dfbffb4e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-20191001151750-bb3f8db39f24/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20191001151750-bb3f8db39f24/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
golang.org/x/sys v0.0.0-20191002063906-3421d5a6bb1c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-20191228213918-04cbcbbfeed8/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20191228213918-04cbcbbfeed8/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
golang.org/x/sys v0.0.0-20200106162015-b016eb3dc98e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-20200113162924-86b910548bc1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20200113162924-86b910548bc1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-20200122134326-e047566fdf82/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20200122134326-e047566fdf82/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-20200202164722-d101bd2416d5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20200202164722-d101bd2416d5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
@@ -410,10 +550,13 @@ golang.org/x/sys v0.0.0-20200212091648-12a6c2dcc1e4/go.mod h1:h1NjWce9XRLGQEsW7w
|
|||||||
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-20200302150141-5c8b2ff67527/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20200302150141-5c8b2ff67527/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
golang.org/x/sys v0.0.0-20200615200032-f1bc736245b1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
golang.org/x/sys v0.0.0-20200831180312-196b9ba8737a/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-20210225134936-a50acf3fe073 h1:8qxJSnu+7dRq6upnbntrmriWByIakBuct5OM/MdQC1M=
|
golang.org/x/sys v0.0.0-20210119212857-b64e53b001e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-20210225134936-a50acf3fe073/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20210426230700-d19ff857e887 h1:dXfMednGJh/SUUFjTLsWJz3P+TQt9qnR11GgeI3vWKs=
|
||||||
|
golang.org/x/sys v0.0.0-20210426230700-d19ff857e887/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw=
|
golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw=
|
||||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||||
golang.org/x/term v0.0.0-20210220032956-6a3ed077a48d h1:SZxvLBoTP5yHO3Frd4z4vrF+DBX9vMVanchswa69toE=
|
golang.org/x/term v0.0.0-20210220032956-6a3ed077a48d h1:SZxvLBoTP5yHO3Frd4z4vrF+DBX9vMVanchswa69toE=
|
||||||
@@ -433,6 +576,7 @@ golang.org/x/time v0.0.0-20210220033141-f8bda1e9f3ba/go.mod h1:tRJNPiyCQ0inRvYxb
|
|||||||
golang.org/x/tools v0.0.0-20180221164845-07fd8470d635/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
golang.org/x/tools v0.0.0-20180221164845-07fd8470d635/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||||
golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||||
|
golang.org/x/tools v0.0.0-20190125232054-d66bd3c5d5a6/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||||
golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY=
|
golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY=
|
||||||
golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
|
golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
|
||||||
golang.org/x/tools v0.0.0-20190312151545-0bb0c0a6e846/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
|
golang.org/x/tools v0.0.0-20190312151545-0bb0c0a6e846/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
|
||||||
@@ -443,7 +587,9 @@ golang.org/x/tools v0.0.0-20190506145303-2d16b83fe98c/go.mod h1:RgjU9mgBXZiqYHBn
|
|||||||
golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
|
golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
|
||||||
golang.org/x/tools v0.0.0-20190606124116-d0a3d012864b/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
|
golang.org/x/tools v0.0.0-20190606124116-d0a3d012864b/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
|
||||||
golang.org/x/tools v0.0.0-20190614205625-5aca471b1d59/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
|
golang.org/x/tools v0.0.0-20190614205625-5aca471b1d59/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
|
||||||
|
golang.org/x/tools v0.0.0-20190617190820-da514acc4774/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
|
||||||
golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
|
golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
|
||||||
|
golang.org/x/tools v0.0.0-20190624222133-a101b041ded4/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
|
||||||
golang.org/x/tools v0.0.0-20190628153133-6cdbf07be9d0/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
|
golang.org/x/tools v0.0.0-20190628153133-6cdbf07be9d0/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
|
||||||
golang.org/x/tools v0.0.0-20190816200558-6889da9d5479/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
golang.org/x/tools v0.0.0-20190816200558-6889da9d5479/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||||
golang.org/x/tools v0.0.0-20190911174233-4f2ddba30aff/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
golang.org/x/tools v0.0.0-20190911174233-4f2ddba30aff/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||||
@@ -464,8 +610,10 @@ golang.org/x/tools v0.0.0-20200207183749-b753a1ba74fa/go.mod h1:TB2adYChydJhpapK
|
|||||||
golang.org/x/tools v0.0.0-20200212150539-ea181f53ac56/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
|
golang.org/x/tools v0.0.0-20200212150539-ea181f53ac56/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
|
||||||
golang.org/x/tools v0.0.0-20200224181240-023911ca70b2/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
|
golang.org/x/tools v0.0.0-20200224181240-023911ca70b2/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
|
||||||
golang.org/x/tools v0.0.0-20200304193943-95d2e580d8eb/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw=
|
golang.org/x/tools v0.0.0-20200304193943-95d2e580d8eb/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw=
|
||||||
|
golang.org/x/tools v0.0.0-20200505023115-26f46d2f7ef8/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
|
||||||
golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
|
golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
|
||||||
golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
|
golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
|
||||||
|
golang.org/x/tools v0.1.0/go.mod h1:xkSsbof2nBLbhDlRMhhhyNLN/zl3eTqcnHD5viDpcZ0=
|
||||||
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||||
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||||
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||||
@@ -508,6 +656,7 @@ google.golang.org/genproto v0.0.0-20200305110556-506484158171/go.mod h1:55QSHmfG
|
|||||||
google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo=
|
google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo=
|
||||||
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=
|
||||||
|
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.26.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
|
google.golang.org/grpc v1.26.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
|
||||||
@@ -540,33 +689,52 @@ gopkg.in/yaml.v2 v2.0.0-20170812160011-eb3733d160e7/go.mod h1:JAlM8MvJe8wmxCU4Bl
|
|||||||
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=
|
||||||
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||||
gopkg.in/yaml.v2 v2.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.5/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||||
|
gopkg.in/yaml.v2 v2.2.7/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||||
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.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
|
gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
|
||||||
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
|
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
|
||||||
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c 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=
|
||||||
|
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b h1:h8qDotaEPuJATrMmW04NCwg7v22aHH28wwpauUhK9Oo=
|
||||||
|
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||||
|
gotest.tools/v3 v3.0.2/go.mod h1:3SzNCllyD9/Y+b5r9JIKQ474KzkZyqLqEfYqMsX94Bk=
|
||||||
|
gotest.tools/v3 v3.0.3/go.mod h1:Z7Lb0S5l+klDB31fvDQX8ss/FlKDxtlFlw3Oa8Ymbl8=
|
||||||
honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
||||||
honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
||||||
honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
||||||
honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
||||||
honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg=
|
honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg=
|
||||||
honnef.co/go/tools v0.0.1-2020.1.3/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k=
|
honnef.co/go/tools v0.0.1-2020.1.3/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k=
|
||||||
k8s.io/api v0.21.0 h1:gu5iGF4V6tfVCQ/R+8Hc0h7H1JuEhzyEi9S4R5LM8+Y=
|
k8s.io/api v0.21.2 h1:vz7DqmRsXTCSa6pNxXwQ1IYeAZgdIsua+DZU+o+SX3Y=
|
||||||
k8s.io/api v0.21.0/go.mod h1:+YbrhBBGgsxbF6o6Kj4KJPJnBmAKuXDeS3E18bgHNVU=
|
k8s.io/api v0.21.2/go.mod h1:Lv6UGJZ1rlMI1qusN8ruAp9PUBFyBwpEHAdG24vIsiU=
|
||||||
k8s.io/apimachinery v0.21.0 h1:3Fx+41if+IRavNcKOz09FwEXDBG6ORh6iMsTSelhkMA=
|
k8s.io/apimachinery v0.21.2 h1:vezUc/BHqWlQDnZ+XkrpXSmnANSLbpnlpwo0Lhk0gpc=
|
||||||
k8s.io/apimachinery v0.21.0/go.mod h1:jbreFvJo3ov9rj7eWT7+sYiRx+qZuCYXwWT1bcDswPY=
|
k8s.io/apimachinery v0.21.2/go.mod h1:CdTY8fU/BlvAbJ2z/8kBwimGki5Zp8/fbVuLY8gJumM=
|
||||||
k8s.io/client-go v0.21.0 h1:n0zzzJsAQmJngpC0IhgFcApZyoGXPrDIAD601HD09ag=
|
k8s.io/cli-runtime v0.21.2/go.mod h1:8u/jFcM0QpoI28f6sfrAAIslLCXUYKD5SsPPMWiHYrI=
|
||||||
k8s.io/client-go v0.21.0/go.mod h1:nNBytTF9qPFDEhoqgEPaarobC8QPae13bElIVHzIglA=
|
k8s.io/client-go v0.21.2 h1:Q1j4L/iMN4pTw6Y4DWppBoUxgKO8LbffEMVEV00MUp0=
|
||||||
|
k8s.io/client-go v0.21.2/go.mod h1:HdJ9iknWpbl3vMGtib6T2PyI/VYxiZfq936WNVHBRrA=
|
||||||
|
k8s.io/code-generator v0.21.2/go.mod h1:8mXJDCB7HcRo1xiEQstcguZkbxZaqeUOrO9SsicWs3U=
|
||||||
|
k8s.io/component-base v0.21.2/go.mod h1:9lvmIThzdlrJj5Hp8Z/TOgIkdfsNARQ1pT+3PByuiuc=
|
||||||
|
k8s.io/component-helpers v0.21.2/go.mod h1:DbyFt/A0p6Cv+R5+QOGSJ5f5t4xDfI8Yb89a57DgJlQ=
|
||||||
k8s.io/gengo v0.0.0-20200413195148-3a45101e95ac/go.mod h1:ezvh/TsK7cY6rbqRK0oQQ8IAqLxYwwyPxAX1Pzy0ii0=
|
k8s.io/gengo v0.0.0-20200413195148-3a45101e95ac/go.mod h1:ezvh/TsK7cY6rbqRK0oQQ8IAqLxYwwyPxAX1Pzy0ii0=
|
||||||
|
k8s.io/gengo v0.0.0-20201214224949-b6c5ce23f027/go.mod h1:FiNAH4ZV3gBg2Kwh89tzAEV2be7d5xI0vBa/VySYy3E=
|
||||||
k8s.io/klog/v2 v2.0.0/go.mod h1:PBfzABfn139FHAV07az/IF9Wp1bkk3vpT2XSJ76fSDE=
|
k8s.io/klog/v2 v2.0.0/go.mod h1:PBfzABfn139FHAV07az/IF9Wp1bkk3vpT2XSJ76fSDE=
|
||||||
|
k8s.io/klog/v2 v2.2.0/go.mod h1:Od+F08eJP+W3HUb4pSrPpgp9DGU4GzlpG/TmITuYh/Y=
|
||||||
k8s.io/klog/v2 v2.8.0 h1:Q3gmuM9hKEjefWFFYF0Mat+YyFJvsUyYuwyNNJ5C9Ts=
|
k8s.io/klog/v2 v2.8.0 h1:Q3gmuM9hKEjefWFFYF0Mat+YyFJvsUyYuwyNNJ5C9Ts=
|
||||||
k8s.io/klog/v2 v2.8.0/go.mod h1:hy9LJ/NvuK+iVyP4Ehqva4HxZG/oXyIS3n3Jmire4Ec=
|
k8s.io/klog/v2 v2.8.0/go.mod h1:hy9LJ/NvuK+iVyP4Ehqva4HxZG/oXyIS3n3Jmire4Ec=
|
||||||
|
k8s.io/kube-openapi v0.0.0-20210305001622-591a79e4bda7 h1:vEx13qjvaZ4yfObSSXW7BrMc/KQBBT/Jyee8XtLf4x0=
|
||||||
k8s.io/kube-openapi v0.0.0-20210305001622-591a79e4bda7/go.mod h1:wXW5VT87nVfh/iLV8FpR2uDvrFyomxbtb1KivDbvPTE=
|
k8s.io/kube-openapi v0.0.0-20210305001622-591a79e4bda7/go.mod h1:wXW5VT87nVfh/iLV8FpR2uDvrFyomxbtb1KivDbvPTE=
|
||||||
|
k8s.io/kubectl v0.21.2 h1:9XPCetvOMDqrIZZXb1Ei+g8t6KrIp9ENJaysQjUuLiE=
|
||||||
|
k8s.io/kubectl v0.21.2/go.mod h1:PgeUclpG8VVmmQIl8zpLar3IQEpFc9mrmvlwY3CK1xo=
|
||||||
|
k8s.io/metrics v0.21.2/go.mod h1:wzlOINZMCtWq8dR9gHlyaOemmYlOpAoldEIXE82gAhI=
|
||||||
k8s.io/utils v0.0.0-20201110183641-67b214c5f920 h1:CbnUZsM497iRC5QMVkHwyl8s2tB3g7yaSHkYPkpgelw=
|
k8s.io/utils v0.0.0-20201110183641-67b214c5f920 h1:CbnUZsM497iRC5QMVkHwyl8s2tB3g7yaSHkYPkpgelw=
|
||||||
k8s.io/utils v0.0.0-20201110183641-67b214c5f920/go.mod h1:jPW/WVKK9YHAvNhRxK0md/EJ228hCsBRufyofKtW8HA=
|
k8s.io/utils v0.0.0-20201110183641-67b214c5f920/go.mod h1:jPW/WVKK9YHAvNhRxK0md/EJ228hCsBRufyofKtW8HA=
|
||||||
rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8=
|
rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8=
|
||||||
rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0=
|
rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0=
|
||||||
rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA=
|
rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA=
|
||||||
|
sigs.k8s.io/kustomize/api v0.8.8/go.mod h1:He1zoK0nk43Pc6NlV085xDXDXTNprtcyKZVm3swsdNY=
|
||||||
|
sigs.k8s.io/kustomize/cmd/config v0.9.10/go.mod h1:Mrby0WnRH7hA6OwOYnYpfpiY0WJIMgYrEDfwOeFdMK0=
|
||||||
|
sigs.k8s.io/kustomize/kustomize/v4 v4.1.2/go.mod h1:PxBvo4WGYlCLeRPL+ziT64wBXqbgfcalOS/SXa/tcyo=
|
||||||
|
sigs.k8s.io/kustomize/kyaml v0.10.17/go.mod h1:mlQFagmkm1P+W4lZJbJ/yaxMd8PqMRSC4cPcfUVt5Hg=
|
||||||
sigs.k8s.io/structured-merge-diff/v4 v4.0.2/go.mod h1:bJZC9H9iH24zzfZ/41RGcq60oK1F7G282QMXDPYydCw=
|
sigs.k8s.io/structured-merge-diff/v4 v4.0.2/go.mod h1:bJZC9H9iH24zzfZ/41RGcq60oK1F7G282QMXDPYydCw=
|
||||||
sigs.k8s.io/structured-merge-diff/v4 v4.1.0 h1:C4r9BgJ98vrKnnVCjwCSXcWjWe0NKcUQkmzDXZXGwH8=
|
sigs.k8s.io/structured-merge-diff/v4 v4.1.0 h1:C4r9BgJ98vrKnnVCjwCSXcWjWe0NKcUQkmzDXZXGwH8=
|
||||||
sigs.k8s.io/structured-merge-diff/v4 v4.1.0/go.mod h1:bJZC9H9iH24zzfZ/41RGcq60oK1F7G282QMXDPYydCw=
|
sigs.k8s.io/structured-merge-diff/v4 v4.1.0/go.mod h1:bJZC9H9iH24zzfZ/41RGcq60oK1F7G282QMXDPYydCw=
|
||||||
|
|||||||
25
cli/goUtils/funcWrappers.go
Normal file
25
cli/goUtils/funcWrappers.go
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
package goUtils
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/up9inc/mizu/cli/mizu"
|
||||||
|
"reflect"
|
||||||
|
"runtime/debug"
|
||||||
|
)
|
||||||
|
|
||||||
|
func HandleExcWrapper(fn interface{}, params ...interface{}) (result []reflect.Value) {
|
||||||
|
defer func() {
|
||||||
|
if panicMessage := recover(); panicMessage != nil {
|
||||||
|
stack := debug.Stack()
|
||||||
|
mizu.Log.Fatalf("Unhandled panic: %v\n stack: %s", panicMessage, stack)
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
f := reflect.ValueOf(fn)
|
||||||
|
if f.Type().NumIn() != len(params) {
|
||||||
|
panic("incorrect number of parameters!")
|
||||||
|
}
|
||||||
|
inputs := make([]reflect.Value, len(params))
|
||||||
|
for k, in := range params {
|
||||||
|
inputs[k] = reflect.ValueOf(in)
|
||||||
|
}
|
||||||
|
return f.Call(inputs)
|
||||||
|
}
|
||||||
@@ -1,52 +0,0 @@
|
|||||||
package kubernetes
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bytes"
|
|
||||||
"context"
|
|
||||||
"fmt"
|
|
||||||
"k8s.io/apimachinery/pkg/util/httpstream"
|
|
||||||
"k8s.io/client-go/tools/portforward"
|
|
||||||
"k8s.io/client-go/transport/spdy"
|
|
||||||
"net/http"
|
|
||||||
"net/url"
|
|
||||||
"strings"
|
|
||||||
)
|
|
||||||
|
|
||||||
type PortForward struct {
|
|
||||||
stopChan chan struct{}
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewPortForward(kubernetesProvider *Provider, namespace string, podName string, localPort uint16, podPort uint16, cancel context.CancelFunc) (*PortForward, error) {
|
|
||||||
dialer := getHttpDialer(kubernetesProvider, namespace, podName)
|
|
||||||
stopChan, readyChan := make(chan struct{}, 1), make(chan struct{}, 1)
|
|
||||||
out, errOut := new(bytes.Buffer), new(bytes.Buffer)
|
|
||||||
|
|
||||||
forwarder, err := portforward.New(dialer, []string{fmt.Sprintf("%d:%d", localPort, podPort)}, stopChan, readyChan, out, errOut)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
go func() {
|
|
||||||
err = forwarder.ForwardPorts() // this is blocking
|
|
||||||
if err != nil {
|
|
||||||
fmt.Printf("kubernetes port-forwarding error: %s", err)
|
|
||||||
cancel()
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
return &PortForward{stopChan: stopChan}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (portForward *PortForward) Stop() {
|
|
||||||
close(portForward.stopChan)
|
|
||||||
}
|
|
||||||
|
|
||||||
func getHttpDialer(kubernetesProvider *Provider, namespace string, podName string) httpstream.Dialer {
|
|
||||||
roundTripper, upgrader, err := spdy.RoundTripperFor(&kubernetesProvider.clientConfig)
|
|
||||||
if err != nil {
|
|
||||||
panic(err)
|
|
||||||
}
|
|
||||||
path := fmt.Sprintf("/api/v1/namespaces/%s/pods/%s/portforward", namespace, podName)
|
|
||||||
hostIP := strings.TrimLeft(kubernetesProvider.clientConfig.Host, "htps:/")
|
|
||||||
serverURL := url.URL{Scheme: "https", Path: path, Host: hostIP}
|
|
||||||
|
|
||||||
return spdy.NewDialer(upgrader, &http.Client{Transport: roundTripper}, http.MethodPost, &serverURL)
|
|
||||||
}
|
|
||||||
@@ -1,20 +1,26 @@
|
|||||||
package kubernetes
|
package kubernetes
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"bytes"
|
||||||
_ "bytes"
|
_ "bytes"
|
||||||
"context"
|
"context"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"github.com/up9inc/mizu/cli/mizu"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"regexp"
|
"regexp"
|
||||||
|
"strconv"
|
||||||
|
|
||||||
|
"github.com/up9inc/mizu/cli/mizu"
|
||||||
"github.com/up9inc/mizu/shared"
|
"github.com/up9inc/mizu/shared"
|
||||||
|
"io"
|
||||||
core "k8s.io/api/core/v1"
|
core "k8s.io/api/core/v1"
|
||||||
rbac "k8s.io/api/rbac/v1"
|
rbac "k8s.io/api/rbac/v1"
|
||||||
k8serrors "k8s.io/apimachinery/pkg/api/errors"
|
k8serrors "k8s.io/apimachinery/pkg/api/errors"
|
||||||
|
resource "k8s.io/apimachinery/pkg/api/resource"
|
||||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||||
|
"k8s.io/apimachinery/pkg/runtime"
|
||||||
"k8s.io/apimachinery/pkg/util/intstr"
|
"k8s.io/apimachinery/pkg/util/intstr"
|
||||||
"k8s.io/apimachinery/pkg/watch"
|
"k8s.io/apimachinery/pkg/watch"
|
||||||
applyconfapp "k8s.io/client-go/applyconfigurations/apps/v1"
|
applyconfapp "k8s.io/client-go/applyconfigurations/apps/v1"
|
||||||
@@ -26,8 +32,10 @@ import (
|
|||||||
_ "k8s.io/client-go/plugin/pkg/client/auth/oidc"
|
_ "k8s.io/client-go/plugin/pkg/client/auth/oidc"
|
||||||
_ "k8s.io/client-go/plugin/pkg/client/auth/openstack"
|
_ "k8s.io/client-go/plugin/pkg/client/auth/openstack"
|
||||||
restclient "k8s.io/client-go/rest"
|
restclient "k8s.io/client-go/rest"
|
||||||
|
"k8s.io/client-go/tools/cache"
|
||||||
"k8s.io/client-go/tools/clientcmd"
|
"k8s.io/client-go/tools/clientcmd"
|
||||||
_ "k8s.io/client-go/tools/portforward"
|
_ "k8s.io/client-go/tools/portforward"
|
||||||
|
watchtools "k8s.io/client-go/tools/watch"
|
||||||
"k8s.io/client-go/util/homedir"
|
"k8s.io/client-go/util/homedir"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -39,15 +47,19 @@ type Provider struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const (
|
const (
|
||||||
serviceAccountName = "mizu-service-account"
|
fieldManagerName = "mizu-manager"
|
||||||
fieldManagerName = "mizu-manager"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
func NewProvider(kubeConfigPath string) *Provider {
|
func NewProvider(kubeConfigPath string) (*Provider, error) {
|
||||||
kubernetesConfig := loadKubernetesConfiguration(kubeConfigPath)
|
kubernetesConfig := loadKubernetesConfiguration(kubeConfigPath)
|
||||||
restClientConfig, err := kubernetesConfig.ClientConfig()
|
restClientConfig, err := kubernetesConfig.ClientConfig()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
panic(err.Error())
|
if clientcmd.IsEmptyConfig(err) {
|
||||||
|
return nil, fmt.Errorf("Couldn't find the kube config file, or file is empty. Try adding '--kube-config=<path to kube config file>'\n")
|
||||||
|
}
|
||||||
|
if clientcmd.IsConfigurationInvalid(err) {
|
||||||
|
return nil, fmt.Errorf("Invalid kube config file. Try using a different config with '--kube-config=<path to kube config file>'\n")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
clientSet := getClientSet(restClientConfig)
|
clientSet := getClientSet(restClientConfig)
|
||||||
|
|
||||||
@@ -55,7 +67,7 @@ func NewProvider(kubeConfigPath string) *Provider {
|
|||||||
clientSet: clientSet,
|
clientSet: clientSet,
|
||||||
kubernetesConfig: kubernetesConfig,
|
kubernetesConfig: kubernetesConfig,
|
||||||
clientConfig: *restClientConfig,
|
clientConfig: *restClientConfig,
|
||||||
}
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (provider *Provider) CurrentNamespace() string {
|
func (provider *Provider) CurrentNamespace() string {
|
||||||
@@ -63,6 +75,46 @@ func (provider *Provider) CurrentNamespace() string {
|
|||||||
return ns
|
return ns
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (provider *Provider) WaitUtilNamespaceDeleted(ctx context.Context, name string) error {
|
||||||
|
fieldSelector := fmt.Sprintf("metadata.name=%s", name)
|
||||||
|
var limit int64 = 1
|
||||||
|
lw := &cache.ListWatch{
|
||||||
|
ListFunc: func(options metav1.ListOptions) (runtime.Object, error) {
|
||||||
|
options.FieldSelector = fieldSelector
|
||||||
|
options.Limit = limit
|
||||||
|
return provider.clientSet.CoreV1().Namespaces().List(ctx, options)
|
||||||
|
},
|
||||||
|
WatchFunc: func(options metav1.ListOptions) (watch.Interface, error) {
|
||||||
|
options.FieldSelector = fieldSelector
|
||||||
|
options.Limit = limit
|
||||||
|
return provider.clientSet.CoreV1().Namespaces().Watch(ctx, options)
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
var preconditionFunc watchtools.PreconditionFunc = func(store cache.Store) (bool, error) {
|
||||||
|
_, exists, err := store.Get(&core.Namespace{ObjectMeta: metav1.ObjectMeta{Name: name}})
|
||||||
|
if err != nil {
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
|
if exists {
|
||||||
|
return false, nil
|
||||||
|
}
|
||||||
|
return true, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
conditionFunc := func(e watch.Event) (bool, error) {
|
||||||
|
if e.Type == watch.Deleted {
|
||||||
|
return true, nil
|
||||||
|
}
|
||||||
|
return false, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
obj := &core.Namespace{}
|
||||||
|
_, err := watchtools.UntilWithSync(ctx, lw, obj, preconditionFunc, conditionFunc)
|
||||||
|
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
func (provider *Provider) GetPodWatcher(ctx context.Context, namespace string) watch.Interface {
|
func (provider *Provider) GetPodWatcher(ctx context.Context, namespace string) watch.Interface {
|
||||||
watcher, err := provider.clientSet.CoreV1().Pods(namespace).Watch(ctx, metav1.ListOptions{Watch: true})
|
watcher, err := provider.clientSet.CoreV1().Pods(namespace).Watch(ctx, metav1.ListOptions{Watch: true})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -71,24 +123,76 @@ func (provider *Provider) GetPodWatcher(ctx context.Context, namespace string) w
|
|||||||
return watcher
|
return watcher
|
||||||
}
|
}
|
||||||
|
|
||||||
func (provider *Provider) CreateMizuAggregatorPod(ctx context.Context, namespace string, podName string, podImage string, linkServiceAccount bool, mizuApiFilteringOptions *shared.TrafficFilteringOptions) (*core.Pod, error) {
|
func (provider *Provider) CreateNamespace(ctx context.Context, name string) (*core.Namespace, error) {
|
||||||
marshaledFilteringOptions, err := json.Marshal(mizuApiFilteringOptions)
|
namespaceSpec := &core.Namespace{
|
||||||
|
ObjectMeta: metav1.ObjectMeta{
|
||||||
|
Name: name,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
return provider.clientSet.CoreV1().Namespaces().Create(ctx, namespaceSpec, metav1.CreateOptions{})
|
||||||
|
}
|
||||||
|
|
||||||
|
type ApiServerOptions struct {
|
||||||
|
Namespace string
|
||||||
|
PodName string
|
||||||
|
PodImage string
|
||||||
|
ServiceAccountName string
|
||||||
|
IsNamespaceRestricted bool
|
||||||
|
MizuApiFilteringOptions *shared.TrafficFilteringOptions
|
||||||
|
MaxEntriesDBSizeBytes int64
|
||||||
|
}
|
||||||
|
|
||||||
|
func (provider *Provider) CreateMizuApiServerPod(ctx context.Context, opts *ApiServerOptions) (*core.Pod, error) {
|
||||||
|
marshaledFilteringOptions, err := json.Marshal(opts.MizuApiFilteringOptions)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
configMapVolumeName := &core.ConfigMapVolumeSource{}
|
||||||
|
configMapVolumeName.Name = mizu.ConfigMapName
|
||||||
|
configMapOptional := true
|
||||||
|
configMapVolumeName.Optional = &configMapOptional
|
||||||
|
|
||||||
|
cpuLimit, err := resource.ParseQuantity("750m")
|
||||||
|
if err != nil {
|
||||||
|
return nil, errors.New(fmt.Sprintf("invalid cpu limit for %s container", opts.PodName))
|
||||||
|
}
|
||||||
|
memLimit, err := resource.ParseQuantity("512Mi")
|
||||||
|
if err != nil {
|
||||||
|
return nil, errors.New(fmt.Sprintf("invalid memory limit for %s container", opts.PodName))
|
||||||
|
}
|
||||||
|
cpuRequests, err := resource.ParseQuantity("50m")
|
||||||
|
if err != nil {
|
||||||
|
return nil, errors.New(fmt.Sprintf("invalid cpu request for %s container", opts.PodName))
|
||||||
|
}
|
||||||
|
memRequests, err := resource.ParseQuantity("50Mi")
|
||||||
|
if err != nil {
|
||||||
|
return nil, errors.New(fmt.Sprintf("invalid memory request for %s container", opts.PodName))
|
||||||
|
}
|
||||||
|
|
||||||
|
command := []string{"./mizuagent", "--api-server"}
|
||||||
|
if opts.IsNamespaceRestricted {
|
||||||
|
command = append(command, "--namespace", opts.Namespace)
|
||||||
|
}
|
||||||
|
|
||||||
pod := &core.Pod{
|
pod := &core.Pod{
|
||||||
ObjectMeta: metav1.ObjectMeta{
|
ObjectMeta: metav1.ObjectMeta{
|
||||||
Name: podName,
|
Name: opts.PodName,
|
||||||
Namespace: namespace,
|
Namespace: opts.Namespace,
|
||||||
Labels: map[string]string{"app": podName},
|
Labels: map[string]string{"app": opts.PodName},
|
||||||
},
|
},
|
||||||
Spec: core.PodSpec{
|
Spec: core.PodSpec{
|
||||||
Containers: []core.Container{
|
Containers: []core.Container{
|
||||||
{
|
{
|
||||||
Name: podName,
|
Name: opts.PodName,
|
||||||
Image: podImage,
|
Image: opts.PodImage,
|
||||||
ImagePullPolicy: core.PullAlways,
|
ImagePullPolicy: core.PullAlways,
|
||||||
Command: []string{"./mizuagent", "--aggregator"},
|
VolumeMounts: []core.VolumeMount{
|
||||||
|
{
|
||||||
|
Name: mizu.ConfigMapName,
|
||||||
|
MountPath: shared.RulePolicyPath,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Command: command,
|
||||||
Env: []core.EnvVar{
|
Env: []core.EnvVar{
|
||||||
{
|
{
|
||||||
Name: shared.HostModeEnvVar,
|
Name: shared.HostModeEnvVar,
|
||||||
@@ -98,19 +202,40 @@ func (provider *Provider) CreateMizuAggregatorPod(ctx context.Context, namespace
|
|||||||
Name: shared.MizuFilteringOptionsEnvVar,
|
Name: shared.MizuFilteringOptionsEnvVar,
|
||||||
Value: string(marshaledFilteringOptions),
|
Value: string(marshaledFilteringOptions),
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
Name: shared.MaxEntriesDBSizeBytesEnvVar,
|
||||||
|
Value: strconv.FormatInt(opts.MaxEntriesDBSizeBytes, 10),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Resources: core.ResourceRequirements{
|
||||||
|
Limits: core.ResourceList{
|
||||||
|
"cpu": cpuLimit,
|
||||||
|
"memory": memLimit,
|
||||||
|
},
|
||||||
|
Requests: core.ResourceList{
|
||||||
|
"cpu": cpuRequests,
|
||||||
|
"memory": memRequests,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Volumes: []core.Volume{
|
||||||
|
{
|
||||||
|
Name: mizu.ConfigMapName,
|
||||||
|
VolumeSource: core.VolumeSource{
|
||||||
|
ConfigMap: configMapVolumeName,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
DNSPolicy: core.DNSClusterFirstWithHostNet,
|
DNSPolicy: core.DNSClusterFirstWithHostNet,
|
||||||
TerminationGracePeriodSeconds: new(int64),
|
TerminationGracePeriodSeconds: new(int64),
|
||||||
// Affinity: TODO: define node selector for all relevant nodes for this mizu instance
|
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
//define the service account only when it exists to prevent pod crash
|
//define the service account only when it exists to prevent pod crash
|
||||||
if linkServiceAccount {
|
if opts.ServiceAccountName != "" {
|
||||||
pod.Spec.ServiceAccountName = serviceAccountName
|
pod.Spec.ServiceAccountName = opts.ServiceAccountName
|
||||||
}
|
}
|
||||||
return provider.clientSet.CoreV1().Pods(namespace).Create(ctx, pod, metav1.CreateOptions{})
|
return provider.clientSet.CoreV1().Pods(opts.Namespace).Create(ctx, pod, metav1.CreateOptions{})
|
||||||
}
|
}
|
||||||
|
|
||||||
func (provider *Provider) CreateService(ctx context.Context, namespace string, serviceName string, appLabelValue string) (*core.Service, error) {
|
func (provider *Provider) CreateService(ctx context.Context, namespace string, serviceName string, appLabelValue string) (*core.Service, error) {
|
||||||
@@ -128,9 +253,57 @@ func (provider *Provider) CreateService(ctx context.Context, namespace string, s
|
|||||||
return provider.clientSet.CoreV1().Services(namespace).Create(ctx, &service, metav1.CreateOptions{})
|
return provider.clientSet.CoreV1().Services(namespace).Create(ctx, &service, metav1.CreateOptions{})
|
||||||
}
|
}
|
||||||
|
|
||||||
func (provider *Provider) DoesMizuRBACExist(ctx context.Context, namespace string) (bool, error) {
|
func (provider *Provider) DoesServiceAccountExist(ctx context.Context, namespace string, serviceAccountName string) (bool, error) {
|
||||||
serviceAccount, err := provider.clientSet.CoreV1().ServiceAccounts(namespace).Get(ctx, serviceAccountName, metav1.GetOptions{})
|
serviceAccount, err := provider.clientSet.CoreV1().ServiceAccounts(namespace).Get(ctx, serviceAccountName, metav1.GetOptions{})
|
||||||
|
return provider.doesResourceExist(serviceAccount, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (provider *Provider) DoesConfigMapExist(ctx context.Context, namespace string, name string) (bool, error) {
|
||||||
|
resource, err := provider.clientSet.CoreV1().ConfigMaps(namespace).Get(ctx, name, metav1.GetOptions{})
|
||||||
|
return provider.doesResourceExist(resource, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (provider *Provider) DoesServicesExist(ctx context.Context, namespace string, name string) (bool, error) {
|
||||||
|
resource, err := provider.clientSet.CoreV1().Services(namespace).Get(ctx, name, metav1.GetOptions{})
|
||||||
|
return provider.doesResourceExist(resource, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (provider *Provider) DoesNamespaceExist(ctx context.Context, name string) (bool, error) {
|
||||||
|
resource, err := provider.clientSet.CoreV1().Namespaces().Get(ctx, name, metav1.GetOptions{})
|
||||||
|
return provider.doesResourceExist(resource, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (provider *Provider) DoesClusterRoleExist(ctx context.Context, name string) (bool, error) {
|
||||||
|
resource, err := provider.clientSet.RbacV1().ClusterRoles().Get(ctx, name, metav1.GetOptions{})
|
||||||
|
return provider.doesResourceExist(resource, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (provider *Provider) DoesClusterRoleBindingExist(ctx context.Context, name string) (bool, error) {
|
||||||
|
resource, err := provider.clientSet.RbacV1().ClusterRoleBindings().Get(ctx, name, metav1.GetOptions{})
|
||||||
|
return provider.doesResourceExist(resource, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (provider *Provider) DoesRoleExist(ctx context.Context, namespace string, name string) (bool, error) {
|
||||||
|
resource, err := provider.clientSet.RbacV1().Roles(namespace).Get(ctx, name, metav1.GetOptions{})
|
||||||
|
return provider.doesResourceExist(resource, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (provider *Provider) DoesRoleBindingExist(ctx context.Context, namespace string, name string) (bool, error) {
|
||||||
|
resource, err := provider.clientSet.RbacV1().RoleBindings(namespace).Get(ctx, name, metav1.GetOptions{})
|
||||||
|
return provider.doesResourceExist(resource, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (provider *Provider) DoesPodExist(ctx context.Context, namespace string, name string) (bool, error) {
|
||||||
|
resource, err := provider.clientSet.CoreV1().Pods(namespace).Get(ctx, name, metav1.GetOptions{})
|
||||||
|
return provider.doesResourceExist(resource, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (provider *Provider) DoesDaemonSetExist(ctx context.Context, namespace string, name string) (bool, error) {
|
||||||
|
resource, err := provider.clientSet.AppsV1().DaemonSets(namespace).Get(ctx, name, metav1.GetOptions{})
|
||||||
|
return provider.doesResourceExist(resource, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (provider *Provider) doesResourceExist(resource interface{}, err error) (bool, error) {
|
||||||
var statusError *k8serrors.StatusError
|
var statusError *k8serrors.StatusError
|
||||||
if errors.As(err, &statusError) {
|
if errors.As(err, &statusError) {
|
||||||
// expected behavior when resource does not exist
|
// expected behavior when resource does not exist
|
||||||
@@ -141,27 +314,10 @@ func (provider *Provider) DoesMizuRBACExist(ctx context.Context, namespace strin
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return false, err
|
return false, err
|
||||||
}
|
}
|
||||||
return serviceAccount != nil, nil
|
return resource != nil, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (provider *Provider) DoesServicesExist(ctx context.Context, namespace string, serviceName string) (bool, error) {
|
func (provider *Provider) CreateMizuRBAC(ctx context.Context, namespace string, serviceAccountName string, clusterRoleName string, clusterRoleBindingName string, version string) error {
|
||||||
service, err := provider.clientSet.CoreV1().Services(namespace).Get(ctx, serviceName, metav1.GetOptions{})
|
|
||||||
|
|
||||||
var statusError *k8serrors.StatusError
|
|
||||||
if errors.As(err, &statusError) {
|
|
||||||
if statusError.ErrStatus.Reason == metav1.StatusReasonNotFound {
|
|
||||||
return false, nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if err != nil {
|
|
||||||
return false, err
|
|
||||||
}
|
|
||||||
return service != nil, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (provider *Provider) CreateMizuRBAC(ctx context.Context, namespace string, version string) error {
|
|
||||||
clusterRoleName := "mizu-cluster-role"
|
|
||||||
|
|
||||||
serviceAccount := &core.ServiceAccount{
|
serviceAccount := &core.ServiceAccount{
|
||||||
ObjectMeta: metav1.ObjectMeta{
|
ObjectMeta: metav1.ObjectMeta{
|
||||||
Name: serviceAccountName,
|
Name: serviceAccountName,
|
||||||
@@ -184,7 +340,7 @@ func (provider *Provider) CreateMizuRBAC(ctx context.Context, namespace string,
|
|||||||
}
|
}
|
||||||
clusterRoleBinding := &rbac.ClusterRoleBinding{
|
clusterRoleBinding := &rbac.ClusterRoleBinding{
|
||||||
ObjectMeta: metav1.ObjectMeta{
|
ObjectMeta: metav1.ObjectMeta{
|
||||||
Name: "mizu-cluster-role-binding",
|
Name: clusterRoleBindingName,
|
||||||
Labels: map[string]string{"mizu-cli-version": version},
|
Labels: map[string]string{"mizu-cli-version": version},
|
||||||
},
|
},
|
||||||
RoleRef: rbac.RoleRef{
|
RoleRef: rbac.RoleRef{
|
||||||
@@ -201,45 +357,239 @@ func (provider *Provider) CreateMizuRBAC(ctx context.Context, namespace string,
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
_, err := provider.clientSet.CoreV1().ServiceAccounts(namespace).Create(ctx, serviceAccount, metav1.CreateOptions{})
|
_, err := provider.clientSet.CoreV1().ServiceAccounts(namespace).Create(ctx, serviceAccount, metav1.CreateOptions{})
|
||||||
if err != nil {
|
if err != nil && !k8serrors.IsAlreadyExists(err) {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
_, err = provider.clientSet.RbacV1().ClusterRoles().Create(ctx, clusterRole, metav1.CreateOptions{})
|
_, err = provider.clientSet.RbacV1().ClusterRoles().Create(ctx, clusterRole, metav1.CreateOptions{})
|
||||||
if err != nil {
|
if err != nil && !k8serrors.IsAlreadyExists(err) {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
_, err = provider.clientSet.RbacV1().ClusterRoleBindings().Create(ctx, clusterRoleBinding, metav1.CreateOptions{})
|
_, err = provider.clientSet.RbacV1().ClusterRoleBindings().Create(ctx, clusterRoleBinding, metav1.CreateOptions{})
|
||||||
if err != nil {
|
if err != nil && !k8serrors.IsAlreadyExists(err) {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (provider *Provider) CreateMizuRBACNamespaceRestricted(ctx context.Context, namespace string, serviceAccountName string, roleName string, roleBindingName string, version string) error {
|
||||||
|
serviceAccount := &core.ServiceAccount{
|
||||||
|
ObjectMeta: metav1.ObjectMeta{
|
||||||
|
Name: serviceAccountName,
|
||||||
|
Namespace: namespace,
|
||||||
|
Labels: map[string]string{"mizu-cli-version": version},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
role := &rbac.Role{
|
||||||
|
ObjectMeta: metav1.ObjectMeta{
|
||||||
|
Name: roleName,
|
||||||
|
Labels: map[string]string{"mizu-cli-version": version},
|
||||||
|
},
|
||||||
|
Rules: []rbac.PolicyRule{
|
||||||
|
{
|
||||||
|
APIGroups: []string{"", "extensions", "apps"},
|
||||||
|
Resources: []string{"pods", "services", "endpoints"},
|
||||||
|
Verbs: []string{"list", "get", "watch"},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
roleBinding := &rbac.RoleBinding{
|
||||||
|
ObjectMeta: metav1.ObjectMeta{
|
||||||
|
Name: roleBindingName,
|
||||||
|
Labels: map[string]string{"mizu-cli-version": version},
|
||||||
|
},
|
||||||
|
RoleRef: rbac.RoleRef{
|
||||||
|
Name: roleName,
|
||||||
|
Kind: "Role",
|
||||||
|
APIGroup: "rbac.authorization.k8s.io",
|
||||||
|
},
|
||||||
|
Subjects: []rbac.Subject{
|
||||||
|
{
|
||||||
|
Kind: "ServiceAccount",
|
||||||
|
Name: serviceAccountName,
|
||||||
|
Namespace: namespace,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
_, err := provider.clientSet.CoreV1().ServiceAccounts(namespace).Create(ctx, serviceAccount, metav1.CreateOptions{})
|
||||||
|
if err != nil && !k8serrors.IsAlreadyExists(err) {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
_, err = provider.clientSet.RbacV1().Roles(namespace).Create(ctx, role, metav1.CreateOptions{})
|
||||||
|
if err != nil && !k8serrors.IsAlreadyExists(err) {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
_, err = provider.clientSet.RbacV1().RoleBindings(namespace).Create(ctx, roleBinding, metav1.CreateOptions{})
|
||||||
|
if err != nil && !k8serrors.IsAlreadyExists(err) {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (provider *Provider) RemoveNamespace(ctx context.Context, name string) error {
|
||||||
|
if isFound, err := provider.DoesNamespaceExist(ctx, name); err != nil {
|
||||||
|
return err
|
||||||
|
} else if !isFound {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return provider.clientSet.CoreV1().Namespaces().Delete(ctx, name, metav1.DeleteOptions{})
|
||||||
|
}
|
||||||
|
|
||||||
|
func (provider *Provider) RemoveNonNamespacedResources(ctx context.Context, clusterRoleName string, clusterRoleBindingName string) error {
|
||||||
|
if err := provider.RemoveClusterRole(ctx, clusterRoleName); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := provider.RemoveClusterRoleBinding(ctx, clusterRoleBindingName); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (provider *Provider) RemoveClusterRole(ctx context.Context, name string) error {
|
||||||
|
if isFound, err := provider.DoesClusterRoleExist(ctx, name); err != nil {
|
||||||
|
return err
|
||||||
|
} else if !isFound {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return provider.clientSet.RbacV1().ClusterRoles().Delete(ctx, name, metav1.DeleteOptions{})
|
||||||
|
}
|
||||||
|
|
||||||
|
func (provider *Provider) RemoveClusterRoleBinding(ctx context.Context, name string) error {
|
||||||
|
if isFound, err := provider.DoesClusterRoleBindingExist(ctx, name); err != nil {
|
||||||
|
return err
|
||||||
|
} else if !isFound {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return provider.clientSet.RbacV1().ClusterRoleBindings().Delete(ctx, name, metav1.DeleteOptions{})
|
||||||
|
}
|
||||||
|
|
||||||
|
func (provider *Provider) RemoveRoleBinding(ctx context.Context, namespace string, name string) error {
|
||||||
|
if isFound, err := provider.DoesRoleBindingExist(ctx, namespace, name); err != nil {
|
||||||
|
return err
|
||||||
|
} else if !isFound {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return provider.clientSet.RbacV1().RoleBindings(namespace).Delete(ctx, name, metav1.DeleteOptions{})
|
||||||
|
}
|
||||||
|
|
||||||
|
func (provider *Provider) RemoveRole(ctx context.Context, namespace string, name string) error {
|
||||||
|
if isFound, err := provider.DoesRoleExist(ctx, namespace, name); err != nil {
|
||||||
|
return err
|
||||||
|
} else if !isFound {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return provider.clientSet.RbacV1().Roles(namespace).Delete(ctx, name, metav1.DeleteOptions{})
|
||||||
|
}
|
||||||
|
|
||||||
|
func (provider *Provider) RemoveServicAccount(ctx context.Context, namespace string, name string) error {
|
||||||
|
if isFound, err := provider.DoesServiceAccountExist(ctx, namespace, name); err != nil {
|
||||||
|
return err
|
||||||
|
} else if !isFound {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return provider.clientSet.CoreV1().ServiceAccounts(namespace).Delete(ctx, name, metav1.DeleteOptions{})
|
||||||
|
}
|
||||||
|
|
||||||
func (provider *Provider) RemovePod(ctx context.Context, namespace string, podName string) error {
|
func (provider *Provider) RemovePod(ctx context.Context, namespace string, podName string) error {
|
||||||
|
if isFound, err := provider.DoesPodExist(ctx, namespace, podName); err != nil {
|
||||||
|
return err
|
||||||
|
} else if !isFound {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
return provider.clientSet.CoreV1().Pods(namespace).Delete(ctx, podName, metav1.DeleteOptions{})
|
return provider.clientSet.CoreV1().Pods(namespace).Delete(ctx, podName, metav1.DeleteOptions{})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (provider *Provider) RemoveConfigMap(ctx context.Context, namespace string, configMapName string) error {
|
||||||
|
if isFound, err := provider.DoesConfigMapExist(ctx, namespace, configMapName); err != nil {
|
||||||
|
return err
|
||||||
|
} else if !isFound {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return provider.clientSet.CoreV1().ConfigMaps(namespace).Delete(ctx, configMapName, metav1.DeleteOptions{})
|
||||||
|
}
|
||||||
|
|
||||||
func (provider *Provider) RemoveService(ctx context.Context, namespace string, serviceName string) error {
|
func (provider *Provider) RemoveService(ctx context.Context, namespace string, serviceName string) error {
|
||||||
|
if isFound, err := provider.DoesServicesExist(ctx, namespace, serviceName); err != nil {
|
||||||
|
return err
|
||||||
|
} else if !isFound {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
return provider.clientSet.CoreV1().Services(namespace).Delete(ctx, serviceName, metav1.DeleteOptions{})
|
return provider.clientSet.CoreV1().Services(namespace).Delete(ctx, serviceName, metav1.DeleteOptions{})
|
||||||
}
|
}
|
||||||
|
|
||||||
func (provider *Provider) RemoveDaemonSet(ctx context.Context, namespace string, daemonSetName string) error {
|
func (provider *Provider) RemoveDaemonSet(ctx context.Context, namespace string, daemonSetName string) error {
|
||||||
|
if isFound, err := provider.DoesDaemonSetExist(ctx, namespace, daemonSetName); err != nil {
|
||||||
|
return err
|
||||||
|
} else if !isFound {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
return provider.clientSet.AppsV1().DaemonSets(namespace).Delete(ctx, daemonSetName, metav1.DeleteOptions{})
|
return provider.clientSet.AppsV1().DaemonSets(namespace).Delete(ctx, daemonSetName, metav1.DeleteOptions{})
|
||||||
}
|
}
|
||||||
|
|
||||||
func (provider *Provider) ApplyMizuTapperDaemonSet(ctx context.Context, namespace string, daemonSetName string, podImage string, tapperPodName string, aggregatorPodIp string, nodeToTappedPodIPMap map[string][]string, linkServiceAccount bool) error {
|
func (provider *Provider) CreateConfigMap(ctx context.Context, namespace string, configMapName string, data string) error {
|
||||||
|
if data == "" {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
configMapData := make(map[string]string, 0)
|
||||||
|
configMapData[shared.RulePolicyFileName] = data
|
||||||
|
configMap := &core.ConfigMap{
|
||||||
|
TypeMeta: metav1.TypeMeta{
|
||||||
|
Kind: "ConfigMap",
|
||||||
|
APIVersion: "v1",
|
||||||
|
},
|
||||||
|
ObjectMeta: metav1.ObjectMeta{
|
||||||
|
Name: configMapName,
|
||||||
|
Namespace: namespace,
|
||||||
|
},
|
||||||
|
Data: configMapData,
|
||||||
|
}
|
||||||
|
if _, err := provider.clientSet.CoreV1().ConfigMaps(namespace).Create(ctx, configMap, metav1.CreateOptions{}); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (provider *Provider) ApplyMizuTapperDaemonSet(ctx context.Context, namespace string, daemonSetName string, podImage string, tapperPodName string, apiServerPodIp string, nodeToTappedPodIPMap map[string][]string, serviceAccountName string, tapOutgoing bool) error {
|
||||||
|
mizu.Log.Debugf("Applying %d tapper deamonsets, ns: %s, daemonSetName: %s, podImage: %s, tapperPodName: %s", len(nodeToTappedPodIPMap), namespace, daemonSetName, podImage, tapperPodName)
|
||||||
|
|
||||||
|
if len(nodeToTappedPodIPMap) == 0 {
|
||||||
|
return fmt.Errorf("Daemon set %s must tap at least 1 pod", daemonSetName)
|
||||||
|
}
|
||||||
|
|
||||||
nodeToTappedPodIPMapJsonStr, err := json.Marshal(nodeToTappedPodIPMap)
|
nodeToTappedPodIPMapJsonStr, err := json.Marshal(nodeToTappedPodIPMap)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
privileged := true
|
mizuCmd := []string{
|
||||||
|
"./mizuagent",
|
||||||
|
"-i", "any",
|
||||||
|
"--tap",
|
||||||
|
"--hardump",
|
||||||
|
"--api-server-address", fmt.Sprintf("ws://%s/wsTapper", apiServerPodIp),
|
||||||
|
}
|
||||||
|
if tapOutgoing {
|
||||||
|
mizuCmd = append(mizuCmd, "--anydirection")
|
||||||
|
}
|
||||||
|
|
||||||
agentContainer := applyconfcore.Container()
|
agentContainer := applyconfcore.Container()
|
||||||
agentContainer.WithName(tapperPodName)
|
agentContainer.WithName(tapperPodName)
|
||||||
agentContainer.WithImage(podImage)
|
agentContainer.WithImage(podImage)
|
||||||
agentContainer.WithImagePullPolicy(core.PullAlways)
|
agentContainer.WithImagePullPolicy(core.PullAlways)
|
||||||
agentContainer.WithSecurityContext(applyconfcore.SecurityContext().WithPrivileged(privileged))
|
agentContainer.WithSecurityContext(applyconfcore.SecurityContext().WithPrivileged(true))
|
||||||
agentContainer.WithCommand("./mizuagent", "-i", "any", "--tap", "--hardump", "--aggregator-address", fmt.Sprintf("ws://%s/wsTapper", aggregatorPodIp))
|
agentContainer.WithCommand(mizuCmd...)
|
||||||
agentContainer.WithEnv(
|
agentContainer.WithEnv(
|
||||||
applyconfcore.EnvVar().WithName(shared.HostModeEnvVar).WithValue("1"),
|
applyconfcore.EnvVar().WithName(shared.HostModeEnvVar).WithValue("1"),
|
||||||
applyconfcore.EnvVar().WithName(shared.TappedAddressesPerNodeDictEnvVar).WithValue(string(nodeToTappedPodIPMapJsonStr)),
|
applyconfcore.EnvVar().WithName(shared.TappedAddressesPerNodeDictEnvVar).WithValue(string(nodeToTappedPodIPMapJsonStr)),
|
||||||
@@ -251,6 +601,32 @@ func (provider *Provider) ApplyMizuTapperDaemonSet(ctx context.Context, namespac
|
|||||||
),
|
),
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
|
cpuLimit, err := resource.ParseQuantity("500m")
|
||||||
|
if err != nil {
|
||||||
|
return errors.New(fmt.Sprintf("invalid cpu limit for %s container", tapperPodName))
|
||||||
|
}
|
||||||
|
memLimit, err := resource.ParseQuantity("1Gi")
|
||||||
|
if err != nil {
|
||||||
|
return errors.New(fmt.Sprintf("invalid memory limit for %s container", tapperPodName))
|
||||||
|
}
|
||||||
|
cpuRequests, err := resource.ParseQuantity("50m")
|
||||||
|
if err != nil {
|
||||||
|
return errors.New(fmt.Sprintf("invalid cpu request for %s container", tapperPodName))
|
||||||
|
}
|
||||||
|
memRequests, err := resource.ParseQuantity("50Mi")
|
||||||
|
if err != nil {
|
||||||
|
return errors.New(fmt.Sprintf("invalid memory request for %s container", tapperPodName))
|
||||||
|
}
|
||||||
|
agentResourceLimits := core.ResourceList{
|
||||||
|
"cpu": cpuLimit,
|
||||||
|
"memory": memLimit,
|
||||||
|
}
|
||||||
|
agentResourceRequests := core.ResourceList{
|
||||||
|
"cpu": cpuRequests,
|
||||||
|
"memory": memRequests,
|
||||||
|
}
|
||||||
|
agentResources := applyconfcore.ResourceRequirements().WithRequests(agentResourceRequests).WithLimits(agentResourceLimits)
|
||||||
|
agentContainer.WithResources(agentResources)
|
||||||
|
|
||||||
nodeNames := make([]string, 0, len(nodeToTappedPodIPMap))
|
nodeNames := make([]string, 0, len(nodeToTappedPodIPMap))
|
||||||
for nodeName := range nodeToTappedPodIPMap {
|
for nodeName := range nodeToTappedPodIPMap {
|
||||||
@@ -280,7 +656,7 @@ func (provider *Provider) ApplyMizuTapperDaemonSet(ctx context.Context, namespac
|
|||||||
podSpec.WithHostNetwork(true)
|
podSpec.WithHostNetwork(true)
|
||||||
podSpec.WithDNSPolicy(core.DNSClusterFirstWithHostNet)
|
podSpec.WithDNSPolicy(core.DNSClusterFirstWithHostNet)
|
||||||
podSpec.WithTerminationGracePeriodSeconds(0)
|
podSpec.WithTerminationGracePeriodSeconds(0)
|
||||||
if linkServiceAccount {
|
if serviceAccountName != "" {
|
||||||
podSpec.WithServiceAccountName(serviceAccountName)
|
podSpec.WithServiceAccountName(serviceAccountName)
|
||||||
}
|
}
|
||||||
podSpec.WithContainers(agentContainer)
|
podSpec.WithContainers(agentContainer)
|
||||||
@@ -301,18 +677,55 @@ func (provider *Provider) ApplyMizuTapperDaemonSet(ctx context.Context, namespac
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
func (provider *Provider) GetAllPodsMatchingRegex(ctx context.Context, regex *regexp.Regexp) ([]core.Pod, error) {
|
func (provider *Provider) ListAllPodsMatchingRegex(ctx context.Context, regex *regexp.Regexp, namespaces []string) ([]core.Pod, error) {
|
||||||
pods, err := provider.clientSet.CoreV1().Pods(mizu.K8sAllNamespaces).List(ctx, metav1.ListOptions{})
|
var pods []core.Pod
|
||||||
if err != nil {
|
for _, namespace := range namespaces {
|
||||||
return nil, err
|
namespacePods, err := provider.clientSet.CoreV1().Pods(namespace).List(ctx, metav1.ListOptions{})
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to get pods in ns: [%s], %w", namespace, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
pods = append(pods, namespacePods.Items...)
|
||||||
}
|
}
|
||||||
|
|
||||||
matchingPods := make([]core.Pod, 0)
|
matchingPods := make([]core.Pod, 0)
|
||||||
for _, pod := range pods.Items {
|
for _, pod := range pods {
|
||||||
if regex.MatchString(pod.Name) {
|
if regex.MatchString(pod.Name) {
|
||||||
matchingPods = append(matchingPods, pod)
|
matchingPods = append(matchingPods, pod)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return matchingPods, err
|
return matchingPods, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (provider *Provider) ListAllRunningPodsMatchingRegex(ctx context.Context, regex *regexp.Regexp, namespaces []string) ([]core.Pod, error) {
|
||||||
|
pods, err := provider.ListAllPodsMatchingRegex(ctx, regex, namespaces)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
matchingPods := make([]core.Pod, 0)
|
||||||
|
for _, pod := range pods {
|
||||||
|
if isPodRunning(&pod) {
|
||||||
|
matchingPods = append(matchingPods, pod)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return matchingPods, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (provider *Provider) GetPodLogs(namespace string, podName string, ctx context.Context) (string, error) {
|
||||||
|
podLogOpts := core.PodLogOptions{}
|
||||||
|
req := provider.clientSet.CoreV1().Pods(namespace).GetLogs(podName, &podLogOpts)
|
||||||
|
podLogs, err := req.Stream(ctx)
|
||||||
|
if err != nil {
|
||||||
|
return "", fmt.Errorf("error opening log stream on ns: %s, pod: %s, %w", namespace, podName, err)
|
||||||
|
}
|
||||||
|
defer podLogs.Close()
|
||||||
|
buf := new(bytes.Buffer)
|
||||||
|
if _, err = io.Copy(buf, podLogs); err != nil {
|
||||||
|
return "", fmt.Errorf("error copy information from podLogs to buf, ns: %s, pod: %s, %w", namespace, podName, err)
|
||||||
|
}
|
||||||
|
str := buf.String()
|
||||||
|
return str, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func getClientSet(config *restclient.Config) *kubernetes.Clientset {
|
func getClientSet(config *restclient.Config) *kubernetes.Clientset {
|
||||||
@@ -324,11 +737,16 @@ func getClientSet(config *restclient.Config) *kubernetes.Clientset {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func loadKubernetesConfiguration(kubeConfigPath string) clientcmd.ClientConfig {
|
func loadKubernetesConfiguration(kubeConfigPath string) clientcmd.ClientConfig {
|
||||||
|
if kubeConfigPath == "" {
|
||||||
|
kubeConfigPath = os.Getenv("KUBECONFIG")
|
||||||
|
}
|
||||||
|
|
||||||
if kubeConfigPath == "" {
|
if kubeConfigPath == "" {
|
||||||
home := homedir.HomeDir()
|
home := homedir.HomeDir()
|
||||||
kubeConfigPath = filepath.Join(home, ".kube", "config")
|
kubeConfigPath = filepath.Join(home, ".kube", "config")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
mizu.Log.Debugf("Using kube config %s", kubeConfigPath)
|
||||||
configPathList := filepath.SplitList(kubeConfigPath)
|
configPathList := filepath.SplitList(kubeConfigPath)
|
||||||
configLoadingRules := &clientcmd.ClientConfigLoadingRules{}
|
configLoadingRules := &clientcmd.ClientConfigLoadingRules{}
|
||||||
if len(configPathList) <= 1 {
|
if len(configPathList) <= 1 {
|
||||||
@@ -344,3 +762,7 @@ func loadKubernetesConfiguration(kubeConfigPath string) clientcmd.ClientConfig {
|
|||||||
},
|
},
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func isPodRunning(pod *core.Pod) bool {
|
||||||
|
return pod.Status.Phase == core.PodRunning
|
||||||
|
}
|
||||||
|
|||||||
65
cli/kubernetes/proxy.go
Normal file
65
cli/kubernetes/proxy.go
Normal file
@@ -0,0 +1,65 @@
|
|||||||
|
package kubernetes
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"github.com/up9inc/mizu/cli/mizu"
|
||||||
|
"k8s.io/kubectl/pkg/proxy"
|
||||||
|
"net"
|
||||||
|
"net/http"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
const k8sProxyApiPrefix = "/"
|
||||||
|
const mizuServicePort = 80
|
||||||
|
|
||||||
|
func StartProxy(kubernetesProvider *Provider, mizuPort uint16, mizuNamespace string, mizuServiceName string) error {
|
||||||
|
mizu.Log.Debugf("Starting proxy. namespace: [%v], service name: [%s], port: [%v]", mizuNamespace, mizuServiceName, mizuPort)
|
||||||
|
filter := &proxy.FilterServer{
|
||||||
|
AcceptPaths: proxy.MakeRegexpArrayOrDie(proxy.DefaultPathAcceptRE),
|
||||||
|
RejectPaths: proxy.MakeRegexpArrayOrDie(proxy.DefaultPathRejectRE),
|
||||||
|
AcceptHosts: proxy.MakeRegexpArrayOrDie(proxy.DefaultHostAcceptRE),
|
||||||
|
RejectMethods: proxy.MakeRegexpArrayOrDie(proxy.DefaultMethodRejectRE),
|
||||||
|
}
|
||||||
|
|
||||||
|
proxyHandler, err := proxy.NewProxyHandler(k8sProxyApiPrefix, filter, &kubernetesProvider.clientConfig, time.Second*2)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
mux := http.NewServeMux()
|
||||||
|
mux.Handle(k8sProxyApiPrefix, proxyHandler)
|
||||||
|
mux.Handle("/static/", getRerouteHttpHandlerMizuStatic(proxyHandler, mizuNamespace, mizuServiceName))
|
||||||
|
mux.Handle("/mizu/", getRerouteHttpHandlerMizuAPI(proxyHandler, mizuNamespace, mizuServiceName))
|
||||||
|
|
||||||
|
l, err := net.Listen("tcp", fmt.Sprintf("%s:%d", "127.0.0.1", int(mizuPort)))
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
server := http.Server{
|
||||||
|
Handler: mux,
|
||||||
|
}
|
||||||
|
return server.Serve(l)
|
||||||
|
}
|
||||||
|
|
||||||
|
func getMizuApiServerProxiedHostAndPath(mizuNamespace string, mizuServiceName string) string {
|
||||||
|
return fmt.Sprintf("/api/v1/namespaces/%s/services/%s:%d/proxy/", mizuNamespace, mizuServiceName, mizuServicePort)
|
||||||
|
}
|
||||||
|
|
||||||
|
func GetMizuApiServerProxiedHostAndPath(mizuPort uint16) string {
|
||||||
|
return fmt.Sprintf("localhost:%d/mizu", mizuPort)
|
||||||
|
}
|
||||||
|
|
||||||
|
func getRerouteHttpHandlerMizuAPI(proxyHandler http.Handler, mizuNamespace string, mizuServiceName string) http.Handler {
|
||||||
|
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
r.URL.Path = strings.Replace(r.URL.Path, "/mizu/", getMizuApiServerProxiedHostAndPath(mizuNamespace, mizuServiceName), 1)
|
||||||
|
proxyHandler.ServeHTTP(w, r)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func getRerouteHttpHandlerMizuStatic(proxyHandler http.Handler, mizuNamespace string, mizuServiceName string) http.Handler {
|
||||||
|
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
r.URL.Path = strings.Replace(r.URL.Path, "/static/", fmt.Sprintf("%s/static/", getMizuApiServerProxiedHostAndPath(mizuNamespace, mizuServiceName)), 1)
|
||||||
|
proxyHandler.ServeHTTP(w, r)
|
||||||
|
})
|
||||||
|
}
|
||||||
@@ -4,49 +4,64 @@ import (
|
|||||||
"context"
|
"context"
|
||||||
"errors"
|
"errors"
|
||||||
"regexp"
|
"regexp"
|
||||||
|
"sync"
|
||||||
|
|
||||||
corev1 "k8s.io/api/core/v1"
|
corev1 "k8s.io/api/core/v1"
|
||||||
"k8s.io/apimachinery/pkg/watch"
|
"k8s.io/apimachinery/pkg/watch"
|
||||||
)
|
)
|
||||||
|
|
||||||
func FilteredWatch(ctx context.Context, watcher watch.Interface, podFilter *regexp.Regexp) (chan *corev1.Pod, chan *corev1.Pod, chan *corev1.Pod, chan error) {
|
func FilteredWatch(ctx context.Context, kubernetesProvider *Provider, targetNamespaces []string, podFilter *regexp.Regexp) (chan *corev1.Pod, chan *corev1.Pod, chan *corev1.Pod, chan error) {
|
||||||
addedChan := make(chan *corev1.Pod)
|
addedChan := make(chan *corev1.Pod)
|
||||||
modifiedChan := make(chan *corev1.Pod)
|
modifiedChan := make(chan *corev1.Pod)
|
||||||
removedChan := make(chan *corev1.Pod)
|
removedChan := make(chan *corev1.Pod)
|
||||||
errorChan := make(chan error)
|
errorChan := make(chan error)
|
||||||
go func() {
|
|
||||||
for {
|
|
||||||
select {
|
|
||||||
case e := <-watcher.ResultChan():
|
|
||||||
|
|
||||||
if e.Object == nil {
|
var wg sync.WaitGroup
|
||||||
errorChan <- errors.New("kubernetes pod watch failed")
|
|
||||||
|
for _, targetNamespace := range targetNamespaces {
|
||||||
|
wg.Add(1)
|
||||||
|
|
||||||
|
go func(targetNamespace string) {
|
||||||
|
defer wg.Done()
|
||||||
|
watcher := kubernetesProvider.GetPodWatcher(ctx, targetNamespace)
|
||||||
|
|
||||||
|
for {
|
||||||
|
select {
|
||||||
|
case e := <-watcher.ResultChan():
|
||||||
|
if e.Object == nil {
|
||||||
|
errorChan <- errors.New("kubernetes pod watch failed")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
pod := e.Object.(*corev1.Pod)
|
||||||
|
|
||||||
|
if !podFilter.MatchString(pod.Name) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
switch e.Type {
|
||||||
|
case watch.Added:
|
||||||
|
addedChan <- pod
|
||||||
|
case watch.Modified:
|
||||||
|
modifiedChan <- pod
|
||||||
|
case watch.Deleted:
|
||||||
|
removedChan <- pod
|
||||||
|
}
|
||||||
|
case <-ctx.Done():
|
||||||
|
watcher.Stop()
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
pod := e.Object.(*corev1.Pod)
|
|
||||||
|
|
||||||
if !podFilter.MatchString(pod.Name) {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
switch e.Type {
|
|
||||||
case watch.Added:
|
|
||||||
addedChan <- pod
|
|
||||||
case watch.Modified:
|
|
||||||
modifiedChan <- pod
|
|
||||||
case watch.Deleted:
|
|
||||||
removedChan <- pod
|
|
||||||
}
|
|
||||||
case <-ctx.Done():
|
|
||||||
watcher.Stop()
|
|
||||||
close(addedChan)
|
|
||||||
close(modifiedChan)
|
|
||||||
close(removedChan)
|
|
||||||
close(errorChan)
|
|
||||||
return
|
|
||||||
}
|
}
|
||||||
}
|
}(targetNamespace)
|
||||||
|
}
|
||||||
|
|
||||||
|
go func() {
|
||||||
|
<-ctx.Done()
|
||||||
|
wg.Wait()
|
||||||
|
close(addedChan)
|
||||||
|
close(modifiedChan)
|
||||||
|
close(removedChan)
|
||||||
|
close(errorChan)
|
||||||
}()
|
}()
|
||||||
|
|
||||||
return addedChan, modifiedChan, removedChan, errorChan
|
return addedChan, modifiedChan, removedChan, errorChan
|
||||||
|
|||||||
@@ -1,7 +1,10 @@
|
|||||||
package main
|
package main
|
||||||
|
|
||||||
import "github.com/up9inc/mizu/cli/cmd"
|
import (
|
||||||
|
"github.com/up9inc/mizu/cli/cmd"
|
||||||
|
"github.com/up9inc/mizu/cli/goUtils"
|
||||||
|
)
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
cmd.Execute()
|
goUtils.HandleExcWrapper(cmd.Execute)
|
||||||
}
|
}
|
||||||
|
|||||||
279
cli/mizu/config.go
Normal file
279
cli/mizu/config.go
Normal file
@@ -0,0 +1,279 @@
|
|||||||
|
package mizu
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"io/ioutil"
|
||||||
|
"os"
|
||||||
|
"path"
|
||||||
|
"reflect"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/creasty/defaults"
|
||||||
|
"github.com/spf13/cobra"
|
||||||
|
"github.com/spf13/pflag"
|
||||||
|
"github.com/up9inc/mizu/cli/mizu/configStructs"
|
||||||
|
"github.com/up9inc/mizu/cli/uiUtils"
|
||||||
|
"gopkg.in/yaml.v3"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
Separator = "="
|
||||||
|
SetCommandName = "set"
|
||||||
|
)
|
||||||
|
|
||||||
|
var allowedSetFlags = []string{
|
||||||
|
AgentImageConfigName,
|
||||||
|
MizuResourcesNamespaceConfigName,
|
||||||
|
TelemetryConfigName,
|
||||||
|
DumpLogsConfigName,
|
||||||
|
KubeConfigPathName,
|
||||||
|
configStructs.AnalysisDestinationTapName,
|
||||||
|
configStructs.SleepIntervalSecTapName,
|
||||||
|
}
|
||||||
|
|
||||||
|
var Config = ConfigStruct{}
|
||||||
|
|
||||||
|
func (config *ConfigStruct) Validate() error {
|
||||||
|
if config.IsNsRestrictedMode() {
|
||||||
|
if config.Tap.AllNamespaces || len(config.Tap.Namespaces) != 1 || config.Tap.Namespaces[0] != config.MizuResourcesNamespace {
|
||||||
|
return fmt.Errorf("Not supported mode. Mizu can't resolve IPs in other namespaces when running in namespace restricted mode.\n"+
|
||||||
|
"You can use the same namespace for --%s and --%s", configStructs.NamespacesTapName, MizuResourcesNamespaceConfigName)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func InitConfig(cmd *cobra.Command) error {
|
||||||
|
if err := defaults.Set(&Config); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := mergeConfigFile(); err != nil {
|
||||||
|
return fmt.Errorf("invalid config %w\n"+
|
||||||
|
"you can regenerate the file using `mizu config -r` or just remove it %v", err, GetConfigFilePath())
|
||||||
|
}
|
||||||
|
|
||||||
|
cmd.Flags().Visit(initFlag)
|
||||||
|
|
||||||
|
finalConfigPrettified, _ := uiUtils.PrettyJson(Config)
|
||||||
|
Log.Debugf("Init config finished\n Final config: %v", finalConfigPrettified)
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func GetConfigWithDefaults() (string, error) {
|
||||||
|
defaultConf := ConfigStruct{}
|
||||||
|
if err := defaults.Set(&defaultConf); err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
return uiUtils.PrettyYaml(defaultConf)
|
||||||
|
}
|
||||||
|
|
||||||
|
func GetConfigFilePath() string {
|
||||||
|
return path.Join(GetMizuFolderPath(), "config.yaml")
|
||||||
|
}
|
||||||
|
|
||||||
|
func mergeConfigFile() error {
|
||||||
|
reader, openErr := os.Open(GetConfigFilePath())
|
||||||
|
if openErr != nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
buf, readErr := ioutil.ReadAll(reader)
|
||||||
|
if readErr != nil {
|
||||||
|
return readErr
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := yaml.Unmarshal(buf, &Config); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
Log.Debugf("Found config file, merged to default options")
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func initFlag(f *pflag.Flag) {
|
||||||
|
configElem := reflect.ValueOf(&Config).Elem()
|
||||||
|
|
||||||
|
sliceValue, isSliceValue := f.Value.(pflag.SliceValue)
|
||||||
|
if !isSliceValue {
|
||||||
|
mergeFlagValue(configElem, f.Name, f.Value.String())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if f.Name == SetCommandName {
|
||||||
|
mergeSetFlag(sliceValue.GetSlice())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
mergeFlagValues(configElem, f.Name, sliceValue.GetSlice())
|
||||||
|
}
|
||||||
|
|
||||||
|
func mergeSetFlag(setValues []string) {
|
||||||
|
configElem := reflect.ValueOf(&Config).Elem()
|
||||||
|
|
||||||
|
for _, setValue := range setValues {
|
||||||
|
if !strings.Contains(setValue, Separator) {
|
||||||
|
Log.Warningf(uiUtils.Warning, fmt.Sprintf("Ignoring set argument %s (set argument format: <flag name>=<flag value>)", setValue))
|
||||||
|
}
|
||||||
|
|
||||||
|
split := strings.SplitN(setValue, Separator, 2)
|
||||||
|
if len(split) != 2 {
|
||||||
|
Log.Warningf(uiUtils.Warning, fmt.Sprintf("Ignoring set argument %s (set argument format: <flag name>=<flag value>)", setValue))
|
||||||
|
}
|
||||||
|
|
||||||
|
argumentKey, argumentValue := split[0], split[1]
|
||||||
|
|
||||||
|
if !Contains(allowedSetFlags, argumentKey) {
|
||||||
|
Log.Warningf(uiUtils.Warning, fmt.Sprintf("Ignoring set argument %s, flag name must be one of the following: \"%s\"", setValue, strings.Join(allowedSetFlags, "\", \"")))
|
||||||
|
}
|
||||||
|
|
||||||
|
mergeFlagValue(configElem, argumentKey, argumentValue)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func mergeFlagValue(currentElem reflect.Value, flagKey string, flagValue string) {
|
||||||
|
for i := 0; i < currentElem.NumField(); i++ {
|
||||||
|
currentField := currentElem.Type().Field(i)
|
||||||
|
currentFieldByName := currentElem.FieldByName(currentField.Name)
|
||||||
|
|
||||||
|
if currentField.Type.Kind() == reflect.Struct {
|
||||||
|
mergeFlagValue(currentFieldByName, flagKey, flagValue)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if currentField.Tag.Get("yaml") != flagKey {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
flagValueKind := currentField.Type.Kind()
|
||||||
|
|
||||||
|
parsedValue, err := getParsedValue(flagValueKind, flagValue)
|
||||||
|
if err != nil {
|
||||||
|
Log.Warningf(uiUtils.Red, fmt.Sprintf("Invalid value %v for flag name %s, expected %s", flagValue, flagKey, flagValueKind))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
currentFieldByName.Set(parsedValue)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func mergeFlagValues(currentElem reflect.Value, flagKey string, flagValues []string) {
|
||||||
|
for i := 0; i < currentElem.NumField(); i++ {
|
||||||
|
currentField := currentElem.Type().Field(i)
|
||||||
|
currentFieldByName := currentElem.FieldByName(currentField.Name)
|
||||||
|
|
||||||
|
if currentField.Type.Kind() == reflect.Struct {
|
||||||
|
mergeFlagValues(currentFieldByName, flagKey, flagValues)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if currentField.Tag.Get("yaml") != flagKey {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
flagValueKind := currentField.Type.Elem().Kind()
|
||||||
|
|
||||||
|
parsedValues := reflect.MakeSlice(reflect.SliceOf(currentField.Type.Elem()), 0, 0)
|
||||||
|
for _, flagValue := range flagValues {
|
||||||
|
parsedValue, err := getParsedValue(flagValueKind, flagValue)
|
||||||
|
if err != nil {
|
||||||
|
Log.Warningf(uiUtils.Red, fmt.Sprintf("Invalid value %v for flag name %s, expected %s", flagValue, flagKey, flagValueKind))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
parsedValues = reflect.Append(parsedValues, parsedValue)
|
||||||
|
}
|
||||||
|
|
||||||
|
currentFieldByName.Set(parsedValues)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func getParsedValue(kind reflect.Kind, value string) (reflect.Value, error) {
|
||||||
|
switch kind {
|
||||||
|
case reflect.String:
|
||||||
|
return reflect.ValueOf(value), nil
|
||||||
|
case reflect.Bool:
|
||||||
|
boolArgumentValue, err := strconv.ParseBool(value)
|
||||||
|
if err != nil {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
return reflect.ValueOf(boolArgumentValue), nil
|
||||||
|
case reflect.Int:
|
||||||
|
intArgumentValue, err := strconv.ParseInt(value, 10, 64)
|
||||||
|
if err != nil {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
return reflect.ValueOf(int(intArgumentValue)), nil
|
||||||
|
case reflect.Int8:
|
||||||
|
intArgumentValue, err := strconv.ParseInt(value, 10, 8)
|
||||||
|
if err != nil {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
return reflect.ValueOf(int8(intArgumentValue)), nil
|
||||||
|
case reflect.Int16:
|
||||||
|
intArgumentValue, err := strconv.ParseInt(value, 10, 16)
|
||||||
|
if err != nil {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
return reflect.ValueOf(int16(intArgumentValue)), nil
|
||||||
|
case reflect.Int32:
|
||||||
|
intArgumentValue, err := strconv.ParseInt(value, 10, 32)
|
||||||
|
if err != nil {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
return reflect.ValueOf(int32(intArgumentValue)), nil
|
||||||
|
case reflect.Int64:
|
||||||
|
intArgumentValue, err := strconv.ParseInt(value, 10, 64)
|
||||||
|
if err != nil {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
return reflect.ValueOf(intArgumentValue), nil
|
||||||
|
case reflect.Uint:
|
||||||
|
uintArgumentValue, err := strconv.ParseUint(value, 10, 64)
|
||||||
|
if err != nil {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
return reflect.ValueOf(uint(uintArgumentValue)), nil
|
||||||
|
case reflect.Uint8:
|
||||||
|
uintArgumentValue, err := strconv.ParseUint(value, 10, 8)
|
||||||
|
if err != nil {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
return reflect.ValueOf(uint8(uintArgumentValue)), nil
|
||||||
|
case reflect.Uint16:
|
||||||
|
uintArgumentValue, err := strconv.ParseUint(value, 10, 16)
|
||||||
|
if err != nil {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
return reflect.ValueOf(uint16(uintArgumentValue)), nil
|
||||||
|
case reflect.Uint32:
|
||||||
|
uintArgumentValue, err := strconv.ParseUint(value, 10, 32)
|
||||||
|
if err != nil {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
return reflect.ValueOf(uint32(uintArgumentValue)), nil
|
||||||
|
case reflect.Uint64:
|
||||||
|
uintArgumentValue, err := strconv.ParseUint(value, 10, 64)
|
||||||
|
if err != nil {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
return reflect.ValueOf(uintArgumentValue), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return reflect.ValueOf(nil), errors.New("value to parse does not match type")
|
||||||
|
}
|
||||||
35
cli/mizu/configStruct.go
Normal file
35
cli/mizu/configStruct.go
Normal file
@@ -0,0 +1,35 @@
|
|||||||
|
package mizu
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"github.com/up9inc/mizu/cli/mizu/configStructs"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
AgentImageConfigName = "agent-image"
|
||||||
|
MizuResourcesNamespaceConfigName = "mizu-resources-namespace"
|
||||||
|
TelemetryConfigName = "telemetry"
|
||||||
|
DumpLogsConfigName = "dump-logs"
|
||||||
|
KubeConfigPathName = "kube-config-path"
|
||||||
|
)
|
||||||
|
|
||||||
|
type ConfigStruct struct {
|
||||||
|
Tap configStructs.TapConfig `yaml:"tap"`
|
||||||
|
Fetch configStructs.FetchConfig `yaml:"fetch"`
|
||||||
|
Version configStructs.VersionConfig `yaml:"version"`
|
||||||
|
View configStructs.ViewConfig `yaml:"view"`
|
||||||
|
AgentImage string `yaml:"agent-image"`
|
||||||
|
MizuResourcesNamespace string `yaml:"mizu-resources-namespace" default:"mizu"`
|
||||||
|
Telemetry bool `yaml:"telemetry" default:"true"`
|
||||||
|
DumpLogs bool `yaml:"dump-logs" default:"false"`
|
||||||
|
KubeConfigPath string `yaml:"kube-config-path" default:""`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (config *ConfigStruct) SetDefaults() {
|
||||||
|
config.AgentImage = fmt.Sprintf("gcr.io/up9-docker-hub/mizu/%s:%s", Branch, SemVer)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (config *ConfigStruct) IsNsRestrictedMode() bool {
|
||||||
|
return config.MizuResourcesNamespace != "mizu" // Notice "mizu" string must match the default MizuResourcesNamespace
|
||||||
|
}
|
||||||
15
cli/mizu/configStructs/fetchConfig.go
Normal file
15
cli/mizu/configStructs/fetchConfig.go
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
package configStructs
|
||||||
|
|
||||||
|
const (
|
||||||
|
DirectoryFetchName = "directory"
|
||||||
|
FromTimestampFetchName = "from"
|
||||||
|
ToTimestampFetchName = "to"
|
||||||
|
MizuPortFetchName = "port"
|
||||||
|
)
|
||||||
|
|
||||||
|
type FetchConfig struct {
|
||||||
|
Directory string `yaml:"directory" default:"."`
|
||||||
|
FromTimestamp int `yaml:"from" default:"0"`
|
||||||
|
ToTimestamp int `yaml:"to" default:"0"`
|
||||||
|
MizuPort uint16 `yaml:"port" default:"8899"`
|
||||||
|
}
|
||||||
81
cli/mizu/configStructs/tapConfig.go
Normal file
81
cli/mizu/configStructs/tapConfig.go
Normal file
@@ -0,0 +1,81 @@
|
|||||||
|
package configStructs
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"regexp"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/up9inc/mizu/shared/units"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
AnalysisDestinationTapName = "dest"
|
||||||
|
SleepIntervalSecTapName = "upload-interval"
|
||||||
|
GuiPortTapName = "gui-port"
|
||||||
|
NamespacesTapName = "namespaces"
|
||||||
|
AnalysisTapName = "analysis"
|
||||||
|
AllNamespacesTapName = "all-namespaces"
|
||||||
|
PlainTextFilterRegexesTapName = "regex-masking"
|
||||||
|
HideHealthChecksTapName = "hide-healthchecks"
|
||||||
|
DisableRedactionTapName = "no-redact"
|
||||||
|
HumanMaxEntriesDBSizeTapName = "max-entries-db-size"
|
||||||
|
DirectionTapName = "direction"
|
||||||
|
DryRunTapName = "dry-run"
|
||||||
|
EnforcePolicyFile = "test-rules"
|
||||||
|
)
|
||||||
|
|
||||||
|
type TapConfig struct {
|
||||||
|
AnalysisDestination string `yaml:"dest" default:"up9.app"`
|
||||||
|
SleepIntervalSec int `yaml:"upload-interval" default:"10"`
|
||||||
|
PodRegexStr string `yaml:"regex" default:".*"`
|
||||||
|
GuiPort uint16 `yaml:"gui-port" default:"8899"`
|
||||||
|
Namespaces []string `yaml:"namespaces"`
|
||||||
|
Analysis bool `yaml:"analysis" default:"false"`
|
||||||
|
AllNamespaces bool `yaml:"all-namespaces" default:"false"`
|
||||||
|
PlainTextFilterRegexes []string `yaml:"regex-masking"`
|
||||||
|
HideHealthChecks bool `yaml:"hide-healthchecks" default:"false"`
|
||||||
|
DisableRedaction bool `yaml:"no-redact" default:"false"`
|
||||||
|
HumanMaxEntriesDBSize string `yaml:"max-entries-db-size" default:"200MB"`
|
||||||
|
Direction string `yaml:"direction" default:"in"`
|
||||||
|
DryRun bool `yaml:"dry-run" default:"false"`
|
||||||
|
EnforcePolicyFile string `yaml:"test-rules"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (config *TapConfig) PodRegex() *regexp.Regexp {
|
||||||
|
podRegex, _ := regexp.Compile(config.PodRegexStr)
|
||||||
|
return podRegex
|
||||||
|
}
|
||||||
|
|
||||||
|
func (config *TapConfig) TapOutgoing() bool {
|
||||||
|
directionLowerCase := strings.ToLower(config.Direction)
|
||||||
|
if directionLowerCase == "any" {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
func (config *TapConfig) MaxEntriesDBSizeBytes() int64 {
|
||||||
|
maxEntriesDBSizeBytes, _ := units.HumanReadableToBytes(config.HumanMaxEntriesDBSize)
|
||||||
|
return maxEntriesDBSizeBytes
|
||||||
|
}
|
||||||
|
|
||||||
|
func (config *TapConfig) Validate() error {
|
||||||
|
_, compileErr := regexp.Compile(config.PodRegexStr)
|
||||||
|
if compileErr != nil {
|
||||||
|
return errors.New(fmt.Sprintf("%s is not a valid regex %s", config.PodRegexStr, compileErr))
|
||||||
|
}
|
||||||
|
|
||||||
|
_, parseHumanDataSizeErr := units.HumanReadableToBytes(config.HumanMaxEntriesDBSize)
|
||||||
|
if parseHumanDataSizeErr != nil {
|
||||||
|
return errors.New(fmt.Sprintf("Could not parse --%s value %s", HumanMaxEntriesDBSizeTapName, config.HumanMaxEntriesDBSize))
|
||||||
|
}
|
||||||
|
|
||||||
|
directionLowerCase := strings.ToLower(config.Direction)
|
||||||
|
if directionLowerCase != "any" && directionLowerCase != "in" {
|
||||||
|
return errors.New(fmt.Sprintf("%s is not a valid value for flag --%s. Acceptable values are in/any.", config.Direction, DirectionTapName))
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
9
cli/mizu/configStructs/versionConfig.go
Normal file
9
cli/mizu/configStructs/versionConfig.go
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
package configStructs
|
||||||
|
|
||||||
|
const (
|
||||||
|
DebugInfoVersionName = "debug"
|
||||||
|
)
|
||||||
|
|
||||||
|
type VersionConfig struct {
|
||||||
|
DebugInfo bool `yaml:"debug" default:"false"`
|
||||||
|
}
|
||||||
11
cli/mizu/configStructs/viewConfig.go
Normal file
11
cli/mizu/configStructs/viewConfig.go
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
package configStructs
|
||||||
|
|
||||||
|
const (
|
||||||
|
GuiPortViewName = "gui-port"
|
||||||
|
KubeConfigPathViewName = "kube-config"
|
||||||
|
)
|
||||||
|
|
||||||
|
type ViewConfig struct {
|
||||||
|
GuiPort uint16 `yaml:"gui-port" default:"8899"`
|
||||||
|
KubeConfigPath string `yaml:"kube-config"`
|
||||||
|
}
|
||||||
@@ -1,15 +1,36 @@
|
|||||||
package mizu
|
package mizu
|
||||||
|
|
||||||
|
import (
|
||||||
|
"os"
|
||||||
|
"path"
|
||||||
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
Version = "v0.0.1"
|
SemVer = "0.0.1"
|
||||||
Branch = "develop"
|
Branch = "develop"
|
||||||
GitCommitHash = "" // this var is overridden using ldflags in makefile when building
|
GitCommitHash = "" // this var is overridden using ldflags in makefile when building
|
||||||
|
BuildTimestamp = "" // this var is overridden using ldflags in makefile when building
|
||||||
|
RBACVersion = "v1"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
ResourcesNamespace = "default"
|
MizuResourcesPrefix = "mizu-"
|
||||||
TapperDaemonSetName = "mizu-tapper-daemon-set"
|
ApiServerPodName = MizuResourcesPrefix + "api-server"
|
||||||
AggregatorPodName = "mizu-collector"
|
ClusterRoleBindingName = MizuResourcesPrefix + "cluster-role-binding"
|
||||||
TapperPodName = "mizu-tapper"
|
ClusterRoleName = MizuResourcesPrefix + "cluster-role"
|
||||||
K8sAllNamespaces = ""
|
K8sAllNamespaces = ""
|
||||||
|
RoleBindingName = MizuResourcesPrefix + "role-binding"
|
||||||
|
RoleName = MizuResourcesPrefix + "role"
|
||||||
|
ServiceAccountName = MizuResourcesPrefix + "service-account"
|
||||||
|
TapperDaemonSetName = MizuResourcesPrefix + "tapper-daemon-set"
|
||||||
|
TapperPodName = MizuResourcesPrefix + "tapper"
|
||||||
|
ConfigMapName = MizuResourcesPrefix + "policy"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
func GetMizuFolderPath() string {
|
||||||
|
home, homeDirErr := os.UserHomeDir()
|
||||||
|
if homeDirErr != nil {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
return path.Join(home, ".mizu")
|
||||||
|
}
|
||||||
|
|||||||
38
cli/mizu/logger.go
Normal file
38
cli/mizu/logger.go
Normal file
@@ -0,0 +1,38 @@
|
|||||||
|
package mizu
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/op/go-logging"
|
||||||
|
"os"
|
||||||
|
"path"
|
||||||
|
)
|
||||||
|
|
||||||
|
var Log = logging.MustGetLogger("mizu_cli")
|
||||||
|
|
||||||
|
var format = logging.MustStringFormatter(
|
||||||
|
`%{time} %{level:.5s} ▶ %{pid} %{shortfile} %{shortfunc} ▶ %{message}`,
|
||||||
|
)
|
||||||
|
|
||||||
|
func GetLogFilePath() string {
|
||||||
|
return path.Join(GetMizuFolderPath(), "mizu_cli.log")
|
||||||
|
}
|
||||||
|
|
||||||
|
func InitLogger() {
|
||||||
|
logPath := GetLogFilePath()
|
||||||
|
f, err := os.OpenFile(logPath, os.O_RDWR|os.O_CREATE|os.O_APPEND, 0666)
|
||||||
|
if err != nil {
|
||||||
|
Log.Infof("Failed to open mizu log file: %v, err %v", logPath, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
fileLog := logging.NewLogBackend(f, "", 0)
|
||||||
|
consoleLog := logging.NewLogBackend(os.Stderr, "", 0)
|
||||||
|
|
||||||
|
backend2Formatter := logging.NewBackendFormatter(fileLog, format)
|
||||||
|
|
||||||
|
backend1Leveled := logging.AddModuleLevel(consoleLog)
|
||||||
|
backend1Leveled.SetLevel(logging.INFO, "")
|
||||||
|
|
||||||
|
logging.SetBackend(backend1Leveled, backend2Formatter)
|
||||||
|
|
||||||
|
Log.Debugf("\n\n\n")
|
||||||
|
Log.Debugf("Running mizu version %v", SemVer)
|
||||||
|
}
|
||||||
11
cli/mizu/sliceUtils.go
Normal file
11
cli/mizu/sliceUtils.go
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
package mizu
|
||||||
|
|
||||||
|
func Contains(slice []string, containsValue string) bool {
|
||||||
|
for _, sliceValue := range slice {
|
||||||
|
if sliceValue == containsValue {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false
|
||||||
|
}
|
||||||
36
cli/mizu/telemetry.go
Normal file
36
cli/mizu/telemetry.go
Normal file
@@ -0,0 +1,36 @@
|
|||||||
|
package mizu
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"net/http"
|
||||||
|
)
|
||||||
|
|
||||||
|
const telemetryUrl = "https://us-east4-up9-prod.cloudfunctions.net/mizu-telemetry"
|
||||||
|
|
||||||
|
func ReportRun(cmd string, args interface{}) {
|
||||||
|
if !Config.Telemetry {
|
||||||
|
Log.Debugf("not reporting due to config value")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
argsBytes, _ := json.Marshal(args)
|
||||||
|
argsMap := map[string]string{
|
||||||
|
"telemetry_type": "execution",
|
||||||
|
"cmd": cmd,
|
||||||
|
"args": string(argsBytes),
|
||||||
|
"component": "mizu_cli",
|
||||||
|
"BuildTimestamp": BuildTimestamp,
|
||||||
|
"Branch": Branch,
|
||||||
|
"version": SemVer}
|
||||||
|
argsMap["message"] = fmt.Sprintf("mizu %v - %v", argsMap["cmd"], string(argsBytes))
|
||||||
|
|
||||||
|
jsonValue, _ := json.Marshal(argsMap)
|
||||||
|
|
||||||
|
if resp, err := http.Post(telemetryUrl, "application/json", bytes.NewBuffer(jsonValue)); err != nil {
|
||||||
|
Log.Debugf("error sending telemetry err: %v, response %v", err, resp)
|
||||||
|
} else {
|
||||||
|
Log.Debugf("Successfully reported telemetry")
|
||||||
|
}
|
||||||
|
}
|
||||||
93
cli/mizu/versionCheck.go
Normal file
93
cli/mizu/versionCheck.go
Normal file
@@ -0,0 +1,93 @@
|
|||||||
|
package mizu
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"io/ioutil"
|
||||||
|
"net/http"
|
||||||
|
"net/url"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/google/go-github/v37/github"
|
||||||
|
"github.com/up9inc/mizu/cli/uiUtils"
|
||||||
|
"github.com/up9inc/mizu/shared"
|
||||||
|
"github.com/up9inc/mizu/shared/semver"
|
||||||
|
)
|
||||||
|
|
||||||
|
func getApiVersion(port uint16) (string, error) {
|
||||||
|
versionUrl, _ := url.Parse(fmt.Sprintf("http://localhost:%d/mizu/metadata/version", port))
|
||||||
|
req := &http.Request{
|
||||||
|
Method: http.MethodGet,
|
||||||
|
URL: versionUrl,
|
||||||
|
}
|
||||||
|
statusResp, err := http.DefaultClient.Do(req)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
defer statusResp.Body.Close()
|
||||||
|
|
||||||
|
versionResponse := &shared.VersionResponse{}
|
||||||
|
if err := json.NewDecoder(statusResp.Body).Decode(&versionResponse); err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
return versionResponse.SemVer, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func CheckVersionCompatibility(port uint16) (bool, error) {
|
||||||
|
apiSemVer, err := getApiVersion(port)
|
||||||
|
if err != nil {
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if semver.SemVersion(apiSemVer).Major() == semver.SemVersion(SemVer).Major() &&
|
||||||
|
semver.SemVersion(apiSemVer).Minor() == semver.SemVersion(SemVer).Minor() {
|
||||||
|
return true, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
Log.Errorf(uiUtils.Red, fmt.Sprintf("cli version (%s) is not compatible with api version (%s)", SemVer, apiSemVer))
|
||||||
|
return false, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func CheckNewerVersion() {
|
||||||
|
Log.Debugf("Checking for newer version...")
|
||||||
|
start := time.Now()
|
||||||
|
client := github.NewClient(nil)
|
||||||
|
latestRelease, _, err := client.Repositories.GetLatestRelease(context.Background(), "up9inc", "mizu")
|
||||||
|
if err != nil {
|
||||||
|
Log.Debugf("[ERROR] Failed to get latest release")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
versionFileUrl := ""
|
||||||
|
for _, asset := range latestRelease.Assets {
|
||||||
|
if *asset.Name == "version.txt" {
|
||||||
|
versionFileUrl = *asset.BrowserDownloadURL
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if versionFileUrl == "" {
|
||||||
|
Log.Debugf("[ERROR] Version file not found in the latest release")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
res, err := http.Get(versionFileUrl)
|
||||||
|
if err != nil {
|
||||||
|
Log.Debugf("[ERROR] Failed to get the version file %v", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
data, err := ioutil.ReadAll(res.Body)
|
||||||
|
res.Body.Close()
|
||||||
|
if err != nil {
|
||||||
|
Log.Debugf("[ERROR] Failed to read the version file -> %v", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
gitHubVersion := string(data)
|
||||||
|
gitHubVersion = gitHubVersion[:len(gitHubVersion)-1]
|
||||||
|
Log.Debugf("Finished version validation, took %v", time.Since(start))
|
||||||
|
if SemVer < gitHubVersion {
|
||||||
|
Log.Infof(uiUtils.Yellow, fmt.Sprintf("Update available! %v -> %v (%v)", SemVer, gitHubVersion, *latestRelease.HTMLURL))
|
||||||
|
}
|
||||||
|
}
|
||||||
15
cli/uiUtils/colors.go
Normal file
15
cli/uiUtils/colors.go
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
package uiUtils
|
||||||
|
|
||||||
|
|
||||||
|
const (
|
||||||
|
Black = "\033[1;30m%s\033[0m"
|
||||||
|
Red = "\033[1;31m%s\033[0m"
|
||||||
|
Green = "\033[1;32m%s\033[0m"
|
||||||
|
Yellow = "\033[1;33m%s\033[0m"
|
||||||
|
Purple = "\033[1;34m%s\033[0m"
|
||||||
|
Magenta = "\033[1;35m%s\033[0m"
|
||||||
|
Teal = "\033[1;36m%s\033[0m"
|
||||||
|
White = "\033[1;37m%s\033[0m"
|
||||||
|
Error = Red
|
||||||
|
Warning = Yellow
|
||||||
|
)
|
||||||
25
cli/uiUtils/confirmation.go
Normal file
25
cli/uiUtils/confirmation.go
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
package uiUtils
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bufio"
|
||||||
|
"fmt"
|
||||||
|
"log"
|
||||||
|
"os"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
func AskForConfirmation(s string) bool {
|
||||||
|
reader := bufio.NewReader(os.Stdin)
|
||||||
|
|
||||||
|
fmt.Printf(Magenta, s)
|
||||||
|
|
||||||
|
response, err := reader.ReadString('\n')
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
response = strings.ToLower(strings.TrimSpace(response))
|
||||||
|
if response == "" || response == "y" || response == "yes" {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user