mirror of
https://github.com/kubeshark/kubeshark.git
synced 2026-02-17 03:19:54 +00:00
Compare commits
374 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
37459cc03f | ||
|
|
7a6856a6e9 | ||
|
|
4ddcb7cb92 | ||
|
|
d782380dc5 | ||
|
|
f9325792d2 | ||
|
|
d30be4c1f0 | ||
|
|
0286e0140e | ||
|
|
ef355331ce | ||
|
|
aed86bb3bc | ||
|
|
c55adcd357 | ||
|
|
bcea6cdc49 | ||
|
|
20ffe2a2de | ||
|
|
677669fbb9 | ||
|
|
f5a0cb01a4 | ||
|
|
1a2697dd0d | ||
|
|
09111aeb1d | ||
|
|
83fe96259b | ||
|
|
4003718011 | ||
|
|
5ab763b949 | ||
|
|
4ca9606148 | ||
|
|
2638672603 | ||
|
|
a702f0f93a | ||
|
|
4fde6e9dac | ||
|
|
18d90cdf36 | ||
|
|
9c665e664b | ||
|
|
54ea2afe71 | ||
|
|
50c89e6245 | ||
|
|
676e50b0b1 | ||
|
|
6bab381280 | ||
|
|
27dee4e09b | ||
|
|
b31af7214b | ||
|
|
d5fd2ff1da | ||
|
|
7477f867f9 | ||
|
|
cc4638afe6 | ||
|
|
acf3894824 | ||
|
|
f973748c61 | ||
|
|
6235217ead | ||
|
|
bb1ac08c94 | ||
|
|
1374a7982d | ||
|
|
1980e9e5ed | ||
|
|
92ea8cdb2c | ||
|
|
0f7bd9ea02 | ||
|
|
a57d078b04 | ||
|
|
c39576b559 | ||
|
|
52f1eae341 | ||
|
|
e8d7cd0751 | ||
|
|
dd8412bf62 | ||
|
|
796457d876 | ||
|
|
5130b09c40 | ||
|
|
c8a3033f87 | ||
|
|
6b4bcc8abd | ||
|
|
5ca3107422 | ||
|
|
ce477095fd | ||
|
|
5fed5808d2 | ||
|
|
5c59cd643a | ||
|
|
aae03c52e9 | ||
|
|
dacdb69164 | ||
|
|
6ff5166f21 | ||
|
|
907b8032f2 | ||
|
|
e15eb71b77 | ||
|
|
ae1bcf4c0c | ||
|
|
20d69228d3 | ||
|
|
59fbe4c479 | ||
|
|
4db8e8902b | ||
|
|
f5bacbd1ea | ||
|
|
b94098fea6 | ||
|
|
92c7e2b91d | ||
|
|
d97d481392 | ||
|
|
8963630e9e | ||
|
|
610b9efdb0 | ||
|
|
0e5611b7e9 | ||
|
|
26a9c31d1e | ||
|
|
68c4ee9a4f | ||
|
|
bfbbc27e62 | ||
|
|
e2df973fe6 | ||
|
|
656809512b | ||
|
|
b96542a8ed | ||
|
|
a55f51f0e7 | ||
|
|
f102079e3c | ||
|
|
80e881fee2 | ||
|
|
1ba444dba1 | ||
|
|
0b7d535a81 | ||
|
|
9d0c2a693e | ||
|
|
2b2c7687a1 | ||
|
|
4708998f54 | ||
|
|
7570df3828 | ||
|
|
44c8908358 | ||
|
|
0ca5482946 | ||
|
|
c20f74f582 | ||
|
|
a2eff2654d | ||
|
|
9771d689ca | ||
|
|
5a044875d3 | ||
|
|
c49c344c2a | ||
|
|
e3e9681110 | ||
|
|
adf2274213 | ||
|
|
cb5344090a | ||
|
|
2110afc514 | ||
|
|
2c4a5d06ab | ||
|
|
14650aa3f4 | ||
|
|
5d36d9184d | ||
|
|
63122cb0a7 | ||
|
|
b88bdb90f6 | ||
|
|
833d08bb40 | ||
|
|
8e20ca797b | ||
|
|
bf68689212 | ||
|
|
dbc18b9648 | ||
|
|
282baee881 | ||
|
|
02b2cbaa03 | ||
|
|
2834ae1e85 | ||
|
|
9c45166075 | ||
|
|
482036182b | ||
|
|
f535719ddd | ||
|
|
da2aaa9bd8 | ||
|
|
9ada330fcf | ||
|
|
84f7107a33 | ||
|
|
de046c65b0 | ||
|
|
b039c2abad | ||
|
|
1e1b5f0c0f | ||
|
|
15895d5947 | ||
|
|
2da7c0f0ed | ||
|
|
06785ec877 | ||
|
|
cfe9e863b7 | ||
|
|
fd97a09624 | ||
|
|
52ce6044ea | ||
|
|
3a83531590 | ||
|
|
e358aa4c8f | ||
|
|
03b1313a9f | ||
|
|
32dfe40e18 | ||
|
|
12aaa762f6 | ||
|
|
a75bac181d | ||
|
|
2d78785558 | ||
|
|
cba0c682e5 | ||
|
|
791f762803 | ||
|
|
d316589bda | ||
|
|
36828bcc1d | ||
|
|
23332639d0 | ||
|
|
3b69508581 | ||
|
|
397d3931ad | ||
|
|
4de795e463 | ||
|
|
ab029f4394 | ||
|
|
a06d5cfbde | ||
|
|
b565492eba | ||
|
|
f266f32fea | ||
|
|
90c210452d | ||
|
|
0a915b3fe7 | ||
|
|
a830bbe023 | ||
|
|
f1ba397543 | ||
|
|
4e17ac5654 | ||
|
|
d274db2d87 | ||
|
|
0a2aacfb02 | ||
|
|
3c64c1c7ca | ||
|
|
005f000ef6 | ||
|
|
1ef3778051 | ||
|
|
9f1e311689 | ||
|
|
9aaf18842b | ||
|
|
880842c39f | ||
|
|
296e1bb667 | ||
|
|
2910611111 | ||
|
|
c47959dbd8 | ||
|
|
af557f7052 | ||
|
|
b745f65971 | ||
|
|
873f252544 | ||
|
|
9696ad9bad | ||
|
|
a1bda0a6c3 | ||
|
|
a62842ac9f | ||
|
|
e667597e6e | ||
|
|
86240e4121 | ||
|
|
b0c8c0c192 | ||
|
|
1c18eb1b84 | ||
|
|
01d6005a7b | ||
|
|
4c97316c02 | ||
|
|
d66c7445e6 | ||
|
|
12ca3d8779 | ||
|
|
02a125bb86 | ||
|
|
08d7fa988e | ||
|
|
b1ad2efb96 | ||
|
|
ed7b754eca | ||
|
|
c026656b5e | ||
|
|
6caa94f08f | ||
|
|
b77ea63f42 | ||
|
|
2635964a28 | ||
|
|
a16faca5fb | ||
|
|
8cf6f56a3c | ||
|
|
a849aae94c | ||
|
|
8118569460 | ||
|
|
2e75834dd0 | ||
|
|
dd53a36d5f | ||
|
|
ad78f1dcd7 | ||
|
|
a13fec3dae | ||
|
|
bb85312b9f | ||
|
|
18be46809e | ||
|
|
b7f7daa05c | ||
|
|
95d2a868e1 | ||
|
|
36077a9985 | ||
|
|
51e0dd8ba9 | ||
|
|
7f265dc4c5 | ||
|
|
1c75ce314b | ||
|
|
89836d8d75 | ||
|
|
763f72a640 | ||
|
|
a6ec246dd1 | ||
|
|
3e30815fb4 | ||
|
|
a6bf39fad5 | ||
|
|
58a1eac247 | ||
|
|
ad574956df | ||
|
|
618cb3a409 | ||
|
|
2582b7a65c | ||
|
|
4641ee7c54 | ||
|
|
14a5fe11e7 | ||
|
|
6909e6e657 | ||
|
|
3e132905ce | ||
|
|
ea0b3fb34e | ||
|
|
5382a52025 | ||
|
|
ed8d36cdad | ||
|
|
1ee8fb6292 | ||
|
|
eb61831a2c | ||
|
|
81c25f0bd4 | ||
|
|
b970640ebc | ||
|
|
d2fe3f6620 | ||
|
|
31d95c6557 | ||
|
|
67e9cc1099 | ||
|
|
4cf3c9c6d3 | ||
|
|
b7b012539d | ||
|
|
8a90f02161 | ||
|
|
a866576cfc | ||
|
|
6811bd5050 | ||
|
|
4009386d82 | ||
|
|
a6ebc460b0 | ||
|
|
a20f83597c | ||
|
|
9a9e5fda0a | ||
|
|
57cd7a365b | ||
|
|
a3ec5d147e | ||
|
|
8af2e562f8 | ||
|
|
c97550cecc | ||
|
|
63f6877924 | ||
|
|
655626bc42 | ||
|
|
35dbd5fde2 | ||
|
|
2c29903910 | ||
|
|
f49e29045c | ||
|
|
67593345a7 | ||
|
|
f069fdaae1 | ||
|
|
d478a7ec43 | ||
|
|
af96e55f61 | ||
|
|
e3ead981ec | ||
|
|
fbee4454e4 | ||
|
|
e9e16551ad | ||
|
|
e635b97d11 | ||
|
|
779257b864 | ||
|
|
e696851261 | ||
|
|
4e50e17d81 | ||
|
|
991eb2ab16 | ||
|
|
f0db3b81a8 | ||
|
|
9df1812d8e | ||
|
|
4f6da91d74 | ||
|
|
e2e69a3dc4 | ||
|
|
b6db64d868 | ||
|
|
160ae77145 | ||
|
|
2944493e2d | ||
|
|
3a9c113f77 | ||
|
|
47f2e69b7e | ||
|
|
6240d85377 | ||
|
|
29ba963c48 | ||
|
|
0473181f0a | ||
|
|
145e7cda01 | ||
|
|
b7ff076571 | ||
|
|
3aafbd7e1c | ||
|
|
58e9363fda | ||
|
|
6a85ab53eb | ||
|
|
212e4687d8 | ||
|
|
167b17dfd2 | ||
|
|
9d179c7227 | ||
|
|
147e812edb | ||
|
|
91196bb306 | ||
|
|
26834a6e04 | ||
|
|
754f385865 | ||
|
|
b30b62ef77 | ||
|
|
26788bb3a6 | ||
|
|
2706cd4d50 | ||
|
|
b40104b74c | ||
|
|
d308468f1b | ||
|
|
10e695d7a0 | ||
|
|
837e35255b | ||
|
|
56e801a582 | ||
|
|
04c0f8cbcd | ||
|
|
da846da334 | ||
|
|
ba6b5c868c | ||
|
|
9d378ed75b | ||
|
|
70982c2844 | ||
|
|
61f24320b8 | ||
|
|
eb4a541376 | ||
|
|
77710cc411 | ||
|
|
8b8c4609ce | ||
|
|
14b616a856 | ||
|
|
82d603c0fd | ||
|
|
f1a2ee7fb4 | ||
|
|
15021daa2e | ||
|
|
f83e565cd4 | ||
|
|
8636a4731e | ||
|
|
aa3510e936 | ||
|
|
fd48cc6d87 | ||
|
|
111d000c12 | ||
|
|
9c98a4c2b1 | ||
|
|
d2d4ed5aee | ||
|
|
30fce5d765 | ||
|
|
90040798b8 | ||
|
|
9eecddddd5 | ||
|
|
cc49e815d6 | ||
|
|
c26eb843e3 | ||
|
|
26efaa101d | ||
|
|
352567c56e | ||
|
|
51fc3307be | ||
|
|
cdf1c39a52 | ||
|
|
db1f7d34cf | ||
|
|
9212c195b4 | ||
|
|
7b333556d0 | ||
|
|
8ba96acf05 | ||
|
|
f164e54fee | ||
|
|
649b733ba1 | ||
|
|
e8ea93cb64 | ||
|
|
5dacd41ba9 | ||
|
|
7f837fe947 | ||
|
|
02bd7883cb | ||
|
|
1841798646 | ||
|
|
749bee6d55 | ||
|
|
043b845c06 | ||
|
|
8c7f82c6f0 | ||
|
|
ec4fa2ee4f | ||
|
|
b50eced489 | ||
|
|
5392475486 | ||
|
|
65bb262652 | ||
|
|
842d95c836 | ||
|
|
9fa9b67328 | ||
|
|
6337b75f0e | ||
|
|
b9d2e671c7 | ||
|
|
0840642c98 | ||
|
|
d5b01347df | ||
|
|
7dca1ad889 | ||
|
|
616eccb2cf | ||
|
|
30f07479cb | ||
|
|
7f880417e9 | ||
|
|
6b52458642 | ||
|
|
858a64687d | ||
|
|
819ccf54cd | ||
|
|
7cc077c8a0 | ||
|
|
fae5f22d25 | ||
|
|
eba7a3b476 | ||
|
|
cf231538f4 | ||
|
|
073b0b72d3 | ||
|
|
c8705822b3 | ||
|
|
d4436d9f15 | ||
|
|
4e0ff74944 | ||
|
|
366c1d0c6c | ||
|
|
17fa163ee3 | ||
|
|
3644fdb533 | ||
|
|
ab7c4e72c6 | ||
|
|
e25e7925b6 | ||
|
|
80237c8090 | ||
|
|
a310953f05 | ||
|
|
a9e92b60f5 | ||
|
|
35e40cd230 | ||
|
|
2575ad722a | ||
|
|
afd5757315 | ||
|
|
dba8b1f215 | ||
|
|
6dd0ef1268 | ||
|
|
83cfaed1a3 | ||
|
|
41cb9ee12e | ||
|
|
667f0dc87d | ||
|
|
a34c2fc0dc | ||
|
|
7a31263e4a | ||
|
|
7f9fd82c0e | ||
|
|
a37d1f4aeb | ||
|
|
acdbdedd5d | ||
|
|
a9b5eba9d4 | ||
|
|
80201224c6 | ||
|
|
e6e7d8d58b |
@@ -2,7 +2,7 @@
|
||||
.dockerignore
|
||||
.editorconfig
|
||||
.gitignore
|
||||
.env.*
|
||||
**/.env*
|
||||
Dockerfile
|
||||
Makefile
|
||||
LICENSE
|
||||
|
||||
16
.github/ISSUE_TEMPLATE/bug_report.md
vendored
16
.github/ISSUE_TEMPLATE/bug_report.md
vendored
@@ -12,9 +12,9 @@ A clear and concise description of what the bug is.
|
||||
|
||||
**To Reproduce**
|
||||
Steps to reproduce the behavior:
|
||||
1. Run mizu <command> '...'
|
||||
2. Click on '....'
|
||||
3. Scroll down to '....'
|
||||
1. Run `mizu <command> ...`
|
||||
2. Click on '...'
|
||||
3. Scroll down to '...'
|
||||
4. See error
|
||||
|
||||
**Expected behavior**
|
||||
@@ -22,17 +22,17 @@ A clear and concise description of what you expected to happen.
|
||||
|
||||
**Logs**
|
||||
Upload logs:
|
||||
1. Run the mizu command with `--set dump-logs=true` (e.g `mizu tap --set dump-logs=true`)
|
||||
1. Run the mizu command with `--set dump-logs=true` (e.g `mizu tap --set dump-logs=true`)
|
||||
2. Try to reproduce the issue
|
||||
3. CNTRL+C on terminal tab which runs mizu
|
||||
4. Upload the logs zip file from ~/.mizu/mizu_logs_**.zip
|
||||
3. <kbd>CTRL</kbd>+<kbd>C</kbd> on terminal tab which runs `mizu`
|
||||
4. Upload the logs zip file from `~/.mizu/mizu_logs_**.zip`
|
||||
|
||||
**Screenshots**
|
||||
If applicable, add screenshots to help explain your problem.
|
||||
|
||||
**Desktop (please complete the following information):**
|
||||
- OS: [e.g. iOS]
|
||||
- Browser [e.g. chrome]
|
||||
- OS: [e.g. macOS]
|
||||
- Web Browser: [e.g. Google Chrome]
|
||||
|
||||
**Additional context**
|
||||
Add any other context about the problem here.
|
||||
|
||||
18
.github/workflows/acceptance_tests.yml
vendored
18
.github/workflows/acceptance_tests.yml
vendored
@@ -1,4 +1,5 @@
|
||||
name: acceptance tests
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
branches:
|
||||
@@ -6,6 +7,11 @@ on:
|
||||
push:
|
||||
branches:
|
||||
- 'develop'
|
||||
|
||||
concurrency:
|
||||
group: mizu-acceptance-tests-${{ github.ref }}
|
||||
cancel-in-progress: true
|
||||
|
||||
jobs:
|
||||
run-acceptance-tests:
|
||||
name: Run acceptance tests
|
||||
@@ -24,3 +30,15 @@ jobs:
|
||||
|
||||
- name: Test
|
||||
run: make acceptance-test
|
||||
|
||||
- name: Slack notification on failure
|
||||
uses: ravsamhq/notify-slack-action@v1
|
||||
if: always()
|
||||
with:
|
||||
status: ${{ job.status }}
|
||||
notification_title: 'Mizu {workflow} has {status_message}'
|
||||
message_format: '{emoji} *{workflow}* {status_message} during <{run_url}|run>, after commit: <{commit_url}|{commit_sha}>'
|
||||
footer: 'Linked Repo <{repo_url}|{repo}>'
|
||||
notify_when: 'failure'
|
||||
env:
|
||||
SLACK_WEBHOOK_URL: ${{ secrets.SLACK_WEBHOOK_URL }}
|
||||
|
||||
22
.github/workflows/inactive-issues-close.yaml
vendored
Normal file
22
.github/workflows/inactive-issues-close.yaml
vendored
Normal file
@@ -0,0 +1,22 @@
|
||||
name: Close inactive issues
|
||||
on:
|
||||
schedule:
|
||||
- cron: "0 0 * * *"
|
||||
|
||||
jobs:
|
||||
close-issues:
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
issues: write
|
||||
pull-requests: write
|
||||
steps:
|
||||
- uses: actions/stale@v3
|
||||
with:
|
||||
days-before-issue-stale: 30
|
||||
days-before-issue-close: 14
|
||||
stale-issue-label: "stale"
|
||||
stale-issue-message: "This issue is stale because it has been open for 30 days with no activity."
|
||||
close-issue-message: "This issue was closed because it has been inactive for 14 days since being marked as stale."
|
||||
days-before-pr-stale: -1
|
||||
days-before-pr-close: -1
|
||||
repo-token: ${{ secrets.GITHUB_TOKEN }}
|
||||
49
.github/workflows/pr_validation.yml
vendored
49
.github/workflows/pr_validation.yml
vendored
@@ -1,9 +1,15 @@
|
||||
name: PR validation
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
branches:
|
||||
- 'develop'
|
||||
- 'main'
|
||||
|
||||
concurrency:
|
||||
group: mizu-pr-validation-${{ github.ref }}
|
||||
cancel-in-progress: true
|
||||
|
||||
jobs:
|
||||
build-cli:
|
||||
name: Build CLI
|
||||
@@ -12,7 +18,7 @@ jobs:
|
||||
- name: Set up Go 1.16
|
||||
uses: actions/setup-go@v2
|
||||
with:
|
||||
go-version: '^1.16'
|
||||
go-version: '1.16'
|
||||
|
||||
- name: Check out code into the Go module directory
|
||||
uses: actions/checkout@v2
|
||||
@@ -27,7 +33,7 @@ jobs:
|
||||
- name: Set up Go 1.16
|
||||
uses: actions/setup-go@v2
|
||||
with:
|
||||
go-version: '^1.16'
|
||||
go-version: '1.16'
|
||||
|
||||
- name: Check out code into the Go module directory
|
||||
uses: actions/checkout@v2
|
||||
@@ -39,42 +45,17 @@ jobs:
|
||||
- name: Build Agent
|
||||
run: make agent
|
||||
|
||||
run-tests-cli:
|
||||
name: Run CLI tests
|
||||
build-ui:
|
||||
name: Build UI
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Set up Go 1.16
|
||||
uses: actions/setup-go@v2
|
||||
- name: Set up Node 16
|
||||
uses: actions/setup-node@v2
|
||||
with:
|
||||
go-version: '^1.16'
|
||||
node-version: '16'
|
||||
|
||||
- name: Check out code into the Go module directory
|
||||
uses: actions/checkout@v2
|
||||
|
||||
- name: Test
|
||||
run: make test-cli
|
||||
|
||||
- name: Upload coverage to Codecov
|
||||
uses: codecov/codecov-action@v2
|
||||
|
||||
run-tests-agent:
|
||||
name: Run Agent tests
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Set up Go 1.16
|
||||
uses: actions/setup-go@v2
|
||||
with:
|
||||
go-version: '^1.16'
|
||||
|
||||
- name: Check out code into the Go module directory
|
||||
uses: actions/checkout@v2
|
||||
|
||||
- shell: bash
|
||||
run: |
|
||||
sudo apt-get install libpcap-dev
|
||||
|
||||
- name: Test
|
||||
run: make test-agent
|
||||
|
||||
- name: Upload coverage to Codecov
|
||||
uses: codecov/codecov-action@v2
|
||||
- name: Build UI
|
||||
run: make ui
|
||||
|
||||
248
.github/workflows/publish.yml
vendored
248
.github/workflows/publish.yml
vendored
@@ -1,57 +1,74 @@
|
||||
name: publish
|
||||
name: Release
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- 'develop'
|
||||
- 'main'
|
||||
- 'feature/multiarch_build'
|
||||
|
||||
concurrency:
|
||||
group: mizu-publish-${{ github.ref }}
|
||||
cancel-in-progress: true
|
||||
|
||||
jobs:
|
||||
docker:
|
||||
docker-registry:
|
||||
name: Push Docker image to Docker Hub
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
strategy:
|
||||
max-parallel: 2
|
||||
fail-fast: false
|
||||
matrix:
|
||||
target:
|
||||
- amd64
|
||||
- arm64v8
|
||||
|
||||
steps:
|
||||
- name: Checkout
|
||||
- name: Check out the repo
|
||||
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
|
||||
|
||||
- name: Determine versioning strategy
|
||||
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
|
||||
|
||||
- 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
|
||||
uses: docker/metadata-action@v3
|
||||
with:
|
||||
images: ${{ steps.base_image_step.outputs.image }}
|
||||
images: |
|
||||
up9inc/mizu
|
||||
tags: |
|
||||
type=sha
|
||||
type=raw,${{ github.sha }}
|
||||
type=raw,${{ steps.versioning.outputs.version }}
|
||||
- name: Login to DockerHub
|
||||
flavor: |
|
||||
latest=auto
|
||||
prefix=
|
||||
suffix=-${{ matrix.target }},onlatest=true
|
||||
|
||||
- name: Login to Docker Hub
|
||||
uses: docker/login-action@v1
|
||||
with:
|
||||
registry: gcr.io
|
||||
username: _json_key
|
||||
password: ${{ secrets.GCR_JSON_KEY }}
|
||||
username: ${{ secrets.DOCKERHUB_USER }}
|
||||
password: ${{ secrets.DOCKERHUB_PASS }}
|
||||
|
||||
- name: Build and push
|
||||
uses: docker/build-push-action@v2
|
||||
with:
|
||||
@@ -60,16 +77,198 @@ jobs:
|
||||
tags: ${{ steps.meta.outputs.tags }}
|
||||
labels: ${{ steps.meta.outputs.labels }}
|
||||
build-args: |
|
||||
ARCH=${{ matrix.target }}
|
||||
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 }}
|
||||
|
||||
gcp-registry:
|
||||
name: Push Docker image to GCR
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
strategy:
|
||||
max-parallel: 2
|
||||
fail-fast: false
|
||||
matrix:
|
||||
target:
|
||||
- amd64
|
||||
- arm64v8
|
||||
|
||||
steps:
|
||||
- name: Check out the repo
|
||||
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: Determine versioning strategy
|
||||
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: docker/metadata-action@v3
|
||||
with:
|
||||
images: |
|
||||
${{ steps.base_image_step.outputs.image }}
|
||||
tags: |
|
||||
type=raw,${{ steps.versioning.outputs.version }}
|
||||
flavor: |
|
||||
latest=auto
|
||||
prefix=
|
||||
suffix=-${{ matrix.target }},onlatest=true
|
||||
|
||||
- name: Login to GCR
|
||||
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: |
|
||||
ARCH=${{ matrix.target }}
|
||||
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 }}
|
||||
|
||||
docker-manifest:
|
||||
name: Create and Push a Docker Manifest
|
||||
runs-on: ubuntu-latest
|
||||
needs: [docker-registry]
|
||||
steps:
|
||||
- name: Determine versioning strategy
|
||||
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: Docker meta
|
||||
id: meta
|
||||
uses: docker/metadata-action@v3
|
||||
with:
|
||||
images: |
|
||||
up9inc/mizu
|
||||
tags: |
|
||||
type=raw,${{ steps.versioning.outputs.version }}
|
||||
|
||||
- name: Login to Docker Hub
|
||||
uses: docker/login-action@v1
|
||||
with:
|
||||
username: ${{ secrets.DOCKERHUB_USER }}
|
||||
password: ${{ secrets.DOCKERHUB_PASS }}
|
||||
|
||||
- name: Create manifest
|
||||
run: |
|
||||
while IFS= read -r line; do
|
||||
docker manifest create $line --amend $line-amd64 --amend $line-arm64v8
|
||||
done <<< "${{ steps.meta.outputs.tags }}"
|
||||
|
||||
- name: Push manifest
|
||||
run: |
|
||||
while IFS= read -r line; do
|
||||
docker manifest push $line
|
||||
done <<< "${{ steps.meta.outputs.tags }}"
|
||||
|
||||
cli:
|
||||
name: Build the CLI and publish
|
||||
runs-on: ubuntu-latest
|
||||
needs: [docker-manifest, gcp-registry]
|
||||
steps:
|
||||
- name: Set up Go 1.17
|
||||
uses: actions/setup-go@v2
|
||||
with:
|
||||
go-version: '1.17'
|
||||
|
||||
- name: Check out the repo
|
||||
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: Build and Push CLI
|
||||
run: make push-cli SEM_VER='${{ steps.versioning.outputs.version }}' BUILD_TIMESTAMP='${{ steps.version_parameters.outputs.build_timestamp }}'
|
||||
- shell: bash
|
||||
|
||||
- name: Log the version into a .txt file
|
||||
shell: bash
|
||||
run: |
|
||||
echo '${{ steps.versioning.outputs.version }}' >> cli/bin/version.txt
|
||||
- name: publish
|
||||
|
||||
- name: Release
|
||||
uses: ncipollo/release-action@v1
|
||||
with:
|
||||
token: ${{ secrets.GITHUB_TOKEN }}
|
||||
@@ -78,4 +277,3 @@ jobs:
|
||||
tag: ${{ steps.versioning.outputs.version }}
|
||||
prerelease: ${{ github.ref != 'refs/heads/main' }}
|
||||
bodyFile: 'cli/bin/README.md'
|
||||
|
||||
|
||||
56
.github/workflows/tests_validation.yml
vendored
Normal file
56
.github/workflows/tests_validation.yml
vendored
Normal file
@@ -0,0 +1,56 @@
|
||||
name: tests validation
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
branches:
|
||||
- 'develop'
|
||||
- 'main'
|
||||
push:
|
||||
branches:
|
||||
- 'develop'
|
||||
- 'main'
|
||||
|
||||
concurrency:
|
||||
group: mizu-tests-validation-${{ github.ref }}
|
||||
cancel-in-progress: true
|
||||
|
||||
jobs:
|
||||
run-tests-cli:
|
||||
name: Run CLI tests
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Set up Go 1.16
|
||||
uses: actions/setup-go@v2
|
||||
with:
|
||||
go-version: '^1.16'
|
||||
|
||||
- name: Check out code into the Go module directory
|
||||
uses: actions/checkout@v2
|
||||
|
||||
- name: Test
|
||||
run: make test-cli
|
||||
|
||||
- name: Upload coverage to Codecov
|
||||
uses: codecov/codecov-action@v2
|
||||
|
||||
run-tests-agent:
|
||||
name: Run Agent tests
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Set up Go 1.16
|
||||
uses: actions/setup-go@v2
|
||||
with:
|
||||
go-version: '^1.16'
|
||||
|
||||
- name: Check out code into the Go module directory
|
||||
uses: actions/checkout@v2
|
||||
|
||||
- shell: bash
|
||||
run: |
|
||||
sudo apt-get install libpcap-dev
|
||||
|
||||
- name: Test
|
||||
run: make test-agent
|
||||
|
||||
- name: Upload coverage to Codecov
|
||||
uses: codecov/codecov-action@v2
|
||||
27
.gitignore
vendored
27
.gitignore
vendored
@@ -15,7 +15,32 @@
|
||||
# vendor/
|
||||
.idea/
|
||||
build
|
||||
*.db
|
||||
|
||||
# Mac OS
|
||||
.DS_Store
|
||||
.vscode/
|
||||
|
||||
# Ignore the scripts that are created for development
|
||||
*dev.*
|
||||
|
||||
# Environment variables
|
||||
.env
|
||||
|
||||
# pprof
|
||||
pprof/*
|
||||
|
||||
# Database Files
|
||||
*.bin
|
||||
*.gob
|
||||
|
||||
# Nohup Files - https://man7.org/linux/man-pages/man1/nohup.1p.html
|
||||
nohup.*
|
||||
|
||||
# Cypress tests
|
||||
cypress.env.json
|
||||
*/cypress/downloads
|
||||
*/cypress/fixtures
|
||||
*/cypress/plugins
|
||||
*/cypress/screenshots
|
||||
*/cypress/videos
|
||||
*/cypress/support
|
||||
|
||||
86
Dockerfile
86
Dockerfile
@@ -1,17 +1,27 @@
|
||||
FROM node:14-slim AS site-build
|
||||
ARG ARCH=amd64
|
||||
|
||||
### Front-end
|
||||
FROM node:16 AS front-end
|
||||
|
||||
WORKDIR /app/ui-build
|
||||
|
||||
COPY ui .
|
||||
COPY ui/package.json .
|
||||
COPY ui/package-lock.json .
|
||||
RUN npm i
|
||||
COPY ui .
|
||||
RUN npm run build
|
||||
|
||||
|
||||
FROM golang:1.16-alpine AS builder
|
||||
# Set necessary environment variables needed for our image.
|
||||
ENV CGO_ENABLED=1 GOOS=linux GOARCH=amd64
|
||||
### Base of the builder image
|
||||
FROM golang:1.17-bullseye AS builder-base
|
||||
|
||||
RUN apk add libpcap-dev gcc g++ make
|
||||
# Set necessary environment variables needed for our image.
|
||||
ENV CGO_ENABLED=1 GOOS=linux GOARCH=${GOARCH}
|
||||
|
||||
RUN apt-get update \
|
||||
&& apt-get install -y --no-install-recommends \
|
||||
build-essential \
|
||||
libpcap-dev
|
||||
|
||||
# Move to agent working directory (/agent-build).
|
||||
WORKDIR /app/agent-build
|
||||
@@ -19,34 +29,68 @@ WORKDIR /app/agent-build
|
||||
COPY agent/go.mod agent/go.sum ./
|
||||
COPY shared/go.mod shared/go.mod ../shared/
|
||||
COPY tap/go.mod tap/go.mod ../tap/
|
||||
COPY tap/api/go.* ../tap/api/
|
||||
RUN go mod download
|
||||
# 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
|
||||
|
||||
ARG COMMIT_HASH
|
||||
ARG GIT_BRANCH
|
||||
ARG BUILD_TIMESTAMP
|
||||
ARG SEM_VER
|
||||
# cheap trick to make the build faster (as long as go.mod did not change)
|
||||
RUN go list -f '{{.Path}}@{{.Version}}' -m all | sed 1d | grep -e 'go-cache' | xargs go get
|
||||
|
||||
# Copy and build agent code
|
||||
COPY shared ../shared
|
||||
COPY tap ../tap
|
||||
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
|
||||
### Intermediate builder image for AMD64 architecture
|
||||
FROM builder-base AS builder-amd64
|
||||
|
||||
ENV GOARCH=amd64
|
||||
|
||||
|
||||
### Intermediate builder image for ARM64 architecture
|
||||
FROM builder-base AS builder-arm64v8
|
||||
|
||||
ENV GOARCH=arm64
|
||||
ENV CC=aarch64-linux-gnu-gcc
|
||||
ENV PKG_CONFIG_PATH=/usr/lib/aarch64-linux-gnu/pkgconfig
|
||||
|
||||
RUN dpkg --add-architecture arm64 \
|
||||
&& apt-get update \
|
||||
&& apt-get install -y --no-install-recommends \
|
||||
gcc-aarch64-linux-gnu \
|
||||
libpcap-dev:arm64
|
||||
|
||||
|
||||
### Final builder image where the building happens
|
||||
FROM builder-${ARCH} AS builder
|
||||
|
||||
ARG COMMIT_HASH
|
||||
ARG GIT_BRANCH
|
||||
ARG BUILD_TIMESTAMP
|
||||
ARG SEM_VER=0.0.0
|
||||
|
||||
WORKDIR /app/agent-build
|
||||
|
||||
RUN go build -ldflags="-extldflags '-fuse-ld=bfd' -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 .
|
||||
|
||||
COPY devops/build_extensions.sh ..
|
||||
RUN cd .. && /bin/bash build_extensions.sh
|
||||
|
||||
|
||||
|
||||
### The shipped image
|
||||
ARG ARCH=amd64
|
||||
FROM up9inc/debian-pcap:stable-slim-${ARCH}
|
||||
|
||||
RUN apk add bash libpcap-dev tcpdump
|
||||
WORKDIR /app
|
||||
|
||||
# Copy binary and config files from /build to root folder of scratch container.
|
||||
COPY --from=builder ["/app/agent-build/mizuagent", "."]
|
||||
COPY --from=site-build ["/app/ui-build/build", "site"]
|
||||
COPY --from=builder ["/app/agent/build/extensions", "extensions"]
|
||||
COPY --from=front-end ["/app/ui-build/build", "site"]
|
||||
|
||||
# gin-gonic runs in debug mode without this
|
||||
ENV GIN_MODE=release
|
||||
|
||||
33
Makefile
33
Makefile
@@ -8,7 +8,7 @@ SHELL=/bin/bash
|
||||
# HELP
|
||||
# This will output the help for each task
|
||||
# thanks to https://marmelab.com/blog/2016/02/29/auto-documented-makefile.html
|
||||
.PHONY: help ui agent cli tap docker
|
||||
.PHONY: help ui extensions extensions-debug agent agent-debug cli tap docker
|
||||
|
||||
help: ## This help.
|
||||
@awk 'BEGIN {FS = ":.*?## "} /^[a-zA-Z_-]+:.*?## / {printf "\033[36m%-30s\033[0m %s\n", $$1, $$2}' $(MAKEFILE_LIST)
|
||||
@@ -23,17 +23,27 @@ export SEM_VER?=0.0.0
|
||||
|
||||
ui: ## Build UI.
|
||||
@(cd ui; npm i ; npm run build; )
|
||||
@ls -l ui/build
|
||||
@ls -l ui/build
|
||||
|
||||
cli: ## Build CLI.
|
||||
@echo "building cli"; cd cli && $(MAKE) build
|
||||
|
||||
cli-debug: ## Build CLI.
|
||||
@echo "building cli"; cd cli && $(MAKE) build-debug
|
||||
|
||||
build-cli-ci: ## Build CLI for CI.
|
||||
@echo "building cli for ci"; cd cli && $(MAKE) build GIT_BRANCH=ci SUFFIX=ci
|
||||
|
||||
agent: ## Build agent.
|
||||
@(echo "building mizu agent .." )
|
||||
@(cd agent; go build -o build/mizuagent main.go)
|
||||
${MAKE} extensions
|
||||
@ls -l agent/build
|
||||
|
||||
agent-debug: ## Build agent for debug.
|
||||
@(echo "building mizu agent for debug.." )
|
||||
@(cd agent; go build -gcflags="all=-N -l" -o build/mizuagent main.go)
|
||||
${MAKE} extensions-debug
|
||||
@ls -l agent/build
|
||||
|
||||
docker: ## Build and publish agent docker image.
|
||||
@@ -43,11 +53,15 @@ push: push-docker push-cli ## Build and publish agent docker image & CLI.
|
||||
|
||||
push-docker: ## Build and publish agent docker image.
|
||||
@echo "publishing Docker image .. "
|
||||
./build-push-featurebranch.sh
|
||||
devops/build-push-featurebranch.sh
|
||||
|
||||
push-docker-debug:
|
||||
@echo "publishing debug Docker image .. "
|
||||
devops/build-push-featurebranch-debug.sh
|
||||
|
||||
build-docker-ci: ## Build agent docker image for CI.
|
||||
@echo "building docker image for ci"
|
||||
./build-agent-ci.sh
|
||||
devops/build-agent-ci.sh
|
||||
|
||||
push-cli: ## Build and publish CLI.
|
||||
@echo "publishing CLI .. "
|
||||
@@ -57,7 +71,7 @@ push-cli: ## Build and publish CLI.
|
||||
gsutil cp -r ./cli/bin/* gs://${BUCKET_PATH}/
|
||||
gsutil setmeta -r -h "Cache-Control:public, max-age=30" gs://${BUCKET_PATH}/\*
|
||||
|
||||
clean: clean-ui clean-agent clean-cli clean-docker ## Clean all build artifacts.
|
||||
clean: clean-ui clean-agent clean-cli clean-docker clean-extensions ## Clean all build artifacts.
|
||||
|
||||
clean-ui: ## Clean UI.
|
||||
@(rm -rf ui/build ; echo "UI cleanup done" )
|
||||
@@ -68,9 +82,18 @@ clean-agent: ## Clean agent.
|
||||
clean-cli: ## Clean CLI.
|
||||
@(cd cli; make clean ; echo "CLI cleanup done" )
|
||||
|
||||
clean-extensions: ## Clean extensions
|
||||
@(rm -rf tap/extensions/*.so ; echo "Extensions cleanup done" )
|
||||
|
||||
clean-docker:
|
||||
@(echo "DOCKER cleanup - NOT IMPLEMENTED YET " )
|
||||
|
||||
extensions-debug:
|
||||
devops/build_extensions_debug.sh
|
||||
|
||||
extensions:
|
||||
devops/build_extensions.sh
|
||||
|
||||
test-cli:
|
||||
@echo "running cli tests"; cd cli && $(MAKE) test
|
||||
|
||||
|
||||
91
README.md
91
README.md
@@ -1,17 +1,40 @@
|
||||

|
||||
|
||||
<p align="center">
|
||||
<a href="https://github.com/up9inc/mizu/releases/latest">
|
||||
<img alt="GitHub Latest Release" src="https://img.shields.io/github/v/release/up9inc/mizu?logo=GitHub&style=flat-square">
|
||||
</a>
|
||||
<a href="https://github.com/up9inc/mizu/blob/main/LICENSE">
|
||||
<img alt="GitHub License" src="https://img.shields.io/github/license/up9inc/mizu?logo=GitHub&style=flat-square">
|
||||
</a>
|
||||
<a href="https://join.slack.com/t/up9/shared_invite/zt-tfjnduli-QzlR8VV4Z1w3YnPIAJfhlQ">
|
||||
<img alt="Slack" src="https://img.shields.io/badge/slack-join_chat-white.svg?logo=slack&style=social">
|
||||
</a>
|
||||
</p>
|
||||
|
||||
# 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
|
||||
A simple-yet-powerful API traffic viewer for Kubernetes enabling you to view all API communication between microservices to help your debug and troubleshoot regressions.
|
||||
|
||||
Think TCPDump and Wireshark re-invented for Kubernetes.
|
||||
|
||||

|
||||
|
||||
## 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
|
||||
- Monitoring network traffic in real-time. Supported protocols:
|
||||
- [HTTP/1.x](https://datatracker.ietf.org/doc/html/rfc2616) (REST, GraphQL, SOAP, etc.)
|
||||
- [HTTP/2](https://datatracker.ietf.org/doc/html/rfc7540) (gRPC)
|
||||
- [AMQP](https://www.rabbitmq.com/amqp-0-9-1-reference.html) (RabbitMQ, Apache Qpid, etc.)
|
||||
- [Apache Kafka](https://kafka.apache.org/protocol)
|
||||
- [Redis](https://redis.io/topics/protocol)
|
||||
- Works with Kubernetes APIs. No installation or code instrumentation
|
||||
- Rich filtering
|
||||
|
||||
## Requirements
|
||||
|
||||
A Kubernetes server version of 1.16.0 or higher is required.
|
||||
|
||||
## Download
|
||||
|
||||
@@ -38,18 +61,11 @@ SHA256 checksums are available on the [Releases](https://github.com/up9inc/mizu/
|
||||
### 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` assumes user running the command has permissions to create resources (such as pods, services, namespaces) on your Kubernetes cluster (no worries - `mizu` resources are cleaned up upon termination)
|
||||
|
||||
For detailed list of k8s permissions see [PERMISSIONS](PERMISSIONS.md) document
|
||||
|
||||
|
||||
## How to Run
|
||||
|
||||
1. Find pods you'd like to tap to in your Kubernetes cluster
|
||||
2. Run `mizu tap` or `mizu tap PODNAME`
|
||||
3. Open browser on `http://localhost:8899/mizu` **or** as instructed in the CLI
|
||||
2. Run `mizu tap` or `mizu tap PODNAME`
|
||||
3. Open browser on `http://localhost:8899` **or** as instructed in the CLI
|
||||
4. Watch the API traffic flowing
|
||||
5. Type ^C to stop
|
||||
|
||||
@@ -75,7 +91,7 @@ To tap all pods in current namespace -
|
||||
```
|
||||
|
||||
|
||||
To tap specific pod -
|
||||
### To tap specific pod
|
||||
```bash
|
||||
$ kubectl get pods
|
||||
NAME READY STATUS RESTARTS AGE
|
||||
@@ -88,7 +104,7 @@ To tap specific pod -
|
||||
^C
|
||||
```
|
||||
|
||||
To tap multiple pods using regex -
|
||||
### To tap multiple pods using regex
|
||||
```bash
|
||||
$ kubectl get pods
|
||||
NAME READY STATUS RESTARTS AGE
|
||||
@@ -106,21 +122,22 @@ To tap multiple pods using regex -
|
||||
|
||||
## Configuration
|
||||
|
||||
Mizu can work with config file which should be stored in ${HOME}/.mizu/config.yaml (macOS: ~/.mizu/config.yaml) <br />
|
||||
In case no config file found, defaults will be used <br />
|
||||
Mizu can optionally work with a config file that can be provided as a CLI argument (using `--set config-path=<PATH>`) or if not provided, will be stored at ${HOME}/.mizu/config.yaml
|
||||
In case of partial configuration defined, all other fields will be used with defaults <br />
|
||||
You can always override the defaults or config file with CLI flags
|
||||
|
||||
To get the default config params run `mizu config` <br />
|
||||
To generate a new config file with default values use `mizu config -r`
|
||||
|
||||
### Telemetry
|
||||
|
||||
By default, mizu reports usage telemetry. It can be disabled by adding a line of `telemetry: false` in the `${HOME}/.mizu/config.yaml` file
|
||||
|
||||
|
||||
## Advanced Usage
|
||||
|
||||
### Kubeconfig
|
||||
|
||||
It is possible to change the kubeconfig path using `KUBECONFIG` environment variable or the command like flag
|
||||
with `--set kube-config-path=<PATH>`. </br >
|
||||
If both are not set - Mizu assumes that configuration is at `${HOME}/.kube/config`
|
||||
|
||||
### Namespace-Restricted Mode
|
||||
|
||||
Some users have permission to only manage resources in one particular namespace assigned to them
|
||||
@@ -134,37 +151,47 @@ using the `--namespace` flag or by setting `tap.namespaces` in the config file
|
||||
|
||||
Setting `mizu-resources-namespace=mizu` resets Mizu to its default behavior
|
||||
|
||||
For detailed list of k8s permissions see [PERMISSIONS](docs/PERMISSIONS.md) document
|
||||
|
||||
### User agent filtering
|
||||
|
||||
User-agent filtering (like health checks) - can be configured using command-line options:
|
||||
|
||||
```shell
|
||||
$ mizu tap "^ca.*" --set ignored-user-agents=kube-probe --set ignored-user-agents=prometheus
|
||||
$ mizu tap "^ca.*" --set tap.ignored-user-agents=kube-probe --set tap.ignored-user-agents=prometheus
|
||||
+carts-66c77f5fbb-fq65r
|
||||
+catalogue-5f4cb7cf5-7zrmn
|
||||
Web interface is now available at http://localhost:8899
|
||||
^C
|
||||
|
||||
```
|
||||
|
||||
Any request that contains `User-Agent` header with one of the specified values (`kube-probe` or `prometheus`) will not be captured
|
||||
|
||||
### API Rules validation
|
||||
### Traffic validation rules
|
||||
|
||||
This feature allows you to define set of simple rules, and test the API against them.
|
||||
This feature allows you to define set of simple rules, and test the traffic against them.
|
||||
Such validation may test response for specific JSON fields, headers, etc.
|
||||
|
||||
Please see [API RULES](docs/POLICY_RULES.md) page for more details and syntax.
|
||||
Please see [TRAFFIC RULES](docs/POLICY_RULES.md) page for more details and syntax.
|
||||
|
||||
### OpenAPI Specification (OAS) Contract Monitoring
|
||||
|
||||
## How to Run local UI
|
||||
An OAS/Swagger file can contain schemas under `parameters` and `responses` fields. With `--contract catalogue.yaml`
|
||||
CLI option, you can pass your API description to Mizu and the traffic will automatically be validated
|
||||
against the contracts.
|
||||
|
||||
- run from mizu/agent `go run main.go --hars-read --hars-dir <folder>`
|
||||
Please see [CONTRACT MONITORING](docs/CONTRACT_MONITORING.md) page for more details and syntax.
|
||||
|
||||
- copy Har files into the folder from last command
|
||||
### Configure proxy host
|
||||
|
||||
- change `MizuWebsocketURL` and `apiURL` in `api.js` file
|
||||
By default, mizu will be accessible via local host: 'http://localhost:8899', it is possible to change the host, for
|
||||
instance, to '0.0.0.0' which can grant access via machine IP address. This setting can be changed via command line
|
||||
flag `--set tap.proxy-host=<value>` or via config file:
|
||||
tap proxy-host: 0.0.0.0 and when changed it will support accessing by IP
|
||||
|
||||
- run from mizu/ui - `npm run start`
|
||||
### Install Mizu standalone
|
||||
|
||||
- open browser on `localhost:3000`
|
||||
Mizu can be run detached from the cli using the install command: `mizu install`. This type of mizu instance will run
|
||||
indefinitely in the cluster.
|
||||
|
||||
For more information please refer to [INSTALL STANDALONE](docs/INSTALL_STANDALONE.md)
|
||||
|
||||
15
TESTING.md
15
TESTING.md
@@ -1,15 +0,0 @@
|
||||

|
||||
# TESTING
|
||||
Testing guidelines for Mizu project
|
||||
|
||||
## Unit-tests
|
||||
* TBD
|
||||
* TBD
|
||||
* TBD
|
||||
|
||||
|
||||
|
||||
## System tests
|
||||
* TBD
|
||||
* TBD
|
||||
* TBD
|
||||
@@ -1,2 +1,2 @@
|
||||
test: ## Run acceptance tests.
|
||||
@go test ./...
|
||||
@go test ./... -timeout 1h -v
|
||||
|
||||
284
acceptanceTests/config_test.go
Normal file
284
acceptanceTests/config_test.go
Normal file
@@ -0,0 +1,284 @@
|
||||
package acceptanceTests
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"os/exec"
|
||||
"testing"
|
||||
|
||||
"gopkg.in/yaml.v3"
|
||||
)
|
||||
|
||||
type tapConfig struct {
|
||||
GuiPort uint16 `yaml:"gui-port"`
|
||||
}
|
||||
|
||||
type configStruct struct {
|
||||
Tap tapConfig `yaml:"tap"`
|
||||
}
|
||||
|
||||
func TestConfigRegenerate(t *testing.T) {
|
||||
if testing.Short() {
|
||||
t.Skip("ignored acceptance test")
|
||||
}
|
||||
|
||||
cliPath, cliPathErr := getCliPath()
|
||||
if cliPathErr != nil {
|
||||
t.Errorf("failed to get cli path, err: %v", cliPathErr)
|
||||
return
|
||||
}
|
||||
|
||||
configPath, configPathErr := getConfigPath()
|
||||
if configPathErr != nil {
|
||||
t.Errorf("failed to get config path, err: %v", cliPathErr)
|
||||
return
|
||||
}
|
||||
|
||||
configCmdArgs := getDefaultConfigCommandArgs()
|
||||
|
||||
configCmdArgs = append(configCmdArgs, "-r")
|
||||
|
||||
configCmd := exec.Command(cliPath, configCmdArgs...)
|
||||
t.Logf("running command: %v", configCmd.String())
|
||||
|
||||
t.Cleanup(func() {
|
||||
if err := os.Remove(configPath); err != nil {
|
||||
t.Logf("failed to delete config file, err: %v", err)
|
||||
}
|
||||
})
|
||||
|
||||
if err := configCmd.Start(); err != nil {
|
||||
t.Errorf("failed to start config command, err: %v", err)
|
||||
return
|
||||
}
|
||||
|
||||
if err := configCmd.Wait(); err != nil {
|
||||
t.Errorf("failed to wait config command, err: %v", err)
|
||||
return
|
||||
}
|
||||
|
||||
_, readFileErr := ioutil.ReadFile(configPath)
|
||||
if readFileErr != nil {
|
||||
t.Errorf("failed to read config file, err: %v", readFileErr)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
func TestConfigGuiPort(t *testing.T) {
|
||||
if testing.Short() {
|
||||
t.Skip("ignored acceptance test")
|
||||
}
|
||||
|
||||
tests := []uint16{8898}
|
||||
|
||||
for _, guiPort := range tests {
|
||||
t.Run(fmt.Sprintf("%d", guiPort), func(t *testing.T) {
|
||||
cliPath, cliPathErr := getCliPath()
|
||||
if cliPathErr != nil {
|
||||
t.Errorf("failed to get cli path, err: %v", cliPathErr)
|
||||
return
|
||||
}
|
||||
|
||||
configPath, configPathErr := getConfigPath()
|
||||
if configPathErr != nil {
|
||||
t.Errorf("failed to get config path, err: %v", cliPathErr)
|
||||
return
|
||||
}
|
||||
|
||||
config := configStruct{}
|
||||
config.Tap.GuiPort = guiPort
|
||||
|
||||
configBytes, marshalErr := yaml.Marshal(config)
|
||||
if marshalErr != nil {
|
||||
t.Errorf("failed to marshal config, err: %v", marshalErr)
|
||||
return
|
||||
}
|
||||
|
||||
if writeErr := ioutil.WriteFile(configPath, configBytes, 0644); writeErr != nil {
|
||||
t.Errorf("failed to write config to file, err: %v", writeErr)
|
||||
return
|
||||
}
|
||||
|
||||
tapCmdArgs := getDefaultTapCommandArgs()
|
||||
|
||||
tapNamespace := getDefaultTapNamespace()
|
||||
tapCmdArgs = append(tapCmdArgs, tapNamespace...)
|
||||
|
||||
tapCmd := exec.Command(cliPath, tapCmdArgs...)
|
||||
t.Logf("running command: %v", tapCmd.String())
|
||||
|
||||
t.Cleanup(func() {
|
||||
if err := cleanupCommand(tapCmd); err != nil {
|
||||
t.Logf("failed to cleanup tap command, err: %v", err)
|
||||
}
|
||||
|
||||
if err := os.Remove(configPath); err != nil {
|
||||
t.Logf("failed to delete config file, err: %v", err)
|
||||
}
|
||||
})
|
||||
|
||||
if err := tapCmd.Start(); err != nil {
|
||||
t.Errorf("failed to start tap command, err: %v", err)
|
||||
return
|
||||
}
|
||||
|
||||
apiServerUrl := getApiServerUrl(guiPort)
|
||||
|
||||
if err := waitTapPodsReady(apiServerUrl); err != nil {
|
||||
t.Errorf("failed to start tap pods on time, err: %v", err)
|
||||
return
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestConfigSetGuiPort(t *testing.T) {
|
||||
if testing.Short() {
|
||||
t.Skip("ignored acceptance test")
|
||||
}
|
||||
|
||||
tests := []struct {
|
||||
ConfigFileGuiPort uint16
|
||||
SetGuiPort uint16
|
||||
}{
|
||||
{ConfigFileGuiPort: 8898, SetGuiPort: 8897},
|
||||
}
|
||||
|
||||
for _, guiPortStruct := range tests {
|
||||
t.Run(fmt.Sprintf("%d", guiPortStruct.SetGuiPort), func(t *testing.T) {
|
||||
cliPath, cliPathErr := getCliPath()
|
||||
if cliPathErr != nil {
|
||||
t.Errorf("failed to get cli path, err: %v", cliPathErr)
|
||||
return
|
||||
}
|
||||
|
||||
configPath, configPathErr := getConfigPath()
|
||||
if configPathErr != nil {
|
||||
t.Errorf("failed to get config path, err: %v", cliPathErr)
|
||||
return
|
||||
}
|
||||
|
||||
config := configStruct{}
|
||||
config.Tap.GuiPort = guiPortStruct.ConfigFileGuiPort
|
||||
|
||||
configBytes, marshalErr := yaml.Marshal(config)
|
||||
if marshalErr != nil {
|
||||
t.Errorf("failed to marshal config, err: %v", marshalErr)
|
||||
return
|
||||
}
|
||||
|
||||
if writeErr := ioutil.WriteFile(configPath, configBytes, 0644); writeErr != nil {
|
||||
t.Errorf("failed to write config to file, err: %v", writeErr)
|
||||
return
|
||||
}
|
||||
|
||||
tapCmdArgs := getDefaultTapCommandArgs()
|
||||
|
||||
tapNamespace := getDefaultTapNamespace()
|
||||
tapCmdArgs = append(tapCmdArgs, tapNamespace...)
|
||||
|
||||
tapCmdArgs = append(tapCmdArgs, "--set", fmt.Sprintf("tap.gui-port=%v", guiPortStruct.SetGuiPort))
|
||||
|
||||
tapCmd := exec.Command(cliPath, tapCmdArgs...)
|
||||
t.Logf("running command: %v", tapCmd.String())
|
||||
|
||||
t.Cleanup(func() {
|
||||
if err := cleanupCommand(tapCmd); err != nil {
|
||||
t.Logf("failed to cleanup tap command, err: %v", err)
|
||||
}
|
||||
|
||||
if err := os.Remove(configPath); err != nil {
|
||||
t.Logf("failed to delete config file, err: %v", err)
|
||||
}
|
||||
})
|
||||
|
||||
if err := tapCmd.Start(); err != nil {
|
||||
t.Errorf("failed to start tap command, err: %v", err)
|
||||
return
|
||||
}
|
||||
|
||||
apiServerUrl := getApiServerUrl(guiPortStruct.SetGuiPort)
|
||||
|
||||
if err := waitTapPodsReady(apiServerUrl); err != nil {
|
||||
t.Errorf("failed to start tap pods on time, err: %v", err)
|
||||
return
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestConfigFlagGuiPort(t *testing.T) {
|
||||
if testing.Short() {
|
||||
t.Skip("ignored acceptance test")
|
||||
}
|
||||
|
||||
tests := []struct {
|
||||
ConfigFileGuiPort uint16
|
||||
FlagGuiPort uint16
|
||||
}{
|
||||
{ConfigFileGuiPort: 8898, FlagGuiPort: 8896},
|
||||
}
|
||||
|
||||
for _, guiPortStruct := range tests {
|
||||
t.Run(fmt.Sprintf("%d", guiPortStruct.FlagGuiPort), func(t *testing.T) {
|
||||
cliPath, cliPathErr := getCliPath()
|
||||
if cliPathErr != nil {
|
||||
t.Errorf("failed to get cli path, err: %v", cliPathErr)
|
||||
return
|
||||
}
|
||||
|
||||
configPath, configPathErr := getConfigPath()
|
||||
if configPathErr != nil {
|
||||
t.Errorf("failed to get config path, err: %v", cliPathErr)
|
||||
return
|
||||
}
|
||||
|
||||
config := configStruct{}
|
||||
config.Tap.GuiPort = guiPortStruct.ConfigFileGuiPort
|
||||
|
||||
configBytes, marshalErr := yaml.Marshal(config)
|
||||
if marshalErr != nil {
|
||||
t.Errorf("failed to marshal config, err: %v", marshalErr)
|
||||
return
|
||||
}
|
||||
|
||||
if writeErr := ioutil.WriteFile(configPath, configBytes, 0644); writeErr != nil {
|
||||
t.Errorf("failed to write config to file, err: %v", writeErr)
|
||||
return
|
||||
}
|
||||
|
||||
tapCmdArgs := getDefaultTapCommandArgs()
|
||||
|
||||
tapNamespace := getDefaultTapNamespace()
|
||||
tapCmdArgs = append(tapCmdArgs, tapNamespace...)
|
||||
|
||||
tapCmdArgs = append(tapCmdArgs, "-p", fmt.Sprintf("%v", guiPortStruct.FlagGuiPort))
|
||||
|
||||
tapCmd := exec.Command(cliPath, tapCmdArgs...)
|
||||
t.Logf("running command: %v", tapCmd.String())
|
||||
|
||||
t.Cleanup(func() {
|
||||
if err := cleanupCommand(tapCmd); err != nil {
|
||||
t.Logf("failed to cleanup tap command, err: %v", err)
|
||||
}
|
||||
|
||||
if err := os.Remove(configPath); err != nil {
|
||||
t.Logf("failed to delete config file, err: %v", err)
|
||||
}
|
||||
})
|
||||
|
||||
if err := tapCmd.Start(); err != nil {
|
||||
t.Errorf("failed to start tap command, err: %v", err)
|
||||
return
|
||||
}
|
||||
|
||||
apiServerUrl := getApiServerUrl(guiPortStruct.FlagGuiPort)
|
||||
|
||||
if err := waitTapPodsReady(apiServerUrl); err != nil {
|
||||
t.Errorf("failed to start tap pods on time, err: %v", err)
|
||||
return
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
{
|
||||
"watchForFileChanges":false,
|
||||
"viewportWidth": 1920,
|
||||
"viewportHeight": 1080,
|
||||
"video": false,
|
||||
"screenshotOnRunFailure": false,
|
||||
"testFiles": [
|
||||
"tests/GuiPort.js",
|
||||
"tests/MultipleNamespaces.js",
|
||||
"tests/Redact.js",
|
||||
"tests/NoRedact.js",
|
||||
"tests/Regex.js",
|
||||
"tests/RegexMasking.js"
|
||||
],
|
||||
|
||||
"env": {
|
||||
"testUrl": "http://localhost:8899/",
|
||||
"redactHeaderContent": "User-Header[REDACTED]",
|
||||
"redactBodyContent": "{ \"User\": \"[REDACTED]\" }",
|
||||
"regexMaskingBodyContent": "[REDACTED]"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
{
|
||||
"watchForFileChanges":false,
|
||||
"viewportWidth": 1920,
|
||||
"viewportHeight": 3500,
|
||||
"video": false,
|
||||
"screenshotOnRunFailure": false,
|
||||
"testFiles": [
|
||||
"tests/IgnoredUserAgents.js"
|
||||
],
|
||||
|
||||
"env": {
|
||||
"testUrl": "http://localhost:8899/"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,46 @@
|
||||
const columns = {podName : 1, namespace : 2, tapping : 3};
|
||||
const greenStatusImageSrc = '/static/media/success.662997eb.svg';
|
||||
|
||||
function getDomPathInStatusBar(line, column) {
|
||||
return `.expandedStatusBar > :nth-child(2) > > :nth-child(2) > :nth-child(${line}) > :nth-child(${column})`;
|
||||
}
|
||||
|
||||
export function checkLine(line, expectedValues) {
|
||||
cy.get(getDomPathInStatusBar(line, columns.podName)).invoke('text').then(podValue => {
|
||||
const podName = getOnlyPodName(podValue);
|
||||
expect(podName).to.equal(expectedValues.podName);
|
||||
|
||||
cy.get(getDomPathInStatusBar(line, columns.namespace)).invoke('text').then(namespaceValue => {
|
||||
expect(namespaceValue).to.equal(expectedValues.namespace);
|
||||
cy.get(getDomPathInStatusBar(line, columns.tapping)).children().should('have.attr', 'src', greenStatusImageSrc);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
export function findLineAndCheck(expectedValues) {
|
||||
cy.get('.expandedStatusBar > :nth-child(2) > > :nth-child(2) > > :nth-child(1)').then(pods => {
|
||||
cy.get('.expandedStatusBar > :nth-child(2) > > :nth-child(2) > > :nth-child(2)').then(namespaces => {
|
||||
// organizing namespaces array
|
||||
const podObjectsArray = Object.values(pods ?? {});
|
||||
const namespacesObjectsArray = Object.values(namespaces ?? {});
|
||||
let lineNumber = -1;
|
||||
namespacesObjectsArray.forEach((namespaceObj, index) => {
|
||||
const currentLine = index + 1;
|
||||
lineNumber = (namespaceObj.getAttribute && namespaceObj.innerHTML === expectedValues.namespace && (getOnlyPodName(podObjectsArray[index].innerHTML)) === expectedValues.podName) ? currentLine : lineNumber;
|
||||
});
|
||||
lineNumber === -1 ? throwError(expectedValues) : checkLine(lineNumber, expectedValues);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function throwError(expectedValues) {
|
||||
throw new Error(`The pod named ${expectedValues.podName} doesn't match any namespace named ${expectedValues.namespace}`);
|
||||
}
|
||||
|
||||
export function getExpectedDetailsDict(podName, namespace) {
|
||||
return {podName : podName, namespace : namespace};
|
||||
}
|
||||
|
||||
function getOnlyPodName(podElementFullStr) {
|
||||
return podElementFullStr.substring(0, podElementFullStr.indexOf('-'));
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
export function isValueExistsInElement(shouldInclude, content, domPathToContainer){
|
||||
it(`should ${shouldInclude ? '' : 'not'} include '${content}'`, function () {
|
||||
cy.get(domPathToContainer).then(htmlText => {
|
||||
const allTextString = htmlText.text();
|
||||
if (allTextString.includes(content) !== shouldInclude)
|
||||
throw new Error(`One of the containers part contains ${content}`)
|
||||
});
|
||||
});
|
||||
}
|
||||
8
acceptanceTests/cypress/integration/tests/GuiPort.js
Normal file
8
acceptanceTests/cypress/integration/tests/GuiPort.js
Normal file
@@ -0,0 +1,8 @@
|
||||
it('check', function () {
|
||||
cy.visit(`http://localhost:${Cypress.env('port')}/`);
|
||||
|
||||
cy.get('.header').should('be.visible');
|
||||
cy.get('.TrafficPageHeader').should('be.visible');
|
||||
cy.get('.TrafficPage-ListContainer').should('be.visible');
|
||||
cy.get('.TrafficPage-Container').should('be.visible');
|
||||
});
|
||||
@@ -0,0 +1,33 @@
|
||||
import {isValueExistsInElement} from "../testHelpers/TrafficHelper";
|
||||
|
||||
it('Loading Mizu', function () {
|
||||
cy.visit(Cypress.env('testUrl'));
|
||||
});
|
||||
|
||||
it('going through each entry', function () {
|
||||
cy.get('#total-entries').then(number => {
|
||||
const getNum = () => {
|
||||
const numOfEntries = number.text();
|
||||
return parseInt(numOfEntries);
|
||||
};
|
||||
cy.wrap({ there: getNum }).invoke('there').should('be.gte', 25);
|
||||
|
||||
checkThatAllEntriesShown();
|
||||
|
||||
const entriesNum = getNum();
|
||||
[...Array(entriesNum).keys()].map(checkEntry);
|
||||
});
|
||||
});
|
||||
|
||||
function checkThatAllEntriesShown() {
|
||||
cy.get('#entries-length').then(number => {
|
||||
if (number.text() === '1')
|
||||
cy.get('[title="Fetch old records"]').click();
|
||||
});
|
||||
}
|
||||
|
||||
function checkEntry(entryIndex) {
|
||||
cy.get(`#entry-${entryIndex}`).click();
|
||||
cy.get('#tbody-Headers').should('be.visible');
|
||||
isValueExistsInElement(false, 'Ignored-User-Agent', '#tbody-Headers');
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
import {findLineAndCheck, getExpectedDetailsDict} from '../testHelpers/StatusBarHelper';
|
||||
|
||||
it('opening', function () {
|
||||
cy.visit(Cypress.env('testUrl'));
|
||||
cy.get('.podsCount').trigger('mouseover');
|
||||
});
|
||||
|
||||
[1, 2, 3].map(doItFunc);
|
||||
|
||||
function doItFunc(number) {
|
||||
const podName = Cypress.env(`name${number}`);
|
||||
const namespace = Cypress.env(`namespace${number}`);
|
||||
|
||||
it(`verifying the pod (${podName}, ${namespace})`, function () {
|
||||
findLineAndCheck(getExpectedDetailsDict(podName, namespace));
|
||||
});
|
||||
}
|
||||
8
acceptanceTests/cypress/integration/tests/NoRedact.js
Normal file
8
acceptanceTests/cypress/integration/tests/NoRedact.js
Normal file
@@ -0,0 +1,8 @@
|
||||
import {isValueExistsInElement} from '../testHelpers/TrafficHelper';
|
||||
|
||||
it('Loading Mizu', function () {
|
||||
cy.visit(Cypress.env('testUrl'));
|
||||
})
|
||||
|
||||
isValueExistsInElement(false, Cypress.env('redactHeaderContent'), '#tbody-Headers');
|
||||
isValueExistsInElement(false, Cypress.env('redactBodyContent'), '.hljs');
|
||||
8
acceptanceTests/cypress/integration/tests/Redact.js
Normal file
8
acceptanceTests/cypress/integration/tests/Redact.js
Normal file
@@ -0,0 +1,8 @@
|
||||
import {isValueExistsInElement} from '../testHelpers/TrafficHelper';
|
||||
|
||||
it('Loading Mizu', function () {
|
||||
cy.visit(Cypress.env('testUrl'));
|
||||
})
|
||||
|
||||
isValueExistsInElement(true, Cypress.env('redactHeaderContent'), '#tbody-Headers');
|
||||
isValueExistsInElement(true, Cypress.env('redactBodyContent'), '.hljs');
|
||||
11
acceptanceTests/cypress/integration/tests/Regex.js
Normal file
11
acceptanceTests/cypress/integration/tests/Regex.js
Normal file
@@ -0,0 +1,11 @@
|
||||
import {getExpectedDetailsDict, checkLine} from '../testHelpers/StatusBarHelper';
|
||||
|
||||
|
||||
it('opening', function () {
|
||||
cy.visit(Cypress.env('testUrl'));
|
||||
cy.get('.podsCount').trigger('mouseover');
|
||||
|
||||
cy.get('.expandedStatusBar > :nth-child(2) > > :nth-child(2) >').should('have.length', 1); // one line
|
||||
|
||||
checkLine(1, getExpectedDetailsDict(Cypress.env('name'), Cypress.env('namespace')));
|
||||
});
|
||||
@@ -0,0 +1,7 @@
|
||||
import {isValueExistsInElement} from "../testHelpers/TrafficHelper";
|
||||
|
||||
it('Loading Mizu', function () {
|
||||
cy.visit(Cypress.env('testUrl'));
|
||||
})
|
||||
|
||||
isValueExistsInElement(true, Cypress.env('regexMaskingBodyContent'), '.hljs');
|
||||
@@ -1,3 +1,13 @@
|
||||
module github.com/up9inc/mizu/tests
|
||||
|
||||
go 1.16
|
||||
|
||||
require (
|
||||
github.com/gorilla/websocket v1.4.2
|
||||
github.com/up9inc/mizu/shared v0.0.0
|
||||
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b
|
||||
)
|
||||
|
||||
replace github.com/up9inc/mizu/shared v0.0.0 => ../shared
|
||||
|
||||
replace github.com/up9inc/mizu/tap/api v0.0.0 => ../tap/api
|
||||
|
||||
701
acceptanceTests/go.sum
Normal file
701
acceptanceTests/go.sum
Normal file
@@ -0,0 +1,701 @@
|
||||
cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
|
||||
cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
|
||||
cloud.google.com/go v0.38.0/go.mod h1:990N+gfupTy94rShfmMCWGDn0LpTmnzTp2qbd1dvSRU=
|
||||
cloud.google.com/go v0.44.1/go.mod h1:iSa0KzasP4Uvy3f1mN/7PiObzGgflwredwwASm/v6AU=
|
||||
cloud.google.com/go v0.44.2/go.mod h1:60680Gw3Yr4ikxnPRS/oxxkBccT6SA1yMk63TGekxKY=
|
||||
cloud.google.com/go v0.45.1/go.mod h1:RpBamKRgapWJb87xiFSdk4g1CME7QZg3uwTez+TSTjc=
|
||||
cloud.google.com/go v0.46.3/go.mod h1:a6bKKbmY7er1mI7TEI4lsAkts/mkhTSZK8w33B4RAg0=
|
||||
cloud.google.com/go v0.50.0/go.mod h1:r9sluTvynVuxRIOHXQEHMFffphuXHOMZMycpNR5e6To=
|
||||
cloud.google.com/go v0.52.0/go.mod h1:pXajvRH/6o3+F9jDHZWQ5PbGhn+o8w9qiu/CffaVdO4=
|
||||
cloud.google.com/go v0.53.0/go.mod h1:fp/UouUEsRkN6ryDKNW/Upv/JBKnv6WDthjR6+vze6M=
|
||||
cloud.google.com/go v0.54.0/go.mod h1:1rq2OEkV3YMf6n/9ZvGWI3GWw0VoqH/1x2nd8Is/bPc=
|
||||
cloud.google.com/go/bigquery v1.0.1/go.mod h1:i/xbL2UlR5RvWAURpBYZTtm/cXjCha9lbfbpx4poX+o=
|
||||
cloud.google.com/go/bigquery v1.3.0/go.mod h1:PjpwJnslEMmckchkHFfq+HTD2DmtT67aNFKH1/VBDHE=
|
||||
cloud.google.com/go/bigquery v1.4.0/go.mod h1:S8dzgnTigyfTmLBfrtrhyYhwRxG72rYxvftPBK2Dvzc=
|
||||
cloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7TKLgZqpHYE=
|
||||
cloud.google.com/go/datastore v1.1.0/go.mod h1:umbIZjpQpHh4hmRpGhH4tLFup+FVzqBi1b3c64qFpCk=
|
||||
cloud.google.com/go/firestore v1.1.0/go.mod h1:ulACoGHTpvq5r8rxGJ4ddJZBZqakUQqClKRT5SZwBmk=
|
||||
cloud.google.com/go/pubsub v1.0.1/go.mod h1:R0Gpsv3s54REJCy4fxDixWD93lHJMoZTyQ2kNxGRt3I=
|
||||
cloud.google.com/go/pubsub v1.1.0/go.mod h1:EwwdRX2sKPjnvnqCa270oGRyludottCI76h+R3AArQw=
|
||||
cloud.google.com/go/pubsub v1.2.0/go.mod h1:jhfEVHT8odbXTkndysNHCcx0awwzvfOlguIAii9o8iA=
|
||||
cloud.google.com/go/storage v1.0.0/go.mod h1:IhtSnM/ZTZV8YYJWCY8RULGVqBDmpoyjwiyrjsg+URw=
|
||||
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=
|
||||
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/go.mod h1:r+4oMnoxhatjLLJ6zxSWATqVooLgysK6ZNox3g/xq24=
|
||||
github.com/Azure/go-autorest/autorest v0.11.12/go.mod h1:eipySxLmqSyC5s5k1CLupqet0PSENBEDP93LQ9a8QYw=
|
||||
github.com/Azure/go-autorest/autorest/adal v0.9.5/go.mod h1:B7KF7jKIeC9Mct5spmyCB/A8CG/sEz1vwIRGv/bbw7A=
|
||||
github.com/Azure/go-autorest/autorest/date v0.3.0/go.mod h1:BI0uouVdmngYNUzGWeSYnokU+TrmwEsOqdt8Y6sso74=
|
||||
github.com/Azure/go-autorest/autorest/mocks v0.4.1/go.mod h1:LTp+uSrOhSkaKrUy935gNZuuIPPVsHlr9DSOxSayd+k=
|
||||
github.com/Azure/go-autorest/logger v0.2.0/go.mod h1:T9E3cAhj2VqvPOtCYAvby9aBXkZmbF5NWuPV8+WeEW8=
|
||||
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/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/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/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-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-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/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-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/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.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw=
|
||||
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/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/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/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/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
|
||||
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/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-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/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.11/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
|
||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/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/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/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk=
|
||||
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/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/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/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/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.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/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/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-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.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.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/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.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.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.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.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/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.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/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q=
|
||||
github.com/golang-jwt/jwt/v4 v4.1.0 h1:XUgk2Ex5veyVFVeLm0xhusUTQybEbexJXrvPNOKkSY0=
|
||||
github.com/golang-jwt/jwt/v4 v4.1.0/go.mod h1:/xlHOz8bRuivTWchD4jCa+NbatV+wEUSzwAxVc6locg=
|
||||
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
|
||||
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-20191227052852-215e87163ea7/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.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
|
||||
github.com/golang/mock v1.3.1/go.mod h1:sBzyDLLjw3U8JLTeZvSv8jJB+tU5PVekmnlKIyFUx0Y=
|
||||
github.com/golang/mock v1.4.0/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw=
|
||||
github.com/golang/mock v1.4.1/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw=
|
||||
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
||||
github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
||||
github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
||||
github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw=
|
||||
github.com/golang/protobuf v1.3.4/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw=
|
||||
github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8=
|
||||
github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA=
|
||||
github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs=
|
||||
github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w=
|
||||
github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0=
|
||||
github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8=
|
||||
github.com/golang/protobuf v1.4.2/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 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.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
|
||||
github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
|
||||
github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/go-cmp v0.5.4 h1:L8R9j+yAqZuZjsqh/z+F1NCffTKKLShY6zXTItVIZ8M=
|
||||
github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
|
||||
github.com/google/gofuzz v1.1.0 h1:Hsa8mG0dQ46ij8Sl2AYJDUv1oA9/d6Vk+3LG99Oe02g=
|
||||
github.com/google/gofuzz v1.1.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
|
||||
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-20190515194954-54271f7e092f/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc=
|
||||
github.com/google/pprof v0.0.0-20191218002539-d4f498aebedc/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/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.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.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk=
|
||||
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/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/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
|
||||
github.com/gregjones/httpcache v0.0.0-20180305231024-9cad4c3443a7/go.mod h1:FecbI9+v66THATjSRHfNgh1IVFe/9kFxbXtjV0ctIMA=
|
||||
github.com/grpc-ecosystem/go-grpc-middleware v1.0.0/go.mod h1:FiyG127CGDf3tlThmgyCl78X/SZQqEOJBCDaAfeWzPs=
|
||||
github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0/go.mod h1:8NvIoxWQoOIhqOTXgfV/d3M/q6VIi02HzZEHgUlZvzk=
|
||||
github.com/grpc-ecosystem/grpc-gateway v1.9.0/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY=
|
||||
github.com/hashicorp/consul/api v1.1.0/go.mod h1:VmuI/Lkw1nC05EYQWNKwWGbkg+FbDBtguAZLlVdkD9Q=
|
||||
github.com/hashicorp/consul/sdk v0.1.1/go.mod h1:VKf9jXwCTEY1QZP2MOLRhb5i/I/ssyNV1vwHyQBF0x8=
|
||||
github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4=
|
||||
github.com/hashicorp/go-cleanhttp v0.5.1/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80=
|
||||
github.com/hashicorp/go-immutable-radix v1.0.0/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60=
|
||||
github.com/hashicorp/go-msgpack v0.5.3/go.mod h1:ahLV/dePpqEmjfWmKiqvPkv/twdG7iPBM1vqhUKIvfM=
|
||||
github.com/hashicorp/go-multierror v1.0.0/go.mod h1:dHtQlpGsu+cZNNAkkCN/P3hoUDHhCYQXV3UM06sGGrk=
|
||||
github.com/hashicorp/go-rootcerts v1.0.0/go.mod h1:K6zTfqpRlCUIjkwsN4Z+hiSfzSTQa6eBIzfwKfwNnHU=
|
||||
github.com/hashicorp/go-sockaddr v1.0.0/go.mod h1:7Xibr9yA9JjQq1JpNB2Vw7kxv8xerXegt+ozgdvDeDU=
|
||||
github.com/hashicorp/go-syslog v1.0.0/go.mod h1:qPfqrKkXGihmCqbJM2mZgkZGvKG1dFdvsLplgctolz4=
|
||||
github.com/hashicorp/go-uuid v1.0.0/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/golang-lru v0.5.0/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/logutils v1.0.0/go.mod h1:QIAnNjmIWmVIIkWDTG1z5v++HQmx9WQRO+LraFDTW64=
|
||||
github.com/hashicorp/mdns v1.0.0/go.mod h1:tL+uN++7HEJ6SQLQ2/p+z2pH24WQKWjBPkE0mNTz8vQ=
|
||||
github.com/hashicorp/memberlist v0.1.3/go.mod h1:ajVTdAv/9Im8oMAAj5G31PhhMCZJV2pPBoIllUwCN7I=
|
||||
github.com/hashicorp/serf v0.8.2/go.mod h1:6hOLApaqBFA1NXqRQAsxw9QxuDEvNxSQRwA/JwenrHc=
|
||||
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/imdario/mergo v0.3.5/go.mod h1:2EnlNZ0deacrJVfApfmtdGgDfMuh/nq6Ok1EcJh5FfA=
|
||||
github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8=
|
||||
github.com/jonboulle/clockwork v0.1.0/go.mod h1:Ii8DK3G1RaLaWxj9trq07+26W01tbo22gdxWY5EU2bo=
|
||||
github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU=
|
||||
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/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU=
|
||||
github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk=
|
||||
github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU=
|
||||
github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w=
|
||||
github.com/kisielk/errcheck v1.1.0/go.mod h1:EZBBE59ingxPouuu3KfxchcWSUPOHkagtvWXihfKN4Q=
|
||||
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/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
|
||||
github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc=
|
||||
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
|
||||
github.com/kr/pretty v0.2.0/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
|
||||
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
|
||||
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.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
|
||||
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/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-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-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.2-0.20181231171920-c182affec369/go.mod h1:BSXmuO+STAnVfrANrmjBb36TMTDstsz7MSK+HVaYKv4=
|
||||
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/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-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/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 v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y=
|
||||
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-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg=
|
||||
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 v1.0.1 h1:9f412s+6RmYXLWZSEzVVgPGK7C2PphHj5RJrvfx9AWI=
|
||||
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/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U=
|
||||
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/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno=
|
||||
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 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/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/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/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/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.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/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.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-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.2.0/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
|
||||
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.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-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/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/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/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/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/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/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/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA=
|
||||
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/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/jwalterweatherman v1.0.0/go.mod h1:cQK4TGJAtQXfYWX+Ddv3mKDzgVb68N+wFjFa4jdeBTo=
|
||||
github.com/spf13/pflag v0.0.0-20170130214245-9ff6c6923cff/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4=
|
||||
github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4=
|
||||
github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA=
|
||||
github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
|
||||
github.com/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/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.2.0/go.mod h1:qt09Ya8vawLte6SNmTgCsAVtYtaKzEcn8ATUoHMkEqE=
|
||||
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
|
||||
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
|
||||
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
|
||||
github.com/stretchr/testify v1.6.1 h1:hDPOHmpOpP40lSULcqw7IrRb/u7w6RpDC9399XyoNd0=
|
||||
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/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/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/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.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
||||
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.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8=
|
||||
go.opencensus.io v0.22.2/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/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0=
|
||||
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-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-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-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-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-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-20210220033148-5ea612d1eb83/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I=
|
||||
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
||||
golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
||||
golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8=
|
||||
golang.org/x/exp v0.0.0-20190829153037-c13cbed26979/go.mod h1:86+5VVa7VpoJ4kLfm080zCjGlMRFzhUhsZKEZO7MGek=
|
||||
golang.org/x/exp v0.0.0-20191030013958-a1ab85dbe136/go.mod h1:JXzH8nQsPlswgeRAPE3MuO9GYsAcnJvJ4vnMwN/5qkY=
|
||||
golang.org/x/exp v0.0.0-20191129062945-2f5052295587/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4=
|
||||
golang.org/x/exp v0.0.0-20191227195350-da58074b4299/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4=
|
||||
golang.org/x/exp v0.0.0-20200119233911-0405dc783f0a/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4=
|
||||
golang.org/x/exp v0.0.0-20200207192155-f17229e696bd/go.mod h1:J/WKrq2StrnmMY6+EHIKF9dgMWnmCNThgcyBT1FY9mM=
|
||||
golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6/go.mod h1:3jZMyOhIsHpP37uCMkUooju7aAi5cS1Q23tOzKc+0MU=
|
||||
golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js=
|
||||
golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
|
||||
golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
|
||||
golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU=
|
||||
golang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
|
||||
golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
|
||||
golang.org/x/lint v0.0.0-20190409202823-959b441ac422/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
|
||||
golang.org/x/lint v0.0.0-20190909230951-414d861bb4ac/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
|
||||
golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
|
||||
golang.org/x/lint v0.0.0-20191125180803-fdd1cda4f05f/go.mod h1:5qLYkcX4OjUUV8bRuDixDT3tpyyb+LUpUlRWLxfhWrs=
|
||||
golang.org/x/lint v0.0.0-20200130185559-910be7a94367/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY=
|
||||
golang.org/x/lint v0.0.0-20200302205851-738671d3881b/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY=
|
||||
golang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE=
|
||||
golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o=
|
||||
golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc=
|
||||
golang.org/x/mod v0.1.0/go.mod h1:0QHyrYULN0/3qlju5TqG8bIK38QM8yzMo5ekMj3DlcY=
|
||||
golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/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.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-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-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-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-20181220203305-927f97764cc3/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-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-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-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-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-20190724013045-ca1201d0de80/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20190827160401-ba9fcec4b297/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20191209160850-c0dbc17a3553/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20200114155413-6afb5195e5aa/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20200222125558-5a598a2470a0/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-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-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/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-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
||||
golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
||||
golang.org/x/oauth2 v0.0.0-20191202225959-858c2ad4c8b6/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
||||
golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
||||
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sys v0.0.0-20180823144017-11551d06cbcc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20181026203630-95b1ffbd15a5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20181107165924-66b7b1311ac8/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-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-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-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-20190616124812-15dcb6c0061f/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-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-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-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-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-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-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-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-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210119212857-b64e53b001e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
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-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||
golang.org/x/term v0.0.0-20210220032956-6a3ed077a48d/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||
golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
|
||||
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/text v0.3.4 h1:0YWbFKbhXG/wIiuHDSKpS0Iy7FSA+u45VtBMfQcFTTc=
|
||||
golang.org/x/text v0.3.4/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||
golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||
golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||
golang.org/x/time v0.0.0-20210220033141-f8bda1e9f3ba/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||
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-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-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-20190312170243-e65039ee4138/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
|
||||
golang.org/x/tools v0.0.0-20190328211700-ab21143f2384/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
|
||||
golang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
|
||||
golang.org/x/tools v0.0.0-20190506145303-2d16b83fe98c/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-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-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-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-20191012152004-8de300cfc20a/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
golang.org/x/tools v0.0.0-20191112195655-aa38f8e97acc/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
golang.org/x/tools v0.0.0-20191113191852-77e3bb0ad9e7/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
golang.org/x/tools v0.0.0-20191115202509-3a792d9c32b2/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
golang.org/x/tools v0.0.0-20191125144606-a911d9008d1f/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
golang.org/x/tools v0.0.0-20191130070609-6e064ea0cf2d/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
golang.org/x/tools v0.0.0-20191216173652-a0e659d51361/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
|
||||
golang.org/x/tools v0.0.0-20191227053925-7b8e75db28f4/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
|
||||
golang.org/x/tools v0.0.0-20200117161641-43d50277825c/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
|
||||
golang.org/x/tools v0.0.0-20200122220014-bf1340f18c4a/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
|
||||
golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
|
||||
golang.org/x/tools v0.0.0-20200204074204-1cc6d1ef6c74/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
|
||||
golang.org/x/tools v0.0.0-20200207183749-b753a1ba74fa/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-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-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-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE=
|
||||
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE=
|
||||
google.golang.org/api v0.7.0/go.mod h1:WtwebWUNSVBH/HAw79HIFXZNqEvBhG+Ra+ax0hx3E3M=
|
||||
google.golang.org/api v0.8.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg=
|
||||
google.golang.org/api v0.9.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg=
|
||||
google.golang.org/api v0.13.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI=
|
||||
google.golang.org/api v0.14.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI=
|
||||
google.golang.org/api v0.15.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI=
|
||||
google.golang.org/api v0.17.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=
|
||||
google.golang.org/api v0.18.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=
|
||||
google.golang.org/api v0.20.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=
|
||||
google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
|
||||
google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
|
||||
google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
|
||||
google.golang.org/appengine v1.6.1/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww/cMBSeb0=
|
||||
google.golang.org/appengine v1.6.5/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc=
|
||||
google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
|
||||
google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
|
||||
google.golang.org/genproto v0.0.0-20190418145605-e7d98fc518a7/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
|
||||
google.golang.org/genproto v0.0.0-20190425155659-357c62f0e4bb/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
|
||||
google.golang.org/genproto v0.0.0-20190502173448-54afdca5d873/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
|
||||
google.golang.org/genproto v0.0.0-20190801165951-fa694d86fc64/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=
|
||||
google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=
|
||||
google.golang.org/genproto v0.0.0-20190911173649-1774047e7e51/go.mod h1:IbNlFCBrqXvoKpeg0TB2l7cyZUmoaFKYIwrEpbDKLA8=
|
||||
google.golang.org/genproto v0.0.0-20191108220845-16a3f7862a1a/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
|
||||
google.golang.org/genproto v0.0.0-20191115194625-c23dd37a84c9/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
|
||||
google.golang.org/genproto v0.0.0-20191216164720-4f79533eabd1/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
|
||||
google.golang.org/genproto v0.0.0-20191230161307-f3c370f40bfb/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
|
||||
google.golang.org/genproto v0.0.0-20200115191322-ca5a22157cba/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
|
||||
google.golang.org/genproto v0.0.0-20200122232147-0452cf42e150/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
|
||||
google.golang.org/genproto v0.0.0-20200204135345-fa8e72b47b90/go.mod h1:GmwEX6Z4W5gMy59cAlVYjN9JhxgbQH6Gn+gFDQe2lzA=
|
||||
google.golang.org/genproto v0.0.0-20200212174721-66ed5ce911ce/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
|
||||
google.golang.org/genproto v0.0.0-20200224152610-e50cd9704f63/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
|
||||
google.golang.org/genproto v0.0.0-20200305110556-506484158171/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
|
||||
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.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.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg=
|
||||
google.golang.org/grpc v1.26.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
|
||||
google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
|
||||
google.golang.org/grpc v1.27.1/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
|
||||
google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8=
|
||||
google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0=
|
||||
google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM=
|
||||
google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE=
|
||||
google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo=
|
||||
google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
|
||||
google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
|
||||
google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
|
||||
google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c=
|
||||
gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f h1:BLraFXnmrev5lT+xlilqcH8XK9/i0At2xKjWk4p6zsU=
|
||||
gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=
|
||||
gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys=
|
||||
gopkg.in/inf.v0 v0.9.1 h1:73M5CoZyi3ZLMOyDlQh031Cx6N9NDJ2Vvfl76EDAgDc=
|
||||
gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw=
|
||||
gopkg.in/ini.v1 v1.51.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
|
||||
gopkg.in/resty.v1 v1.12.0/go.mod h1:mDo4pnntr5jdWRML875a/NmxYqAlA73dVijT2AXvQQo=
|
||||
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw=
|
||||
gopkg.in/yaml.v2 v2.0.0-20170812160011-eb3733d160e7/go.mod h1:JAlM8MvJe8wmxCU4Bli9HhUf9+ttbYbLASfIpnQbh74=
|
||||
gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v2 v2.2.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.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.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
|
||||
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
|
||||
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
gopkg.in/yaml.v3 v3.0.0-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-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-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-2020.1.3/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k=
|
||||
k8s.io/api v0.21.2 h1:vz7DqmRsXTCSa6pNxXwQ1IYeAZgdIsua+DZU+o+SX3Y=
|
||||
k8s.io/api v0.21.2/go.mod h1:Lv6UGJZ1rlMI1qusN8ruAp9PUBFyBwpEHAdG24vIsiU=
|
||||
k8s.io/apimachinery v0.21.2 h1:vezUc/BHqWlQDnZ+XkrpXSmnANSLbpnlpwo0Lhk0gpc=
|
||||
k8s.io/apimachinery v0.21.2/go.mod h1:CdTY8fU/BlvAbJ2z/8kBwimGki5Zp8/fbVuLY8gJumM=
|
||||
k8s.io/cli-runtime v0.21.2/go.mod h1:8u/jFcM0QpoI28f6sfrAAIslLCXUYKD5SsPPMWiHYrI=
|
||||
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-20201214224949-b6c5ce23f027/go.mod h1:FiNAH4ZV3gBg2Kwh89tzAEV2be7d5xI0vBa/VySYy3E=
|
||||
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/go.mod h1:hy9LJ/NvuK+iVyP4Ehqva4HxZG/oXyIS3n3Jmire4Ec=
|
||||
k8s.io/kube-openapi v0.0.0-20210305001622-591a79e4bda7/go.mod h1:wXW5VT87nVfh/iLV8FpR2uDvrFyomxbtb1KivDbvPTE=
|
||||
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/go.mod h1:jPW/WVKK9YHAvNhRxK0md/EJ228hCsBRufyofKtW8HA=
|
||||
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/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.1.0 h1:C4r9BgJ98vrKnnVCjwCSXcWjWe0NKcUQkmzDXZXGwH8=
|
||||
sigs.k8s.io/structured-merge-diff/v4 v4.1.0/go.mod h1:bJZC9H9iH24zzfZ/41RGcq60oK1F7G282QMXDPYydCw=
|
||||
sigs.k8s.io/yaml v1.2.0 h1:kr/MCeFWJWTwyaHoR9c8EjH9OumOmoF9YGiZd7lFm/Q=
|
||||
sigs.k8s.io/yaml v1.2.0/go.mod h1:yfXDCHCao9+ENCvLSE62v9VSji2MKu5jeNfTrofGhJc=
|
||||
206
acceptanceTests/logs_test.go
Normal file
206
acceptanceTests/logs_test.go
Normal file
@@ -0,0 +1,206 @@
|
||||
package acceptanceTests
|
||||
|
||||
import (
|
||||
"archive/zip"
|
||||
"os/exec"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestLogs(t *testing.T) {
|
||||
if testing.Short() {
|
||||
t.Skip("ignored acceptance test")
|
||||
}
|
||||
|
||||
cliPath, cliPathErr := getCliPath()
|
||||
if cliPathErr != nil {
|
||||
t.Errorf("failed to get cli path, err: %v", cliPathErr)
|
||||
return
|
||||
}
|
||||
|
||||
tapCmdArgs := getDefaultTapCommandArgs()
|
||||
|
||||
tapNamespace := getDefaultTapNamespace()
|
||||
tapCmdArgs = append(tapCmdArgs, tapNamespace...)
|
||||
|
||||
tapCmd := exec.Command(cliPath, tapCmdArgs...)
|
||||
t.Logf("running command: %v", tapCmd.String())
|
||||
|
||||
t.Cleanup(func() {
|
||||
if err := cleanupCommand(tapCmd); err != nil {
|
||||
t.Logf("failed to cleanup tap command, err: %v", err)
|
||||
}
|
||||
})
|
||||
|
||||
if err := tapCmd.Start(); err != nil {
|
||||
t.Errorf("failed to start tap command, err: %v", err)
|
||||
return
|
||||
}
|
||||
|
||||
apiServerUrl := getApiServerUrl(defaultApiServerPort)
|
||||
|
||||
if err := waitTapPodsReady(apiServerUrl); err != nil {
|
||||
t.Errorf("failed to start tap pods on time, err: %v", err)
|
||||
return
|
||||
}
|
||||
|
||||
logsCmdArgs := getDefaultLogsCommandArgs()
|
||||
|
||||
logsCmd := exec.Command(cliPath, logsCmdArgs...)
|
||||
t.Logf("running command: %v", logsCmd.String())
|
||||
|
||||
if err := logsCmd.Start(); err != nil {
|
||||
t.Errorf("failed to start logs command, err: %v", err)
|
||||
return
|
||||
}
|
||||
|
||||
if err := logsCmd.Wait(); err != nil {
|
||||
t.Errorf("failed to wait logs command, err: %v", err)
|
||||
return
|
||||
}
|
||||
|
||||
logsPath, logsPathErr := getLogsPath()
|
||||
if logsPathErr != nil {
|
||||
t.Errorf("failed to get logs path, err: %v", logsPathErr)
|
||||
return
|
||||
}
|
||||
|
||||
zipReader, zipError := zip.OpenReader(logsPath)
|
||||
if zipError != nil {
|
||||
t.Errorf("failed to get zip reader, err: %v", zipError)
|
||||
return
|
||||
}
|
||||
|
||||
t.Cleanup(func() {
|
||||
if err := zipReader.Close(); err != nil {
|
||||
t.Logf("failed to close zip reader, err: %v", err)
|
||||
}
|
||||
})
|
||||
|
||||
var logsFileNames []string
|
||||
for _, file := range zipReader.File {
|
||||
logsFileNames = append(logsFileNames, file.Name)
|
||||
}
|
||||
|
||||
if !Contains(logsFileNames, "mizu.mizu-api-server.mizu-api-server.log") {
|
||||
t.Errorf("api server logs not found")
|
||||
return
|
||||
}
|
||||
|
||||
if !Contains(logsFileNames, "mizu.mizu-api-server.basenine.log") {
|
||||
t.Errorf("basenine logs not found")
|
||||
return
|
||||
}
|
||||
|
||||
if !Contains(logsFileNames, "mizu_cli.log") {
|
||||
t.Errorf("cli logs not found")
|
||||
return
|
||||
}
|
||||
|
||||
if !Contains(logsFileNames, "mizu_events.log") {
|
||||
t.Errorf("events logs not found")
|
||||
return
|
||||
}
|
||||
|
||||
if !ContainsPartOfValue(logsFileNames, "mizu.mizu-tapper-daemon-set") {
|
||||
t.Errorf("tapper logs not found")
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
func TestLogsPath(t *testing.T) {
|
||||
if testing.Short() {
|
||||
t.Skip("ignored acceptance test")
|
||||
}
|
||||
|
||||
cliPath, cliPathErr := getCliPath()
|
||||
if cliPathErr != nil {
|
||||
t.Errorf("failed to get cli path, err: %v", cliPathErr)
|
||||
return
|
||||
}
|
||||
|
||||
tapCmdArgs := getDefaultTapCommandArgs()
|
||||
|
||||
tapNamespace := getDefaultTapNamespace()
|
||||
tapCmdArgs = append(tapCmdArgs, tapNamespace...)
|
||||
|
||||
tapCmd := exec.Command(cliPath, tapCmdArgs...)
|
||||
t.Logf("running command: %v", tapCmd.String())
|
||||
|
||||
t.Cleanup(func() {
|
||||
if err := cleanupCommand(tapCmd); err != nil {
|
||||
t.Logf("failed to cleanup tap command, err: %v", err)
|
||||
}
|
||||
})
|
||||
|
||||
if err := tapCmd.Start(); err != nil {
|
||||
t.Errorf("failed to start tap command, err: %v", err)
|
||||
return
|
||||
}
|
||||
|
||||
apiServerUrl := getApiServerUrl(defaultApiServerPort)
|
||||
|
||||
if err := waitTapPodsReady(apiServerUrl); err != nil {
|
||||
t.Errorf("failed to start tap pods on time, err: %v", err)
|
||||
return
|
||||
}
|
||||
|
||||
logsCmdArgs := getDefaultLogsCommandArgs()
|
||||
|
||||
logsPath := "../logs.zip"
|
||||
logsCmdArgs = append(logsCmdArgs, "-f", logsPath)
|
||||
|
||||
logsCmd := exec.Command(cliPath, logsCmdArgs...)
|
||||
t.Logf("running command: %v", logsCmd.String())
|
||||
|
||||
if err := logsCmd.Start(); err != nil {
|
||||
t.Errorf("failed to start logs command, err: %v", err)
|
||||
return
|
||||
}
|
||||
|
||||
if err := logsCmd.Wait(); err != nil {
|
||||
t.Errorf("failed to wait logs command, err: %v", err)
|
||||
return
|
||||
}
|
||||
|
||||
zipReader, zipError := zip.OpenReader(logsPath)
|
||||
if zipError != nil {
|
||||
t.Errorf("failed to get zip reader, err: %v", zipError)
|
||||
return
|
||||
}
|
||||
|
||||
t.Cleanup(func() {
|
||||
if err := zipReader.Close(); err != nil {
|
||||
t.Logf("failed to close zip reader, err: %v", err)
|
||||
}
|
||||
})
|
||||
|
||||
var logsFileNames []string
|
||||
for _, file := range zipReader.File {
|
||||
logsFileNames = append(logsFileNames, file.Name)
|
||||
}
|
||||
|
||||
if !Contains(logsFileNames, "mizu.mizu-api-server.mizu-api-server.log") {
|
||||
t.Errorf("api server logs not found")
|
||||
return
|
||||
}
|
||||
|
||||
if !Contains(logsFileNames, "mizu.mizu-api-server.basenine.log") {
|
||||
t.Errorf("basenine logs not found")
|
||||
return
|
||||
}
|
||||
|
||||
if !Contains(logsFileNames, "mizu_cli.log") {
|
||||
t.Errorf("cli logs not found")
|
||||
return
|
||||
}
|
||||
|
||||
if !Contains(logsFileNames, "mizu_events.log") {
|
||||
t.Errorf("events logs not found")
|
||||
return
|
||||
}
|
||||
|
||||
if !ContainsPartOfValue(logsFileNames, "mizu.mizu-tapper-daemon-set") {
|
||||
t.Errorf("tapper logs not found")
|
||||
return
|
||||
}
|
||||
}
|
||||
@@ -26,14 +26,21 @@ fi
|
||||
echo "Starting minikube..."
|
||||
minikube start
|
||||
|
||||
echo "Creating mizu tests namespace"
|
||||
echo "Creating mizu tests namespaces"
|
||||
kubectl create namespace mizu-tests
|
||||
kubectl create namespace mizu-tests2
|
||||
|
||||
echo "Creating httpbin deployment"
|
||||
echo "Creating httpbin deployments"
|
||||
kubectl create deployment httpbin --image=kennethreitz/httpbin -n mizu-tests
|
||||
kubectl create deployment httpbin2 --image=kennethreitz/httpbin -n mizu-tests
|
||||
|
||||
echo "Creating httpbin service"
|
||||
kubectl create deployment httpbin --image=kennethreitz/httpbin -n mizu-tests2
|
||||
|
||||
echo "Creating httpbin services"
|
||||
kubectl expose deployment httpbin --type=NodePort --port=80 -n mizu-tests
|
||||
kubectl expose deployment httpbin2 --type=NodePort --port=80 -n mizu-tests
|
||||
|
||||
kubectl expose deployment httpbin --type=NodePort --port=80 -n mizu-tests2
|
||||
|
||||
echo "Starting proxy"
|
||||
kubectl proxy --port=8080 &
|
||||
|
||||
@@ -1,34 +1,43 @@
|
||||
package acceptanceTests
|
||||
|
||||
import (
|
||||
"archive/zip"
|
||||
"bytes"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"os/exec"
|
||||
"path"
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
|
||||
func TestTapAndFetch(t *testing.T) {
|
||||
func TestTap(t *testing.T) {
|
||||
if testing.Short() {
|
||||
t.Skip("ignored acceptance test")
|
||||
}
|
||||
|
||||
tests := []int{1, 100}
|
||||
tests := []int{50}
|
||||
|
||||
for _, entriesCount := range tests {
|
||||
t.Run(fmt.Sprintf("%d", entriesCount), func(t *testing.T) {
|
||||
cliPath, cliPathErr := GetCliPath()
|
||||
cliPath, cliPathErr := getCliPath()
|
||||
if cliPathErr != nil {
|
||||
t.Errorf("failed to get cli path, err: %v", cliPathErr)
|
||||
return
|
||||
}
|
||||
|
||||
tapCmdArgs := GetDefaultTapCommandArgs()
|
||||
tapCmdArgs := getDefaultTapCommandArgs()
|
||||
|
||||
tapNamespace := getDefaultTapNamespace()
|
||||
tapCmdArgs = append(tapCmdArgs, tapNamespace...)
|
||||
|
||||
tapCmd := exec.Command(cliPath, tapCmdArgs...)
|
||||
t.Logf("running command: %v", tapCmd.String())
|
||||
|
||||
t.Cleanup(func() {
|
||||
if err := CleanupCommand(tapCmd); err != nil {
|
||||
if err := cleanupCommand(tapCmd); err != nil {
|
||||
t.Logf("failed to cleanup tap command, err: %v", err)
|
||||
}
|
||||
})
|
||||
@@ -38,88 +47,607 @@ func TestTapAndFetch(t *testing.T) {
|
||||
return
|
||||
}
|
||||
|
||||
time.Sleep(30 * time.Second)
|
||||
apiServerUrl := getApiServerUrl(defaultApiServerPort)
|
||||
|
||||
proxyUrl := "http://localhost:8080/api/v1/namespaces/mizu-tests/services/httpbin/proxy/get"
|
||||
if err := waitTapPodsReady(apiServerUrl); err != nil {
|
||||
t.Errorf("failed to start tap pods on time, err: %v", err)
|
||||
return
|
||||
}
|
||||
|
||||
proxyUrl := getProxyUrl(defaultNamespaceName, defaultServiceName)
|
||||
for i := 0; i < entriesCount; i++ {
|
||||
if _, requestErr := ExecuteHttpRequest(proxyUrl); requestErr != nil {
|
||||
if _, requestErr := executeHttpGetRequest(fmt.Sprintf("%v/get", proxyUrl)); requestErr != nil {
|
||||
t.Errorf("failed to send proxy request, err: %v", requestErr)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
time.Sleep(5 * time.Second)
|
||||
timestamp := time.Now().UnixNano() / int64(time.Millisecond)
|
||||
entriesCheckFunc := func() error {
|
||||
timestamp := time.Now().UnixNano() / int64(time.Millisecond)
|
||||
|
||||
entriesUrl := fmt.Sprintf("http://localhost:8899/mizu/api/entries?limit=%v&operator=lt×tamp=%v", entriesCount, timestamp)
|
||||
requestResult, requestErr := ExecuteHttpRequest(entriesUrl)
|
||||
if requestErr != nil {
|
||||
t.Errorf("failed to get entries, err: %v", requestErr)
|
||||
return
|
||||
}
|
||||
|
||||
entries, ok := requestResult.([]interface{})
|
||||
if !ok {
|
||||
t.Errorf("invalid entries type")
|
||||
return
|
||||
}
|
||||
|
||||
if len(entries) != entriesCount {
|
||||
t.Errorf("unexpected entries result - Expected: %v, actual: %v", entriesCount, len(entries))
|
||||
return
|
||||
}
|
||||
|
||||
entry, ok := entries[0].(map[string]interface{})
|
||||
if !ok {
|
||||
t.Errorf("invalid entry type")
|
||||
return
|
||||
}
|
||||
|
||||
entryUrl := fmt.Sprintf("http://localhost:8899/mizu/api/entries/%v", entry["id"])
|
||||
requestResult, requestErr = ExecuteHttpRequest(entryUrl)
|
||||
if requestErr != nil {
|
||||
t.Errorf("failed to get entry, err: %v", requestErr)
|
||||
return
|
||||
}
|
||||
|
||||
if requestResult == nil {
|
||||
t.Errorf("unexpected nil entry result")
|
||||
return
|
||||
}
|
||||
|
||||
fetchCmdArgs := GetDefaultFetchCommandArgs()
|
||||
fetchCmd := exec.Command(cliPath, fetchCmdArgs...)
|
||||
t.Logf("running command: %v", fetchCmd.String())
|
||||
|
||||
t.Cleanup(func() {
|
||||
if err := CleanupCommand(fetchCmd); err != nil {
|
||||
t.Logf("failed to cleanup fetch command, err: %v", err)
|
||||
entries, err := getDBEntries(timestamp, entriesCount, 1*time.Second)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
})
|
||||
err = checkEntriesAtLeast(entries, 1)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
entry := entries[0]
|
||||
|
||||
if err := fetchCmd.Start(); err != nil {
|
||||
t.Errorf("failed to start fetch command, err: %v", err)
|
||||
return
|
||||
entryUrl := fmt.Sprintf("%v/entries/%v", apiServerUrl, entry["id"])
|
||||
requestResult, requestErr := executeHttpGetRequest(entryUrl)
|
||||
if requestErr != nil {
|
||||
return fmt.Errorf("failed to get entry, err: %v", requestErr)
|
||||
}
|
||||
|
||||
if requestResult == nil {
|
||||
return fmt.Errorf("unexpected nil entry result")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
time.Sleep(5 * time.Second)
|
||||
|
||||
harBytes, readFileErr := ioutil.ReadFile("./unknown_source.har")
|
||||
if readFileErr != nil {
|
||||
t.Errorf("failed to read har file, err: %v", readFileErr)
|
||||
return
|
||||
}
|
||||
|
||||
harEntries, err := GetEntriesFromHarBytes(harBytes)
|
||||
if err != nil {
|
||||
t.Errorf("failed to get entries from har, err: %v", err)
|
||||
return
|
||||
}
|
||||
|
||||
if len(harEntries) != entriesCount {
|
||||
t.Errorf("unexpected har entries result - Expected: %v, actual: %v", entriesCount, len(harEntries))
|
||||
if err := retriesExecute(shortRetriesCount, entriesCheckFunc); err != nil {
|
||||
t.Errorf("%v", err)
|
||||
return
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestTapGuiPort(t *testing.T) {
|
||||
if testing.Short() {
|
||||
t.Skip("ignored acceptance test")
|
||||
}
|
||||
|
||||
tests := []uint16{8898}
|
||||
|
||||
for _, guiPort := range tests {
|
||||
t.Run(fmt.Sprintf("%d", guiPort), func(t *testing.T) {
|
||||
cliPath, cliPathErr := getCliPath()
|
||||
if cliPathErr != nil {
|
||||
t.Errorf("failed to get cli path, err: %v", cliPathErr)
|
||||
return
|
||||
}
|
||||
|
||||
tapCmdArgs := getDefaultTapCommandArgs()
|
||||
|
||||
tapNamespace := getDefaultTapNamespace()
|
||||
tapCmdArgs = append(tapCmdArgs, tapNamespace...)
|
||||
|
||||
tapCmdArgs = append(tapCmdArgs, "-p", fmt.Sprintf("%d", guiPort))
|
||||
|
||||
tapCmd := exec.Command(cliPath, tapCmdArgs...)
|
||||
t.Logf("running command: %v", tapCmd.String())
|
||||
|
||||
t.Cleanup(func() {
|
||||
if err := cleanupCommand(tapCmd); err != nil {
|
||||
t.Logf("failed to cleanup tap command, err: %v", err)
|
||||
}
|
||||
})
|
||||
|
||||
if err := tapCmd.Start(); err != nil {
|
||||
t.Errorf("failed to start tap command, err: %v", err)
|
||||
return
|
||||
}
|
||||
|
||||
apiServerUrl := getApiServerUrl(guiPort)
|
||||
|
||||
if err := waitTapPodsReady(apiServerUrl); err != nil {
|
||||
t.Errorf("failed to start tap pods on time, err: %v", err)
|
||||
return
|
||||
}
|
||||
|
||||
runCypressTests(t, fmt.Sprintf("npx cypress run --spec \"cypress/integration/tests/GuiPort.js\" --env port=%d --config-file cypress/integration/configurations/Default.json", guiPort))
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestTapAllNamespaces(t *testing.T) {
|
||||
if testing.Short() {
|
||||
t.Skip("ignored acceptance test")
|
||||
}
|
||||
|
||||
expectedPods := []PodDescriptor{
|
||||
{Name: "httpbin", Namespace: "mizu-tests"},
|
||||
{Name: "httpbin2", Namespace: "mizu-tests"},
|
||||
{Name: "httpbin", Namespace: "mizu-tests2"},
|
||||
}
|
||||
|
||||
cliPath, cliPathErr := getCliPath()
|
||||
if cliPathErr != nil {
|
||||
t.Errorf("failed to get cli path, err: %v", cliPathErr)
|
||||
return
|
||||
}
|
||||
|
||||
tapCmdArgs := getDefaultTapCommandArgs()
|
||||
tapCmdArgs = append(tapCmdArgs, "-A")
|
||||
|
||||
tapCmd := exec.Command(cliPath, tapCmdArgs...)
|
||||
t.Logf("running command: %v", tapCmd.String())
|
||||
|
||||
t.Cleanup(func() {
|
||||
if err := cleanupCommand(tapCmd); err != nil {
|
||||
t.Logf("failed to cleanup tap command, err: %v", err)
|
||||
}
|
||||
})
|
||||
|
||||
if err := tapCmd.Start(); err != nil {
|
||||
t.Errorf("failed to start tap command, err: %v", err)
|
||||
return
|
||||
}
|
||||
|
||||
apiServerUrl := getApiServerUrl(defaultApiServerPort)
|
||||
|
||||
if err := waitTapPodsReady(apiServerUrl); err != nil {
|
||||
t.Errorf("failed to start tap pods on time, err: %v", err)
|
||||
return
|
||||
}
|
||||
|
||||
runCypressTests(t, fmt.Sprintf("npx cypress run --spec \"cypress/integration/tests/MultipleNamespaces.js\" --env name1=%v,name2=%v,name3=%v,namespace1=%v,namespace2=%v,namespace3=%v --config-file cypress/integration/configurations/Default.json",
|
||||
expectedPods[0].Name, expectedPods[1].Name, expectedPods[2].Name, expectedPods[0].Namespace, expectedPods[1].Namespace, expectedPods[2].Namespace))
|
||||
}
|
||||
|
||||
func TestTapMultipleNamespaces(t *testing.T) {
|
||||
if testing.Short() {
|
||||
t.Skip("ignored acceptance test")
|
||||
}
|
||||
|
||||
expectedPods := []PodDescriptor{
|
||||
{Name: "httpbin", Namespace: "mizu-tests"},
|
||||
{Name: "httpbin2", Namespace: "mizu-tests"},
|
||||
{Name: "httpbin", Namespace: "mizu-tests2"},
|
||||
}
|
||||
|
||||
cliPath, cliPathErr := getCliPath()
|
||||
if cliPathErr != nil {
|
||||
t.Errorf("failed to get cli path, err: %v", cliPathErr)
|
||||
return
|
||||
}
|
||||
|
||||
tapCmdArgs := getDefaultTapCommandArgs()
|
||||
var namespacesCmd []string
|
||||
for _, expectedPod := range expectedPods {
|
||||
namespacesCmd = append(namespacesCmd, "-n", expectedPod.Namespace)
|
||||
}
|
||||
tapCmdArgs = append(tapCmdArgs, namespacesCmd...)
|
||||
|
||||
tapCmd := exec.Command(cliPath, tapCmdArgs...)
|
||||
t.Logf("running command: %v", tapCmd.String())
|
||||
|
||||
t.Cleanup(func() {
|
||||
if err := cleanupCommand(tapCmd); err != nil {
|
||||
t.Logf("failed to cleanup tap command, err: %v", err)
|
||||
}
|
||||
})
|
||||
|
||||
if err := tapCmd.Start(); err != nil {
|
||||
t.Errorf("failed to start tap command, err: %v", err)
|
||||
return
|
||||
}
|
||||
|
||||
apiServerUrl := getApiServerUrl(defaultApiServerPort)
|
||||
|
||||
if err := waitTapPodsReady(apiServerUrl); err != nil {
|
||||
t.Errorf("failed to start tap pods on time, err: %v", err)
|
||||
return
|
||||
}
|
||||
|
||||
runCypressTests(t, fmt.Sprintf("npx cypress run --spec \"cypress/integration/tests/MultipleNamespaces.js\" --env name1=%v,name2=%v,name3=%v,namespace1=%v,namespace2=%v,namespace3=%v --config-file cypress/integration/configurations/Default.json",
|
||||
expectedPods[0].Name, expectedPods[1].Name, expectedPods[2].Name, expectedPods[0].Namespace, expectedPods[1].Namespace, expectedPods[2].Namespace))
|
||||
}
|
||||
|
||||
func TestTapRegex(t *testing.T) {
|
||||
if testing.Short() {
|
||||
t.Skip("ignored acceptance test")
|
||||
}
|
||||
|
||||
regexPodName := "httpbin2"
|
||||
expectedPods := []PodDescriptor{
|
||||
{Name: regexPodName, Namespace: "mizu-tests"},
|
||||
}
|
||||
|
||||
cliPath, cliPathErr := getCliPath()
|
||||
if cliPathErr != nil {
|
||||
t.Errorf("failed to get cli path, err: %v", cliPathErr)
|
||||
return
|
||||
}
|
||||
|
||||
tapCmdArgs := getDefaultTapCommandArgsWithRegex(regexPodName)
|
||||
|
||||
tapNamespace := getDefaultTapNamespace()
|
||||
tapCmdArgs = append(tapCmdArgs, tapNamespace...)
|
||||
|
||||
tapCmd := exec.Command(cliPath, tapCmdArgs...)
|
||||
t.Logf("running command: %v", tapCmd.String())
|
||||
|
||||
t.Cleanup(func() {
|
||||
if err := cleanupCommand(tapCmd); err != nil {
|
||||
t.Logf("failed to cleanup tap command, err: %v", err)
|
||||
}
|
||||
})
|
||||
|
||||
if err := tapCmd.Start(); err != nil {
|
||||
t.Errorf("failed to start tap command, err: %v", err)
|
||||
return
|
||||
}
|
||||
|
||||
apiServerUrl := getApiServerUrl(defaultApiServerPort)
|
||||
|
||||
if err := waitTapPodsReady(apiServerUrl); err != nil {
|
||||
t.Errorf("failed to start tap pods on time, err: %v", err)
|
||||
return
|
||||
}
|
||||
|
||||
runCypressTests(t, fmt.Sprintf("npx cypress run --spec \"cypress/integration/tests/Regex.js\" --env name=%v,namespace=%v --config-file cypress/integration/configurations/Default.json",
|
||||
expectedPods[0].Name, expectedPods[0].Namespace))
|
||||
}
|
||||
|
||||
func TestTapDryRun(t *testing.T) {
|
||||
if testing.Short() {
|
||||
t.Skip("ignored acceptance test")
|
||||
}
|
||||
|
||||
cliPath, cliPathErr := getCliPath()
|
||||
if cliPathErr != nil {
|
||||
t.Errorf("failed to get cli path, err: %v", cliPathErr)
|
||||
return
|
||||
}
|
||||
|
||||
tapCmdArgs := getDefaultTapCommandArgs()
|
||||
|
||||
tapNamespace := getDefaultTapNamespace()
|
||||
tapCmdArgs = append(tapCmdArgs, tapNamespace...)
|
||||
|
||||
tapCmdArgs = append(tapCmdArgs, "--dry-run")
|
||||
|
||||
tapCmd := exec.Command(cliPath, tapCmdArgs...)
|
||||
t.Logf("running command: %v", tapCmd.String())
|
||||
|
||||
if err := tapCmd.Start(); err != nil {
|
||||
t.Errorf("failed to start tap command, err: %v", err)
|
||||
return
|
||||
}
|
||||
|
||||
resultChannel := make(chan string, 1)
|
||||
|
||||
go func() {
|
||||
if err := tapCmd.Wait(); err != nil {
|
||||
resultChannel <- "fail"
|
||||
return
|
||||
}
|
||||
resultChannel <- "success"
|
||||
}()
|
||||
|
||||
go func() {
|
||||
time.Sleep(shortRetriesCount * time.Second)
|
||||
resultChannel <- "fail"
|
||||
}()
|
||||
|
||||
testResult := <-resultChannel
|
||||
if testResult != "success" {
|
||||
t.Errorf("unexpected result - dry run cmd not done")
|
||||
}
|
||||
}
|
||||
|
||||
func TestTapRedact(t *testing.T) {
|
||||
if testing.Short() {
|
||||
t.Skip("ignored acceptance test")
|
||||
}
|
||||
|
||||
cliPath, cliPathErr := getCliPath()
|
||||
if cliPathErr != nil {
|
||||
t.Errorf("failed to get cli path, err: %v", cliPathErr)
|
||||
return
|
||||
}
|
||||
|
||||
tapCmdArgs := getDefaultTapCommandArgs()
|
||||
|
||||
tapNamespace := getDefaultTapNamespace()
|
||||
tapCmdArgs = append(tapCmdArgs, tapNamespace...)
|
||||
|
||||
tapCmd := exec.Command(cliPath, tapCmdArgs...)
|
||||
t.Logf("running command: %v", tapCmd.String())
|
||||
|
||||
t.Cleanup(func() {
|
||||
if err := cleanupCommand(tapCmd); err != nil {
|
||||
t.Logf("failed to cleanup tap command, err: %v", err)
|
||||
}
|
||||
})
|
||||
|
||||
if err := tapCmd.Start(); err != nil {
|
||||
t.Errorf("failed to start tap command, err: %v", err)
|
||||
return
|
||||
}
|
||||
|
||||
apiServerUrl := getApiServerUrl(defaultApiServerPort)
|
||||
|
||||
if err := waitTapPodsReady(apiServerUrl); err != nil {
|
||||
t.Errorf("failed to start tap pods on time, err: %v", err)
|
||||
return
|
||||
}
|
||||
|
||||
proxyUrl := getProxyUrl(defaultNamespaceName, defaultServiceName)
|
||||
requestHeaders := map[string]string{"User-Header": "Mizu"}
|
||||
requestBody := map[string]string{"User": "Mizu"}
|
||||
for i := 0; i < defaultEntriesCount; i++ {
|
||||
if _, requestErr := executeHttpPostRequestWithHeaders(fmt.Sprintf("%v/post", proxyUrl), requestHeaders, requestBody); requestErr != nil {
|
||||
t.Errorf("failed to send proxy request, err: %v", requestErr)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
runCypressTests(t, fmt.Sprintf("npx cypress run --spec \"cypress/integration/tests/Redact.js\" --config-file cypress/integration/configurations/Default.json"))
|
||||
}
|
||||
|
||||
func TestTapNoRedact(t *testing.T) {
|
||||
if testing.Short() {
|
||||
t.Skip("ignored acceptance test")
|
||||
}
|
||||
|
||||
cliPath, cliPathErr := getCliPath()
|
||||
if cliPathErr != nil {
|
||||
t.Errorf("failed to get cli path, err: %v", cliPathErr)
|
||||
return
|
||||
}
|
||||
|
||||
tapCmdArgs := getDefaultTapCommandArgs()
|
||||
|
||||
tapNamespace := getDefaultTapNamespace()
|
||||
tapCmdArgs = append(tapCmdArgs, tapNamespace...)
|
||||
|
||||
tapCmdArgs = append(tapCmdArgs, "--no-redact")
|
||||
|
||||
tapCmd := exec.Command(cliPath, tapCmdArgs...)
|
||||
t.Logf("running command: %v", tapCmd.String())
|
||||
|
||||
t.Cleanup(func() {
|
||||
if err := cleanupCommand(tapCmd); err != nil {
|
||||
t.Logf("failed to cleanup tap command, err: %v", err)
|
||||
}
|
||||
})
|
||||
|
||||
if err := tapCmd.Start(); err != nil {
|
||||
t.Errorf("failed to start tap command, err: %v", err)
|
||||
return
|
||||
}
|
||||
|
||||
apiServerUrl := getApiServerUrl(defaultApiServerPort)
|
||||
|
||||
if err := waitTapPodsReady(apiServerUrl); err != nil {
|
||||
t.Errorf("failed to start tap pods on time, err: %v", err)
|
||||
return
|
||||
}
|
||||
|
||||
proxyUrl := getProxyUrl(defaultNamespaceName, defaultServiceName)
|
||||
requestHeaders := map[string]string{"User-Header": "Mizu"}
|
||||
requestBody := map[string]string{"User": "Mizu"}
|
||||
for i := 0; i < defaultEntriesCount; i++ {
|
||||
if _, requestErr := executeHttpPostRequestWithHeaders(fmt.Sprintf("%v/post", proxyUrl), requestHeaders, requestBody); requestErr != nil {
|
||||
t.Errorf("failed to send proxy request, err: %v", requestErr)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
runCypressTests(t, "npx cypress run --spec \"cypress/integration/tests/NoRedact.js\" --config-file cypress/integration/configurations/Default.json")
|
||||
}
|
||||
|
||||
func TestTapRegexMasking(t *testing.T) {
|
||||
if testing.Short() {
|
||||
t.Skip("ignored acceptance test")
|
||||
}
|
||||
|
||||
cliPath, cliPathErr := getCliPath()
|
||||
if cliPathErr != nil {
|
||||
t.Errorf("failed to get cli path, err: %v", cliPathErr)
|
||||
return
|
||||
}
|
||||
|
||||
tapCmdArgs := getDefaultTapCommandArgs()
|
||||
|
||||
tapNamespace := getDefaultTapNamespace()
|
||||
tapCmdArgs = append(tapCmdArgs, tapNamespace...)
|
||||
|
||||
tapCmdArgs = append(tapCmdArgs, "-r", "Mizu")
|
||||
|
||||
tapCmd := exec.Command(cliPath, tapCmdArgs...)
|
||||
t.Logf("running command: %v", tapCmd.String())
|
||||
|
||||
t.Cleanup(func() {
|
||||
if err := cleanupCommand(tapCmd); err != nil {
|
||||
t.Logf("failed to cleanup tap command, err: %v", err)
|
||||
}
|
||||
})
|
||||
|
||||
if err := tapCmd.Start(); err != nil {
|
||||
t.Errorf("failed to start tap command, err: %v", err)
|
||||
return
|
||||
}
|
||||
|
||||
apiServerUrl := getApiServerUrl(defaultApiServerPort)
|
||||
|
||||
if err := waitTapPodsReady(apiServerUrl); err != nil {
|
||||
t.Errorf("failed to start tap pods on time, err: %v", err)
|
||||
return
|
||||
}
|
||||
|
||||
proxyUrl := getProxyUrl(defaultNamespaceName, defaultServiceName)
|
||||
for i := 0; i < defaultEntriesCount; i++ {
|
||||
response, requestErr := http.Post(fmt.Sprintf("%v/post", proxyUrl), "text/plain", bytes.NewBufferString("Mizu"))
|
||||
if _, requestErr = executeHttpRequest(response, requestErr); requestErr != nil {
|
||||
t.Errorf("failed to send proxy request, err: %v", requestErr)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
runCypressTests(t, "npx cypress run --spec \"cypress/integration/tests/RegexMasking.js\" --config-file cypress/integration/configurations/Default.json")
|
||||
|
||||
}
|
||||
|
||||
func TestTapIgnoredUserAgents(t *testing.T) {
|
||||
if testing.Short() {
|
||||
t.Skip("ignored acceptance test")
|
||||
}
|
||||
|
||||
cliPath, cliPathErr := getCliPath()
|
||||
if cliPathErr != nil {
|
||||
t.Errorf("failed to get cli path, err: %v", cliPathErr)
|
||||
return
|
||||
}
|
||||
|
||||
tapCmdArgs := getDefaultTapCommandArgs()
|
||||
|
||||
tapNamespace := getDefaultTapNamespace()
|
||||
tapCmdArgs = append(tapCmdArgs, tapNamespace...)
|
||||
|
||||
ignoredUserAgentValue := "ignore"
|
||||
tapCmdArgs = append(tapCmdArgs, "--set", fmt.Sprintf("tap.ignored-user-agents=%v", ignoredUserAgentValue))
|
||||
|
||||
tapCmd := exec.Command(cliPath, tapCmdArgs...)
|
||||
t.Logf("running command: %v", tapCmd.String())
|
||||
|
||||
t.Cleanup(func() {
|
||||
if err := cleanupCommand(tapCmd); err != nil {
|
||||
t.Logf("failed to cleanup tap command, err: %v", err)
|
||||
}
|
||||
})
|
||||
|
||||
if err := tapCmd.Start(); err != nil {
|
||||
t.Errorf("failed to start tap command, err: %v", err)
|
||||
return
|
||||
}
|
||||
|
||||
apiServerUrl := getApiServerUrl(defaultApiServerPort)
|
||||
|
||||
if err := waitTapPodsReady(apiServerUrl); err != nil {
|
||||
t.Errorf("failed to start tap pods on time, err: %v", err)
|
||||
return
|
||||
}
|
||||
|
||||
proxyUrl := getProxyUrl(defaultNamespaceName, defaultServiceName)
|
||||
|
||||
ignoredUserAgentCustomHeader := "Ignored-User-Agent"
|
||||
headers := map[string]string{"User-Agent": ignoredUserAgentValue, ignoredUserAgentCustomHeader: ""}
|
||||
for i := 0; i < defaultEntriesCount; i++ {
|
||||
if _, requestErr := executeHttpGetRequestWithHeaders(fmt.Sprintf("%v/get", proxyUrl), headers); requestErr != nil {
|
||||
t.Errorf("failed to send proxy request, err: %v", requestErr)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
for i := 0; i < defaultEntriesCount; i++ {
|
||||
if _, requestErr := executeHttpGetRequest(fmt.Sprintf("%v/get", proxyUrl)); requestErr != nil {
|
||||
t.Errorf("failed to send proxy request, err: %v", requestErr)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
runCypressTests(t, "npx cypress run --spec \"cypress/integration/tests/IgnoredUserAgents.js\" --config-file cypress/integration/configurations/HugeMizu.json")
|
||||
}
|
||||
|
||||
func TestTapDumpLogs(t *testing.T) {
|
||||
if testing.Short() {
|
||||
t.Skip("ignored acceptance test")
|
||||
}
|
||||
|
||||
cliPath, cliPathErr := getCliPath()
|
||||
if cliPathErr != nil {
|
||||
t.Errorf("failed to get cli path, err: %v", cliPathErr)
|
||||
return
|
||||
}
|
||||
|
||||
tapCmdArgs := getDefaultTapCommandArgs()
|
||||
|
||||
tapNamespace := getDefaultTapNamespace()
|
||||
tapCmdArgs = append(tapCmdArgs, tapNamespace...)
|
||||
|
||||
tapCmdArgs = append(tapCmdArgs, "--set", "dump-logs=true")
|
||||
|
||||
tapCmd := exec.Command(cliPath, tapCmdArgs...)
|
||||
t.Logf("running command: %v", tapCmd.String())
|
||||
|
||||
if err := tapCmd.Start(); err != nil {
|
||||
t.Errorf("failed to start tap command, err: %v", err)
|
||||
return
|
||||
}
|
||||
|
||||
apiServerUrl := getApiServerUrl(defaultApiServerPort)
|
||||
|
||||
if err := waitTapPodsReady(apiServerUrl); err != nil {
|
||||
t.Errorf("failed to start tap pods on time, err: %v", err)
|
||||
return
|
||||
}
|
||||
|
||||
if err := cleanupCommand(tapCmd); err != nil {
|
||||
t.Errorf("failed to cleanup tap command, err: %v", err)
|
||||
return
|
||||
}
|
||||
|
||||
mizuFolderPath, mizuPathErr := getMizuFolderPath()
|
||||
if mizuPathErr != nil {
|
||||
t.Errorf("failed to get mizu folder path, err: %v", mizuPathErr)
|
||||
return
|
||||
}
|
||||
|
||||
files, readErr := ioutil.ReadDir(mizuFolderPath)
|
||||
if readErr != nil {
|
||||
t.Errorf("failed to read mizu folder files, err: %v", readErr)
|
||||
return
|
||||
}
|
||||
|
||||
var dumpLogsPath string
|
||||
for _, file := range files {
|
||||
fileName := file.Name()
|
||||
if strings.Contains(fileName, "mizu_logs") {
|
||||
dumpLogsPath = path.Join(mizuFolderPath, fileName)
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if dumpLogsPath == "" {
|
||||
t.Errorf("dump logs file not found")
|
||||
return
|
||||
}
|
||||
|
||||
zipReader, zipError := zip.OpenReader(dumpLogsPath)
|
||||
if zipError != nil {
|
||||
t.Errorf("failed to get zip reader, err: %v", zipError)
|
||||
return
|
||||
}
|
||||
|
||||
t.Cleanup(func() {
|
||||
if err := zipReader.Close(); err != nil {
|
||||
t.Logf("failed to close zip reader, err: %v", err)
|
||||
}
|
||||
})
|
||||
|
||||
var logsFileNames []string
|
||||
for _, file := range zipReader.File {
|
||||
logsFileNames = append(logsFileNames, file.Name)
|
||||
}
|
||||
|
||||
if !Contains(logsFileNames, "mizu.mizu-api-server.mizu-api-server.log") {
|
||||
t.Errorf("api server logs not found")
|
||||
return
|
||||
}
|
||||
|
||||
if !Contains(logsFileNames, "mizu.mizu-api-server.basenine.log") {
|
||||
t.Errorf("basenine logs not found")
|
||||
return
|
||||
}
|
||||
|
||||
if !Contains(logsFileNames, "mizu_cli.log") {
|
||||
t.Errorf("cli logs not found")
|
||||
return
|
||||
}
|
||||
|
||||
if !Contains(logsFileNames, "mizu_events.log") {
|
||||
t.Errorf("events logs not found")
|
||||
return
|
||||
}
|
||||
|
||||
if !ContainsPartOfValue(logsFileNames, "mizu.mizu-tapper-daemon-set") {
|
||||
t.Errorf("tapper logs not found")
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package acceptanceTests
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
@@ -9,10 +10,45 @@ import (
|
||||
"os"
|
||||
"os/exec"
|
||||
"path"
|
||||
"strings"
|
||||
"sync"
|
||||
"syscall"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/gorilla/websocket"
|
||||
"github.com/up9inc/mizu/shared"
|
||||
)
|
||||
|
||||
func GetCliPath() (string, error) {
|
||||
const (
|
||||
longRetriesCount = 100
|
||||
shortRetriesCount = 10
|
||||
defaultApiServerPort = shared.DefaultApiServerPort
|
||||
defaultNamespaceName = "mizu-tests"
|
||||
defaultServiceName = "httpbin"
|
||||
defaultEntriesCount = 50
|
||||
waitAfterTapPodsReady = 3 * time.Second
|
||||
cleanCommandTimeout = 1 * time.Minute
|
||||
)
|
||||
|
||||
type PodDescriptor struct {
|
||||
Name string
|
||||
Namespace string
|
||||
}
|
||||
|
||||
func isPodDescriptorInPodArray(pods []map[string]interface{}, podDescriptor PodDescriptor) bool {
|
||||
for _, pod := range pods {
|
||||
podNamespace := pod["namespace"].(string)
|
||||
podName := pod["name"].(string)
|
||||
|
||||
if podDescriptor.Namespace == podNamespace && strings.Contains(podName, podDescriptor.Name) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func getCliPath() (string, error) {
|
||||
dir, filePathErr := os.Getwd()
|
||||
if filePathErr != nil {
|
||||
return "", filePathErr
|
||||
@@ -22,34 +58,150 @@ func GetCliPath() (string, error) {
|
||||
return cliPath, nil
|
||||
}
|
||||
|
||||
func GetDefaultCommandArgs() []string {
|
||||
func getMizuFolderPath() (string, error) {
|
||||
home, homeDirErr := os.UserHomeDir()
|
||||
if homeDirErr != nil {
|
||||
return "", homeDirErr
|
||||
}
|
||||
|
||||
return path.Join(home, ".mizu"), nil
|
||||
}
|
||||
|
||||
func getConfigPath() (string, error) {
|
||||
mizuFolderPath, mizuPathError := getMizuFolderPath()
|
||||
if mizuPathError != nil {
|
||||
return "", mizuPathError
|
||||
}
|
||||
|
||||
return path.Join(mizuFolderPath, "config.yaml"), nil
|
||||
}
|
||||
|
||||
func getProxyUrl(namespace string, service string) string {
|
||||
return fmt.Sprintf("http://localhost:8080/api/v1/namespaces/%v/services/%v/proxy", namespace, service)
|
||||
}
|
||||
|
||||
func getApiServerUrl(port uint16) string {
|
||||
return fmt.Sprintf("http://localhost:%v", port)
|
||||
}
|
||||
|
||||
func getWebSocketUrl(port uint16) string {
|
||||
return fmt.Sprintf("ws://localhost:%v/ws", port)
|
||||
}
|
||||
|
||||
func getDefaultCommandArgs() []string {
|
||||
setFlag := "--set"
|
||||
telemetry := "telemetry=false"
|
||||
|
||||
return []string{setFlag, telemetry}
|
||||
}
|
||||
|
||||
func GetDefaultTapCommandArgs() []string {
|
||||
tapCommand := "tap"
|
||||
setFlag := "--set"
|
||||
namespaces := "tap.namespaces=mizu-tests"
|
||||
agentImage := "agent-image=gcr.io/up9-docker-hub/mizu/ci:0.0.0"
|
||||
imagePullPolicy := "image-pull-policy=Never"
|
||||
imagePullPolicy := "image-pull-policy=IfNotPresent"
|
||||
headless := "headless=true"
|
||||
|
||||
defaultCmdArgs := GetDefaultCommandArgs()
|
||||
|
||||
return append([]string{tapCommand, setFlag, namespaces, setFlag, agentImage, setFlag, imagePullPolicy}, defaultCmdArgs...)
|
||||
return []string{setFlag, telemetry, setFlag, agentImage, setFlag, imagePullPolicy, setFlag, headless}
|
||||
}
|
||||
|
||||
func GetDefaultFetchCommandArgs() []string {
|
||||
tapCommand := "fetch"
|
||||
|
||||
defaultCmdArgs := GetDefaultCommandArgs()
|
||||
func getDefaultTapCommandArgs() []string {
|
||||
tapCommand := "tap"
|
||||
defaultCmdArgs := getDefaultCommandArgs()
|
||||
|
||||
return append([]string{tapCommand}, defaultCmdArgs...)
|
||||
}
|
||||
|
||||
func JsonBytesToInterface(jsonBytes []byte) (interface{}, error) {
|
||||
func getDefaultTapCommandArgsWithRegex(regex string) []string {
|
||||
tapCommand := "tap"
|
||||
defaultCmdArgs := getDefaultCommandArgs()
|
||||
|
||||
return append([]string{tapCommand, regex}, defaultCmdArgs...)
|
||||
}
|
||||
|
||||
func getDefaultLogsCommandArgs() []string {
|
||||
logsCommand := "logs"
|
||||
defaultCmdArgs := getDefaultCommandArgs()
|
||||
|
||||
return append([]string{logsCommand}, defaultCmdArgs...)
|
||||
}
|
||||
|
||||
func getDefaultTapNamespace() []string {
|
||||
return []string{"-n", "mizu-tests"}
|
||||
}
|
||||
|
||||
func getDefaultConfigCommandArgs() []string {
|
||||
configCommand := "config"
|
||||
defaultCmdArgs := getDefaultCommandArgs()
|
||||
|
||||
return append([]string{configCommand}, defaultCmdArgs...)
|
||||
}
|
||||
|
||||
func getDefaultCleanCommandArgs() []string {
|
||||
cleanCommand := "clean"
|
||||
defaultCmdArgs := getDefaultCommandArgs()
|
||||
|
||||
return append([]string{cleanCommand}, defaultCmdArgs...)
|
||||
}
|
||||
|
||||
func getDefaultViewCommandArgs() []string {
|
||||
viewCommand := "view"
|
||||
defaultCmdArgs := getDefaultCommandArgs()
|
||||
|
||||
return append([]string{viewCommand}, defaultCmdArgs...)
|
||||
}
|
||||
|
||||
func runCypressTests(t *testing.T, cypressRunCmd string) {
|
||||
cypressCmd := exec.Command("bash", "-c", cypressRunCmd)
|
||||
t.Logf("running command: %v", cypressCmd.String())
|
||||
out, err := cypressCmd.Output()
|
||||
if err != nil {
|
||||
t.Errorf("%s", out)
|
||||
return
|
||||
}
|
||||
t.Logf("%s", out)
|
||||
}
|
||||
|
||||
func retriesExecute(retriesCount int, executeFunc func() error) error {
|
||||
var lastError interface{}
|
||||
|
||||
for i := 0; i < retriesCount; i++ {
|
||||
if err := tryExecuteFunc(executeFunc); err != nil {
|
||||
lastError = err
|
||||
|
||||
time.Sleep(1 * time.Second)
|
||||
continue
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
return fmt.Errorf("reached max retries count, retries count: %v, last err: %v", retriesCount, lastError)
|
||||
}
|
||||
|
||||
func tryExecuteFunc(executeFunc func() error) (err interface{}) {
|
||||
defer func() {
|
||||
if panicErr := recover(); panicErr != nil {
|
||||
err = panicErr
|
||||
}
|
||||
}()
|
||||
|
||||
return executeFunc()
|
||||
}
|
||||
|
||||
func waitTapPodsReady(apiServerUrl string) error {
|
||||
resolvingUrl := fmt.Sprintf("%v/status/connectedTappersCount", apiServerUrl)
|
||||
tapPodsReadyFunc := func() error {
|
||||
requestResult, requestErr := executeHttpGetRequest(resolvingUrl)
|
||||
if requestErr != nil {
|
||||
return requestErr
|
||||
}
|
||||
|
||||
connectedTappersCount := requestResult.(float64)
|
||||
if connectedTappersCount == 0 {
|
||||
return fmt.Errorf("no connected tappers running")
|
||||
}
|
||||
time.Sleep(waitAfterTapPodsReady)
|
||||
return nil
|
||||
}
|
||||
|
||||
return retriesExecute(longRetriesCount, tapPodsReadyFunc)
|
||||
}
|
||||
|
||||
func jsonBytesToInterface(jsonBytes []byte) (interface{}, error) {
|
||||
var result interface{}
|
||||
if parseErr := json.Unmarshal(jsonBytes, &result); parseErr != nil {
|
||||
return nil, parseErr
|
||||
@@ -58,23 +210,95 @@ func JsonBytesToInterface(jsonBytes []byte) (interface{}, error) {
|
||||
return result, nil
|
||||
}
|
||||
|
||||
func ExecuteHttpRequest(url string) (interface{}, error) {
|
||||
response, requestErr := http.Get(url)
|
||||
func executeHttpRequest(response *http.Response, requestErr error) (interface{}, error) {
|
||||
if requestErr != nil {
|
||||
return nil, requestErr
|
||||
} else if response.StatusCode != 200 {
|
||||
return nil, fmt.Errorf("invalid status code %v", response.StatusCode)
|
||||
}
|
||||
|
||||
defer func() { response.Body.Close() }()
|
||||
|
||||
data, readErr := ioutil.ReadAll(response.Body)
|
||||
if readErr != nil {
|
||||
return nil, readErr
|
||||
}
|
||||
|
||||
return JsonBytesToInterface(data)
|
||||
return jsonBytesToInterface(data)
|
||||
}
|
||||
|
||||
func CleanupCommand(cmd *exec.Cmd) error {
|
||||
func executeHttpGetRequestWithHeaders(url string, headers map[string]string) (interface{}, error) {
|
||||
request, err := http.NewRequest(http.MethodGet, url, nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
for headerKey, headerValue := range headers {
|
||||
request.Header.Add(headerKey, headerValue)
|
||||
}
|
||||
|
||||
client := &http.Client{}
|
||||
response, requestErr := client.Do(request)
|
||||
return executeHttpRequest(response, requestErr)
|
||||
}
|
||||
|
||||
func executeHttpGetRequest(url string) (interface{}, error) {
|
||||
response, requestErr := http.Get(url)
|
||||
return executeHttpRequest(response, requestErr)
|
||||
}
|
||||
|
||||
func executeHttpPostRequestWithHeaders(url string, headers map[string]string, body interface{}) (interface{}, error) {
|
||||
requestBody, jsonErr := json.Marshal(body)
|
||||
if jsonErr != nil {
|
||||
return nil, jsonErr
|
||||
}
|
||||
|
||||
request, err := http.NewRequest(http.MethodPost, url, bytes.NewBuffer(requestBody))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
request.Header.Add("Content-Type", "application/json")
|
||||
for headerKey, headerValue := range headers {
|
||||
request.Header.Add(headerKey, headerValue)
|
||||
}
|
||||
|
||||
client := &http.Client{}
|
||||
response, requestErr := client.Do(request)
|
||||
return executeHttpRequest(response, requestErr)
|
||||
}
|
||||
|
||||
func runMizuClean() error {
|
||||
cliPath, err := getCliPath()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
cleanCmdArgs := getDefaultCleanCommandArgs()
|
||||
|
||||
cleanCmd := exec.Command(cliPath, cleanCmdArgs...)
|
||||
|
||||
commandDone := make(chan error)
|
||||
go func() {
|
||||
if err := cleanCmd.Run(); err != nil {
|
||||
commandDone <- err
|
||||
}
|
||||
commandDone <- nil
|
||||
}()
|
||||
|
||||
select {
|
||||
case err = <-commandDone:
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
case <-time.After(cleanCommandTimeout):
|
||||
return errors.New("clean command timed out")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func cleanupCommand(cmd *exec.Cmd) error {
|
||||
if err := cmd.Process.Signal(syscall.SIGQUIT); err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -86,28 +310,114 @@ func CleanupCommand(cmd *exec.Cmd) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func GetEntriesFromHarBytes(harBytes []byte) ([]interface{}, error){
|
||||
harInterface, convertErr := JsonBytesToInterface(harBytes)
|
||||
if convertErr != nil {
|
||||
return nil, convertErr
|
||||
func getPods(tapStatusInterface interface{}) ([]map[string]interface{}, error) {
|
||||
tapPodsInterface := tapStatusInterface.([]interface{})
|
||||
|
||||
var pods []map[string]interface{}
|
||||
for _, podInterface := range tapPodsInterface {
|
||||
pods = append(pods, podInterface.(map[string]interface{}))
|
||||
}
|
||||
|
||||
har, ok := harInterface.(map[string]interface{})
|
||||
if !ok {
|
||||
return nil, errors.New("invalid har type")
|
||||
}
|
||||
|
||||
harLogInterface := har["log"]
|
||||
harLog, ok := harLogInterface.(map[string]interface{})
|
||||
if !ok {
|
||||
return nil, errors.New("invalid har log type")
|
||||
}
|
||||
|
||||
harEntriesInterface := harLog["entries"]
|
||||
harEntries, ok := harEntriesInterface.([]interface{})
|
||||
if !ok {
|
||||
return nil, errors.New("invalid har entries type")
|
||||
}
|
||||
|
||||
return harEntries, nil
|
||||
return pods, nil
|
||||
}
|
||||
|
||||
func getLogsPath() (string, error) {
|
||||
dir, filePathErr := os.Getwd()
|
||||
if filePathErr != nil {
|
||||
return "", filePathErr
|
||||
}
|
||||
|
||||
logsPath := path.Join(dir, "mizu_logs.zip")
|
||||
return logsPath, nil
|
||||
}
|
||||
|
||||
// waitTimeout waits for the waitgroup for the specified max timeout.
|
||||
// Returns true if waiting timed out.
|
||||
func waitTimeout(wg *sync.WaitGroup, timeout time.Duration) bool {
|
||||
channel := make(chan struct{})
|
||||
go func() {
|
||||
defer close(channel)
|
||||
wg.Wait()
|
||||
}()
|
||||
select {
|
||||
case <-channel:
|
||||
return false // completed normally
|
||||
case <-time.After(timeout):
|
||||
return true // timed out
|
||||
}
|
||||
}
|
||||
|
||||
// checkEntriesAtLeast checks whether the number of entries greater than or equal to n
|
||||
func checkEntriesAtLeast(entries []map[string]interface{}, n int) error {
|
||||
if len(entries) < n {
|
||||
return fmt.Errorf("Unexpected entries result - Expected more than %d entries", n-1)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// getDBEntries retrieves the entries from the database before the given timestamp.
|
||||
// Also limits the results according to the limit parameter.
|
||||
// Timeout for the WebSocket connection is defined by the timeout parameter.
|
||||
func getDBEntries(timestamp int64, limit int, timeout time.Duration) (entries []map[string]interface{}, err error) {
|
||||
query := fmt.Sprintf("timestamp < %d and limit(%d)", timestamp, limit)
|
||||
webSocketUrl := getWebSocketUrl(defaultApiServerPort)
|
||||
|
||||
var connection *websocket.Conn
|
||||
connection, _, err = websocket.DefaultDialer.Dial(webSocketUrl, nil)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
defer connection.Close()
|
||||
|
||||
handleWSConnection := func(wg *sync.WaitGroup) {
|
||||
defer wg.Done()
|
||||
for {
|
||||
_, message, err := connection.ReadMessage()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
var data map[string]interface{}
|
||||
if err = json.Unmarshal([]byte(message), &data); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
if data["messageType"] == "entry" {
|
||||
entries = append(entries, data)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
err = connection.WriteMessage(websocket.TextMessage, []byte(query))
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
var wg sync.WaitGroup
|
||||
go handleWSConnection(&wg)
|
||||
wg.Add(1)
|
||||
|
||||
waitTimeout(&wg, timeout)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func Contains(slice []string, containsValue string) bool {
|
||||
for _, sliceValue := range slice {
|
||||
if sliceValue == containsValue {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
func ContainsPartOfValue(slice []string, containsValue string) bool {
|
||||
for _, sliceValue := range slice {
|
||||
if strings.Contains(sliceValue, containsValue) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
6
agent/.snyk
Normal file
6
agent/.snyk
Normal file
@@ -0,0 +1,6 @@
|
||||
# Snyk (https://snyk.io) policy file, patches or ignores known vulnerabilities.
|
||||
version: v1.14.0
|
||||
ignore:
|
||||
SNYK-GOLANG-GITHUBCOMGINGONICGIN-1041736:
|
||||
- '*':
|
||||
reason: None Given
|
||||
@@ -1,20 +0,0 @@
|
||||
# mizu agent
|
||||
Agent for MIZU (API server and tapper)
|
||||
Basic APIs:
|
||||
* /fetch - retrieve traffic data
|
||||
* /stats - retrieve statistics of collected data
|
||||
* /viewer - web ui
|
||||
|
||||
## Remote Debugging
|
||||
### Setup remote debugging
|
||||
1. Run `go get github.com/go-delve/delve/cmd/dlv`
|
||||
2. Create a "Go Remote" run/debug configuration in Intellij, set to localhost:2345
|
||||
3. Build and push a debug image using
|
||||
`docker build . -t gcr.io/up9-docker-hub/mizu/debug:latest -f debug.Dockerfile && docker push gcr.io/up9-docker-hub/mizu/debug:latest`
|
||||
|
||||
### 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}`
|
||||
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.
|
||||
|
||||
<small>Do note that dlv won't start the api until a debugger connects to it.</small>
|
||||
26
agent/go.mod
26
agent/go.mod
@@ -3,30 +3,36 @@ module mizuserver
|
||||
go 1.16
|
||||
|
||||
require (
|
||||
github.com/beevik/etree v1.1.0
|
||||
github.com/antelman107/net-wait-go v0.0.0-20210623112055-cf684aebda7b
|
||||
github.com/chanced/openapi v0.0.6
|
||||
github.com/djherbis/atime v1.0.0
|
||||
github.com/fsnotify/fsnotify v1.4.9
|
||||
github.com/getkin/kin-openapi v0.76.0
|
||||
github.com/gin-contrib/static v0.0.1
|
||||
github.com/gin-gonic/gin v1.7.2
|
||||
github.com/gin-gonic/gin v1.7.7
|
||||
github.com/go-playground/locales v0.13.0
|
||||
github.com/go-playground/universal-translator v0.17.0
|
||||
github.com/go-playground/validator/v10 v10.5.0
|
||||
github.com/google/martian v2.1.0+incompatible
|
||||
github.com/google/uuid v1.1.2
|
||||
github.com/gorilla/websocket v1.4.2
|
||||
github.com/op/go-logging v0.0.0-20160315200505-970db520ece7
|
||||
github.com/orcaman/concurrent-map v0.0.0-20210106121528-16402b402231
|
||||
github.com/ory/kratos-client-go v0.8.2-alpha.1
|
||||
github.com/patrickmn/go-cache v2.1.0+incompatible
|
||||
github.com/romana/rlog v0.0.0-20171115192701-f018bc92e7d7
|
||||
github.com/stretchr/testify v1.7.0
|
||||
github.com/up9inc/basenine/client/go v0.0.0-20220117225014-f91ba3692a36
|
||||
github.com/up9inc/mizu/shared v0.0.0
|
||||
github.com/up9inc/mizu/tap v0.0.0
|
||||
github.com/up9inc/mizu/tap/api v0.0.0
|
||||
github.com/yalp/jsonpath v0.0.0-20180802001716-5cc68e5049a0
|
||||
go.mongodb.org/mongo-driver v1.5.1
|
||||
gorm.io/driver/sqlite v1.1.4
|
||||
gorm.io/gorm v1.21.8
|
||||
k8s.io/api v0.21.0
|
||||
k8s.io/apimachinery v0.21.0
|
||||
k8s.io/client-go v0.21.0
|
||||
golang.org/x/text v0.3.5 // indirect
|
||||
k8s.io/api v0.21.2
|
||||
k8s.io/apimachinery v0.21.2
|
||||
k8s.io/client-go v0.21.2
|
||||
)
|
||||
|
||||
replace github.com/up9inc/mizu/shared v0.0.0 => ../shared
|
||||
|
||||
replace github.com/up9inc/mizu/tap v0.0.0 => ../tap
|
||||
|
||||
replace github.com/up9inc/mizu/tap/api v0.0.0 => ../tap/api
|
||||
|
||||
513
agent/go.sum
513
agent/go.sum
@@ -8,20 +8,32 @@ cloud.google.com/go v0.46.3/go.mod h1:a6bKKbmY7er1mI7TEI4lsAkts/mkhTSZK8w33B4RAg
|
||||
cloud.google.com/go v0.50.0/go.mod h1:r9sluTvynVuxRIOHXQEHMFffphuXHOMZMycpNR5e6To=
|
||||
cloud.google.com/go v0.52.0/go.mod h1:pXajvRH/6o3+F9jDHZWQ5PbGhn+o8w9qiu/CffaVdO4=
|
||||
cloud.google.com/go v0.53.0/go.mod h1:fp/UouUEsRkN6ryDKNW/Upv/JBKnv6WDthjR6+vze6M=
|
||||
cloud.google.com/go v0.54.0 h1:3ithwDMr7/3vpAMXiH+ZQnYbuIsh+OPhUPMFC9enmn0=
|
||||
cloud.google.com/go v0.54.0/go.mod h1:1rq2OEkV3YMf6n/9ZvGWI3GWw0VoqH/1x2nd8Is/bPc=
|
||||
cloud.google.com/go v0.56.0/go.mod h1:jr7tqZxxKOVYizybht9+26Z/gUq7tiRzu+ACVAMbKVk=
|
||||
cloud.google.com/go v0.57.0/go.mod h1:oXiQ6Rzq3RAkkY7N6t3TcE6jE+CIBBbA36lwQ1JyzZs=
|
||||
cloud.google.com/go v0.62.0/go.mod h1:jmCYTdRCQuc1PHIIJ/maLInMho30T/Y0M4hTdTShOYc=
|
||||
cloud.google.com/go v0.65.0 h1:Dg9iHVQfrhq82rUNu9ZxUDrJLaxFUe/HlCVaLyRruq8=
|
||||
cloud.google.com/go v0.65.0/go.mod h1:O5N8zS7uWy9vkA9vayVHs65eM1ubvY4h553ofrNHObY=
|
||||
cloud.google.com/go/bigquery v1.0.1/go.mod h1:i/xbL2UlR5RvWAURpBYZTtm/cXjCha9lbfbpx4poX+o=
|
||||
cloud.google.com/go/bigquery v1.3.0/go.mod h1:PjpwJnslEMmckchkHFfq+HTD2DmtT67aNFKH1/VBDHE=
|
||||
cloud.google.com/go/bigquery v1.4.0/go.mod h1:S8dzgnTigyfTmLBfrtrhyYhwRxG72rYxvftPBK2Dvzc=
|
||||
cloud.google.com/go/bigquery v1.5.0/go.mod h1:snEHRnqQbz117VIFhE8bmtwIDY80NLUZUMb4Nv6dBIg=
|
||||
cloud.google.com/go/bigquery v1.7.0/go.mod h1://okPTzCYNXSlb24MZs83e2Do+h+VXtc4gLoIoXIAPc=
|
||||
cloud.google.com/go/bigquery v1.8.0/go.mod h1:J5hqkt3O0uAFnINi6JXValWIb1v0goeZM77hZzJN/fQ=
|
||||
cloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7TKLgZqpHYE=
|
||||
cloud.google.com/go/datastore v1.1.0/go.mod h1:umbIZjpQpHh4hmRpGhH4tLFup+FVzqBi1b3c64qFpCk=
|
||||
cloud.google.com/go/firestore v1.1.0/go.mod h1:ulACoGHTpvq5r8rxGJ4ddJZBZqakUQqClKRT5SZwBmk=
|
||||
cloud.google.com/go/pubsub v1.0.1/go.mod h1:R0Gpsv3s54REJCy4fxDixWD93lHJMoZTyQ2kNxGRt3I=
|
||||
cloud.google.com/go/pubsub v1.1.0/go.mod h1:EwwdRX2sKPjnvnqCa270oGRyludottCI76h+R3AArQw=
|
||||
cloud.google.com/go/pubsub v1.2.0/go.mod h1:jhfEVHT8odbXTkndysNHCcx0awwzvfOlguIAii9o8iA=
|
||||
cloud.google.com/go/pubsub v1.3.1/go.mod h1:i+ucay31+CNRpDW4Lu78I4xXG+O1r/MAHgjpRVR+TSU=
|
||||
cloud.google.com/go/storage v1.0.0/go.mod h1:IhtSnM/ZTZV8YYJWCY8RULGVqBDmpoyjwiyrjsg+URw=
|
||||
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.8.0/go.mod h1:Wv1Oy7z6Yz3DshWRJFhqM/UCfaWIRTdp0RXyy7KQOVs=
|
||||
cloud.google.com/go/storage v1.10.0/go.mod h1:FLPqc6j+Ki4BU591ie1oL6qBQGu2Bl/tZ9ullr3+Kg0=
|
||||
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/go.mod h1:r+4oMnoxhatjLLJ6zxSWATqVooLgysK6ZNox3g/xq24=
|
||||
github.com/Azure/go-autorest/autorest v0.11.12 h1:gI8ytXbxMfI+IVbI9mP2JGCTXIuhHLgRlvQ9X4PsnHE=
|
||||
@@ -38,59 +50,161 @@ 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/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/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/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/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-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-20190717042225-c3de453c63f4/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
|
||||
github.com/andreyvit/diff v0.0.0-20170406064948-c7f18ee00883/go.mod h1:rCTlJbsFo29Kk6CurOXKm700vrz8f0KW0JNfpkRJY/8=
|
||||
github.com/antelman107/net-wait-go v0.0.0-20210623112055-cf684aebda7b h1:8m+eVxVVDDyJFidv7Ck1OwqnDaQR6pTSRGlCC2Dnw0A=
|
||||
github.com/antelman107/net-wait-go v0.0.0-20210623112055-cf684aebda7b/go.mod h1:+tQQjzrp2501Nd6JXrb9s/XsNvFK3ZbxOnCdQl/vDRo=
|
||||
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-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/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/go.mod h1:r8Aw8JqVegEf0w2fDnATrX9VpkMcyFeM0FhwO62wh+A=
|
||||
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.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw=
|
||||
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/blang/semver v3.5.1+incompatible/go.mod h1:kRBLl5iJ+tD4TcOOxsy/0fnwebNt5EWlYSAyrTnjyyk=
|
||||
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/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/chanced/cmpjson v0.0.0-20210415035445-da9262c1f20a h1:zG6t+4krPXcCKtLbjFvAh+fKN1d0qfD+RaCj+680OU8=
|
||||
github.com/chanced/cmpjson v0.0.0-20210415035445-da9262c1f20a/go.mod h1:yhcmlFk1hxuZ+5XZbupzT/cEm/eE4ZvWbmsW1+Q/aZE=
|
||||
github.com/chanced/dynamic v0.0.0-20210502140838-c010b5fc3e44 h1:4NOJMtvZaOA6cI2gkIuXk/2b5KTOvm/R4zyPy/yLCM4=
|
||||
github.com/chanced/dynamic v0.0.0-20210502140838-c010b5fc3e44/go.mod h1:XVNfXN5kgZST4PQ0W/oBAHJku2OteCeHxjAbvfd0ARM=
|
||||
github.com/chanced/openapi v0.0.6 h1:2giGS47+T8/7MN2hfRHSIUPx86Qksk+J/ciIWcAM7hY=
|
||||
github.com/chanced/openapi v0.0.6/go.mod h1:SxE2VMLPw+T7Vq8nwbVVhDF2PigvRF4n5XyqsVpRJGU=
|
||||
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/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU=
|
||||
github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
|
||||
github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc=
|
||||
github.com/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/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-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/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.11/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
|
||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/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/dgryski/go-sip13 v0.0.0-20181026042036-e10d5fee7954/go.mod h1:vAd38F8PWV+bWy6jNmig1y/TA+kYO4g3RSRF0IAv0no=
|
||||
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/docker/go-units v0.4.0 h1:3uh0PgVws3nIA0Q+MwDC8yjEPf9zjRfZZWXZYDct3Tw=
|
||||
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/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk=
|
||||
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/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.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
|
||||
github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
|
||||
github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98=
|
||||
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/v5 v5.6.0 h1:b91NhWfaz02IuVxO9faSllyAtNXHMPkC5J8sJCLunww=
|
||||
github.com/evanphx/json-patch/v5 v5.6.0/go.mod h1:G79N1coSVB93tBe7j6PhzjmR3/2VvlbKOFpnXhI9Bw4=
|
||||
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/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/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/fvbommel/sortorder v1.0.1/go.mod h1:uk88iVf1ovNn1iLfgUVU2F9o5eO30ui720w+kxuqRs0=
|
||||
github.com/getkin/kin-openapi v0.76.0 h1:j77zg3Ec+k+r+GA3d8hBoXpAc6KX9TbBPrwQGBIy2sY=
|
||||
github.com/getkin/kin-openapi v0.76.0/go.mod h1:660oXbgy5JFMKreazJaQTw7o+X00qeSyhcnluiMv+Xg=
|
||||
github.com/ghodss/yaml v1.0.0 h1:wQHKEahhL6wmXdzwWG11gIVCkOv05bNOh+Rxn0yngAk=
|
||||
github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
|
||||
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/gin-gonic/gin v1.7.7 h1:3DoBmSbJbZAWqXJC3SLjAPfutPJJRN1U5pALB7EeTTs=
|
||||
github.com/gin-gonic/gin v1.7.7/go.mod h1:axIBovoeJpVj8S3BwE0uPMTeReE4+AfFtqpqaZ1qq1U=
|
||||
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/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-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.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.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/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.3/go.mod h1:Pl9vOtqEWErmShwVjC8pYs9cog34VGT37dQOVbmoatg=
|
||||
github.com/go-openapi/jsonpointer v0.19.5 h1:gZr+CIYByUqjcgeLXnQu2gHYQC9o73G2XUeOFYEICuY=
|
||||
github.com/go-openapi/jsonpointer v0.19.5/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.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.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.5 h1:lTz6Ys4CmqqCQmZPBlbQENR1/GucA2bzYTE12Pw4tFY=
|
||||
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-playground/assert/v2 v2.0.1 h1:MsBgLAaY856+nPRTKrp3/OZK38U/wa0CcBYNjji3q3A=
|
||||
github.com/go-playground/assert/v2 v2.0.1/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4=
|
||||
github.com/go-playground/locales v0.13.0 h1:HyWk6mgj5qFqCT5fjGBuRArbVDfE4hi8+e8ceBS/t7Q=
|
||||
@@ -101,66 +215,57 @@ github.com/go-playground/validator/v10 v10.2.0/go.mod h1:uOYAAleCW8F/7oMFd6aG0GO
|
||||
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/go.mod h1:xm76BBt941f7yWdGnI2DVPFFg1UK3YY04qifoXU3lOk=
|
||||
github.com/go-sql-driver/mysql v1.5.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg=
|
||||
github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY=
|
||||
github.com/gobuffalo/attrs v0.0.0-20190224210810-a9411de4debd/go.mod h1:4duuawTqi2wkkpB4ePgWMaai6/Kc6WEz83bhFwpHzj0=
|
||||
github.com/gobuffalo/depgen v0.0.0-20190329151759-d478694a28d3/go.mod h1:3STtPUQYuzV0gBVOY3vy6CfMm/ljR4pABfrTeHNLHUY=
|
||||
github.com/gobuffalo/depgen v0.1.0/go.mod h1:+ifsuy7fhi15RWncXQQKjWS9JPkdah5sZvtHc2RXGlg=
|
||||
github.com/gobuffalo/envy v1.6.15/go.mod h1:n7DRkBerg/aorDM8kbduw5dN3oXGswK5liaSCx4T5NI=
|
||||
github.com/gobuffalo/envy v1.7.0/go.mod h1:n7DRkBerg/aorDM8kbduw5dN3oXGswK5liaSCx4T5NI=
|
||||
github.com/gobuffalo/flect v0.1.0/go.mod h1:d2ehjJqGOH/Kjqcoz+F7jHTBbmDb38yXA598Hb50EGs=
|
||||
github.com/gobuffalo/flect v0.1.1/go.mod h1:8JCgGVbRjJhVgD6399mQr4fx5rRfGKVzFjbj6RE/9UI=
|
||||
github.com/gobuffalo/flect v0.1.3/go.mod h1:8JCgGVbRjJhVgD6399mQr4fx5rRfGKVzFjbj6RE/9UI=
|
||||
github.com/gobuffalo/genny v0.0.0-20190329151137-27723ad26ef9/go.mod h1:rWs4Z12d1Zbf19rlsn0nurr75KqhYp52EAGGxTbBhNk=
|
||||
github.com/gobuffalo/genny v0.0.0-20190403191548-3ca520ef0d9e/go.mod h1:80lIj3kVJWwOrXWWMRzzdhW3DsrdjILVil/SFKBzF28=
|
||||
github.com/gobuffalo/genny v0.1.0/go.mod h1:XidbUqzak3lHdS//TPu2OgiFB+51Ur5f7CSnXZ/JDvo=
|
||||
github.com/gobuffalo/genny v0.1.1/go.mod h1:5TExbEyY48pfunL4QSXxlDOmdsD44RRq4mVZ0Ex28Xk=
|
||||
github.com/gobuffalo/gitgen v0.0.0-20190315122116-cc086187d211/go.mod h1:vEHJk/E9DmhejeLeNt7UVvlSGv3ziL+djtTr3yyzcOw=
|
||||
github.com/gobuffalo/gogen v0.0.0-20190315121717-8f38393713f5/go.mod h1:V9QVDIxsgKNZs6L2IYiGR8datgMhB577vzTDqypH360=
|
||||
github.com/gobuffalo/gogen v0.1.0/go.mod h1:8NTelM5qd8RZ15VjQTFkAW6qOMx5wBbW4dSCS3BY8gg=
|
||||
github.com/gobuffalo/gogen v0.1.1/go.mod h1:y8iBtmHmGc4qa3urIyo1shvOD8JftTtfcKi+71xfDNE=
|
||||
github.com/gobuffalo/logger v0.0.0-20190315122211-86e12af44bc2/go.mod h1:QdxcLw541hSGtBnhUc4gaNIXRjiDppFGaDqzbrBd3v8=
|
||||
github.com/gobuffalo/mapi v1.0.1/go.mod h1:4VAGh89y6rVOvm5A8fKFxYG+wIW6LO1FMTG9hnKStFc=
|
||||
github.com/gobuffalo/mapi v1.0.2/go.mod h1:4VAGh89y6rVOvm5A8fKFxYG+wIW6LO1FMTG9hnKStFc=
|
||||
github.com/gobuffalo/packd v0.0.0-20190315124812-a385830c7fc0/go.mod h1:M2Juc+hhDXf/PnmBANFCqx4DM3wRbgDvnVWeG2RIxq4=
|
||||
github.com/gobuffalo/packd v0.1.0/go.mod h1:M2Juc+hhDXf/PnmBANFCqx4DM3wRbgDvnVWeG2RIxq4=
|
||||
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/syncx v0.0.0-20190224160051-33c29581e754/go.mod h1:HhnNqWY95UYwwW3uSASeV7vtgYkT2t16hJgV3AEPUpw=
|
||||
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.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/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q=
|
||||
github.com/golang-jwt/jwt/v4 v4.1.0 h1:XUgk2Ex5veyVFVeLm0xhusUTQybEbexJXrvPNOKkSY0=
|
||||
github.com/golang-jwt/jwt/v4 v4.1.0/go.mod h1:/xlHOz8bRuivTWchD4jCa+NbatV+wEUSzwAxVc6locg=
|
||||
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
|
||||
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-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/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.3.1/go.mod h1:sBzyDLLjw3U8JLTeZvSv8jJB+tU5PVekmnlKIyFUx0Y=
|
||||
github.com/golang/mock v1.4.0/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw=
|
||||
github.com/golang/mock v1.4.1/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw=
|
||||
github.com/golang/mock v1.4.3/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw=
|
||||
github.com/golang/mock v1.4.4/go.mod h1:l3mdAwkq5BuhzHwde/uurv3sEJeZMXNpwsxVWU71h+4=
|
||||
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
||||
github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
||||
github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
||||
github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw=
|
||||
github.com/golang/protobuf v1.3.4/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw=
|
||||
github.com/golang/protobuf v1.3.5/go.mod h1:6O5/vntMXwX2lRkT1hjjk0nAC1IDOTvTlVgjlRvqsdk=
|
||||
github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8=
|
||||
github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA=
|
||||
github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs=
|
||||
github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w=
|
||||
github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0=
|
||||
github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8=
|
||||
github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
|
||||
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/snappy v0.0.1/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
|
||||
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 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.3.0/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.1/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.1/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.5/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/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/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
|
||||
@@ -168,48 +273,75 @@ github.com/google/gopacket v1.1.19 h1:ves8RnFZPGiFnTS0uPQStjwru6uO6h+nlr9j6fL7kF
|
||||
github.com/google/gopacket v1.1.19/go.mod h1:iJ8V8n6KS+z2U1A8pUwu8bW5SyEMkXJB8Yo/Vo+TKTo=
|
||||
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/v3 v3.0.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0=
|
||||
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-20191218002539-d4f498aebedc/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-20200430221834-fc25d7d30c6d/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
|
||||
github.com/google/pprof v0.0.0-20200708004538-1a94d8640e99/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
|
||||
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.2 h1:EVhdT+1Kseyi1/pUmXKaFxYsDNy9RQYkMWRH68J/W7Y=
|
||||
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.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/go.mod h1:LRhVm6pbyptWbWbuZ38d1eyptfvIytN3ir6b65WBswg=
|
||||
github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=
|
||||
github.com/gorilla/mux v1.8.0 h1:i40aqfkR1h2SlN9hojwV5ZA91wcXFOvkdNIeFDP5koI=
|
||||
github.com/gorilla/mux v1.8.0/go.mod h1:DVbg23sWSpFRCP0SfiEN6jmj59UnW/n46BH5rLB71So=
|
||||
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/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
|
||||
github.com/gregjones/httpcache v0.0.0-20180305231024-9cad4c3443a7/go.mod h1:FecbI9+v66THATjSRHfNgh1IVFe/9kFxbXtjV0ctIMA=
|
||||
github.com/grpc-ecosystem/go-grpc-middleware v1.0.0/go.mod h1:FiyG127CGDf3tlThmgyCl78X/SZQqEOJBCDaAfeWzPs=
|
||||
github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0/go.mod h1:8NvIoxWQoOIhqOTXgfV/d3M/q6VIi02HzZEHgUlZvzk=
|
||||
github.com/grpc-ecosystem/grpc-gateway v1.9.0/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY=
|
||||
github.com/hashicorp/consul/api v1.1.0/go.mod h1:VmuI/Lkw1nC05EYQWNKwWGbkg+FbDBtguAZLlVdkD9Q=
|
||||
github.com/hashicorp/consul/sdk v0.1.1/go.mod h1:VKf9jXwCTEY1QZP2MOLRhb5i/I/ssyNV1vwHyQBF0x8=
|
||||
github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4=
|
||||
github.com/hashicorp/go-cleanhttp v0.5.1/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80=
|
||||
github.com/hashicorp/go-immutable-radix v1.0.0/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60=
|
||||
github.com/hashicorp/go-msgpack v0.5.3/go.mod h1:ahLV/dePpqEmjfWmKiqvPkv/twdG7iPBM1vqhUKIvfM=
|
||||
github.com/hashicorp/go-multierror v1.0.0/go.mod h1:dHtQlpGsu+cZNNAkkCN/P3hoUDHhCYQXV3UM06sGGrk=
|
||||
github.com/hashicorp/go-rootcerts v1.0.0/go.mod h1:K6zTfqpRlCUIjkwsN4Z+hiSfzSTQa6eBIzfwKfwNnHU=
|
||||
github.com/hashicorp/go-sockaddr v1.0.0/go.mod h1:7Xibr9yA9JjQq1JpNB2Vw7kxv8xerXegt+ozgdvDeDU=
|
||||
github.com/hashicorp/go-syslog v1.0.0/go.mod h1:qPfqrKkXGihmCqbJM2mZgkZGvKG1dFdvsLplgctolz4=
|
||||
github.com/hashicorp/go-uuid v1.0.0/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/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/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/mdns v1.0.0/go.mod h1:tL+uN++7HEJ6SQLQ2/p+z2pH24WQKWjBPkE0mNTz8vQ=
|
||||
github.com/hashicorp/memberlist v0.1.3/go.mod h1:ajVTdAv/9Im8oMAAj5G31PhhMCZJV2pPBoIllUwCN7I=
|
||||
github.com/hashicorp/serf v0.8.2/go.mod h1:6hOLApaqBFA1NXqRQAsxw9QxuDEvNxSQRwA/JwenrHc=
|
||||
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/imdario/mergo v0.3.5 h1:JboBksRwiiAJWvIYJVo46AfV+IAIKZpfrSzVKj42R4Q=
|
||||
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/jinzhu/inflection v1.0.0 h1:K317FqzuhWc8YvSVlFMCCUb36O/S9MCKRDI7QkRKD/E=
|
||||
github.com/jinzhu/inflection v1.0.0/go.mod h1:h+uFLlag+Qp1Va5pdKtLDYj+kHp5pxUVkryuEj+Srlc=
|
||||
github.com/jinzhu/now v1.1.1/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8=
|
||||
github.com/jinzhu/now v1.1.2 h1:eVKgfIdy9b6zbWBMgFpfDPoAMifwSZagU9HmEU6zgiI=
|
||||
github.com/jinzhu/now v1.1.2/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8=
|
||||
github.com/jmespath/go-jmespath v0.4.0/go.mod h1:T8mJZnbsbmF+m6zOOFylbeCJqk5+pHWvzYPziyZiYoo=
|
||||
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/jessevdk/go-flags v1.4.0/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI=
|
||||
github.com/jonboulle/clockwork v0.1.0/go.mod h1:Ii8DK3G1RaLaWxj9trq07+26W01tbo22gdxWY5EU2bo=
|
||||
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/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.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk=
|
||||
github.com/karrick/godirwalk v1.8.0/go.mod h1:H5KPZjojv4lE+QYImBI8xVtrBRgYrIVsaRPx4tDPEn4=
|
||||
github.com/karrick/godirwalk v1.10.3/go.mod h1:RoGL9dQei4vP9ilrpETWE8CLOZ1kiN0LhBygSwrAsHA=
|
||||
github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU=
|
||||
github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w=
|
||||
github.com/kisielk/errcheck v1.1.0/go.mod h1:EZBBE59ingxPouuu3KfxchcWSUPOHkagtvWXihfKN4Q=
|
||||
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/klauspost/compress v1.9.5/go.mod h1:RyIbtBH6LamlWaDj8nUwkbUhJ87Yi3uG0guNDohfE1A=
|
||||
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/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc=
|
||||
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
|
||||
github.com/kr/pretty v0.2.0/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
|
||||
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
|
||||
@@ -219,93 +351,189 @@ github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
|
||||
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/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/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-20190626092158-b2ccc519800e/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc=
|
||||
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/mailru/easyjson v0.7.0 h1:aizVhC/NAAcKWb+5QsU1iNOZb4Yws5UO2I+aIprQITM=
|
||||
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-isatty v0.0.3/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4=
|
||||
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/go.mod h1:WVKg1VTActs4Qso6iwGbiFih2UIHo0ENGwNd0Lj+XmI=
|
||||
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.2-0.20181231171920-c182affec369/go.mod h1:BSXmuO+STAnVfrANrmjBb36TMTDstsz7MSK+HVaYKv4=
|
||||
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/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-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/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 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/term v0.0.0-20201216013528-df9cb8a40635/go.mod h1:FBS0z0QWA44HXygs7VXDUOGoN/1TV3RuWkLO04am3wc=
|
||||
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
|
||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg=
|
||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
|
||||
github.com/modern-go/reflect2 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/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
|
||||
github.com/montanaflynn/stats v0.0.0-20171201202039-1bf9dbcd8cbe/go.mod h1:wL8QJuTMNUDYhXwkmfOly8iTdp5TEcJFWZD2D7SIkUc=
|
||||
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/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/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/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 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/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/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/orcaman/concurrent-map v0.0.0-20210106121528-16402b402231 h1:fa50YL1pzKW+1SsBnJDOHppJN9stOEwS+CRWyUtyYGU=
|
||||
github.com/orcaman/concurrent-map v0.0.0-20210106121528-16402b402231/go.mod h1:Lu3tH6HLW3feq74c2GC+jIMS/K2CFcDWnWD9XkenwhI=
|
||||
github.com/ory/kratos-client-go v0.8.2-alpha.1 h1:YlKhGOSZjounlB9iY4xSWlqHbyLYkeLzlLk8ZL7/nEM=
|
||||
github.com/ory/kratos-client-go v0.8.2-alpha.1/go.mod h1:dOQIsar76K07wMPJD/6aMhrWyY+sFGEagLDLso1CpsA=
|
||||
github.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc=
|
||||
github.com/patrickmn/go-cache v2.1.0+incompatible h1:HRMgzkcYKYpi3C8ajMPV8OFXaaRUnok+kx1WdO15EQc=
|
||||
github.com/patrickmn/go-cache v2.1.0+incompatible/go.mod h1:3Qf8kWWT7OJRJbdiICTKqZju1ZixQ/KpMGzzAfe6+WQ=
|
||||
github.com/pelletier/go-toml v1.7.0/go.mod h1:vwGMzjaWMwyfHwgIBhI2YUM4fB6nL6lVAvS1LBMMhTE=
|
||||
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/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.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
|
||||
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/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.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-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
|
||||
github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
|
||||
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/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.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-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/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/romana/rlog v0.0.0-20171115192701-f018bc92e7d7 h1:jkvpcEatpwuMF5O5LVxTnehj6YZ/aEZN4NWD/Xml4pI=
|
||||
github.com/romana/rlog v0.0.0-20171115192701-f018bc92e7d7/go.mod h1:KTrHyWpO1sevuXPZwyeZc72ddWRFqNSKDFl7uVWKpg0=
|
||||
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/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/ryanuber/columnize v0.0.0-20160712163229-9b3edd62028f/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts=
|
||||
github.com/santhosh-tekuri/jsonschema/v5 v5.0.0 h1:TToq11gyfNlrMFZiYujSekIsPd9AmsA2Bj/iv+s4JHE=
|
||||
github.com/santhosh-tekuri/jsonschema/v5 v5.0.0/go.mod h1:FKdcjfQW6rpZSnxxUvEA5H/cDPdvJ/SZJQLWWXWGrZ0=
|
||||
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/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/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/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA=
|
||||
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/cobra v0.0.3/go.mod h1:1l0Ry5zgKvJasoi3XT1TypsSe7PqH0Sj9dhYf7v3XqQ=
|
||||
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/jwalterweatherman v1.0.0/go.mod h1:cQK4TGJAtQXfYWX+Ddv3mKDzgVb68N+wFjFa4jdeBTo=
|
||||
github.com/spf13/pflag v0.0.0-20170130214245-9ff6c6923cff/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4=
|
||||
github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4=
|
||||
github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA=
|
||||
github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
|
||||
github.com/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/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.2.0/go.mod h1:qt09Ya8vawLte6SNmTgCsAVtYtaKzEcn8ATUoHMkEqE=
|
||||
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
|
||||
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
|
||||
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
|
||||
github.com/stretchr/testify v1.6.1 h1:hDPOHmpOpP40lSULcqw7IrRb/u7w6RpDC9399XyoNd0=
|
||||
github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
|
||||
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/subosito/gotenv v1.2.0/go.mod h1:N0PQaV/YGNqwC0u51sEeR/aUtSLEXKX9iv69rRypqCw=
|
||||
github.com/tidwall/gjson v1.10.2/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk=
|
||||
github.com/tidwall/gjson v1.12.0 h1:61wEp/qfvFnqKH/WCI3M8HuRut+mHT6Mr82QrFmM2SY=
|
||||
github.com/tidwall/gjson v1.12.0/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk=
|
||||
github.com/tidwall/match v1.1.1 h1:+Ho715JplO36QYgwN9PGYNhgZvoUSc9X2c80KVTi+GA=
|
||||
github.com/tidwall/match v1.1.1/go.mod h1:eRSPERbgtNPcGhD8UCthc6PmLEQXEWd3PRB5JTxsfmM=
|
||||
github.com/tidwall/pretty v1.0.0/go.mod h1:XNkn88O1ChpSDQmQeStsy+sBenx6DDtFZJxhVysOjyk=
|
||||
github.com/tidwall/pretty v1.2.0 h1:RWIZEg2iJ8/g6fDDYzMpobmaoGh5OLl4AXtGUGPcqCs=
|
||||
github.com/tidwall/pretty v1.2.0/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU=
|
||||
github.com/tidwall/sjson v1.2.3 h1:5+deguEhHSEjmuICXZ21uSSsXotWMA0orU783+Z7Cp8=
|
||||
github.com/tidwall/sjson v1.2.3/go.mod h1:5WdjKx3AQMvCJ4RG6/2UYT7dLrGvJUV1x4jdTAyGvZs=
|
||||
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/ugorji/go v1.1.7 h1:/68gy2h+1mWMrwZFeD1kQialdSzAb432dtpeJ42ovdo=
|
||||
github.com/ugorji/go v1.1.7/go.mod h1:kZn38zHttfInRq0xu/PH0az30d+z6vm202qpg1oXVMw=
|
||||
github.com/ugorji/go/codec v1.1.7 h1:2SvQaVZ1ouYrrKKwoSk2pzd4A9evlKJb9oTL+OaLUSs=
|
||||
github.com/ugorji/go/codec v1.1.7/go.mod h1:Ax+UKWsSmolVDwsd+7N3ZtXu+yMGCf907BLYF3GoBXY=
|
||||
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/stringprep v1.0.2/go.mod h1:8F9zXuvzgwmyT5DUm4GUfZGDdT3W+LCvS6+da4O5kxM=
|
||||
github.com/up9inc/basenine/client/go v0.0.0-20220117225014-f91ba3692a36 h1:WtzDQg2i1KYxRYM2LnFOdIzpxhiEBVKIa7byFXDEdK0=
|
||||
github.com/up9inc/basenine/client/go v0.0.0-20220117225014-f91ba3692a36/go.mod h1:SvJGPoa/6erhUQV7kvHBwM/0x5LyO6XaG2lUaCaKiUI=
|
||||
github.com/vektah/gqlparser v1.1.2/go.mod h1:1ycwN7Ij5njmMkPPAOaRFY4rET2Enx7IkVv3vaXspKw=
|
||||
github.com/vishvananda/netns v0.0.0-20210104183010-2eb08e3e575f h1:p4VB7kIXpOQvVn1ZaTIVp+3vuYAXFe3OJEvjbUYJLaA=
|
||||
github.com/vishvananda/netns v0.0.0-20210104183010-2eb08e3e575f/go.mod h1:DD4vA1DwXk04H54A1oHXtwZmA0grkVMdPxx/VGLCah0=
|
||||
github.com/wI2L/jsondiff v0.1.1 h1:r2TkoEet7E4JMO5+s1RCY2R0LrNPNHY6hbDeow2hRHw=
|
||||
github.com/wI2L/jsondiff v0.1.1/go.mod h1:bAbJSAJXZtfOCZ5y3v7Mfb6UQa3DGdGFjQj1cNv8EcM=
|
||||
github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU=
|
||||
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/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/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
||||
github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
||||
github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
||||
github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
||||
go.mongodb.org/mongo-driver v1.5.1 h1:9nOVLGDfOaZ9R0tBumx/BcuqkbFpyTCU2r/Po7A2azI=
|
||||
go.mongodb.org/mongo-driver v1.5.1/go.mod h1:gRXCHX4Jo7J0IJ1oDQyUxF7jfy19UfxniMS4xxMmUqw=
|
||||
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.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8=
|
||||
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.4/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/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0=
|
||||
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-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-20190422162423-af44ce270edf/go.mod h1:WFFai1msRO1wXaEeE5yQxYXgSfI8pQAWXbQop6sCtWE=
|
||||
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-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-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-20200302210943-78000ba7a073/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-20210220033148-5ea612d1eb83 h1:/ZScEX8SfEmUGRHs0gxpqteO5nfNW6axyZbBdw9A12g=
|
||||
@@ -340,18 +568,27 @@ 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.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.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-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-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-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-20181220203305-927f97764cc3/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-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-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-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-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-20190628185345-da137c7871d7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20190724013045-ca1201d0de80/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20190827160401-ba9fcec4b297/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20191209160850-c0dbc17a3553/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
@@ -361,58 +598,80 @@ 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-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-20200501053045-e0ff5e5a1de5/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
|
||||
golang.org/x/net v0.0.0-20200506145744-7e3656a0809f/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
|
||||
golang.org/x/net v0.0.0-20200513185701-a91f0712d120/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
|
||||
golang.org/x/net v0.0.0-20200520182314-0ba52f642ac2/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
|
||||
golang.org/x/net v0.0.0-20200625001655-4c5254603344/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
|
||||
golang.org/x/net v0.0.0-20200707034311-ab3426394381/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
|
||||
golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
|
||||
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/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/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-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
||||
golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
||||
golang.org/x/oauth2 v0.0.0-20191202225959-858c2ad4c8b6/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
||||
golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d h1:TzXSXBo42m9gQenoE3b9BGiEpg5IG2JkU5FkPIawgtw=
|
||||
golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
||||
golang.org/x/oauth2 v0.0.0-20210323180902-22b0adad7558 h1:D7nTwh4J0i+5mW4Zjzn5omvlr6YBcWywE6KOcatyNxY=
|
||||
golang.org/x/oauth2 v0.0.0-20210323180902-22b0adad7558/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
|
||||
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20190412183630-56d357773e84/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sys v0.0.0-20180823144017-11551d06cbcc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20181026203630-95b1ffbd15a5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20181107165924-66b7b1311ac8/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-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190403152447-81d4e9dc473e/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-20190419153524-e8e3143a4f4a/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-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190531175056-4c3a928424d2/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190616124812-15dcb6c0061f/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-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-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-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-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-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-20200217220822-9197077df867/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-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200331124033-c3d80250170d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200501052902-10377860bb8e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200511232937-7e40ca221e25/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200515095857-1151b9dac4a9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200523222454-059865788121/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200615200032-f1bc736245b1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200803210538-64077c9b5642/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-20201119102817-f84b799fce68/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/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210119212857-b64e53b001e4/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-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||
golang.org/x/term v0.0.0-20210220032956-6a3ed077a48d h1:SZxvLBoTP5yHO3Frd4z4vrF+DBX9vMVanchswa69toE=
|
||||
@@ -423,34 +682,35 @@ golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3
|
||||
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
|
||||
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/text v0.3.4/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/text v0.3.5 h1:i6eZZ+zk0SOf0xgBpEpPD18qWcJda6q1sxt3S0kzyUQ=
|
||||
golang.org/x/text v0.3.5/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/text v0.3.6 h1:aRYxNxv6iGQlyVaZmk6ZgYEDa+Jg18DxebPSrd6bg1M=
|
||||
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||
golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||
golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||
golang.org/x/time v0.0.0-20210220033141-f8bda1e9f3ba h1:O8mE0/t419eoIwhTFpKVkHiTs/Igowgfkj25AcZrtiE=
|
||||
golang.org/x/time v0.0.0-20210220033141-f8bda1e9f3ba/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||
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-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-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-20190312170243-e65039ee4138/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
|
||||
golang.org/x/tools v0.0.0-20190329151228-23e29df326fe/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
|
||||
golang.org/x/tools v0.0.0-20190416151739-9c9e1878f421/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
|
||||
golang.org/x/tools v0.0.0-20190420181800-aa740d480789/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
|
||||
golang.org/x/tools v0.0.0-20190328211700-ab21143f2384/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
|
||||
golang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
|
||||
golang.org/x/tools v0.0.0-20190506145303-2d16b83fe98c/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
|
||||
golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
|
||||
golang.org/x/tools v0.0.0-20190531172133-b3315ee88b7d/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-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-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-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-20191012152004-8de300cfc20a/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
golang.org/x/tools v0.0.0-20191112195655-aa38f8e97acc/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
golang.org/x/tools v0.0.0-20191113191852-77e3bb0ad9e7/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
golang.org/x/tools v0.0.0-20191115202509-3a792d9c32b2/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
@@ -465,9 +725,21 @@ golang.org/x/tools v0.0.0-20200204074204-1cc6d1ef6c74/go.mod h1:TB2adYChydJhpapK
|
||||
golang.org/x/tools v0.0.0-20200207183749-b753a1ba74fa/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-20200227222343-706bc42d1f0d/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-20200312045724-11d5b4c81c7d/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw=
|
||||
golang.org/x/tools v0.0.0-20200331025713-a30bf2db82d4/go.mod h1:Sl4aGygMT6LrqrWclx+PTx3U+LnKx/seiNR+3G19Ar8=
|
||||
golang.org/x/tools v0.0.0-20200501065659-ab2804fb9c9d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
|
||||
golang.org/x/tools v0.0.0-20200505023115-26f46d2f7ef8/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
|
||||
golang.org/x/tools v0.0.0-20200512131952-2bc93b1c0c88/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
|
||||
golang.org/x/tools v0.0.0-20200515010526-7d3b6ebf133d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
|
||||
golang.org/x/tools v0.0.0-20200618134242-20370b0cb4b2/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-20200729194436-6467de6f59a7/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA=
|
||||
golang.org/x/tools v0.0.0-20200804011535-6c149bb5ef0d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA=
|
||||
golang.org/x/tools v0.0.0-20200825202427-b303f430e36d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA=
|
||||
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-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
@@ -482,13 +754,20 @@ google.golang.org/api v0.14.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsb
|
||||
google.golang.org/api v0.15.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI=
|
||||
google.golang.org/api v0.17.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=
|
||||
google.golang.org/api v0.18.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=
|
||||
google.golang.org/api v0.19.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=
|
||||
google.golang.org/api v0.20.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=
|
||||
google.golang.org/api v0.22.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=
|
||||
google.golang.org/api v0.24.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE=
|
||||
google.golang.org/api v0.28.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE=
|
||||
google.golang.org/api v0.29.0/go.mod h1:Lcubydp8VUV7KeIHD9z2Bys/sm/vGKnG1UHuDBSrHWM=
|
||||
google.golang.org/api v0.30.0/go.mod h1:QGmEvQ87FHZNiUVJkT14jQNYJ4ZJjdRF23ZXz5138Fc=
|
||||
google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
|
||||
google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
|
||||
google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
|
||||
google.golang.org/appengine v1.6.1/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww/cMBSeb0=
|
||||
google.golang.org/appengine v1.6.5 h1:tycE03LOZYQNhDpS27tcQdAzLCVMaj7QT2SXxebnpCM=
|
||||
google.golang.org/appengine v1.6.5/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc=
|
||||
google.golang.org/appengine v1.6.6 h1:lMO5rYAqUxkmaj76jAkRUvt5JZgFymx/+Q5Mzfivuhc=
|
||||
google.golang.org/appengine v1.6.6/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc=
|
||||
google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
|
||||
google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
|
||||
google.golang.org/genproto v0.0.0-20190418145605-e7d98fc518a7/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
|
||||
@@ -506,15 +785,31 @@ google.golang.org/genproto v0.0.0-20200122232147-0452cf42e150/go.mod h1:n3cpQtvx
|
||||
google.golang.org/genproto v0.0.0-20200204135345-fa8e72b47b90/go.mod h1:GmwEX6Z4W5gMy59cAlVYjN9JhxgbQH6Gn+gFDQe2lzA=
|
||||
google.golang.org/genproto v0.0.0-20200212174721-66ed5ce911ce/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
|
||||
google.golang.org/genproto v0.0.0-20200224152610-e50cd9704f63/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
|
||||
google.golang.org/genproto v0.0.0-20200228133532-8c2c7df3a383/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
|
||||
google.golang.org/genproto v0.0.0-20200305110556-506484158171/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
|
||||
google.golang.org/genproto v0.0.0-20200312145019-da6875a35672/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
|
||||
google.golang.org/genproto v0.0.0-20200331122359-1ee6d9798940/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
|
||||
google.golang.org/genproto v0.0.0-20200430143042-b979b6f78d84/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
|
||||
google.golang.org/genproto v0.0.0-20200511104702-f5ebc3bea380/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
|
||||
google.golang.org/genproto v0.0.0-20200515170657-fc4c6c6a6587/go.mod h1:YsZOwe1myG/8QRHRsmBRE1LrgQY60beZKjly0O1fX9U=
|
||||
google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo=
|
||||
google.golang.org/genproto v0.0.0-20200618031413-b414f8b61790/go.mod h1:jDfRM7FcilCzHH/e9qn6dsT145K34l5v+OpcnNgKAAA=
|
||||
google.golang.org/genproto v0.0.0-20200729003335-053ba62fc06f/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
|
||||
google.golang.org/genproto v0.0.0-20200804131852-c06518451d9c/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
|
||||
google.golang.org/genproto v0.0.0-20200825200019-8632dd797987/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
|
||||
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.21.0/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.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY=
|
||||
google.golang.org/grpc v1.26.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
|
||||
google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
|
||||
google.golang.org/grpc v1.27.1/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
|
||||
google.golang.org/grpc v1.28.0/go.mod h1:rpkK4SK4GF4Ach/+MFLZUBavHOvF2JJB5uozKKal+60=
|
||||
google.golang.org/grpc v1.29.1/go.mod h1:itym6AZVZYACWQqET3MqgPpjcuV5QH3BxFS3IjizoKk=
|
||||
google.golang.org/grpc v1.30.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak=
|
||||
google.golang.org/grpc v1.31.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak=
|
||||
google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8=
|
||||
google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0=
|
||||
google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM=
|
||||
@@ -523,8 +818,10 @@ google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzi
|
||||
google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
|
||||
google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
|
||||
google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
|
||||
google.golang.org/protobuf v1.24.0/go.mod h1:r/3tXBNzIEhYS9I1OUVjXDlt8tc493IdKGjtUeSXeh4=
|
||||
google.golang.org/protobuf v1.25.0 h1:Ejskq+SyPohKW+1uil0JJMtmHCgJPJ/qWTxr8qp+R4c=
|
||||
google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c=
|
||||
gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
@@ -534,44 +831,64 @@ gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=
|
||||
gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys=
|
||||
gopkg.in/inf.v0 v0.9.1 h1:73M5CoZyi3ZLMOyDlQh031Cx6N9NDJ2Vvfl76EDAgDc=
|
||||
gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw=
|
||||
gopkg.in/ini.v1 v1.51.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
|
||||
gopkg.in/resty.v1 v1.12.0/go.mod h1:mDo4pnntr5jdWRML875a/NmxYqAlA73dVijT2AXvQQo=
|
||||
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw=
|
||||
gopkg.in/yaml.v2 v2.0.0-20170812160011-eb3733d160e7/go.mod h1:JAlM8MvJe8wmxCU4Bli9HhUf9+ttbYbLASfIpnQbh74=
|
||||
gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v2 v2.2.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.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.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
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.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=
|
||||
gorm.io/driver/sqlite v1.1.4 h1:PDzwYE+sI6De2+mxAneV9Xs11+ZyKV6oxD3wDGkaNvM=
|
||||
gorm.io/driver/sqlite v1.1.4/go.mod h1:mJCeTFr7+crvS+TRnWc5Z3UvwxUN1BGBLMrf5LA9DYw=
|
||||
gorm.io/gorm v1.20.7/go.mod h1:0HFTzE/SqkGTzK6TlDPPQbAYCluiVvhzoA1+aVyzenw=
|
||||
gorm.io/gorm v1.21.8 h1:2CEwZSzogdhsKPlJ9OvBKTdlWIpELXb6HbfLfMNhSYI=
|
||||
gorm.io/gorm v1.21.8/go.mod h1:F+OptMscr0P2F2qU97WT1WimdH9GaQPoDW7AYd5i2Y0=
|
||||
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-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-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-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.0/go.mod h1:+YbrhBBGgsxbF6o6Kj4KJPJnBmAKuXDeS3E18bgHNVU=
|
||||
k8s.io/apimachinery v0.21.0 h1:3Fx+41if+IRavNcKOz09FwEXDBG6ORh6iMsTSelhkMA=
|
||||
k8s.io/apimachinery v0.21.0/go.mod h1:jbreFvJo3ov9rj7eWT7+sYiRx+qZuCYXwWT1bcDswPY=
|
||||
k8s.io/client-go v0.21.0 h1:n0zzzJsAQmJngpC0IhgFcApZyoGXPrDIAD601HD09ag=
|
||||
k8s.io/client-go v0.21.0/go.mod h1:nNBytTF9qPFDEhoqgEPaarobC8QPae13bElIVHzIglA=
|
||||
honnef.co/go/tools v0.0.1-2020.1.4/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k=
|
||||
k8s.io/api v0.21.2 h1:vz7DqmRsXTCSa6pNxXwQ1IYeAZgdIsua+DZU+o+SX3Y=
|
||||
k8s.io/api v0.21.2/go.mod h1:Lv6UGJZ1rlMI1qusN8ruAp9PUBFyBwpEHAdG24vIsiU=
|
||||
k8s.io/apimachinery v0.21.2 h1:vezUc/BHqWlQDnZ+XkrpXSmnANSLbpnlpwo0Lhk0gpc=
|
||||
k8s.io/apimachinery v0.21.2/go.mod h1:CdTY8fU/BlvAbJ2z/8kBwimGki5Zp8/fbVuLY8gJumM=
|
||||
k8s.io/cli-runtime v0.21.2/go.mod h1:8u/jFcM0QpoI28f6sfrAAIslLCXUYKD5SsPPMWiHYrI=
|
||||
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-20201214224949-b6c5ce23f027/go.mod h1:FiNAH4ZV3gBg2Kwh89tzAEV2be7d5xI0vBa/VySYy3E=
|
||||
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/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/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/go.mod h1:jPW/WVKK9YHAvNhRxK0md/EJ228hCsBRufyofKtW8HA=
|
||||
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/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.1.0 h1:C4r9BgJ98vrKnnVCjwCSXcWjWe0NKcUQkmzDXZXGwH8=
|
||||
sigs.k8s.io/structured-merge-diff/v4 v4.1.0/go.mod h1:bJZC9H9iH24zzfZ/41RGcq60oK1F7G282QMXDPYydCw=
|
||||
sigs.k8s.io/yaml v1.2.0 h1:kr/MCeFWJWTwyaHoR9c8EjH9OumOmoF9YGiZd7lFm/Q=
|
||||
sigs.k8s.io/yaml v1.2.0/go.mod h1:yfXDCHCao9+ENCvLSE62v9VSji2MKu5jeNfTrofGhJc=
|
||||
sigs.k8s.io/yaml v1.3.0 h1:a2VclLzOGrwOHDiV8EfBGhvjHvP46CtW5j6POvhYGGo=
|
||||
sigs.k8s.io/yaml v1.3.0/go.mod h1:GeOyir5tyXNByN85N/dRIT9es5UQNerPYEKK56eTBm8=
|
||||
|
||||
14
agent/kratos/Dockerfile
Normal file
14
agent/kratos/Dockerfile
Normal file
@@ -0,0 +1,14 @@
|
||||
FROM gcr.io/up9-docker-hub/mizu-kratos-base/simple-password-policy:latest
|
||||
|
||||
USER root
|
||||
|
||||
RUN apk add sqlite
|
||||
|
||||
RUN mkdir -p /etc/config/kratos
|
||||
|
||||
COPY ./kratos.yml /etc/config/kratos/kratos.yml
|
||||
COPY ./identity.schema.json /etc/config/kratos/identity.schema.json
|
||||
COPY ./start.sh /opt/start.sh
|
||||
RUN chmod +x /opt/start.sh
|
||||
|
||||
ENTRYPOINT ["/opt/start.sh"]
|
||||
29
agent/kratos/build-push-featurebranch.sh
Executable file
29
agent/kratos/build-push-featurebranch.sh
Executable file
@@ -0,0 +1,29 @@
|
||||
#!/bin/bash
|
||||
set -e
|
||||
|
||||
GCP_PROJECT=up9-docker-hub
|
||||
REPOSITORY=gcr.io/$GCP_PROJECT
|
||||
SERVER_NAME=mizu-kratos
|
||||
GIT_BRANCH=$(git branch | grep \* | cut -d ' ' -f2 | tr '[:upper:]' '[:lower:]')
|
||||
|
||||
DOCKER_REPO=$REPOSITORY/$SERVER_NAME/$GIT_BRANCH
|
||||
SEM_VER=${SEM_VER=0.0.0}
|
||||
ARCH=amd64
|
||||
|
||||
DOCKER_TAGGED_BUILDS=("$DOCKER_REPO:latest" "$DOCKER_REPO:$SEM_VER")
|
||||
|
||||
if [ "$GIT_BRANCH" = 'develop' -o "$GIT_BRANCH" = 'master' -o "$GIT_BRANCH" = 'main' ]
|
||||
then
|
||||
echo "Pushing to $GIT_BRANCH is allowed only via CI"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "building ${DOCKER_TAGGED_BUILDS[@]}"
|
||||
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} .
|
||||
|
||||
for DOCKER_TAG in "${DOCKER_TAGGED_BUILDS[@]}"
|
||||
do
|
||||
echo pushing "$DOCKER_TAG"
|
||||
docker push "$DOCKER_TAG"
|
||||
done
|
||||
43
agent/kratos/identity.schema.json
Normal file
43
agent/kratos/identity.schema.json
Normal file
@@ -0,0 +1,43 @@
|
||||
{
|
||||
"$id": "https://schemas.ory.sh/presets/kratos/quickstart/email-password/identity.schema.json",
|
||||
"$schema": "http://json-schema.org/draft-07/schema#",
|
||||
"title": "Person",
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"traits": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"username": {
|
||||
"type": "string",
|
||||
"format": "username",
|
||||
"title": "Username",
|
||||
"minLength": 3,
|
||||
"ory.sh/kratos": {
|
||||
"credentials": {
|
||||
"password": {
|
||||
"identifier": true
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"name": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"first": {
|
||||
"title": "First Name",
|
||||
"type": "string"
|
||||
},
|
||||
"last": {
|
||||
"title": "Last Name",
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"username"
|
||||
],
|
||||
"additionalProperties": false
|
||||
}
|
||||
}
|
||||
}
|
||||
84
agent/kratos/kratos.yml
Executable file
84
agent/kratos/kratos.yml
Executable file
@@ -0,0 +1,84 @@
|
||||
version: v0.8.2-alpha.1
|
||||
|
||||
dsn: sqlite:///app/data/kratos.sqlite?_fk=true
|
||||
|
||||
serve:
|
||||
public:
|
||||
base_url: http://127.0.0.1:4433/
|
||||
cors:
|
||||
enabled: true
|
||||
admin:
|
||||
base_url: http://kratos:4434/
|
||||
|
||||
selfservice:
|
||||
default_browser_return_url: http://127.0.0.1:4455/
|
||||
whitelisted_return_urls:
|
||||
- http://127.0.0.1:4455
|
||||
|
||||
methods:
|
||||
password:
|
||||
enabled: true
|
||||
|
||||
flows:
|
||||
error:
|
||||
ui_url: http://127.0.0.1:4455/error
|
||||
|
||||
settings:
|
||||
ui_url: http://127.0.0.1:4455/settings
|
||||
privileged_session_max_age: 15m
|
||||
|
||||
recovery:
|
||||
enabled: true
|
||||
ui_url: http://127.0.0.1:4455/recovery
|
||||
|
||||
verification:
|
||||
enabled: false
|
||||
ui_url: http://127.0.0.1:4455/verification
|
||||
after:
|
||||
default_browser_return_url: http://127.0.0.1:4455/
|
||||
|
||||
logout:
|
||||
after:
|
||||
default_browser_return_url: http://127.0.0.1:4455/login
|
||||
|
||||
login:
|
||||
ui_url: http://127.0.0.1:4455/login
|
||||
lifespan: 10m
|
||||
|
||||
registration:
|
||||
lifespan: 10m
|
||||
ui_url: http://127.0.0.1:4455/registration
|
||||
after:
|
||||
password:
|
||||
hooks:
|
||||
-
|
||||
hook: session
|
||||
|
||||
log:
|
||||
level: info
|
||||
format: text
|
||||
leak_sensitive_values: true
|
||||
|
||||
secrets:
|
||||
cookie:
|
||||
- PLEASE-CHANGE-ME-I-AM-VERY-INSECURE
|
||||
cipher:
|
||||
- 32-LONG-SECRET-NOT-SECURE-AT-ALL
|
||||
|
||||
ciphers:
|
||||
algorithm: xchacha20-poly1305
|
||||
|
||||
hashers:
|
||||
argon2:
|
||||
parallelism: 1
|
||||
memory: 128MB
|
||||
iterations: 2
|
||||
salt_length: 16
|
||||
key_length: 16
|
||||
|
||||
identity:
|
||||
default_schema_url: file:///etc/config/kratos/identity.schema.json
|
||||
|
||||
courier:
|
||||
smtp:
|
||||
connection_uri: smtps://test:test@mailslurper:1025/?skip_ssl_verify=true
|
||||
4
agent/kratos/start.sh
Executable file
4
agent/kratos/start.sh
Executable file
@@ -0,0 +1,4 @@
|
||||
#!/bin/sh
|
||||
|
||||
kratos migrate sql sqlite:///app/data/kratos.sqlite?_fk=true --yes # this initializes the db
|
||||
kratos serve -c /etc/config/kratos/kratos.yml --watch-courier # start kratos
|
||||
414
agent/main.go
414
agent/main.go
@@ -2,23 +2,44 @@ package main
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"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"
|
||||
"io/ioutil"
|
||||
"mizuserver/pkg/api"
|
||||
"mizuserver/pkg/config"
|
||||
"mizuserver/pkg/controllers"
|
||||
"mizuserver/pkg/middlewares"
|
||||
"mizuserver/pkg/models"
|
||||
"mizuserver/pkg/oas"
|
||||
"mizuserver/pkg/routes"
|
||||
"mizuserver/pkg/sensitiveDataFiltering"
|
||||
"mizuserver/pkg/servicemap"
|
||||
"mizuserver/pkg/up9"
|
||||
"mizuserver/pkg/utils"
|
||||
"net/http"
|
||||
"os"
|
||||
"os/signal"
|
||||
"path"
|
||||
"path/filepath"
|
||||
"plugin"
|
||||
"sort"
|
||||
"strconv"
|
||||
"strings"
|
||||
"syscall"
|
||||
"time"
|
||||
|
||||
v1 "k8s.io/api/core/v1"
|
||||
|
||||
"github.com/antelman107/net-wait-go/wait"
|
||||
"github.com/gin-contrib/static"
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/gorilla/websocket"
|
||||
"github.com/op/go-logging"
|
||||
basenine "github.com/up9inc/basenine/client/go"
|
||||
"github.com/up9inc/mizu/shared"
|
||||
"github.com/up9inc/mizu/shared/logger"
|
||||
"github.com/up9inc/mizu/tap"
|
||||
tapApi "github.com/up9inc/mizu/tap/api"
|
||||
)
|
||||
|
||||
var tapperMode = flag.Bool("tap", false, "Run in tapper mode without API")
|
||||
@@ -29,63 +50,96 @@ var namespace = flag.String("namespace", "", "Resolve IPs if they belong to reso
|
||||
var harsReaderMode = flag.Bool("hars-read", false, "Run in hars-read mode")
|
||||
var harsDir = flag.String("hars-dir", "", "Directory to read hars from")
|
||||
|
||||
var extensions []*tapApi.Extension // global
|
||||
var extensionsMap map[string]*tapApi.Extension // global
|
||||
|
||||
var startTime int64
|
||||
|
||||
const (
|
||||
socketConnectionRetries = 30
|
||||
socketConnectionRetryDelay = time.Second * 2
|
||||
socketHandshakeTimeout = time.Second * 2
|
||||
uiIndexPath = "./site/index.html"
|
||||
)
|
||||
|
||||
func main() {
|
||||
logLevel := determineLogLevel()
|
||||
logger.InitLoggerStderrOnly(logLevel)
|
||||
flag.Parse()
|
||||
hostMode := os.Getenv(shared.HostModeEnvVar) == "1"
|
||||
tapOpts := &tap.TapOpts{HostMode: hostMode}
|
||||
if err := config.LoadConfig(); err != nil {
|
||||
logger.Log.Fatalf("Error loading config file %v", err)
|
||||
}
|
||||
loadExtensions()
|
||||
|
||||
|
||||
if !*tapperMode && !*apiServerMode && !*standaloneMode && !*harsReaderMode{
|
||||
if !*tapperMode && !*apiServerMode && !*standaloneMode && !*harsReaderMode {
|
||||
panic("One of the flags --tap, --api or --standalone or --hars-read must be provided")
|
||||
}
|
||||
|
||||
if *standaloneMode {
|
||||
api.StartResolving(*namespace)
|
||||
|
||||
harOutputChannel, outboundLinkOutputChannel := tap.StartPassiveTapper(tapOpts)
|
||||
filteredHarChannel := make(chan *tap.OutputChannelItem)
|
||||
outputItemsChannel := make(chan *tapApi.OutputChannelItem)
|
||||
filteredOutputItemsChannel := make(chan *tapApi.OutputChannelItem)
|
||||
|
||||
go filterHarItems(harOutputChannel, filteredHarChannel, getTrafficFilteringOptions())
|
||||
go api.StartReadingEntries(filteredHarChannel, nil)
|
||||
go api.StartReadingOutbound(outboundLinkOutputChannel)
|
||||
filteringOptions := getTrafficFilteringOptions()
|
||||
hostMode := os.Getenv(shared.HostModeEnvVar) == "1"
|
||||
tapOpts := &tap.TapOpts{HostMode: hostMode}
|
||||
tap.StartPassiveTapper(tapOpts, outputItemsChannel, extensions, filteringOptions)
|
||||
|
||||
go filterItems(outputItemsChannel, filteredOutputItemsChannel)
|
||||
go api.StartReadingEntries(filteredOutputItemsChannel, nil, extensionsMap)
|
||||
|
||||
hostApi(nil)
|
||||
} else if *tapperMode {
|
||||
logger.Log.Infof("Starting tapper, websocket address: %s", *apiServerAddress)
|
||||
if *apiServerAddress == "" {
|
||||
panic("API server address must be provided with --api-server-address when using --tap")
|
||||
}
|
||||
|
||||
hostMode := os.Getenv(shared.HostModeEnvVar) == "1"
|
||||
tapOpts := &tap.TapOpts{HostMode: hostMode}
|
||||
tapTargets := getTapTargets()
|
||||
if tapTargets != nil {
|
||||
tap.SetFilterAuthorities(tapTargets)
|
||||
rlog.Infof("Filtering for the following authorities: %v", tap.GetFilterIPs())
|
||||
tapOpts.FilterAuthorities = tapTargets
|
||||
logger.Log.Infof("Filtering for the following authorities: %v", tapOpts.FilterAuthorities)
|
||||
}
|
||||
|
||||
harOutputChannel, outboundLinkOutputChannel := tap.StartPassiveTapper(tapOpts)
|
||||
filteredOutputItemsChannel := make(chan *tapApi.OutputChannelItem)
|
||||
|
||||
socketConnection, err := shared.ConnectToSocketServer(*apiServerAddress, shared.DEFAULT_SOCKET_RETRIES, shared.DEFAULT_SOCKET_RETRY_SLEEP_TIME, false)
|
||||
filteringOptions := getTrafficFilteringOptions()
|
||||
tap.StartPassiveTapper(tapOpts, filteredOutputItemsChannel, extensions, filteringOptions)
|
||||
socketConnection, err := dialSocketWithRetry(*apiServerAddress, socketConnectionRetries, socketConnectionRetryDelay)
|
||||
if err != nil {
|
||||
panic(fmt.Sprintf("Error connecting to socket server at %s %v", *apiServerAddress, err))
|
||||
}
|
||||
logger.Log.Infof("Connected successfully to websocket %s", *apiServerAddress)
|
||||
|
||||
go pipeTapChannelToSocket(socketConnection, harOutputChannel)
|
||||
go pipeOutboundLinksChannelToSocket(socketConnection, outboundLinkOutputChannel)
|
||||
go pipeTapChannelToSocket(socketConnection, filteredOutputItemsChannel)
|
||||
} else if *apiServerMode {
|
||||
configureBasenineServer(shared.BasenineHost, shared.BaseninePort)
|
||||
startTime = time.Now().UnixNano() / int64(time.Millisecond)
|
||||
api.StartResolving(*namespace)
|
||||
|
||||
socketHarOutChannel := make(chan *tap.OutputChannelItem, 1000)
|
||||
filteredHarChannel := make(chan *tap.OutputChannelItem)
|
||||
outputItemsChannel := make(chan *tapApi.OutputChannelItem)
|
||||
filteredOutputItemsChannel := make(chan *tapApi.OutputChannelItem)
|
||||
enableExpFeatureIfNeeded()
|
||||
go filterItems(outputItemsChannel, filteredOutputItemsChannel)
|
||||
go api.StartReadingEntries(filteredOutputItemsChannel, nil, extensionsMap)
|
||||
|
||||
go filterHarItems(socketHarOutChannel, filteredHarChannel, getTrafficFilteringOptions())
|
||||
go api.StartReadingEntries(filteredHarChannel, nil)
|
||||
syncEntriesConfig := getSyncEntriesConfig()
|
||||
if syncEntriesConfig != nil {
|
||||
if err := up9.SyncEntries(syncEntriesConfig); err != nil {
|
||||
logger.Log.Error("Error syncing entries, err: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
hostApi(socketHarOutChannel)
|
||||
hostApi(outputItemsChannel)
|
||||
} else if *harsReaderMode {
|
||||
socketHarOutChannel := make(chan *tap.OutputChannelItem, 1000)
|
||||
filteredHarChannel := make(chan *tap.OutputChannelItem)
|
||||
outputItemsChannel := make(chan *tapApi.OutputChannelItem, 1000)
|
||||
filteredHarChannel := make(chan *tapApi.OutputChannelItem)
|
||||
|
||||
go filterHarItems(socketHarOutChannel, filteredHarChannel, getTrafficFilteringOptions())
|
||||
go api.StartReadingEntries(filteredHarChannel, harsDir)
|
||||
go filterItems(outputItemsChannel, filteredHarChannel)
|
||||
go api.StartReadingEntries(filteredHarChannel, harsDir, extensionsMap)
|
||||
hostApi(nil)
|
||||
}
|
||||
|
||||
@@ -93,10 +147,91 @@ func main() {
|
||||
signal.Notify(signalChan, os.Interrupt)
|
||||
<-signalChan
|
||||
|
||||
rlog.Info("Exiting")
|
||||
logger.Log.Info("Exiting")
|
||||
}
|
||||
|
||||
func hostApi(socketHarOutputChannel chan<- *tap.OutputChannelItem) {
|
||||
func enableExpFeatureIfNeeded() {
|
||||
if config.Config.OAS {
|
||||
oas.GetOasGeneratorInstance().Start()
|
||||
}
|
||||
if config.Config.ServiceMap {
|
||||
servicemap.GetInstance().SetConfig(config.Config)
|
||||
}
|
||||
}
|
||||
|
||||
func configureBasenineServer(host string, port string) {
|
||||
if !wait.New(
|
||||
wait.WithProto("tcp"),
|
||||
wait.WithWait(200*time.Millisecond),
|
||||
wait.WithBreak(50*time.Millisecond),
|
||||
wait.WithDeadline(5*time.Second),
|
||||
wait.WithDebug(true),
|
||||
).Do([]string{fmt.Sprintf("%s:%s", host, port)}) {
|
||||
logger.Log.Panicf("Basenine is not available!")
|
||||
}
|
||||
|
||||
// Limit the database size to default 200MB
|
||||
err := basenine.Limit(host, port, config.Config.MaxDBSizeBytes)
|
||||
if err != nil {
|
||||
logger.Log.Panicf("Error while limiting database size: %v", err)
|
||||
}
|
||||
|
||||
// Define the macros
|
||||
for _, extension := range extensions {
|
||||
macros := extension.Dissector.Macros()
|
||||
for macro, expanded := range macros {
|
||||
err = basenine.Macro(host, port, macro, expanded)
|
||||
if err != nil {
|
||||
logger.Log.Panicf("Error while adding a macro: %v", err)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func loadExtensions() {
|
||||
dir, _ := filepath.Abs(filepath.Dir(os.Args[0]))
|
||||
extensionsDir := path.Join(dir, "./extensions/")
|
||||
|
||||
files, err := ioutil.ReadDir(extensionsDir)
|
||||
if err != nil {
|
||||
logger.Log.Fatal(err)
|
||||
}
|
||||
extensions = make([]*tapApi.Extension, len(files))
|
||||
extensionsMap = make(map[string]*tapApi.Extension)
|
||||
for i, file := range files {
|
||||
filename := file.Name()
|
||||
logger.Log.Infof("Loading extension: %s", filename)
|
||||
extension := &tapApi.Extension{
|
||||
Path: path.Join(extensionsDir, filename),
|
||||
}
|
||||
plug, _ := plugin.Open(extension.Path)
|
||||
extension.Plug = plug
|
||||
symDissector, err := plug.Lookup("Dissector")
|
||||
|
||||
var dissector tapApi.Dissector
|
||||
var ok bool
|
||||
dissector, ok = symDissector.(tapApi.Dissector)
|
||||
if err != nil || !ok {
|
||||
panic(fmt.Sprintf("Failed to load the extension: %s", extension.Path))
|
||||
}
|
||||
dissector.Register(extension)
|
||||
extension.Dissector = dissector
|
||||
extensions[i] = extension
|
||||
extensionsMap[extension.Protocol.Name] = extension
|
||||
}
|
||||
|
||||
sort.Slice(extensions, func(i, j int) bool {
|
||||
return extensions[i].Protocol.Priority < extensions[j].Protocol.Priority
|
||||
})
|
||||
|
||||
for _, extension := range extensions {
|
||||
logger.Log.Infof("Extension Properties: %+v", extension)
|
||||
}
|
||||
|
||||
controllers.InitExtensionsMap(extensionsMap)
|
||||
}
|
||||
|
||||
func hostApi(socketHarOutputChannel chan<- *tapApi.OutputChannelItem) {
|
||||
app := gin.Default()
|
||||
|
||||
app.GET("/echo", func(c *gin.Context) {
|
||||
@@ -104,94 +239,118 @@ func hostApi(socketHarOutputChannel chan<- *tap.OutputChannelItem) {
|
||||
})
|
||||
|
||||
eventHandlers := api.RoutesEventHandlers{
|
||||
SocketHarOutChannel: socketHarOutputChannel,
|
||||
SocketOutChannel: 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
|
||||
app.Use(DisableRootStaticCache())
|
||||
|
||||
api.WebSocketRoutes(app, &eventHandlers)
|
||||
if err := setUIFlags(); err != nil {
|
||||
logger.Log.Errorf("Error setting ui mode, err: %v", err)
|
||||
}
|
||||
app.Use(static.ServeRoot("/", "./site"))
|
||||
|
||||
app.Use(middlewares.CORSMiddleware()) // This has to be called after the static middleware, does not work if its called before
|
||||
|
||||
api.WebSocketRoutes(app, &eventHandlers, startTime)
|
||||
|
||||
if config.Config.StandaloneMode {
|
||||
routes.ConfigRoutes(app)
|
||||
routes.UserRoutes(app)
|
||||
routes.InstallRoutes(app)
|
||||
}
|
||||
if config.Config.OAS {
|
||||
routes.OASRoutes(app)
|
||||
}
|
||||
if config.Config.ServiceMap {
|
||||
routes.ServiceMapRoutes(app)
|
||||
}
|
||||
|
||||
routes.QueryRoutes(app)
|
||||
routes.EntriesRoutes(app)
|
||||
routes.MetadataRoutes(app)
|
||||
routes.StatusRoutes(app)
|
||||
routes.NotFoundRoute(app)
|
||||
|
||||
utils.StartServer(app)
|
||||
}
|
||||
|
||||
func CORSMiddleware() gin.HandlerFunc {
|
||||
func DisableRootStaticCache() 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
|
||||
if c.Request.RequestURI == "/" {
|
||||
// Disable cache only for the main static route
|
||||
c.Writer.Header().Set("Cache-Control", "no-store")
|
||||
}
|
||||
|
||||
c.Next()
|
||||
}
|
||||
}
|
||||
|
||||
func getTapTargets() []string {
|
||||
nodeName := os.Getenv(shared.NodeNameEnvVar)
|
||||
var tappedAddressesPerNodeDict map[string][]string
|
||||
err := json.Unmarshal([]byte(os.Getenv(shared.TappedAddressesPerNodeDictEnvVar)), &tappedAddressesPerNodeDict)
|
||||
func setUIFlags() error {
|
||||
read, err := ioutil.ReadFile(uiIndexPath)
|
||||
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 err
|
||||
}
|
||||
|
||||
replacedContent := strings.Replace(string(read), "__IS_STANDALONE__", strconv.FormatBool(config.Config.StandaloneMode), 1)
|
||||
replacedContent = strings.Replace(replacedContent, "__IS_OAS_ENABLED__", strconv.FormatBool(config.Config.OAS), 1)
|
||||
replacedContent = strings.Replace(replacedContent, "__IS_SERVICE_MAP_ENABLED__", strconv.FormatBool(config.Config.ServiceMap), 1)
|
||||
|
||||
err = ioutil.WriteFile(uiIndexPath, []byte(replacedContent), 0)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func parseEnvVar(env string) map[string][]v1.Pod {
|
||||
var mapOfList map[string][]v1.Pod
|
||||
|
||||
val, present := os.LookupEnv(env)
|
||||
|
||||
if !present {
|
||||
return mapOfList
|
||||
}
|
||||
|
||||
err := json.Unmarshal([]byte(val), &mapOfList)
|
||||
if err != nil {
|
||||
panic(fmt.Sprintf("env var %s's value of %v is invalid! must be map[string][]v1.Pod %v", env, mapOfList, err))
|
||||
}
|
||||
return mapOfList
|
||||
}
|
||||
|
||||
func getTapTargets() []v1.Pod {
|
||||
nodeName := os.Getenv(shared.NodeNameEnvVar)
|
||||
tappedAddressesPerNodeDict := parseEnvVar(shared.TappedAddressesPerNodeDictEnvVar)
|
||||
return tappedAddressesPerNodeDict[nodeName]
|
||||
}
|
||||
|
||||
func getTrafficFilteringOptions() *shared.TrafficFilteringOptions {
|
||||
func getTrafficFilteringOptions() *tapApi.TrafficFilteringOptions {
|
||||
filteringOptionsJson := os.Getenv(shared.MizuFilteringOptionsEnvVar)
|
||||
if filteringOptionsJson == "" {
|
||||
return nil
|
||||
return &tapApi.TrafficFilteringOptions{
|
||||
IgnoredUserAgents: []string{},
|
||||
}
|
||||
}
|
||||
var filteringOptions shared.TrafficFilteringOptions
|
||||
var filteringOptions tapApi.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))
|
||||
panic(fmt.Sprintf("env var %s's value of %s is invalid! json must match the api.TrafficFilteringOptions struct %v", shared.MizuFilteringOptionsEnvVar, filteringOptionsJson, err))
|
||||
}
|
||||
|
||||
return &filteringOptions
|
||||
}
|
||||
|
||||
func filterHarItems(inChannel <-chan *tap.OutputChannelItem, outChannel chan *tap.OutputChannelItem, filterOptions *shared.TrafficFilteringOptions) {
|
||||
func filterItems(inChannel <-chan *tapApi.OutputChannelItem, outChannel chan *tapApi.OutputChannelItem) {
|
||||
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 isHealthCheckByUserAgent(message, filterOptions.HealthChecksUserAgentHeaders) {
|
||||
continue
|
||||
}
|
||||
|
||||
if !filterOptions.DisableRedaction {
|
||||
sensitiveDataFiltering.FilterSensitiveInfoFromHarRequest(message, filterOptions)
|
||||
}
|
||||
|
||||
outChannel <- message
|
||||
}
|
||||
}
|
||||
|
||||
func isHealthCheckByUserAgent(message *tap.OutputChannelItem, userAgentsToIgnore []string) bool {
|
||||
for _, header := range message.HarEntry.Request.Headers {
|
||||
if strings.ToLower(header.Name) == "user-agent" {
|
||||
for _, userAgent := range userAgentsToIgnore {
|
||||
if strings.Contains(strings.ToLower(header.Value), strings.ToLower(userAgent)) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func pipeTapChannelToSocket(connection *websocket.Conn, messageDataChannel <-chan *tap.OutputChannelItem) {
|
||||
func pipeTapChannelToSocket(connection *websocket.Conn, messageDataChannel <-chan *tapApi.OutputChannelItem) {
|
||||
if connection == nil {
|
||||
panic("Websocket connection is nil")
|
||||
}
|
||||
@@ -203,31 +362,98 @@ func pipeTapChannelToSocket(connection *websocket.Conn, messageDataChannel <-cha
|
||||
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)
|
||||
logger.Log.Errorf("error converting message to json %v, err: %s, (%v,%+v)", messageData, err, err, err)
|
||||
continue
|
||||
}
|
||||
|
||||
// NOTE: This is where the `*tapApi.OutputChannelItem` leaves the code
|
||||
// and goes into the intermediate WebSocket.
|
||||
err = connection.WriteMessage(websocket.TextMessage, marshaledData)
|
||||
if err != nil {
|
||||
rlog.Infof("error sending message through socket server %s, (%v,%+v)\n", err, err, err)
|
||||
logger.Log.Errorf("error sending message through socket server %v, err: %s, (%v,%+v)", messageData, err, err, err)
|
||||
if errors.Is(err, syscall.EPIPE) {
|
||||
logger.Log.Warning("detected socket disconnection, reestablishing socket connection")
|
||||
connection, err = dialSocketWithRetry(*apiServerAddress, socketConnectionRetries, socketConnectionRetryDelay)
|
||||
if err != nil {
|
||||
logger.Log.Fatalf("error reestablishing socket connection: %v", err)
|
||||
} else {
|
||||
logger.Log.Info("recovered connection successfully")
|
||||
}
|
||||
}
|
||||
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
|
||||
}
|
||||
func getSyncEntriesConfig() *shared.SyncEntriesConfig {
|
||||
syncEntriesConfigJson := os.Getenv(shared.SyncEntriesConfigEnvVar)
|
||||
if syncEntriesConfigJson == "" {
|
||||
return nil
|
||||
}
|
||||
|
||||
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
|
||||
var syncEntriesConfig = &shared.SyncEntriesConfig{}
|
||||
err := json.Unmarshal([]byte(syncEntriesConfigJson), syncEntriesConfig)
|
||||
if err != nil {
|
||||
panic(fmt.Sprintf("env var %s's value of %s is invalid! json must match the shared.SyncEntriesConfig struct, err: %v", shared.SyncEntriesConfigEnvVar, syncEntriesConfigJson, err))
|
||||
}
|
||||
|
||||
return syncEntriesConfig
|
||||
}
|
||||
|
||||
func determineLogLevel() (logLevel logging.Level) {
|
||||
logLevel, err := logging.LogLevel(os.Getenv(shared.LogLevelEnvVar))
|
||||
if err != nil {
|
||||
logLevel = logging.INFO
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func dialSocketWithRetry(socketAddress string, retryAmount int, retryDelay time.Duration) (*websocket.Conn, error) {
|
||||
var lastErr error
|
||||
dialer := &websocket.Dialer{ // we use our own dialer instead of the default due to the default's 45 sec handshake timeout, we occasionally encounter hanging socket handshakes when tapper tries to connect to api too soon
|
||||
Proxy: http.ProxyFromEnvironment,
|
||||
HandshakeTimeout: socketHandshakeTimeout,
|
||||
}
|
||||
for i := 1; i < retryAmount; i++ {
|
||||
socketConnection, _, err := dialer.Dial(socketAddress, nil)
|
||||
if err != nil {
|
||||
if i < retryAmount {
|
||||
logger.Log.Infof("socket connection to %s failed: %v, retrying %d out of %d in %d seconds...", socketAddress, err, i, retryAmount, retryDelay/time.Second)
|
||||
time.Sleep(retryDelay)
|
||||
}
|
||||
} else {
|
||||
go handleIncomingMessageAsTapper(socketConnection)
|
||||
return socketConnection, nil
|
||||
}
|
||||
}
|
||||
return nil, lastErr
|
||||
}
|
||||
|
||||
func handleIncomingMessageAsTapper(socketConnection *websocket.Conn) {
|
||||
for {
|
||||
if _, message, err := socketConnection.ReadMessage(); err != nil {
|
||||
logger.Log.Errorf("error reading message from socket connection, err: %s, (%v,%+v)", err, err, err)
|
||||
if errors.Is(err, syscall.EPIPE) {
|
||||
// socket has disconnected, we can safely stop this goroutine
|
||||
return
|
||||
}
|
||||
} else {
|
||||
var socketMessageBase shared.WebSocketMessageMetadata
|
||||
if err := json.Unmarshal(message, &socketMessageBase); err != nil {
|
||||
logger.Log.Errorf("Could not unmarshal websocket message %v", err)
|
||||
} else {
|
||||
switch socketMessageBase.MessageType {
|
||||
case shared.WebSocketMessageTypeTapConfig:
|
||||
var tapConfigMessage *shared.WebSocketTapConfigMessage
|
||||
if err := json.Unmarshal(message, &tapConfigMessage); err != nil {
|
||||
logger.Log.Errorf("received unknown message from socket connection: %s, err: %s, (%v,%+v)", string(message), err, err, err)
|
||||
} else {
|
||||
tap.UpdateTapTargets(tapConfigMessage.TapTargets)
|
||||
}
|
||||
default:
|
||||
logger.Log.Warningf("Received socket message of type %s for which no handlers are defined", socketMessageBase.MessageType)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
107
agent/pkg/api/contract_validation.go
Normal file
107
agent/pkg/api/contract_validation.go
Normal file
@@ -0,0 +1,107 @@
|
||||
package api
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
|
||||
"github.com/getkin/kin-openapi/openapi3"
|
||||
"github.com/getkin/kin-openapi/openapi3filter"
|
||||
"github.com/getkin/kin-openapi/routers"
|
||||
legacyrouter "github.com/getkin/kin-openapi/routers/legacy"
|
||||
|
||||
"github.com/up9inc/mizu/shared"
|
||||
"github.com/up9inc/mizu/tap/api"
|
||||
)
|
||||
|
||||
const (
|
||||
ContractNotApplicable api.ContractStatus = 0
|
||||
ContractPassed api.ContractStatus = 1
|
||||
ContractFailed api.ContractStatus = 2
|
||||
)
|
||||
|
||||
func loadOAS(ctx context.Context) (doc *openapi3.T, contractContent string, router routers.Router, err error) {
|
||||
path := fmt.Sprintf("%s%s", shared.ConfigDirPath, shared.ContractFileName)
|
||||
bytes, err := ioutil.ReadFile(path)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
contractContent = string(bytes)
|
||||
loader := &openapi3.Loader{Context: ctx}
|
||||
doc, _ = loader.LoadFromData(bytes)
|
||||
err = doc.Validate(ctx)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
router, _ = legacyrouter.NewRouter(doc)
|
||||
return
|
||||
}
|
||||
|
||||
func validateOAS(ctx context.Context, doc *openapi3.T, router routers.Router, req *http.Request, res *http.Response) (isValid bool, reqErr error, resErr error) {
|
||||
isValid = true
|
||||
reqErr = nil
|
||||
resErr = nil
|
||||
|
||||
// Find route
|
||||
route, pathParams, err := router.FindRoute(req)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
// Validate request
|
||||
requestValidationInput := &openapi3filter.RequestValidationInput{
|
||||
Request: req,
|
||||
PathParams: pathParams,
|
||||
Route: route,
|
||||
}
|
||||
if reqErr = openapi3filter.ValidateRequest(ctx, requestValidationInput); reqErr != nil {
|
||||
isValid = false
|
||||
}
|
||||
|
||||
responseValidationInput := &openapi3filter.ResponseValidationInput{
|
||||
RequestValidationInput: requestValidationInput,
|
||||
Status: res.StatusCode,
|
||||
Header: res.Header,
|
||||
}
|
||||
|
||||
if res.Body != nil {
|
||||
body, _ := ioutil.ReadAll(res.Body)
|
||||
res.Body = ioutil.NopCloser(bytes.NewBuffer(body))
|
||||
responseValidationInput.SetBodyBytes(body)
|
||||
}
|
||||
|
||||
// Validate response.
|
||||
if resErr = openapi3filter.ValidateResponse(ctx, responseValidationInput); resErr != nil {
|
||||
isValid = false
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func handleOAS(ctx context.Context, doc *openapi3.T, router routers.Router, req *http.Request, res *http.Response, contractContent string) (contract api.Contract) {
|
||||
contract = api.Contract{
|
||||
Content: contractContent,
|
||||
Status: ContractNotApplicable,
|
||||
}
|
||||
|
||||
isValid, reqErr, resErr := validateOAS(ctx, doc, router, req, res)
|
||||
if isValid {
|
||||
contract.Status = ContractPassed
|
||||
} else {
|
||||
contract.Status = ContractFailed
|
||||
if reqErr != nil {
|
||||
contract.RequestReason = reqErr.Error()
|
||||
} else {
|
||||
contract.RequestReason = ""
|
||||
}
|
||||
if resErr != nil {
|
||||
contract.ResponseReason = resErr.Error()
|
||||
} else {
|
||||
contract.ResponseReason = ""
|
||||
}
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
@@ -7,22 +7,25 @@ import (
|
||||
"fmt"
|
||||
"mizuserver/pkg/holder"
|
||||
"mizuserver/pkg/providers"
|
||||
"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/servicemap"
|
||||
|
||||
"github.com/google/martian/har"
|
||||
"github.com/up9inc/mizu/shared"
|
||||
"github.com/up9inc/mizu/shared/logger"
|
||||
tapApi "github.com/up9inc/mizu/tap/api"
|
||||
|
||||
"mizuserver/pkg/database"
|
||||
"mizuserver/pkg/models"
|
||||
"mizuserver/pkg/oas"
|
||||
"mizuserver/pkg/resolver"
|
||||
"mizuserver/pkg/utils"
|
||||
|
||||
basenine "github.com/up9inc/basenine/client/go"
|
||||
)
|
||||
|
||||
var k8sResolver *resolver.Resolver
|
||||
@@ -31,7 +34,7 @@ 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)
|
||||
logger.Log.Infof("error creating k8s resolver %s", err)
|
||||
return
|
||||
}
|
||||
ctx := context.Background()
|
||||
@@ -40,7 +43,7 @@ func StartResolving(namespace string) {
|
||||
for {
|
||||
select {
|
||||
case err := <-errOut:
|
||||
rlog.Infof("name resolving error %s", err)
|
||||
logger.Log.Infof("name resolving error %s", err)
|
||||
}
|
||||
}
|
||||
}()
|
||||
@@ -49,17 +52,19 @@ func StartResolving(namespace string) {
|
||||
holder.SetResolver(res)
|
||||
}
|
||||
|
||||
func StartReadingEntries(harChannel <-chan *tap.OutputChannelItem, workingDir *string) {
|
||||
func StartReadingEntries(harChannel <-chan *tapApi.OutputChannelItem, workingDir *string, extensionsMap map[string]*tapApi.Extension) {
|
||||
if workingDir != nil && *workingDir != "" {
|
||||
startReadingFiles(*workingDir)
|
||||
} else {
|
||||
startReadingChannel(harChannel)
|
||||
startReadingChannel(harChannel, extensionsMap)
|
||||
}
|
||||
}
|
||||
|
||||
func startReadingFiles(workingDir string) {
|
||||
err := os.MkdirAll(workingDir, os.ModePerm)
|
||||
utils.CheckErr(err)
|
||||
if err := os.MkdirAll(workingDir, os.ModePerm); err != nil {
|
||||
logger.Log.Errorf("Failed to make dir: %s, err: %v", workingDir, err)
|
||||
return
|
||||
}
|
||||
|
||||
for true {
|
||||
dir, _ := os.Open(workingDir)
|
||||
@@ -74,7 +79,7 @@ func startReadingFiles(workingDir string) {
|
||||
sort.Sort(utils.ByModTime(harFiles))
|
||||
|
||||
if len(harFiles) == 0 {
|
||||
rlog.Infof("Waiting for new files\n")
|
||||
logger.Log.Infof("Waiting for new files")
|
||||
time.Sleep(3 * time.Second)
|
||||
continue
|
||||
}
|
||||
@@ -87,53 +92,73 @@ func startReadingFiles(workingDir string) {
|
||||
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) {
|
||||
func startReadingChannel(outputItems <-chan *tapApi.OutputChannelItem, extensionsMap map[string]*tapApi.Extension) {
|
||||
if outputItems == nil {
|
||||
panic("Channel of captured messages is nil")
|
||||
}
|
||||
|
||||
connection, err := basenine.NewConnection(shared.BasenineHost, shared.BaseninePort)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
connection.InsertMode()
|
||||
|
||||
disableOASValidation := false
|
||||
ctx := context.Background()
|
||||
doc, contractContent, router, err := loadOAS(ctx)
|
||||
if err != nil {
|
||||
logger.Log.Infof("Disabled OAS validation: %s", err.Error())
|
||||
disableOASValidation = true
|
||||
}
|
||||
|
||||
for item := range outputItems {
|
||||
providers.EntryAdded()
|
||||
saveHarToDb(item.HarEntry, item.ConnectionInfo)
|
||||
|
||||
extension := extensionsMap[item.Protocol.Name]
|
||||
resolvedSource, resolvedDestionation := resolveIP(item.ConnectionInfo)
|
||||
mizuEntry := extension.Dissector.Analyze(item, resolvedSource, resolvedDestionation)
|
||||
if extension.Protocol.Name == "http" {
|
||||
if !disableOASValidation {
|
||||
var httpPair tapApi.HTTPRequestResponsePair
|
||||
json.Unmarshal([]byte(mizuEntry.HTTPPair), &httpPair)
|
||||
|
||||
contract := handleOAS(ctx, doc, router, httpPair.Request.Payload.RawRequest, httpPair.Response.Payload.RawResponse, contractContent)
|
||||
mizuEntry.ContractStatus = contract.Status
|
||||
mizuEntry.ContractRequestReason = contract.RequestReason
|
||||
mizuEntry.ContractResponseReason = contract.ResponseReason
|
||||
mizuEntry.ContractContent = contract.Content
|
||||
}
|
||||
|
||||
harEntry, err := utils.NewEntry(mizuEntry.Request, mizuEntry.Response, mizuEntry.StartTime, mizuEntry.ElapsedTime)
|
||||
if err == nil {
|
||||
rules, _, _ := models.RunValidationRulesState(*harEntry, mizuEntry.Destination.Name)
|
||||
mizuEntry.Rules = rules
|
||||
}
|
||||
|
||||
oas.GetOasGeneratorInstance().PushEntry(harEntry)
|
||||
}
|
||||
|
||||
data, err := json.Marshal(mizuEntry)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
connection.SendText(string(data))
|
||||
|
||||
servicemap.GetInstance().NewTCPEntry(mizuEntry.Source, mizuEntry.Destination, &item.Protocol)
|
||||
}
|
||||
}
|
||||
|
||||
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
|
||||
)
|
||||
func resolveIP(connectionInfo *tapApi.ConnectionInfo) (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)
|
||||
logger.Log.Debugf("Cannot find resolved name to source: %s", unresolvedSource)
|
||||
if os.Getenv("SKIP_NOT_RESOLVED_SOURCE") == "1" {
|
||||
return
|
||||
}
|
||||
@@ -141,64 +166,18 @@ func saveHarToDb(entry *har.Entry, connectionInfo *tap.ConnectionInfo) {
|
||||
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)
|
||||
logger.Log.Debugf("Cannot find resolved name to dest: %s", 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
|
||||
return resolvedSource, resolvedDestination
|
||||
}
|
||||
|
||||
func CheckIsServiceIP(address string) bool {
|
||||
if k8sResolver == nil {
|
||||
return false
|
||||
}
|
||||
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
|
||||
}
|
||||
|
||||
@@ -1,14 +1,22 @@
|
||||
package api
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/gorilla/websocket"
|
||||
"github.com/romana/rlog"
|
||||
"github.com/up9inc/mizu/shared/debounce"
|
||||
"fmt"
|
||||
"mizuserver/pkg/middlewares"
|
||||
"mizuserver/pkg/models"
|
||||
"net/http"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/gorilla/websocket"
|
||||
basenine "github.com/up9inc/basenine/client/go"
|
||||
"github.com/up9inc/mizu/shared"
|
||||
"github.com/up9inc/mizu/shared/debounce"
|
||||
"github.com/up9inc/mizu/shared/logger"
|
||||
tapApi "github.com/up9inc/mizu/tap/api"
|
||||
)
|
||||
|
||||
type EventHandlers interface {
|
||||
@@ -38,19 +46,20 @@ func init() {
|
||||
connectedWebsockets = make(map[int]*SocketConnection, 0)
|
||||
}
|
||||
|
||||
func WebSocketRoutes(app *gin.Engine, eventHandlers EventHandlers) {
|
||||
func WebSocketRoutes(app *gin.Engine, eventHandlers EventHandlers, startTime int64) {
|
||||
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)
|
||||
websocketHandler(c.Writer, c.Request, eventHandlers, false, startTime)
|
||||
}, middlewares.RequiresAuth())
|
||||
|
||||
app.GET("/wsTapper", func(c *gin.Context) { // TODO: add m2m authentication to this route
|
||||
websocketHandler(c.Writer, c.Request, eventHandlers, true, startTime)
|
||||
})
|
||||
}
|
||||
|
||||
func websocketHandler(w http.ResponseWriter, r *http.Request, eventHandlers EventHandlers, isTapper bool) {
|
||||
conn, err := websocketUpgrader.Upgrade(w, r, nil)
|
||||
func websocketHandler(w http.ResponseWriter, r *http.Request, eventHandlers EventHandlers, isTapper bool, startTime int64) {
|
||||
ws, err := websocketUpgrader.Upgrade(w, r, nil)
|
||||
if err != nil {
|
||||
rlog.Errorf("Failed to set websocket upgrade: %v", err)
|
||||
logger.Log.Errorf("Failed to set websocket upgrade: %v", err)
|
||||
return
|
||||
}
|
||||
|
||||
@@ -58,30 +67,114 @@ func websocketHandler(w http.ResponseWriter, r *http.Request, eventHandlers Even
|
||||
|
||||
connectedWebsocketIdCounter++
|
||||
socketId := connectedWebsocketIdCounter
|
||||
connectedWebsockets[socketId] = &SocketConnection{connection: conn, lock: &sync.Mutex{}, eventHandlers: eventHandlers, isTapper: isTapper}
|
||||
connectedWebsockets[socketId] = &SocketConnection{connection: ws, lock: &sync.Mutex{}, eventHandlers: eventHandlers, isTapper: isTapper}
|
||||
|
||||
websocketIdsLock.Unlock()
|
||||
|
||||
var connection *basenine.Connection
|
||||
var isQuerySet bool
|
||||
|
||||
// `!isTapper` means it's a connection from the web UI
|
||||
if !isTapper {
|
||||
connection, err = basenine.NewConnection(shared.BasenineHost, shared.BaseninePort)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
|
||||
data := make(chan []byte)
|
||||
meta := make(chan []byte)
|
||||
|
||||
defer func() {
|
||||
socketCleanup(socketId, connectedWebsockets[socketId])
|
||||
data <- []byte(basenine.CloseChannel)
|
||||
meta <- []byte(basenine.CloseChannel)
|
||||
connection.Close()
|
||||
}()
|
||||
|
||||
eventHandlers.WebSocketConnect(socketId, isTapper)
|
||||
|
||||
startTimeBytes, _ := models.CreateWebsocketStartTimeMessage(startTime)
|
||||
SendToSocket(socketId, startTimeBytes)
|
||||
|
||||
for {
|
||||
_, msg, err := conn.ReadMessage()
|
||||
_, msg, err := ws.ReadMessage()
|
||||
if err != nil {
|
||||
rlog.Errorf("Error reading message, socket id: %d, error: %v", socketId, err)
|
||||
if _, ok := err.(*websocket.CloseError); ok {
|
||||
logger.Log.Debugf("Received websocket close message, socket id: %d", socketId)
|
||||
} else {
|
||||
logger.Log.Errorf("Error reading message, socket id: %d, error: %v", socketId, err)
|
||||
}
|
||||
|
||||
break
|
||||
}
|
||||
eventHandlers.WebSocketMessage(socketId, msg)
|
||||
|
||||
if !isTapper && !isQuerySet {
|
||||
query := string(msg)
|
||||
err = basenine.Validate(shared.BasenineHost, shared.BaseninePort, query)
|
||||
if err != nil {
|
||||
toastBytes, _ := models.CreateWebsocketToastMessage(&models.ToastMessage{
|
||||
Type: "error",
|
||||
AutoClose: 5000,
|
||||
Text: fmt.Sprintf("Syntax error: %s", err.Error()),
|
||||
})
|
||||
SendToSocket(socketId, toastBytes)
|
||||
break
|
||||
}
|
||||
|
||||
isQuerySet = true
|
||||
|
||||
handleDataChannel := func(c *basenine.Connection, data chan []byte) {
|
||||
for {
|
||||
bytes := <-data
|
||||
|
||||
if string(bytes) == basenine.CloseChannel {
|
||||
return
|
||||
}
|
||||
|
||||
var entry *tapApi.Entry
|
||||
err = json.Unmarshal(bytes, &entry)
|
||||
|
||||
base := tapApi.Summarize(entry)
|
||||
|
||||
baseEntryBytes, _ := models.CreateBaseEntryWebSocketMessage(base)
|
||||
SendToSocket(socketId, baseEntryBytes)
|
||||
}
|
||||
}
|
||||
|
||||
handleMetaChannel := func(c *basenine.Connection, meta chan []byte) {
|
||||
for {
|
||||
bytes := <-meta
|
||||
|
||||
if string(bytes) == basenine.CloseChannel {
|
||||
return
|
||||
}
|
||||
|
||||
var metadata *basenine.Metadata
|
||||
err = json.Unmarshal(bytes, &metadata)
|
||||
if err != nil {
|
||||
logger.Log.Debugf("Error recieving metadata: %v", err.Error())
|
||||
}
|
||||
|
||||
metadataBytes, _ := models.CreateWebsocketQueryMetadataMessage(metadata)
|
||||
SendToSocket(socketId, metadataBytes)
|
||||
}
|
||||
}
|
||||
|
||||
go handleDataChannel(connection, data)
|
||||
go handleMetaChannel(connection, meta)
|
||||
|
||||
connection.Query(query, data, meta)
|
||||
} else {
|
||||
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)
|
||||
logger.Log.Errorf("Error closing socket connection for socket id %d: %v", socketId, err)
|
||||
}
|
||||
|
||||
websocketIdsLock.Lock()
|
||||
@@ -92,7 +185,7 @@ func socketCleanup(socketId int, socketConnection *SocketConnection) {
|
||||
}
|
||||
|
||||
var db = debounce.NewDebouncer(time.Second*5, func() {
|
||||
rlog.Error("Successfully sent to socket")
|
||||
logger.Log.Error("Successfully sent to socket")
|
||||
})
|
||||
|
||||
func SendToSocket(socketId int, message []byte) error {
|
||||
@@ -104,7 +197,7 @@ func SendToSocket(socketId int, message []byte) error {
|
||||
var sent = false
|
||||
time.AfterFunc(time.Second*5, func() {
|
||||
if !sent {
|
||||
rlog.Error("Socket timed out")
|
||||
logger.Log.Error("Socket timed out")
|
||||
socketCleanup(socketId, socketObj)
|
||||
}
|
||||
})
|
||||
|
||||
@@ -5,12 +5,14 @@ import (
|
||||
"fmt"
|
||||
"mizuserver/pkg/models"
|
||||
"mizuserver/pkg/providers"
|
||||
"mizuserver/pkg/providers/tappers"
|
||||
"mizuserver/pkg/up9"
|
||||
"sync"
|
||||
|
||||
"github.com/romana/rlog"
|
||||
tapApi "github.com/up9inc/mizu/tap/api"
|
||||
|
||||
"github.com/up9inc/mizu/shared"
|
||||
"github.com/up9inc/mizu/tap"
|
||||
"github.com/up9inc/mizu/shared/logger"
|
||||
)
|
||||
|
||||
var browserClientSocketUUIDs = make([]int, 0)
|
||||
@@ -18,7 +20,7 @@ var socketListLock = sync.Mutex{}
|
||||
|
||||
type RoutesEventHandlers struct {
|
||||
EventHandlers
|
||||
SocketHarOutChannel chan<- *tap.OutputChannelItem
|
||||
SocketOutChannel chan<- *tapApi.OutputChannelItem
|
||||
}
|
||||
|
||||
func init() {
|
||||
@@ -27,9 +29,10 @@ func init() {
|
||||
|
||||
func (h *RoutesEventHandlers) WebSocketConnect(socketId int, isTapper bool) {
|
||||
if isTapper {
|
||||
rlog.Infof("Websocket event - Tapper connected, socket ID: %d", socketId)
|
||||
logger.Log.Infof("Websocket event - Tapper connected, socket ID: %d", socketId)
|
||||
tappers.Connected()
|
||||
} else {
|
||||
rlog.Infof("Websocket event - Browser socket connected, socket ID: %d", socketId)
|
||||
logger.Log.Infof("Websocket event - Browser socket connected, socket ID: %d", socketId)
|
||||
socketListLock.Lock()
|
||||
browserClientSocketUUIDs = append(browserClientSocketUUIDs, socketId)
|
||||
socketListLock.Unlock()
|
||||
@@ -38,9 +41,10 @@ func (h *RoutesEventHandlers) WebSocketConnect(socketId int, isTapper bool) {
|
||||
|
||||
func (h *RoutesEventHandlers) WebSocketDisconnect(socketId int, isTapper bool) {
|
||||
if isTapper {
|
||||
rlog.Infof("Websocket event - Tapper disconnected, socket ID: %d", socketId)
|
||||
logger.Log.Infof("Websocket event - Tapper disconnected, socket ID: %d", socketId)
|
||||
tappers.Disconnected()
|
||||
} else {
|
||||
rlog.Infof("Websocket event - Browser socket disconnected, socket ID: %d", socketId)
|
||||
logger.Log.Infof("Websocket event - Browser socket disconnected, socket ID: %d", socketId)
|
||||
socketListLock.Lock()
|
||||
removeSocketUUIDFromBrowserSlice(socketId)
|
||||
socketListLock.Unlock()
|
||||
@@ -52,7 +56,7 @@ func BroadcastToBrowserClients(message []byte) {
|
||||
go func(socketId int) {
|
||||
err := SendToSocket(socketId, message)
|
||||
if err != nil {
|
||||
rlog.Errorf("error sending message to socket ID %d: %v", socketId, err)
|
||||
logger.Log.Errorf("error sending message to socket ID %d: %v", socketId, err)
|
||||
}
|
||||
}(socketId)
|
||||
}
|
||||
@@ -62,36 +66,36 @@ 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)
|
||||
logger.Log.Infof("Could not unmarshal websocket message %v", 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)
|
||||
logger.Log.Infof("Could not unmarshal message of message type %s %v", socketMessageBase.MessageType, err)
|
||||
} else {
|
||||
h.SocketHarOutChannel <- tappedEntryMessage.Data
|
||||
// NOTE: This is where the message comes back from the intermediate WebSocket to code.
|
||||
h.SocketOutChannel <- 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)
|
||||
logger.Log.Infof("Could not unmarshal message of message type %s %v", 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)
|
||||
logger.Log.Infof("Could not unmarshal message of message type %s %v", socketMessageBase.MessageType, err)
|
||||
} else {
|
||||
handleTLSLink(outboundLinkMessage)
|
||||
}
|
||||
default:
|
||||
rlog.Infof("Received socket message of type %s for which no handlers are defined", socketMessageBase.MessageType)
|
||||
logger.Log.Infof("Received socket message of type %s for which no handlers are defined", socketMessageBase.MessageType)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -112,9 +116,9 @@ func handleTLSLink(outboundLinkMessage models.WebsocketOutboundLinkMessage) {
|
||||
}
|
||||
marshaledMessage, err := json.Marshal(outboundLinkMessage)
|
||||
if err != nil {
|
||||
rlog.Errorf("Error marshaling outbound link message for broadcasting: %v", err)
|
||||
logger.Log.Errorf("Error marshaling outbound link message for broadcasting: %v", err)
|
||||
} else {
|
||||
rlog.Errorf("Broadcasting outboundlink message %s", string(marshaledMessage))
|
||||
logger.Log.Errorf("Broadcasting outboundlink message %s", string(marshaledMessage))
|
||||
BroadcastToBrowserClients(marshaledMessage)
|
||||
}
|
||||
}
|
||||
|
||||
53
agent/pkg/config/config.go
Normal file
53
agent/pkg/config/config.go
Normal file
@@ -0,0 +1,53 @@
|
||||
package config
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"github.com/up9inc/mizu/shared"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
)
|
||||
|
||||
// these values are used when the config.json file is not present
|
||||
const (
|
||||
defaultMaxDatabaseSizeBytes int64 = 200 * 1000 * 1000
|
||||
DefaultDatabasePath string = "./entries"
|
||||
)
|
||||
|
||||
var Config *shared.MizuAgentConfig
|
||||
|
||||
func LoadConfig() error {
|
||||
if Config != nil {
|
||||
return nil
|
||||
}
|
||||
filePath := fmt.Sprintf("%s%s", shared.ConfigDirPath, shared.ConfigFileName)
|
||||
|
||||
content, err := ioutil.ReadFile(filePath)
|
||||
if err != nil {
|
||||
if os.IsNotExist(err) {
|
||||
return applyDefaultConfig()
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
if err = json.Unmarshal(content, &Config); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func applyDefaultConfig() error {
|
||||
defaultConfig, err := getDefaultConfig()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
Config = defaultConfig
|
||||
return nil
|
||||
}
|
||||
|
||||
func getDefaultConfig() (*shared.MizuAgentConfig, error) {
|
||||
return &shared.MizuAgentConfig{
|
||||
MaxDBSizeBytes: defaultMaxDatabaseSizeBytes,
|
||||
AgentDatabasePath: DefaultDatabasePath,
|
||||
}, nil
|
||||
}
|
||||
154
agent/pkg/controllers/config_controller.go
Normal file
154
agent/pkg/controllers/config_controller.go
Normal file
@@ -0,0 +1,154 @@
|
||||
package controllers
|
||||
|
||||
import (
|
||||
"context"
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/up9inc/mizu/shared"
|
||||
"github.com/up9inc/mizu/shared/kubernetes"
|
||||
"github.com/up9inc/mizu/shared/logger"
|
||||
tapApi "github.com/up9inc/mizu/tap/api"
|
||||
v1 "k8s.io/api/core/v1"
|
||||
"mizuserver/pkg/config"
|
||||
"mizuserver/pkg/models"
|
||||
"mizuserver/pkg/providers"
|
||||
"mizuserver/pkg/providers/tapConfig"
|
||||
"mizuserver/pkg/providers/tappedPods"
|
||||
"mizuserver/pkg/providers/tappers"
|
||||
"net/http"
|
||||
"regexp"
|
||||
"time"
|
||||
)
|
||||
|
||||
var cancelTapperSyncer context.CancelFunc
|
||||
|
||||
func PostTapConfig(c *gin.Context) {
|
||||
requestTapConfig := &models.TapConfig{}
|
||||
|
||||
if err := c.Bind(requestTapConfig); err != nil {
|
||||
c.JSON(http.StatusBadRequest, err)
|
||||
return
|
||||
}
|
||||
|
||||
if cancelTapperSyncer != nil {
|
||||
cancelTapperSyncer()
|
||||
|
||||
tappedPods.Set([]*shared.PodInfo{})
|
||||
tappers.ResetStatus()
|
||||
|
||||
broadcastTappedPodsStatus()
|
||||
}
|
||||
|
||||
var tappedNamespaces []string
|
||||
for namespace, tapped := range requestTapConfig.TappedNamespaces {
|
||||
if tapped {
|
||||
tappedNamespaces = append(tappedNamespaces, namespace)
|
||||
}
|
||||
}
|
||||
|
||||
podRegex, _ := regexp.Compile(".*")
|
||||
|
||||
kubernetesProvider, err := providers.GetKubernetesProvider()
|
||||
if err != nil {
|
||||
c.JSON(http.StatusInternalServerError, err)
|
||||
return
|
||||
}
|
||||
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
|
||||
if _, err := startMizuTapperSyncer(ctx, kubernetesProvider, tappedNamespaces, *podRegex, []string{}, tapApi.TrafficFilteringOptions{}, false); err != nil {
|
||||
c.JSON(http.StatusInternalServerError, err)
|
||||
cancel()
|
||||
return
|
||||
}
|
||||
|
||||
cancelTapperSyncer = cancel
|
||||
tapConfig.Save(requestTapConfig)
|
||||
|
||||
c.JSON(http.StatusOK, "OK")
|
||||
}
|
||||
|
||||
func GetTapConfig(c *gin.Context) {
|
||||
kubernetesProvider, err := providers.GetKubernetesProvider()
|
||||
if err != nil {
|
||||
c.JSON(http.StatusInternalServerError, err)
|
||||
return
|
||||
}
|
||||
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
defer cancel()
|
||||
|
||||
namespaces, err := kubernetesProvider.ListAllNamespaces(ctx)
|
||||
if err != nil {
|
||||
c.JSON(http.StatusInternalServerError, err)
|
||||
return
|
||||
}
|
||||
|
||||
savedTapConfig := tapConfig.Get()
|
||||
|
||||
tappedNamespaces := make(map[string]bool)
|
||||
for _, namespace := range namespaces {
|
||||
if namespace.Name == config.Config.MizuResourcesNamespace {
|
||||
continue
|
||||
}
|
||||
|
||||
tappedNamespaces[namespace.Name] = savedTapConfig.TappedNamespaces[namespace.Name]
|
||||
}
|
||||
|
||||
tapConfigToReturn := models.TapConfig{TappedNamespaces: tappedNamespaces}
|
||||
c.JSON(http.StatusOK, tapConfigToReturn)
|
||||
}
|
||||
|
||||
func startMizuTapperSyncer(ctx context.Context, provider *kubernetes.Provider, targetNamespaces []string, podFilterRegex regexp.Regexp, ignoredUserAgents []string, mizuApiFilteringOptions tapApi.TrafficFilteringOptions, serviceMesh bool) (*kubernetes.MizuTapperSyncer, error) {
|
||||
tapperSyncer, err := kubernetes.CreateAndStartMizuTapperSyncer(ctx, provider, kubernetes.TapperSyncerConfig{
|
||||
TargetNamespaces: targetNamespaces,
|
||||
PodFilterRegex: podFilterRegex,
|
||||
MizuResourcesNamespace: config.Config.MizuResourcesNamespace,
|
||||
AgentImage: config.Config.AgentImage,
|
||||
TapperResources: config.Config.TapperResources,
|
||||
ImagePullPolicy: v1.PullPolicy(config.Config.PullPolicy),
|
||||
LogLevel: config.Config.LogLevel,
|
||||
IgnoredUserAgents: ignoredUserAgents,
|
||||
MizuApiFilteringOptions: mizuApiFilteringOptions,
|
||||
MizuServiceAccountExists: true, //assume service account exists since install mode will not function without it anyway
|
||||
ServiceMesh: serviceMesh,
|
||||
}, time.Now())
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// handle tapperSyncer events (pod changes and errors)
|
||||
go func() {
|
||||
for {
|
||||
select {
|
||||
case syncerErr, ok := <-tapperSyncer.ErrorOut:
|
||||
if !ok {
|
||||
logger.Log.Debug("mizuTapperSyncer err channel closed, ending listener loop")
|
||||
return
|
||||
}
|
||||
logger.Log.Fatalf("fatal tap syncer error: %v", syncerErr)
|
||||
case _, ok := <-tapperSyncer.TapPodChangesOut:
|
||||
if !ok {
|
||||
logger.Log.Debug("mizuTapperSyncer pod changes channel closed, ending listener loop")
|
||||
return
|
||||
}
|
||||
|
||||
tappedPods.Set(kubernetes.GetPodInfosForPods(tapperSyncer.CurrentlyTappedPods))
|
||||
broadcastTappedPodsStatus()
|
||||
case tapperStatus, ok := <-tapperSyncer.TapperStatusChangedOut:
|
||||
if !ok {
|
||||
logger.Log.Debug("mizuTapperSyncer tapper status changed channel closed, ending listener loop")
|
||||
return
|
||||
}
|
||||
|
||||
tappers.SetStatus(&tapperStatus)
|
||||
broadcastTappedPodsStatus()
|
||||
case <-ctx.Done():
|
||||
logger.Log.Debug("mizuTapperSyncer event listener loop exiting due to context done")
|
||||
return
|
||||
}
|
||||
}
|
||||
}()
|
||||
|
||||
return tapperSyncer, nil
|
||||
}
|
||||
@@ -2,256 +2,144 @@ 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"
|
||||
"strconv"
|
||||
"time"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/google/martian/har"
|
||||
"github.com/romana/rlog"
|
||||
|
||||
basenine "github.com/up9inc/basenine/client/go"
|
||||
"github.com/up9inc/mizu/shared"
|
||||
"github.com/up9inc/mizu/shared/logger"
|
||||
tapApi "github.com/up9inc/mizu/tap/api"
|
||||
)
|
||||
|
||||
var extensionsMap map[string]*tapApi.Extension // global
|
||||
|
||||
func InitExtensionsMap(ref map[string]*tapApi.Extension) {
|
||||
extensionsMap = ref
|
||||
}
|
||||
|
||||
func Error(c *gin.Context, err error) bool {
|
||||
if err != nil {
|
||||
logger.Log.Errorf("Error getting entry: %v", err)
|
||||
c.Error(err)
|
||||
c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{
|
||||
"error": true,
|
||||
"type": "error",
|
||||
"autoClose": "5000",
|
||||
"msg": err.Error(),
|
||||
})
|
||||
return true // signal that there was an error and the caller should return
|
||||
}
|
||||
return false // no error, can continue
|
||||
}
|
||||
|
||||
func GetEntries(c *gin.Context) {
|
||||
entriesFilter := &models.EntriesFilter{}
|
||||
entriesRequest := &models.EntriesRequest{}
|
||||
|
||||
if err := c.BindQuery(entriesFilter); err != nil {
|
||||
if err := c.BindQuery(entriesRequest); err != nil {
|
||||
c.JSON(http.StatusBadRequest, err)
|
||||
}
|
||||
err := validation.Validate(entriesFilter)
|
||||
validationError := validation.Validate(entriesRequest)
|
||||
if validationError != nil {
|
||||
c.JSON(http.StatusBadRequest, validationError)
|
||||
}
|
||||
|
||||
if entriesRequest.TimeoutMs == 0 {
|
||||
entriesRequest.TimeoutMs = 3000
|
||||
}
|
||||
|
||||
data, meta, err := basenine.Fetch(shared.BasenineHost, shared.BaseninePort,
|
||||
entriesRequest.LeftOff, entriesRequest.Direction, entriesRequest.Query,
|
||||
entriesRequest.Limit, time.Duration(entriesRequest.TimeoutMs)*time.Millisecond)
|
||||
if err != nil {
|
||||
c.JSON(http.StatusBadRequest, err)
|
||||
c.JSON(http.StatusInternalServerError, validationError)
|
||||
}
|
||||
|
||||
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)
|
||||
response := &models.EntriesResponse{}
|
||||
var dataSlice []interface{}
|
||||
|
||||
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
|
||||
for _, row := range data {
|
||||
var entry *tapApi.Entry
|
||||
err = json.Unmarshal(row, &entry)
|
||||
if err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{
|
||||
"error": true,
|
||||
"type": "error",
|
||||
"autoClose": "5000",
|
||||
"msg": string(row),
|
||||
})
|
||||
return // exit
|
||||
}
|
||||
baseEntries = append(baseEntries, harEntry)
|
||||
|
||||
base := tapApi.Summarize(entry)
|
||||
|
||||
dataSlice = append(dataSlice, base)
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, baseEntries)
|
||||
}
|
||||
|
||||
func GetHARs(c *gin.Context) {
|
||||
entriesFilter := &models.HarFetchRequestQuery{}
|
||||
order := database.OrderDesc
|
||||
if err := c.BindQuery(entriesFilter); err != nil {
|
||||
c.JSON(http.StatusBadRequest, err)
|
||||
}
|
||||
err := validation.Validate(entriesFilter)
|
||||
var metadata *basenine.Metadata
|
||||
err = json.Unmarshal(meta, &metadata)
|
||||
if err != nil {
|
||||
c.JSON(http.StatusBadRequest, err)
|
||||
logger.Log.Debugf("Error recieving metadata: %v", err.Error())
|
||||
}
|
||||
|
||||
var timestampFrom, timestampTo int64
|
||||
response.Data = dataSlice
|
||||
response.Meta = metadata
|
||||
|
||||
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")
|
||||
|
||||
uploadParams := &models.UploadEntriesRequestQuery{}
|
||||
if err := c.BindQuery(uploadParams); err != nil {
|
||||
c.JSON(http.StatusBadRequest, err)
|
||||
return
|
||||
}
|
||||
if err := validation.Validate(uploadParams); 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", uploadParams.Dest)
|
||||
token, err := up9.CreateAnonymousToken(uploadParams.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, uploadParams.Dest, uploadParams.SleepIntervalSec)
|
||||
c.String(http.StatusOK, "OK")
|
||||
}
|
||||
|
||||
func GetFullEntries(c *gin.Context) {
|
||||
entriesFilter := &models.HarFetchRequestQuery{}
|
||||
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)
|
||||
c.JSON(http.StatusOK, response)
|
||||
}
|
||||
|
||||
func GetEntry(c *gin.Context) {
|
||||
var entryData models.MizuEntry
|
||||
database.GetEntriesTable().
|
||||
Where(map[string]string{"entryId": c.Param("entryId")}).
|
||||
First(&entryData)
|
||||
singleEntryRequest := &models.SingleEntryRequest{}
|
||||
|
||||
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",
|
||||
})
|
||||
if err := c.BindQuery(singleEntryRequest); err != nil {
|
||||
c.JSON(http.StatusBadRequest, err)
|
||||
}
|
||||
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",
|
||||
})
|
||||
validationError := validation.Validate(singleEntryRequest)
|
||||
if validationError != nil {
|
||||
c.JSON(http.StatusBadRequest, validationError)
|
||||
}
|
||||
c.JSON(http.StatusOK, fullEntryWithPolicy)
|
||||
}
|
||||
|
||||
func DeleteAllEntries(c *gin.Context) {
|
||||
database.GetEntriesTable().
|
||||
Where("1 = 1").
|
||||
Delete(&models.MizuEntry{})
|
||||
id, _ := strconv.Atoi(c.Param("id"))
|
||||
var entry *tapApi.Entry
|
||||
bytes, err := basenine.Single(shared.BasenineHost, shared.BaseninePort, id, singleEntryRequest.Query)
|
||||
if Error(c, err) {
|
||||
return // exit
|
||||
}
|
||||
err = json.Unmarshal(bytes, &entry)
|
||||
if err != nil {
|
||||
c.JSON(http.StatusNotFound, gin.H{
|
||||
"error": true,
|
||||
"type": "error",
|
||||
"autoClose": "5000",
|
||||
"msg": string(bytes),
|
||||
})
|
||||
return // exit
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, map[string]string{
|
||||
"msg": "Success",
|
||||
extension := extensionsMap[entry.Protocol.Name]
|
||||
representation, bodySize, _ := extension.Dissector.Represent(entry.Request, entry.Response)
|
||||
|
||||
var rules []map[string]interface{}
|
||||
var isRulesEnabled bool
|
||||
if entry.Protocol.Name == "http" {
|
||||
harEntry, _ := utils.NewEntry(entry.Request, entry.Response, entry.StartTime, entry.ElapsedTime)
|
||||
_, rulesMatched, _isRulesEnabled := models.RunValidationRulesState(*harEntry, entry.Destination.Name)
|
||||
isRulesEnabled = _isRulesEnabled
|
||||
inrec, _ := json.Marshal(rulesMatched)
|
||||
json.Unmarshal(inrec, &rules)
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, tapApi.EntryWrapper{
|
||||
Protocol: entry.Protocol,
|
||||
Representation: string(representation),
|
||||
BodySize: bodySize,
|
||||
Data: entry,
|
||||
Rules: rules,
|
||||
IsRulesEnabled: isRulesEnabled,
|
||||
})
|
||||
|
||||
}
|
||||
|
||||
func GetGeneralStats(c *gin.Context) {
|
||||
c.JSON(http.StatusOK, providers.GetGeneralStats())
|
||||
}
|
||||
|
||||
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())
|
||||
}
|
||||
|
||||
18
agent/pkg/controllers/install_controller.go
Normal file
18
agent/pkg/controllers/install_controller.go
Normal file
@@ -0,0 +1,18 @@
|
||||
package controllers
|
||||
|
||||
import (
|
||||
"mizuserver/pkg/providers"
|
||||
"net/http"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/up9inc/mizu/shared/logger"
|
||||
)
|
||||
|
||||
func IsSetupNecessary(c *gin.Context) {
|
||||
if IsInstallNeeded, err := providers.IsInstallNeeded(); err != nil {
|
||||
logger.Log.Errorf("unknown internal while checking if install is needed %s", err)
|
||||
c.AbortWithStatusJSON(500, gin.H{"error": "internal error occured while checking if install is needed"})
|
||||
} else {
|
||||
c.JSON(http.StatusOK, IsInstallNeeded)
|
||||
}
|
||||
}
|
||||
62
agent/pkg/controllers/oas_controller.go
Normal file
62
agent/pkg/controllers/oas_controller.go
Normal file
@@ -0,0 +1,62 @@
|
||||
package controllers
|
||||
|
||||
import (
|
||||
"github.com/chanced/openapi"
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/up9inc/mizu/shared/logger"
|
||||
"mizuserver/pkg/oas"
|
||||
"net/http"
|
||||
)
|
||||
|
||||
func GetOASServers(c *gin.Context) {
|
||||
m := make([]string, 0)
|
||||
oas.GetOasGeneratorInstance().ServiceSpecs.Range(func(key, value interface{}) bool {
|
||||
m = append(m, key.(string))
|
||||
return true
|
||||
})
|
||||
|
||||
c.JSON(http.StatusOK, m)
|
||||
}
|
||||
|
||||
func GetOASSpec(c *gin.Context) {
|
||||
res, ok := oas.GetOasGeneratorInstance().ServiceSpecs.Load(c.Param("id"))
|
||||
if !ok {
|
||||
c.JSON(http.StatusNotFound, gin.H{
|
||||
"error": true,
|
||||
"type": "error",
|
||||
"autoClose": "5000",
|
||||
"msg": "Service not found among specs",
|
||||
})
|
||||
return // exit
|
||||
}
|
||||
|
||||
gen := res.(*oas.SpecGen)
|
||||
spec, err := gen.GetSpec()
|
||||
if err != nil {
|
||||
c.JSON(http.StatusInternalServerError, gin.H{
|
||||
"error": true,
|
||||
"type": "error",
|
||||
"autoClose": "5000",
|
||||
"msg": err,
|
||||
})
|
||||
return // exit
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, spec)
|
||||
}
|
||||
|
||||
func GetOASAllSpecs(c *gin.Context) {
|
||||
res := map[string]*openapi.OpenAPI{}
|
||||
oas.GetOasGeneratorInstance().ServiceSpecs.Range(func(key, value interface{}) bool {
|
||||
svc := key.(string)
|
||||
gen := value.(*oas.SpecGen)
|
||||
spec, err := gen.GetSpec()
|
||||
if err != nil {
|
||||
logger.Log.Warningf("Failed to obtain spec for service %s: %s", svc, err)
|
||||
return true
|
||||
}
|
||||
res[svc] = spec
|
||||
return true
|
||||
})
|
||||
c.JSON(http.StatusOK, res)
|
||||
}
|
||||
43
agent/pkg/controllers/oas_controller_test.go
Normal file
43
agent/pkg/controllers/oas_controller_test.go
Normal file
@@ -0,0 +1,43 @@
|
||||
package controllers
|
||||
|
||||
import (
|
||||
"github.com/gin-gonic/gin"
|
||||
"mizuserver/pkg/oas"
|
||||
"net/http/httptest"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestGetOASServers(t *testing.T) {
|
||||
recorder := httptest.NewRecorder()
|
||||
c, _ := gin.CreateTestContext(recorder)
|
||||
oas.GetOasGeneratorInstance().Start()
|
||||
oas.GetOasGeneratorInstance().ServiceSpecs.Store("some", oas.NewGen("some"))
|
||||
|
||||
GetOASServers(c)
|
||||
t.Logf("Written body: %s", recorder.Body.String())
|
||||
return
|
||||
}
|
||||
|
||||
func TestGetOASAllSpecs(t *testing.T) {
|
||||
recorder := httptest.NewRecorder()
|
||||
c, _ := gin.CreateTestContext(recorder)
|
||||
oas.GetOasGeneratorInstance().Start()
|
||||
oas.GetOasGeneratorInstance().ServiceSpecs.Store("some", oas.NewGen("some"))
|
||||
|
||||
GetOASAllSpecs(c)
|
||||
t.Logf("Written body: %s", recorder.Body.String())
|
||||
return
|
||||
}
|
||||
|
||||
func TestGetOASSpec(t *testing.T) {
|
||||
recorder := httptest.NewRecorder()
|
||||
c, _ := gin.CreateTestContext(recorder)
|
||||
oas.GetOasGeneratorInstance().Start()
|
||||
oas.GetOasGeneratorInstance().ServiceSpecs.Store("some", oas.NewGen("some"))
|
||||
|
||||
c.Params = []gin.Param{{Key: "id", Value: "some"}}
|
||||
|
||||
GetOASSpec(c)
|
||||
t.Logf("Written body: %s", recorder.Body.String())
|
||||
return
|
||||
}
|
||||
31
agent/pkg/controllers/query_controller.go
Normal file
31
agent/pkg/controllers/query_controller.go
Normal file
@@ -0,0 +1,31 @@
|
||||
package controllers
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
basenine "github.com/up9inc/basenine/client/go"
|
||||
"github.com/up9inc/mizu/shared"
|
||||
)
|
||||
|
||||
type ValidateResponse struct {
|
||||
Valid bool `json:"valid"`
|
||||
Message string `json:"message"`
|
||||
}
|
||||
|
||||
func PostValidate(c *gin.Context) {
|
||||
query := c.PostForm("query")
|
||||
valid := true
|
||||
message := ""
|
||||
|
||||
err := basenine.Validate(shared.BasenineHost, shared.BaseninePort, query)
|
||||
if err != nil {
|
||||
valid = false
|
||||
message = err.Error()
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, ValidateResponse{
|
||||
Valid: valid,
|
||||
Message: message,
|
||||
})
|
||||
}
|
||||
@@ -1,12 +0,0 @@
|
||||
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())
|
||||
}
|
||||
|
||||
36
agent/pkg/controllers/service_map_controller.go
Normal file
36
agent/pkg/controllers/service_map_controller.go
Normal file
@@ -0,0 +1,36 @@
|
||||
package controllers
|
||||
|
||||
import (
|
||||
"mizuserver/pkg/servicemap"
|
||||
"net/http"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
type ServiceMapController struct {
|
||||
service servicemap.ServiceMap
|
||||
}
|
||||
|
||||
func NewServiceMapController() *ServiceMapController {
|
||||
return &ServiceMapController{
|
||||
service: servicemap.GetInstance(),
|
||||
}
|
||||
}
|
||||
|
||||
func (s *ServiceMapController) Status(c *gin.Context) {
|
||||
c.JSON(http.StatusOK, s.service.GetStatus())
|
||||
}
|
||||
|
||||
func (s *ServiceMapController) Get(c *gin.Context) {
|
||||
response := &servicemap.ServiceMapResponse{
|
||||
Status: s.service.GetStatus(),
|
||||
Nodes: s.service.GetNodes(),
|
||||
Edges: s.service.GetEdges(),
|
||||
}
|
||||
c.JSON(http.StatusOK, response)
|
||||
}
|
||||
|
||||
func (s *ServiceMapController) Reset(c *gin.Context) {
|
||||
s.service.Reset()
|
||||
s.Status(c)
|
||||
}
|
||||
147
agent/pkg/controllers/service_map_controller_test.go
Normal file
147
agent/pkg/controllers/service_map_controller_test.go
Normal file
@@ -0,0 +1,147 @@
|
||||
package controllers
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"testing"
|
||||
|
||||
"mizuserver/pkg/servicemap"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/stretchr/testify/suite"
|
||||
"github.com/up9inc/mizu/shared"
|
||||
tapApi "github.com/up9inc/mizu/tap/api"
|
||||
)
|
||||
|
||||
const (
|
||||
a = "aService"
|
||||
b = "bService"
|
||||
Ip = "127.0.0.1"
|
||||
Port = "80"
|
||||
)
|
||||
|
||||
var (
|
||||
TCPEntryA = &tapApi.TCP{
|
||||
Name: a,
|
||||
Port: Port,
|
||||
IP: fmt.Sprintf("%s.%s", Ip, a),
|
||||
}
|
||||
TCPEntryB = &tapApi.TCP{
|
||||
Name: b,
|
||||
Port: Port,
|
||||
IP: fmt.Sprintf("%s.%s", Ip, b),
|
||||
}
|
||||
)
|
||||
|
||||
var ProtocolHttp = &tapApi.Protocol{
|
||||
Name: "http",
|
||||
LongName: "Hypertext Transfer Protocol -- HTTP/1.1",
|
||||
Abbreviation: "HTTP",
|
||||
Macro: "http",
|
||||
Version: "1.1",
|
||||
BackgroundColor: "#205cf5",
|
||||
ForegroundColor: "#ffffff",
|
||||
FontSize: 12,
|
||||
ReferenceLink: "https://datatracker.ietf.org/doc/html/rfc2616",
|
||||
Ports: []string{"80", "443", "8080"},
|
||||
Priority: 0,
|
||||
}
|
||||
|
||||
type ServiceMapControllerSuite struct {
|
||||
suite.Suite
|
||||
|
||||
c *ServiceMapController
|
||||
w *httptest.ResponseRecorder
|
||||
g *gin.Context
|
||||
}
|
||||
|
||||
func (s *ServiceMapControllerSuite) SetupTest() {
|
||||
s.c = NewServiceMapController()
|
||||
s.c.service.SetConfig(&shared.MizuAgentConfig{
|
||||
ServiceMap: true,
|
||||
})
|
||||
s.c.service.NewTCPEntry(TCPEntryA, TCPEntryB, ProtocolHttp)
|
||||
|
||||
s.w = httptest.NewRecorder()
|
||||
s.g, _ = gin.CreateTestContext(s.w)
|
||||
}
|
||||
|
||||
func (s *ServiceMapControllerSuite) TestGetStatus() {
|
||||
assert := s.Assert()
|
||||
|
||||
s.c.Status(s.g)
|
||||
assert.Equal(http.StatusOK, s.w.Code)
|
||||
|
||||
var status servicemap.ServiceMapStatus
|
||||
err := json.Unmarshal(s.w.Body.Bytes(), &status)
|
||||
assert.NoError(err)
|
||||
assert.Equal("enabled", status.Status)
|
||||
assert.Equal(1, status.EntriesProcessedCount)
|
||||
assert.Equal(2, status.NodeCount)
|
||||
assert.Equal(1, status.EdgeCount)
|
||||
}
|
||||
|
||||
func (s *ServiceMapControllerSuite) TestGet() {
|
||||
assert := s.Assert()
|
||||
|
||||
s.c.Get(s.g)
|
||||
assert.Equal(http.StatusOK, s.w.Code)
|
||||
|
||||
var response servicemap.ServiceMapResponse
|
||||
err := json.Unmarshal(s.w.Body.Bytes(), &response)
|
||||
assert.NoError(err)
|
||||
|
||||
// response status
|
||||
assert.Equal("enabled", response.Status.Status)
|
||||
assert.Equal(1, response.Status.EntriesProcessedCount)
|
||||
assert.Equal(2, response.Status.NodeCount)
|
||||
assert.Equal(1, response.Status.EdgeCount)
|
||||
|
||||
// response nodes
|
||||
aNode := servicemap.ServiceMapNode{
|
||||
Id: 1,
|
||||
Name: TCPEntryA.IP,
|
||||
Entry: TCPEntryA,
|
||||
Count: 1,
|
||||
}
|
||||
bNode := servicemap.ServiceMapNode{
|
||||
Id: 2,
|
||||
Name: TCPEntryB.IP,
|
||||
Entry: TCPEntryB,
|
||||
Count: 1,
|
||||
}
|
||||
assert.Contains(response.Nodes, aNode)
|
||||
assert.Contains(response.Nodes, bNode)
|
||||
assert.Len(response.Nodes, 2)
|
||||
|
||||
// response edges
|
||||
assert.Equal([]servicemap.ServiceMapEdge{
|
||||
{
|
||||
Source: aNode,
|
||||
Destination: bNode,
|
||||
Protocol: ProtocolHttp,
|
||||
Count: 1,
|
||||
},
|
||||
}, response.Edges)
|
||||
}
|
||||
|
||||
func (s *ServiceMapControllerSuite) TestGetReset() {
|
||||
assert := s.Assert()
|
||||
|
||||
s.c.Reset(s.g)
|
||||
assert.Equal(http.StatusOK, s.w.Code)
|
||||
|
||||
var status servicemap.ServiceMapStatus
|
||||
err := json.Unmarshal(s.w.Body.Bytes(), &status)
|
||||
assert.NoError(err)
|
||||
assert.Equal("enabled", status.Status)
|
||||
assert.Equal(0, status.EntriesProcessedCount)
|
||||
assert.Equal(0, status.NodeCount)
|
||||
assert.Equal(0, status.EdgeCount)
|
||||
}
|
||||
|
||||
func TestServiceMapControllerSuite(t *testing.T) {
|
||||
suite.Run(t, new(ServiceMapControllerSuite))
|
||||
}
|
||||
@@ -3,30 +3,103 @@ package controllers
|
||||
import (
|
||||
"encoding/json"
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/romana/rlog"
|
||||
"github.com/up9inc/mizu/shared"
|
||||
"github.com/up9inc/mizu/shared/logger"
|
||||
"mizuserver/pkg/api"
|
||||
"mizuserver/pkg/holder"
|
||||
"mizuserver/pkg/providers"
|
||||
"mizuserver/pkg/providers/tappedPods"
|
||||
"mizuserver/pkg/providers/tappers"
|
||||
"mizuserver/pkg/up9"
|
||||
"mizuserver/pkg/validation"
|
||||
"net/http"
|
||||
)
|
||||
|
||||
func HealthCheck(c *gin.Context) {
|
||||
tappersStatus := make([]*shared.TapperStatus, 0)
|
||||
for _, value := range tappers.GetStatus() {
|
||||
tappersStatus = append(tappersStatus, value)
|
||||
}
|
||||
|
||||
response := shared.HealthResponse{
|
||||
TappedPods: tappedPods.Get(),
|
||||
ConnectedTappersCount: tappers.GetConnectedCount(),
|
||||
TappersStatus: tappersStatus,
|
||||
}
|
||||
c.JSON(http.StatusOK, response)
|
||||
}
|
||||
|
||||
func PostTappedPods(c *gin.Context) {
|
||||
tapStatus := &shared.TapStatus{}
|
||||
if err := c.Bind(tapStatus); err != nil {
|
||||
var requestTappedPods []*shared.PodInfo
|
||||
if err := c.Bind(&requestTappedPods); 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)
|
||||
|
||||
logger.Log.Infof("[Status] POST request: %d tapped pods", len(requestTappedPods))
|
||||
tappedPods.Set(requestTappedPods)
|
||||
broadcastTappedPodsStatus()
|
||||
}
|
||||
|
||||
func broadcastTappedPodsStatus() {
|
||||
tappedPodsStatus := tappedPods.GetTappedPodsStatus()
|
||||
|
||||
message := shared.CreateWebSocketStatusMessage(tappedPodsStatus)
|
||||
if jsonBytes, err := json.Marshal(message); err != nil {
|
||||
rlog.Errorf("Could not Marshal message %v\n", err)
|
||||
logger.Log.Errorf("Could not Marshal message %v", err)
|
||||
} else {
|
||||
api.BroadcastToBrowserClients(jsonBytes)
|
||||
}
|
||||
}
|
||||
|
||||
func PostTapperStatus(c *gin.Context) {
|
||||
tapperStatus := &shared.TapperStatus{}
|
||||
if err := c.Bind(tapperStatus); err != nil {
|
||||
c.JSON(http.StatusBadRequest, err)
|
||||
return
|
||||
}
|
||||
|
||||
if err := validation.Validate(tapperStatus); err != nil {
|
||||
c.JSON(http.StatusBadRequest, err)
|
||||
return
|
||||
}
|
||||
|
||||
logger.Log.Infof("[Status] POST request, tapper status: %v", tapperStatus)
|
||||
tappers.SetStatus(tapperStatus)
|
||||
broadcastTappedPodsStatus()
|
||||
}
|
||||
|
||||
func GetConnectedTappersCount(c *gin.Context) {
|
||||
c.JSON(http.StatusOK, tappers.GetConnectedCount())
|
||||
}
|
||||
|
||||
func GetAuthStatus(c *gin.Context) {
|
||||
authStatus, err := providers.GetAuthStatus()
|
||||
if err != nil {
|
||||
c.JSON(http.StatusInternalServerError, err)
|
||||
return
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, authStatus)
|
||||
}
|
||||
|
||||
func GetTappingStatus(c *gin.Context) {
|
||||
tappedPodsStatus := tappedPods.GetTappedPodsStatus()
|
||||
c.JSON(http.StatusOK, tappedPodsStatus)
|
||||
}
|
||||
|
||||
func AnalyzeInformation(c *gin.Context) {
|
||||
c.JSON(http.StatusOK, up9.GetAnalyzeInfo())
|
||||
}
|
||||
|
||||
func GetGeneralStats(c *gin.Context) {
|
||||
c.JSON(http.StatusOK, providers.GetGeneralStats())
|
||||
}
|
||||
|
||||
func GetRecentTLSLinks(c *gin.Context) {
|
||||
c.JSON(http.StatusOK, providers.GetAllRecentTLSAddresses())
|
||||
}
|
||||
|
||||
func GetCurrentResolvingInformation(c *gin.Context) {
|
||||
c.JSON(http.StatusOK, holder.GetResolver().GetMap())
|
||||
}
|
||||
|
||||
39
agent/pkg/controllers/user_controller.go
Normal file
39
agent/pkg/controllers/user_controller.go
Normal file
@@ -0,0 +1,39 @@
|
||||
package controllers
|
||||
|
||||
import (
|
||||
"mizuserver/pkg/providers"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/up9inc/mizu/shared/logger"
|
||||
)
|
||||
|
||||
func Login(c *gin.Context) {
|
||||
if token, err := providers.PerformLogin(c.PostForm("username"), c.PostForm("password"), c.Request.Context()); err != nil {
|
||||
c.AbortWithStatusJSON(401, gin.H{"error": "bad login"})
|
||||
} else {
|
||||
c.JSON(200, gin.H{"token": token})
|
||||
}
|
||||
}
|
||||
|
||||
func Logout(c *gin.Context) {
|
||||
token := c.GetHeader("x-session-token")
|
||||
if err := providers.Logout(token, c.Request.Context()); err != nil {
|
||||
c.AbortWithStatusJSON(500, gin.H{"error": "error occured while logging out, the session might still be valid"})
|
||||
} else {
|
||||
c.JSON(200, "")
|
||||
}
|
||||
}
|
||||
|
||||
func Register(c *gin.Context) {
|
||||
if token, _, err, formErrorMessages := providers.RegisterUser(c.PostForm("username"), c.PostForm("password"), c.Request.Context()); err != nil {
|
||||
if formErrorMessages != nil {
|
||||
logger.Log.Infof("user attempted to register but had form errors %v %v", formErrorMessages, err)
|
||||
c.AbortWithStatusJSON(400, formErrorMessages)
|
||||
} else {
|
||||
logger.Log.Errorf("unknown internal error registering user %s", err)
|
||||
c.AbortWithStatusJSON(500, gin.H{"error": "internal error occured while registering"})
|
||||
}
|
||||
} else {
|
||||
c.JSON(200, gin.H{"token": token})
|
||||
}
|
||||
}
|
||||
@@ -1,73 +0,0 @@
|
||||
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
|
||||
}
|
||||
|
||||
@@ -1,121 +0,0 @@
|
||||
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")
|
||||
}
|
||||
}
|
||||
19
agent/pkg/middlewares/cors.go
Normal file
19
agent/pkg/middlewares/cors.go
Normal file
@@ -0,0 +1,19 @@
|
||||
package middlewares
|
||||
|
||||
import "github.com/gin-gonic/gin"
|
||||
|
||||
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, x-session-token")
|
||||
c.Writer.Header().Set("Access-Control-Allow-Methods", "POST, OPTIONS, GET, PUT")
|
||||
|
||||
if c.Request.Method == "OPTIONS" {
|
||||
c.AbortWithStatus(204)
|
||||
return
|
||||
}
|
||||
|
||||
c.Next()
|
||||
}
|
||||
}
|
||||
49
agent/pkg/middlewares/requiresAuth.go
Normal file
49
agent/pkg/middlewares/requiresAuth.go
Normal file
@@ -0,0 +1,49 @@
|
||||
package middlewares
|
||||
|
||||
import (
|
||||
"mizuserver/pkg/config"
|
||||
"mizuserver/pkg/providers"
|
||||
"time"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/patrickmn/go-cache"
|
||||
"github.com/up9inc/mizu/shared/logger"
|
||||
)
|
||||
|
||||
const cachedValidTokensRetainmentTime = time.Minute * 1
|
||||
|
||||
var cachedValidTokens = cache.New(cachedValidTokensRetainmentTime, cachedValidTokensRetainmentTime)
|
||||
|
||||
func RequiresAuth() gin.HandlerFunc {
|
||||
return func(c *gin.Context) {
|
||||
// auth is irrelevant for ephermeral mizu
|
||||
if !config.Config.StandaloneMode {
|
||||
c.Next()
|
||||
return
|
||||
}
|
||||
|
||||
token := c.GetHeader("x-session-token")
|
||||
if token == "" {
|
||||
c.AbortWithStatusJSON(401, gin.H{"error": "token header is empty"})
|
||||
return
|
||||
}
|
||||
|
||||
if _, isTokenCached := cachedValidTokens.Get(token); isTokenCached {
|
||||
c.Next()
|
||||
return
|
||||
}
|
||||
|
||||
if isTokenValid, err := providers.VerifyToken(token, c.Request.Context()); err != nil {
|
||||
logger.Log.Errorf("error verifying token %s", err)
|
||||
c.AbortWithStatusJSON(401, gin.H{"error": "unknown auth error occured"})
|
||||
return
|
||||
} else if !isTokenValid {
|
||||
c.AbortWithStatusJSON(401, gin.H{"error": "invalid token"})
|
||||
return
|
||||
}
|
||||
|
||||
cachedValidTokens.Set(token, true, cachedValidTokensRetainmentTime)
|
||||
|
||||
c.Next()
|
||||
}
|
||||
}
|
||||
@@ -2,148 +2,49 @@ package models
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
|
||||
"mizuserver/pkg/rules"
|
||||
"mizuserver/pkg/utils"
|
||||
"time"
|
||||
|
||||
tapApi "github.com/up9inc/mizu/tap/api"
|
||||
|
||||
"github.com/google/martian/har"
|
||||
basenine "github.com/up9inc/basenine/client/go"
|
||||
"github.com/up9inc/mizu/shared"
|
||||
"github.com/up9inc/mizu/tap"
|
||||
)
|
||||
|
||||
type DataUnmarshaler interface {
|
||||
UnmarshalData(*MizuEntry) error
|
||||
}
|
||||
|
||||
func GetEntry(r *MizuEntry, v DataUnmarshaler) error {
|
||||
func GetEntry(r *tapApi.Entry, v tapApi.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 TapConfig struct {
|
||||
TappedNamespaces map[string]bool `json:"tappedNamespaces"`
|
||||
}
|
||||
|
||||
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 EntriesRequest struct {
|
||||
LeftOff int `form:"leftOff" validate:"required,min=-1"`
|
||||
Direction int `form:"direction" validate:"required,oneof='1' '-1'"`
|
||||
Query string `form:"query"`
|
||||
Limit int `form:"limit" validate:"required,min=1"`
|
||||
TimeoutMs int `form:"timeoutMs" validate:"min=1"`
|
||||
}
|
||||
|
||||
type ApplicableRules struct {
|
||||
Latency int64 `json:"latency,omitempty"`
|
||||
Status bool `json:"status,omitempty"`
|
||||
NumberOfRules int `json:"numberOfRules,omitempty"`
|
||||
type SingleEntryRequest struct {
|
||||
Query string `form:"query"`
|
||||
}
|
||||
|
||||
func NewApplicableRules(status bool, latency int64, number int) ApplicableRules {
|
||||
ar := ApplicableRules{}
|
||||
ar.Status = status
|
||||
ar.Latency = latency
|
||||
ar.NumberOfRules = number
|
||||
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 `form:"limit" validate:"required,min=1,max=200"`
|
||||
Operator string `form:"operator" validate:"required,oneof='lt' 'gt'"`
|
||||
Timestamp int64 `form:"timestamp" validate:"required,min=1"`
|
||||
}
|
||||
|
||||
type UploadEntriesRequestQuery struct {
|
||||
Dest string `form:"dest"`
|
||||
SleepIntervalSec int `form:"interval"`
|
||||
}
|
||||
|
||||
type HarFetchRequestQuery struct {
|
||||
From int64 `form:"from"`
|
||||
To int64 `form:"to"`
|
||||
type EntriesResponse struct {
|
||||
Data []interface{} `json:"data"`
|
||||
Meta *basenine.Metadata `json:"meta"`
|
||||
}
|
||||
|
||||
type WebSocketEntryMessage struct {
|
||||
*shared.WebSocketMessageMetadata
|
||||
Data *BaseEntryDetails `json:"data,omitempty"`
|
||||
Data *tapApi.BaseEntry `json:"data,omitempty"`
|
||||
}
|
||||
|
||||
type WebSocketTappedEntryMessage struct {
|
||||
*shared.WebSocketMessageMetadata
|
||||
Data *tap.OutputChannelItem
|
||||
Data *tapApi.OutputChannelItem
|
||||
}
|
||||
|
||||
type WebsocketOutboundLinkMessage struct {
|
||||
@@ -151,7 +52,33 @@ type WebsocketOutboundLinkMessage struct {
|
||||
Data *tap.OutboundLink
|
||||
}
|
||||
|
||||
func CreateBaseEntryWebSocketMessage(base *BaseEntryDetails) ([]byte, error) {
|
||||
type AuthStatus struct {
|
||||
Email string `json:"email"`
|
||||
Model string `json:"model"`
|
||||
}
|
||||
|
||||
type ToastMessage struct {
|
||||
Type string `json:"type"`
|
||||
AutoClose uint `json:"autoClose"`
|
||||
Text string `json:"text"`
|
||||
}
|
||||
|
||||
type WebSocketToastMessage struct {
|
||||
*shared.WebSocketMessageMetadata
|
||||
Data *ToastMessage `json:"data,omitempty"`
|
||||
}
|
||||
|
||||
type WebSocketQueryMetadataMessage struct {
|
||||
*shared.WebSocketMessageMetadata
|
||||
Data *basenine.Metadata `json:"data,omitempty"`
|
||||
}
|
||||
|
||||
type WebSocketStartTimeMessage struct {
|
||||
*shared.WebSocketMessageMetadata
|
||||
Data int64 `json:"data"`
|
||||
}
|
||||
|
||||
func CreateBaseEntryWebSocketMessage(base *tapApi.BaseEntry) ([]byte, error) {
|
||||
message := &WebSocketEntryMessage{
|
||||
WebSocketMessageMetadata: &shared.WebSocketMessageMetadata{
|
||||
MessageType: shared.WebSocketMessageTypeEntry,
|
||||
@@ -161,7 +88,7 @@ func CreateBaseEntryWebSocketMessage(base *BaseEntryDetails) ([]byte, error) {
|
||||
return json.Marshal(message)
|
||||
}
|
||||
|
||||
func CreateWebsocketTappedEntryMessage(base *tap.OutputChannelItem) ([]byte, error) {
|
||||
func CreateWebsocketTappedEntryMessage(base *tapApi.OutputChannelItem) ([]byte, error) {
|
||||
message := &WebSocketTappedEntryMessage{
|
||||
WebSocketMessageMetadata: &shared.WebSocketMessageMetadata{
|
||||
MessageType: shared.WebSocketMessageTypeTappedEntry,
|
||||
@@ -181,6 +108,36 @@ func CreateWebsocketOutboundLinkMessage(base *tap.OutboundLink) ([]byte, error)
|
||||
return json.Marshal(message)
|
||||
}
|
||||
|
||||
func CreateWebsocketToastMessage(base *ToastMessage) ([]byte, error) {
|
||||
message := &WebSocketToastMessage{
|
||||
WebSocketMessageMetadata: &shared.WebSocketMessageMetadata{
|
||||
MessageType: shared.WebSocketMessageTypeToast,
|
||||
},
|
||||
Data: base,
|
||||
}
|
||||
return json.Marshal(message)
|
||||
}
|
||||
|
||||
func CreateWebsocketQueryMetadataMessage(base *basenine.Metadata) ([]byte, error) {
|
||||
message := &WebSocketQueryMetadataMessage{
|
||||
WebSocketMessageMetadata: &shared.WebSocketMessageMetadata{
|
||||
MessageType: shared.WebSocketMessageTypeQueryMetadata,
|
||||
},
|
||||
Data: base,
|
||||
}
|
||||
return json.Marshal(message)
|
||||
}
|
||||
|
||||
func CreateWebsocketStartTimeMessage(base int64) ([]byte, error) {
|
||||
message := &WebSocketStartTimeMessage{
|
||||
WebSocketMessageMetadata: &shared.WebSocketMessageMetadata{
|
||||
MessageType: shared.WebSocketMessageTypeStartTime,
|
||||
},
|
||||
Data: base,
|
||||
}
|
||||
return json.Marshal(message)
|
||||
}
|
||||
|
||||
// ExtendedHAR is the top level object of a HAR log.
|
||||
type ExtendedHAR struct {
|
||||
Log *ExtendedLog `json:"log"`
|
||||
@@ -201,26 +158,12 @@ type ExtendedCreator struct {
|
||||
Source *string `json:"_source"`
|
||||
}
|
||||
|
||||
type FullEntryWithPolicy struct {
|
||||
RulesMatched []rules.RulesMatched `json:"rulesMatched,omitempty"`
|
||||
Entry har.Entry `json:"entry"`
|
||||
Service string `json:"service"`
|
||||
func RunValidationRulesState(harEntry har.Entry, service string) (tapApi.ApplicableRules, []rules.RulesMatched, bool) {
|
||||
resultPolicyToSend, isEnabled := rules.MatchRequestPolicy(harEntry, service)
|
||||
statusPolicyToSend, latency, numberOfRules := rules.PassedValidationRules(resultPolicyToSend)
|
||||
return tapApi.ApplicableRules{Status: statusPolicyToSend, Latency: latency, NumberOfRules: numberOfRules}, resultPolicyToSend, isEnabled
|
||||
}
|
||||
|
||||
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, numberOfRules := rules.PassedValidationRules(resultPolicyToSend, numberOfRules)
|
||||
ar := NewApplicableRules(statusPolicyToSend, latency, numberOfRules)
|
||||
return ar
|
||||
type InstallState struct {
|
||||
Completed bool `json:"completed"`
|
||||
}
|
||||
|
||||
158
agent/pkg/oas/feeder_test.go
Normal file
158
agent/pkg/oas/feeder_test.go
Normal file
@@ -0,0 +1,158 @@
|
||||
package oas
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"github.com/google/martian/har"
|
||||
"github.com/up9inc/mizu/shared/logger"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"sort"
|
||||
"strings"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func getFiles(baseDir string) (result []string, err error) {
|
||||
result = make([]string, 0, 0)
|
||||
logger.Log.Infof("Reading files from tree: %s", baseDir)
|
||||
|
||||
// https://yourbasic.org/golang/list-files-in-directory/
|
||||
err = filepath.Walk(baseDir,
|
||||
func(path string, info os.FileInfo, err error) error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
ext := strings.ToLower(filepath.Ext(path))
|
||||
if !info.IsDir() && (ext == ".har" || ext == ".ldjson") {
|
||||
result = append(result, path)
|
||||
}
|
||||
|
||||
return nil
|
||||
})
|
||||
|
||||
sort.SliceStable(result, func(i, j int) bool {
|
||||
return fileSize(result[i]) < fileSize(result[j])
|
||||
})
|
||||
|
||||
logger.Log.Infof("Got files: %d", len(result))
|
||||
return result, err
|
||||
}
|
||||
|
||||
func fileSize(fname string) int64 {
|
||||
fi, err := os.Stat(fname)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
return fi.Size()
|
||||
}
|
||||
|
||||
func feedEntries(fromFiles []string) (err error) {
|
||||
for _, file := range fromFiles {
|
||||
logger.Log.Info("Processing file: " + file)
|
||||
ext := strings.ToLower(filepath.Ext(file))
|
||||
switch ext {
|
||||
case ".har":
|
||||
err = feedFromHAR(file)
|
||||
if err != nil {
|
||||
logger.Log.Warning("Failed processing file: " + err.Error())
|
||||
continue
|
||||
}
|
||||
case ".ldjson":
|
||||
err = feedFromLDJSON(file)
|
||||
if err != nil {
|
||||
logger.Log.Warning("Failed processing file: " + err.Error())
|
||||
continue
|
||||
}
|
||||
default:
|
||||
return errors.New("Unsupported file extension: " + ext)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func feedFromHAR(file string) error {
|
||||
fd, err := os.Open(file)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
defer fd.Close()
|
||||
|
||||
data, err := ioutil.ReadAll(fd)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
var harDoc har.HAR
|
||||
err = json.Unmarshal(data, &harDoc)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
for _, entry := range harDoc.Log.Entries {
|
||||
GetOasGeneratorInstance().PushEntry(entry)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func feedFromLDJSON(file string) error {
|
||||
fd, err := os.Open(file)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
defer fd.Close()
|
||||
|
||||
reader := bufio.NewReader(fd)
|
||||
|
||||
var meta map[string]interface{}
|
||||
|
||||
buf := strings.Builder{}
|
||||
for {
|
||||
substr, isPrefix, err := reader.ReadLine()
|
||||
if err == io.EOF {
|
||||
break
|
||||
}
|
||||
|
||||
buf.WriteString(string(substr))
|
||||
if isPrefix {
|
||||
continue
|
||||
}
|
||||
|
||||
line := buf.String()
|
||||
buf.Reset()
|
||||
|
||||
if meta == nil {
|
||||
err := json.Unmarshal([]byte(line), &meta)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
} else {
|
||||
var entry har.Entry
|
||||
err := json.Unmarshal([]byte(line), &entry)
|
||||
if err != nil {
|
||||
logger.Log.Warningf("Failed decoding entry: %s", line)
|
||||
}
|
||||
GetOasGeneratorInstance().PushEntry(&entry)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func TestFilesList(t *testing.T) {
|
||||
res, err := getFiles("./test_artifacts/")
|
||||
t.Log(len(res))
|
||||
t.Log(res)
|
||||
if err != nil || len(res) != 2 {
|
||||
t.Logf("Should return 2 files but returned %d", len(res))
|
||||
t.FailNow()
|
||||
}
|
||||
}
|
||||
108
agent/pkg/oas/gibberish.go
Normal file
108
agent/pkg/oas/gibberish.go
Normal file
@@ -0,0 +1,108 @@
|
||||
package oas
|
||||
|
||||
import (
|
||||
"regexp"
|
||||
"strings"
|
||||
"unicode"
|
||||
)
|
||||
|
||||
var (
|
||||
patBase64 = regexp.MustCompile(`^(?:[A-Za-z0-9+/]{4})*(?:[A-Za-z0-9+/]{2}==|[A-Za-z0-9+/]{3}=)?$`)
|
||||
patUuid4 = regexp.MustCompile(`(?i)[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}`)
|
||||
patEmail = regexp.MustCompile(`^\w+([-+.']\w+)*@\w+([-.]\w+)*\.\w+([-.]\w+)*$`)
|
||||
patHexLower = regexp.MustCompile(`(0x)?[0-9a-f]{6,}`)
|
||||
patHexUpper = regexp.MustCompile(`(0x)?[0-9A-F]{6,}`)
|
||||
)
|
||||
|
||||
func IsGibberish(str string) bool {
|
||||
if patBase64.MatchString(str) && len(str) > 32 {
|
||||
return true
|
||||
}
|
||||
|
||||
if patUuid4.MatchString(str) {
|
||||
return true
|
||||
}
|
||||
|
||||
if patEmail.MatchString(str) {
|
||||
return true
|
||||
}
|
||||
|
||||
if patHexLower.MatchString(str) || patHexUpper.MatchString(str) {
|
||||
return true
|
||||
}
|
||||
|
||||
noise := noiseLevel(str)
|
||||
if noise >= 0.2 {
|
||||
return true
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
func noiseLevel(str string) (score float64) {
|
||||
// opinionated algo of certain char pairs marking the non-human strings
|
||||
prev := *new(rune)
|
||||
cnt := 0.0
|
||||
for _, char := range str {
|
||||
cnt += 1
|
||||
if prev > 0 {
|
||||
switch {
|
||||
// continued class of upper/lower/digit adds no noise
|
||||
case unicode.IsUpper(prev) && unicode.IsUpper(char):
|
||||
case unicode.IsLower(prev) && unicode.IsLower(char):
|
||||
case unicode.IsDigit(prev) && unicode.IsDigit(char):
|
||||
|
||||
// upper =>
|
||||
case unicode.IsUpper(prev) && unicode.IsLower(char):
|
||||
score += 0.25
|
||||
case unicode.IsUpper(prev) && unicode.IsDigit(char):
|
||||
score += 0.25
|
||||
|
||||
// lower =>
|
||||
case unicode.IsLower(prev) && unicode.IsUpper(char):
|
||||
score += 0.75
|
||||
case unicode.IsLower(prev) && unicode.IsDigit(char):
|
||||
score += 0.25
|
||||
|
||||
// digit =>
|
||||
case unicode.IsDigit(prev) && unicode.IsUpper(char):
|
||||
score += 0.75
|
||||
case unicode.IsDigit(prev) && unicode.IsLower(char):
|
||||
score += 0.75
|
||||
|
||||
// the rest is 100% noise
|
||||
default:
|
||||
score += 1
|
||||
}
|
||||
}
|
||||
prev = char
|
||||
}
|
||||
|
||||
score /= cnt // weigh it
|
||||
|
||||
return score
|
||||
}
|
||||
|
||||
func IsVersionString(component string) bool {
|
||||
if component == "" {
|
||||
return false
|
||||
}
|
||||
|
||||
hasV := false
|
||||
if strings.HasPrefix(component, "v") {
|
||||
component = component[1:]
|
||||
hasV = true
|
||||
}
|
||||
|
||||
for _, c := range component {
|
||||
if string(c) != "." && !unicode.IsDigit(c) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
if !hasV && strings.Contains(component, ".") {
|
||||
return false
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
71
agent/pkg/oas/gibberish_test.go
Normal file
71
agent/pkg/oas/gibberish_test.go
Normal file
@@ -0,0 +1,71 @@
|
||||
package oas
|
||||
|
||||
import "testing"
|
||||
|
||||
func TestNegative(t *testing.T) {
|
||||
cases := []string{
|
||||
"",
|
||||
"b", // can be valid hexadecimal
|
||||
"GetUniversalVariableUser",
|
||||
"callback",
|
||||
"runs",
|
||||
"tcfv2",
|
||||
"StartUpCheckout",
|
||||
"GetCart",
|
||||
}
|
||||
|
||||
for _, str := range cases {
|
||||
if IsGibberish(str) {
|
||||
t.Errorf("Mistakenly true: %s", str)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestPositive(t *testing.T) {
|
||||
cases := []string{
|
||||
"e21f7112-3d3b-4632-9da3-a4af2e0e9166",
|
||||
"952bea17-3776-11ea-9341-42010a84012a",
|
||||
"456795af-b48f-4a8d-9b37-3e932622c2f0",
|
||||
"0a0d0174-b338-4520-a1c3-24f7e3d5ec50.html",
|
||||
"6120c057c7a97b03f6986f1b",
|
||||
"610bc3fd5a77a7fa25033fb0",
|
||||
"610bd0315a77a7fa25034368",
|
||||
"610bd0315a77a7fa25034368zh",
|
||||
"710a462e",
|
||||
"1554507871",
|
||||
"qwerqwerasdfqwer@protonmai.com",
|
||||
"john.dow.1981@protonmail.com",
|
||||
"ci12NC01YzkyNTEzYzllMDRhLTAtYy5tb25pdG9yaW5nLmpzb24=", // long base64
|
||||
"11ca096cbc224a67360493d44a9903",
|
||||
"c738338322370b47a79251f7510dd", // prefixed hex
|
||||
"QgAAAC6zw0qH2DJtnXe8Z7rUJP0FgAFKkOhcHdFWzL1ZYggtwBgiB3LSoele9o3ZqFh7iCBhHbVLAnMuJ0HF8hEw7UKecE6wd-MBXgeRMdubGydhAMZSmuUjRpqplML40bmrb8VjJKNZswD1Cg",
|
||||
"QgAAAC6zw0qH2DJtnXe8Z7rUJP0rG4sjLa_KVLlww5WEDJ__30J15en-K_6Y68jb_rU93e2TFY6fb0MYiQ1UrLNMQufqODHZUl39Lo6cXAOVOThjAMZSmuVH7n85JOYSCgzpvowMAVueGG0Xxg",
|
||||
"203ef0f713abcebd8d62c35c0e3f12f87d71e5e4",
|
||||
"MDEyOk9yZ2FuaXphdGlvbjU3MzI0Nzk1",
|
||||
"730970532670-compute@developer.gserviceaccount.com",
|
||||
"arn-aws-ecs-eu-west-2-396248696294-cluster-london-01-ECSCluster-27iuIYva8nO4", // ?
|
||||
"AAAA028295945",
|
||||
"sp_ANQXRpqH_urn$3Auri$3Abase64$3A6698b0a3-97ad-52ce-8fc3-17d99e37a726",
|
||||
"n63nd45qsj",
|
||||
"n9z9QGNiz",
|
||||
"proxy.3d2100fd7107262ecb55ce6847f01fa5.html",
|
||||
"r-ext-5579e00a95c90",
|
||||
"r-ext-5579e8b12f11e",
|
||||
"r-v4-5c92513c9e04a",
|
||||
"r-v4-5c92513c9e04a-0-c.monitoring.json",
|
||||
"segments-1563566437171.639994",
|
||||
"t_52d94268-8810-4a7e-ba87-ffd657a6752f",
|
||||
"timeouts-1563566437171.639994",
|
||||
|
||||
// TODO
|
||||
// "fb6cjraf9cejut2a",
|
||||
// "Fxvd1timk", // questionable
|
||||
// "JEHJW4BKVFDRTMTUQLHKK5WVAU",
|
||||
}
|
||||
|
||||
for _, str := range cases {
|
||||
if !IsGibberish(str) {
|
||||
t.Errorf("Mistakenly false: %s", str)
|
||||
}
|
||||
}
|
||||
}
|
||||
75
agent/pkg/oas/ignores.go
Normal file
75
agent/pkg/oas/ignores.go
Normal file
@@ -0,0 +1,75 @@
|
||||
package oas
|
||||
|
||||
import "strings"
|
||||
|
||||
var ignoredExtensions = []string{"gif", "svg", "css", "png", "ico", "js", "woff2", "woff", "jpg", "jpeg", "swf", "ttf", "map", "webp", "otf", "mp3"}
|
||||
|
||||
var ignoredCtypePrefixes = []string{"image/", "font/", "video/", "audio/", "text/javascript"}
|
||||
var ignoredCtypes = []string{"application/javascript", "application/x-javascript", "text/css", "application/font-woff2", "application/font-woff", "application/x-font-woff"}
|
||||
|
||||
var ignoredHeaders = []string{
|
||||
"a-im", "accept",
|
||||
"authorization", "cache-control", "connection", "content-encoding", "content-length", "content-type", "cookie",
|
||||
"date", "dnt", "expect", "forwarded", "from", "front-end-https", "host", "http2-settings",
|
||||
"max-forwards", "origin", "pragma", "proxy-authorization", "proxy-connection", "range", "referer",
|
||||
"save-data", "te", "trailer", "transfer-encoding", "upgrade", "upgrade-insecure-requests",
|
||||
"server", "user-agent", "via", "warning", "strict-transport-security",
|
||||
"x-att-deviceid", "x-correlation-id", "correlation-id", "x-client-data",
|
||||
"x-http-method-override", "x-real-ip", "x-request-id", "x-request-start", "x-requested-with", "x-uidh",
|
||||
"x-same-domain", "x-content-type-options", "x-frame-options", "x-xss-protection",
|
||||
"x-wap-profile", "x-scheme",
|
||||
"newrelic", "x-cloud-trace-context", "sentry-trace",
|
||||
"expires", "set-cookie", "p3p", "location", "content-security-policy", "content-security-policy-report-only",
|
||||
"last-modified", "content-language",
|
||||
"keep-alive", "etag", "alt-svc", "x-csrf-token", "x-ua-compatible", "vary", "x-powered-by",
|
||||
"age", "allow", "www-authenticate",
|
||||
}
|
||||
|
||||
var ignoredHeaderPrefixes = []string{
|
||||
":", "accept-", "access-control-", "if-", "sec-", "grpc-",
|
||||
"x-forwarded-", "x-original-",
|
||||
"x-up9-", "x-envoy-", "x-hasura-", "x-b3-", "x-datadog-", "x-envoy-", "x-amz-", "x-newrelic-", "x-prometheus-",
|
||||
"x-akamai-", "x-spotim-", "x-amzn-", "x-ratelimit-",
|
||||
}
|
||||
|
||||
func isCtypeIgnored(ctype string) bool {
|
||||
for _, prefix := range ignoredCtypePrefixes {
|
||||
if strings.HasPrefix(ctype, prefix) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
for _, toIgnore := range ignoredCtypes {
|
||||
if ctype == toIgnore {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func isExtIgnored(path string) bool {
|
||||
for _, extIgn := range ignoredExtensions {
|
||||
if strings.HasSuffix(path, "."+extIgn) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func isHeaderIgnored(name string) bool {
|
||||
name = strings.ToLower(name)
|
||||
|
||||
for _, ignore := range ignoredHeaders {
|
||||
if name == ignore {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
for _, prefix := range ignoredHeaderPrefixes {
|
||||
if strings.HasPrefix(name, prefix) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
107
agent/pkg/oas/oas_generator.go
Normal file
107
agent/pkg/oas/oas_generator.go
Normal file
@@ -0,0 +1,107 @@
|
||||
package oas
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"github.com/google/martian/har"
|
||||
"github.com/up9inc/mizu/shared/logger"
|
||||
"net/url"
|
||||
"sync"
|
||||
)
|
||||
|
||||
var (
|
||||
syncOnce sync.Once
|
||||
instance *oasGenerator
|
||||
)
|
||||
|
||||
func GetOasGeneratorInstance() *oasGenerator {
|
||||
syncOnce.Do(func() {
|
||||
instance = newOasGenerator()
|
||||
logger.Log.Debug("Oas Generator Initialized")
|
||||
})
|
||||
return instance
|
||||
}
|
||||
|
||||
func (g *oasGenerator) Start() {
|
||||
if g.started {
|
||||
return
|
||||
}
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
g.cancel = cancel
|
||||
g.ctx = ctx
|
||||
g.entriesChan = make(chan har.Entry, 100) // buffer up to 100 entries for OAS processing
|
||||
g.ServiceSpecs = &sync.Map{}
|
||||
g.started = true
|
||||
go instance.runGeneretor()
|
||||
}
|
||||
|
||||
func (g *oasGenerator) runGeneretor() {
|
||||
for {
|
||||
select {
|
||||
case <-g.ctx.Done():
|
||||
logger.Log.Infof("OAS Generator was canceled")
|
||||
return
|
||||
|
||||
case entry, ok := <-g.entriesChan:
|
||||
if !ok {
|
||||
logger.Log.Infof("OAS Generator - entries channel closed")
|
||||
break
|
||||
}
|
||||
u, err := url.Parse(entry.Request.URL)
|
||||
if err != nil {
|
||||
logger.Log.Errorf("Failed to parse entry URL: %v, err: %v", entry.Request.URL, err)
|
||||
}
|
||||
|
||||
val, found := g.ServiceSpecs.Load(u.Host)
|
||||
var gen *SpecGen
|
||||
if !found {
|
||||
gen = NewGen(u.Scheme + "://" + u.Host)
|
||||
g.ServiceSpecs.Store(u.Host, gen)
|
||||
} else {
|
||||
gen = val.(*SpecGen)
|
||||
}
|
||||
|
||||
opId, err := gen.feedEntry(entry)
|
||||
if err != nil {
|
||||
txt, suberr := json.Marshal(entry)
|
||||
if suberr == nil {
|
||||
logger.Log.Debugf("Problematic entry: %s", txt)
|
||||
}
|
||||
|
||||
logger.Log.Warningf("Failed processing entry: %s", err)
|
||||
continue
|
||||
}
|
||||
|
||||
logger.Log.Debugf("Handled entry %s as opId: %s", entry.Request.URL, opId) // TODO: set opId back to entry?
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (g *oasGenerator) PushEntry(entry *har.Entry) {
|
||||
if !g.started {
|
||||
return
|
||||
}
|
||||
select {
|
||||
case g.entriesChan <- *entry:
|
||||
default:
|
||||
logger.Log.Warningf("OAS Generator - entry wasn't sent to channel because the channel has no buffer or there is no receiver")
|
||||
}
|
||||
}
|
||||
|
||||
func newOasGenerator() *oasGenerator {
|
||||
return &oasGenerator{
|
||||
started: false,
|
||||
ctx: nil,
|
||||
cancel: nil,
|
||||
ServiceSpecs: nil,
|
||||
entriesChan: nil,
|
||||
}
|
||||
}
|
||||
|
||||
type oasGenerator struct {
|
||||
started bool
|
||||
ctx context.Context
|
||||
cancel context.CancelFunc
|
||||
ServiceSpecs *sync.Map
|
||||
entriesChan chan har.Entry
|
||||
}
|
||||
497
agent/pkg/oas/specgen.go
Normal file
497
agent/pkg/oas/specgen.go
Normal file
@@ -0,0 +1,497 @@
|
||||
package oas
|
||||
|
||||
import (
|
||||
"encoding/base64"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"github.com/chanced/openapi"
|
||||
"github.com/google/martian/har"
|
||||
"github.com/google/uuid"
|
||||
"github.com/up9inc/mizu/shared/logger"
|
||||
"mime"
|
||||
"net/url"
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync"
|
||||
)
|
||||
|
||||
type reqResp struct { // hello, generics in Go
|
||||
Req *har.Request
|
||||
Resp *har.Response
|
||||
}
|
||||
|
||||
type SpecGen struct {
|
||||
oas *openapi.OpenAPI
|
||||
tree *Node
|
||||
lock sync.Mutex
|
||||
}
|
||||
|
||||
func NewGen(server string) *SpecGen {
|
||||
spec := new(openapi.OpenAPI)
|
||||
spec.Version = "3.1.0"
|
||||
|
||||
info := openapi.Info{Title: server}
|
||||
info.Version = "0.0"
|
||||
spec.Info = &info
|
||||
spec.Paths = &openapi.Paths{Items: map[openapi.PathValue]*openapi.PathObj{}}
|
||||
|
||||
spec.Servers = make([]*openapi.Server, 0)
|
||||
spec.Servers = append(spec.Servers, &openapi.Server{URL: server})
|
||||
|
||||
gen := SpecGen{oas: spec, tree: new(Node)}
|
||||
return &gen
|
||||
}
|
||||
|
||||
func (g *SpecGen) StartFromSpec(oas *openapi.OpenAPI) {
|
||||
g.oas = oas
|
||||
for pathStr, pathObj := range oas.Paths.Items {
|
||||
pathSplit := strings.Split(string(pathStr), "/")
|
||||
g.tree.getOrSet(pathSplit, pathObj)
|
||||
}
|
||||
}
|
||||
|
||||
func (g *SpecGen) feedEntry(entry har.Entry) (string, error) {
|
||||
g.lock.Lock()
|
||||
defer g.lock.Unlock()
|
||||
|
||||
opId, err := g.handlePathObj(&entry)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
// NOTE: opId can be empty for some failed entries
|
||||
return opId, err
|
||||
}
|
||||
|
||||
func (g *SpecGen) GetSpec() (*openapi.OpenAPI, error) {
|
||||
g.lock.Lock()
|
||||
defer g.lock.Unlock()
|
||||
|
||||
g.tree.compact()
|
||||
|
||||
for _, pathop := range g.tree.listOps() {
|
||||
if pathop.op.Summary == "" {
|
||||
pathop.op.Summary = pathop.path
|
||||
}
|
||||
}
|
||||
|
||||
// put paths back from tree into OAS
|
||||
g.oas.Paths = g.tree.listPaths()
|
||||
|
||||
suggestTags(g.oas)
|
||||
|
||||
// to make a deep copy, no better idea than marshal+unmarshal
|
||||
specText, err := json.MarshalIndent(g.oas, "", "\t")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
spec := new(openapi.OpenAPI)
|
||||
err = json.Unmarshal(specText, spec)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return spec, err
|
||||
}
|
||||
|
||||
func suggestTags(oas *openapi.OpenAPI) {
|
||||
paths := getPathsKeys(oas.Paths.Items)
|
||||
for len(paths) > 0 {
|
||||
group := make([]string, 0)
|
||||
group = append(group, paths[0])
|
||||
paths = paths[1:]
|
||||
|
||||
pathsClone := append(paths[:0:0], paths...)
|
||||
for _, path := range pathsClone {
|
||||
if getSimilarPrefix([]string{group[0], path}) != "" {
|
||||
group = append(group, path)
|
||||
paths = deleteFromSlice(paths, path)
|
||||
}
|
||||
}
|
||||
|
||||
common := getSimilarPrefix(group)
|
||||
|
||||
if len(group) > 1 {
|
||||
for _, path := range group {
|
||||
pathObj := oas.Paths.Items[openapi.PathValue(path)]
|
||||
for _, op := range getOps(pathObj) {
|
||||
if op.Tags == nil {
|
||||
op.Tags = make([]string, 0)
|
||||
}
|
||||
// only add tags if not present
|
||||
if len(op.Tags) == 0 {
|
||||
op.Tags = append(op.Tags, common)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//groups[common] = group
|
||||
}
|
||||
}
|
||||
|
||||
func getSimilarPrefix(strs []string) string {
|
||||
chunked := make([][]string, 0)
|
||||
for _, item := range strs {
|
||||
chunked = append(chunked, strings.Split(item, "/"))
|
||||
}
|
||||
|
||||
cmn := longestCommonXfix(chunked, true)
|
||||
res := make([]string, 0)
|
||||
for _, chunk := range cmn {
|
||||
if chunk != "api" && !IsVersionString(chunk) && !strings.HasPrefix(chunk, "{") {
|
||||
res = append(res, chunk)
|
||||
}
|
||||
}
|
||||
return strings.Join(res[1:], ".")
|
||||
}
|
||||
|
||||
func deleteFromSlice(s []string, val string) []string {
|
||||
temp := s[:0]
|
||||
for _, x := range s {
|
||||
if x != val {
|
||||
temp = append(temp, x)
|
||||
}
|
||||
}
|
||||
return temp
|
||||
}
|
||||
|
||||
func getPathsKeys(mymap map[openapi.PathValue]*openapi.PathObj) []string {
|
||||
keys := make([]string, len(mymap))
|
||||
|
||||
i := 0
|
||||
for k := range mymap {
|
||||
keys[i] = string(k)
|
||||
i++
|
||||
}
|
||||
return keys
|
||||
}
|
||||
|
||||
func (g *SpecGen) handlePathObj(entry *har.Entry) (string, error) {
|
||||
urlParsed, err := url.Parse(entry.Request.URL)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
if isExtIgnored(urlParsed.Path) {
|
||||
logger.Log.Debugf("Dropped traffic entry due to ignored extension: %s", urlParsed.Path)
|
||||
}
|
||||
|
||||
ctype := getRespCtype(entry.Response)
|
||||
if isCtypeIgnored(ctype) {
|
||||
logger.Log.Debugf("Dropped traffic entry due to ignored response ctype: %s", ctype)
|
||||
}
|
||||
|
||||
if entry.Response.Status < 100 {
|
||||
logger.Log.Debugf("Dropped traffic entry due to status<100: %s", entry.StartedDateTime)
|
||||
return "", nil
|
||||
}
|
||||
|
||||
if entry.Response.Status == 301 || entry.Response.Status == 308 {
|
||||
logger.Log.Debugf("Dropped traffic entry due to permanent redirect status: %s", entry.StartedDateTime)
|
||||
return "", nil
|
||||
}
|
||||
|
||||
split := strings.Split(urlParsed.Path, "/")
|
||||
node := g.tree.getOrSet(split, new(openapi.PathObj))
|
||||
opObj, err := handleOpObj(entry, node.ops)
|
||||
|
||||
if opObj != nil {
|
||||
return opObj.OperationID, err
|
||||
}
|
||||
|
||||
return "", err
|
||||
}
|
||||
|
||||
func handleOpObj(entry *har.Entry, pathObj *openapi.PathObj) (*openapi.Operation, error) {
|
||||
isSuccess := 100 <= entry.Response.Status && entry.Response.Status < 400
|
||||
opObj, wasMissing, err := getOpObj(pathObj, entry.Request.Method, isSuccess)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if !isSuccess && wasMissing {
|
||||
logger.Log.Debugf("Dropped traffic entry due to failed status and no known endpoint at: %s", entry.StartedDateTime)
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
err = handleRequest(entry.Request, opObj, isSuccess)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
err = handleResponse(entry.Response, opObj, isSuccess)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return opObj, nil
|
||||
}
|
||||
|
||||
func handleRequest(req *har.Request, opObj *openapi.Operation, isSuccess bool) error {
|
||||
// TODO: we don't handle the situation when header/qstr param can be defined on pathObj level. Also the path param defined on opObj
|
||||
|
||||
qstrGW := nvParams{
|
||||
In: openapi.InQuery,
|
||||
Pairs: func() []NVPair {
|
||||
return qstrToNVP(req.QueryString)
|
||||
},
|
||||
IsIgnored: func(name string) bool { return false },
|
||||
GeneralizeName: func(name string) string { return name },
|
||||
}
|
||||
handleNameVals(qstrGW, &opObj.Parameters)
|
||||
|
||||
hdrGW := nvParams{
|
||||
In: openapi.InHeader,
|
||||
Pairs: func() []NVPair {
|
||||
return hdrToNVP(req.Headers)
|
||||
},
|
||||
IsIgnored: isHeaderIgnored,
|
||||
GeneralizeName: strings.ToLower,
|
||||
}
|
||||
handleNameVals(hdrGW, &opObj.Parameters)
|
||||
|
||||
if req.PostData != nil && req.PostData.Text != "" && isSuccess {
|
||||
reqBody, err := getRequestBody(req, opObj, isSuccess)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if reqBody != nil {
|
||||
reqCtype := getReqCtype(req)
|
||||
reqMedia, err := fillContent(reqResp{Req: req}, reqBody.Content, reqCtype, err)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
_ = reqMedia
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func handleResponse(resp *har.Response, opObj *openapi.Operation, isSuccess bool) error {
|
||||
// TODO: we don't support "default" response
|
||||
respObj, err := getResponseObj(resp, opObj, isSuccess)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
handleRespHeaders(resp.Headers, respObj)
|
||||
|
||||
respCtype := getRespCtype(resp)
|
||||
respContent := respObj.Content
|
||||
respMedia, err := fillContent(reqResp{Resp: resp}, respContent, respCtype, err)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
_ = respMedia
|
||||
return nil
|
||||
}
|
||||
|
||||
func handleRespHeaders(reqHeaders []har.Header, respObj *openapi.ResponseObj) {
|
||||
visited := map[string]*openapi.HeaderObj{}
|
||||
for _, pair := range reqHeaders {
|
||||
if isHeaderIgnored(pair.Name) {
|
||||
continue
|
||||
}
|
||||
|
||||
nameGeneral := strings.ToLower(pair.Name)
|
||||
|
||||
initHeaders(respObj)
|
||||
objHeaders := respObj.Headers
|
||||
param := findHeaderByName(&respObj.Headers, pair.Name)
|
||||
if param == nil {
|
||||
param = createHeader(openapi.TypeString)
|
||||
objHeaders[nameGeneral] = param
|
||||
}
|
||||
exmp := ¶m.Examples
|
||||
err := fillParamExample(&exmp, pair.Value)
|
||||
if err != nil {
|
||||
logger.Log.Warningf("Failed to add example to a parameter: %s", err)
|
||||
}
|
||||
visited[nameGeneral] = param
|
||||
}
|
||||
|
||||
// maintain "required" flag
|
||||
if respObj.Headers != nil {
|
||||
for name, param := range respObj.Headers {
|
||||
paramObj, err := param.ResolveHeader(headerResolver)
|
||||
if err != nil {
|
||||
logger.Log.Warningf("Failed to resolve param: %s", err)
|
||||
continue
|
||||
}
|
||||
|
||||
_, ok := visited[strings.ToLower(name)]
|
||||
if !ok {
|
||||
flag := false
|
||||
paramObj.Required = &flag
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func fillContent(reqResp reqResp, respContent openapi.Content, ctype string, err error) (*openapi.MediaType, error) {
|
||||
content, found := respContent[ctype]
|
||||
if !found {
|
||||
respContent[ctype] = &openapi.MediaType{}
|
||||
content = respContent[ctype]
|
||||
}
|
||||
|
||||
var text string
|
||||
if reqResp.Req != nil {
|
||||
text = reqResp.Req.PostData.Text
|
||||
} else {
|
||||
text = decRespText(reqResp.Resp.Content)
|
||||
}
|
||||
|
||||
var exampleMsg []byte
|
||||
// try treating it as json
|
||||
any, isJSON := anyJSON(text)
|
||||
if isJSON {
|
||||
// re-marshal with forced indent
|
||||
exampleMsg, err = json.MarshalIndent(any, "", "\t")
|
||||
if err != nil {
|
||||
panic("Failed to re-marshal value, super-strange")
|
||||
}
|
||||
} else {
|
||||
exampleMsg, err = json.Marshal(text)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
content.Example = exampleMsg
|
||||
return respContent[ctype], nil
|
||||
}
|
||||
|
||||
func decRespText(content *har.Content) (res string) {
|
||||
res = string(content.Text)
|
||||
if content.Encoding == "base64" {
|
||||
data, err := base64.StdEncoding.DecodeString(res)
|
||||
if err != nil {
|
||||
logger.Log.Warningf("error decoding response text as base64: %s", err)
|
||||
} else {
|
||||
res = string(data)
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func getRespCtype(resp *har.Response) string {
|
||||
var ctype string
|
||||
ctype = resp.Content.MimeType
|
||||
for _, hdr := range resp.Headers {
|
||||
if strings.ToLower(hdr.Name) == "content-type" {
|
||||
ctype = hdr.Value
|
||||
}
|
||||
}
|
||||
|
||||
mediaType, _, err := mime.ParseMediaType(ctype)
|
||||
if err != nil {
|
||||
return ""
|
||||
}
|
||||
return mediaType
|
||||
}
|
||||
|
||||
func getReqCtype(req *har.Request) string {
|
||||
var ctype string
|
||||
ctype = req.PostData.MimeType
|
||||
for _, hdr := range req.Headers {
|
||||
if strings.ToLower(hdr.Name) == "content-type" {
|
||||
ctype = hdr.Value
|
||||
}
|
||||
}
|
||||
|
||||
mediaType, _, err := mime.ParseMediaType(ctype)
|
||||
if err != nil {
|
||||
return ""
|
||||
}
|
||||
return mediaType
|
||||
}
|
||||
|
||||
func getResponseObj(resp *har.Response, opObj *openapi.Operation, isSuccess bool) (*openapi.ResponseObj, error) {
|
||||
statusStr := strconv.Itoa(resp.Status)
|
||||
|
||||
var response openapi.Response
|
||||
response, found := opObj.Responses[statusStr]
|
||||
if !found {
|
||||
if opObj.Responses == nil {
|
||||
opObj.Responses = map[string]openapi.Response{}
|
||||
}
|
||||
|
||||
opObj.Responses[statusStr] = &openapi.ResponseObj{Content: map[string]*openapi.MediaType{}}
|
||||
response = opObj.Responses[statusStr]
|
||||
}
|
||||
|
||||
resResponse, err := response.ResolveResponse(responseResolver)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if isSuccess {
|
||||
resResponse.Description = "Successful call with status " + statusStr
|
||||
} else {
|
||||
resResponse.Description = "Failed call with status " + statusStr
|
||||
}
|
||||
return resResponse, nil
|
||||
}
|
||||
|
||||
func getRequestBody(req *har.Request, opObj *openapi.Operation, isSuccess bool) (*openapi.RequestBodyObj, error) {
|
||||
if opObj.RequestBody == nil {
|
||||
opObj.RequestBody = &openapi.RequestBodyObj{Description: "Generic request body", Required: true, Content: map[string]*openapi.MediaType{}}
|
||||
}
|
||||
|
||||
reqBody, err := opObj.RequestBody.ResolveRequestBody(reqBodyResolver)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// TODO: maintain required flag for it, but only consider successful responses
|
||||
//reqBody.Content[]
|
||||
|
||||
return reqBody, nil
|
||||
}
|
||||
|
||||
func getOpObj(pathObj *openapi.PathObj, method string, createIfNone bool) (*openapi.Operation, bool, error) {
|
||||
method = strings.ToLower(method)
|
||||
var op **openapi.Operation
|
||||
|
||||
switch method {
|
||||
case "get":
|
||||
op = &pathObj.Get
|
||||
case "put":
|
||||
op = &pathObj.Put
|
||||
case "post":
|
||||
op = &pathObj.Post
|
||||
case "delete":
|
||||
op = &pathObj.Delete
|
||||
case "options":
|
||||
op = &pathObj.Options
|
||||
case "head":
|
||||
op = &pathObj.Head
|
||||
case "patch":
|
||||
op = &pathObj.Patch
|
||||
case "trace":
|
||||
op = &pathObj.Trace
|
||||
default:
|
||||
return nil, false, errors.New("unsupported HTTP method: " + method)
|
||||
}
|
||||
|
||||
isMissing := false
|
||||
if *op == nil {
|
||||
isMissing = true
|
||||
if createIfNone {
|
||||
*op = &openapi.Operation{Responses: map[string]openapi.Response{}}
|
||||
newUUID := uuid.New().String()
|
||||
(**op).OperationID = newUUID
|
||||
} else {
|
||||
return nil, isMissing, nil
|
||||
}
|
||||
}
|
||||
|
||||
return *op, isMissing, nil
|
||||
}
|
||||
197
agent/pkg/oas/specgen_test.go
Normal file
197
agent/pkg/oas/specgen_test.go
Normal file
@@ -0,0 +1,197 @@
|
||||
package oas
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"github.com/chanced/openapi"
|
||||
"github.com/google/martian/har"
|
||||
"github.com/up9inc/mizu/shared/logger"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"strings"
|
||||
"testing"
|
||||
)
|
||||
|
||||
// if started via env, write file into subdir
|
||||
func writeFiles(label string, spec *openapi.OpenAPI) {
|
||||
if os.Getenv("MIZU_OAS_WRITE_FILES") != "" {
|
||||
path := "./oas-samples"
|
||||
err := os.MkdirAll(path, 0o755)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
content, err := json.MarshalIndent(spec, "", "\t")
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
err = ioutil.WriteFile(path+"/"+label+".json", content, 0644)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestEntries(t *testing.T) {
|
||||
files, err := getFiles("./test_artifacts/")
|
||||
// files, err = getFiles("/media/bigdisk/UP9")
|
||||
if err != nil {
|
||||
t.Log(err)
|
||||
t.FailNow()
|
||||
}
|
||||
GetOasGeneratorInstance().Start()
|
||||
|
||||
if err := feedEntries(files); err != nil {
|
||||
t.Log(err)
|
||||
t.Fail()
|
||||
}
|
||||
|
||||
loadStartingOAS()
|
||||
|
||||
svcs := strings.Builder{}
|
||||
GetOasGeneratorInstance().ServiceSpecs.Range(func(key, val interface{}) bool {
|
||||
gen := val.(*SpecGen)
|
||||
svc := key.(string)
|
||||
svcs.WriteString(svc + ",")
|
||||
spec, err := gen.GetSpec()
|
||||
if err != nil {
|
||||
t.Log(err)
|
||||
t.FailNow()
|
||||
return false
|
||||
}
|
||||
|
||||
err = spec.Validate()
|
||||
if err != nil {
|
||||
specText, _ := json.MarshalIndent(spec, "", "\t")
|
||||
t.Log(string(specText))
|
||||
t.Log(err)
|
||||
t.FailNow()
|
||||
}
|
||||
|
||||
return true
|
||||
})
|
||||
|
||||
GetOasGeneratorInstance().ServiceSpecs.Range(func(key, val interface{}) bool {
|
||||
svc := key.(string)
|
||||
gen := val.(*SpecGen)
|
||||
spec, err := gen.GetSpec()
|
||||
if err != nil {
|
||||
t.Log(err)
|
||||
t.FailNow()
|
||||
}
|
||||
|
||||
specText, _ := json.MarshalIndent(spec, "", "\t")
|
||||
t.Logf("%s", string(specText))
|
||||
|
||||
err = spec.Validate()
|
||||
if err != nil {
|
||||
t.Log(err)
|
||||
t.FailNow()
|
||||
}
|
||||
writeFiles(svc, spec)
|
||||
|
||||
return true
|
||||
})
|
||||
|
||||
}
|
||||
|
||||
func TestFileLDJSON(t *testing.T) {
|
||||
GetOasGeneratorInstance().Start()
|
||||
file := "test_artifacts/output_rdwtyeoyrj.har.ldjson"
|
||||
err := feedFromLDJSON(file)
|
||||
if err != nil {
|
||||
logger.Log.Warning("Failed processing file: " + err.Error())
|
||||
t.Fail()
|
||||
}
|
||||
|
||||
loadStartingOAS()
|
||||
|
||||
GetOasGeneratorInstance().ServiceSpecs.Range(func(_, val interface{}) bool {
|
||||
gen := val.(*SpecGen)
|
||||
spec, err := gen.GetSpec()
|
||||
if err != nil {
|
||||
t.Log(err)
|
||||
t.FailNow()
|
||||
}
|
||||
|
||||
specText, _ := json.MarshalIndent(spec, "", "\t")
|
||||
t.Logf("%s", string(specText))
|
||||
|
||||
err = spec.Validate()
|
||||
if err != nil {
|
||||
t.Log(err)
|
||||
t.FailNow()
|
||||
}
|
||||
|
||||
return true
|
||||
})
|
||||
}
|
||||
|
||||
func loadStartingOAS() {
|
||||
file := "test_artifacts/catalogue.json"
|
||||
fd, err := os.Open(file)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
defer fd.Close()
|
||||
|
||||
data, err := ioutil.ReadAll(fd)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
var doc *openapi.OpenAPI
|
||||
err = json.Unmarshal(data, &doc)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
gen := NewGen("catalogue")
|
||||
gen.StartFromSpec(doc)
|
||||
|
||||
GetOasGeneratorInstance().ServiceSpecs.Store("catalogue", gen)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func TestEntriesNegative(t *testing.T) {
|
||||
files := []string{"invalid"}
|
||||
err := feedEntries(files)
|
||||
if err == nil {
|
||||
t.Logf("Should have failed")
|
||||
t.Fail()
|
||||
}
|
||||
}
|
||||
|
||||
func TestLoadValidHAR(t *testing.T) {
|
||||
inp := `{"startedDateTime": "2021-02-03T07:48:12.959000+00:00", "time": 1, "request": {"method": "GET", "url": "http://unresolved_target/1.0.0/health", "httpVersion": "HTTP/1.1", "cookies": [], "headers": [], "queryString": [], "headersSize": -1, "bodySize": -1}, "response": {"status": 200, "statusText": "OK", "httpVersion": "HTTP/1.1", "cookies": [], "headers": [], "content": {"size": 2, "mimeType": "", "text": "OK"}, "redirectURL": "", "headersSize": -1, "bodySize": 2}, "cache": {}, "timings": {"send": -1, "wait": -1, "receive": 1}}`
|
||||
var entry *har.Entry
|
||||
var err = json.Unmarshal([]byte(inp), &entry)
|
||||
if err != nil {
|
||||
t.Logf("Failed to decode entry: %s", err)
|
||||
// t.FailNow() demonstrates the problem of library
|
||||
}
|
||||
}
|
||||
|
||||
func TestLoadValid3_1(t *testing.T) {
|
||||
fd, err := os.Open("test_artifacts/catalogue.json")
|
||||
if err != nil {
|
||||
t.Log(err)
|
||||
t.FailNow()
|
||||
}
|
||||
|
||||
defer fd.Close()
|
||||
|
||||
data, err := ioutil.ReadAll(fd)
|
||||
if err != nil {
|
||||
t.Log(err)
|
||||
t.FailNow()
|
||||
}
|
||||
|
||||
var oas openapi.OpenAPI
|
||||
err = json.Unmarshal(data, &oas)
|
||||
if err != nil {
|
||||
t.Log(err)
|
||||
t.FailNow()
|
||||
}
|
||||
return
|
||||
}
|
||||
51
agent/pkg/oas/test_artifacts/catalogue.json
Normal file
51
agent/pkg/oas/test_artifacts/catalogue.json
Normal file
@@ -0,0 +1,51 @@
|
||||
{
|
||||
"openapi": "3.1.0",
|
||||
"info": {
|
||||
"title": "Preloaded",
|
||||
"version": "0.1",
|
||||
"description": "Test file for loading pre-existing OAS"
|
||||
},
|
||||
"paths": {
|
||||
"/catalogue/{id}": {
|
||||
"parameters": [
|
||||
{
|
||||
"name": "id",
|
||||
"in": "path",
|
||||
"required": true,
|
||||
"style": "simple",
|
||||
"schema": {
|
||||
"type": "string"
|
||||
},
|
||||
"example": "some-uuid-maybe"
|
||||
}
|
||||
],
|
||||
"get": {
|
||||
"parameters": [ {
|
||||
"name": "non-required-header",
|
||||
"in": "header",
|
||||
"required": true,
|
||||
"style": "simple",
|
||||
"schema": {
|
||||
"type": "string"
|
||||
},
|
||||
"example": "some-uuid-maybe"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"/catalogue/{id}/details": {
|
||||
"parameters": [
|
||||
{
|
||||
"name": "id",
|
||||
"in": "path",
|
||||
"style": "simple",
|
||||
"required": true,
|
||||
"schema": {
|
||||
"type": "string"
|
||||
},
|
||||
"example": "some-uuid-maybe"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
13
agent/pkg/oas/test_artifacts/output_rdwtyeoyrj.har.ldjson
Normal file
13
agent/pkg/oas/test_artifacts/output_rdwtyeoyrj.har.ldjson
Normal file
@@ -0,0 +1,13 @@
|
||||
{"messageType": "http", "_source": null, "firstMessageTime": 1627298057.784151, "lastMessageTime": 1627298065.729303, "messageCount": 12}
|
||||
{"_id": "", "startedDateTime": "2021-07-26T11:14:17.78415179Z", "time": 13, "request": {"method": "GET", "url": "http://catalogue/catalogue/size?tags=", "httpVersion": "HTTP/1.1", "cookies": [], "headers": [{"name": "x-some", "value": "demo val"},{"name": "Host", "value": "catalogue"}, {"name": "Connection", "value": "close"}], "queryString": [{"name": "tags", "value": ""}], "headersSize": -1, "bodySize": 0}, "response": {"status": 200, "statusText": "OK", "httpVersion": "HTTP/1.1", "cookies": [], "headers": [{"name": "Content-Type", "value": "application/json; charset=utf-8"}, {"name": "Date", "value": "Mon, 26 Jul 2021 11:14:17 GMT"}, {"name": "Content-Length", "value": "22"}], "content": {"size": 22, "mimeType": "application/json; charset=utf-8", "text": "eyJlcnIiOm51bGwsInNpemUiOjl9", "encoding": "base64"}, "redirectURL": "", "headersSize": -1, "bodySize": 22}, "cache": {}, "timings": {"send": -1, "wait": -1, "receive": 13}}
|
||||
{"_id": "", "startedDateTime": "2021-07-26T11:14:17.784918698Z", "time": 19, "request": {"method": "GET", "url": "http://catalogue/catalogue?page=1&size=6&tags=", "httpVersion": "HTTP/1.1", "cookies": [], "headers": [{"name": "Connection", "value": "close"}, {"name": "Host", "value": "catalogue"}], "queryString": [{"name": "page", "value": "1"}, {"name": "size", "value": "6"}, {"name": "tags", "value": ""}], "headersSize": -1, "bodySize": 0}, "response": {"status": 200, "statusText": "OK", "httpVersion": "HTTP/1.1", "cookies": [], "headers": [{"name": "Content-Type", "value": "application/json; charset=utf-8"}, {"name": "Date", "value": "Mon, 26 Jul 2021 11:14:17 GMT"}, {"name": "Content-Length", "value": "1927"}], "content": {"size": 1927, "mimeType": "application/json; charset=utf-8", "text": "W3siaWQiOiIwM2ZlZjZhYy0xODk2LTRjZTgtYmQ2OS1iNzk4Zjg1YzZlMGIiLCJuYW1lIjoiSG9seSIsImRlc2NyaXB0aW9uIjoiU29ja3MgZml0IGZvciBhIE1lc3NpYWguIFlvdSB0b28gY2FuIGV4cGVyaWVuY2Ugd2Fsa2luZyBpbiB3YXRlciB3aXRoIHRoZXNlIHNwZWNpYWwgZWRpdGlvbiBiZWF1dGllcy4gRWFjaCBob2xlIGlzIGxvdmluZ2x5IHByb2dnbGVkIHRvIGxlYXZlIHNtb290aCBlZGdlcy4gVGhlIG9ubHkgc29jayBhcHByb3ZlZCBieSBhIGhpZ2hlciBwb3dlci4iLCJpbWFnZVVybCI6WyIvY2F0YWxvZ3VlL2ltYWdlcy9ob2x5XzEuanBlZyIsIi9jYXRhbG9ndWUvaW1hZ2VzL2hvbHlfMi5qcGVnIl0sInByaWNlIjo5OS45OSwiY291bnQiOjEsInRhZyI6WyJhY3Rpb24iLCJtYWdpYyJdfSx7ImlkIjoiMzM5NWE0M2UtMmQ4OC00MGRlLWI5NWYtZTAwZTE1MDIwODViIiwibmFtZSI6IkNvbG91cmZ1bCIsImRlc2NyaXB0aW9uIjoicHJvaWRlbnQgb2NjYWVjYXQgaXJ1cmUgZXQgZXhjZXB0ZXVyIGxhYm9yZSBtaW5pbSBuaXNpIGFtZXQgaXJ1cmUiLCJpbWFnZVVybCI6WyIvY2F0YWxvZ3VlL2ltYWdlcy9jb2xvdXJmdWxfc29ja3MuanBnIiwiL2NhdGFsb2d1ZS9pbWFnZXMvY29sb3VyZnVsX3NvY2tzLmpwZyJdLCJwcmljZSI6MTgsImNvdW50Ijo0MzgsInRhZyI6WyJicm93biIsImJsdWUiXX0seyJpZCI6IjUxMGEwZDdlLThlODMtNDE5My1iNDgzLWUyN2UwOWRkYzM0ZCIsIm5hbWUiOiJTdXBlclNwb3J0IFhMIiwiZGVzY3JpcHRpb24iOiJSZWFkeSBmb3IgYWN0aW9uLiBFbmdpbmVlcnM6IGJlIHJlYWR5IHRvIHNtYXNoIHRoYXQgbmV4dCBidWchIEJlIHJlYWR5LCB3aXRoIHRoZXNlIHN1cGVyLWFjdGlvbi1zcG9ydC1tYXN0ZXJwaWVjZXMuIFRoaXMgcGFydGljdWxhciBlbmdpbmVlciB3YXMgY2hhc2VkIGF3YXkgZnJvbSB0aGUgb2ZmaWNlIHdpdGggYSBzdGljay4iLCJpbWFnZVVybCI6WyIvY2F0YWxvZ3VlL2ltYWdlcy9wdW1hXzEuanBlZyIsIi9jYXRhbG9ndWUvaW1hZ2VzL3B1bWFfMi5qcGVnIl0sInByaWNlIjoxNSwiY291bnQiOjgyMCwidGFnIjpbInNwb3J0IiwiZm9ybWFsIiwiYmxhY2siXX0seyJpZCI6IjgwOGEyZGUxLTFhYWEtNGMyNS1hOWI5LTY2MTJlOGYyOWEzOCIsIm5hbWUiOiJDcm9zc2VkIiwiZGVzY3JpcHRpb24iOiJBIG1hdHVyZSBzb2NrLCBjcm9zc2VkLCB3aXRoIGFuIGFpciBvZiBub25jaGFsYW5jZS4iLCJpbWFnZVVybCI6WyIvY2F0YWxvZ3VlL2ltYWdlcy9jcm9zc18xLmpwZWciLCIvY2F0YWxvZ3VlL2ltYWdlcy9jcm9zc18yLmpwZWciXSwicHJpY2UiOjE3LjMyLCJjb3VudCI6NzM4LCJ0YWciOlsiYmx1ZSIsImFjdGlvbiIsInJlZCIsImZvcm1hbCJdfSx7ImlkIjoiODE5ZTFmYmYtOGI3ZS00ZjZkLTgxMWYtNjkzNTM0OTE2YThiIiwibmFtZSI6IkZpZ3Vlcm9hIiwiZGVzY3JpcHRpb24iOiJlbmltIG9mZmljaWEgYWxpcXVhIGV4Y2VwdGV1ciBlc3NlIGRlc2VydW50IHF1aXMgYWxpcXVpcCBub3N0cnVkIGFuaW0iLCJpbWFnZVVybCI6WyIvY2F0YWxvZ3VlL2ltYWdlcy9XQVQuanBnIiwiL2NhdGFsb2d1ZS9pbWFnZXMvV0FUMi5qcGciXSwicHJpY2UiOjE0LCJjb3VudCI6ODA4LCJ0YWciOlsiZ3JlZW4iLCJmb3JtYWwiLCJibHVlIl19LHsiaWQiOiI4MzdhYjE0MS0zOTllLTRjMWYtOWFiYy1iYWNlNDAyOTZiYWMiLCJuYW1lIjoiQ2F0IHNvY2tzIiwiZGVzY3JpcHRpb24iOiJjb25zZXF1YXQgYW1ldCBjdXBpZGF0YXQgbWluaW0gbGFib3J1bSB0ZW1wb3IgZWxpdCBleCBjb25zZXF1YXQgaW4iLCJpbWFnZVVybCI6WyIvY2F0YWxvZ3VlL2ltYWdlcy9jYXRzb2Nrcy5qcGciLCIvY2F0YWxvZ3VlL2ltYWdlcy9jYXRzb2NrczIuanBnIl0sInByaWNlIjoxNSwiY291bnQiOjE3NSwidGFnIjpbImJyb3duIiwiZm9ybWFsIiwiZ3JlZW4iXX1dCg==", "encoding": "base64"}, "redirectURL": "", "headersSize": -1, "bodySize": 1927}, "cache": {}, "timings": {"send": -1, "wait": -1, "receive": 19}}
|
||||
{"_id": "", "startedDateTime": "2021-07-26T11:14:17.78418182Z", "time": 7, "request": {"method": "GET", "url": "http://catalogue/tags", "httpVersion": "HTTP/1.1", "cookies": [], "headers": [{"name": "Connection", "value": "close"}, {"name": "Host", "value": "catalogue"}], "queryString": [], "headersSize": -1, "bodySize": 0}, "response": {"status": 200, "statusText": "OK", "httpVersion": "HTTP/1.1", "cookies": [], "headers": [{"name": "Content-Type", "value": "application/json; charset=utf-8"}, {"name": "Date", "value": "Mon, 26 Jul 2021 11:14:17 GMT"}, {"name": "Content-Length", "value": "107"}], "content": {"size": 107, "mimeType": "application/json; charset=utf-8", "text": "eyJlcnIiOm51bGwsInRhZ3MiOlsiYnJvd24iLCJnZWVrIiwiZm9ybWFsIiwiYmx1ZSIsInNraW4iLCJyZWQiLCJhY3Rpb24iLCJzcG9ydCIsImJsYWNrIiwibWFnaWMiLCJncmVlbiJdfQ==", "encoding": "base64"}, "redirectURL": "", "headersSize": -1, "bodySize": 107}, "cache": {}, "timings": {"send": -1, "wait": -1, "receive": 7}}
|
||||
{"_id": "", "startedDateTime": "2021-07-26T11:14:18.131501482Z", "time": 5, "request": {"method": "GET", "url": "http://catalogue/catalogue/3395a43e-2d88-40de-b95f-e00e1502085b", "httpVersion": "HTTP/1.1", "cookies": [], "headers": [{"name": "Connection", "value": "close"}, {"name": "Host", "value": "catalogue"}, {"name": "x-some", "value": "demoval"}], ",queryString": [], "headersSize": -1, "bodySize": 0}, "response": {"status": 200, "statusText": "OK", "httpVersion": "HTTP/1.1", "cookies": [], "headers": [{"name": "Content-Type", "value": "application/json; charset=utf-8"}, {"name": "Date", "value": "Mon, 26 Jul 2021 11:14:17 GMT"}, {"name": "Content-Length", "value": "286"}], "content": {"size": 286, "mimeType": "application/json; charset=utf-8", "text": "eyJjb3VudCI6NDM4LCJkZXNjcmlwdGlvbiI6InByb2lkZW50IG9jY2FlY2F0IGlydXJlIGV0IGV4Y2VwdGV1ciBsYWJvcmUgbWluaW0gbmlzaSBhbWV0IGlydXJlIiwiaWQiOiIzMzk1YTQzZS0yZDg4LTQwZGUtYjk1Zi1lMDBlMTUwMjA4NWIiLCJpbWFnZVVybCI6WyIvY2F0YWxvZ3VlL2ltYWdlcy9jb2xvdXJmdWxfc29ja3MuanBnIiwiL2NhdGFsb2d1ZS9pbWFnZXMvY29sb3VyZnVsX3NvY2tzLmpwZyJdLCJuYW1lIjoiQ29sb3VyZnVsIiwicHJpY2UiOjE4LCJ0YWciOlsiYnJvd24iLCJibHVlIl19", "encoding": "base64"}, "redirectURL": "", "headersSize": -1, "bodySize": 286}, "cache": {}, "timings": {"send": -1, "wait": -1, "receive": 5}}
|
||||
{"_id": "", "startedDateTime": "2021-07-26T11:14:18.379836908Z", "time": 14, "request": {"method": "GET", "url": "http://carts/carts/mHK0P7zTktmV1zv57iWAvCTd43FFMHap/items", "httpVersion": "HTTP/1.1", "cookies": [], "headers": [{"name": "Connection", "value": "close"}, {"name": "Host", "value": "carts"}], "queryString": [], "headersSize": -1, "bodySize": 0}, "response": {"status": 200, "statusText": "OK", "httpVersion": "HTTP/1.1", "cookies": [], "headers": [{"name": "X-Application-Context", "value": "carts:80"}, {"name": "Content-Type", "value": "application/json;charset=UTF-8"}, {"name": "Date", "value": "Mon, 26 Jul 2021 11:14:17 GMT"}, {"name": "Transfer-Encoding", "value": "chunked"}], "content": {"size": 113, "mimeType": "application/json;charset=UTF-8", "text": "W3siaWQiOiI2MGZlOThmYjg2YzBmYzAwMDg2OWE5MGMiLCJpdGVtSWQiOiIzMzk1YTQzZS0yZDg4LTQwZGUtYjk1Zi1lMDBlMTUwMjA4NWIiLCJxdWFudGl0eSI6MSwidW5pdFByaWNlIjoxOC4wfV0=", "encoding": "base64"}, "redirectURL": "", "headersSize": -1, "bodySize": -1}, "cache": {}, "timings": {"send": -1, "wait": -1, "receive": 14}}
|
||||
{"_id": "", "startedDateTime": "2021-07-26T11:14:22.920540124Z", "time": 3, "request": {"method": "GET", "url": "http://catalogue/catalogue/808a2de1-1aaa-4c25-a9b9-6612e8f29a38", "httpVersion": "HTTP/1.1", "cookies": [], "headers": [{"name": "Connection", "value": "close"}, {"name": "Host", "value": "catalogue"}], "queryString": [], "headersSize": -1, "bodySize": 0}, "response": {"status": 200, "statusText": "OK", "httpVersion": "HTTP/1.1", "cookies": [], "headers": [{"name": "Content-Type", "value": "application/json; charset=utf-8"}, {"name": "Date", "value": "Mon, 26 Jul 2021 11:14:22 GMT"}, {"name": "Content-Length", "value": "275"}], "content": {"size": 275, "mimeType": "application/json; charset=utf-8", "text": "eyJjb3VudCI6NzM4LCJkZXNjcmlwdGlvbiI6IkEgbWF0dXJlIHNvY2ssIGNyb3NzZWQsIHdpdGggYW4gYWlyIG9mIG5vbmNoYWxhbmNlLiIsImlkIjoiODA4YTJkZTEtMWFhYS00YzI1LWE5YjktNjYxMmU4ZjI5YTM4IiwiaW1hZ2VVcmwiOlsiL2NhdGFsb2d1ZS9pbWFnZXMvY3Jvc3NfMS5qcGVnIiwiL2NhdGFsb2d1ZS9pbWFnZXMvY3Jvc3NfMi5qcGVnIl0sIm5hbWUiOiJDcm9zc2VkIiwicHJpY2UiOjE3LjMyLCJ0YWciOlsiYmx1ZSIsInJlZCIsImFjdGlvbiIsImZvcm1hbCJdfQ==", "encoding": "base64"}, "redirectURL": "", "headersSize": -1, "bodySize": 275}, "cache": {}, "timings": {"send": -1, "wait": -1, "receive": 3}}
|
||||
{"_id": "", "startedDateTime": "2021-07-26T11:14:22.921609501Z", "time": 3, "request": {"method": "GET", "url": "http://catalogue/catalogue?sort=id&size=3&tags=blue", "httpVersion": "HTTP/1.1", "cookies": [], "headers": [{"name": "Connection", "value": "close"}, {"name": "Host", "value": "catalogue"}], "queryString": [{"name": "size", "value": "3"}, {"name": "tags", "value": "blue"}, {"name": "sort", "value": "id"}], "headersSize": -1, "bodySize": 0}, "response": {"status": 200, "statusText": "OK", "httpVersion": "HTTP/1.1", "cookies": [], "headers": [{"name": "Content-Length", "value": "789"}, {"name": "Content-Type", "value": "application/json; charset=utf-8"}, {"name": "Date", "value": "Mon, 26 Jul 2021 11:14:22 GMT"}], "content": {"size": 789, "mimeType": "application/json; charset=utf-8", "text": "W3siaWQiOiIzMzk1YTQzZS0yZDg4LTQwZGUtYjk1Zi1lMDBlMTUwMjA4NWIiLCJuYW1lIjoiQ29sb3VyZnVsIiwiZGVzY3JpcHRpb24iOiJwcm9pZGVudCBvY2NhZWNhdCBpcnVyZSBldCBleGNlcHRldXIgbGFib3JlIG1pbmltIG5pc2kgYW1ldCBpcnVyZSIsImltYWdlVXJsIjpbIi9jYXRhbG9ndWUvaW1hZ2VzL2NvbG91cmZ1bF9zb2Nrcy5qcGciLCIvY2F0YWxvZ3VlL2ltYWdlcy9jb2xvdXJmdWxfc29ja3MuanBnIl0sInByaWNlIjoxOCwiY291bnQiOjQzOCwidGFnIjpbImJsdWUiXX0seyJpZCI6IjgwOGEyZGUxLTFhYWEtNGMyNS1hOWI5LTY2MTJlOGYyOWEzOCIsIm5hbWUiOiJDcm9zc2VkIiwiZGVzY3JpcHRpb24iOiJBIG1hdHVyZSBzb2NrLCBjcm9zc2VkLCB3aXRoIGFuIGFpciBvZiBub25jaGFsYW5jZS4iLCJpbWFnZVVybCI6WyIvY2F0YWxvZ3VlL2ltYWdlcy9jcm9zc18xLmpwZWciLCIvY2F0YWxvZ3VlL2ltYWdlcy9jcm9zc18yLmpwZWciXSwicHJpY2UiOjE3LjMyLCJjb3VudCI6NzM4LCJ0YWciOlsiYmx1ZSJdfSx7ImlkIjoiODE5ZTFmYmYtOGI3ZS00ZjZkLTgxMWYtNjkzNTM0OTE2YThiIiwibmFtZSI6IkZpZ3Vlcm9hIiwiZGVzY3JpcHRpb24iOiJlbmltIG9mZmljaWEgYWxpcXVhIGV4Y2VwdGV1ciBlc3NlIGRlc2VydW50IHF1aXMgYWxpcXVpcCBub3N0cnVkIGFuaW0iLCJpbWFnZVVybCI6WyIvY2F0YWxvZ3VlL2ltYWdlcy9XQVQuanBnIiwiL2NhdGFsb2d1ZS9pbWFnZXMvV0FUMi5qcGciXSwicHJpY2UiOjE0LCJjb3VudCI6ODA4LCJ0YWciOlsiYmx1ZSJdfV0K", "encoding": "base64"}, "redirectURL": "", "headersSize": -1, "bodySize": 789}, "cache": {}, "timings": {"send": -1, "wait": -1, "receive": 3}}
|
||||
{"_id": "", "startedDateTime": "2021-07-26T11:14:22.923197848Z", "time": 3, "request": {"method": "GET", "url": "http://catalogue/catalogue/3395a43e-2d88-40de-b95f-e00e1502085b", "httpVersion": "HTTP/1.1", "cookies": [], "headers": [{"name": "Host", "value": "catalogue"}, {"name": "Connection", "value": "close"}], "queryString": [], "headersSize": -1, "bodySize": 0}, "response": {"status": 200, "statusText": "OK", "httpVersion": "HTTP/1.1", "cookies": [], "headers": [{"name": "Content-Type", "value": "application/json; charset=utf-8"}, {"name": "Date", "value": "Mon, 26 Jul 2021 11:14:22 GMT"}, {"name": "Content-Length", "value": "286"}], "content": {"size": 286, "mimeType": "application/json; charset=utf-8", "text": "eyJjb3VudCI6NDM4LCJkZXNjcmlwdGlvbiI6InByb2lkZW50IG9jY2FlY2F0IGlydXJlIGV0IGV4Y2VwdGV1ciBsYWJvcmUgbWluaW0gbmlzaSBhbWV0IGlydXJlIiwiaWQiOiIzMzk1YTQzZS0yZDg4LTQwZGUtYjk1Zi1lMDBlMTUwMjA4NWIiLCJpbWFnZVVybCI6WyIvY2F0YWxvZ3VlL2ltYWdlcy9jb2xvdXJmdWxfc29ja3MuanBnIiwiL2NhdGFsb2d1ZS9pbWFnZXMvY29sb3VyZnVsX3NvY2tzLmpwZyJdLCJuYW1lIjoiQ29sb3VyZnVsIiwicHJpY2UiOjE4LCJ0YWciOlsiYnJvd24iLCJibHVlIl19", "encoding": "base64"}, "redirectURL": "", "headersSize": -1, "bodySize": 286}, "cache": {}, "timings": {"send": -1, "wait": -1, "receive": 3}}
|
||||
{"_id": "", "startedDateTime": "2021-07-26T11:14:23.175549218Z", "time": 26, "request": {"method": "GET", "url": "http://carts/carts/mHK0P7zTktmV1zv57iWAvCTd43FFMHap/items", "httpVersion": "HTTP/1.1", "cookies": [], "headers": [{"name": "Connection", "value": "close"}, {"name": "Host", "value": "carts"}], "queryString": [], "headersSize": -1, "bodySize": 0}, "response": {"status": 200, "statusText": "OK", "httpVersion": "HTTP/1.1", "cookies": [], "headers": [{"name": "X-Application-Context", "value": "carts:80"}, {"name": "Content-Type", "value": "application/json;charset=UTF-8"}, {"name": "Date", "value": "Mon, 26 Jul 2021 11:14:22 GMT"}, {"name": "Transfer-Encoding", "value": "chunked"}], "content": {"size": 113, "mimeType": "application/json;charset=UTF-8", "text": "W3siaWQiOiI2MGZlOThmYjg2YzBmYzAwMDg2OWE5MGMiLCJpdGVtSWQiOiIzMzk1YTQzZS0yZDg4LTQwZGUtYjk1Zi1lMDBlMTUwMjA4NWIiLCJxdWFudGl0eSI6MSwidW5pdFByaWNlIjoxOC4wfV0=", "encoding": "base64"}, "redirectURL": "", "headersSize": -1, "bodySize": -1}, "cache": {}, "timings": {"send": -1, "wait": -1, "receive": 26}}
|
||||
{"_id": "", "startedDateTime": "2021-07-26T11:14:25.239777333Z", "time": 10, "request": {"method": "GET", "url": "http://carts/carts/mHK0P7zTktmV1zv57iWAvCTd43FFMHap/items", "httpVersion": "HTTP/1.1", "cookies": [], "headers": [{"name": "Connection", "value": "close"}, {"name": "Host", "value": "carts"}], "queryString": [], "headersSize": -1, "bodySize": 0}, "response": {"status": 200, "statusText": "OK", "httpVersion": "HTTP/1.1", "cookies": [], "headers": [{"name": "Content-Type", "value": "application/json;charset=UTF-8"}, {"name": "Transfer-Encoding", "value": "chunked"}, {"name": "Date", "value": "Mon, 26 Jul 2021 11:14:25 GMT"}, {"name": "X-Application-Context", "value": "carts:80"}], "content": {"size": 113, "mimeType": "application/json;charset=UTF-8", "text": "W3siaWQiOiI2MGZlOThmYjg2YzBmYzAwMDg2OWE5MGMiLCJpdGVtSWQiOiIzMzk1YTQzZS0yZDg4LTQwZGUtYjk1Zi1lMDBlMTUwMjA4NWIiLCJxdWFudGl0eSI6MSwidW5pdFByaWNlIjoxOC4wfV0=", "encoding": "base64"}, "redirectURL": "", "headersSize": -1, "bodySize": -1}, "cache": {}, "timings": {"send": -1, "wait": -1, "receive": 10}}
|
||||
{"_id": "", "startedDateTime": "2021-07-26T11:14:25.725866772Z", "time": 3, "request": {"method": "GET", "url": "http://catalogue/catalogue?size=5", "httpVersion": "HTTP/1.1", "cookies": [], "headers": [{"name": "Connection", "value": "close"}, {"name": "Host", "value": "catalogue"}], "queryString": [{"name": "size", "value": "5"}], "headersSize": -1, "bodySize": 0}, "response": {"status": 200, "statusText": "OK", "httpVersion": "HTTP/1.1", "cookies": [], "headers": [{"name": "Content-Length", "value": "1643"}, {"name": "Content-Type", "value": "application/json; charset=utf-8"}, {"name": "Date", "value": "Mon, 26 Jul 2021 11:14:25 GMT"}], "content": {"size": 1643, "mimeType": "application/json; charset=utf-8", "text": "W3siaWQiOiIwM2ZlZjZhYy0xODk2LTRjZTgtYmQ2OS1iNzk4Zjg1YzZlMGIiLCJuYW1lIjoiSG9seSIsImRlc2NyaXB0aW9uIjoiU29ja3MgZml0IGZvciBhIE1lc3NpYWguIFlvdSB0b28gY2FuIGV4cGVyaWVuY2Ugd2Fsa2luZyBpbiB3YXRlciB3aXRoIHRoZXNlIHNwZWNpYWwgZWRpdGlvbiBiZWF1dGllcy4gRWFjaCBob2xlIGlzIGxvdmluZ2x5IHByb2dnbGVkIHRvIGxlYXZlIHNtb290aCBlZGdlcy4gVGhlIG9ubHkgc29jayBhcHByb3ZlZCBieSBhIGhpZ2hlciBwb3dlci4iLCJpbWFnZVVybCI6WyIvY2F0YWxvZ3VlL2ltYWdlcy9ob2x5XzEuanBlZyIsIi9jYXRhbG9ndWUvaW1hZ2VzL2hvbHlfMi5qcGVnIl0sInByaWNlIjo5OS45OSwiY291bnQiOjEsInRhZyI6WyJhY3Rpb24iLCJtYWdpYyJdfSx7ImlkIjoiMzM5NWE0M2UtMmQ4OC00MGRlLWI5NWYtZTAwZTE1MDIwODViIiwibmFtZSI6IkNvbG91cmZ1bCIsImRlc2NyaXB0aW9uIjoicHJvaWRlbnQgb2NjYWVjYXQgaXJ1cmUgZXQgZXhjZXB0ZXVyIGxhYm9yZSBtaW5pbSBuaXNpIGFtZXQgaXJ1cmUiLCJpbWFnZVVybCI6WyIvY2F0YWxvZ3VlL2ltYWdlcy9jb2xvdXJmdWxfc29ja3MuanBnIiwiL2NhdGFsb2d1ZS9pbWFnZXMvY29sb3VyZnVsX3NvY2tzLmpwZyJdLCJwcmljZSI6MTgsImNvdW50Ijo0MzgsInRhZyI6WyJicm93biIsImJsdWUiXX0seyJpZCI6IjUxMGEwZDdlLThlODMtNDE5My1iNDgzLWUyN2UwOWRkYzM0ZCIsIm5hbWUiOiJTdXBlclNwb3J0IFhMIiwiZGVzY3JpcHRpb24iOiJSZWFkeSBmb3IgYWN0aW9uLiBFbmdpbmVlcnM6IGJlIHJlYWR5IHRvIHNtYXNoIHRoYXQgbmV4dCBidWchIEJlIHJlYWR5LCB3aXRoIHRoZXNlIHN1cGVyLWFjdGlvbi1zcG9ydC1tYXN0ZXJwaWVjZXMuIFRoaXMgcGFydGljdWxhciBlbmdpbmVlciB3YXMgY2hhc2VkIGF3YXkgZnJvbSB0aGUgb2ZmaWNlIHdpdGggYSBzdGljay4iLCJpbWFnZVVybCI6WyIvY2F0YWxvZ3VlL2ltYWdlcy9wdW1hXzEuanBlZyIsIi9jYXRhbG9ndWUvaW1hZ2VzL3B1bWFfMi5qcGVnIl0sInByaWNlIjoxNSwiY291bnQiOjgyMCwidGFnIjpbInNwb3J0IiwiZm9ybWFsIiwiYmxhY2siXX0seyJpZCI6IjgwOGEyZGUxLTFhYWEtNGMyNS1hOWI5LTY2MTJlOGYyOWEzOCIsIm5hbWUiOiJDcm9zc2VkIiwiZGVzY3JpcHRpb24iOiJBIG1hdHVyZSBzb2NrLCBjcm9zc2VkLCB3aXRoIGFuIGFpciBvZiBub25jaGFsYW5jZS4iLCJpbWFnZVVybCI6WyIvY2F0YWxvZ3VlL2ltYWdlcy9jcm9zc18xLmpwZWciLCIvY2F0YWxvZ3VlL2ltYWdlcy9jcm9zc18yLmpwZWciXSwicHJpY2UiOjE3LjMyLCJjb3VudCI6NzM4LCJ0YWciOlsiYmx1ZSIsImFjdGlvbiIsInJlZCIsImZvcm1hbCJdfSx7ImlkIjoiODE5ZTFmYmYtOGI3ZS00ZjZkLTgxMWYtNjkzNTM0OTE2YThiIiwibmFtZSI6IkZpZ3Vlcm9hIiwiZGVzY3JpcHRpb24iOiJlbmltIG9mZmljaWEgYWxpcXVhIGV4Y2VwdGV1ciBlc3NlIGRlc2VydW50IHF1aXMgYWxpcXVpcCBub3N0cnVkIGFuaW0iLCJpbWFnZVVybCI6WyIvY2F0YWxvZ3VlL2ltYWdlcy9XQVQuanBnIiwiL2NhdGFsb2d1ZS9pbWFnZXMvV0FUMi5qcGciXSwicHJpY2UiOjE0LCJjb3VudCI6ODA4LCJ0YWciOlsiZ3JlZW4iLCJmb3JtYWwiLCJibHVlIl19XQo=", "encoding": "base64"}, "redirectURL": "", "headersSize": -1, "bodySize": 1643}, "cache": {}, "timings": {"send": -1, "wait": -1, "receive": 3}}
|
||||
{"_id": "", "startedDateTime": "2021-07-26T11:14:25.729303217Z", "time": 3, "request": {"method": "GET", "url": "http://catalogue/catalogue/3395a43e-2d88-40de-b95f-e00e1502085b", "httpVersion": "HTTP/1.1", "cookies": [], "headers": [{"name": "Connection", "value": "close"}, {"name": "Host", "value": "catalogue"}], "queryString": [], "headersSize": -1, "bodySize": 0}, "response": {"status": 200, "statusText": "OK", "httpVersion": "HTTP/1.1", "cookies": [], "headers": [{"name": "Content-Type", "value": "application/json; charset=utf-8"}, {"name": "Date", "value": "Mon, 26 Jul 2021 11:14:25 GMT"}, {"name": "Content-Length", "value": "286"}], "content": {"size": 286, "mimeType": "application/json; charset=utf-8", "text": "eyJjb3VudCI6NDM4LCJkZXNjcmlwdGlvbiI6InByb2lkZW50IG9jY2FlY2F0IGlydXJlIGV0IGV4Y2VwdGV1ciBsYWJvcmUgbWluaW0gbmlzaSBhbWV0IGlydXJlIiwiaWQiOiIzMzk1YTQzZS0yZDg4LTQwZGUtYjk1Zi1lMDBlMTUwMjA4NWIiLCJpbWFnZVVybCI6WyIvY2F0YWxvZ3VlL2ltYWdlcy9jb2xvdXJmdWxfc29ja3MuanBnIiwiL2NhdGFsb2d1ZS9pbWFnZXMvY29sb3VyZnVsX3NvY2tzLmpwZyJdLCJuYW1lIjoiQ29sb3VyZnVsIiwicHJpY2UiOjE4LCJ0YWciOlsiYnJvd24iLCJibHVlIl19", "encoding": "base64"}, "redirectURL": "", "headersSize": -1, "bodySize": 286}, "cache": {}, "timings": {"send": -1, "wait": -1, "receive": 3}}
|
||||
24961
agent/pkg/oas/test_artifacts/output_ysuwqrdktj.har
Normal file
24961
agent/pkg/oas/test_artifacts/output_ysuwqrdktj.har
Normal file
File diff suppressed because one or more lines are too long
205
agent/pkg/oas/tree.go
Normal file
205
agent/pkg/oas/tree.go
Normal file
@@ -0,0 +1,205 @@
|
||||
package oas
|
||||
|
||||
import (
|
||||
"github.com/chanced/openapi"
|
||||
"github.com/up9inc/mizu/shared/logger"
|
||||
"strconv"
|
||||
"strings"
|
||||
)
|
||||
|
||||
type NodePath = []string
|
||||
|
||||
type Node struct {
|
||||
constant *string
|
||||
param *openapi.ParameterObj
|
||||
ops *openapi.PathObj
|
||||
parent *Node
|
||||
children []*Node
|
||||
}
|
||||
|
||||
func (n *Node) getOrSet(path NodePath, pathObjToSet *openapi.PathObj) (node *Node) {
|
||||
if pathObjToSet == nil {
|
||||
panic("Invalid function call")
|
||||
}
|
||||
|
||||
pathChunk := path[0]
|
||||
chunkIsParam := strings.HasPrefix(pathChunk, "{") && strings.HasSuffix(pathChunk, "}")
|
||||
chunkIsGibberish := IsGibberish(pathChunk) && !IsVersionString(pathChunk)
|
||||
|
||||
var paramObj *openapi.ParameterObj
|
||||
if chunkIsParam && pathObjToSet != nil {
|
||||
paramObj = findParamByName(pathObjToSet.Parameters, openapi.InPath, pathChunk[1:len(pathChunk)-1])
|
||||
}
|
||||
|
||||
if paramObj == nil {
|
||||
node = n.searchInConstants(pathChunk)
|
||||
}
|
||||
|
||||
if node == nil {
|
||||
node = n.searchInParams(paramObj, chunkIsGibberish)
|
||||
}
|
||||
|
||||
// still no node found, should create it
|
||||
if node == nil {
|
||||
node = new(Node)
|
||||
node.parent = n
|
||||
n.children = append(n.children, node)
|
||||
|
||||
if paramObj != nil {
|
||||
node.param = paramObj
|
||||
} else if chunkIsGibberish {
|
||||
initParams(&pathObjToSet.Parameters)
|
||||
|
||||
newParam := n.createParam()
|
||||
node.param = newParam
|
||||
|
||||
appended := append(*pathObjToSet.Parameters, newParam)
|
||||
pathObjToSet.Parameters = &appended
|
||||
} else {
|
||||
node.constant = &pathChunk
|
||||
}
|
||||
}
|
||||
|
||||
// add example if it's a param
|
||||
if node.param != nil && !chunkIsParam {
|
||||
exmp := &node.param.Examples
|
||||
err := fillParamExample(&exmp, pathChunk)
|
||||
if err != nil {
|
||||
logger.Log.Warningf("Failed to add example to a parameter: %s", err)
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: eat up trailing slash, in a smart way: node.ops!=nil && path[1]==""
|
||||
if len(path) > 1 {
|
||||
return node.getOrSet(path[1:], pathObjToSet)
|
||||
} else if node.ops == nil {
|
||||
node.ops = pathObjToSet
|
||||
}
|
||||
|
||||
return node
|
||||
}
|
||||
|
||||
func (n *Node) createParam() *openapi.ParameterObj {
|
||||
name := "param"
|
||||
|
||||
// REST assumption, not always correct
|
||||
if strings.HasSuffix(*n.constant, "es") && len(*n.constant) > 4 {
|
||||
name = *n.constant
|
||||
name = name[:len(name)-2] + "Id"
|
||||
} else if strings.HasSuffix(*n.constant, "s") && len(*n.constant) > 3 {
|
||||
name = *n.constant
|
||||
name = name[:len(name)-1] + "Id"
|
||||
}
|
||||
|
||||
newParam := createSimpleParam(name, "path", "string")
|
||||
x := n.countParentParams()
|
||||
if x > 1 {
|
||||
newParam.Name = newParam.Name + strconv.Itoa(x)
|
||||
}
|
||||
|
||||
return newParam
|
||||
}
|
||||
|
||||
func (n *Node) searchInParams(paramObj *openapi.ParameterObj, chunkIsGibberish bool) *Node {
|
||||
// look among params
|
||||
if paramObj != nil || chunkIsGibberish {
|
||||
for _, subnode := range n.children {
|
||||
if subnode.constant != nil {
|
||||
continue
|
||||
}
|
||||
|
||||
// TODO: check the regex pattern of param? for exceptions etc
|
||||
|
||||
if paramObj != nil {
|
||||
// TODO: mergeParam(subnode.param, paramObj)
|
||||
return subnode
|
||||
} else {
|
||||
return subnode
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (n *Node) searchInConstants(pathChunk string) *Node {
|
||||
// look among constants
|
||||
for _, subnode := range n.children {
|
||||
if subnode.constant == nil {
|
||||
continue
|
||||
}
|
||||
|
||||
if *subnode.constant == pathChunk {
|
||||
return subnode
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (n *Node) compact() {
|
||||
// TODO
|
||||
}
|
||||
|
||||
func (n *Node) listPaths() *openapi.Paths {
|
||||
paths := &openapi.Paths{Items: map[openapi.PathValue]*openapi.PathObj{}}
|
||||
|
||||
var strChunk string
|
||||
if n.constant != nil {
|
||||
strChunk = *n.constant
|
||||
} else if n.param != nil {
|
||||
strChunk = "{" + n.param.Name + "}"
|
||||
} else {
|
||||
// this is the root node
|
||||
}
|
||||
|
||||
// add self
|
||||
if n.ops != nil {
|
||||
paths.Items[openapi.PathValue(strChunk)] = n.ops
|
||||
}
|
||||
|
||||
// recurse into children
|
||||
for _, child := range n.children {
|
||||
subPaths := child.listPaths()
|
||||
for path, pathObj := range subPaths.Items {
|
||||
var concat string
|
||||
if n.parent == nil {
|
||||
concat = string(path)
|
||||
} else {
|
||||
concat = strChunk + "/" + string(path)
|
||||
}
|
||||
paths.Items[openapi.PathValue(concat)] = pathObj
|
||||
}
|
||||
}
|
||||
|
||||
return paths
|
||||
}
|
||||
|
||||
type PathAndOp struct {
|
||||
path string
|
||||
op *openapi.Operation
|
||||
}
|
||||
|
||||
func (n *Node) listOps() []PathAndOp {
|
||||
res := make([]PathAndOp, 0)
|
||||
for path, pathObj := range n.listPaths().Items {
|
||||
for _, op := range getOps(pathObj) {
|
||||
res = append(res, PathAndOp{path: string(path), op: op})
|
||||
}
|
||||
}
|
||||
return res
|
||||
}
|
||||
|
||||
func (n *Node) countParentParams() int {
|
||||
res := 0
|
||||
node := n
|
||||
for {
|
||||
if node.param != nil {
|
||||
res++
|
||||
}
|
||||
|
||||
if node.parent == nil {
|
||||
break
|
||||
}
|
||||
node = node.parent
|
||||
}
|
||||
return res
|
||||
}
|
||||
344
agent/pkg/oas/utils.go
Normal file
344
agent/pkg/oas/utils.go
Normal file
@@ -0,0 +1,344 @@
|
||||
package oas
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"github.com/chanced/openapi"
|
||||
"github.com/google/martian/har"
|
||||
"github.com/up9inc/mizu/shared/logger"
|
||||
"strconv"
|
||||
"strings"
|
||||
)
|
||||
|
||||
func exampleResolver(ref string) (*openapi.ExampleObj, error) {
|
||||
return nil, errors.New("JSON references are not supported at the moment: " + ref)
|
||||
}
|
||||
|
||||
func responseResolver(ref string) (*openapi.ResponseObj, error) {
|
||||
return nil, errors.New("JSON references are not supported at the moment: " + ref)
|
||||
}
|
||||
|
||||
func reqBodyResolver(ref string) (*openapi.RequestBodyObj, error) {
|
||||
return nil, errors.New("JSON references are not supported at the moment: " + ref)
|
||||
}
|
||||
|
||||
func paramResolver(ref string) (*openapi.ParameterObj, error) {
|
||||
return nil, errors.New("JSON references are not supported at the moment: " + ref)
|
||||
}
|
||||
|
||||
func headerResolver(ref string) (*openapi.HeaderObj, error) {
|
||||
return nil, errors.New("JSON references are not supported at the moment: " + ref)
|
||||
}
|
||||
|
||||
func initParams(obj **openapi.ParameterList) {
|
||||
if *obj == nil {
|
||||
var params openapi.ParameterList
|
||||
params = make([]openapi.Parameter, 0)
|
||||
*obj = ¶ms
|
||||
}
|
||||
}
|
||||
|
||||
func initHeaders(respObj *openapi.ResponseObj) {
|
||||
if respObj.Headers == nil {
|
||||
var created openapi.Headers
|
||||
created = map[string]openapi.Header{}
|
||||
respObj.Headers = created
|
||||
}
|
||||
}
|
||||
|
||||
func createSimpleParam(name string, in openapi.In, ptype openapi.SchemaType) *openapi.ParameterObj {
|
||||
if name == "" {
|
||||
panic("Cannot create parameter with empty name")
|
||||
}
|
||||
required := true // FFS! https://stackoverflow.com/questions/32364027/reference-a-boolean-for-assignment-in-a-struct/32364093
|
||||
schema := new(openapi.SchemaObj)
|
||||
schema.Type = make(openapi.Types, 0)
|
||||
schema.Type = append(schema.Type, ptype)
|
||||
|
||||
style := openapi.StyleSimple
|
||||
if in == openapi.InQuery {
|
||||
style = openapi.StyleForm
|
||||
}
|
||||
|
||||
newParam := openapi.ParameterObj{
|
||||
Name: name,
|
||||
In: in,
|
||||
Style: string(style),
|
||||
Examples: map[string]openapi.Example{},
|
||||
Schema: schema,
|
||||
Required: &required,
|
||||
}
|
||||
return &newParam
|
||||
}
|
||||
|
||||
func findParamByName(params *openapi.ParameterList, in openapi.In, name string) (pathParam *openapi.ParameterObj) {
|
||||
caseInsensitive := in == openapi.InHeader
|
||||
for _, param := range *params {
|
||||
paramObj, err := param.ResolveParameter(paramResolver)
|
||||
if err != nil {
|
||||
logger.Log.Warningf("Failed to resolve reference: %s", err)
|
||||
continue
|
||||
}
|
||||
|
||||
if paramObj.In != in {
|
||||
continue
|
||||
}
|
||||
|
||||
if paramObj.Name == name || (caseInsensitive && strings.ToLower(paramObj.Name) == strings.ToLower(name)) {
|
||||
pathParam = paramObj
|
||||
break
|
||||
}
|
||||
}
|
||||
return pathParam
|
||||
}
|
||||
|
||||
func findHeaderByName(headers *openapi.Headers, name string) *openapi.HeaderObj {
|
||||
for hname, param := range *headers {
|
||||
hdrObj, err := param.ResolveHeader(headerResolver)
|
||||
if err != nil {
|
||||
logger.Log.Warningf("Failed to resolve reference: %s", err)
|
||||
continue
|
||||
}
|
||||
|
||||
if strings.ToLower(hname) == strings.ToLower(name) {
|
||||
return hdrObj
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
type NVPair struct {
|
||||
Name string
|
||||
Value string
|
||||
}
|
||||
|
||||
type nvParams struct {
|
||||
In openapi.In
|
||||
Pairs func() []NVPair
|
||||
IsIgnored func(name string) bool
|
||||
GeneralizeName func(name string) string
|
||||
}
|
||||
|
||||
func qstrToNVP(list []har.QueryString) []NVPair {
|
||||
res := make([]NVPair, len(list))
|
||||
for idx, val := range list {
|
||||
res[idx] = NVPair{Name: val.Name, Value: val.Value}
|
||||
}
|
||||
return res
|
||||
}
|
||||
|
||||
func hdrToNVP(list []har.Header) []NVPair {
|
||||
res := make([]NVPair, len(list))
|
||||
for idx, val := range list {
|
||||
res[idx] = NVPair{Name: val.Name, Value: val.Value}
|
||||
}
|
||||
return res
|
||||
}
|
||||
|
||||
func handleNameVals(gw nvParams, params **openapi.ParameterList) {
|
||||
visited := map[string]*openapi.ParameterObj{}
|
||||
for _, pair := range gw.Pairs() {
|
||||
if gw.IsIgnored(pair.Name) {
|
||||
continue
|
||||
}
|
||||
|
||||
nameGeneral := gw.GeneralizeName(pair.Name)
|
||||
|
||||
initParams(params)
|
||||
param := findParamByName(*params, gw.In, pair.Name)
|
||||
if param == nil {
|
||||
param = createSimpleParam(nameGeneral, gw.In, openapi.TypeString)
|
||||
appended := append(**params, param)
|
||||
*params = &appended
|
||||
}
|
||||
exmp := ¶m.Examples
|
||||
err := fillParamExample(&exmp, pair.Value)
|
||||
if err != nil {
|
||||
logger.Log.Warningf("Failed to add example to a parameter: %s", err)
|
||||
}
|
||||
visited[nameGeneral] = param
|
||||
}
|
||||
|
||||
// maintain "required" flag
|
||||
if *params != nil {
|
||||
for _, param := range **params {
|
||||
paramObj, err := param.ResolveParameter(paramResolver)
|
||||
if err != nil {
|
||||
logger.Log.Warningf("Failed to resolve param: %s", err)
|
||||
continue
|
||||
}
|
||||
if paramObj.In != gw.In {
|
||||
continue
|
||||
}
|
||||
|
||||
_, ok := visited[strings.ToLower(paramObj.Name)]
|
||||
if !ok {
|
||||
flag := false
|
||||
paramObj.Required = &flag
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func createHeader(ptype openapi.SchemaType) *openapi.HeaderObj {
|
||||
required := true // FFS! https://stackoverflow.com/questions/32364027/reference-a-boolean-for-assignment-in-a-struct/32364093
|
||||
schema := new(openapi.SchemaObj)
|
||||
schema.Type = make(openapi.Types, 0)
|
||||
schema.Type = append(schema.Type, ptype)
|
||||
|
||||
style := openapi.StyleSimple
|
||||
newParam := openapi.HeaderObj{
|
||||
Style: string(style),
|
||||
Examples: map[string]openapi.Example{},
|
||||
Schema: schema,
|
||||
Required: &required,
|
||||
}
|
||||
return &newParam
|
||||
}
|
||||
|
||||
func fillParamExample(param **openapi.Examples, exampleValue string) error {
|
||||
if **param == nil {
|
||||
**param = map[string]openapi.Example{}
|
||||
}
|
||||
|
||||
cnt := 0
|
||||
for _, example := range **param {
|
||||
cnt++
|
||||
exampleObj, err := example.ResolveExample(exampleResolver)
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
|
||||
var value string
|
||||
err = json.Unmarshal(exampleObj.Value, &value)
|
||||
if err != nil {
|
||||
logger.Log.Warningf("Failed decoding parameter example into string: %s", err)
|
||||
continue
|
||||
}
|
||||
|
||||
if value == exampleValue || cnt > 5 { // 5 examples is enough
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
valMsg, err := json.Marshal(exampleValue)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
themap := **param
|
||||
themap["example #"+strconv.Itoa(cnt)] = &openapi.ExampleObj{Value: valMsg}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func longestCommonXfix(strs [][]string, pre bool) []string { // https://github.com/jpillora/longestcommon
|
||||
empty := make([]string, 0)
|
||||
//short-circuit empty list
|
||||
if len(strs) == 0 {
|
||||
return empty
|
||||
}
|
||||
xfix := strs[0]
|
||||
//short-circuit single-element list
|
||||
if len(strs) == 1 {
|
||||
return xfix
|
||||
}
|
||||
//compare first to rest
|
||||
for _, str := range strs[1:] {
|
||||
xfixl := len(xfix)
|
||||
strl := len(str)
|
||||
//short-circuit empty strings
|
||||
if xfixl == 0 || strl == 0 {
|
||||
return empty
|
||||
}
|
||||
//maximum possible length
|
||||
maxl := xfixl
|
||||
if strl < maxl {
|
||||
maxl = strl
|
||||
}
|
||||
//compare letters
|
||||
if pre {
|
||||
//prefix, iterate left to right
|
||||
for i := 0; i < maxl; i++ {
|
||||
if xfix[i] != str[i] {
|
||||
xfix = xfix[:i]
|
||||
break
|
||||
}
|
||||
}
|
||||
} else {
|
||||
//suffix, iternate right to left
|
||||
for i := 0; i < maxl; i++ {
|
||||
xi := xfixl - i - 1
|
||||
si := strl - i - 1
|
||||
if xfix[xi] != str[si] {
|
||||
xfix = xfix[xi+1:]
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return xfix
|
||||
}
|
||||
|
||||
// returns all non-nil ops in PathObj
|
||||
func getOps(pathObj *openapi.PathObj) []*openapi.Operation {
|
||||
ops := []**openapi.Operation{&pathObj.Get, &pathObj.Patch, &pathObj.Put, &pathObj.Options, &pathObj.Post, &pathObj.Trace, &pathObj.Head, &pathObj.Delete}
|
||||
res := make([]*openapi.Operation, 0)
|
||||
for _, opp := range ops {
|
||||
if *opp == nil {
|
||||
continue
|
||||
}
|
||||
res = append(res, *opp)
|
||||
}
|
||||
return res
|
||||
}
|
||||
|
||||
// parses JSON into any possible value
|
||||
func anyJSON(text string) (anyVal interface{}, isJSON bool) {
|
||||
isJSON = true
|
||||
asMap := map[string]interface{}{}
|
||||
err := json.Unmarshal([]byte(text), &asMap)
|
||||
if err == nil && asMap != nil {
|
||||
return asMap, isJSON
|
||||
}
|
||||
|
||||
asArray := make([]interface{}, 0)
|
||||
err = json.Unmarshal([]byte(text), &asArray)
|
||||
if err == nil && asArray != nil {
|
||||
return asArray, isJSON
|
||||
}
|
||||
|
||||
asString := ""
|
||||
sPtr := &asString
|
||||
err = json.Unmarshal([]byte(text), &sPtr)
|
||||
if err == nil && sPtr != nil {
|
||||
return asString, isJSON
|
||||
}
|
||||
|
||||
asInt := 0
|
||||
intPtr := &asInt
|
||||
err = json.Unmarshal([]byte(text), &intPtr)
|
||||
if err == nil && intPtr != nil {
|
||||
return asInt, isJSON
|
||||
}
|
||||
|
||||
asFloat := 0.0
|
||||
floatPtr := &asFloat
|
||||
err = json.Unmarshal([]byte(text), &floatPtr)
|
||||
if err == nil && floatPtr != nil {
|
||||
return asFloat, isJSON
|
||||
}
|
||||
|
||||
asBool := false
|
||||
boolPtr := &asBool
|
||||
err = json.Unmarshal([]byte(text), &boolPtr)
|
||||
if err == nil && boolPtr != nil {
|
||||
return asBool, isJSON
|
||||
}
|
||||
|
||||
if text == "null" {
|
||||
return nil, isJSON
|
||||
}
|
||||
|
||||
return nil, false
|
||||
}
|
||||
35
agent/pkg/oas/utils_test.go
Normal file
35
agent/pkg/oas/utils_test.go
Normal file
@@ -0,0 +1,35 @@
|
||||
package oas
|
||||
|
||||
import (
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestAnyJSON(t *testing.T) {
|
||||
testCases := []struct {
|
||||
inp string
|
||||
isJSON bool
|
||||
out interface{}
|
||||
}{
|
||||
{`{"key": 1, "keyNull": null}`, true, nil},
|
||||
{`[{"key": "val"}, ["subarray"], "string", 1, 2.2, true, null]`, true, nil},
|
||||
{`"somestring"`, true, "somestring"},
|
||||
{"0", true, 0},
|
||||
{"0.5", true, 0.5},
|
||||
{"true", true, true},
|
||||
{"null", true, nil},
|
||||
{"sabbra cadabra", false, nil},
|
||||
{"0.1.2.3", false, nil},
|
||||
}
|
||||
for _, tc := range testCases {
|
||||
any, isJSON := anyJSON(tc.inp)
|
||||
if isJSON != tc.isJSON {
|
||||
t.Errorf("Parse flag mismatch: %t != %t", tc.isJSON, isJSON)
|
||||
} else if isJSON && tc.out != nil && tc.out != any {
|
||||
t.Errorf("%s != %s", any, tc.out)
|
||||
} else if tc.inp == "null" && any != nil {
|
||||
t.Errorf("null has to parse as nil (but got %s)", any)
|
||||
} else {
|
||||
t.Logf("%s => %s", tc.inp, any)
|
||||
}
|
||||
}
|
||||
}
|
||||
18
agent/pkg/providers/install_provider.go
Normal file
18
agent/pkg/providers/install_provider.go
Normal file
@@ -0,0 +1,18 @@
|
||||
package providers
|
||||
|
||||
import (
|
||||
"context"
|
||||
"mizuserver/pkg/config"
|
||||
)
|
||||
|
||||
func IsInstallNeeded() (bool, error) {
|
||||
if !config.Config.StandaloneMode { // install not needed in ephermeral mizu
|
||||
return false, nil
|
||||
}
|
||||
|
||||
if anyUserExists, err := AnyUserExists(context.Background()); err != nil {
|
||||
return false, err
|
||||
} else {
|
||||
return !anyUserExists, nil
|
||||
}
|
||||
}
|
||||
27
agent/pkg/providers/kubernetes_provider.go
Normal file
27
agent/pkg/providers/kubernetes_provider.go
Normal file
@@ -0,0 +1,27 @@
|
||||
package providers
|
||||
|
||||
import (
|
||||
"github.com/up9inc/mizu/shared/kubernetes"
|
||||
"sync"
|
||||
)
|
||||
|
||||
var lock = &sync.Mutex{}
|
||||
|
||||
var kubernetesProvider *kubernetes.Provider
|
||||
|
||||
func GetKubernetesProvider() (*kubernetes.Provider, error) {
|
||||
if kubernetesProvider == nil {
|
||||
lock.Lock()
|
||||
defer lock.Unlock()
|
||||
|
||||
if kubernetesProvider == nil {
|
||||
var err error
|
||||
kubernetesProvider, err = kubernetes.NewProviderInCluster()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return kubernetesProvider, nil
|
||||
}
|
||||
@@ -1,19 +1,56 @@
|
||||
package providers
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"github.com/patrickmn/go-cache"
|
||||
"github.com/up9inc/mizu/shared"
|
||||
"github.com/up9inc/mizu/tap"
|
||||
"mizuserver/pkg/models"
|
||||
"os"
|
||||
"time"
|
||||
)
|
||||
|
||||
const tlsLinkRetainmentTime = time.Minute * 15
|
||||
|
||||
var (
|
||||
TapStatus shared.TapStatus
|
||||
authStatus *models.AuthStatus
|
||||
RecentTLSLinks = cache.New(tlsLinkRetainmentTime, tlsLinkRetainmentTime)
|
||||
)
|
||||
|
||||
func GetAuthStatus() (*models.AuthStatus, error) {
|
||||
if authStatus == nil {
|
||||
syncEntriesConfigJson := os.Getenv(shared.SyncEntriesConfigEnvVar)
|
||||
if syncEntriesConfigJson == "" {
|
||||
authStatus = &models.AuthStatus{}
|
||||
return authStatus, nil
|
||||
}
|
||||
|
||||
syncEntriesConfig := &shared.SyncEntriesConfig{}
|
||||
err := json.Unmarshal([]byte(syncEntriesConfigJson), syncEntriesConfig)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to marshal sync entries config, err: %v", err)
|
||||
}
|
||||
|
||||
if syncEntriesConfig.Token == "" {
|
||||
authStatus = &models.AuthStatus{}
|
||||
return authStatus, nil
|
||||
}
|
||||
|
||||
tokenEmail, err := shared.GetTokenEmail(syncEntriesConfig.Token)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to get token email, err: %v", err)
|
||||
}
|
||||
|
||||
authStatus = &models.AuthStatus{
|
||||
Email: tokenEmail,
|
||||
Model: syncEntriesConfig.Workspace,
|
||||
}
|
||||
}
|
||||
|
||||
return authStatus, nil
|
||||
}
|
||||
|
||||
func GetAllRecentTLSAddresses() []string {
|
||||
recentTLSLinks := make([]string, 0)
|
||||
|
||||
|
||||
42
agent/pkg/providers/tapConfig/tap_config_provider.go
Normal file
42
agent/pkg/providers/tapConfig/tap_config_provider.go
Normal file
@@ -0,0 +1,42 @@
|
||||
package tapConfig
|
||||
|
||||
import (
|
||||
"github.com/up9inc/mizu/shared"
|
||||
"github.com/up9inc/mizu/shared/logger"
|
||||
"mizuserver/pkg/models"
|
||||
"mizuserver/pkg/utils"
|
||||
"os"
|
||||
"sync"
|
||||
)
|
||||
|
||||
const FilePath = shared.DataDirPath + "tap-config.json"
|
||||
|
||||
var (
|
||||
lock = &sync.Mutex{}
|
||||
syncOnce sync.Once
|
||||
config *models.TapConfig
|
||||
)
|
||||
|
||||
func Get() *models.TapConfig {
|
||||
syncOnce.Do(func() {
|
||||
if err := utils.ReadJsonFile(FilePath, &config); err != nil {
|
||||
config = &models.TapConfig{TappedNamespaces: make(map[string]bool)}
|
||||
|
||||
if !os.IsNotExist(err) {
|
||||
logger.Log.Errorf("Error reading tap config from file, err: %v", err)
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
return config
|
||||
}
|
||||
|
||||
func Save(tapConfigToSave *models.TapConfig) {
|
||||
lock.Lock()
|
||||
defer lock.Unlock()
|
||||
|
||||
config = tapConfigToSave
|
||||
if err := utils.SaveJsonFile(FilePath, config); err != nil {
|
||||
logger.Log.Errorf("Error saving tap config, err: %v", err)
|
||||
}
|
||||
}
|
||||
56
agent/pkg/providers/tappedPods/tapped_pods_provider.go
Normal file
56
agent/pkg/providers/tappedPods/tapped_pods_provider.go
Normal file
@@ -0,0 +1,56 @@
|
||||
package tappedPods
|
||||
|
||||
import (
|
||||
"github.com/up9inc/mizu/shared"
|
||||
"github.com/up9inc/mizu/shared/logger"
|
||||
"mizuserver/pkg/providers/tappers"
|
||||
"mizuserver/pkg/utils"
|
||||
"os"
|
||||
"strings"
|
||||
"sync"
|
||||
)
|
||||
|
||||
const FilePath = shared.DataDirPath + "tapped-pods.json"
|
||||
|
||||
var (
|
||||
lock = &sync.Mutex{}
|
||||
syncOnce sync.Once
|
||||
tappedPods []*shared.PodInfo
|
||||
)
|
||||
|
||||
func Get() []*shared.PodInfo {
|
||||
syncOnce.Do(func() {
|
||||
if err := utils.ReadJsonFile(FilePath, &tappedPods); err != nil {
|
||||
if !os.IsNotExist(err) {
|
||||
logger.Log.Errorf("Error reading tapped pods from file, err: %v", err)
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
return tappedPods
|
||||
}
|
||||
|
||||
func Set(tappedPodsToSet []*shared.PodInfo) {
|
||||
lock.Lock()
|
||||
defer lock.Unlock()
|
||||
|
||||
tappedPods = tappedPodsToSet
|
||||
if err := utils.SaveJsonFile(FilePath, tappedPods); err != nil {
|
||||
logger.Log.Errorf("Error saving tapped pods, err: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func GetTappedPodsStatus() []shared.TappedPodStatus {
|
||||
tappedPodsStatus := make([]shared.TappedPodStatus, 0)
|
||||
for _, pod := range Get() {
|
||||
var status string
|
||||
if tapperStatus, ok := tappers.GetStatus()[pod.NodeName]; ok {
|
||||
status = strings.ToLower(tapperStatus.Status)
|
||||
}
|
||||
|
||||
isTapped := status == "running"
|
||||
tappedPodsStatus = append(tappedPodsStatus, shared.TappedPodStatus{Name: pod.Name, Namespace: pod.Namespace, IsTapped: isTapped})
|
||||
}
|
||||
|
||||
return tappedPodsStatus
|
||||
}
|
||||
82
agent/pkg/providers/tappers/tappers_provider.go
Normal file
82
agent/pkg/providers/tappers/tappers_provider.go
Normal file
@@ -0,0 +1,82 @@
|
||||
package tappers
|
||||
|
||||
import (
|
||||
"github.com/up9inc/mizu/shared"
|
||||
"github.com/up9inc/mizu/shared/logger"
|
||||
"mizuserver/pkg/utils"
|
||||
"os"
|
||||
"sync"
|
||||
)
|
||||
|
||||
const FilePath = shared.DataDirPath + "tappers-status.json"
|
||||
|
||||
var (
|
||||
lockStatus = &sync.Mutex{}
|
||||
syncOnce sync.Once
|
||||
status map[string]*shared.TapperStatus
|
||||
|
||||
lockConnectedCount = &sync.Mutex{}
|
||||
connectedCount int
|
||||
)
|
||||
|
||||
func GetStatus() map[string]*shared.TapperStatus {
|
||||
initStatus()
|
||||
|
||||
return status
|
||||
}
|
||||
|
||||
func SetStatus(tapperStatus *shared.TapperStatus) {
|
||||
initStatus()
|
||||
|
||||
lockStatus.Lock()
|
||||
defer lockStatus.Unlock()
|
||||
|
||||
status[tapperStatus.NodeName] = tapperStatus
|
||||
|
||||
saveStatus()
|
||||
}
|
||||
|
||||
func ResetStatus() {
|
||||
lockStatus.Lock()
|
||||
defer lockStatus.Unlock()
|
||||
|
||||
status = make(map[string]*shared.TapperStatus)
|
||||
|
||||
saveStatus()
|
||||
}
|
||||
|
||||
func GetConnectedCount() int {
|
||||
return connectedCount
|
||||
}
|
||||
|
||||
func Connected() {
|
||||
lockConnectedCount.Lock()
|
||||
defer lockConnectedCount.Unlock()
|
||||
|
||||
connectedCount++
|
||||
}
|
||||
|
||||
func Disconnected() {
|
||||
lockConnectedCount.Lock()
|
||||
defer lockConnectedCount.Unlock()
|
||||
|
||||
connectedCount--
|
||||
}
|
||||
|
||||
func initStatus() {
|
||||
syncOnce.Do(func() {
|
||||
if err := utils.ReadJsonFile(FilePath, &status); err != nil {
|
||||
status = make(map[string]*shared.TapperStatus)
|
||||
|
||||
if !os.IsNotExist(err) {
|
||||
logger.Log.Errorf("Error reading tappers status from file, err: %v", err)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func saveStatus() {
|
||||
if err := utils.SaveJsonFile(FilePath, status); err != nil {
|
||||
logger.Log.Errorf("Error saving tappers status, err: %v", err)
|
||||
}
|
||||
}
|
||||
162
agent/pkg/providers/user_provider.go
Normal file
162
agent/pkg/providers/user_provider.go
Normal file
@@ -0,0 +1,162 @@
|
||||
package providers
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"net/http/cookiejar"
|
||||
|
||||
ory "github.com/ory/kratos-client-go"
|
||||
"github.com/up9inc/mizu/shared/logger"
|
||||
)
|
||||
|
||||
var client = getKratosClient("http://127.0.0.1:4433", "http://127.0.0.1:4434")
|
||||
|
||||
// returns session token if successful
|
||||
func RegisterUser(username string, password string, ctx context.Context) (token *string, identityId string, err error, formErrorMessages map[string][]ory.UiText) {
|
||||
flow, _, err := client.V0alpha2Api.InitializeSelfServiceRegistrationFlowWithoutBrowser(ctx).Execute()
|
||||
if err != nil {
|
||||
return nil, "", err, nil
|
||||
}
|
||||
|
||||
result, _, err := client.V0alpha2Api.SubmitSelfServiceRegistrationFlow(ctx).Flow(flow.Id).SubmitSelfServiceRegistrationFlowBody(
|
||||
ory.SubmitSelfServiceRegistrationFlowWithPasswordMethodBodyAsSubmitSelfServiceRegistrationFlowBody(&ory.SubmitSelfServiceRegistrationFlowWithPasswordMethodBody{
|
||||
Method: "password",
|
||||
Password: password,
|
||||
Traits: map[string]interface{}{"username": username},
|
||||
}),
|
||||
).Execute()
|
||||
|
||||
if err != nil {
|
||||
parsedKratosError, parsingErr := parseKratosRegistrationFormError(err)
|
||||
if parsingErr != nil {
|
||||
logger.Log.Debugf("error parsing kratos error: %v", parsingErr)
|
||||
return nil, "", err, nil
|
||||
} else {
|
||||
return nil, "", err, parsedKratosError
|
||||
}
|
||||
}
|
||||
|
||||
return result.SessionToken, result.Identity.Id, nil, nil
|
||||
}
|
||||
|
||||
func PerformLogin(username string, password string, ctx context.Context) (*string, error) {
|
||||
flow, _, err := client.V0alpha2Api.InitializeSelfServiceLoginFlowWithoutBrowser(ctx).Execute()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
result, _, err := client.V0alpha2Api.SubmitSelfServiceLoginFlow(ctx).Flow(flow.Id).SubmitSelfServiceLoginFlowBody(
|
||||
ory.SubmitSelfServiceLoginFlowWithPasswordMethodBodyAsSubmitSelfServiceLoginFlowBody(&ory.SubmitSelfServiceLoginFlowWithPasswordMethodBody{
|
||||
Method: "password",
|
||||
Password: password,
|
||||
PasswordIdentifier: username,
|
||||
}),
|
||||
).Execute()
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if result == nil {
|
||||
return nil, errors.New("unknown error occured during login")
|
||||
}
|
||||
|
||||
return result.SessionToken, nil
|
||||
}
|
||||
|
||||
func VerifyToken(token string, ctx context.Context) (bool, error) {
|
||||
flow, _, err := client.V0alpha2Api.ToSession(ctx).XSessionToken(token).Execute()
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
if flow == nil {
|
||||
return false, nil
|
||||
}
|
||||
|
||||
return true, nil
|
||||
}
|
||||
|
||||
func DeleteUser(identityId string, ctx context.Context) error {
|
||||
result, err := client.V0alpha2Api.AdminDeleteIdentity(ctx, identityId).Execute()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if result == nil {
|
||||
return errors.New("unknown error occured during user deletion")
|
||||
}
|
||||
|
||||
if result.StatusCode < 200 || result.StatusCode > 299 {
|
||||
return errors.New(fmt.Sprintf("user deletion returned bad status %d", result.StatusCode))
|
||||
} else {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
func AnyUserExists(ctx context.Context) (bool, error) {
|
||||
request := client.V0alpha2Api.AdminListIdentities(ctx)
|
||||
request.PerPage(1)
|
||||
|
||||
if result, _, err := request.Execute(); err != nil {
|
||||
return false, err
|
||||
} else {
|
||||
return len(result) > 0, nil
|
||||
}
|
||||
}
|
||||
|
||||
func Logout(token string, ctx context.Context) error {
|
||||
logoutRequest := client.V0alpha2Api.SubmitSelfServiceLogoutFlowWithoutBrowser(ctx)
|
||||
logoutRequest = logoutRequest.SubmitSelfServiceLogoutFlowWithoutBrowserBody(ory.SubmitSelfServiceLogoutFlowWithoutBrowserBody{
|
||||
SessionToken: token,
|
||||
})
|
||||
if response, err := logoutRequest.Execute(); err != nil {
|
||||
return err
|
||||
} else if response == nil || response.StatusCode < 200 || response.StatusCode > 299 {
|
||||
return errors.New("unknown error occured during logout")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func getKratosClient(url string, adminUrl string) *ory.APIClient {
|
||||
conf := ory.NewConfiguration()
|
||||
conf.Servers = ory.ServerConfigurations{{URL: url}}
|
||||
|
||||
// this ensures kratos client uses the admin url for admin actions (any new admin action we use will have to be added here)
|
||||
conf.OperationServers = map[string]ory.ServerConfigurations{
|
||||
"V0alpha2ApiService.AdminDeleteIdentity": {{URL: adminUrl}},
|
||||
"V0alpha2ApiService.AdminListIdentities": {{URL: adminUrl}},
|
||||
}
|
||||
|
||||
cj, _ := cookiejar.New(nil)
|
||||
conf.HTTPClient = &http.Client{Jar: cj}
|
||||
return ory.NewAPIClient(conf)
|
||||
}
|
||||
|
||||
// returns map of form value key to error message
|
||||
func parseKratosRegistrationFormError(err error) (map[string][]ory.UiText, error) {
|
||||
var openApiError *ory.GenericOpenAPIError
|
||||
if errors.As(err, &openApiError) {
|
||||
var registrationFlowModel *ory.SelfServiceRegistrationFlow
|
||||
if jsonErr := json.Unmarshal(openApiError.Body(), ®istrationFlowModel); jsonErr != nil {
|
||||
return nil, jsonErr
|
||||
} else {
|
||||
formMessages := registrationFlowModel.Ui.Nodes
|
||||
parsedMessages := make(map[string][]ory.UiText)
|
||||
|
||||
for _, message := range formMessages {
|
||||
if len(message.Messages) > 0 {
|
||||
if _, ok := parsedMessages[message.Group]; !ok {
|
||||
parsedMessages[message.Group] = make([]ory.UiText, 0)
|
||||
}
|
||||
parsedMessages[message.Group] = append(parsedMessages[message.Group], message.Messages...)
|
||||
}
|
||||
}
|
||||
return parsedMessages, nil
|
||||
}
|
||||
} else {
|
||||
return nil, errors.New("error is not a generic openapi error")
|
||||
}
|
||||
}
|
||||
@@ -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)
|
||||
k8sResolver, err := resolver.NewFromOutOfCluster("", errOut)
|
||||
if err != nil {
|
||||
rlog.Errorf("error creating k8s resolver %s", err)
|
||||
logger.Log.Errorf("error creating k8s resolver %s", err)
|
||||
}
|
||||
|
||||
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
|
||||
if resolvedName != nil {
|
||||
rlog.Errorf("resolved 10.107.251.91=%s", *resolvedName)
|
||||
logger.Log.Errorf("resolved 10.107.251.91=%s", *resolvedName)
|
||||
} else {
|
||||
rlog.Error("Could not find a resolved name for 10.107.251.91")
|
||||
logger.Log.Error("Could not find a resolved name for 10.107.251.91")
|
||||
}
|
||||
|
||||
for {
|
||||
select {
|
||||
case err := <- errOut:
|
||||
rlog.Errorf("name resolving error %s", err)
|
||||
logger.Log.Errorf("name resolving error %s", err)
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
@@ -4,10 +4,11 @@ import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"github.com/romana/rlog"
|
||||
|
||||
"github.com/up9inc/mizu/shared/logger"
|
||||
k8serrors "k8s.io/apimachinery/pkg/api/errors"
|
||||
|
||||
"github.com/orcaman/concurrent-map"
|
||||
cmap "github.com/orcaman/concurrent-map"
|
||||
corev1 "k8s.io/api/core/v1"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/watch"
|
||||
@@ -139,6 +140,13 @@ func (resolver *Resolver) watchServices(ctx context.Context) error {
|
||||
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.Spec.Ports != nil {
|
||||
for _, port := range service.Spec.Ports {
|
||||
if port.Port > 0 {
|
||||
resolver.saveResolvedName(fmt.Sprintf("%s:%d", service.Spec.ClusterIP, port.Port), serviceHostname, event.Type)
|
||||
}
|
||||
}
|
||||
}
|
||||
resolver.saveServiceIP(service.Spec.ClusterIP, serviceHostname, event.Type)
|
||||
}
|
||||
if service.Status.LoadBalancer.Ingress != nil {
|
||||
@@ -156,10 +164,10 @@ func (resolver *Resolver) watchServices(ctx context.Context) error {
|
||||
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)
|
||||
logger.Log.Infof("setting %s=nil", key)
|
||||
} else {
|
||||
resolver.nameMap.Set(key, resolved)
|
||||
rlog.Infof("setting %s=%s\n", key, resolved)
|
||||
logger.Log.Infof("setting %s=%s", key, resolved)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -180,7 +188,7 @@ func (resolver *Resolver) infiniteErrorHandleRetryFunc(ctx context.Context, fun
|
||||
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)
|
||||
logger.Log.Infof("Resolver loop encountered permission error, aborting event listening - %v", err)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
15
agent/pkg/routes/config_routes.go
Normal file
15
agent/pkg/routes/config_routes.go
Normal file
@@ -0,0 +1,15 @@
|
||||
package routes
|
||||
|
||||
import (
|
||||
"github.com/gin-gonic/gin"
|
||||
"mizuserver/pkg/controllers"
|
||||
"mizuserver/pkg/middlewares"
|
||||
)
|
||||
|
||||
func ConfigRoutes(ginApp *gin.Engine) {
|
||||
routeGroup := ginApp.Group("/config")
|
||||
routeGroup.Use(middlewares.RequiresAuth())
|
||||
|
||||
routeGroup.POST("/tapConfig", controllers.PostTapConfig)
|
||||
routeGroup.GET("/tapConfig", controllers.GetTapConfig)
|
||||
}
|
||||
@@ -1,26 +1,17 @@
|
||||
package routes
|
||||
|
||||
import (
|
||||
"github.com/gin-gonic/gin"
|
||||
"mizuserver/pkg/controllers"
|
||||
"mizuserver/pkg/middlewares"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
// EntriesRoutes defines the group of har entries routes.
|
||||
func EntriesRoutes(ginApp *gin.Engine) {
|
||||
routeGroup := ginApp.Group("/api")
|
||||
routeGroup := ginApp.Group("/entries")
|
||||
routeGroup.Use(middlewares.RequiresAuth())
|
||||
|
||||
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)
|
||||
routeGroup.GET("/", controllers.GetEntries) // get entries (base/thin entries) and metadata
|
||||
routeGroup.GET("/:id", controllers.GetEntry) // get single (full) entry
|
||||
}
|
||||
|
||||
13
agent/pkg/routes/install_routes.go
Normal file
13
agent/pkg/routes/install_routes.go
Normal file
@@ -0,0 +1,13 @@
|
||||
package routes
|
||||
|
||||
import (
|
||||
"mizuserver/pkg/controllers"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
func InstallRoutes(ginApp *gin.Engine) {
|
||||
routeGroup := ginApp.Group("/install")
|
||||
|
||||
routeGroup.GET("/isNeeded", controllers.IsSetupNecessary)
|
||||
}
|
||||
@@ -1,8 +1,9 @@
|
||||
package routes
|
||||
|
||||
import (
|
||||
"github.com/gin-gonic/gin"
|
||||
"mizuserver/pkg/controllers"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
// MetadataRoutes defines the group of metadata routes.
|
||||
|
||||
18
agent/pkg/routes/oas_routes.go
Normal file
18
agent/pkg/routes/oas_routes.go
Normal file
@@ -0,0 +1,18 @@
|
||||
package routes
|
||||
|
||||
import (
|
||||
"mizuserver/pkg/controllers"
|
||||
"mizuserver/pkg/middlewares"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
// OASRoutes methods to access OAS spec
|
||||
func OASRoutes(ginApp *gin.Engine) {
|
||||
routeGroup := ginApp.Group("/oas")
|
||||
routeGroup.Use(middlewares.RequiresAuth())
|
||||
|
||||
routeGroup.GET("/", controllers.GetOASServers) // list of servers in OAS map
|
||||
routeGroup.GET("/all", controllers.GetOASAllSpecs) // list of servers in OAS map
|
||||
routeGroup.GET("/:id", controllers.GetOASSpec) // get OAS spec for given server
|
||||
}
|
||||
15
agent/pkg/routes/query_routes.go
Normal file
15
agent/pkg/routes/query_routes.go
Normal file
@@ -0,0 +1,15 @@
|
||||
package routes
|
||||
|
||||
import (
|
||||
"mizuserver/pkg/controllers"
|
||||
"mizuserver/pkg/middlewares"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
func QueryRoutes(ginApp *gin.Engine) {
|
||||
routeGroup := ginApp.Group("/query")
|
||||
routeGroup.Use(middlewares.RequiresAuth())
|
||||
|
||||
routeGroup.POST("/validate", controllers.PostValidate)
|
||||
}
|
||||
19
agent/pkg/routes/service_map_routes.go
Normal file
19
agent/pkg/routes/service_map_routes.go
Normal file
@@ -0,0 +1,19 @@
|
||||
package routes
|
||||
|
||||
import (
|
||||
"mizuserver/pkg/controllers"
|
||||
"mizuserver/pkg/middlewares"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
func ServiceMapRoutes(ginApp *gin.Engine) {
|
||||
routeGroup := ginApp.Group("/servicemap")
|
||||
routeGroup.Use(middlewares.RequiresAuth())
|
||||
|
||||
controller := controllers.NewServiceMapController()
|
||||
|
||||
routeGroup.GET("/status", controller.Status)
|
||||
routeGroup.GET("/get", controller.Get)
|
||||
routeGroup.GET("/reset", controller.Reset)
|
||||
}
|
||||
@@ -1,12 +1,30 @@
|
||||
package routes
|
||||
|
||||
import (
|
||||
"github.com/gin-gonic/gin"
|
||||
"mizuserver/pkg/controllers"
|
||||
"mizuserver/pkg/middlewares"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
func StatusRoutes(ginApp *gin.Engine) {
|
||||
routeGroup := ginApp.Group("/status")
|
||||
routeGroup.Use(middlewares.RequiresAuth())
|
||||
|
||||
routeGroup.GET("/health", controllers.HealthCheck)
|
||||
|
||||
routeGroup.POST("/tappedPods", controllers.PostTappedPods)
|
||||
routeGroup.POST("/tapperStatus", controllers.PostTapperStatus)
|
||||
routeGroup.GET("/connectedTappersCount", controllers.GetConnectedTappersCount)
|
||||
routeGroup.GET("/tap", controllers.GetTappingStatus)
|
||||
|
||||
routeGroup.GET("/auth", controllers.GetAuthStatus)
|
||||
|
||||
routeGroup.GET("/analyze", controllers.AnalyzeInformation)
|
||||
|
||||
routeGroup.GET("/general", controllers.GetGeneralStats) // get general stats about entries in DB
|
||||
|
||||
routeGroup.GET("/recentTLSLinks", controllers.GetRecentTLSLinks)
|
||||
|
||||
routeGroup.GET("/resolving", controllers.GetCurrentResolvingInformation)
|
||||
}
|
||||
|
||||
15
agent/pkg/routes/user_routes.go
Normal file
15
agent/pkg/routes/user_routes.go
Normal file
@@ -0,0 +1,15 @@
|
||||
package routes
|
||||
|
||||
import (
|
||||
"mizuserver/pkg/controllers"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
func UserRoutes(ginApp *gin.Engine) {
|
||||
routeGroup := ginApp.Group("/user")
|
||||
|
||||
routeGroup.POST("/login", controllers.Login)
|
||||
routeGroup.POST("/logout", controllers.Logout)
|
||||
routeGroup.POST("/register", controllers.Register)
|
||||
}
|
||||
@@ -1,12 +1,15 @@
|
||||
package rules
|
||||
|
||||
import (
|
||||
"encoding/base64"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"reflect"
|
||||
"regexp"
|
||||
"strings"
|
||||
|
||||
"github.com/up9inc/mizu/shared/logger"
|
||||
|
||||
"github.com/google/martian/har"
|
||||
"github.com/up9inc/mizu/shared"
|
||||
jsonpath "github.com/yalp/jsonpath"
|
||||
@@ -41,16 +44,19 @@ func ValidateService(serviceFromRule string, service string) bool {
|
||||
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
|
||||
func MatchRequestPolicy(harEntry har.Entry, service string) (resultPolicyToSend []RulesMatched, isEnabled bool) {
|
||||
enforcePolicy, err := shared.DecodeEnforcePolicy(fmt.Sprintf("%s%s", shared.ConfigDirPath, shared.ValidationRulesFileName))
|
||||
if err == nil && len(enforcePolicy.Rules) > 0 {
|
||||
isEnabled = true
|
||||
}
|
||||
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 {
|
||||
contentTextDecoded, _ := base64.StdEncoding.DecodeString(string(harEntry.Response.Content.Text))
|
||||
if err := json.Unmarshal(contentTextDecoded, &bodyJsonMap); err != nil {
|
||||
continue
|
||||
}
|
||||
out, err := jsonpath.Read(bodyJsonMap, rule.Key)
|
||||
@@ -63,6 +69,7 @@ func MatchRequestPolicy(harEntry har.Entry, service string) (int, []RulesMatched
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
logger.Log.Info(matchValue, rule.Value)
|
||||
} else {
|
||||
val := fmt.Sprint(out)
|
||||
matchValue, err = regexp.MatchString(rule.Value, val)
|
||||
@@ -89,22 +96,28 @@ func MatchRequestPolicy(harEntry har.Entry, service string) (int, []RulesMatched
|
||||
resultPolicyToSend = appendRulesMatched(resultPolicyToSend, true, rule)
|
||||
}
|
||||
}
|
||||
return len(enforcePolicy.Rules), resultPolicyToSend
|
||||
return
|
||||
}
|
||||
|
||||
func PassedValidationRules(rulesMatched []RulesMatched, numberOfRules int) (bool, int64, int) {
|
||||
if len(rulesMatched) == 0 {
|
||||
return false, 0, 0
|
||||
func PassedValidationRules(rulesMatched []RulesMatched) (bool, int64, int) {
|
||||
var numberOfRulesMatched = len(rulesMatched)
|
||||
var responseTime int64 = -1
|
||||
|
||||
if numberOfRulesMatched == 0 {
|
||||
return false, 0, numberOfRulesMatched
|
||||
}
|
||||
|
||||
for _, rule := range rulesMatched {
|
||||
if rule.Matched == false {
|
||||
return false, -1, len(rulesMatched)
|
||||
return false, responseTime, numberOfRulesMatched
|
||||
} else {
|
||||
if strings.ToLower(rule.Rule.Type) == "slo" {
|
||||
if rule.Rule.ResponseTime < responseTime || responseTime == -1 {
|
||||
responseTime = rule.Rule.ResponseTime
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
for _, rule := range rulesMatched {
|
||||
if strings.ToLower(rule.Rule.Type) == "latency" {
|
||||
return true, rule.Rule.Latency, len(rulesMatched)
|
||||
}
|
||||
}
|
||||
return true, -1, len(rulesMatched)
|
||||
|
||||
return true, responseTime, numberOfRulesMatched
|
||||
}
|
||||
@@ -1,10 +0,0 @@
|
||||
package sensitiveDataFiltering
|
||||
|
||||
const maskedFieldPlaceholderValue = "[REDACTED]"
|
||||
|
||||
//these values MUST be all lower case and contain no `-` or `_` characters
|
||||
var personallyIdentifiableDataFields = []string{"token", "authorization", "authentication", "cookie", "userid", "password",
|
||||
"username", "user", "key", "passcode", "pass", "auth", "authtoken", "jwt",
|
||||
"bearer", "clientid", "clientsecret", "redirecturi", "phonenumber",
|
||||
"zip", "zipcode", "address", "country", "firstname", "lastname",
|
||||
"middlename", "fname", "lname", "birthdate"}
|
||||
@@ -1,198 +0,0 @@
|
||||
package sensitiveDataFiltering
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"encoding/xml"
|
||||
"errors"
|
||||
"fmt"
|
||||
"github.com/up9inc/mizu/tap"
|
||||
"net/url"
|
||||
"strings"
|
||||
|
||||
"github.com/beevik/etree"
|
||||
"github.com/google/martian/har"
|
||||
"github.com/up9inc/mizu/shared"
|
||||
)
|
||||
|
||||
func FilterSensitiveInfoFromHarRequest(harOutputItem *tap.OutputChannelItem, options *shared.TrafficFilteringOptions) {
|
||||
harOutputItem.HarEntry.Request.Headers = filterHarHeaders(harOutputItem.HarEntry.Request.Headers)
|
||||
harOutputItem.HarEntry.Response.Headers = filterHarHeaders(harOutputItem.HarEntry.Response.Headers)
|
||||
|
||||
harOutputItem.HarEntry.Request.Cookies = make([]har.Cookie, 0, 0)
|
||||
harOutputItem.HarEntry.Response.Cookies = make([]har.Cookie, 0, 0)
|
||||
|
||||
harOutputItem.HarEntry.Request.URL = filterUrl(harOutputItem.HarEntry.Request.URL)
|
||||
for i, queryString := range harOutputItem.HarEntry.Request.QueryString {
|
||||
if isFieldNameSensitive(queryString.Name) {
|
||||
harOutputItem.HarEntry.Request.QueryString[i].Value = maskedFieldPlaceholderValue
|
||||
}
|
||||
}
|
||||
|
||||
if harOutputItem.HarEntry.Request.PostData != nil {
|
||||
requestContentType := getContentTypeHeaderValue(harOutputItem.HarEntry.Request.Headers)
|
||||
filteredRequestBody, err := filterHttpBody([]byte(harOutputItem.HarEntry.Request.PostData.Text), requestContentType, options)
|
||||
if err == nil {
|
||||
harOutputItem.HarEntry.Request.PostData.Text = string(filteredRequestBody)
|
||||
}
|
||||
}
|
||||
if harOutputItem.HarEntry.Response.Content != nil {
|
||||
responseContentType := getContentTypeHeaderValue(harOutputItem.HarEntry.Response.Headers)
|
||||
filteredResponseBody, err := filterHttpBody(harOutputItem.HarEntry.Response.Content.Text, responseContentType, options)
|
||||
if err == nil {
|
||||
harOutputItem.HarEntry.Response.Content.Text = filteredResponseBody
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func filterHarHeaders(headers []har.Header) []har.Header {
|
||||
newHeaders := make([]har.Header, 0)
|
||||
for i, header := range headers {
|
||||
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
|
||||
} else {
|
||||
newHeaders = append(newHeaders, header)
|
||||
}
|
||||
}
|
||||
return newHeaders
|
||||
}
|
||||
|
||||
func getContentTypeHeaderValue(headers []har.Header) string {
|
||||
for _, header := range headers {
|
||||
if strings.ToLower(header.Name) == "content-type" {
|
||||
return header.Value
|
||||
}
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func isFieldNameSensitive(fieldName string) bool {
|
||||
name := strings.ToLower(fieldName)
|
||||
name = strings.ReplaceAll(name, "_", "")
|
||||
name = strings.ReplaceAll(name, "-", "")
|
||||
name = strings.ReplaceAll(name, " ", "")
|
||||
|
||||
for _, sensitiveField := range personallyIdentifiableDataFields {
|
||||
if strings.Contains(name, sensitiveField) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
func filterHttpBody(bytes []byte, contentType string, options *shared.TrafficFilteringOptions) ([]byte, error) {
|
||||
mimeType := strings.Split(contentType, ";")[0]
|
||||
switch strings.ToLower(mimeType) {
|
||||
case "application/json":
|
||||
return filterJsonBody(bytes)
|
||||
case "text/html":
|
||||
fallthrough
|
||||
case "application/xhtml+xml":
|
||||
fallthrough
|
||||
case "text/xml":
|
||||
fallthrough
|
||||
case "application/xml":
|
||||
return filterXmlEtree(bytes)
|
||||
case "text/plain":
|
||||
if options != nil && options.PlainTextMaskingRegexes != nil {
|
||||
return filterPlainText(bytes, options), nil
|
||||
}
|
||||
}
|
||||
return bytes, nil
|
||||
}
|
||||
|
||||
func filterPlainText(bytes []byte, options *shared.TrafficFilteringOptions) []byte {
|
||||
for _, regex := range options.PlainTextMaskingRegexes {
|
||||
bytes = regex.ReplaceAll(bytes, []byte(maskedFieldPlaceholderValue))
|
||||
}
|
||||
return bytes
|
||||
}
|
||||
|
||||
func filterXmlEtree(bytes []byte) ([]byte, error) {
|
||||
if !IsValidXML(bytes) {
|
||||
return nil, errors.New("Invalid XML")
|
||||
}
|
||||
xmlDoc := etree.NewDocument()
|
||||
err := xmlDoc.ReadFromBytes(bytes)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
} else {
|
||||
filterXmlElement(xmlDoc.Root())
|
||||
}
|
||||
return xmlDoc.WriteToBytes()
|
||||
}
|
||||
|
||||
func IsValidXML(data []byte) bool {
|
||||
return xml.Unmarshal(data, new(interface{})) == nil
|
||||
}
|
||||
|
||||
func filterXmlElement(element *etree.Element) {
|
||||
for i, attribute := range element.Attr {
|
||||
if isFieldNameSensitive(attribute.Key) {
|
||||
element.Attr[i].Value = maskedFieldPlaceholderValue
|
||||
}
|
||||
}
|
||||
if element.ChildElements() == nil || len(element.ChildElements()) == 0 {
|
||||
if isFieldNameSensitive(element.Tag) {
|
||||
element.SetText(maskedFieldPlaceholderValue)
|
||||
}
|
||||
} else {
|
||||
for _, element := range element.ChildElements() {
|
||||
filterXmlElement(element)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func filterJsonBody(bytes []byte) ([]byte, error) {
|
||||
var bodyJsonMap map[string] interface{}
|
||||
err := json.Unmarshal(bytes ,&bodyJsonMap)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
filterJsonMap(bodyJsonMap)
|
||||
return json.Marshal(bodyJsonMap)
|
||||
}
|
||||
|
||||
func filterJsonMap(jsonMap map[string] interface{}) {
|
||||
for key, value := range jsonMap {
|
||||
if value == nil {
|
||||
return
|
||||
}
|
||||
nestedMap, isNested := value.(map[string] interface{})
|
||||
if isNested {
|
||||
filterJsonMap(nestedMap)
|
||||
} else {
|
||||
if isFieldNameSensitive(key) {
|
||||
jsonMap[key] = maskedFieldPlaceholderValue
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// receives string representing url, returns string url without sensitive query param values (http://service/api?userId=bob&password=123&type=login -> http://service/api?userId=[REDACTED]&password=[REDACTED]&type=login)
|
||||
func filterUrl(originalUrl string) string {
|
||||
parsedUrl, err := url.Parse(originalUrl)
|
||||
if err != nil {
|
||||
return fmt.Sprintf("http://%s", maskedFieldPlaceholderValue)
|
||||
} else {
|
||||
if len(parsedUrl.RawQuery) > 0 {
|
||||
newQueryArgs := make([]string, 0)
|
||||
for urlQueryParamName, urlQueryParamValues := range parsedUrl.Query() {
|
||||
newValues := urlQueryParamValues
|
||||
if isFieldNameSensitive(urlQueryParamName) {
|
||||
newValues = []string {maskedFieldPlaceholderValue}
|
||||
}
|
||||
for _, paramValue := range newValues {
|
||||
newQueryArgs = append(newQueryArgs, fmt.Sprintf("%s=%s", urlQueryParamName, paramValue))
|
||||
}
|
||||
}
|
||||
|
||||
parsedUrl.RawQuery = strings.Join(newQueryArgs, "&")
|
||||
}
|
||||
|
||||
return parsedUrl.String()
|
||||
}
|
||||
}
|
||||
32
agent/pkg/servicemap/models.go
Normal file
32
agent/pkg/servicemap/models.go
Normal file
@@ -0,0 +1,32 @@
|
||||
package servicemap
|
||||
|
||||
import (
|
||||
tapApi "github.com/up9inc/mizu/tap/api"
|
||||
)
|
||||
|
||||
type ServiceMapStatus struct {
|
||||
Status string `json:"status"`
|
||||
EntriesProcessedCount int `json:"entriesProcessedCount"`
|
||||
NodeCount int `json:"nodeCount"`
|
||||
EdgeCount int `json:"edgeCount"`
|
||||
}
|
||||
|
||||
type ServiceMapResponse struct {
|
||||
Status ServiceMapStatus `json:"status"`
|
||||
Nodes []ServiceMapNode `json:"nodes"`
|
||||
Edges []ServiceMapEdge `json:"edges"`
|
||||
}
|
||||
|
||||
type ServiceMapNode struct {
|
||||
Id int `json:"id"`
|
||||
Name string `json:"name"`
|
||||
Entry *tapApi.TCP `json:"entry"`
|
||||
Count int `json:"count"`
|
||||
}
|
||||
|
||||
type ServiceMapEdge struct {
|
||||
Source ServiceMapNode `json:"source"`
|
||||
Destination ServiceMapNode `json:"destination"`
|
||||
Count int `json:"count"`
|
||||
Protocol *tapApi.Protocol `json:"protocol"`
|
||||
}
|
||||
271
agent/pkg/servicemap/servicemap.go
Normal file
271
agent/pkg/servicemap/servicemap.go
Normal file
@@ -0,0 +1,271 @@
|
||||
package servicemap
|
||||
|
||||
import (
|
||||
"sync"
|
||||
|
||||
"github.com/up9inc/mizu/shared"
|
||||
"github.com/up9inc/mizu/shared/logger"
|
||||
tapApi "github.com/up9inc/mizu/tap/api"
|
||||
)
|
||||
|
||||
const (
|
||||
ServiceMapEnabled = "enabled"
|
||||
ServiceMapDisabled = "disabled"
|
||||
UnresolvedNodeName = "unresolved"
|
||||
)
|
||||
|
||||
var instance *serviceMap
|
||||
var once sync.Once
|
||||
|
||||
func GetInstance() ServiceMap {
|
||||
once.Do(func() {
|
||||
instance = newServiceMap()
|
||||
logger.Log.Debug("Service Map Initialized")
|
||||
})
|
||||
return instance
|
||||
}
|
||||
|
||||
type serviceMap struct {
|
||||
config *shared.MizuAgentConfig
|
||||
graph *graph
|
||||
entriesProcessed int
|
||||
}
|
||||
|
||||
type ServiceMap interface {
|
||||
SetConfig(config *shared.MizuAgentConfig)
|
||||
IsEnabled() bool
|
||||
NewTCPEntry(source *tapApi.TCP, destination *tapApi.TCP, protocol *tapApi.Protocol)
|
||||
GetStatus() ServiceMapStatus
|
||||
GetNodes() []ServiceMapNode
|
||||
GetEdges() []ServiceMapEdge
|
||||
GetEntriesProcessedCount() int
|
||||
GetNodesCount() int
|
||||
GetEdgesCount() int
|
||||
Reset()
|
||||
}
|
||||
|
||||
func newServiceMap() *serviceMap {
|
||||
return &serviceMap{
|
||||
config: nil,
|
||||
entriesProcessed: 0,
|
||||
graph: newDirectedGraph(),
|
||||
}
|
||||
}
|
||||
|
||||
type key string
|
||||
|
||||
type entryData struct {
|
||||
key key
|
||||
entry *tapApi.TCP
|
||||
}
|
||||
|
||||
type nodeData struct {
|
||||
id int
|
||||
entry *tapApi.TCP
|
||||
count int
|
||||
}
|
||||
|
||||
type edgeProtocol struct {
|
||||
protocol *tapApi.Protocol
|
||||
count int
|
||||
}
|
||||
|
||||
type edgeData struct {
|
||||
data map[key]*edgeProtocol
|
||||
}
|
||||
|
||||
type graph struct {
|
||||
Nodes map[key]*nodeData
|
||||
Edges map[key]map[key]*edgeData
|
||||
}
|
||||
|
||||
func newDirectedGraph() *graph {
|
||||
return &graph{
|
||||
Nodes: make(map[key]*nodeData),
|
||||
Edges: make(map[key]map[key]*edgeData),
|
||||
}
|
||||
}
|
||||
|
||||
func newNodeData(id int, e *tapApi.TCP) *nodeData {
|
||||
return &nodeData{
|
||||
id: id,
|
||||
entry: e,
|
||||
count: 1,
|
||||
}
|
||||
}
|
||||
|
||||
func newEdgeData(p *tapApi.Protocol) *edgeData {
|
||||
return &edgeData{
|
||||
data: map[key]*edgeProtocol{
|
||||
key(p.Name): {
|
||||
protocol: p,
|
||||
count: 1,
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func (s *serviceMap) nodeExists(k key) (*nodeData, bool) {
|
||||
n, ok := s.graph.Nodes[k]
|
||||
return n, ok
|
||||
}
|
||||
|
||||
func (s *serviceMap) addNode(k key, e *tapApi.TCP) (*nodeData, bool) {
|
||||
nd, exists := s.nodeExists(k)
|
||||
if !exists {
|
||||
s.graph.Nodes[k] = newNodeData(len(s.graph.Nodes)+1, e)
|
||||
return s.graph.Nodes[k], true
|
||||
}
|
||||
return nd, false
|
||||
}
|
||||
|
||||
func (s *serviceMap) addEdge(u, v *entryData, p *tapApi.Protocol) {
|
||||
if n, ok := s.addNode(u.key, u.entry); !ok {
|
||||
n.count++
|
||||
}
|
||||
if n, ok := s.addNode(v.key, v.entry); !ok {
|
||||
n.count++
|
||||
}
|
||||
|
||||
if _, ok := s.graph.Edges[u.key]; !ok {
|
||||
s.graph.Edges[u.key] = make(map[key]*edgeData)
|
||||
}
|
||||
|
||||
// new edge u -> v pair
|
||||
// protocol is the same for u and v
|
||||
if e, ok := s.graph.Edges[u.key][v.key]; ok {
|
||||
// edge data already exists for u -> v pair
|
||||
// we have a new protocol for this u -> v pair
|
||||
|
||||
k := key(p.Name)
|
||||
if pd, pOk := e.data[k]; pOk {
|
||||
// protocol key already exists, just increment the count
|
||||
pd.count++
|
||||
} else {
|
||||
// new protocol key
|
||||
e.data[k] = &edgeProtocol{
|
||||
protocol: p,
|
||||
count: 1,
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// new edge data for u -> v pair
|
||||
s.graph.Edges[u.key][v.key] = newEdgeData(p)
|
||||
}
|
||||
|
||||
s.entriesProcessed++
|
||||
}
|
||||
|
||||
func (s *serviceMap) SetConfig(config *shared.MizuAgentConfig) {
|
||||
s.config = config
|
||||
}
|
||||
|
||||
func (s *serviceMap) IsEnabled() bool {
|
||||
if s.config != nil && s.config.ServiceMap {
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func (s *serviceMap) NewTCPEntry(src *tapApi.TCP, dst *tapApi.TCP, p *tapApi.Protocol) {
|
||||
if !s.IsEnabled() {
|
||||
return
|
||||
}
|
||||
|
||||
srcEntry := &entryData{
|
||||
key: key(src.IP),
|
||||
entry: src,
|
||||
}
|
||||
if len(srcEntry.entry.Name) == 0 {
|
||||
srcEntry.entry.Name = UnresolvedNodeName
|
||||
}
|
||||
|
||||
dstEntry := &entryData{
|
||||
key: key(dst.IP),
|
||||
entry: dst,
|
||||
}
|
||||
if len(dstEntry.entry.Name) == 0 {
|
||||
dstEntry.entry.Name = UnresolvedNodeName
|
||||
}
|
||||
|
||||
s.addEdge(srcEntry, dstEntry, p)
|
||||
}
|
||||
|
||||
func (s *serviceMap) GetStatus() ServiceMapStatus {
|
||||
status := ServiceMapDisabled
|
||||
if s.IsEnabled() {
|
||||
status = ServiceMapEnabled
|
||||
}
|
||||
|
||||
return ServiceMapStatus{
|
||||
Status: status,
|
||||
EntriesProcessedCount: s.entriesProcessed,
|
||||
NodeCount: s.GetNodesCount(),
|
||||
EdgeCount: s.GetEdgesCount(),
|
||||
}
|
||||
}
|
||||
|
||||
func (s *serviceMap) GetNodes() []ServiceMapNode {
|
||||
var nodes []ServiceMapNode
|
||||
for i, n := range s.graph.Nodes {
|
||||
nodes = append(nodes, ServiceMapNode{
|
||||
Id: n.id,
|
||||
Name: string(i),
|
||||
Entry: n.entry,
|
||||
Count: n.count,
|
||||
})
|
||||
}
|
||||
return nodes
|
||||
}
|
||||
|
||||
func (s *serviceMap) GetEdges() []ServiceMapEdge {
|
||||
var edges []ServiceMapEdge
|
||||
for u, m := range s.graph.Edges {
|
||||
for v := range m {
|
||||
for _, p := range s.graph.Edges[u][v].data {
|
||||
edges = append(edges, ServiceMapEdge{
|
||||
Source: ServiceMapNode{
|
||||
Id: s.graph.Nodes[u].id,
|
||||
Name: string(u),
|
||||
Entry: s.graph.Nodes[u].entry,
|
||||
Count: s.graph.Nodes[u].count,
|
||||
},
|
||||
Destination: ServiceMapNode{
|
||||
Id: s.graph.Nodes[v].id,
|
||||
Name: string(v),
|
||||
Entry: s.graph.Nodes[v].entry,
|
||||
Count: s.graph.Nodes[v].count,
|
||||
},
|
||||
Count: p.count,
|
||||
Protocol: p.protocol,
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
return edges
|
||||
}
|
||||
|
||||
func (s *serviceMap) GetEntriesProcessedCount() int {
|
||||
return s.entriesProcessed
|
||||
}
|
||||
|
||||
func (s *serviceMap) GetNodesCount() int {
|
||||
return len(s.graph.Nodes)
|
||||
}
|
||||
|
||||
func (s *serviceMap) GetEdgesCount() int {
|
||||
var count int
|
||||
for u, m := range s.graph.Edges {
|
||||
for v := range m {
|
||||
for range s.graph.Edges[u][v].data {
|
||||
count++
|
||||
}
|
||||
}
|
||||
}
|
||||
return count
|
||||
}
|
||||
|
||||
func (s *serviceMap) Reset() {
|
||||
s.entriesProcessed = 0
|
||||
s.graph = newDirectedGraph()
|
||||
}
|
||||
405
agent/pkg/servicemap/servicemap_test.go
Normal file
405
agent/pkg/servicemap/servicemap_test.go
Normal file
@@ -0,0 +1,405 @@
|
||||
package servicemap
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/suite"
|
||||
"github.com/up9inc/mizu/shared"
|
||||
tapApi "github.com/up9inc/mizu/tap/api"
|
||||
)
|
||||
|
||||
const (
|
||||
a = "aService"
|
||||
b = "bService"
|
||||
c = "cService"
|
||||
d = "dService"
|
||||
Ip = "127.0.0.1"
|
||||
Port = "80"
|
||||
)
|
||||
|
||||
var (
|
||||
TCPEntryA = &tapApi.TCP{
|
||||
Name: a,
|
||||
Port: Port,
|
||||
IP: fmt.Sprintf("%s.%s", Ip, a),
|
||||
}
|
||||
TCPEntryB = &tapApi.TCP{
|
||||
Name: b,
|
||||
Port: Port,
|
||||
IP: fmt.Sprintf("%s.%s", Ip, b),
|
||||
}
|
||||
TCPEntryC = &tapApi.TCP{
|
||||
Name: c,
|
||||
Port: Port,
|
||||
IP: fmt.Sprintf("%s.%s", Ip, c),
|
||||
}
|
||||
TCPEntryD = &tapApi.TCP{
|
||||
Name: d,
|
||||
Port: Port,
|
||||
IP: fmt.Sprintf("%s.%s", Ip, d),
|
||||
}
|
||||
TCPEntryUnresolved = &tapApi.TCP{
|
||||
Name: "",
|
||||
Port: Port,
|
||||
IP: Ip,
|
||||
}
|
||||
TCPEntryUnresolved2 = &tapApi.TCP{
|
||||
Name: "",
|
||||
Port: Port,
|
||||
IP: fmt.Sprintf("%s.%s", Ip, UnresolvedNodeName),
|
||||
}
|
||||
ProtocolHttp = &tapApi.Protocol{
|
||||
Name: "http",
|
||||
LongName: "Hypertext Transfer Protocol -- HTTP/1.1",
|
||||
Abbreviation: "HTTP",
|
||||
Macro: "http",
|
||||
Version: "1.1",
|
||||
BackgroundColor: "#205cf5",
|
||||
ForegroundColor: "#ffffff",
|
||||
FontSize: 12,
|
||||
ReferenceLink: "https://datatracker.ietf.org/doc/html/rfc2616",
|
||||
Ports: []string{"80", "443", "8080"},
|
||||
Priority: 0,
|
||||
}
|
||||
ProtocolRedis = &tapApi.Protocol{
|
||||
Name: "redis",
|
||||
LongName: "Redis Serialization Protocol",
|
||||
Abbreviation: "REDIS",
|
||||
Macro: "redis",
|
||||
Version: "3.x",
|
||||
BackgroundColor: "#a41e11",
|
||||
ForegroundColor: "#ffffff",
|
||||
FontSize: 11,
|
||||
ReferenceLink: "https://redis.io/topics/protocol",
|
||||
Ports: []string{"6379"},
|
||||
Priority: 3,
|
||||
}
|
||||
)
|
||||
|
||||
type ServiceMapDisabledSuite struct {
|
||||
suite.Suite
|
||||
|
||||
instance ServiceMap
|
||||
}
|
||||
|
||||
type ServiceMapEnabledSuite struct {
|
||||
suite.Suite
|
||||
|
||||
instance ServiceMap
|
||||
}
|
||||
|
||||
func (s *ServiceMapDisabledSuite) SetupTest() {
|
||||
s.instance = GetInstance()
|
||||
}
|
||||
|
||||
func (s *ServiceMapEnabledSuite) SetupTest() {
|
||||
s.instance = GetInstance()
|
||||
s.instance.SetConfig(&shared.MizuAgentConfig{
|
||||
ServiceMap: true,
|
||||
})
|
||||
}
|
||||
|
||||
func (s *ServiceMapDisabledSuite) TestServiceMapInstance() {
|
||||
assert := s.Assert()
|
||||
|
||||
assert.NotNil(s.instance)
|
||||
}
|
||||
|
||||
func (s *ServiceMapDisabledSuite) TestServiceMapSingletonInstance() {
|
||||
assert := s.Assert()
|
||||
|
||||
instance2 := GetInstance()
|
||||
|
||||
assert.NotNil(s.instance)
|
||||
assert.NotNil(instance2)
|
||||
assert.Equal(s.instance, instance2)
|
||||
}
|
||||
|
||||
func (s *ServiceMapDisabledSuite) TestServiceMapIsEnabledShouldReturnFalseByDefault() {
|
||||
assert := s.Assert()
|
||||
|
||||
enabled := s.instance.IsEnabled()
|
||||
|
||||
assert.False(enabled)
|
||||
}
|
||||
|
||||
func (s *ServiceMapDisabledSuite) TestGetStatusShouldReturnDisabledByDefault() {
|
||||
assert := s.Assert()
|
||||
|
||||
status := s.instance.GetStatus()
|
||||
|
||||
assert.Equal("disabled", status.Status)
|
||||
assert.Equal(0, status.EntriesProcessedCount)
|
||||
assert.Equal(0, status.NodeCount)
|
||||
assert.Equal(0, status.EdgeCount)
|
||||
}
|
||||
|
||||
func (s *ServiceMapDisabledSuite) TestNewTCPEntryShouldDoNothingWhenDisabled() {
|
||||
assert := s.Assert()
|
||||
|
||||
s.instance.NewTCPEntry(TCPEntryA, TCPEntryB, ProtocolHttp)
|
||||
s.instance.NewTCPEntry(TCPEntryC, TCPEntryD, ProtocolHttp)
|
||||
status := s.instance.GetStatus()
|
||||
|
||||
assert.Equal("disabled", status.Status)
|
||||
assert.Equal(0, status.EntriesProcessedCount)
|
||||
assert.Equal(0, status.NodeCount)
|
||||
assert.Equal(0, status.EdgeCount)
|
||||
}
|
||||
|
||||
// Enabled
|
||||
|
||||
func (s *ServiceMapEnabledSuite) TestServiceMapIsEnabled() {
|
||||
assert := s.Assert()
|
||||
|
||||
enabled := s.instance.IsEnabled()
|
||||
|
||||
assert.True(enabled)
|
||||
}
|
||||
|
||||
func (s *ServiceMapEnabledSuite) TestServiceMap() {
|
||||
assert := s.Assert()
|
||||
|
||||
// A -> B - HTTP
|
||||
s.instance.NewTCPEntry(TCPEntryA, TCPEntryB, ProtocolHttp)
|
||||
|
||||
nodes := s.instance.GetNodes()
|
||||
edges := s.instance.GetEdges()
|
||||
|
||||
// Counts for the first entry
|
||||
assert.Equal(1, s.instance.GetEntriesProcessedCount())
|
||||
assert.Equal(2, s.instance.GetNodesCount())
|
||||
assert.Equal(2, len(nodes))
|
||||
assert.Equal(1, s.instance.GetEdgesCount())
|
||||
assert.Equal(1, len(edges))
|
||||
//http protocol
|
||||
assert.Equal(1, edges[0].Count)
|
||||
assert.Equal(ProtocolHttp.Name, edges[0].Protocol.Name)
|
||||
|
||||
// same A -> B - HTTP, http protocol count should be 2, edges count should be 1
|
||||
s.instance.NewTCPEntry(TCPEntryA, TCPEntryB, ProtocolHttp)
|
||||
|
||||
nodes = s.instance.GetNodes()
|
||||
edges = s.instance.GetEdges()
|
||||
|
||||
// Counts for a second entry
|
||||
assert.Equal(2, s.instance.GetEntriesProcessedCount())
|
||||
assert.Equal(2, s.instance.GetNodesCount())
|
||||
assert.Equal(2, len(nodes))
|
||||
// edges count should still be 1, but http protocol count should be 2
|
||||
assert.Equal(1, s.instance.GetEdgesCount())
|
||||
assert.Equal(1, len(edges))
|
||||
// http protocol
|
||||
assert.Equal(2, edges[0].Count) //http
|
||||
assert.Equal(ProtocolHttp.Name, edges[0].Protocol.Name)
|
||||
|
||||
// same A -> B - REDIS, http protocol count should be 2 and redis protocol count should 1, edges count should be 2
|
||||
s.instance.NewTCPEntry(TCPEntryA, TCPEntryB, ProtocolRedis)
|
||||
|
||||
nodes = s.instance.GetNodes()
|
||||
edges = s.instance.GetEdges()
|
||||
|
||||
// Counts after second entry
|
||||
assert.Equal(3, s.instance.GetEntriesProcessedCount())
|
||||
assert.Equal(2, s.instance.GetNodesCount())
|
||||
assert.Equal(2, len(nodes))
|
||||
// edges count should be 2, http protocol count should be 2 and redis protocol should be 1
|
||||
assert.Equal(2, s.instance.GetEdgesCount())
|
||||
assert.Equal(2, len(edges))
|
||||
// http and redis protocols
|
||||
httpIndex := -1
|
||||
redisIndex := -1
|
||||
for i, e := range edges {
|
||||
if e.Protocol.Name == ProtocolHttp.Name {
|
||||
httpIndex = i
|
||||
continue
|
||||
}
|
||||
if e.Protocol.Name == ProtocolRedis.Name {
|
||||
redisIndex = i
|
||||
}
|
||||
}
|
||||
assert.NotEqual(-1, httpIndex)
|
||||
assert.NotEqual(-1, redisIndex)
|
||||
// http protocol
|
||||
assert.Equal(2, edges[httpIndex].Count)
|
||||
assert.Equal(ProtocolHttp.Name, edges[httpIndex].Protocol.Name)
|
||||
// redis protocol
|
||||
assert.Equal(1, edges[redisIndex].Count)
|
||||
assert.Equal(ProtocolRedis.Name, edges[redisIndex].Protocol.Name)
|
||||
|
||||
// other entries
|
||||
s.instance.NewTCPEntry(TCPEntryUnresolved, TCPEntryA, ProtocolHttp)
|
||||
s.instance.NewTCPEntry(TCPEntryB, TCPEntryUnresolved2, ProtocolHttp)
|
||||
s.instance.NewTCPEntry(TCPEntryC, TCPEntryD, ProtocolHttp)
|
||||
s.instance.NewTCPEntry(TCPEntryA, TCPEntryC, ProtocolHttp)
|
||||
|
||||
status := s.instance.GetStatus()
|
||||
nodes = s.instance.GetNodes()
|
||||
edges = s.instance.GetEdges()
|
||||
expectedEntriesProcessedCount := 7
|
||||
expectedNodeCount := 6
|
||||
expectedEdgeCount := 6
|
||||
|
||||
// Counts after all entries
|
||||
assert.Equal(expectedEntriesProcessedCount, s.instance.GetEntriesProcessedCount())
|
||||
assert.Equal(expectedNodeCount, s.instance.GetNodesCount())
|
||||
assert.Equal(expectedNodeCount, len(nodes))
|
||||
assert.Equal(expectedEdgeCount, s.instance.GetEdgesCount())
|
||||
assert.Equal(expectedEdgeCount, len(edges))
|
||||
|
||||
// Status
|
||||
assert.Equal("enabled", status.Status)
|
||||
assert.Equal(expectedEntriesProcessedCount, status.EntriesProcessedCount)
|
||||
assert.Equal(expectedNodeCount, status.NodeCount)
|
||||
assert.Equal(expectedEdgeCount, status.EdgeCount)
|
||||
|
||||
// Nodes
|
||||
aNode := -1
|
||||
bNode := -1
|
||||
cNode := -1
|
||||
dNode := -1
|
||||
unresolvedNode := -1
|
||||
unresolvedNode2 := -1
|
||||
var validateNode = func(node ServiceMapNode, entryName string, count int) int {
|
||||
// id
|
||||
assert.GreaterOrEqual(node.Id, 1)
|
||||
assert.LessOrEqual(node.Id, expectedNodeCount)
|
||||
|
||||
// entry
|
||||
// node.Name is the key of the node, key = entry.IP
|
||||
// entry.Name is the name of the service and could be unresolved
|
||||
assert.Equal(node.Name, node.Entry.IP)
|
||||
assert.Equal(Port, node.Entry.Port)
|
||||
assert.Equal(entryName, node.Entry.Name)
|
||||
|
||||
// count
|
||||
assert.Equal(count, node.Count)
|
||||
|
||||
return node.Id
|
||||
}
|
||||
|
||||
for _, v := range nodes {
|
||||
if strings.HasSuffix(v.Name, a) {
|
||||
aNode = validateNode(v, a, 5)
|
||||
continue
|
||||
}
|
||||
if strings.HasSuffix(v.Name, b) {
|
||||
bNode = validateNode(v, b, 4)
|
||||
continue
|
||||
}
|
||||
if strings.HasSuffix(v.Name, c) {
|
||||
cNode = validateNode(v, c, 2)
|
||||
continue
|
||||
}
|
||||
if strings.HasSuffix(v.Name, d) {
|
||||
dNode = validateNode(v, d, 1)
|
||||
continue
|
||||
}
|
||||
if v.Name == Ip {
|
||||
unresolvedNode = validateNode(v, UnresolvedNodeName, 1)
|
||||
continue
|
||||
}
|
||||
if strings.HasSuffix(v.Name, UnresolvedNodeName) {
|
||||
unresolvedNode2 = validateNode(v, UnresolvedNodeName, 1)
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
// Make sure we found all the nodes
|
||||
nodeIds := [...]int{aNode, bNode, cNode, dNode, unresolvedNode, unresolvedNode2}
|
||||
for _, v := range nodeIds {
|
||||
assert.NotEqual(-1, v)
|
||||
}
|
||||
|
||||
// Edges
|
||||
abEdge := -1
|
||||
uaEdge := -1
|
||||
buEdge := -1
|
||||
cdEdge := -1
|
||||
acEdge := -1
|
||||
var validateEdge = func(edge ServiceMapEdge, sourceEntryName string, destEntryName string, protocolName string, protocolCount int) {
|
||||
// source
|
||||
assert.Contains(nodeIds, edge.Source.Id)
|
||||
assert.LessOrEqual(edge.Source.Id, expectedNodeCount)
|
||||
assert.Equal(edge.Source.Name, edge.Source.Entry.IP)
|
||||
assert.Equal(sourceEntryName, edge.Source.Entry.Name)
|
||||
|
||||
// destination
|
||||
assert.Contains(nodeIds, edge.Destination.Id)
|
||||
assert.LessOrEqual(edge.Destination.Id, expectedNodeCount)
|
||||
assert.Equal(edge.Destination.Name, edge.Destination.Entry.IP)
|
||||
assert.Equal(destEntryName, edge.Destination.Entry.Name)
|
||||
|
||||
// protocol
|
||||
assert.Equal(protocolName, edge.Protocol.Name)
|
||||
assert.Equal(protocolCount, edge.Count)
|
||||
}
|
||||
|
||||
for i, v := range edges {
|
||||
if v.Source.Entry.Name == a && v.Destination.Entry.Name == b && v.Protocol.Name == "http" {
|
||||
validateEdge(v, a, b, ProtocolHttp.Name, 2)
|
||||
abEdge = i
|
||||
continue
|
||||
}
|
||||
if v.Source.Entry.Name == a && v.Destination.Entry.Name == b && v.Protocol.Name == "redis" {
|
||||
validateEdge(v, a, b, ProtocolRedis.Name, 1)
|
||||
abEdge = i
|
||||
continue
|
||||
}
|
||||
if v.Source.Entry.Name == UnresolvedNodeName && v.Destination.Entry.Name == a {
|
||||
validateEdge(v, UnresolvedNodeName, a, ProtocolHttp.Name, 1)
|
||||
uaEdge = i
|
||||
continue
|
||||
}
|
||||
if v.Source.Entry.Name == b && v.Destination.Entry.Name == UnresolvedNodeName {
|
||||
validateEdge(v, b, UnresolvedNodeName, ProtocolHttp.Name, 1)
|
||||
buEdge = i
|
||||
continue
|
||||
}
|
||||
if v.Source.Entry.Name == c && v.Destination.Entry.Name == d {
|
||||
validateEdge(v, c, d, ProtocolHttp.Name, 1)
|
||||
cdEdge = i
|
||||
continue
|
||||
}
|
||||
if v.Source.Entry.Name == a && v.Destination.Entry.Name == c {
|
||||
validateEdge(v, a, c, ProtocolHttp.Name, 1)
|
||||
acEdge = i
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
// Make sure we found all the edges
|
||||
for _, v := range [...]int{abEdge, uaEdge, buEdge, cdEdge, acEdge} {
|
||||
assert.NotEqual(-1, v)
|
||||
}
|
||||
|
||||
// Reset
|
||||
s.instance.Reset()
|
||||
status = s.instance.GetStatus()
|
||||
nodes = s.instance.GetNodes()
|
||||
edges = s.instance.GetEdges()
|
||||
|
||||
// Counts after reset
|
||||
assert.Equal(0, s.instance.GetEntriesProcessedCount())
|
||||
assert.Equal(0, s.instance.GetNodesCount())
|
||||
assert.Equal(0, s.instance.GetEdgesCount())
|
||||
|
||||
// Status after reset
|
||||
assert.Equal("enabled", status.Status)
|
||||
assert.Equal(0, status.EntriesProcessedCount)
|
||||
assert.Equal(0, status.NodeCount)
|
||||
assert.Equal(0, status.EdgeCount)
|
||||
|
||||
// Nodes after reset
|
||||
assert.Equal([]ServiceMapNode(nil), nodes)
|
||||
|
||||
// Edges after reset
|
||||
assert.Equal([]ServiceMapEdge(nil), edges)
|
||||
}
|
||||
|
||||
func TestServiceMapSuite(t *testing.T) {
|
||||
suite.Run(t, new(ServiceMapDisabledSuite))
|
||||
suite.Run(t, new(ServiceMapEnabledSuite))
|
||||
}
|
||||
@@ -3,22 +3,28 @@ package up9
|
||||
import (
|
||||
"bytes"
|
||||
"compress/zlib"
|
||||
"encoding/base64"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"github.com/romana/rlog"
|
||||
"github.com/up9inc/mizu/shared"
|
||||
"io/ioutil"
|
||||
"log"
|
||||
"mizuserver/pkg/database"
|
||||
"mizuserver/pkg/models"
|
||||
"mizuserver/pkg/utils"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"regexp"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/google/martian/har"
|
||||
basenine "github.com/up9inc/basenine/client/go"
|
||||
"github.com/up9inc/mizu/shared"
|
||||
"github.com/up9inc/mizu/shared/logger"
|
||||
tapApi "github.com/up9inc/mizu/tap/api"
|
||||
)
|
||||
|
||||
const (
|
||||
AnalyzeCheckSleepTime = 5 * time.Second
|
||||
SentCountLogInterval = 100
|
||||
)
|
||||
|
||||
type GuestToken struct {
|
||||
@@ -30,41 +36,24 @@ type ModelStatus struct {
|
||||
LastMajorGeneration float64 `json:"lastMajorGeneration"`
|
||||
}
|
||||
|
||||
func getGuestToken(url string, target *GuestToken) error {
|
||||
resp, err := http.Get(url)
|
||||
if err != nil {
|
||||
return err
|
||||
func GetRemoteUrl(analyzeDestination string, analyzeModel string, analyzeToken string, guestMode bool) string {
|
||||
if guestMode {
|
||||
return fmt.Sprintf("https://%s/share/%s", analyzeDestination, analyzeToken)
|
||||
}
|
||||
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)
|
||||
|
||||
return fmt.Sprintf("https://%s/app/workspaces/%s", analyzeDestination, analyzeModel)
|
||||
}
|
||||
|
||||
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 {
|
||||
func CheckIfModelReady(analyzeDestination string, analyzeModel string, analyzeToken string, guestMode bool) bool {
|
||||
statusUrl, _ := url.Parse(fmt.Sprintf("https://trcc.%s/models/%s/status", analyzeDestination, analyzeModel))
|
||||
|
||||
authHeader := getAuthHeader(guestMode)
|
||||
req := &http.Request{
|
||||
Method: http.MethodGet,
|
||||
URL: statusUrl,
|
||||
Header: map[string][]string{
|
||||
"Content-Type": {"application/json"},
|
||||
"Guest-Auth": {analyzeToken},
|
||||
authHeader: {analyzeToken},
|
||||
},
|
||||
}
|
||||
statusResp, err := http.DefaultClient.Do(req)
|
||||
@@ -79,17 +68,23 @@ func CheckIfModelReady(analyzeDestination string, analyzeModel string, analyzeTo
|
||||
return target.LastMajorGeneration > 0
|
||||
}
|
||||
|
||||
func getAuthHeader(guestMode bool) string {
|
||||
if guestMode {
|
||||
return "Guest-Auth"
|
||||
}
|
||||
|
||||
return "Authorization"
|
||||
}
|
||||
|
||||
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
|
||||
GuestMode bool
|
||||
SentCount int
|
||||
AnalyzedModel string
|
||||
AnalyzeToken string
|
||||
@@ -98,6 +93,7 @@ type AnalyzeInformation struct {
|
||||
|
||||
func (info *AnalyzeInformation) Reset() {
|
||||
info.IsAnalyzing = false
|
||||
info.GuestMode = true
|
||||
info.AnalyzedModel = ""
|
||||
info.AnalyzeToken = ""
|
||||
info.AnalyzeDestination = ""
|
||||
@@ -109,46 +105,181 @@ 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),
|
||||
RemoteUrl: GetRemoteUrl(analyzeInformation.AnalyzeDestination, analyzeInformation.AnalyzedModel, analyzeInformation.AnalyzeToken, analyzeInformation.GuestMode),
|
||||
IsRemoteReady: CheckIfModelReady(analyzeInformation.AnalyzeDestination, analyzeInformation.AnalyzedModel, analyzeInformation.AnalyzeToken, analyzeInformation.GuestMode),
|
||||
SentCount: analyzeInformation.SentCount,
|
||||
}
|
||||
}
|
||||
|
||||
func UploadEntriesImpl(token string, model string, envPrefix string, sleepIntervalSec int) {
|
||||
func SyncEntries(syncEntriesConfig *shared.SyncEntriesConfig) error {
|
||||
logger.Log.Infof("Sync entries - started")
|
||||
|
||||
var (
|
||||
token, model string
|
||||
guestMode bool
|
||||
)
|
||||
if syncEntriesConfig.Token == "" {
|
||||
logger.Log.Infof("Sync entries - creating anonymous token. env %s", syncEntriesConfig.Env)
|
||||
guestToken, err := createAnonymousToken(syncEntriesConfig.Env)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed creating anonymous token, err: %v", err)
|
||||
}
|
||||
|
||||
token = guestToken.Token
|
||||
model = guestToken.Model
|
||||
guestMode = true
|
||||
} else {
|
||||
token = fmt.Sprintf("bearer %s", syncEntriesConfig.Token)
|
||||
model = syncEntriesConfig.Workspace
|
||||
guestMode = false
|
||||
|
||||
logger.Log.Infof("Sync entries - upserting model. env %s, model %s", syncEntriesConfig.Env, model)
|
||||
if err := upsertModel(token, model, syncEntriesConfig.Env); err != nil {
|
||||
return fmt.Errorf("failed upserting model, err: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
modelRegex, _ := regexp.Compile("[A-Za-z0-9][-A-Za-z0-9_.]*[A-Za-z0-9]+$")
|
||||
if len(model) > 63 || !modelRegex.MatchString(model) {
|
||||
return fmt.Errorf("invalid model name, model name: %s", model)
|
||||
}
|
||||
|
||||
logger.Log.Infof("Sync entries - syncing. token: %s, model: %s, guest mode: %v", token, model, guestMode)
|
||||
go syncEntriesImpl(token, model, syncEntriesConfig.Env, syncEntriesConfig.UploadIntervalSec, guestMode)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func upsertModel(token string, model string, envPrefix string) error {
|
||||
upsertModelUrl, _ := url.Parse(fmt.Sprintf("https://trcc.%s/models/%s", envPrefix, model))
|
||||
|
||||
authHeader := getAuthHeader(false)
|
||||
req := &http.Request{
|
||||
Method: http.MethodPost,
|
||||
URL: upsertModelUrl,
|
||||
Header: map[string][]string{
|
||||
authHeader: {token},
|
||||
},
|
||||
}
|
||||
|
||||
response, err := http.DefaultClient.Do(req)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed request to upsert model, err: %v", err)
|
||||
}
|
||||
|
||||
// In case the model is not created (not 201) and doesn't exists (not 409)
|
||||
if response.StatusCode != 201 && response.StatusCode != 409 {
|
||||
return fmt.Errorf("failed request to upsert model, status code: %v", response.StatusCode)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
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 {
|
||||
logger.Log.Infof("Failed to get token, %s", err)
|
||||
return nil, err
|
||||
}
|
||||
return token, nil
|
||||
}
|
||||
|
||||
func getGuestToken(url string, target *GuestToken) error {
|
||||
resp, err := http.Get(url)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
logger.Log.Infof("Got token from the server, starting to json decode... status code: %v", resp.StatusCode)
|
||||
return json.NewDecoder(resp.Body).Decode(target)
|
||||
}
|
||||
|
||||
func syncEntriesImpl(token string, model string, envPrefix string, uploadIntervalSec int, guestMode bool) {
|
||||
analyzeInformation.IsAnalyzing = true
|
||||
analyzeInformation.GuestMode = guestMode
|
||||
analyzeInformation.AnalyzedModel = model
|
||||
analyzeInformation.AnalyzeToken = token
|
||||
analyzeInformation.AnalyzeDestination = envPrefix
|
||||
analyzeInformation.SentCount = 0
|
||||
|
||||
sleepTime := time.Second * time.Duration(sleepIntervalSec)
|
||||
// "http or grpc" filter indicates that we're only interested in HTTP and gRPC entries
|
||||
query := "http or grpc"
|
||||
|
||||
var timestampFrom int64 = 0
|
||||
logger.Log.Infof("Getting entries from the database")
|
||||
|
||||
for {
|
||||
timestampTo := time.Now().UnixNano() / int64(time.Millisecond)
|
||||
rlog.Infof("Getting entries from %v, to %v\n", timestampFrom, timestampTo)
|
||||
entriesArray := database.GetEntriesFromDb(timestampFrom, timestampTo)
|
||||
var connection *basenine.Connection
|
||||
var err error
|
||||
connection, err = basenine.NewConnection(shared.BasenineHost, shared.BaseninePort)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
if len(entriesArray) > 0 {
|
||||
data := make(chan []byte)
|
||||
meta := make(chan []byte)
|
||||
|
||||
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)
|
||||
defer func() {
|
||||
data <- []byte(basenine.CloseChannel)
|
||||
meta <- []byte(basenine.CloseChannel)
|
||||
connection.Close()
|
||||
}()
|
||||
|
||||
lastTimeSynced := time.Time{}
|
||||
|
||||
batch := make([]har.Entry, 0)
|
||||
|
||||
handleDataChannel := func(wg *sync.WaitGroup, connection *basenine.Connection, data chan []byte) {
|
||||
defer wg.Done()
|
||||
for {
|
||||
dataBytes := <-data
|
||||
|
||||
if string(dataBytes) == basenine.CloseChannel {
|
||||
return
|
||||
}
|
||||
rlog.Infof("About to upload %v entries\n", len(fullEntriesExtra))
|
||||
|
||||
body, jMarshalErr := json.Marshal(fullEntriesExtra)
|
||||
var dataMap map[string]interface{}
|
||||
err = json.Unmarshal(dataBytes, &dataMap)
|
||||
|
||||
var entry tapApi.Entry
|
||||
if err := json.Unmarshal([]byte(dataBytes), &entry); err != nil {
|
||||
continue
|
||||
}
|
||||
harEntry, err := utils.NewEntry(entry.Request, entry.Response, entry.StartTime, entry.ElapsedTime)
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
if entry.Source.Name != "" {
|
||||
harEntry.Request.Headers = append(harEntry.Request.Headers, har.Header{Name: "x-mizu-source", Value: entry.Source.Name})
|
||||
}
|
||||
if entry.Destination.Name != "" {
|
||||
harEntry.Request.Headers = append(harEntry.Request.Headers, har.Header{Name: "x-mizu-destination", Value: entry.Destination.Name})
|
||||
harEntry.Request.URL = utils.SetHostname(harEntry.Request.URL, entry.Destination.Name)
|
||||
}
|
||||
|
||||
// go's default marshal behavior is to encode []byte fields to base64, python's default unmarshal behavior is to not decode []byte fields from base64
|
||||
if harEntry.Response.Content.Text, err = base64.StdEncoding.DecodeString(string(harEntry.Response.Content.Text)); err != nil {
|
||||
continue
|
||||
}
|
||||
|
||||
batch = append(batch, *harEntry)
|
||||
|
||||
now := time.Now()
|
||||
if lastTimeSynced.Add(time.Duration(uploadIntervalSec) * time.Second).After(now) {
|
||||
continue
|
||||
}
|
||||
lastTimeSynced = now
|
||||
|
||||
body, jMarshalErr := json.Marshal(batch)
|
||||
batchSize := len(batch)
|
||||
if jMarshalErr != nil {
|
||||
analyzeInformation.Reset()
|
||||
rlog.Infof("Stopping analyzing")
|
||||
log.Fatal(jMarshalErr)
|
||||
logger.Log.Infof("Stopping sync entries")
|
||||
logger.Log.Fatal(jMarshalErr)
|
||||
}
|
||||
batch = make([]har.Entry, 0)
|
||||
|
||||
var in bytes.Buffer
|
||||
w := zlib.NewWriter(&in)
|
||||
@@ -156,33 +287,50 @@ func UploadEntriesImpl(token string, model string, envPrefix string, sleepInterv
|
||||
_ = w.Close()
|
||||
reqBody := ioutil.NopCloser(bytes.NewReader(in.Bytes()))
|
||||
|
||||
authHeader := getAuthHeader(guestMode)
|
||||
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},
|
||||
authHeader: {token},
|
||||
},
|
||||
Body: reqBody,
|
||||
}
|
||||
|
||||
if _, postErr := http.DefaultClient.Do(req); postErr != nil {
|
||||
analyzeInformation.Reset()
|
||||
rlog.Info("Stopping analyzing")
|
||||
log.Fatal(postErr)
|
||||
logger.Log.Info("Stopping sync entries")
|
||||
logger.Log.Fatal(postErr)
|
||||
}
|
||||
analyzeInformation.SentCount += len(entriesArray)
|
||||
rlog.Infof("Finish uploading %v entries to %s\n", len(entriesArray), GetTrafficDumpUrl(envPrefix, model))
|
||||
analyzeInformation.SentCount += batchSize
|
||||
|
||||
} else {
|
||||
rlog.Infof("Nothing to upload")
|
||||
if analyzeInformation.SentCount%SentCountLogInterval == 0 {
|
||||
logger.Log.Infof("Uploaded %v entries until now", analyzeInformation.SentCount)
|
||||
}
|
||||
}
|
||||
|
||||
rlog.Infof("Sleeping for %v...\n", sleepTime)
|
||||
time.Sleep(sleepTime)
|
||||
timestampFrom = timestampTo
|
||||
}
|
||||
|
||||
handleMetaChannel := func(wg *sync.WaitGroup, connection *basenine.Connection, meta chan []byte) {
|
||||
defer wg.Done()
|
||||
for {
|
||||
metaBytes := <-meta
|
||||
|
||||
if string(metaBytes) == basenine.CloseChannel {
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var wg sync.WaitGroup
|
||||
go handleDataChannel(&wg, connection, data)
|
||||
go handleMetaChannel(&wg, connection, meta)
|
||||
wg.Add(2)
|
||||
|
||||
connection.Query(query, data, meta)
|
||||
|
||||
wg.Wait()
|
||||
}
|
||||
|
||||
func UpdateAnalyzeStatus(callback func(data []byte)) {
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user