Compare commits

...

147 Commits

Author SHA1 Message Date
Igor Gov
a4f7e61a6e Fix proxy retries (#718) 2022-01-30 12:13:06 +02:00
RoyUP9
a5fef90781 Small fix in check resources (#716) 2022-01-30 09:36:22 +02:00
Igor Gov
70cef9dc4b Fix: show agent-image in config after generation (#717) 2022-01-30 09:25:56 +02:00
Igor Gov
0f3dd66d2d Experimental feature: elastic exporter (#713) 2022-01-30 09:22:13 +02:00
Igor Gov
5536e5bb44 Fixing minor bugs and remove unused dependency (#714) 2022-01-30 08:51:17 +02:00
M. Mert Yıldıran
3bab83754f Fix the interface conversion and index out of range errors in the Redis dissector (#710) 2022-01-27 22:58:43 +03:00
Andrey Pokhilko
d011478a74 OAS: series of small improvements (#700) 2022-01-27 16:17:21 +02:00
M. Mert Yıldıran
7fa1a191a6 TRA-4235 Move Basenine binary into the same agent image but run it as a separate container (#702)
* TRA-4235 Revert "Move Basenine binary into a separate container"

* Deploy the same agent image as a separate container for Basenine

Co-authored-by: Igor Gov <iggvrv@gmail.com>
2022-01-27 11:40:26 +03:00
gadotroee
65bb338ed6 Revert "TRA-4169 apply query by enter (#665)" (#703)
This reverts commit c098ff3323.
2022-01-26 19:32:49 +02:00
leon-up9
c098ff3323 TRA-4169 apply query by enter (#665) 2022-01-26 15:35:13 +02:00
gadotroee
19b4810ded Docker file best practices (#698)
(based on https://github.com/up9inc/mizu/pull/692/files)
2022-01-26 15:22:52 +02:00
Adam Kol
beb8363722 better looking code (#699) 2022-01-26 15:15:20 +02:00
Adam Kol
1eb67c69d9 Cypress: big UI test first version is ready (#689) 2022-01-26 13:18:25 +02:00
RoyUP9
be3375f797 Added post install connectivity check (#686) 2022-01-26 12:11:34 +02:00
Andrey Pokhilko
0c56c0f541 Have own HAR library code (#695) 2022-01-26 10:57:18 +02:00
Gustavo Massaneiro
843ac722c9 [TRA-4189] Traffic Volume in GB for General Status and telemetry (#696) 2022-01-26 10:49:42 +02:00
M. Mert Yıldıran
a9a61edd50 Add ARM64 and cross-compilation support to the agent image (#659)
* modified Dockerfile to work for both amd64 (Intel) and arm64 (M1)

* added changelog

* Update `Dockerfile` to have `ARCH` build argument

* Remove `docs/CHANGES.md`

* Upgrade the Basenine version from `v0.3.0` to `v0.4.6`

* Update `publish.yml` to have `ARCH` build argument

* Switch `BasenineImageRepo` to Docker Hub

* Have separate build arguments for `ARCH` and `GOARCH`

* Upgrade the Basenine version from `v0.4.6` to `v0.4.10`

* Oops forgot to update the 10th duplicated shell script

* Fix the oopsie and reduce duplications

* Fix `Dockerfile`

* Fix the incompatibility issue between Go plugins and gold linker in Alpine inside `Dockerfile`

* Fix `asm: xxhash_amd64.s:120: when dynamic linking, R15 is clobbered by a global variable access` error

* Update `Dockerfile` to have cross-compilation on an AMD64 machine

Also revert changes in the shell scripts

* Delete `debug.Dockerfile`

* Create a custom base (`debian:buster-slim` based) image for the shipped image

* Replace `mertyildiran/debian-pcap` with `up9inc/debian-pcap`

* Upgrade Basenine version to `v0.4.12`

* Use `debian:stable-slim` as the base

* Fix the indentation in the `Dockerfile`

* Update `publish.yml`

* Enable `publish.yml` for `feature/multiarch_build` branch

* Tag correctly and set `ARCH` Docker argument

* Remove the lines that are forgotten to be removed from the shell scripts

* Add `MizuAgentImageRepo` constant and use it as default `AgentImage` value

* Bring back `Set up Cloud SDK` step to `Build the CLI and publish` job

* Build ARM64 CLI for Linux as well

* Revert "Enable `publish.yml` for `feature/multiarch_build` branch"

This reverts commit d30be4c1f0.

* Revert Go 1.17 upgrade

* Remove `build_extensions_debug.sh` as well

* Make the `Dockerfile` to compile the agent statically

* Statically link the protocol extensions

* Fix `Dockerfile`

* Bring back `-s -w` flags

* Verify the signatures of the downloads in `dockcross/linux-arm64-musl`

* Revert modifications in some shell scripts

* Make the `BUILDARCH` and `TARGETARCH` separation in the `Dockerfile`

* Separate cross-compilation builder image into a separate repo named `up9inc/linux-arm64-musl-go-libpcap`

* Fill the shell script and specify the tag for `dockcross/linux-arm64-musl`

* Remove the unnecessary dependencies from `builder-native-base`

* Improve the comments in the `Dockerfile`

* Upgrade Basenine version to `v0.4.13`

* Fix `Dockerfile`

* Revert "Revert "Enable `publish.yml` for `feature/multiarch_build` branch""

This reverts commit 303e466bdc.

* Revert "Revert "Revert "Enable `publish.yml` for `feature/multiarch_build` branch"""

This reverts commit 0fe252bbdb.

* Remove `push-docker-debug` from the `Makefile`

* Rename `publish.yml` to `release.yml`

Co-authored-by: Alex Haiut <alex@up9.com>
2022-01-25 21:24:50 +03:00
RamiBerm
83c9194703 TRA-4202 role management (#688)
* WIP

* wip

* Update keto.yml, socket_routes.go, and 12 more files...

* fixes and docs

* Update api.js

* Update auth.go and api.js

* Update user_role_provider.go

* Update config_routes.go and api.js

* Update consts.go
2022-01-25 14:25:24 +02:00
Igor Gov
86edc91f4c Update helm chart to latest stable release (#694) 2022-01-25 13:14:13 +02:00
Gustavo Massaneiro
e30b52f528 [TRA-4190] ExecutionTime telemetry (#685) 2022-01-25 11:13:49 +02:00
lirazyehezkel
80418f1802 TRA-4205 React router (#684)
* React router

* removed comment
2022-01-25 10:45:07 +02:00
Gustavo Massaneiro
85edd6e5b0 updated debug.Dockerfile with the latest changes for the ui split build (#691) 2022-01-24 13:52:09 -03:00
RoyUP9
3067bf5eaf Fixed ui split (#690) 2022-01-24 18:11:45 +02:00
RoyUP9
d216c64154 Added support of ui split build (#682) 2022-01-24 10:34:50 +02:00
lirazyehezkel
39f0b74897 Split UI build (#681)
* Split ui build into build and build-ent folders

* remove is_standalone var

Co-authored-by: RoyUP9 <87927115+RoyUP9@users.noreply.github.com>
2022-01-24 10:02:35 +02:00
RoyUP9
569f8ae143 Added post install check (#630) 2022-01-23 16:52:58 +02:00
gadotroee
bcea6cdc49 Update publish.yml (#679) 2022-01-23 16:18:50 +02:00
gadotroee
1a2697dd0d Images to docker hub (#676) 2022-01-23 12:41:47 +02:00
Gustavo Massaneiro
2638672603 updated debug.Dockerfile to use node 16 (#675) 2022-01-22 15:33:48 -03:00
M. Mert Yıldıran
a702f0f93a Merge repeated keys in query parameters into arrays (#674) 2022-01-22 20:09:31 +03:00
lirazyehezkel
18d90cdf36 Support node 16 (#673)
* upgrade node-sass

* upgrade axios

* update dockerfile

Co-authored-by: gadotroee <55343099+gadotroee@users.noreply.github.com>
2022-01-20 18:41:00 +02:00
AmitUp9
9c665e664b TRA-4158 login touch ups (#667)
* logos switched and put in place

* removed unnessesry commas

* fix warning

* remove dev code

Co-authored-by: gadotroee <55343099+gadotroee@users.noreply.github.com>
2022-01-20 18:29:26 +02:00
leon-up9
54ea2afe71 TRA-4176 plus icon is not visible (#664)
* flipped button

* remove comment

* edit style

* overflow for .resolvedName removed

Co-authored-by: Leon <>
Co-authored-by: gadotroee <55343099+gadotroee@users.noreply.github.com>
2022-01-20 18:21:50 +02:00
RamiBerm
50c89e6245 Revert "TRA-4157 fix ws auth (#669)" (#671)
This reverts commit 676e50b0b1.
2022-01-20 16:29:23 +02:00
RamiBerm
676e50b0b1 TRA-4157 fix ws auth (#669)
* Update socket_routes.go, user_controller.go, and 2 more files...

* Update user_controller.go

* Switch to http-only cookies for more security
2022-01-20 14:10:25 +02:00
gadotroee
6bab381280 Make kratos image configurable (#670) 2022-01-20 13:48:02 +02:00
gadotroee
27dee4e09b TRA-4193 - Try port forward if proxy is not available (#662) 2022-01-20 11:33:00 +02:00
M. Mert Yıldıran
b31af7214b TRA-4140 Fix HTTP/1.0 is recognized as HTTP/1.1 (#666) 2022-01-20 11:02:21 +03:00
Gustavo Massaneiro
d5fd2ff1da Service Map (#623)
* debug builds and gcflags

* update dockerfile for debug

* service map routes and controller

* service map graph structure

* service map interface and new methods

* adding service map edges from mizu entries

* new service map count methods

* implementing the status endpoint

* ServiceMapResponse and ServiceMapEdge models

* service map get endpoint logic

* reset logic and endpoint

* fixed service map get status

* improvements to graph node structure

* front-end implementation and service map buttons

* new render endpoint to render the graph in real time

* spinner sass

* new ServiceMapModal component

* testing react-force-graph-2d lib

* Improvements to service map graph structure, added node id and updated edge source/destination type

* Revert "testing react-force-graph-2d lib"

This reverts commit 1153938386.

* testing react-graph-vis lib

* updated to work with react-graph-vis lib

* removed render endpoint

* go mod tidy

* serviceMap config flag

* using the serviceMap config flag

* passing mizu config to service map as a dependency

* service map tests

* Removed print functions

* finished service map tests

* new service property

* service map controller tests

* moved service map reset button to service map modal
reset closes the modal

* service map modal refresh button and logic

* reset button resets data and refresh

* service map modal close button

* node size/edge size based on the count value
edge color based on protocol

* nodes and edges shadow

* enabled physics to avoid node overlap, changed kafka protocol color to dark green

* showing edges count values and fixed bidirectional edges overlap

* go mod tidy

* removed console.log

* Using the destination node protocol instead of the source node protocol

* Revert "debug builds and gcflags"
Addressed by #624 and #626

This reverts commit 17ecaece3e.

* Revert "update dockerfile for debug"
Addressed by #635

This reverts commit 5dfc15b140.

* using the entire tap Protocol struct instead of only the protocol name

* using the backend protocol background color for node colors

* fixed test, the node list order can change

* re-factoring to get 100% coverage

* using protocol colors just for edges

* re-factored service map to use TCP Entry data. Node key is the entry ip-address instead of the name

* fallback to ip-address when entry name is unresolved

* re-factored front-end

* adjustment to main div style

* added support for multiple protocols for the same edge

* using the item protocol instead of the extension variable

* fixed controller tests

* displaying service name and ip-address on graph nodes

* fixed service map test, we cannot guarantee the slice order

* auth middleware

* created a new pkg for the service map

* re-factoring

* re-factored front-end

* reverting the import order as previous

* Aligning with other UI feature flags handling

* we don't need to get the status anymore, we have window["isServiceMapEnabled"]

* small adjustments

* renamed from .tsx to .ts

* button styles and minor improvements

* moved service map modal from trafficPage to app component

Co-authored-by: Igor Gov <igor.govorov1@gmail.com>
2022-01-19 15:27:12 -03:00
Igor Gov
7477f867f9 TRA-4122 - OAS dialog 2 (#637)
* parent d97d481392
author Amit Fainholts <amit@up9.com> 1641398982 +0200
committer Igor Gov <igor.govorov1@gmail.com> 1642070154 +0200

init

* revert

* small fixes

* api request to oas added

* redoc version downgraded and remove redundant package-lock

* remove redundant package json

* redoc updated to latest

* libraries updated to prevent vulnerabilities

* pr fixes

* remove ! from global var

* update useEffect in OasModal and other pr fixes

* change errors messages

Co-authored-by: Amit Fainholts <amit@up9.com>
Co-authored-by: Igor Gov <igor.govorov1@gmail.com>
Co-authored-by: lirazyehezkel <61656597+lirazyehezkel@users.noreply.github.com>
2022-01-19 13:06:39 +02:00
Adam Kol
cc4638afe6 Cypress: two new tests --> IgnoredUserAgents and RegexMasking (#663) 2022-01-18 17:32:50 +02:00
M. Mert Yıldıran
acf3894824 Fix EntryItem border on heading mode (#661) 2022-01-18 17:37:58 +03:00
leon-up9
6235217ead Bug/UI/tra 4169 apply qury by enter (#660)
* add shortcuts listeners and config

* key added

* listen for shortcut on input

* refactoring listensing to Enter Press

* comment for support

Co-authored-by: Leon <>
2022-01-18 12:08:53 +02:00
Adam Kol
c8a3033f87 Cypress: fix redact tests (#658) 2022-01-17 12:57:12 +02:00
Adam Kol
6b4bcc8abd Cypress: refactor for Redact and NoRedact tests (#656) 2022-01-17 10:43:39 +02:00
RoyUP9
5ca3107422 Added build ui to pr validation flow (#655) 2022-01-16 17:43:18 +02:00
lirazyehezkel
ce477095fd Mizu recoil fix (#654) 2022-01-16 15:44:08 +02:00
lirazyehezkel
5fed5808d2 TRA-4159 Mizu state management (#631)
* initiate recoil state management with entPage and tappingStatus

* first recoil selector

* insert entries and focusedEntryId into recoil

* ws connection, entry data

* manage query by recoil

* identifier for cypress

* conflicts fix

* css fix

* cr fixes

Co-authored-by: gadotroee <55343099+gadotroee@users.noreply.github.com>
2022-01-16 15:27:09 +02:00
Igor Gov
5c59cd643a Adding badges: latest release, license, slack (#653) 2022-01-16 14:58:18 +02:00
Adam Kol
aae03c52e9 UI important identifier (#652) 2022-01-16 14:17:18 +02:00
Alex Haiut
dacdb69164 added CHANGELOG and updated release README template (#650) 2022-01-16 13:15:19 +02:00
Igor Gov
e15eb71b77 Fix: no panic on failure to sync entries to up9.app (#648) 2022-01-16 11:50:40 +02:00
RoyUP9
ae1bcf4c0c Added api server timeout env for install and tap (#647) 2022-01-16 11:48:22 +02:00
Adam Kol
20d69228d3 Cypress: new Redact and NoRedact tests (#618) 2022-01-16 09:57:14 +02:00
M. Mert Yıldıran
59fbe4c479 Show HTTP path segments as a list of strings in the right pane (#641)
* Show HTTP path segments as a list of strings in the right pane

* Use better variable names
2022-01-16 09:43:52 +03:00
M. Mert Yıldıran
4db8e8902b Add support of displaying nested data structures of Kafka in the right-pane (#643)
* Handle nested `topicData` in `representProduceRequest`

* Handle nested `topics` in `representCreateTopicsRequest` and `representCreateTopicsResponse`

* Handle nested `responses` in `representProduceResponse`

* Handle nested `topics` in `representFetchRequest` and nested `responses` in `representFetchResponse`

* Introduce `ignoreKeys` argument to `representMapAsTable` and ignore the keys based on that argument

* Bring back the `nil` checks
2022-01-16 09:36:29 +03:00
M. Mert Yıldıran
f5bacbd1ea Display Redis value as EntryBodySection (#644) 2022-01-15 20:46:10 +03:00
M. Mert Yıldıran
b94098fea6 Sort key-value pairs on client-side in EntryTableSection (#645) 2022-01-15 20:43:22 +03:00
RamiBerm
92c7e2b91d Install page on enter event handler (#640) 2022-01-13 17:07:47 +02:00
Nimrod Gilboa Markevich
d97d481392 Mark Linkerd as beta (#636) 2022-01-13 10:47:30 +02:00
Igor Gov
8963630e9e Experimental feature: OAS Generator (#632) 2022-01-13 09:34:55 +02:00
Gustavo Massaneiro
610b9efdb0 updated dockerfile used for debug (#635) 2022-01-13 01:54:59 -03:00
Igor Gov
0e5611b7e9 FE setting experimental feature flags (#633) 2022-01-12 18:05:12 +02:00
RoyUP9
26a9c31d1e Extracted agent status to consistent volume (#628) 2022-01-12 16:03:50 +02:00
Nimrod Gilboa Markevich
68c4ee9a4f Replace source IP with X-Forwarded-For in http, if exists (#606)
Support source IP resolving for HTTP traffic in Istio service mesh.

If X-Forwarded-For HTTP request header is present, replace the source address with the left-most address in X-Forwarded-For (earliest in Envoy implementation of this header).

This allows Mizu to resolve source IPs in Istio service meshes, if the use_remote_address option is on. Added instructions on how to turn it on.
2022-01-12 14:22:02 +02:00
Igor Gov
bfbbc27e62 Adding experimental feature flags (#627) 2022-01-12 09:33:41 +02:00
Igor Gov
e2df973fe6 Adding make file debug functionalities (#626) 2022-01-12 09:16:38 +02:00
Igor Gov
656809512b Adding make file debug functionalities (#624) 2022-01-12 09:04:13 +02:00
RoyUP9
b96542a8ed Refactor to agent status (#622) 2022-01-11 20:01:39 +02:00
RoyUP9
a55f51f0e7 Extracted tap config to consistent volume (#617) 2022-01-11 13:44:41 +02:00
M. Mert Yıldıran
f102079e3c Fix the build error (#621) 2022-01-11 13:05:58 +03:00
M. Mert Yıldıran
80e881fee2 Upgrade Basenine to 0.3.0, do a refactor to enable redact helper and update the cheatsheet (#614)
* Upgrade Basenine version from `0.2.26` to `0.3.0`

* Remove `Summarize` method from `Dissector` interface and refactor the data structures in `tap/api/api.go`

* Rename `MizuEntry` to `Entry` and `BaseEntryDetails` to `BaseEntry`

* Populate `ContractStatus` field as well

* Update the cheatsheet

* Upgrade the Basenine version in the helm chart as well

* Remove a forgoten `console.log` call
2022-01-11 12:51:30 +03:00
M. Mert Yıldıran
1ba444dba1 Fix the eslint warnings (#620) 2022-01-11 12:30:35 +03:00
M. Mert Yıldıran
0b7d535a81 Make the scrollable reference snap to the bottom after clicking pause/play buttons (resuming streaming) (#619) 2022-01-11 12:09:10 +03:00
Adam Kol
9d0c2a693e Cypress: new TapRegex test is ready + pageObjects (#611)
* introducing pageObjects

* fixes

* pretty code

Co-authored-by: lirazyehezkel <61656597+lirazyehezkel@users.noreply.github.com>
2022-01-11 10:56:27 +02:00
M. Mert Yıldıran
2b2c7687a1 Fix the CSS issue on tooltip in case of right-pane is scrolled down (#598) 2022-01-11 11:44:16 +03:00
M. Mert Yıldıran
4708998f54 Fix the CSS issue on EntryItem Queryable src.name (#602) 2022-01-11 11:38:13 +03:00
Nimrod Gilboa Markevich
7570df3828 Document ways to edit permissions (#616)
Document the ways by which users can overwrite / add to Mizu's permissions.
2022-01-10 15:36:16 +02:00
RamiBerm
44c8908358 TRA-4077 login design (#608)
* fix /ws auth

* remove useless success toasts on login/signup

* WIP

* log in page

* Update InstallPage.tsx

* Update App.sass, SettingModal.tsx, and 2 more files...

* Update socket_routes.go

* Merge branch 'feature/TRA-4077_login_design' of git@github.com:up9inc/mizu.git

* Update LoginPage.tsx

* Update GlobalKeydownTrigger.tsx and LoginPage.tsx

* Update GlobalKeydownTrigger.tsx

* Revert "Update GlobalKeydownTrigger.tsx"

This reverts commit 6fa579becd.

* Revert "Update GlobalKeydownTrigger.tsx and LoginPage.tsx"

This reverts commit bdc0425353.

* Update LoginPage.tsx
2022-01-10 11:54:05 +02:00
Igor Gov
0ca5482946 Helm chart for installing mizu standalone (#609) 2022-01-10 10:26:57 +02:00
lirazyehezkel
c20f74f582 mizu enterprise ui bugs (#597) 2022-01-10 10:01:52 +02:00
Adam Kol
a2eff2654d Cypress: new 'Multiple Namespaces' test (#600) 2022-01-09 16:47:35 +02:00
M. Mert Yıldıran
9771d689ca Fix the acceptance tests and a typo in CONFIGURATION.md (#610)
* Enable acceptance tests

* Fix the acceptance tests and a typo in `CONFIGURATION.md`

* Include the container name into the log fetching function

* Duplicate the fix for the logs test

* Revert "Enable acceptance tests"

This reverts commit c10a67c293.
2022-01-09 17:38:41 +03:00
Nimrod Gilboa Markevich
5a044875d3 Rename Istio to service mesh (#605)
- Rename --istio flag to the more general --service-mesh
- Rename internal variables, consts and structures to reflect this conceptual change
- Update the docs accordingly
2022-01-09 13:21:14 +02:00
RoyUP9
c49c344c2a Added kubernetes provider singleton (#599) 2022-01-09 10:50:58 +02:00
M. Mert Yıldıran
e3e9681110 Move Basenine binary into a separate container (#603)
* Move Basenine binary into a separate container

* Set `WorkingDir` to `shared.DataDirPath` in the `basenine` container

* Use `consts.go` to set the Basenine image and port

* Bring back the `net-wait-go` usage to prevent startup failures
2022-01-09 11:18:34 +03:00
RoyUP9
adf2274213 Added api server running check in install command (#596) 2022-01-06 14:58:28 +02:00
lirazyehezkel
cb5344090a TRA-4089 Mizu enterprise frame (#594)
* ent app

* mizu ent frame

* apis

* design settings modal by Javier

* fix warnings

* fix warnings

* text change

* redirect after logout

* cr fixes
2022-01-06 14:50:50 +02:00
RamiBerm
2110afc514 TRA-4075 fix logout (#595) 2022-01-06 14:01:14 +02:00
RoyUP9
2c4a5d06ab Init tapped namespaces map (#593) 2022-01-06 13:21:58 +02:00
Igor Gov
14650aa3f4 Updating mizu base docker image alpine 3.14 -> 3.15 (#591)
Co-authored-by: Igor Gov <igor.govorov1@gmail.com>
2022-01-06 12:47:50 +02:00
RamiBerm
5d36d9184d fix enterprise cors (#592) 2022-01-06 12:39:03 +02:00
RamiBerm
63122cb0a7 TRA-4147 simpler kratos password policy (#590) 2022-01-06 12:15:46 +02:00
RoyUP9
b88bdb90f6 Fixed standalone config, small refactor (#589) 2022-01-06 12:04:58 +02:00
RamiBerm
833d08bb40 TRA-4075 integrate kratos user management (#583)
* WIP

* WIP

* WIP

* WIP

* Update App.tsx and Header.tsx

* Update createResources.go, provider.go, and 2 more files...

* WIP

* fix eof newlines

* Fix ts imports, add readiness probe to kratos to prevent mizu being used while kratos isnt ready

* cleaned code

* fix install create namespace

* Update package-lock.json

* Update provider.go

* Update provider.go

* Update provider.go

* Update install_controller.go

* Update kratos.yml

* Update start.sh

* Update provider.go

* Update provider.go

* Update main.go, socket_routes.go, and 8 more files...

* Update App.tsx

* Update installRunner.go

* Update App.tsx
2022-01-05 17:50:27 +02:00
RoyUP9
8e20ca797b Added endpoint for getting tapped namespaces (#587) 2022-01-05 11:15:42 +02:00
RoyUP9
bf68689212 renamed standalone route to config route (#586) 2022-01-04 18:46:03 +02:00
Adam Kol
dbc18b9648 Cypress: first-time-using cypress files + new GuiPort test on the UI (#584) 2022-01-04 18:02:02 +02:00
lirazyehezkel
282baee881 is standalone variable (#585) 2022-01-04 17:51:07 +02:00
RoyUP9
02b2cbaa03 Added update config route for install mode (#581) 2022-01-04 17:27:14 +02:00
Nimrod Gilboa Markevich
2834ae1e85 Support custom RBAC resources (#572)
Motivation: Allow users to change the default RBAC resources (ServiceAccount, ClusterRole, ClusterRoleBinding, Role and RoleBinding) without having Mizu delete them every run.

Adds app.kubernetes.io/created-by and app.kubernetes.io/managed-by labels to all resources.
The value of app.kubernetes.io/created-by is either mizu-cli or mizu-agent.
The value of app.kubernetes.io/managed-by is mizu.
When Mizu cleans resources (ctrl-c in tap cmd or mizu clean cmd) it removes all RBAC resources that have managed-by=mizu, and only those.

A user may have a ClusterRole named mizu-clusterrole. If it doesn't have the label app.kubernetes.io/managed-by=mizu, then Mizu won't overwrite it and won't delete it.
Other resources (deployments, services etc.) are always removed, regardless of their labels.
2022-01-04 09:48:22 +02:00
Igor Gov
9c45166075 Fixing Mizu url in readme (#580) 2022-01-02 10:42:10 +02:00
RoyUP9
482036182b Added setting ui mode - enterprise or normal (#575) 2021-12-30 14:58:53 +02:00
M. Mert Yıldıran
f535719ddd Use wss:// instead of ws:// in case of HTTPS (#573) 2021-12-30 06:59:05 +02:00
Igor Gov
da2aaa9bd8 API server provider readiness check with echo (#570) 2021-12-29 12:21:12 +02:00
lirazyehezkel
9ada330fcf Mizu enterprise preparing (#567) 2021-12-29 11:49:44 +02:00
Igor Gov
84f7107a33 API server provider readiness check with echo (#569) 2021-12-29 10:50:41 +02:00
RoyUP9
de046c65b0 Mizu install mode (#566) 2021-12-28 15:44:37 +02:00
Igor Gov
b039c2abad Fix: [EKS] server could not find the requested resourceevents.events.k8s.io (#565) 2021-12-28 13:24:51 +02:00
RoyUP9
1e1b5f0c0f move mizu agent config from config (#563) 2021-12-28 09:59:21 +02:00
RoyUP9
15895d5947 Changed sync tappers to start only when sync tappers config is true (#562) 2021-12-27 17:19:00 +02:00
RamiBerm
2da7c0f0ed TRA-4065 fix mizu tapping (#561)
* Update main.go and passive_tapper.go

* Update main.go
2021-12-27 15:11:36 +02:00
RoyUP9
06785ec877 fixed acceptance test go.sum (#560) 2021-12-27 14:45:42 +02:00
RamiBerm
cfe9e863b7 TRA-4065 support inflight tap target update (#556)
* WIP

* WIP

* Update main.go

* Update main.go and passive_tapper.go

* Update passive_tapper.go

* Update passive_tapper.go

* Update passive_tapper.go

* Update passive_tapper.go
2021-12-27 11:50:34 +02:00
RoyUP9
fd97a09624 extracted create and clean resources from tap runner (#557) 2021-12-27 11:32:48 +02:00
M. Mert Yıldıran
52ce6044ea Add 4px padding to the top of endpointServiceContainer (#555) 2021-12-26 16:20:06 +03:00
M. Mert Yıldıran
3a83531590 Use react-lowlight to highlight and json-beautify, xml-formatter to prettify the EntryBodySection (#554)
* Use `react-lowlight` to highlight and `json-beautify` to prettify the `EntryBodySection`

* Bring back the line numbers

* Make the Base64 decoding optional but make it `true` by default

* Align line numbers to right and don't have a dot character

* Make line numbers semi transparent

* Make `markers` code more elegant

* Prettify XML as well
2021-12-26 16:12:17 +03:00
M. Mert Yıldıran
e358aa4c8f Remove TCP related logging to reduce the amount of logs on DEBUG level (#553) 2021-12-26 15:10:02 +03:00
M. Mert Yıldıran
03b1313a9f Don't use Queryable for the Mime type and Encoding fields but use it directly in CollapsibleTitle suffixed components and only enable it for EntryBodySection (#550)
Co-authored-by: gadotroee <55343099+gadotroee@users.noreply.github.com>
2021-12-23 17:45:51 +03:00
M. Mert Yıldıran
32dfe40e18 Make EntryItem more responsive (#552) 2021-12-23 10:25:39 +03:00
M. Mert Yıldıran
12aaa762f6 Fix React Hook useEffect has a missing dependency: 'handleQueryChange' warning (#551) 2021-12-22 20:23:21 +03:00
David Levanon
a75bac181d support linkerd (#547)
* support linkerd - initial commit

* renaming readEnvironmentVariable
2021-12-20 13:57:58 +02:00
gadotroee
2d78785558 Fix acceptance tests (after pods status request change) (#545) 2021-12-19 13:46:14 +02:00
Igor Gov
cba0c682e5 Report pods "isTapped" to FE (#535) 2021-12-19 13:03:53 +02:00
RamiBerm
791f762803 TRA-4062 remove duplicate target pod print (#543) 2021-12-19 11:52:43 +02:00
RamiBerm
d316589bda TRA-4060 fix proxying error (#542) 2021-12-19 11:24:17 +02:00
Igor Gov
36828bcc1d Bringing back the pod watch api server events to make acceptance test more stable (#541) 2021-12-19 09:29:09 +02:00
M. Mert Yıldıran
23332639d0 Fix a JavaScript error in case of null attribute and an interface conversion error in the API server (#540) 2021-12-17 17:26:36 +03:00
Igor Gov
3b69508581 Fix: acceptance tests (#539) 2021-12-16 14:38:32 +02:00
Igor Gov
397d3931ad Fix health tapper status count (#538) 2021-12-16 12:39:45 +02:00
Igor Gov
4de795e463 Alert on acceptance tests failure (#537) 2021-12-16 11:04:36 +02:00
David Levanon
ab029f4394 Send pod info to tapper (#532) 2021-12-16 10:51:03 +02:00
M. Mert Yıldıran
a06d5cfbde Update the "Started listening at" timestamp and queriedTotal state based on database truncation (#533) 2021-12-16 10:46:50 +02:00
M. Mert Yıldıran
b565492eba Change the connection status text and the toggle connection behavior (#534) 2021-12-16 10:42:17 +02:00
Igor Gov
f266f32fea Decreased API server boot time (#536) 2021-12-16 10:36:03 +02:00
Igor Gov
90c210452d API server stores tappers status (#531) 2021-12-15 14:52:49 +02:00
M. Mert Yıldıran
0a915b3fe7 Fix the CSS issue in Queryable component for src.name field on heading mode (#530) 2021-12-15 12:28:46 +03:00
M. Mert Yıldıran
a830bbe023 Fix the glitch (#529)
* Fix the glitch

* Bring back the functionality to "Fetch old records" and "Snap to bottom" buttons
2021-12-15 12:26:18 +03:00
Alex Haiut
f1ba397543 make description of mizu config options public (#527) 2021-12-14 20:03:26 +02:00
M. Mert Yıldıran
4e17ac5654 Remove unnecessary fields and split service into src.name and dst.name (#525)
* Remove unnecessary fields and split `service` into `src.name` and `dst.name`

* Don't fall back to IP address but instead display `[Unresolved]` text

* Fix the CSS issues in the plus icon position and replace the separator `->` text with `SwapHorizIcon`
2021-12-14 11:36:02 +03:00
M. Mert Yıldıran
d274db2d87 Fix the CSS issues in queryable vertical protocol element (#526) 2021-12-12 19:38:14 +03:00
M. Mert Yıldıran
0a2aacfb02 Include milliseconds information into the timestamps in the UI (#524)
* Include milliseconds information into the timestamps in the UI

* Upgrade Basenine version from `0.2.16` to `0.2.17`

* Increase the `width` of timestamp
2021-12-10 18:03:17 +03:00
Igor Gov
3c64c1c7ca Report the platform in telemtry (#523)
Co-authored-by: Igor Gov <igor.govorov1@gmail.com>
2021-12-09 13:12:15 +02:00
Igor Gov
005f000ef6 Disable version check for devs (#522) 2021-12-09 12:11:36 +02:00
M. Mert Yıldıran
1ef3778051 Add type switch for Base field of MizuEntry (#520) 2021-12-08 16:53:57 +03:00
M. Mert Yıldıran
9f1e311689 TRA-4017 Bring back getOldEntries method using fetch API and always start streaming from now (#518)
* Bring back `getOldEntries` method using fetch API

* Determine no more data on top based on `leftOff` value

* Remove `entriesBuffer` state

* Always open WebSocket with some `leftOff` value

* Rename `leftOff` state to `leftOffBottom`

* Don't set the `focusedEntryId` through WebSocket if the WebSocket is closed

* Call `setQueriedCurrent` with addition

* Close WebSocket upon reaching to top

* Open WebSocket upon snapping to bottom

* Close the WebSocket on snap broken event instead

* Set queried current value to zero upon filter submit

* Upgrade `react-scrollable-feed-virtualized` version and use `scrollToIndex` function

* Change the footer text format

* Improve no more data top logic

* Fix `closeWebSocket()` call logic in `onSnapBrokenEvent` and handle `data.meta` being `null` in `getOldEntries`

* Fix the issues around fetching old records

* Clean up `EntriesList.module.sass`

* Decrement initial `leftOffTop` value by `2`

* Fix the order of `incomingEntries` in `getOldEntries`

* Request `leftOffTop - 1` from `fetchEntries`

* Limit the front-end total entries fetched through WebSocket count to `10000`

* Lose the UI performance gain that's provided by #452

* Revert "Fix the selected entry behavior by propagating the `focusedEntryId` through WebSocket (before #452) TRA-3983 (#513)"

This reverts commit 873f252544.

* Fix the issues caused by 09371f141f

* Upgrade Basenine version from `0.2.13` to `0.2.14`

* Upgrade Basenine version from `0.2.14` to `0.2.15`

* Fix the condition of "Fetch old records" button visibility

* Upgrade Basenine version from `0.2.15` to `0.2.16` and fix the UI code related to fetching old records

* Make `newEntries` constant
2021-12-08 15:19:35 +03:00
M. Mert Yıldıran
9aaf18842b Fix the CSS issue in Queryable inside EntryViewLine (#521) 2021-12-07 14:15:49 +03:00
299 changed files with 70515 additions and 7427 deletions

View File

@@ -2,7 +2,6 @@
.dockerignore
.editorconfig
.gitignore
**/.env*
Dockerfile
Makefile
LICENSE

View File

@@ -30,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 }}

View File

@@ -44,3 +44,18 @@ jobs:
- name: Build Agent
run: make agent
build-ui:
name: Build UI
runs-on: ubuntu-latest
steps:
- name: Set up Node 16
uses: actions/setup-node@v2
with:
node-version: '16'
- name: Check out code into the Go module directory
uses: actions/checkout@v2
- name: Build UI
run: make ui

View File

@@ -1,90 +0,0 @@
name: publish
on:
push:
branches:
- 'develop'
- 'main'
concurrency:
group: mizu-publish-${{ github.ref }}
cancel-in-progress: true
jobs:
docker:
runs-on: ubuntu-latest
steps:
- name: Set up Go 1.16
uses: actions/setup-go@v2
with:
go-version: '1.16'
- name: Checkout
uses: actions/checkout@v2
- name: Set up Cloud SDK
uses: google-github-actions/setup-gcloud@master
with:
service_account_key: ${{ secrets.GCR_JSON_KEY }}
export_default_credentials: true
- uses: haya14busa/action-cond@v1
id: condval
with:
cond: ${{ github.ref == 'refs/heads/main' }}
if_true: "minor"
if_false: "patch"
- name: Auto Increment Semver Action
uses: MCKanpolat/auto-semver-action@1.0.5
id: versioning
with:
releaseType: ${{ steps.condval.outputs.value }}
github_token: ${{ secrets.GITHUB_TOKEN }}
- name: Get version parameters
shell: bash
run: |
echo "##[set-output name=build_timestamp;]$(echo $(date +%s))"
echo "##[set-output name=branch;]$(echo ${GITHUB_REF#refs/heads/})"
id: version_parameters
- name: Get base image name
shell: bash
run: echo "##[set-output name=image;]$(echo gcr.io/up9-docker-hub/mizu/${GITHUB_REF#refs/heads/})"
id: base_image_step
- name: Docker meta
id: meta
uses: crazy-max/ghaction-docker-meta@v2
with:
images: ${{ steps.base_image_step.outputs.image }}
tags: |
type=sha
type=raw,${{ github.sha }}
type=raw,${{ steps.versioning.outputs.version }}
- name: Login to DockerHub
uses: docker/login-action@v1
with:
registry: gcr.io
username: _json_key
password: ${{ secrets.GCR_JSON_KEY }}
- name: Build and push
uses: docker/build-push-action@v2
with:
context: .
push: true
tags: ${{ steps.meta.outputs.tags }}
labels: ${{ steps.meta.outputs.labels }}
build-args: |
SEM_VER=${{ steps.versioning.outputs.version }}
BUILD_TIMESTAMP=${{ steps.version_parameters.outputs.build_timestamp }}
GIT_BRANCH=${{ steps.version_parameters.outputs.branch }}
COMMIT_HASH=${{ github.sha }}
- name: Build and Push CLI
run: make push-cli SEM_VER='${{ steps.versioning.outputs.version }}' BUILD_TIMESTAMP='${{ steps.version_parameters.outputs.build_timestamp }}'
- shell: bash
run: |
echo '${{ steps.versioning.outputs.version }}' >> cli/bin/version.txt
- name: publish
uses: ncipollo/release-action@v1
with:
token: ${{ secrets.GITHUB_TOKEN }}
artifacts: "cli/bin/*"
commit: ${{ steps.version_parameters.outputs.branch }}
tag: ${{ steps.versioning.outputs.version }}
prerelease: ${{ github.ref != 'refs/heads/main' }}
bodyFile: 'cli/bin/README.md'

278
.github/workflows/release.yml vendored Normal file
View File

@@ -0,0 +1,278 @@
name: Release
on:
push:
branches:
- 'develop'
- 'main'
concurrency:
group: mizu-publish-${{ github.ref }}
cancel-in-progress: true
jobs:
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: Check out the repo
uses: actions/checkout@v2
- 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 }}
flavor: |
latest=auto
prefix=
suffix=-${{ matrix.target }},onlatest=true
- name: Login to Docker Hub
uses: docker/login-action@v1
with:
username: ${{ secrets.DOCKERHUB_USER }}
password: ${{ secrets.DOCKERHUB_PASS }}
- 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: |
TARGETARCH=${{ 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: |
TARGETARCH=${{ 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.16
uses: actions/setup-go@v2
with:
go-version: '1.16'
- 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 }}'
- name: Log the version into a .txt file
shell: bash
run: |
echo '${{ steps.versioning.outputs.version }}' >> cli/bin/version.txt
- name: Release
uses: ncipollo/release-action@v1
with:
token: ${{ secrets.GITHUB_TOKEN }}
artifacts: "cli/bin/*"
commit: ${{ steps.version_parameters.outputs.branch }}
tag: ${{ steps.versioning.outputs.version }}
prerelease: ${{ github.ref != 'refs/heads/main' }}
bodyFile: 'cli/bin/README.md'

View File

@@ -1,25 +0,0 @@
name: Security validation
on:
pull_request:
branches:
- 'develop'
- 'main'
jobs:
security:
name: Check for vulnerabilities
runs-on: ubuntu-latest
env:
SNYK_TOKEN: ${{ secrets.SNYK_TOKEN }}
steps:
- uses: actions/checkout@v2
- uses: snyk/actions/setup@master
- name: Set up Go 1.16
uses: actions/setup-go@v2
with:
go-version: '1.16'
- name: Run snyl on all projects
run: snyk test --all-projects

9
.gitignore vendored
View File

@@ -35,3 +35,12 @@ pprof/*
# 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

View File

@@ -1,4 +1,8 @@
FROM node:14-slim AS site-build
ARG BUILDARCH=amd64
ARG TARGETARCH=amd64
### Front-end
FROM node:16 AS front-end
WORKDIR /app/ui-build
@@ -7,64 +11,95 @@ COPY ui/package-lock.json .
RUN npm i
COPY ui .
RUN npm run build
RUN npm run build-ent
### Base builder image for native builds architecture
FROM golang:1.16-alpine AS builder-native-base
ENV CGO_ENABLED=1 GOOS=linux
RUN apk add libpcap-dev g++ perl-utils
FROM golang:1.16-alpine AS builder
# Set necessary environment variables needed for our image.
ENV CGO_ENABLED=1 GOOS=linux GOARCH=amd64
### Intermediate builder image for x86-64 to x86-64 native builds
FROM builder-native-base AS builder-from-amd64-to-amd64
ENV GOARCH=amd64
RUN apk add libpcap-dev gcc g++ make bash perl-utils
# Move to agent working directory (/agent-build).
### Intermediate builder image for AArch64 to AArch64 native builds
FROM builder-native-base AS builder-from-arm64v8-to-arm64v8
ENV GOARCH=arm64
### Builder image for x86-64 to AArch64 cross-compilation
FROM up9inc/linux-arm64-musl-go-libpcap AS builder-from-amd64-to-arm64v8
ENV CGO_ENABLED=1 GOOS=linux
ENV GOARCH=arm64 CGO_CFLAGS="-I/work/libpcap"
### Final builder image where the build happens
# Possible build strategies:
# BUILDARCH=amd64 TARGETARCH=amd64
# BUILDARCH=arm64v8 TARGETARCH=arm64v8
# BUILDARCH=amd64 TARGETARCH=arm64v8
ARG BUILDARCH=amd64
ARG TARGETARCH=amd64
FROM builder-from-${BUILDARCH}-to-${TARGETARCH} AS builder
# Move to agent working directory (/agent-build)
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/
COPY tap/api/go.mod ../tap/api/
COPY tap/extensions/amqp/go.mod ../tap/extensions/amqp/
COPY tap/extensions/http/go.mod ../tap/extensions/http/
COPY tap/extensions/kafka/go.mod ../tap/extensions/kafka/
COPY tap/extensions/redis/go.mod ../tap/extensions/redis/
RUN go mod download
# cheap trick to make the build faster (As long as go.mod wasn't changes)
# cheap trick to make the build faster (as long as go.mod 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 .
ARG COMMIT_HASH
ARG GIT_BRANCH
ARG BUILD_TIMESTAMP
ARG SEM_VER=0.0.0
# 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 .
WORKDIR /app/agent-build
# Download Basenine executable, verify the sha1sum and move it to a directory in $PATH
ADD https://github.com/up9inc/basenine/releases/download/v0.2.13/basenine_linux_amd64 ./basenine_linux_amd64
ADD https://github.com/up9inc/basenine/releases/download/v0.2.13/basenine_linux_amd64.sha256 ./basenine_linux_amd64.sha256
RUN shasum -a 256 -c basenine_linux_amd64.sha256
RUN chmod +x ./basenine_linux_amd64
RUN go build -ldflags="-extldflags=-static -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
# Download Basenine executable, verify the sha1sum
ADD https://github.com/up9inc/basenine/releases/download/v0.4.13/basenine_linux_${GOARCH} ./basenine_linux_${GOARCH}
ADD https://github.com/up9inc/basenine/releases/download/v0.4.13/basenine_linux_${GOARCH}.sha256 ./basenine_linux_${GOARCH}.sha256
RUN shasum -a 256 -c basenine_linux_${GOARCH}.sha256
RUN chmod +x ./basenine_linux_${GOARCH}
RUN mv ./basenine_linux_${GOARCH} ./basenine
FROM alpine:3.14
RUN apk add bash libpcap-dev
WORKDIR /app
# Copy binary and config files from /build to root folder of scratch container.
COPY --from=builder ["/app/agent-build/mizuagent", "."]
COPY --from=builder ["/app/agent-build/basenine_linux_amd64", "/usr/local/bin/basenine"]
COPY --from=builder ["/app/agent/build/extensions", "extensions"]
COPY --from=site-build ["/app/ui-build/build", "site"]
RUN mkdir /app/data/
### The shipped image
ARG TARGETARCH=amd64
FROM ${TARGETARCH}/busybox:latest
# gin-gonic runs in debug mode without this
ENV GIN_MODE=release
WORKDIR /app/data/
WORKDIR /app
# Copy binary and config files from /build to root folder of scratch container.
COPY --from=builder ["/app/agent-build/mizuagent", "."]
COPY --from=builder ["/app/agent-build/basenine", "/usr/local/bin/basenine"]
COPY --from=front-end ["/app/ui-build/build", "site"]
COPY --from=front-end ["/app/ui-build/build-ent", "site-standalone"]
# this script runs both apiserver and passivetapper and exits either if one of them exits, preventing a scenario where the container runs without one process
ENTRYPOINT "/app/mizuagent"
ENTRYPOINT ["/app/mizuagent"]

View File

@@ -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 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)
@@ -28,13 +28,20 @@ ui: ## Build UI.
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)
@ls -l agent/build
docker: ## Build and publish agent docker image.
@@ -46,10 +53,6 @@ push-docker: ## Build and publish agent docker image.
@echo "publishing Docker image .. "
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"
devops/build-agent-ci.sh
@@ -76,9 +79,6 @@ clean-cli: ## Clean CLI.
clean-docker:
@(echo "DOCKER cleanup - NOT IMPLEMENTED YET " )
extensions:
devops/build_extensions.sh
test-cli:
@echo "running cli tests"; cd cli && $(MAKE) test

View File

@@ -1,5 +1,17 @@
![Mizu: The API Traffic Viewer for Kubernetes](assets/mizu-logo.svg)
<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 enabling you to view all API communication between microservices to help your debug and troubleshoot regressions.
@@ -12,7 +24,7 @@ Think TCPDump and Wireshark re-invented for Kubernetes.
- Simple and powerful CLI
- Monitoring network traffic in real-time. Supported protocols:
- [HTTP/1.1](https://datatracker.ietf.org/doc/html/rfc2616) (REST, etc.)
- [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)
@@ -52,8 +64,8 @@ Pick one from the [Releases](https://github.com/up9inc/mizu/releases) page
## 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
@@ -172,16 +184,14 @@ Please see [CONTRACT MONITORING](docs/CONTRACT_MONITORING.md) page for more deta
### Configure proxy host
By default, mizu will be accessible via local host: 'http://localhost:8899/mizu/', 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
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 in daemon mode
### Install Mizu standalone
Mizu can be run detached from the cli using the daemon flag: `mizu tap --daemon`. This type of mizu instance will run
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 [DAEMON MODE](docs/DAEMON_MODE.md)
For more information please refer to [INSTALL STANDALONE](docs/INSTALL_STANDALONE.md)

View File

@@ -0,0 +1,25 @@
{
"watchForFileChanges":false,
"viewportWidth": 1920,
"viewportHeight": 1080,
"video": false,
"screenshotOnRunFailure": false,
"defaultCommandTimeout": 5000,
"testFiles": [
"tests/GuiPort.js",
"tests/MultipleNamespaces.js",
"tests/Redact.js",
"tests/NoRedact.js",
"tests/Regex.js",
"tests/RegexMasking.js",
"tests/IgnoredUserAgents.js",
"tests/UiTest.js"
],
"env": {
"testUrl": "http://localhost:8899/",
"redactHeaderContent": "User-Header[REDACTED]",
"redactBodyContent": "{ \"User\": \"[REDACTED]\" }",
"regexMaskingBodyContent": "[REDACTED]"
}
}

View File

@@ -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('-'));
}

View File

@@ -0,0 +1,18 @@
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}`)
});
});
}
export function resizeToHugeMizu() {
cy.viewport(1920, 3500);
}
export function resizeToNormalMizu() {
cy.viewport(1920, 1080);
}

View 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');
});

View File

@@ -0,0 +1,34 @@
import {isValueExistsInElement, resizeToHugeMizu} from "../testHelpers/TrafficHelper";
it('Loading Mizu', function () {
cy.visit(Cypress.env('testUrl'));
});
it('going through each entry', function () {
resizeToHugeMizu();
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');
}

View File

@@ -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));
});
}

View 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');

View 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');

View 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')));
});

View File

@@ -0,0 +1,7 @@
import {isValueExistsInElement} from "../testHelpers/TrafficHelper";
it('Loading Mizu', function () {
cy.visit(Cypress.env('testUrl'));
})
isValueExistsInElement(true, Cypress.env('regexMaskingBodyContent'), '.hljs');

View File

@@ -0,0 +1,218 @@
import {findLineAndCheck, getExpectedDetailsDict} from "../testHelpers/StatusBarHelper";
import {resizeToHugeMizu, resizeToNormalMizu} from "../testHelpers/TrafficHelper";
const greenFilterColor = 'rgb(210, 250, 210)';
const redFilterColor = 'rgb(250, 214, 220)';
const refreshWaitTimeout = 10000;
it('opening mizu', function () {
cy.visit(Cypress.env('testUrl'));
});
it('top bar check', function () {
const podName1 = 'httpbin', namespace1 = 'mizu-tests';
const podName2 = 'httpbin2', namespace2 = 'mizu-tests';
cy.get('.podsCount').trigger('mouseover');
findLineAndCheck(getExpectedDetailsDict(podName1, namespace1));
findLineAndCheck(getExpectedDetailsDict(podName2, namespace2));
cy.reload();
});
it('filtering guide check', function () {
cy.get('[title="Open Filtering Guide (Cheatsheet)"]').click();
cy.get('#modal-modal-title').should('be.visible');
cy.get('[lang="en"]').click(0, 0);
cy.get('#modal-modal-title').should('not.exist');
});
checkIllegalFilter('invalid filter');
checkFilter({
name: 'http',
leftSidePath: '> :nth-child(1) > :nth-child(1)',
leftSideExpectedText: 'HTTP',
rightSidePath: '[title=HTTP]',
rightSideExpectedText: 'Hypertext Transfer Protocol -- HTTP/1.1',
applyByEnter: true
});
checkFilter({
name: 'response.status == 200',
leftSidePath: '[title="Status Code"]',
leftSideExpectedText: '200',
rightSidePath: '> :nth-child(2) [title="Status Code"]',
rightSideExpectedText: '200',
applyByEnter: false
});
checkFilter({
name: 'src.name == ""',
leftSidePath: '[title="Source Name"]',
leftSideExpectedText: '[Unresolved]',
rightSidePath: '> :nth-child(2) [title="Source Name"]',
rightSideExpectedText: '[Unresolved]',
applyByEnter: false
});
checkFilter({
name: 'method == "GET"',
leftSidePath: '> :nth-child(3) > :nth-child(1) > :nth-child(1) > :nth-child(2)',
leftSideExpectedText: 'GET',
rightSidePath: '> :nth-child(2) > :nth-child(2) > :nth-child(1) > :nth-child(1) > :nth-child(2)',
rightSideExpectedText: 'GET',
applyByEnter: true
});
checkFilter({
name: 'summary == "/get"',
leftSidePath: '> :nth-child(3) > :nth-child(1) > :nth-child(2) > :nth-child(2)',
leftSideExpectedText: '/get',
rightSidePath: '> :nth-child(2) > :nth-child(2) > :nth-child(1) > :nth-child(2) > :nth-child(2)',
rightSideExpectedText: '/get',
applyByEnter: false
});
checkFilter({
name: 'dst.name == "httpbin.mizu-tests"',
leftSidePath: '> :nth-child(3) > :nth-child(2) > :nth-child(3) > :nth-child(2)',
leftSideExpectedText: 'httpbin.mizu-tests',
rightSidePath: '> :nth-child(2) > :nth-child(2) > :nth-child(2) > :nth-child(3) > :nth-child(2)',
rightSideExpectedText: 'httpbin.mizu-tests',
applyByEnter: false
});
checkFilter({
name: 'src.ip == "127.0.0.1"',
leftSidePath: '[title="Source IP"]',
leftSideExpectedText: '127.0.0.1',
rightSidePath: '> :nth-child(2) [title="Source IP"]',
rightSideExpectedText: '127.0.0.1',
applyByEnter: false
});
checkFilterNoResults('method == "POST"');
function checkFilterNoResults(filterName) {
it(`checking the filter: ${filterName}. Expecting no results`, function () {
cy.get('#total-entries').then(number => {
const totalEntries = number.text();
// applying the filter
cy.get('.w-tc-editor-text').type(filterName);
cy.get('.w-tc-editor').should('have.attr', 'style').and('include', greenFilterColor);
cy.get('[type="submit"]').click();
// waiting for the entries number to load
cy.get('#total-entries', {timeout: refreshWaitTimeout}).should('have.text', totalEntries);
// the DOM should show 0 entries
cy.get('#entries-length').should('have.text', '0');
// going through every potential entry and verifies that it doesn't exist
[...Array(parseInt(totalEntries)).keys()].map(shouldNotExist);
cy.get('[title="Fetch old records"]').click();
cy.get('#noMoreDataTop', {timeout: refreshWaitTimeout}).should('be.visible');
cy.get('#entries-length').should('have.text', '0'); // after loading all entries there should still be 0 entries
// reloading then waiting for the entries number to load
cy.reload();
cy.get('#total-entries', {timeout: refreshWaitTimeout}).should('have.text', totalEntries);
});
});
}
function shouldNotExist(entryNum) {
cy.get(`entry-${entryNum}`).should('not.exist');
}
function checkIllegalFilter(illegalFilterName) {
it(`should show red search bar with the input: ${illegalFilterName}`, function () {
cy.get('#total-entries').then(number => {
const totalEntries = number.text();
cy.get('.w-tc-editor-text').type(illegalFilterName);
cy.get('.w-tc-editor').should('have.attr', 'style').and('include', redFilterColor);
cy.get('[type="submit"]').click();
cy.get('[role="alert"]').should('be.visible');
cy.get('.w-tc-editor-text').clear();
// reloading then waiting for the entries number to load
cy.reload();
cy.get('#total-entries', {timeout: refreshWaitTimeout}).should('have.text', totalEntries);
});
});
}
function checkFilter(filterDetails){
const {name, leftSidePath, rightSidePath, rightSideExpectedText, leftSideExpectedText, applyByEnter} = filterDetails;
const entriesForDeeperCheck = 5;
it(`checking the filter: ${name}`, function () {
cy.get('#total-entries').then(number => {
const totalEntries = number.text();
// checks the hover on the last entry (the only one in DOM at the beginning)
leftOnHoverCheck(totalEntries - 1, leftSidePath, name);
// applying the filter with alt+enter or with the button
cy.get('.w-tc-editor-text').type(`${name}${applyByEnter ? '{alt+enter}' : ''}`);
cy.get('.w-tc-editor').should('have.attr', 'style').and('include', greenFilterColor);
if (!applyByEnter)
cy.get('[type="submit"]').click();
// only one entry in DOM after filtering, checking all four checks on it
leftTextCheck(totalEntries - 1, leftSidePath, leftSideExpectedText);
leftOnHoverCheck(totalEntries - 1, leftSidePath, name);
rightTextCheck(rightSidePath, rightSideExpectedText);
rightOnHoverCheck(rightSidePath, name);
cy.get('[title="Fetch old records"]').click();
resizeToHugeMizu();
// waiting for the entries number to load
cy.get('#entries-length', {timeout: refreshWaitTimeout}).should('have.text', totalEntries);
// checking only 'leftTextCheck' on all entries because the rest of the checks require more time
[...Array(parseInt(totalEntries)).keys()].forEach(entryNum => {
leftTextCheck(entryNum, leftSidePath, leftSideExpectedText);
});
// making the other 3 checks on the first X entries (longer time for each check)
deeperChcek(leftSidePath, rightSidePath, name, leftSideExpectedText, rightSideExpectedText, entriesForDeeperCheck);
// reloading then waiting for the entries number to load
resizeToNormalMizu();
cy.reload();
cy.get('#total-entries', {timeout: refreshWaitTimeout}).should('have.text', totalEntries);
});
});
}
function deeperChcek(leftSidePath, rightSidePath, filterName, leftSideExpectedText, rightSideExpectedText, entriesNumToCheck) {
[...Array(entriesNumToCheck).keys()].forEach(entryNum => {
leftOnHoverCheck(entryNum, leftSidePath, filterName);
cy.get(`#list #entry-${entryNum}`).click();
rightTextCheck(rightSidePath, rightSideExpectedText);
rightOnHoverCheck(rightSidePath, filterName);
});
}
function leftTextCheck(entryNum, path, expectedText) {
cy.get(`#list #entry-${entryNum} ${path}`).invoke('text').should('eq', expectedText);
}
function leftOnHoverCheck(entryNum, path, filterName) {
cy.get(`#list #entry-${entryNum} ${path}`).trigger('mouseover');
cy.get(`#list #entry-${entryNum} .Queryable-Tooltip`).should('have.text', filterName);
}
function rightTextCheck(path, expectedText) {
cy.get(`.TrafficPage-Container > :nth-child(2) ${path}`).should('have.text', expectedText);
}
function rightOnHoverCheck(path, expectedText) {
cy.get(`.TrafficPage-Container > :nth-child(2) ${path}`).trigger('mouseover');
cy.get(`.TrafficPage-Container > :nth-child(2) .Queryable-Tooltip`).should('have.text', expectedText);
}

View File

@@ -75,6 +75,7 @@ github.com/cpuguy83/go-md2man/v2 v2.0.0/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsr
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=
@@ -110,6 +111,7 @@ github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9
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=
@@ -156,6 +158,7 @@ github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/me
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=
@@ -191,10 +194,11 @@ github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMyw
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 h1:/CP5g8u/VJHijgedC/Legn3BAbAaWPgecwXBIDzw5no=
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=
@@ -243,6 +247,7 @@ github.com/imdario/mergo v0.3.5/go.mod h1:2EnlNZ0deacrJVfApfmtdGgDfMuh/nq6Ok1EcJ
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=
@@ -288,8 +293,10 @@ github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh
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=
@@ -314,6 +321,7 @@ github.com/peterbourgon/diskv v2.0.1+incompatible/go.mod h1:uqqh8zWWbv1HBMNONnaR
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=
@@ -357,6 +365,7 @@ github.com/spf13/cobra v1.1.1/go.mod h1:WnodtKOvamDL/PwE2M4iKs8aMDBZ5Q5klgD3qfVJ
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=
@@ -366,6 +375,7 @@ github.com/stretchr/objx v0.2.0/go.mod h1:qt09Ya8vawLte6SNmTgCsAVtYtaKzEcn8ATUoH
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=
@@ -462,6 +472,7 @@ golang.org/x/net v0.0.0-20200301022130-244492dfa37a/go.mod h1:z5CRVTTTmAJ677TzLL
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=
@@ -521,6 +532,7 @@ 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=
@@ -570,6 +582,7 @@ 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=
@@ -630,6 +643,7 @@ gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f h1:BLraFXnmrev5lT+xlilqcH8X
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=
@@ -641,6 +655,7 @@ 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=
@@ -653,7 +668,9 @@ honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWh
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=
@@ -664,6 +681,7 @@ k8s.io/gengo v0.0.0-20200413195148-3a45101e95ac/go.mod h1:ezvh/TsK7cY6rbqRK0oQQ8
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=
@@ -677,5 +695,7 @@ sigs.k8s.io/kustomize/cmd/config v0.9.10/go.mod h1:Mrby0WnRH7hA6OwOYnYpfpiY0WJIM
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=

View File

@@ -81,11 +81,16 @@ func TestLogs(t *testing.T) {
logsFileNames = append(logsFileNames, file.Name)
}
if !Contains(logsFileNames, "mizu.mizu-api-server.log") {
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
@@ -174,11 +179,16 @@ func TestLogsPath(t *testing.T) {
logsFileNames = append(logsFileNames, file.Name)
}
if !Contains(logsFileNames, "mizu.mizu-api-server.log") {
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

View File

@@ -3,7 +3,6 @@ package acceptanceTests
import (
"archive/zip"
"bytes"
"encoding/json"
"fmt"
"io/ioutil"
"net/http"
@@ -63,35 +62,7 @@ func TestTap(t *testing.T) {
}
}
entriesCheckFunc := func() error {
timestamp := time.Now().UnixNano() / int64(time.Millisecond)
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]
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
}
if err := retriesExecute(shortRetriesCount, entriesCheckFunc); err != nil {
t.Errorf("%v", err)
return
}
runCypressTests(t, "npx cypress run --spec \"cypress/integration/tests/UiTest.js\"")
})
}
}
@@ -138,6 +109,8 @@ func TestTapGuiPort(t *testing.T) {
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", guiPort))
})
}
}
@@ -149,6 +122,7 @@ func TestTapAllNamespaces(t *testing.T) {
expectedPods := []PodDescriptor{
{Name: "httpbin", Namespace: "mizu-tests"},
{Name: "httpbin2", Namespace: "mizu-tests"},
{Name: "httpbin", Namespace: "mizu-tests2"},
}
@@ -182,25 +156,8 @@ func TestTapAllNamespaces(t *testing.T) {
return
}
podsUrl := fmt.Sprintf("%v/status/tap", apiServerUrl)
requestResult, requestErr := executeHttpGetRequest(podsUrl)
if requestErr != nil {
t.Errorf("failed to get tap status, err: %v", requestErr)
return
}
pods, err := getPods(requestResult)
if err != nil {
t.Errorf("failed to get pods, err: %v", err)
return
}
for _, expectedPod := range expectedPods {
if !isPodDescriptorInPodArray(pods, expectedPod) {
t.Errorf("unexpected result - expected pod not found, pod namespace: %v, pod name: %v", expectedPod.Namespace, expectedPod.Name)
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",
expectedPods[0].Name, expectedPods[1].Name, expectedPods[2].Name, expectedPods[0].Namespace, expectedPods[1].Namespace, expectedPods[2].Namespace))
}
func TestTapMultipleNamespaces(t *testing.T) {
@@ -248,30 +205,8 @@ func TestTapMultipleNamespaces(t *testing.T) {
return
}
podsUrl := fmt.Sprintf("%v/status/tap", apiServerUrl)
requestResult, requestErr := executeHttpGetRequest(podsUrl)
if requestErr != nil {
t.Errorf("failed to get tap status, err: %v", requestErr)
return
}
pods, err := getPods(requestResult)
if err != nil {
t.Errorf("failed to get pods, err: %v", err)
return
}
if len(expectedPods) != len(pods) {
t.Errorf("unexpected result - expected pods length: %v, actual pods length: %v", len(expectedPods), len(pods))
return
}
for _, expectedPod := range expectedPods {
if !isPodDescriptorInPodArray(pods, expectedPod) {
t.Errorf("unexpected result - expected pod not found, pod namespace: %v, pod name: %v", expectedPod.Namespace, expectedPod.Name)
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",
expectedPods[0].Name, expectedPods[1].Name, expectedPods[2].Name, expectedPods[0].Namespace, expectedPods[1].Namespace, expectedPods[2].Namespace))
}
func TestTapRegex(t *testing.T) {
@@ -316,30 +251,8 @@ func TestTapRegex(t *testing.T) {
return
}
podsUrl := fmt.Sprintf("%v/status/tap", apiServerUrl)
requestResult, requestErr := executeHttpGetRequest(podsUrl)
if requestErr != nil {
t.Errorf("failed to get tap status, err: %v", requestErr)
return
}
pods, err := getPods(requestResult)
if err != nil {
t.Errorf("failed to get pods, err: %v", err)
return
}
if len(expectedPods) != len(pods) {
t.Errorf("unexpected result - expected pods length: %v, actual pods length: %v", len(expectedPods), len(pods))
return
}
for _, expectedPod := range expectedPods {
if !isPodDescriptorInPodArray(pods, expectedPod) {
t.Errorf("unexpected result - expected pod not found, pod namespace: %v, pod name: %v", expectedPod.Namespace, expectedPod.Name)
return
}
}
runCypressTests(t, fmt.Sprintf("npx cypress run --spec \"cypress/integration/tests/Regex.js\" --env name=%v,namespace=%v",
expectedPods[0].Name, expectedPods[0].Namespace))
}
func TestTapDryRun(t *testing.T) {
@@ -436,59 +349,7 @@ func TestTapRedact(t *testing.T) {
}
}
redactCheckFunc := func() error {
timestamp := time.Now().UnixNano() / int64(time.Millisecond)
entries, err := getDBEntries(timestamp, defaultEntriesCount, 1*time.Second)
if err != nil {
return err
}
err = checkEntriesAtLeast(entries, 1)
if err != nil {
return err
}
firstEntry := entries[0]
entryUrl := fmt.Sprintf("%v/entries/%v", apiServerUrl, firstEntry["id"])
requestResult, requestErr := executeHttpGetRequest(entryUrl)
if requestErr != nil {
return fmt.Errorf("failed to get entry, err: %v", requestErr)
}
entry := requestResult.(map[string]interface{})["data"].(map[string]interface{})
request := entry["request"].(map[string]interface{})
headers := request["_headers"].([]interface{})
for _, headerInterface := range headers {
header := headerInterface.(map[string]interface{})
if header["name"].(string) != "User-Header" {
continue
}
userHeader := header["value"].(string)
if userHeader != "[REDACTED]" {
return fmt.Errorf("unexpected result - user agent is not redacted")
}
}
postData := request["postData"].(map[string]interface{})
textDataStr := postData["text"].(string)
var textData map[string]string
if parseErr := json.Unmarshal([]byte(textDataStr), &textData); parseErr != nil {
return fmt.Errorf("failed to parse text data, err: %v", parseErr)
}
if textData["User"] != "[REDACTED]" {
return fmt.Errorf("unexpected result - user in body is not redacted")
}
return nil
}
if err := retriesExecute(shortRetriesCount, redactCheckFunc); err != nil {
t.Errorf("%v", err)
return
}
runCypressTests(t, "npx cypress run --spec \"cypress/integration/tests/Redact.js\"")
}
func TestTapNoRedact(t *testing.T) {
@@ -540,59 +401,7 @@ func TestTapNoRedact(t *testing.T) {
}
}
redactCheckFunc := func() error {
timestamp := time.Now().UnixNano() / int64(time.Millisecond)
entries, err := getDBEntries(timestamp, defaultEntriesCount, 1*time.Second)
if err != nil {
return err
}
err = checkEntriesAtLeast(entries, 1)
if err != nil {
return err
}
firstEntry := entries[0]
entryUrl := fmt.Sprintf("%v/entries/%v", apiServerUrl, firstEntry["id"])
requestResult, requestErr := executeHttpGetRequest(entryUrl)
if requestErr != nil {
return fmt.Errorf("failed to get entry, err: %v", requestErr)
}
entry := requestResult.(map[string]interface{})["data"].(map[string]interface{})
request := entry["request"].(map[string]interface{})
headers := request["_headers"].([]interface{})
for _, headerInterface := range headers {
header := headerInterface.(map[string]interface{})
if header["name"].(string) != "User-Header" {
continue
}
userHeader := header["value"].(string)
if userHeader == "[REDACTED]" {
return fmt.Errorf("unexpected result - user agent is redacted")
}
}
postData := request["postData"].(map[string]interface{})
textDataStr := postData["text"].(string)
var textData map[string]string
if parseErr := json.Unmarshal([]byte(textDataStr), &textData); parseErr != nil {
return fmt.Errorf("failed to parse text data, err: %v", parseErr)
}
if textData["User"] == "[REDACTED]" {
return fmt.Errorf("unexpected result - user in body is redacted")
}
return nil
}
if err := retriesExecute(shortRetriesCount, redactCheckFunc); err != nil {
t.Errorf("%v", err)
return
}
runCypressTests(t, "npx cypress run --spec \"cypress/integration/tests/NoRedact.js\"")
}
func TestTapRegexMasking(t *testing.T) {
@@ -643,41 +452,8 @@ func TestTapRegexMasking(t *testing.T) {
}
}
redactCheckFunc := func() error {
timestamp := time.Now().UnixNano() / int64(time.Millisecond)
runCypressTests(t, "npx cypress run --spec \"cypress/integration/tests/RegexMasking.js\"")
entries, err := getDBEntries(timestamp, defaultEntriesCount, 1*time.Second)
if err != nil {
return err
}
err = checkEntriesAtLeast(entries, 1)
if err != nil {
return err
}
firstEntry := entries[0]
entryUrl := fmt.Sprintf("%v/entries/%v", apiServerUrl, firstEntry["id"])
requestResult, requestErr := executeHttpGetRequest(entryUrl)
if requestErr != nil {
return fmt.Errorf("failed to get entry, err: %v", requestErr)
}
entry := requestResult.(map[string]interface{})["data"].(map[string]interface{})
request := entry["request"].(map[string]interface{})
postData := request["postData"].(map[string]interface{})
textData := postData["text"].(string)
if textData != "[REDACTED]" {
return fmt.Errorf("unexpected result - body is not redacted")
}
return nil
}
if err := retriesExecute(shortRetriesCount, redactCheckFunc); err != nil {
t.Errorf("%v", err)
return
}
}
func TestTapIgnoredUserAgents(t *testing.T) {
@@ -738,45 +514,7 @@ func TestTapIgnoredUserAgents(t *testing.T) {
}
}
ignoredUserAgentsCheckFunc := func() error {
timestamp := time.Now().UnixNano() / int64(time.Millisecond)
entries, err := getDBEntries(timestamp, defaultEntriesCount, 1*time.Second)
if err != nil {
return err
}
err = checkEntriesAtLeast(entries, 1)
if err != nil {
return err
}
for _, entryInterface := range entries {
entryUrl := fmt.Sprintf("%v/entries/%v", apiServerUrl, entryInterface["id"])
requestResult, requestErr := executeHttpGetRequest(entryUrl)
if requestErr != nil {
return fmt.Errorf("failed to get entry, err: %v", requestErr)
}
entry := requestResult.(map[string]interface{})["data"].(map[string]interface{})
request := entry["request"].(map[string]interface{})
headers := request["_headers"].([]interface{})
for _, headerInterface := range headers {
header := headerInterface.(map[string]interface{})
if header["name"].(string) != ignoredUserAgentCustomHeader {
continue
}
return fmt.Errorf("unexpected result - user agent is not ignored")
}
}
return nil
}
if err := retriesExecute(shortRetriesCount, ignoredUserAgentsCheckFunc); err != nil {
t.Errorf("%v", err)
return
}
runCypressTests(t, "npx cypress run --spec \"cypress/integration/tests/IgnoredUserAgents.js\"")
}
func TestTapDumpLogs(t *testing.T) {
@@ -860,11 +598,16 @@ func TestTapDumpLogs(t *testing.T) {
logsFileNames = append(logsFileNames, file.Name)
}
if !Contains(logsFileNames, "mizu.mizu-api-server.log") {
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
@@ -880,251 +623,3 @@ func TestTapDumpLogs(t *testing.T) {
return
}
}
func TestDaemonSeeTraffic(t *testing.T) {
if testing.Short() {
t.Skip("ignored acceptance test")
}
tests := []int{50}
for _, entriesCount := range tests {
t.Run(fmt.Sprintf("%d", entriesCount), func(t *testing.T) {
cliPath, cliPathErr := getCliPath()
if cliPathErr != nil {
t.Errorf("failed to get cli path, err: %v", cliPathErr)
return
}
tapDaemonCmdArgs := getDefaultTapCommandArgsWithDaemonMode()
tapNamespace := getDefaultTapNamespace()
tapDaemonCmdArgs = append(tapDaemonCmdArgs, tapNamespace...)
tapCmd := exec.Command(cliPath, tapDaemonCmdArgs...)
viewCmd := exec.Command(cliPath, getDefaultViewCommandArgs()...)
t.Cleanup(func() {
daemonCleanup(t, viewCmd)
})
t.Logf("running command: %v", tapCmd.String())
if err := tapCmd.Run(); err != nil {
t.Errorf("error occured while running the tap command, err: %v", err)
return
}
t.Logf("running command: %v", viewCmd.String())
if err := viewCmd.Start(); err != nil {
t.Errorf("error occured while running the view 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 < entriesCount; i++ {
if _, requestErr := executeHttpGetRequest(fmt.Sprintf("%v/get", proxyUrl)); requestErr != nil {
t.Errorf("failed to send proxy request, err: %v", requestErr)
return
}
}
entriesCheckFunc := func() error {
timestamp := time.Now().UnixNano() / int64(time.Millisecond)
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]
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
}
if err := retriesExecute(shortRetriesCount, entriesCheckFunc); err != nil {
t.Errorf("%v", err)
return
}
})
}
}
func TestDaemonMultipleNamespacesSeePods(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 := getDefaultTapCommandArgsWithDaemonMode()
var namespacesCmd []string
for _, expectedPod := range expectedPods {
namespacesCmd = append(namespacesCmd, "-n", expectedPod.Namespace)
}
tapCmdArgs = append(tapCmdArgs, namespacesCmd...)
tapCmd := exec.Command(cliPath, tapCmdArgs...)
viewCmd := exec.Command(cliPath, getDefaultViewCommandArgs()...)
t.Cleanup(func() {
daemonCleanup(t, viewCmd)
})
t.Logf("running command: %v", tapCmd.String())
if err := tapCmd.Run(); err != nil {
t.Errorf("failed to start tap command, err: %v", err)
return
}
t.Logf("running command: %v", viewCmd.String())
if err := viewCmd.Start(); err != nil {
t.Errorf("error occured while running the view 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
}
podsUrl := fmt.Sprintf("%v/status/tap", apiServerUrl)
requestResult, requestErr := executeHttpGetRequest(podsUrl)
if requestErr != nil {
t.Errorf("failed to get tap status, err: %v", requestErr)
return
}
pods, err := getPods(requestResult)
if err != nil {
t.Errorf("failed to get pods, err: %v", err)
return
}
if len(expectedPods) != len(pods) {
t.Errorf("unexpected result - expected pods length: %v, actual pods length: %v", len(expectedPods), len(pods))
return
}
for _, expectedPod := range expectedPods {
if !isPodDescriptorInPodArray(pods, expectedPod) {
t.Errorf("unexpected result - expected pod not found, pod namespace: %v, pod name: %v", expectedPod.Namespace, expectedPod.Name)
return
}
}
}
func TestDaemonSingleNamespaceSeePods(t *testing.T) {
if testing.Short() {
t.Skip("ignored acceptance test")
}
expectedPods := []PodDescriptor{
{Name: "httpbin", Namespace: "mizu-tests"},
{Name: "httpbin2", Namespace: "mizu-tests"},
}
unexpectedPods := []PodDescriptor{
{Name: "httpbin", Namespace: "mizu-tests2"},
}
cliPath, cliPathErr := getCliPath()
if cliPathErr != nil {
t.Errorf("failed to get cli path, err: %v", cliPathErr)
return
}
tapCmdArgs := getDefaultTapCommandArgsWithDaemonMode()
var namespacesCmd []string
for _, expectedPod := range expectedPods {
namespacesCmd = append(namespacesCmd, "-n", expectedPod.Namespace)
}
tapCmdArgs = append(tapCmdArgs, namespacesCmd...)
tapCmd := exec.Command(cliPath, tapCmdArgs...)
viewCmd := exec.Command(cliPath, getDefaultViewCommandArgs()...)
t.Cleanup(func() {
daemonCleanup(t, viewCmd)
})
t.Logf("running command: %v", tapCmd.String())
if err := tapCmd.Run(); err != nil {
t.Errorf("failed to start tap command, err: %v", err)
return
}
t.Logf("running command: %v", viewCmd.String())
if err := viewCmd.Start(); err != nil {
t.Errorf("error occured while running the view 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
}
podsUrl := fmt.Sprintf("%v/status/tap", apiServerUrl)
requestResult, requestErr := executeHttpGetRequest(podsUrl)
if requestErr != nil {
t.Errorf("failed to get tap status, err: %v", requestErr)
return
}
pods, err := getPods(requestResult)
if err != nil {
t.Errorf("failed to get pods, err: %v", err)
return
}
for _, unexpectedPod := range unexpectedPods {
if isPodDescriptorInPodArray(pods, unexpectedPod) {
t.Errorf("unexpected result - unexpected pod found, pod namespace: %v, pod name: %v", unexpectedPod.Namespace, unexpectedPod.Name)
return
}
}
if len(expectedPods) != len(pods) {
t.Errorf("unexpected result - expected pods length: %v, actual pods length: %v", len(expectedPods), len(pods))
return
}
for _, expectedPod := range expectedPods {
if !isPodDescriptorInPodArray(pods, expectedPod) {
t.Errorf("unexpected result - expected pod not found, pod namespace: %v, pod name: %v", expectedPod.Namespace, expectedPod.Name)
return
}
}
}

View File

@@ -92,7 +92,7 @@ func getDefaultCommandArgs() []string {
setFlag := "--set"
telemetry := "telemetry=false"
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"
return []string{setFlag, telemetry, setFlag, agentImage, setFlag, imagePullPolicy, setFlag, headless}
@@ -105,10 +105,6 @@ func getDefaultTapCommandArgs() []string {
return append([]string{tapCommand}, defaultCmdArgs...)
}
func getDefaultTapCommandArgsWithDaemonMode() []string {
return append(getDefaultTapCommandArgs(), "--daemon")
}
func getDefaultTapCommandArgsWithRegex(regex string) []string {
tapCommand := "tap"
defaultCmdArgs := getDefaultCommandArgs()
@@ -148,6 +144,17 @@ func getDefaultViewCommandArgs() []string {
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{}
@@ -176,16 +183,16 @@ func tryExecuteFunc(executeFunc func() error) (err interface{}) {
}
func waitTapPodsReady(apiServerUrl string) error {
resolvingUrl := fmt.Sprintf("%v/status/tappersCount", apiServerUrl)
resolvingUrl := fmt.Sprintf("%v/status/connectedTappersCount", apiServerUrl)
tapPodsReadyFunc := func() error {
requestResult, requestErr := executeHttpGetRequest(resolvingUrl)
if requestErr != nil {
return requestErr
}
tappersCount := requestResult.(float64)
if tappersCount == 0 {
return fmt.Errorf("no tappers running")
connectedTappersCount := requestResult.(float64)
if connectedTappersCount == 0 {
return fmt.Errorf("no connected tappers running")
}
time.Sleep(waitAfterTapPodsReady)
return nil
@@ -304,11 +311,10 @@ func cleanupCommand(cmd *exec.Cmd) error {
}
func getPods(tapStatusInterface interface{}) ([]map[string]interface{}, error) {
tapStatus := tapStatusInterface.(map[string]interface{})
podsInterface := tapStatus["pods"].([]interface{})
tapPodsInterface := tapStatusInterface.([]interface{})
var pods []map[string]interface{}
for _, podInterface := range podsInterface {
for _, podInterface := range tapPodsInterface {
pods = append(pods, podInterface.(map[string]interface{}))
}
@@ -325,16 +331,6 @@ func getLogsPath() (string, error) {
return logsPath, nil
}
func daemonCleanup(t *testing.T, viewCmd *exec.Cmd) {
if err := runMizuClean(); err != nil {
t.Logf("error running mizu clean: %v", err)
}
if err := cleanupCommand(viewCmd); err != nil {
t.Logf("failed to cleanup view command, err: %v", err)
}
}
// 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 {

View File

@@ -1,20 +0,0 @@
# mizu agent
Agent for MIZU (API server and tapper)
Basic APIs:
* /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 --set agent-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>

View File

@@ -4,24 +4,32 @@ go 1.16
require (
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/elastic/go-elasticsearch/v7 v7.16.0
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/keto-client-go v0.7.0-alpha.1
github.com/ory/kratos-client-go v0.8.2-alpha.1
github.com/patrickmn/go-cache v2.1.0+incompatible
github.com/up9inc/basenine/client/go v0.0.0-20211130202146-cf837626a065
github.com/stretchr/testify v1.7.0
github.com/up9inc/basenine/client/go v0.0.0-20220125035724-573fff0d5075
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/up9inc/mizu/tap/extensions/amqp v0.0.0
github.com/up9inc/mizu/tap/extensions/http v0.0.0
github.com/up9inc/mizu/tap/extensions/kafka v0.0.0
github.com/up9inc/mizu/tap/extensions/redis v0.0.0
github.com/yalp/jsonpath v0.0.0-20180802001716-5cc68e5049a0
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
@@ -32,3 +40,11 @@ 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
replace github.com/up9inc/mizu/tap/extensions/amqp v0.0.0 => ../tap/extensions/amqp
replace github.com/up9inc/mizu/tap/extensions/http v0.0.0 => ../tap/extensions/http
replace github.com/up9inc/mizu/tap/extensions/kafka v0.0.0 => ../tap/extensions/kafka
replace github.com/up9inc/mizu/tap/extensions/redis v0.0.0 => ../tap/extensions/redis

View File

@@ -8,20 +8,30 @@ 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=
@@ -44,7 +54,9 @@ github.com/MakeNowJust/heredoc v0.0.0-20170808103936-bb23615498cd/go.mod h1:64YH
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 h1:WEQqlqaGbrPkxLJWfBwQmfEAE1Z7ONdDLqrN38tNFfI=
github.com/PuerkitoBio/purell v1.1.1/go.mod h1:c11w/QuzBsJSee3cPx9rAFu61PvFxuPbtSwDGJws/X0=
github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578 h1:d+Bc7a5rLufV/sSk/8dngufqelfh6jnri85riMAaF/M=
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=
@@ -60,6 +72,13 @@ github.com/armon/go-metrics v0.0.0-20180917152333-f0300d1749da/go.mod h1:Q73ZrmV
github.com/armon/go-radix v0.0.0-20180808171621-7fddfc383310/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8=
github.com/asaskevich/govalidator v0.0.0-20180720115003-f9ffefc3facf/go.mod h1:lB+ZfQJz7igIIfQNfa7Ml4HSf2uFQQRzpGGRXenZAgY=
github.com/asaskevich/govalidator v0.0.0-20190424111038-f61b66f89f4a/go.mod h1:lB+ZfQJz7igIIfQNfa7Ml4HSf2uFQQRzpGGRXenZAgY=
github.com/asaskevich/govalidator v0.0.0-20200108200545-475eaeb16496/go.mod h1:oGkLhpf+kjZl6xBf758TQhh5XrAeiJv/7FRz/2spLIg=
github.com/asaskevich/govalidator v0.0.0-20200428143746-21a406dcc535/go.mod h1:oGkLhpf+kjZl6xBf758TQhh5XrAeiJv/7FRz/2spLIg=
github.com/asaskevich/govalidator v0.0.0-20200907205600-7a23bdc65eef h1:46PFijGLmAjMPwCCCo7Jf0W6f9slllCkkv7vyc1yOSg=
github.com/asaskevich/govalidator v0.0.0-20200907205600-7a23bdc65eef/go.mod h1:WaHUgvxTVq04UNunO+XhnAqY/wQc+bxr74GqbsZ/Jqw=
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=
@@ -72,10 +91,17 @@ github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA
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=
@@ -98,19 +124,31 @@ github.com/docker/distribution v2.7.1+incompatible/go.mod h1:J2gT2udsDAN96Uj4Kfc
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/eapache/go-xerial-snappy v0.0.0-20180814174437-776d5712da21 h1:YEetp8/yCZMuEPMUDHG0CW/brkkEp8mzqk2+ODEitlw=
github.com/eapache/go-xerial-snappy v0.0.0-20180814174437-776d5712da21/go.mod h1:+020luEh2TKB4/GOp8oxxtq0Daoen/Cii55CzbTV6DU=
github.com/elastic/go-elasticsearch/v7 v7.16.0 h1:GHsxDFXIAlhSleXun4kwA89P7kQFADRChqvgOPeYP5A=
github.com/elastic/go-elasticsearch/v7 v7.16.0/go.mod h1:OJ4wdbtDNk5g503kvlHLyErCgQwwzmDtaFC4XyOxXA4=
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 h1:hxNvNX/xYBp0ovncs8WyWZrOrpBNub/JfaMvbURyft8=
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/frankban/quicktest v1.11.3 h1:8sXhOn0uLys67V8EsXLc6eszDs8VXWxL3iRvebPhedY=
github.com/frankban/quicktest v1.11.3/go.mod h1:wRf/ReqHper53s+kmmSZizM8NamnL3IM0I9ntUbOk+k=
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=
@@ -123,12 +161,11 @@ github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm
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-errors/errors v1.4.1/go.mod h1:sIVyrIiJhuEF+Pj9Ebtd6P/rEYROXFi3BopGUQ5a5Og=
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=
@@ -144,10 +181,22 @@ github.com/go-openapi/analysis v0.0.0-20180825180245-b006789cd277/go.mod h1:k70t
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.4/go.mod h1:3P1osvZa9jKjb8ed2TPng3f0i/UY9snX6gxi44djMjk=
github.com/go-openapi/analysis v0.19.5/go.mod h1:hkEAkxagaIvIP7VTn8ygJNkd4kAYON2rCu0v0ObL0AU=
github.com/go-openapi/analysis v0.19.10/go.mod h1:qmhS3VNFxBlquFJ0RGoDtylO9y4pgTAUNE9AEEMdlJQ=
github.com/go-openapi/analysis v0.19.16/go.mod h1:GLInF007N83Ad3m8a/CbQ5TPzdnGT7workfHwuVjNVk=
github.com/go-openapi/analysis v0.20.0 h1:UN09o0kNhleunxW7LR+KnltD0YrJ8FF03pSqvAN3Vro=
github.com/go-openapi/analysis v0.20.0/go.mod h1:BMchjvaHDykmRMsK40iPtvyOfFdMMxlOmQr9FBZk+Og=
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/errors v0.19.3/go.mod h1:qX0BLWsyaKfvhluLejVpVNwNRdXZhEbTA4kxxpKBC94=
github.com/go-openapi/errors v0.19.6/go.mod h1:cM//ZKUKyO06HSwqAelJ5NsEMMcpa6VpXe8DOa1Mi1M=
github.com/go-openapi/errors v0.19.7/go.mod h1:cM//ZKUKyO06HSwqAelJ5NsEMMcpa6VpXe8DOa1Mi1M=
github.com/go-openapi/errors v0.19.8/go.mod h1:cM//ZKUKyO06HSwqAelJ5NsEMMcpa6VpXe8DOa1Mi1M=
github.com/go-openapi/errors v0.19.9/go.mod h1:cM//ZKUKyO06HSwqAelJ5NsEMMcpa6VpXe8DOa1Mi1M=
github.com/go-openapi/errors v0.20.1 h1:j23mMDtRxMwIobkpId7sWh7Ddcx4ivaoqUbfXx5P+a8=
github.com/go-openapi/errors v0.20.1/go.mod h1:cM//ZKUKyO06HSwqAelJ5NsEMMcpa6VpXe8DOa1Mi1M=
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=
@@ -158,32 +207,73 @@ github.com/go-openapi/jsonreference v0.17.0/go.mod h1:g4xxGn04lDIRh0GJb5QlpE3Hfo
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/jsonreference v0.19.5 h1:1WJP/wi4OjB4iV8KVbH73rQaoialJrqv8gitZLxGLtM=
github.com/go-openapi/jsonreference v0.19.5/go.mod h1:RdybgQwPxbL4UEjuAruzK1x3nE69AqPYEJeo/TWfEeg=
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.3/go.mod h1:YVfqhUCdahYwR3f3iiwQLhicVRvLlU/WO5WPaZvcvSI=
github.com/go-openapi/loads v0.19.4/go.mod h1:zZVHonKd8DXyxyw4yfnVjPzBjIQcLt0CCsn0N0ZrQsk=
github.com/go-openapi/loads v0.19.5/go.mod h1:dswLCAdonkRufe/gSUC3gN8nTSaB9uaS2es0x5/IbjY=
github.com/go-openapi/loads v0.19.6/go.mod h1:brCsvE6j8mnbmGBh103PT/QLHfbyDxA4hsKvYBNEGVc=
github.com/go-openapi/loads v0.19.7/go.mod h1:brCsvE6j8mnbmGBh103PT/QLHfbyDxA4hsKvYBNEGVc=
github.com/go-openapi/loads v0.20.0/go.mod h1:2LhKquiE513rN5xC6Aan6lYOSddlL8Mp20AW9kpviM4=
github.com/go-openapi/loads v0.20.2 h1:z5p5Xf5wujMxS1y8aP+vxwW5qYT2zdJBbXKmQUG3lcc=
github.com/go-openapi/loads v0.20.2/go.mod h1:hTVUotJ+UonAMMZsvakEgmWKgtulweO9vYP2bQYKA/o=
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/runtime v0.19.15/go.mod h1:dhGWCTKRXlAfGnQG0ONViOZpjfg0m2gUt9nTQPQZuoo=
github.com/go-openapi/runtime v0.19.16/go.mod h1:5P9104EJgYcizotuXhEuUrzVc+j1RiSjahULvYmlv98=
github.com/go-openapi/runtime v0.19.24/go.mod h1:Lm9YGCeecBnUUkFTxPC4s1+lwrkJ0pthx8YvyjCfkgk=
github.com/go-openapi/runtime v0.20.0 h1:DEV4oYH28MqakaabtbxH0cjvlzFegi/15kfUVCfiZW0=
github.com/go-openapi/runtime v0.20.0/go.mod h1:2WnLRxMiOUWNN0UZskSkxW0+WXdfB1KmqRKCFH+ZWYk=
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/spec v0.19.6/go.mod h1:Hm2Jr4jv8G1ciIAo+frC/Ft+rR2kQDh8JHKHb3gWUSk=
github.com/go-openapi/spec v0.19.8/go.mod h1:Hm2Jr4jv8G1ciIAo+frC/Ft+rR2kQDh8JHKHb3gWUSk=
github.com/go-openapi/spec v0.19.15/go.mod h1:+81FIL1JwC5P3/Iuuozq3pPE9dXdIEGxFutcFKaVbmU=
github.com/go-openapi/spec v0.20.0/go.mod h1:+81FIL1JwC5P3/Iuuozq3pPE9dXdIEGxFutcFKaVbmU=
github.com/go-openapi/spec v0.20.1/go.mod h1:93x7oh+d+FQsmsieroS4cmR3u0p/ywH649a3qwC9OsQ=
github.com/go-openapi/spec v0.20.3 h1:uH9RQ6vdyPSs2pSy9fL8QPspDF2AMIMPtmK5coSSjtQ=
github.com/go-openapi/spec v0.20.3/go.mod h1:gG4F8wdEDN+YPBMVnzE85Rbhf+Th2DTvA9nFPQ5AYEg=
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.2/go.mod h1:0yX7dbo8mKIvc3XSKp7MNfxw4JytCfCD6+bY1AVL9LU=
github.com/go-openapi/strfmt v0.19.3/go.mod h1:0yX7dbo8mKIvc3XSKp7MNfxw4JytCfCD6+bY1AVL9LU=
github.com/go-openapi/strfmt v0.19.4/go.mod h1:eftuHTlB/dI8Uq8JJOyRlieZf+WkkxUuk0dgdHXr2Qk=
github.com/go-openapi/strfmt v0.19.5/go.mod h1:eftuHTlB/dI8Uq8JJOyRlieZf+WkkxUuk0dgdHXr2Qk=
github.com/go-openapi/strfmt v0.19.11/go.mod h1:UukAYgTaQfqJuAFlNxxMWNvMYiwiXtLsF2VwmoFtbtc=
github.com/go-openapi/strfmt v0.20.0/go.mod h1:UukAYgTaQfqJuAFlNxxMWNvMYiwiXtLsF2VwmoFtbtc=
github.com/go-openapi/strfmt v0.20.2/go.mod h1:43urheQI9dNtE5lTZQfuFJvjYJKPrxicATpEfZwHUNk=
github.com/go-openapi/strfmt v0.20.3 h1:YVG4ZgPZ00km/lRHrIf7c6cKL5/4FAUtG2T9RxWAgDY=
github.com/go-openapi/strfmt v0.20.3/go.mod h1:43urheQI9dNtE5lTZQfuFJvjYJKPrxicATpEfZwHUNk=
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/swag v0.19.7/go.mod h1:ao+8BpOPyKdpQz3AOJfbeEVpLmWAvlT1IfTe5McPyhY=
github.com/go-openapi/swag v0.19.9/go.mod h1:ao+8BpOPyKdpQz3AOJfbeEVpLmWAvlT1IfTe5McPyhY=
github.com/go-openapi/swag v0.19.12/go.mod h1:eFdyEBkTdoAf/9RXBvj4cr1nH7GD8Kzo5HTt47gr72M=
github.com/go-openapi/swag v0.19.13/go.mod h1:QYRuS/SOXUCsnplDa677K7+DxSOj6IPNl/eQntq43wQ=
github.com/go-openapi/swag v0.19.14/go.mod h1:QYRuS/SOXUCsnplDa677K7+DxSOj6IPNl/eQntq43wQ=
github.com/go-openapi/swag v0.19.15 h1:D2NRCBzS9/pEY3gP9Nl8aDqGUcPFrwG2p+CNFrLyrCM=
github.com/go-openapi/swag v0.19.15/go.mod h1:QYRuS/SOXUCsnplDa677K7+DxSOj6IPNl/eQntq43wQ=
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.3/go.mod h1:90Vh6jjkTn+OT1Eefm0ZixWNFjhtOH7vS9k0lo6zwJo=
github.com/go-openapi/validate v0.19.8/go.mod h1:8DJv2CVJQ6kGNpFW6eV9N3JviE1C85nY1c2z52x1Gk4=
github.com/go-openapi/validate v0.19.10/go.mod h1:RKEZTUWDkxKQxN2jDT7ZnZi2bhZlbNMAuKvKB+IaGx8=
github.com/go-openapi/validate v0.19.12/go.mod h1:Rzou8hA/CBw8donlS6WNEUQupNvUZ0waH08tGe6kAQ4=
github.com/go-openapi/validate v0.19.15/go.mod h1:tbn/fdOwYHgrhPBzidZfJC2MIVvs9GA7monOmWBbeCI=
github.com/go-openapi/validate v0.20.1/go.mod h1:b60iJT+xNNLfaQJUqLI7946tYiFEOuE9E4k54HpKcJ0=
github.com/go-openapi/validate v0.20.3 h1:GZPPhhKSZrE8HjB4eEkoYAZmoWA4+tCemSgINH1/vKw=
github.com/go-openapi/validate v0.20.3/go.mod h1:goDdqVGiigM3jChcrYJxD2joalke3ZXeftD16byIjA4=
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=
@@ -194,8 +284,34 @@ 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 h1:5SgMzNM5HxrEjV0ww2lTmX6E2Izsfxas4+YHWRs3Lsk=
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/here v0.6.0/go.mod h1:wAG085dHOYqUpf+Ap+WOdrPTp5IYcDAs/x7PLa8Y5fM=
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/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=
@@ -213,11 +329,14 @@ github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfb
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=
@@ -227,6 +346,8 @@ github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QD
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 h1:Qgr9rKW7uDUkrbSmQeiDsGa8SjGyCOGtuasMWwvp2P4=
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=
@@ -234,10 +355,14 @@ github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5a
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.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 h1:L8R9j+yAqZuZjsqh/z+F1NCffTKKLShY6zXTItVIZ8M=
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=
@@ -245,11 +370,14 @@ 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=
@@ -296,7 +424,13 @@ github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:
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/jessevdk/go-flags v1.4.0/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI=
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/jonboulle/clockwork v0.1.0/go.mod h1:Ii8DK3G1RaLaWxj9trq07+26W01tbo22gdxWY5EU2bo=
github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY=
github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y=
github.com/json-iterator/go v1.1.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=
@@ -305,13 +439,23 @@ github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1
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/karrick/godirwalk v1.8.0/go.mod h1:H5KPZjojv4lE+QYImBI8xVtrBRgYrIVsaRPx4tDPEn4=
github.com/karrick/godirwalk v1.10.3/go.mod h1:RoGL9dQei4vP9ilrpETWE8CLOZ1kiN0LhBygSwrAsHA=
github.com/kisielk/errcheck v1.1.0/go.mod h1:EZBBE59ingxPouuu3KfxchcWSUPOHkagtvWXihfKN4Q=
github.com/kisielk/errcheck v1.2.0/go.mod h1:/BMXB+zMLi60iA8Vv6Ksmxu/1UDYcXs4uQLJ+jE2L00=
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/klauspost/compress v1.9.8/go.mod h1:RyIbtBH6LamlWaDj8nUwkbUhJ87Yi3uG0guNDohfE1A=
github.com/klauspost/compress v1.14.1 h1:hLQYb23E8/fO+1u53d02A97a8UnsddcvYzq4ERRU4ds=
github.com/klauspost/compress v1.14.1/go.mod h1:/3/Vjq9QcHkK5uEr5lBEmyoZ1iFhe47etQ6QUkpK6sk=
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/pretty v0.2.1 h1:Fmg33tUaq4/8ym9TJN1x7sLJnHVwhP33CNkpYV/7rwI=
github.com/kr/pretty v0.2.1/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=
@@ -327,9 +471,13 @@ github.com/mailru/easyjson v0.0.0-20180823135443-60711f1a8329/go.mod h1:C1wdFJiN
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 h1:aizVhC/NAAcKWb+5QsU1iNOZb4Yws5UO2I+aIprQITM=
github.com/mailru/easyjson v0.7.0/go.mod h1:KAzv3t3aY1NaHWoQz1+4F1ccyAH66Jk7yos7ldAVICs=
github.com/mailru/easyjson v0.7.1/go.mod h1:KAzv3t3aY1NaHWoQz1+4F1ccyAH66Jk7yos7ldAVICs=
github.com/mailru/easyjson v0.7.6 h1:8yTIVnZgCoiM1TgqoeTl+LfU5Jg6/xL3QhGQnimLYnA=
github.com/mailru/easyjson v0.7.6/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc=
github.com/markbates/oncer v0.0.0-20181203154359-bf2de49a0be2/go.mod h1:Ld9puTsIW75CHf65OeIOkyKbteujpZVXDpWK6YGZbxE=
github.com/markbates/pkger v0.17.1/go.mod h1:0JoVlrol20BSywW79rN3kdFFsE5xYM+rSCQDXbLhiuI=
github.com/markbates/safe v1.0.1/go.mod h1:nAqgmRi7cY2nqMc92/bSEeQA+R4OheNU2T1kNSCBdG0=
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=
@@ -347,6 +495,12 @@ github.com/mitchellh/gox v0.4.0/go.mod h1:Sd9lOJ0+aimLBi73mGofS1ycjY8lL3uZM3JPS4
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/mitchellh/mapstructure v1.3.2/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo=
github.com/mitchellh/mapstructure v1.3.3/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo=
github.com/mitchellh/mapstructure v1.4.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo=
github.com/mitchellh/mapstructure v1.4.1 h1:CpVNEelQCZBooIPDn+AR3NpivK/TIKU8bDxdASFVQag=
github.com/mitchellh/mapstructure v1.4.1/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo=
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=
@@ -356,12 +510,16 @@ github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lN
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/montanaflynn/stats v0.0.0-20171201202039-1bf9dbcd8cbe/go.mod h1:wL8QJuTMNUDYhXwkmfOly8iTdp5TEcJFWZD2D7SIkUc=
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/ohler55/ojg v1.12.12 h1:hepbQFn7GHAecTPmwS3j5dCiOLsOpzPLvhiqnlAVAoE=
github.com/ohler55/ojg v1.12.12/go.mod h1:LBbIVRAgoFbYBXQhRhuEpaJIqq+goSO63/FQ+nyJU88=
github.com/oklog/ulid v1.3.1 h1:EGfNDEx6MqHz8B3uNV6QAib1UR2Lm97sHi3ocA6ESJ4=
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=
@@ -372,14 +530,24 @@ github.com/onsi/gomega v1.7.0/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1Cpa
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/opentracing/opentracing-go v1.2.0 h1:uEJPy/1a5RIPAJ0Ov+OIO8OxWu77jEv+1B0VhjKrZUs=
github.com/opentracing/opentracing-go v1.2.0/go.mod h1:GxEUsuufX4nBwe+T+Wl9TAgYrxe9dPLANfrWvHYVTgc=
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/keto-client-go v0.7.0-alpha.1 h1:8iZz37j7YXFvFBxO/lAqFkGyQK7KysToQhAE4aNFzqE=
github.com/ory/keto-client-go v0.7.0-alpha.1/go.mod h1:xtlyH3xz9lgzvYnuzFXeGwaui0Swc/hxnmqzY2g8AYs=
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/pborman/uuid v1.2.0/go.mod h1:X/NO0urCmaxf9VXbdlT7C2Yzkj2IKimNn4k+gtPdI/k=
github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic=
github.com/pelletier/go-toml v1.4.0/go.mod h1:PN7xzY2wHTK0K9p34ErDQMlFxa51Fk0OUruD3k1mMwo=
github.com/pelletier/go-toml v1.7.0/go.mod h1:vwGMzjaWMwyfHwgIBhI2YUM4fB6nL6lVAvS1LBMMhTE=
github.com/peterbourgon/diskv v2.0.1+incompatible/go.mod h1:uqqh8zWWbv1HBMNONnaR/tNboyR3/BZd58JJSHlUSCU=
github.com/pierrec/lz4 v2.6.0+incompatible h1:Ix9yFKn1nSPBLFl/yZknTp8TU5G4Ps0JDmguYK6iH1A=
github.com/pierrec/lz4 v2.6.0+incompatible/go.mod h1:pdkljMzZIN41W+lC3N2tnIh5sFi+IEE17M5jbnwPHcY=
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=
@@ -406,15 +574,23 @@ github.com/prometheus/procfs v0.1.3/go.mod h1:lV6e/gmhEcM9IjHGsFOCxxuZ+z1YqCvr4O
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.1.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
github.com/rogpeppe/go-internal v1.2.2/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
github.com/rogpeppe/go-internal v1.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/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/segmentio/kafka-go v0.4.27 h1:sIhEozeL/TLN2mZ5dkG462vcGEWYKS+u31sXPjKhAM4=
github.com/segmentio/kafka-go v0.4.27/go.mod h1:XzMcoMjSzDGHcIwpWUI7GB43iKZ2fTVmryPSGLf/MPg=
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.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo=
github.com/sirupsen/logrus v1.4.1/go.mod h1:ni0Sbl8bgC9z8RoU9G6nDWqqs/fq4eDPysMBDgk/93Q=
github.com/sirupsen/logrus v1.4.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=
@@ -423,6 +599,7 @@ github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasO
github.com/spf13/afero v1.1.2/go.mod h1:j4pytiNVoe2o6bmDsKpLACNPDBIoEAkihy7loJ1B0CQ=
github.com/spf13/afero v1.2.2/go.mod h1:9ZxEEn6pIJ8Rxe320qSDBk6AsU0r9pR7Q4OcevTdifk=
github.com/spf13/cast v1.3.0/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE=
github.com/spf13/cobra v0.0.3/go.mod h1:1l0Ry5zgKvJasoi3XT1TypsSe7PqH0Sj9dhYf7v3XqQ=
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=
@@ -443,33 +620,61 @@ github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/
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/up9inc/basenine/client/go v0.0.0-20211130202146-cf837626a065 h1:kfciLExAWkJMeMoKtnO5G5czqNv5/d0zjupG2nAeBmo=
github.com/up9inc/basenine/client/go v0.0.0-20211130202146-cf837626a065/go.mod h1:SvJGPoa/6erhUQV7kvHBwM/0x5LyO6XaG2lUaCaKiUI=
github.com/up9inc/basenine/client/go v0.0.0-20220125035724-573fff0d5075 h1:Tp0yckZkvb8BNC+bB4B+fBuQdomTner6bOte1S8SYCo=
github.com/up9inc/basenine/client/go v0.0.0-20220125035724-573fff0d5075/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/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/xdg/scram v0.0.0-20180814205039-7eeb5667e42c/go.mod h1:lB8K/P019DLNhemzwFU4jHLhdvlE6uDZjXFejJXr49I=
github.com/xdg/stringprep v0.0.0-20180714160509-73f8eece6fdc/go.mod h1:Jhud4/sHMO4oL310DaZAKk9ZaJ08SJfe+sJh0HrGL1Y=
github.com/xdg/stringprep v1.0.0/go.mod h1:Jhud4/sHMO4oL310DaZAKk9ZaJ08SJfe+sJh0HrGL1Y=
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.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.mongodb.org/mongo-driver v1.3.0/go.mod h1:MSWZXKOynuguX+JSvwP8i+58jYCXxbia8HS3gZBapIE=
go.mongodb.org/mongo-driver v1.3.4/go.mod h1:MSWZXKOynuguX+JSvwP8i+58jYCXxbia8HS3gZBapIE=
go.mongodb.org/mongo-driver v1.4.3/go.mod h1:WcMNYLx/IlOxLe6JRJiv2uXuCz6zBLndR4SoGjYphSc=
go.mongodb.org/mongo-driver v1.4.4/go.mod h1:WcMNYLx/IlOxLe6JRJiv2uXuCz6zBLndR4SoGjYphSc=
go.mongodb.org/mongo-driver v1.4.6/go.mod h1:WcMNYLx/IlOxLe6JRJiv2uXuCz6zBLndR4SoGjYphSc=
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.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=
@@ -478,11 +683,15 @@ golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnf
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-20190422162423-af44ce270edf/go.mod h1:WFFai1msRO1wXaEeE5yQxYXgSfI8pQAWXbQop6sCtWE=
golang.org/x/crypto v0.0.0-20190506204251-e1dfcc566284/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20190530122614-20be4c3c3ed5/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=
@@ -537,6 +746,7 @@ golang.org/x/net v0.0.0-20190522155817-f3200d17e092/go.mod h1:HSz+uSET+XFnRR8LxR
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=
@@ -546,22 +756,37 @@ 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-20200602114024-627f9648deb9/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-20201202161906-c7110b5ffcbb/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
golang.org/x/net v0.0.0-20201224014010-6772e930b67b/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
golang.org/x/net v0.0.0-20210119194325-5f4716e94777/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
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 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=
@@ -573,10 +798,13 @@ golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5h
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-20190403152447-81d4e9dc473e/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=
@@ -597,7 +825,13 @@ golang.org/x/sys v0.0.0-20200217220822-9197077df867/go.mod h1:h1NjWce9XRLGQEsW7w
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=
@@ -623,6 +857,7 @@ golang.org/x/time v0.0.0-20210220033141-f8bda1e9f3ba h1:O8mE0/t419eoIwhTFpKVkHiT
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-20181030221726-6c7e314b6563/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=
@@ -630,9 +865,13 @@ golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3
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-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-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=
@@ -657,9 +896,19 @@ 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=
@@ -676,13 +925,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=
@@ -700,16 +956,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=
@@ -718,6 +989,7 @@ 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=
@@ -744,6 +1016,8 @@ 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-20200605160147-a5ece683394c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.0-20200615113413-eeeca48fe776/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=
@@ -754,6 +1028,7 @@ honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWh
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=
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=
@@ -787,5 +1062,6 @@ sigs.k8s.io/kustomize/kyaml v0.10.17/go.mod h1:mlQFagmkm1P+W4lZJbJ/yaxMd8PqMRSC4
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=

13
agent/keto/Dockerfile Normal file
View File

@@ -0,0 +1,13 @@
FROM oryd/keto:v0.7.0-alpha.1-sqlite
USER root
RUN apk add sqlite
RUN mkdir -p /etc/config/keto
COPY ./keto.yml /etc/config/keto/keto.yml
COPY ./start.sh /opt/start.sh
RUN chmod +x /opt/start.sh
ENTRYPOINT ["/opt/start.sh"]

View File

@@ -3,7 +3,7 @@ set -e
GCP_PROJECT=up9-docker-hub
REPOSITORY=gcr.io/$GCP_PROJECT
SERVER_NAME=mizu
SERVER_NAME=mizu-keto
GIT_BRANCH=$(git branch | grep \* | cut -d ' ' -f2 | tr '[:upper:]' '[:lower:]')
DOCKER_REPO=$REPOSITORY/$SERVER_NAME/$GIT_BRANCH
@@ -19,7 +19,7 @@ fi
echo "building ${DOCKER_TAGGED_BUILDS[@]}"
DOCKER_TAGS_ARGS=$(echo ${DOCKER_TAGGED_BUILDS[@]/#/-t }) # "-t FIRST_TAG -t SECOND_TAG ..."
docker build -f debug.Dockerfile $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} .
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

22
agent/keto/keto.yml Normal file
View File

@@ -0,0 +1,22 @@
version: v0.7.0-alpha.1
dsn: sqlite:///app/data/kratos.sqlite?_fk=true
log:
level: info
format: text
leak_sensitive_values: false
serve:
read:
host: 0.0.0.0
port: 4466
write:
host: 0.0.0.0
port: 4467
namespaces:
- id: 0
name: system
- id: 1
name: workspaces

4
agent/keto/start.sh Normal file
View File

@@ -0,0 +1,4 @@
#!/bin/sh
keto migrate up -c /etc/config/keto/keto.yml --yes # this initializes the db
keto serve -c /etc/config/keto/keto.yml # start keto

14
agent/kratos/Dockerfile Normal file
View 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"]

View File

@@ -0,0 +1,28 @@
#!/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}
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

View 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
View 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: false
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
View 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

View File

@@ -1,7 +1,6 @@
package main
import (
"context"
"encoding/json"
"errors"
"flag"
@@ -10,24 +9,23 @@ import (
"mizuserver/pkg/api"
"mizuserver/pkg/config"
"mizuserver/pkg/controllers"
"mizuserver/pkg/elastic"
"mizuserver/pkg/middlewares"
"mizuserver/pkg/models"
"mizuserver/pkg/providers"
"mizuserver/pkg/oas"
"mizuserver/pkg/routes"
"mizuserver/pkg/servicemap"
"mizuserver/pkg/up9"
"mizuserver/pkg/utils"
"net/http"
"os"
"os/exec"
"os/signal"
"path"
"path/filepath"
"plugin"
"regexp"
"sort"
"strconv"
"strings"
"syscall"
"time"
"github.com/up9inc/mizu/shared/kubernetes"
v1 "k8s.io/api/core/v1"
"github.com/antelman107/net-wait-go/wait"
@@ -40,6 +38,11 @@ import (
"github.com/up9inc/mizu/shared/logger"
"github.com/up9inc/mizu/tap"
tapApi "github.com/up9inc/mizu/tap/api"
amqpExt "github.com/up9inc/mizu/tap/extensions/amqp"
httpExt "github.com/up9inc/mizu/tap/extensions/http"
kafkaExt "github.com/up9inc/mizu/tap/extensions/kafka"
redisExt "github.com/up9inc/mizu/tap/extensions/redis"
)
var tapperMode = flag.Bool("tap", false, "Run in tapper mode without API")
@@ -56,9 +59,10 @@ var extensionsMap map[string]*tapApi.Extension // global
var startTime int64
const (
socketConnectionRetries = 10
socketConnectionRetries = 30
socketConnectionRetryDelay = time.Second * 2
socketHandshakeTimeout = time.Second * 2
uiIndexPath = "./site/index.html"
)
func main() {
@@ -115,20 +119,20 @@ func main() {
go pipeTapChannelToSocket(socketConnection, filteredOutputItemsChannel)
} else if *apiServerMode {
startBasenineServer(shared.BasenineHost, shared.BaseninePort)
configureBasenineServer(shared.BasenineHost, shared.BaseninePort)
startTime = time.Now().UnixNano() / int64(time.Millisecond)
api.StartResolving(*namespace)
outputItemsChannel := make(chan *tapApi.OutputChannelItem)
filteredOutputItemsChannel := make(chan *tapApi.OutputChannelItem)
enableExpFeatureIfNeeded()
go filterItems(outputItemsChannel, filteredOutputItemsChannel)
go api.StartReadingEntries(filteredOutputItemsChannel, nil, extensionsMap)
syncEntriesConfig := getSyncEntriesConfig()
if syncEntriesConfig != nil {
if err := up9.SyncEntries(syncEntriesConfig); err != nil {
panic(fmt.Sprintf("Error syncing entries, err: %v", err))
logger.Log.Error("Error syncing entries, err: %v", err)
}
}
@@ -149,16 +153,17 @@ func main() {
logger.Log.Info("Exiting")
}
func startBasenineServer(host string, port string) {
cmd := exec.Command("basenine", "-addr", host, "-port", port, "-persistent")
cmd.Dir = config.Config.AgentDatabasePath
cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr
err := cmd.Start()
if err != nil {
logger.Log.Panicf("Failed starting Basenine: %v", err)
func enableExpFeatureIfNeeded() {
if config.Config.OAS {
oas.GetOasGeneratorInstance().Start()
}
if config.Config.ServiceMap {
servicemap.GetInstance().SetConfig(config.Config)
}
elastic.GetInstance().Configure(config.Config.Elastic)
}
func configureBasenineServer(host string, port string) {
if !wait.New(
wait.WithProto("tcp"),
wait.WithWait(200*time.Millisecond),
@@ -166,25 +171,16 @@ func startBasenineServer(host string, port string) {
wait.WithDeadline(5*time.Second),
wait.WithDebug(true),
).Do([]string{fmt.Sprintf("%s:%s", host, port)}) {
logger.Log.Panicf("Basenine is not available: %v", err)
logger.Log.Panicf("Basenine is not available!")
}
// Make a channel to gracefully exit Basenine.
channel := make(chan os.Signal)
signal.Notify(channel, os.Interrupt, syscall.SIGTERM)
// Handle the channel.
go func() {
<-channel
cmd.Process.Signal(syscall.SIGTERM)
}()
// Limit the database size to default 200MB
err = basenine.Limit(host, port, config.Config.MaxDBSizeBytes)
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 {
@@ -197,36 +193,36 @@ func startBasenineServer(host string, port string) {
}
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))
extensions = make([]*tapApi.Extension, 4)
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
}
extensionAmqp := &tapApi.Extension{}
dissectorAmqp := amqpExt.NewDissector()
dissectorAmqp.Register(extensionAmqp)
extensionAmqp.Dissector = dissectorAmqp
extensions[0] = extensionAmqp
extensionsMap[extensionAmqp.Protocol.Name] = extensionAmqp
extensionHttp := &tapApi.Extension{}
dissectorHttp := httpExt.NewDissector()
dissectorHttp.Register(extensionHttp)
extensionHttp.Dissector = dissectorHttp
extensions[1] = extensionHttp
extensionsMap[extensionHttp.Protocol.Name] = extensionHttp
extensionKafka := &tapApi.Extension{}
dissectorKafka := kafkaExt.NewDissector()
dissectorKafka.Register(extensionKafka)
extensionKafka.Dissector = dissectorKafka
extensions[2] = extensionKafka
extensionsMap[extensionKafka.Protocol.Name] = extensionKafka
extensionRedis := &tapApi.Extension{}
dissectorRedis := redisExt.NewDissector()
dissectorRedis.Register(extensionRedis)
extensionRedis.Dissector = dissectorRedis
extensions[3] = extensionRedis
extensionsMap[extensionRedis.Protocol.Name] = extensionRedis
sort.Slice(extensions, func(i, j int) bool {
return extensions[i].Protocol.Priority < extensions[j].Protocol.Priority
@@ -251,32 +247,38 @@ func hostApi(socketHarOutputChannel chan<- *tapApi.OutputChannelItem) {
}
app.Use(DisableRootStaticCache())
app.Use(static.ServeRoot("/", "./site"))
app.Use(CORSMiddleware()) // This has to be called after the static middleware, does not work if its called before
if err := setUIFlags(); err != nil {
logger.Log.Errorf("Error setting ui mode, err: %v", err)
}
if config.Config.StandaloneMode {
app.Use(static.ServeRoot("/", "./site-standalone"))
} else {
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)
if config.Config.DaemonMode {
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
kubernetesProvider, err := kubernetes.NewProviderInCluster()
if err != nil {
logger.Log.Fatalf("error creating k8s provider: %+v", err)
}
if _, err := startMizuTapperSyncer(ctx, kubernetesProvider); err != nil {
logger.Log.Fatalf("error initializing tapper syncer: %+v", err)
}
go watchMizuEvents(ctx, kubernetesProvider, cancel)
}
utils.StartServer(app)
}
@@ -291,24 +293,25 @@ func DisableRootStaticCache() gin.HandlerFunc {
}
}
func CORSMiddleware() gin.HandlerFunc {
return func(c *gin.Context) {
c.Writer.Header().Set("Access-Control-Allow-Origin", "*")
c.Writer.Header().Set("Access-Control-Allow-Credentials", "true")
c.Writer.Header().Set("Access-Control-Allow-Headers", "Content-Type, Content-Length, Accept-Encoding, X-CSRF-Token, Authorization, accept, origin, Cache-Control, X-Requested-With")
c.Writer.Header().Set("Access-Control-Allow-Methods", "POST, OPTIONS, GET, PUT")
if c.Request.Method == "OPTIONS" {
c.AbortWithStatus(204)
return
}
c.Next()
func setUIFlags() error {
read, err := ioutil.ReadFile(uiIndexPath)
if err != nil {
return err
}
replacedContent := strings.Replace(string(read), "__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][]string {
var mapOfList map[string][]string
func parseEnvVar(env string) map[string][]v1.Pod {
var mapOfList map[string][]v1.Pod
val, present := os.LookupEnv(env)
@@ -318,12 +321,12 @@ func parseEnvVar(env string) map[string][]string {
err := json.Unmarshal([]byte(val), &mapOfList)
if err != nil {
panic(fmt.Sprintf("env var %s's value of %s is invalid! must be map[string][]string %v", env, mapOfList, err))
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() []string {
func getTapTargets() []v1.Pod {
nodeName := os.Getenv(shared.NodeNameEnvVar)
tappedAddressesPerNodeDict := parseEnvVar(shared.TappedAddressesPerNodeDictEnvVar)
return tappedAddressesPerNodeDict[nodeName]
@@ -428,106 +431,38 @@ func dialSocketWithRetry(socketAddress string, retryAmount int, retryDelay time.
time.Sleep(retryDelay)
}
} else {
go handleIncomingMessageAsTapper(socketConnection)
return socketConnection, nil
}
}
return nil, lastErr
}
func startMizuTapperSyncer(ctx context.Context, provider *kubernetes.Provider) (*kubernetes.MizuTapperSyncer, error) {
tapperSyncer, err := kubernetes.CreateAndStartMizuTapperSyncer(ctx, provider, kubernetes.TapperSyncerConfig{
TargetNamespaces: config.Config.TargetNamespaces,
PodFilterRegex: config.Config.TapTargetRegex.Regexp,
MizuResourcesNamespace: config.Config.MizuResourcesNamespace,
AgentImage: config.Config.AgentImage,
TapperResources: config.Config.TapperResources,
ImagePullPolicy: v1.PullPolicy(config.Config.PullPolicy),
LogLevel: config.Config.LogLevel,
IgnoredUserAgents: config.Config.IgnoredUserAgents,
MizuApiFilteringOptions: config.Config.MizuApiFilteringOptions,
MizuServiceAccountExists: true, //assume service account exists since daemon mode will not function without it anyway
Istio: config.Config.Istio,
})
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 tapPodChangeEvent, ok := <-tapperSyncer.TapPodChangesOut:
if !ok {
logger.Log.Debug("mizuTapperSyncer pod changes channel closed, ending listener loop")
return
}
tapStatus := shared.TapStatus{Pods: kubernetes.GetPodInfosForPods(tapperSyncer.CurrentlyTappedPods)}
serializedTapStatus, err := json.Marshal(shared.CreateWebSocketStatusMessage(tapStatus))
if err != nil {
logger.Log.Fatalf("error serializing tap status: %v", err)
}
api.BroadcastToBrowserClients(serializedTapStatus)
providers.TapStatus.Pods = tapStatus.Pods
providers.ExpectedTapperAmount = tapPodChangeEvent.ExpectedTapperAmount
case <-ctx.Done():
logger.Log.Debug("mizuTapperSyncer event listener loop exiting due to context done")
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
}
}
}()
return tapperSyncer, nil
}
func watchMizuEvents(ctx context.Context, kubernetesProvider *kubernetes.Provider, cancel context.CancelFunc) {
// Round down because k8s CreationTimestamp is given in 1 sec resolution.
startTime := time.Now().Truncate(time.Second)
mizuResourceRegex := regexp.MustCompile(fmt.Sprintf("^%s.*", kubernetes.MizuResourcesPrefix))
eventWatchHelper := kubernetes.NewEventWatchHelper(kubernetesProvider, mizuResourceRegex)
eventChan, errorChan := kubernetes.FilteredWatch(ctx, eventWatchHelper, []string{config.Config.MizuResourcesNamespace}, eventWatchHelper)
for {
select {
case wEvent, ok := <-eventChan:
if !ok {
eventChan = nil
continue
} 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)
}
}
event, err := wEvent.ToEvent()
if err != nil {
logger.Log.Errorf("error parsing Mizu resource event: %+v", err)
cancel()
}
if startTime.After(event.CreationTimestamp.Time) {
continue
}
if event.Type == v1.EventTypeWarning {
logger.Log.Warningf("resource %s in state %s - %s", event.Regarding.Name, event.Reason, event.Note)
}
case err, ok := <-errorChan:
if !ok {
errorChan = nil
continue
}
logger.Log.Errorf("error in watch mizu resource events loop: %+v", err)
cancel()
case <-ctx.Done():
logger.Log.Debugf("watching Mizu resource events loop, ctx done")
return
}
}
}

View File

@@ -5,6 +5,8 @@ import (
"context"
"encoding/json"
"fmt"
"mizuserver/pkg/elastic"
"mizuserver/pkg/har"
"mizuserver/pkg/holder"
"mizuserver/pkg/providers"
"os"
@@ -13,15 +15,17 @@ import (
"strings"
"time"
"github.com/google/martian/har"
"mizuserver/pkg/servicemap"
"mizuserver/pkg/models"
"mizuserver/pkg/oas"
"mizuserver/pkg/resolver"
"mizuserver/pkg/utils"
"github.com/up9inc/mizu/shared"
"github.com/up9inc/mizu/shared/logger"
tapApi "github.com/up9inc/mizu/tap/api"
"mizuserver/pkg/models"
"mizuserver/pkg/resolver"
"mizuserver/pkg/utils"
basenine "github.com/up9inc/basenine/client/go"
)
@@ -114,38 +118,41 @@ func startReadingChannel(outputItems <-chan *tapApi.OutputChannelItem, extension
}
for item := range outputItems {
providers.EntryAdded()
extension := extensionsMap[item.Protocol.Name]
resolvedSource, resolvedDestionation := resolveIP(item.ConnectionInfo)
mizuEntry := extension.Dissector.Analyze(item, resolvedSource, resolvedDestionation)
baseEntry := extension.Dissector.Summarize(mizuEntry)
mizuEntry.Base = baseEntry
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)
baseEntry.ContractStatus = contract.Status
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)
harEntry, err := har.NewEntry(mizuEntry.Request, mizuEntry.Response, mizuEntry.StartTime, mizuEntry.ElapsedTime)
if err == nil {
rules, _, _ := models.RunValidationRulesState(*harEntry, mizuEntry.Service)
baseEntry.Rules = rules
rules, _, _ := models.RunValidationRulesState(*harEntry, mizuEntry.Destination.Name)
mizuEntry.Rules = rules
}
oas.GetOasGeneratorInstance().PushEntry(harEntry)
}
data, err := json.Marshal(mizuEntry)
if err != nil {
panic(err)
}
providers.EntryAdded(len(data))
connection.SendText(string(data))
servicemap.GetInstance().NewTCPEntry(mizuEntry.Source, mizuEntry.Destination, &item.Protocol)
elastic.GetInstance().PushEntry(mizuEntry)
}
}

View File

@@ -6,7 +6,6 @@ import (
"fmt"
"mizuserver/pkg/models"
"net/http"
"strconv"
"sync"
"time"
@@ -16,6 +15,7 @@ import (
"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 {
@@ -49,7 +49,8 @@ func WebSocketRoutes(app *gin.Engine, eventHandlers EventHandlers, startTime int
app.GET("/ws", func(c *gin.Context) {
websocketHandler(c.Writer, c.Request, eventHandlers, false, startTime)
})
app.GET("/wsTapper", func(c *gin.Context) {
app.GET("/wsTapper", func(c *gin.Context) { // TODO: add m2m authentication to this route
websocketHandler(c.Writer, c.Request, eventHandlers, true, startTime)
})
}
@@ -84,10 +85,10 @@ func websocketHandler(w http.ResponseWriter, r *http.Request, eventHandlers Even
meta := make(chan []byte)
defer func() {
socketCleanup(socketId, connectedWebsockets[socketId])
data <- []byte(basenine.CloseChannel)
meta <- []byte(basenine.CloseChannel)
connection.Close()
socketCleanup(socketId, connectedWebsockets[socketId])
}()
eventHandlers.WebSocketConnect(socketId, isTapper)
@@ -95,84 +96,76 @@ func websocketHandler(w http.ResponseWriter, r *http.Request, eventHandlers Even
startTimeBytes, _ := models.CreateWebsocketStartTimeMessage(startTime)
SendToSocket(socketId, startTimeBytes)
queryRecieved := false
for {
_, msg, err := ws.ReadMessage()
if err != nil {
logger.Log.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
}
if !queryRecieved {
if !isTapper && !isQuerySet {
queryRecieved = true
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 dataMap map[string]interface{}
err = json.Unmarshal(bytes, &dataMap)
base := dataMap["base"].(map[string]interface{})
base["id"] = uint(dataMap["id"].(float64))
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)
}
} else {
id, err := strconv.Atoi(string(msg))
if !isTapper && !isQuerySet {
query := string(msg)
err = basenine.Validate(shared.BasenineHost, shared.BaseninePort, query)
if err != nil {
continue
toastBytes, _ := models.CreateWebsocketToastMessage(&models.ToastMessage{
Type: "error",
AutoClose: 5000,
Text: fmt.Sprintf("Syntax error: %s", err.Error()),
})
SendToSocket(socketId, toastBytes)
break
}
focusEntryBytes, _ := models.CreateWebsocketFocusEntry(id)
SendToSocket(socketId, focusEntryBytes)
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)
}
}
}

View File

@@ -5,6 +5,7 @@ import (
"fmt"
"mizuserver/pkg/models"
"mizuserver/pkg/providers"
"mizuserver/pkg/providers/tappers"
"mizuserver/pkg/up9"
"sync"
@@ -29,7 +30,7 @@ func init() {
func (h *RoutesEventHandlers) WebSocketConnect(socketId int, isTapper bool) {
if isTapper {
logger.Log.Infof("Websocket event - Tapper connected, socket ID: %d", socketId)
providers.TapperAdded()
tappers.Connected()
} else {
logger.Log.Infof("Websocket event - Browser socket connected, socket ID: %d", socketId)
socketListLock.Lock()
@@ -41,7 +42,7 @@ func (h *RoutesEventHandlers) WebSocketConnect(socketId int, isTapper bool) {
func (h *RoutesEventHandlers) WebSocketDisconnect(socketId int, isTapper bool) {
if isTapper {
logger.Log.Infof("Websocket event - Tapper disconnected, socket ID: %d", socketId)
providers.TapperRemoved()
tappers.Disconnected()
} else {
logger.Log.Infof("Websocket event - Browser socket disconnected, socket ID: %d", socketId)
socketListLock.Lock()
@@ -83,7 +84,6 @@ func (h *RoutesEventHandlers) WebSocketMessage(_ int, message []byte) {
if err != nil {
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:

View File

@@ -4,7 +4,6 @@ import (
"encoding/json"
"fmt"
"github.com/up9inc/mizu/shared"
"github.com/up9inc/mizu/tap/api"
"io/ioutil"
"os"
)
@@ -12,7 +11,6 @@ import (
// these values are used when the config.json file is not present
const (
defaultMaxDatabaseSizeBytes int64 = 200 * 1000 * 1000
defaultRegexTarget string = ".*"
DefaultDatabasePath string = "./entries"
)
@@ -48,14 +46,8 @@ func applyDefaultConfig() error {
}
func getDefaultConfig() (*shared.MizuAgentConfig, error) {
regex, err := api.CompileRegexToSerializableRegexp(defaultRegexTarget)
if err != nil {
return nil, err
}
return &shared.MizuAgentConfig{
TapTargetRegex: *regex,
MaxDBSizeBytes: defaultMaxDatabaseSizeBytes,
AgentDatabasePath: DefaultDatabasePath,
DaemonMode: false,
}, nil
}

View 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
}

View File

@@ -2,8 +2,8 @@ package controllers
import (
"encoding/json"
"mizuserver/pkg/har"
"mizuserver/pkg/models"
"mizuserver/pkg/utils"
"mizuserver/pkg/validation"
"net/http"
"strconv"
@@ -64,8 +64,8 @@ func GetEntries(c *gin.Context) {
var dataSlice []interface{}
for _, row := range data {
var dataMap map[string]interface{}
err = json.Unmarshal(row, &dataMap)
var entry *tapApi.Entry
err = json.Unmarshal(row, &entry)
if err != nil {
c.JSON(http.StatusBadRequest, gin.H{
"error": true,
@@ -76,8 +76,7 @@ func GetEntries(c *gin.Context) {
return // exit
}
base := dataMap["base"].(map[string]interface{})
base["id"] = uint(dataMap["id"].(float64))
base := tapApi.Summarize(entry)
dataSlice = append(dataSlice, base)
}
@@ -95,9 +94,19 @@ func GetEntries(c *gin.Context) {
}
func GetEntry(c *gin.Context) {
singleEntryRequest := &models.SingleEntryRequest{}
if err := c.BindQuery(singleEntryRequest); err != nil {
c.JSON(http.StatusBadRequest, err)
}
validationError := validation.Validate(singleEntryRequest)
if validationError != nil {
c.JSON(http.StatusBadRequest, validationError)
}
id, _ := strconv.Atoi(c.Param("id"))
var entry tapApi.MizuEntry
bytes, err := basenine.Single(shared.BasenineHost, shared.BaseninePort, id)
var entry *tapApi.Entry
bytes, err := basenine.Single(shared.BasenineHost, shared.BaseninePort, id, singleEntryRequest.Query)
if Error(c, err) {
return // exit
}
@@ -118,14 +127,14 @@ func GetEntry(c *gin.Context) {
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.Service)
harEntry, _ := har.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.MizuEntryWrapper{
c.JSON(http.StatusOK, tapApi.EntryWrapper{
Protocol: entry.Protocol,
Representation: string(representation),
BodySize: bodySize,

View File

@@ -0,0 +1,23 @@
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)
}
}
func SetupAdminUser(c *gin.Context) {
token, err, formErrorMessages := providers.CreateAdminUser(c.PostForm("password"), c.Request.Context())
handleRegistration(token, err, formErrorMessages, c)
}

View 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)
}

View 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
}

View 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)
}

View 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))
}

View File

@@ -2,49 +2,49 @@ package controllers
import (
"encoding/json"
"fmt"
"mizuserver/pkg/api"
"mizuserver/pkg/config"
"mizuserver/pkg/holder"
"mizuserver/pkg/providers"
"mizuserver/pkg/up9"
"mizuserver/pkg/validation"
"net/http"
"github.com/gin-gonic/gin"
"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) {
if config.Config.DaemonMode {
if providers.ExpectedTapperAmount != providers.TappersCount {
c.JSON(http.StatusInternalServerError, fmt.Sprintf("expecting more tappers than are actually connected (%d expected, %d connected)", providers.ExpectedTapperAmount, providers.TappersCount))
return
}
tappersStatus := make([]*shared.TapperStatus, 0)
for _, value := range tappers.GetStatus() {
tappersStatus = append(tappersStatus, value)
}
response := shared.HealthResponse{
TapStatus: providers.TapStatus,
TappersCount: providers.TappersCount,
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
}
logger.Log.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 {
logger.Log.Errorf("Could not Marshal message %v", err)
} else {
@@ -52,8 +52,25 @@ func PostTappedPods(c *gin.Context) {
}
}
func GetTappersCount(c *gin.Context) {
c.JSON(http.StatusOK, providers.TappersCount)
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) {
@@ -67,7 +84,8 @@ func GetAuthStatus(c *gin.Context) {
}
func GetTappingStatus(c *gin.Context) {
c.JSON(http.StatusOK, providers.TapStatus)
tappedPodsStatus := tappedPods.GetTappedPodsStatus()
c.JSON(http.StatusOK, tappedPodsStatus)
}
func AnalyzeInformation(c *gin.Context) {

View File

@@ -0,0 +1,46 @@
package controllers
import (
"mizuserver/pkg/providers"
"github.com/gin-gonic/gin"
"github.com/up9inc/mizu/shared/logger"
ory "github.com/ory/kratos-client-go"
)
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) {
token, _, err, formErrorMessages := providers.RegisterUser(c.PostForm("username"), c.PostForm("password"), c.Request.Context())
handleRegistration(token, err, formErrorMessages, c)
}
func handleRegistration(token *string, err error, formErrorMessages map[string][]ory.UiText, c *gin.Context) {
if 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(201, gin.H{"token": token})
}
}

View File

@@ -0,0 +1,120 @@
package elastic
import (
"bytes"
"crypto/tls"
"encoding/json"
"github.com/elastic/go-elasticsearch/v7"
"github.com/up9inc/mizu/shared"
"github.com/up9inc/mizu/shared/logger"
"github.com/up9inc/mizu/tap/api"
"net/http"
"sync"
"time"
)
type client struct {
es *elasticsearch.Client
index string
insertedCount int
}
var instance *client
var once sync.Once
func GetInstance() *client {
once.Do(func() {
instance = newClient()
})
return instance
}
func (client *client) Configure(config shared.ElasticConfig) {
if config.Url == "" || config.User == "" || config.Password == "" {
logger.Log.Infof("No elastic configuration was supplied, elastic exporter disabled")
return
}
transport := http.DefaultTransport
tlsClientConfig := &tls.Config{InsecureSkipVerify: true}
transport.(*http.Transport).TLSClientConfig = tlsClientConfig
cfg := elasticsearch.Config{
Addresses: []string{config.Url},
Username: config.User,
Password: config.Password,
Transport: transport,
}
es, err := elasticsearch.NewClient(cfg)
if err != nil {
logger.Log.Fatalf("Failed to initialize elastic client %v", err)
}
// Have the client instance return a response
res, err := es.Info()
if err != nil {
logger.Log.Fatalf("Elastic client.Info() ERROR: %v", err)
} else {
client.es = es
client.index = "mizu_traffic_http_" + time.Now().Format("2006_01_02_15_04")
client.insertedCount = 0
logger.Log.Infof("Elastic client configured, index: %s, cluster info: %v", client.index, res)
}
defer res.Body.Close()
}
func newClient() *client {
return &client{
es: nil,
index: "",
}
}
type httpEntry struct {
Source *api.TCP `json:"src"`
Destination *api.TCP `json:"dst"`
Outgoing bool `json:"outgoing"`
CreatedAt time.Time `json:"createdAt"`
Request map[string]interface{} `json:"request"`
Response map[string]interface{} `json:"response"`
Summary string `json:"summary"`
Method string `json:"method"`
Status int `json:"status"`
ElapsedTime int64 `json:"elapsedTime"`
Path string `json:"path"`
}
func (client *client) PushEntry(entry *api.Entry) {
if client.es == nil {
return
}
if entry.Protocol.Name != "http" {
return
}
entryToPush := httpEntry{
Source: entry.Source,
Destination: entry.Destination,
Outgoing: entry.Outgoing,
CreatedAt: entry.StartTime,
Request: entry.Request,
Response: entry.Response,
Summary: entry.Summary,
Method: entry.Method,
Status: entry.Status,
ElapsedTime: entry.ElapsedTime,
Path: entry.Path,
}
entryJson, err := json.Marshal(entryToPush)
if err != nil {
logger.Log.Errorf("json.Marshal ERROR: %v", err)
return
}
var buffer bytes.Buffer
buffer.WriteString(string(entryJson))
res, _ := client.es.Index(client.index, &buffer)
if res.StatusCode == 201 {
client.insertedCount += 1
}
}

375
agent/pkg/har/types.go Normal file
View File

@@ -0,0 +1,375 @@
package har
import (
"encoding/base64"
"github.com/up9inc/mizu/shared/logger"
"time"
"unicode/utf8"
)
/*
HTTP Archive (HAR) format
https://w3c.github.io/web-performance/specs/HAR/Overview.html
*/
// HAR is a container type for deserialization
type HAR struct {
Log Log `json:"log"`
}
// Log represents the root of the exported data. This object MUST be present and its name MUST be "log".
type Log struct {
// The object contains the following name/value pairs:
// Required. Version number of the format.
Version string `json:"version"`
// Required. An object of type creator that contains the name and version
// information of the log creator application.
Creator Creator `json:"creator"`
// Optional. An object of type browser that contains the name and version
// information of the user agent.
Browser Browser `json:"browser"`
// Optional. An array of objects of type page, each representing one exported
// (tracked) page. Leave out this field if the application does not support
// grouping by pages.
Pages []Page `json:"pages,omitempty"`
// Required. An array of objects of type entry, each representing one
// exported (tracked) HTTP request.
Entries []Entry `json:"entries"`
// Optional. A comment provided by the user or the application. Sorting
// entries by startedDateTime (starting from the oldest) is preferred way how
// to export data since it can make importing faster. However the reader
// application should always make sure the array is sorted (if required for
// the import).
Comment string `json:"comment"`
}
// Creator contains information about the log creator application
type Creator struct {
// Required. The name of the application that created the log.
Name string `json:"name"`
// Required. The version number of the application that created the log.
Version string `json:"version"`
// Optional. A comment provided by the user or the application.
Comment string `json:"comment,omitempty"`
}
// Browser that created the log
type Browser struct {
// Required. The name of the browser that created the log.
Name string `json:"name"`
// Required. The version number of the browser that created the log.
Version string `json:"version"`
// Optional. A comment provided by the user or the browser.
Comment string `json:"comment"`
}
// Page object for every exported web page and one <entry> object for every HTTP request.
// In case when an HTTP trace tool isn't able to group requests by a page,
// the <pages> object is empty and individual requests doesn't have a parent page.
type Page struct {
/* There is one <page> object for every exported web page and one <entry>
object for every HTTP request. In case when an HTTP trace tool isn't able to
group requests by a page, the <pages> object is empty and individual
requests doesn't have a parent page.
*/
// Date and time stamp for the beginning of the page load
// (ISO 8601 YYYY-MM-DDThh:mm:ss.sTZD, e.g. 2009-07-24T19:20:30.45+01:00).
StartedDateTime string `json:"startedDateTime"`
// Unique identifier of a page within the . Entries use it to refer the parent page.
ID string `json:"id"`
// Page title.
Title string `json:"title"`
// Detailed timing info about page load.
PageTiming PageTiming `json:"pageTiming"`
// (new in 1.2) A comment provided by the user or the application.
Comment string `json:"comment,omitempty"`
}
// PageTiming describes timings for various events (states) fired during the page load.
// All times are specified in milliseconds. If a time info is not available appropriate field is set to -1.
type PageTiming struct {
// Content of the page loaded. Number of milliseconds since page load started
// (page.startedDateTime). Use -1 if the timing does not apply to the current
// request.
// Depeding on the browser, onContentLoad property represents DOMContentLoad
// event or document.readyState == interactive.
OnContentLoad int `json:"onContentLoad"`
// Page is loaded (onLoad event fired). Number of milliseconds since page
// load started (page.startedDateTime). Use -1 if the timing does not apply
// to the current request.
OnLoad int `json:"onLoad"`
// (new in 1.2) A comment provided by the user or the application.
Comment string `json:"comment"`
}
// Entry is a unique, optional Reference to the parent page.
// Leave out this field if the application does not support grouping by pages.
type Entry struct {
Pageref string `json:"pageref,omitempty"`
// Date and time stamp of the request start
// (ISO 8601 YYYY-MM-DDThh:mm:ss.sTZD).
StartedDateTime string `json:"startedDateTime"`
// Total elapsed time of the request in milliseconds. This is the sum of all
// timings available in the timings object (i.e. not including -1 values) .
Time int `json:"time"`
// Detailed info about the request.
Request Request `json:"request"`
// Detailed info about the response.
Response Response `json:"response"`
// Info about cache usage.
Cache Cache `json:"cache"`
// Detailed timing info about request/response round trip.
PageTimings PageTimings `json:"pageTimings"`
// optional (new in 1.2) IP address of the server that was connected
// (result of DNS resolution).
ServerIPAddress string `json:"serverIPAddress,omitempty"`
// optional (new in 1.2) Unique ID of the parent TCP/IP connection, can be
// the client port number. Note that a port number doesn't have to be unique
// identifier in cases where the port is shared for more connections. If the
// port isn't available for the application, any other unique connection ID
// can be used instead (e.g. connection index). Leave out this field if the
// application doesn't support this info.
Connection string `json:"connection,omitempty"`
// (new in 1.2) A comment provided by the user or the application.
Comment string `json:"comment,omitempty"`
}
// Request contains detailed info about performed request.
type Request struct {
// Request method (GET, POST, ...).
Method string `json:"method"`
// Absolute URL of the request (fragments are not included).
URL string `json:"url"`
// Request HTTP Version.
HTTPVersion string `json:"httpVersion"`
// List of cookie objects.
Cookies []Cookie `json:"cookies"`
// List of header objects.
Headers []NVP `json:"headers"`
// List of query parameter objects.
QueryString []NVP `json:"queryString"`
// Posted data.
PostData PostData `json:"postData"`
// Total number of bytes from the start of the HTTP request message until
// (and including) the double CRLF before the body. Set to -1 if the info
// is not available.
HeaderSize int `json:"headerSize"`
// Size of the request body (POST data payload) in bytes. Set to -1 if the
// info is not available.
BodySize int `json:"bodySize"`
// (new in 1.2) A comment provided by the user or the application.
Comment string `json:"comment"`
}
// Response contains detailed info about the response.
type Response struct {
// Response status.
Status int `json:"status"`
// Response status description.
StatusText string `json:"statusText"`
// Response HTTP Version.
HTTPVersion string `json:"httpVersion"`
// List of cookie objects.
Cookies []Cookie `json:"cookies"`
// List of header objects.
Headers []NVP `json:"headers"`
// Details about the response body.
Content Content `json:"content"`
// Redirection target URL from the Location response header.
RedirectURL string `json:"redirectURL"`
// Total number of bytes from the start of the HTTP response message until
// (and including) the double CRLF before the body. Set to -1 if the info is
// not available.
// The size of received response-headers is computed only from headers that
// are really received from the server. Additional headers appended by the
// browser are not included in this number, but they appear in the list of
// header objects.
HeadersSize int `json:"headersSize"`
// Size of the received response body in bytes. Set to zero in case of
// responses coming from the cache (304). Set to -1 if the info is not
// available.
BodySize int `json:"bodySize"`
// optional (new in 1.2) A comment provided by the user or the application.
Comment string `json:"comment,omitempty"`
}
// Cookie contains list of all cookies (used in <request> and <response> objects).
type Cookie struct {
// The name of the cookie.
Name string `json:"name"`
// The cookie value.
Value string `json:"value"`
// optional The path pertaining to the cookie.
Path string `json:"path,omitempty"`
// optional The host of the cookie.
Domain string `json:"domain,omitempty"`
// optional Cookie expiration time.
// (ISO 8601 YYYY-MM-DDThh:mm:ss.sTZD, e.g. 2009-07-24T19:20:30.123+02:00).
Expires string `json:"expires,omitempty"`
// optional Set to true if the cookie is HTTP only, false otherwise.
HTTPOnly bool `json:"httpOnly,omitempty"`
// optional (new in 1.2) True if the cookie was transmitted over ssl, false
// otherwise.
Secure bool `json:"secure,omitempty"`
// optional (new in 1.2) A comment provided by the user or the application.
Comment string `json:"comment,omitempty"`
}
// NVP is simply a name/value pair with a comment
type NVP struct {
Name string `json:"name"`
Value string `json:"value"`
Comment string `json:"comment,omitempty"`
}
// PostData describes posted data, if any (embedded in <request> object).
type PostData struct {
// Mime type of posted data.
MimeType string `json:"mimeType"`
// List of posted parameters (in case of URL encoded parameters).
Params []PostParam `json:"params"`
// Plain text posted data
Text string `json:"text"`
// optional (new in 1.2) A comment provided by the user or the
// application.
Comment string `json:"comment,omitempty"`
}
func (d PostData) B64Decoded() (bool, []byte, string) {
// there is a weird gap in HAR spec 1.2, that does not define encoding for binary POST bodies
// we have own convention of putting `base64` into comment field to handle it similar to response `Content`
return b64Decoded(d.Comment, d.Text)
}
// PostParam is a list of posted parameters, if any (embedded in <postData> object).
type PostParam struct {
// name of a posted parameter.
Name string `json:"name"`
// optional value of a posted parameter or content of a posted file.
Value string `json:"value,omitempty"`
// optional name of a posted file.
FileName string `json:"fileName,omitempty"`
// optional content type of a posted file.
ContentType string `json:"contentType,omitempty"`
// optional (new in 1.2) A comment provided by the user or the application.
Comment string `json:"comment,omitempty"`
}
// Content describes details about response content (embedded in <response> object).
type Content struct {
// Length of the returned content in bytes. Should be equal to
// response.bodySize if there is no compression and bigger when the content
// has been compressed.
Size int `json:"size"`
// optional Number of bytes saved. Leave out this field if the information
// is not available.
Compression int `json:"compression,omitempty"`
// MIME type of the response text (value of the Content-Type response
// header). The charset attribute of the MIME type is included (if
// available).
MimeType string `json:"mimeType"`
// optional Response body sent from the server or loaded from the browser
// cache. This field is populated with textual content only. The text field
// is either HTTP decoded text or a encoded (e.g. "base64") representation of
// the response body. Leave out this field if the information is not
// available.
Text string `json:"text,omitempty"`
// optional (new in 1.2) Encoding used for response text field e.g
// "base64". Leave out this field if the text field is HTTP decoded
// (decompressed & unchunked), than trans-coded from its original character
// set into UTF-8.
Encoding string `json:"encoding,omitempty"`
// optional (new in 1.2) A comment provided by the user or the application.
Comment string `json:"comment,omitempty"`
}
func (c Content) B64Decoded() (bool, []byte, string) {
return b64Decoded(c.Encoding, c.Text)
}
func b64Decoded(enc string, text string) (isBinary bool, asBytes []byte, asString string) {
if enc == "base64" {
decoded, err := base64.StdEncoding.DecodeString(text)
if err != nil {
logger.Log.Warningf("Failed to decode content as base64: %s", text)
return false, []byte(text), text
}
valid := utf8.Valid(decoded)
return !valid, decoded, string(decoded)
} else {
return false, nil, text
}
}
// Cache contains info about a request coming from browser cache.
type Cache struct {
// optional State of a cache entry before the request. Leave out this field
// if the information is not available.
BeforeRequest CacheObject `json:"beforeRequest,omitempty"`
// optional State of a cache entry after the request. Leave out this field if
// the information is not available.
AfterRequest CacheObject `json:"afterRequest,omitempty"`
// optional (new in 1.2) A comment provided by the user or the application.
Comment string `json:"comment,omitempty"`
}
// CacheObject is used by both beforeRequest and afterRequest
type CacheObject struct {
// optional - Expiration time of the cache entry.
Expires string `json:"expires,omitempty"`
// The last time the cache entry was opened.
LastAccess string `json:"lastAccess"`
// Etag
ETag string `json:"eTag"`
// The number of times the cache entry has been opened.
HitCount int `json:"hitCount"`
// optional (new in 1.2) A comment provided by the user or the application.
Comment string `json:"comment,omitempty"`
}
// PageTimings describes various phases within request-response round trip.
// All times are specified in milliseconds.
type PageTimings struct {
Blocked int `json:"blocked,omitempty"`
// optional - Time spent in a queue waiting for a network connection. Use -1
// if the timing does not apply to the current request.
DNS int `json:"dns,omitempty"`
// optional - DNS resolution time. The time required to resolve a host name.
// Use -1 if the timing does not apply to the current request.
Connect int `json:"connect,omitempty"`
// optional - Time required to create TCP connection. Use -1 if the timing
// does not apply to the current request.
Send int `json:"send"`
// Time required to send HTTP request to the server.
Wait int `json:"wait"`
// Waiting for a response from the server.
Receive int `json:"receive"`
// Time required to read entire response from the server (or cache).
Ssl int `json:"ssl,omitempty"`
// optional (new in 1.2) - Time required for SSL/TLS negotiation. If this
// field is defined then the time is also included in the connect field (to
// ensure backward compatibility with HAR 1.1). Use -1 if the timing does not
// apply to the current request.
Comment string `json:"comment,omitempty"`
// optional (new in 1.2) - A comment provided by the user or the application.
}
// TestResult contains results for an individual HTTP request
type TestResult struct {
URL string `json:"url"`
Status int `json:"status"` // 200, 500, etc.
StartTime time.Time `json:"startTime"`
EndTime time.Time `json:"endTime"`
Latency int `json:"latency"` // milliseconds
Method string `json:"method"`
HarFile string `json:"harfile"`
}
// aliases for martian lib compatibility
type Header = NVP
type QueryString = NVP
type Param = PostParam
type Timings = PageTimings

View File

@@ -0,0 +1,38 @@
package har
import "testing"
func TestContentEncoded(t *testing.T) {
testCases := []struct {
text string
isBinary bool
expectedStr string
binaryLen int
}{
{"not-base64", false, "not-base64", 10},
{"dGVzdA==", false, "test", 4},
{"test", true, "\f@A", 3}, // valid UTF-8 with some non-printable chars
{"IsDggPCAgPiAgID8gICAgN/vv/e/v/u/v7/9v7+/vyIKIu+3kO+3ke+3ku+3k++3lO+3le+3lu+3l++3mO+3me+3mu+3m++3nO+3ne+3nu+3n++3oO+3oe+3ou+3o++3pO+3pe+3pu+3p++3qO+3qe+3qu+3q++3rO+3re+3ru+3ryIK", true, "test", 132}, // invalid UTF-8 (thus binary), taken from https://www.cl.cam.ac.uk/~mgk25/ucs/examples/UTF-8-test.txt
}
for _, tc := range testCases {
c := Content{
Encoding: "base64",
Text: tc.text,
}
isBinary, asBytes, asString := c.B64Decoded()
_ = asBytes
if tc.isBinary != isBinary {
t.Errorf("Binary flag mismatch: %t != %t", tc.isBinary, isBinary)
}
if !isBinary && tc.expectedStr != asString {
t.Errorf("Decode value mismatch: %s != %s", tc.expectedStr, asString)
}
if tc.binaryLen != len(asBytes) {
t.Errorf("Binary len mismatch: %d != %d", tc.binaryLen, len(asBytes))
}
}
}

View File

@@ -1,4 +1,4 @@
package utils
package har
import (
"bytes"
@@ -8,7 +8,6 @@ import (
"strings"
"time"
"github.com/google/martian/har"
"github.com/up9inc/mizu/shared/logger"
)
@@ -55,14 +54,14 @@ import (
// return cookies
//}
func BuildHeaders(rawHeaders []interface{}) ([]har.Header, string, string, string, string, string) {
func BuildHeaders(rawHeaders []interface{}) ([]Header, string, string, string, string, string) {
var host, scheme, authority, path, status string
headers := make([]har.Header, 0, len(rawHeaders))
headers := make([]Header, 0, len(rawHeaders))
for _, header := range rawHeaders {
h := header.(map[string]interface{})
headers = append(headers, har.Header{
headers = append(headers, Header{
Name: h["name"].(string),
Value: h["value"].(string),
})
@@ -87,8 +86,8 @@ func BuildHeaders(rawHeaders []interface{}) ([]har.Header, string, string, strin
return headers, host, scheme, authority, path, status
}
func BuildPostParams(rawParams []interface{}) []har.Param {
params := make([]har.Param, 0, len(rawParams))
func BuildPostParams(rawParams []interface{}) []Param {
params := make([]Param, 0, len(rawParams))
for _, param := range rawParams {
p := param.(map[string]interface{})
name := ""
@@ -108,10 +107,10 @@ func BuildPostParams(rawParams []interface{}) []har.Param {
contentType = p["contentType"].(string)
}
params = append(params, har.Param{
params = append(params, Param{
Name: name,
Value: value,
Filename: fileName,
FileName: fileName,
ContentType: contentType,
})
}
@@ -119,9 +118,9 @@ func BuildPostParams(rawParams []interface{}) []har.Param {
return params
}
func NewRequest(request map[string]interface{}) (harRequest *har.Request, err error) {
func NewRequest(request map[string]interface{}) (harRequest *Request, err error) {
headers, host, scheme, authority, path, _ := BuildHeaders(request["_headers"].([]interface{}))
cookies := make([]har.Cookie, 0) // BuildCookies(request["_cookies"].([]interface{}))
cookies := make([]Cookie, 0) // BuildCookies(request["_cookies"].([]interface{}))
postData, _ := request["postData"].(map[string]interface{})
mimeType, _ := postData["mimeType"]
@@ -134,10 +133,10 @@ func NewRequest(request map[string]interface{}) (harRequest *har.Request, err er
postDataText = text.(string)
}
queryString := make([]har.QueryString, 0)
queryString := make([]QueryString, 0)
for _, _qs := range request["_queryString"].([]interface{}) {
qs := _qs.(map[string]interface{})
queryString = append(queryString, har.QueryString{
queryString = append(queryString, QueryString{
Name: qs["name"].(string),
Value: qs["value"].(string),
})
@@ -148,21 +147,21 @@ func NewRequest(request map[string]interface{}) (harRequest *har.Request, err er
url = fmt.Sprintf("%s://%s%s", scheme, authority, path)
}
harParams := make([]har.Param, 0)
harParams := make([]Param, 0)
if postData["params"] != nil {
harParams = BuildPostParams(postData["params"].([]interface{}))
}
harRequest = &har.Request{
harRequest = &Request{
Method: request["method"].(string),
URL: url,
HTTPVersion: request["httpVersion"].(string),
HeadersSize: -1,
BodySize: int64(bytes.NewBufferString(postDataText).Len()),
HeaderSize: -1,
BodySize: bytes.NewBufferString(postDataText).Len(),
QueryString: queryString,
Headers: headers,
Cookies: cookies,
PostData: &har.PostData{
PostData: PostData{
MimeType: mimeType.(string),
Params: harParams,
Text: postDataText,
@@ -172,9 +171,9 @@ func NewRequest(request map[string]interface{}) (harRequest *har.Request, err er
return
}
func NewResponse(response map[string]interface{}) (harResponse *har.Response, err error) {
func NewResponse(response map[string]interface{}) (harResponse *Response, err error) {
headers, _, _, _, _, _status := BuildHeaders(response["_headers"].([]interface{}))
cookies := make([]har.Cookie, 0) // BuildCookies(response["_cookies"].([]interface{}))
cookies := make([]Cookie, 0) // BuildCookies(response["_cookies"].([]interface{}))
content, _ := response["content"].(map[string]interface{})
mimeType, _ := content["mimeType"]
@@ -188,11 +187,11 @@ func NewResponse(response map[string]interface{}) (harResponse *har.Response, er
bodyText = text.(string)
}
harContent := &har.Content{
harContent := &Content{
Encoding: encoding.(string),
MimeType: mimeType.(string),
Text: []byte(bodyText),
Size: int64(len(bodyText)),
Text: bodyText,
Size: len(bodyText),
}
status := int(response["status"].(float64))
@@ -206,20 +205,20 @@ func NewResponse(response map[string]interface{}) (harResponse *har.Response, er
}
}
harResponse = &har.Response{
harResponse = &Response{
HTTPVersion: response["httpVersion"].(string),
Status: status,
StatusText: response["statusText"].(string),
HeadersSize: -1,
BodySize: int64(bytes.NewBufferString(bodyText).Len()),
BodySize: bytes.NewBufferString(bodyText).Len(),
Headers: headers,
Cookies: cookies,
Content: harContent,
Content: *harContent,
}
return
}
func NewEntry(request map[string]interface{}, response map[string]interface{}, startTime time.Time, elapsedTime int64) (*har.Entry, error) {
func NewEntry(request map[string]interface{}, response map[string]interface{}, startTime time.Time, elapsedTime int64) (*Entry, error) {
harRequest, err := NewRequest(request)
if err != nil {
logger.Log.Errorf("Failed converting request to HAR %s (%v,%+v)", err, err, err)
@@ -236,16 +235,16 @@ func NewEntry(request map[string]interface{}, response map[string]interface{}, s
elapsedTime = 1
}
harEntry := har.Entry{
StartedDateTime: startTime,
Time: elapsedTime,
Request: harRequest,
Response: harResponse,
Cache: &har.Cache{},
Timings: &har.Timings{
harEntry := Entry{
StartedDateTime: startTime.Format(time.RFC3339),
Time: int(elapsedTime),
Request: *harRequest,
Response: *harResponse,
Cache: Cache{},
PageTimings: PageTimings{
Send: -1,
Wait: -1,
Receive: elapsedTime,
Receive: int(elapsedTime),
},
}

View File

@@ -0,0 +1,73 @@
package middlewares
import (
"mizuserver/pkg/config"
"mizuserver/pkg/providers"
"github.com/gin-gonic/gin"
ory "github.com/ory/kratos-client-go"
"github.com/up9inc/mizu/shared/logger"
)
func RequiresAuth() gin.HandlerFunc {
return func(c *gin.Context) {
// auth is irrelevant for ephermeral mizu
if !config.Config.StandaloneMode {
c.Next()
return
}
verifyKratosSessionForRequest(c)
if !c.IsAborted() {
c.Next()
}
}
}
func RequiresAdmin() gin.HandlerFunc {
return func(c *gin.Context) {
// auth is irrelevant for ephermeral mizu
if !config.Config.StandaloneMode {
c.Next()
return
}
session := verifyKratosSessionForRequest(c)
if c.IsAborted() {
return
}
traits := session.Identity.Traits.(map[string]interface{})
username := traits["username"].(string)
isAdmin, err := providers.CheckIfUserHasSystemRole(username, providers.AdminRole)
if err != nil {
logger.Log.Errorf("error checking user role %v", err)
c.AbortWithStatusJSON(403, gin.H{"error": "unknown auth error occured"})
} else if !isAdmin {
logger.Log.Warningf("user %s attempted to call an admin only endpoint with insufficient privileges", username)
c.AbortWithStatusJSON(403, gin.H{"error": "unauthorized"})
} else {
c.Next()
}
}
}
func verifyKratosSessionForRequest(c *gin.Context) *ory.Session {
token := c.GetHeader("x-session-token")
if token == "" {
c.AbortWithStatusJSON(401, gin.H{"error": "token header is empty"})
return nil
}
if session, err := providers.VerifyToken(token, c.Request.Context()); err != nil {
logger.Log.Errorf("error verifying token %v", err)
c.AbortWithStatusJSON(401, gin.H{"error": "unknown auth error occured"})
return nil
} else if session == nil {
c.AbortWithStatusJSON(401, gin.H{"error": "invalid token"})
return nil
} else {
return session
}
}

View 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()
}
}

View File

@@ -2,20 +2,23 @@ package models
import (
"encoding/json"
tapApi "github.com/up9inc/mizu/tap/api"
"mizuserver/pkg/har"
"mizuserver/pkg/rules"
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"
)
func GetEntry(r *tapApi.MizuEntry, v tapApi.DataUnmarshaler) error {
func GetEntry(r *tapApi.Entry, v tapApi.DataUnmarshaler) error {
return v.UnmarshalData(r)
}
type TapConfig struct {
TappedNamespaces map[string]bool `json:"tappedNamespaces"`
}
type EntriesRequest struct {
LeftOff int `form:"leftOff" validate:"required,min=-1"`
Direction int `form:"direction" validate:"required,oneof='1' '-1'"`
@@ -24,6 +27,10 @@ type EntriesRequest struct {
TimeoutMs int `form:"timeoutMs" validate:"min=1"`
}
type SingleEntryRequest struct {
Query string `form:"query"`
}
type EntriesResponse struct {
Data []interface{} `json:"data"`
Meta *basenine.Metadata `json:"meta"`
@@ -31,7 +38,7 @@ type EntriesResponse struct {
type WebSocketEntryMessage struct {
*shared.WebSocketMessageMetadata
Data map[string]interface{} `json:"data,omitempty"`
Data *tapApi.BaseEntry `json:"data,omitempty"`
}
type WebSocketTappedEntryMessage struct {
@@ -70,12 +77,7 @@ type WebSocketStartTimeMessage struct {
Data int64 `json:"data"`
}
type WebSocketFocusEntryMessage struct {
*shared.WebSocketMessageMetadata
Id int `json:"id"`
}
func CreateBaseEntryWebSocketMessage(base map[string]interface{}) ([]byte, error) {
func CreateBaseEntryWebSocketMessage(base *tapApi.BaseEntry) ([]byte, error) {
message := &WebSocketEntryMessage{
WebSocketMessageMetadata: &shared.WebSocketMessageMetadata{
MessageType: shared.WebSocketMessageTypeEntry,
@@ -135,16 +137,6 @@ func CreateWebsocketStartTimeMessage(base int64) ([]byte, error) {
return json.Marshal(message)
}
func CreateWebsocketFocusEntry(id int) ([]byte, error) {
message := &WebSocketFocusEntryMessage{
WebSocketMessageMetadata: &shared.WebSocketMessageMetadata{
MessageType: shared.WebSocketMessageFocusEntry,
},
Id: id,
}
return json.Marshal(message)
}
// ExtendedHAR is the top level object of a HAR log.
type ExtendedHAR struct {
Log *ExtendedLog `json:"log"`
@@ -170,3 +162,7 @@ func RunValidationRulesState(harEntry har.Entry, service string) (tapApi.Applica
statusPolicyToSend, latency, numberOfRules := rules.PassedValidationRules(resultPolicyToSend)
return tapApi.ApplicableRules{Status: statusPolicyToSend, Latency: latency, NumberOfRules: numberOfRules}, resultPolicyToSend, isEnabled
}
type InstallState struct {
Completed bool `json:"completed"`
}

View File

@@ -0,0 +1,190 @@
package oas
import (
"bufio"
"encoding/json"
"errors"
"github.com/up9inc/mizu/shared/logger"
"io"
"io/ioutil"
"mizuserver/pkg/har"
"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, isSync bool) (count int, err error) {
badFiles := make([]string, 0)
cnt := 0
for _, file := range fromFiles {
logger.Log.Info("Processing file: " + file)
ext := strings.ToLower(filepath.Ext(file))
eCnt := 0
switch ext {
case ".har":
eCnt, err = feedFromHAR(file, isSync)
if err != nil {
logger.Log.Warning("Failed processing file: " + err.Error())
badFiles = append(badFiles, file)
continue
}
case ".ldjson":
eCnt, err = feedFromLDJSON(file, isSync)
if err != nil {
logger.Log.Warning("Failed processing file: " + err.Error())
badFiles = append(badFiles, file)
continue
}
default:
return 0, errors.New("Unsupported file extension: " + ext)
}
cnt += eCnt
}
for _, f := range badFiles {
logger.Log.Infof("Bad file: %s", f)
}
return cnt, nil
}
func feedFromHAR(file string, isSync bool) (int, error) {
fd, err := os.Open(file)
if err != nil {
panic(err)
}
defer fd.Close()
data, err := ioutil.ReadAll(fd)
if err != nil {
return 0, err
}
var harDoc har.HAR
err = json.Unmarshal(data, &harDoc)
if err != nil {
return 0, err
}
cnt := 0
for _, entry := range harDoc.Log.Entries {
cnt += 1
feedEntry(&entry, isSync)
}
return cnt, nil
}
func feedEntry(entry *har.Entry, isSync bool) {
if entry.Response.Status == 302 {
logger.Log.Debugf("Dropped traffic entry due to permanent redirect status: %s", entry.StartedDateTime)
}
if strings.Contains(entry.Request.URL, "taboola") {
logger.Log.Debugf("Interesting: %s", entry.Request.URL)
} else {
//return
}
if isSync {
GetOasGeneratorInstance().entriesChan <- *entry // blocking variant, right?
} else {
GetOasGeneratorInstance().PushEntry(entry)
}
}
func feedFromLDJSON(file string, isSync bool) (int, 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{}
cnt := 0
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 0, err
}
} else {
var entry har.Entry
err := json.Unmarshal([]byte(line), &entry)
if err != nil {
logger.Log.Warningf("Failed decoding entry: %s", line)
} else {
cnt += 1
feedEntry(&entry, isSync)
}
}
}
return cnt, nil
}
func TestFilesList(t *testing.T) {
res, err := getFiles("./test_artifacts/")
t.Log(len(res))
t.Log(res)
if err != nil || len(res) != 3 {
t.Logf("Should return 2 files but returned %d", len(res))
t.FailNow()
}
}

109
agent/pkg/oas/gibberish.go Normal file
View File

@@ -0,0 +1,109 @@
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,}`)
patLongNum = regexp.MustCompile(`\d{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) || patLongNum.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
}

View 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)
}
}
}

77
agent/pkg/oas/ignores.go Normal file
View File

@@ -0,0 +1,77 @@
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", "status", "x-cache", "x-application-context", "retry-after",
"newrelic", "x-cloud-trace-context", "sentry-trace", "x-cache-hits", "x-served-by", "x-span-name",
"expires", "set-cookie", "p3p", "content-security-policy", "content-security-policy-report-only",
"last-modified", "content-language", "x-varnish", "true-client-ip", "akamai-origin-hop",
"keep-alive", "etag", "alt-svc", "x-csrf-token", "x-ua-compatible", "vary", "x-powered-by",
"age", "allow", "www-authenticate", "expect-ct", "timing-allow-origin", "referrer-policy",
"x-aspnet-version", "x-aspnetmvc-version", "x-timer", "x-abuse-info", "x-mod-pagespeed",
"duration_ms", // UP9 custom
}
var ignoredHeaderPrefixes = []string{
":", "accept-", "access-control-", "if-", "sec-", "grpc-",
"x-forwarded-", "x-original-", "cf-",
"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
}

View File

@@ -0,0 +1,107 @@
package oas
import (
"context"
"encoding/json"
"github.com/up9inc/mizu/shared/logger"
"mizuserver/pkg/har"
"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
}

500
agent/pkg/oas/specgen.go Normal file
View File

@@ -0,0 +1,500 @@
package oas
import (
"encoding/json"
"errors"
"github.com/chanced/openapi"
"github.com/google/uuid"
"github.com/up9inc/mizu/shared/logger"
"mime"
"mizuserver/pkg/har"
"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 = "1.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)
return "", nil
}
if entry.Request.Method == "OPTIONS" {
logger.Log.Debugf("Dropped traffic entry due to its method: %s", urlParsed.Path)
return "", nil
}
ctype := getRespCtype(&entry.Response)
if isCtypeIgnored(ctype) {
logger.Log.Debugf("Dropped traffic entry due to ignored response ctype: %s", ctype)
return "", nil
}
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
}
if entry.Response.Status == 502 || entry.Response.Status == 503 || entry.Response.Status == 504 {
logger.Log.Debugf("Dropped traffic entry due to temporary server error: %s", entry.StartedDateTime)
return "", nil
}
var split []string
if urlParsed.RawPath != "" {
split = strings.Split(urlParsed.RawPath, "/")
} else {
split = strings.Split(urlParsed.Path, "/")
}
node := g.tree.getOrSet(split, new(openapi.PathObj))
opObj, err := handleOpObj(entry, node.pathObj)
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: 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: req.Headers,
IsIgnored: isHeaderIgnored,
GeneralizeName: strings.ToLower,
}
handleNameVals(hdrGW, &opObj.Parameters)
if 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 := &param.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
var isBinary bool
if reqResp.Req != nil {
isBinary, _, text = reqResp.Req.PostData.B64Decoded()
} else {
isBinary, _, text = reqResp.Resp.Content.B64Decoded()
}
if !isBinary && text != "" {
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 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
}

View File

@@ -0,0 +1,228 @@
package oas
import (
"encoding/json"
"github.com/chanced/openapi"
"github.com/op/go-logging"
"github.com/up9inc/mizu/shared/logger"
"io/ioutil"
"mizuserver/pkg/har"
"os"
"strings"
"testing"
"time"
)
// if started via env, write file into subdir
func outputSpec(label string, spec *openapi.OpenAPI, t *testing.T) {
content, err := json.MarshalIndent(spec, "", "\t")
if err != nil {
panic(err)
}
if os.Getenv("MIZU_OAS_WRITE_FILES") != "" {
path := "./oas-samples"
err := os.MkdirAll(path, 0o755)
if err != nil {
panic(err)
}
err = ioutil.WriteFile(path+"/"+label+".json", content, 0644)
if err != nil {
panic(err)
}
t.Logf("Written: %s", label)
} else {
t.Logf("%s", string(content))
}
}
func TestEntries(t *testing.T) {
logger.InitLoggerStderrOnly(logging.INFO)
files, err := getFiles("./test_artifacts/")
if err != nil {
t.Log(err)
t.FailNow()
}
GetOasGeneratorInstance().Start()
loadStartingOAS()
cnt, err := feedEntries(files, true)
if err != nil {
t.Log(err)
t.Fail()
}
waitQueueProcessed()
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()
}
outputSpec(svc, spec, t)
err = spec.Validate()
if err != nil {
t.Log(err)
t.FailNow()
}
return true
})
logger.Log.Infof("Total entries: %d", cnt)
}
func TestFileSingle(t *testing.T) {
GetOasGeneratorInstance().Start()
// loadStartingOAS()
file := "test_artifacts/params.har"
files := []string{file}
cnt, err := feedEntries(files, true)
if err != nil {
logger.Log.Warning("Failed processing file: " + err.Error())
t.Fail()
}
waitQueueProcessed()
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()
}
outputSpec(svc, spec, t)
err = spec.Validate()
if err != nil {
t.Log(err)
t.FailNow()
}
return true
})
logger.Log.Infof("Processed entries: %d", cnt)
}
func waitQueueProcessed() {
for {
time.Sleep(100 * time.Millisecond)
queue := len(GetOasGeneratorInstance().entriesChan)
logger.Log.Infof("Queue: %d", queue)
if queue < 1 {
break
}
}
}
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, false)
if err == nil {
t.Logf("Should have failed")
t.Fail()
}
}
func TestEntriesPositive(t *testing.T) {
files := []string{"test_artifacts/params.har"}
_, err := feedEntries(files, false)
if err != nil {
t.Logf("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 `martian` HAR 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
}

View 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"
}
]
}
}
}

File diff suppressed because one or more lines are too long

View 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}}

View File

@@ -0,0 +1,127 @@
{
"log": {
"version": "1.2",
"creator": {
"name": "mitmproxy har_dump",
"version": "0.1",
"comment": "mitmproxy version mitmproxy 4.0.4"
},
"entries": [
{
"startedDateTime": "2019-09-06T06:14:43.864529+00:00",
"time": 111,
"request": {
"method": "GET",
"url": "https://httpbin.org/e21f7112-3d3b-4632-9da3-a4af2e0e9166/sub1",
"httpVersion": "HTTP/1.1",
"cookies": [],
"headers": [],
"headersSize": 1542,
"bodySize": 0,
"queryString": []
},
"response": {
"status": 200,
"statusText": "OK",
"httpVersion": "HTTP/1.1",
"cookies": [],
"headers": [
],
"content": {
"mimeType": "text/html",
"text": "",
"size": 0
},
"redirectURL": "",
"headersSize": 245,
"bodySize": 39
},
"cache": {},
"timings": {
"send": 22,
"receive": 2,
"wait": 87,
"connect": -1,
"ssl": -1
},
"serverIPAddress": "54.210.29.33"
},
{
"startedDateTime": "2019-09-06T06:16:18.747122+00:00",
"time": 630,
"request": {
"method": "GET",
"url": "https://httpbin.org/952bea17-3776-11ea-9341-42010a84012a/sub2",
"httpVersion": "HTTP/1.1",
"cookies": [],
"headers": [],
"queryString": [],
"headersSize": 1542,
"bodySize": 0
},
"response": {
"status": 200,
"statusText": "OK",
"httpVersion": "HTTP/1.1",
"cookies": [],
"headers": [],
"content": {
"size": 39,
"compression": -20,
"mimeType": "application/json",
"text": "null"
},
"redirectURL": "",
"headersSize": 248,
"bodySize": 39
},
"cache": {},
"timings": {
"send": 14,
"receive": 4,
"wait": 350,
"connect": 262,
"ssl": -1
}
},
{
"startedDateTime": "2019-09-06T06:16:19.747122+00:00",
"time": 630,
"request": {
"method": "GET",
"url": "https://httpbin.org/952bea17-3776-11ea-9341-42010a84012a;mparam=matrixparam",
"httpVersion": "HTTP/1.1",
"cookies": [],
"headers": [],
"queryString": [],
"headersSize": 1542,
"bodySize": 0
},
"response": {
"status": 200,
"statusText": "OK",
"httpVersion": "HTTP/1.1",
"cookies": [],
"headers": [],
"content": {
"size": 39,
"compression": -20,
"mimeType": "application/json",
"text": "null"
},
"redirectURL": "",
"headersSize": 248,
"bodySize": 39
},
"cache": {},
"timings": {
"send": 14,
"receive": 4,
"wait": 350,
"connect": 262,
"ssl": -1
}
}
]
}
}

244
agent/pkg/oas/tree.go Normal file
View File

@@ -0,0 +1,244 @@
package oas
import (
"github.com/chanced/openapi"
"github.com/up9inc/mizu/shared/logger"
"net/url"
"strconv"
"strings"
)
type NodePath = []string
type Node struct {
constant *string
pathParam *openapi.ParameterObj
pathObj *openapi.PathObj
parent *Node
children []*Node
}
func (n *Node) getOrSet(path NodePath, existingPathObj *openapi.PathObj) (node *Node) {
if existingPathObj == nil {
panic("Invalid function call")
}
pathChunk := path[0]
potentialMatrix := strings.SplitN(pathChunk, ";", 2)
if len(potentialMatrix) > 1 {
pathChunk = potentialMatrix[0]
logger.Log.Warningf("URI matrix params are not supported: %s", potentialMatrix[1])
}
chunkIsParam := strings.HasPrefix(pathChunk, "{") && strings.HasSuffix(pathChunk, "}")
pathChunk, err := url.PathUnescape(pathChunk)
if err != nil {
logger.Log.Warningf("URI segment is not correctly encoded: %s", pathChunk)
// any side effects on continuing?
}
chunkIsGibberish := IsGibberish(pathChunk) && !IsVersionString(pathChunk)
var paramObj *openapi.ParameterObj
if chunkIsParam && existingPathObj != nil && existingPathObj.Parameters != nil {
_, paramObj = findParamByName(existingPathObj.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.pathParam = paramObj
} else if chunkIsGibberish {
newParam := n.createParam()
node.pathParam = newParam
} else {
node.constant = &pathChunk
}
}
// add example if it's a gibberish chunk
if node.pathParam != nil && !chunkIsParam {
exmp := &node.pathParam.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.pathObj!=nil && path[1]==""
if len(path) > 1 {
return node.getOrSet(path[1:], existingPathObj)
} else if node.pathObj == nil {
node.pathObj = existingPathObj
}
return node
}
func (n *Node) createParam() *openapi.ParameterObj {
name := "param"
if n.constant != nil { // the node is already a 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"
} else if isAlpha(*n.constant) {
name = *n.constant + "Id"
}
name = cleanNonAlnum([]byte(name))
}
newParam := createSimpleParam(name, "path", "string")
x := n.countParentParams()
if x > 0 {
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.pathParam, 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.pathParam != nil {
strChunk = "{" + n.pathParam.Name + "}"
} else {
// this is the root node
}
// add self
if n.pathObj != nil {
fillPathParams(n, n.pathObj)
paths.Items[openapi.PathValue(strChunk)] = n.pathObj
}
// 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
}
func fillPathParams(n *Node, pathObj *openapi.PathObj) {
// collect all path parameters from parent hierarchy
node := n
for {
if node.pathParam != nil {
initParams(&pathObj.Parameters)
idx, paramObj := findParamByName(pathObj.Parameters, openapi.InPath, node.pathParam.Name)
if paramObj == nil {
appended := append(*pathObj.Parameters, node.pathParam)
pathObj.Parameters = &appended
} else {
(*pathObj.Parameters)[idx] = paramObj
}
}
node = node.parent
if node == nil {
break
}
}
}
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.pathParam != nil {
res++
}
if node.parent == nil {
break
}
node = node.parent
}
return res
}

View File

@@ -0,0 +1,37 @@
package oas
import (
"github.com/chanced/openapi"
"strings"
"testing"
)
func TestTree(t *testing.T) {
testCases := []struct {
inp string
numParams int
label string
}{
{"/", 0, ""},
{"/v1.0.0/config/launcher/sp_nKNHCzsN/f34efcae-6583-11eb-908a-00b0fcb9d4f6/vendor,init,conversation", 1, "vendor,init,conversation"},
{"/v1.0.0/config/launcher/sp_nKNHCzsN/{f34efcae-6583-11eb-908a-00b0fcb9d4f6}/vendor,init,conversation", 0, "vendor,init,conversation"},
{"/getSvgs/size/small/brand/SFLY/layoutId/170943/layoutVersion/1/sizeId/742/surface/0/isLandscape/true/childSkus/%7B%7D", 1, ""},
}
tree := new(Node)
for _, tc := range testCases {
split := strings.Split(tc.inp, "/")
pathObj := new(openapi.PathObj)
node := tree.getOrSet(split, pathObj)
fillPathParams(node, pathObj)
if node.constant != nil && *node.constant != tc.label {
t.Errorf("Constant does not match: %s != %s", *node.constant, tc.label)
}
if tc.numParams > 0 && (pathObj.Parameters == nil || len(*pathObj.Parameters) < tc.numParams) {
t.Errorf("Wrong num of params, expected: %d", tc.numParams)
}
}
}

348
agent/pkg/oas/utils.go Normal file
View File

@@ -0,0 +1,348 @@
package oas
import (
"encoding/json"
"errors"
"github.com/chanced/openapi"
"github.com/up9inc/mizu/shared/logger"
"mizuserver/pkg/har"
"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 = &params
}
}
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) (idx int, pathParam *openapi.ParameterObj) {
caseInsensitive := in == openapi.InHeader
for i, param := range *params {
idx = i
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 idx, 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 nvParams struct {
In openapi.In
Pairs []har.NVP
IsIgnored func(name string) bool
GeneralizeName func(name string) string
}
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 := &param.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
}
func cleanNonAlnum(s []byte) string {
j := 0
for _, b := range s {
if ('a' <= b && b <= 'z') ||
('A' <= b && b <= 'Z') ||
('0' <= b && b <= '9') ||
b == ' ' {
s[j] = b
j++
}
}
return string(s[:j])
}
func isAlpha(s string) bool {
for _, r := range s {
if (r < 'a' || r > 'z') && (r < 'A' || r > 'Z') {
return false
}
}
return true
}

View 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)
}
}
}

View File

@@ -0,0 +1,47 @@
package providers
import (
"context"
"errors"
"mizuserver/pkg/config"
ory "github.com/ory/kratos-client-go"
)
const AdminUsername = "admin"
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
}
}
func CreateAdminUser(password string, ctx context.Context) (token *string, err error, formErrorMessages map[string][]ory.UiText) {
if isInstallNeeded, err := IsInstallNeeded(); err != nil {
return nil, err, nil
} else if !isInstallNeeded {
return nil, errors.New("The admin user has already been created"), nil
}
token, identityId, err, formErrors := RegisterUser(AdminUsername, password, ctx)
if err != nil {
return nil, err, formErrors
}
err = SetUserSystemRole(AdminUsername, AdminRole)
if err != nil {
//Delete the user to prevent a half-setup situation where admin user is created without admin privileges
DeleteUser(identityId, ctx)
return nil, err, nil
}
return token, nil, nil
}

View 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
}

View File

@@ -7,6 +7,7 @@ import (
type GeneralStats struct {
EntriesCount int
EntriesVolumeInGB float64
FirstEntryTimestamp int
LastEntryTimestamp int
}
@@ -21,8 +22,9 @@ func GetGeneralStats() GeneralStats {
return generalStats
}
func EntryAdded() {
func EntryAdded(size int) {
generalStats.EntriesCount++
generalStats.EntriesVolumeInGB += float64(size) / (1 << 30)
currentTimestamp := int(time.Now().Unix())
@@ -32,5 +34,3 @@ func EntryAdded() {
generalStats.LastEntryTimestamp = currentTimestamp
}

View File

@@ -12,6 +12,10 @@ func TestNoEntryAddedCount(t *testing.T) {
if entriesStats.EntriesCount != 0 {
t.Errorf("unexpected result - expected: %v, actual: %v", 0, entriesStats.EntriesCount)
}
if entriesStats.EntriesVolumeInGB != 0 {
t.Errorf("unexpected result - expected: %v, actual: %v", 0, entriesStats.EntriesVolumeInGB)
}
}
func TestEntryAddedCount(t *testing.T) {
@@ -20,7 +24,7 @@ func TestEntryAddedCount(t *testing.T) {
for _, entriesCount := range tests {
t.Run(fmt.Sprintf("%d", entriesCount), func(t *testing.T) {
for i := 0; i < entriesCount; i++ {
providers.EntryAdded()
providers.EntryAdded(0)
}
entriesStats := providers.GetGeneralStats()
@@ -29,7 +33,38 @@ func TestEntryAddedCount(t *testing.T) {
t.Errorf("unexpected result - expected: %v, actual: %v", entriesCount, entriesStats.EntriesCount)
}
if entriesStats.EntriesVolumeInGB != 0 {
t.Errorf("unexpected result - expected: %v, actual: %v", 0, entriesStats.EntriesVolumeInGB)
}
t.Cleanup(providers.ResetGeneralStats)
})
}
}
func TestEntryAddedVolume(t *testing.T) {
// 6 bytes + 4 bytes
tests := [][]byte{[]byte("volume"), []byte("test")}
var expectedEntriesCount int
var expectedVolumeInGB float64
for _, data := range tests {
t.Run(fmt.Sprintf("%d", len(data)), func(t *testing.T) {
expectedEntriesCount++
expectedVolumeInGB += float64(len(data)) / (1 << 30)
providers.EntryAdded(len(data))
entriesStats := providers.GetGeneralStats()
if entriesStats.EntriesCount != expectedEntriesCount {
t.Errorf("unexpected result - expected: %v, actual: %v", expectedEntriesCount, entriesStats.EntriesCount)
}
if entriesStats.EntriesVolumeInGB != expectedVolumeInGB {
t.Errorf("unexpected result - expected: %v, actual: %v", expectedVolumeInGB, entriesStats.EntriesVolumeInGB)
}
})
}
}

View File

@@ -8,19 +8,14 @@ import (
"github.com/up9inc/mizu/tap"
"mizuserver/pkg/models"
"os"
"sync"
"time"
)
const tlsLinkRetainmentTime = time.Minute * 15
var (
TappersCount int
TapStatus shared.TapStatus
authStatus *models.AuthStatus
RecentTLSLinks = cache.New(tlsLinkRetainmentTime, tlsLinkRetainmentTime)
ExpectedTapperAmount = -1 //only relevant in daemon mode as cli manages tappers otherwise
tappersCountLock = sync.Mutex{}
)
func GetAuthStatus() (*models.AuthStatus, error) {
@@ -68,15 +63,3 @@ func GetAllRecentTLSAddresses() []string {
return recentTLSLinks
}
func TapperAdded() {
tappersCountLock.Lock()
TappersCount++
tappersCountLock.Unlock()
}
func TapperRemoved() {
tappersCountLock.Lock()
TappersCount--
tappersCountLock.Unlock()
}

View 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)
}
}

View 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
}

View 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)
}
}

View 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) (*ory.Session, error) {
flow, _, err := client.V0alpha2Api.ToSession(ctx).XSessionToken(token).Execute()
if err != nil {
return nil, err
}
if flow == nil {
return nil, nil
}
return flow, 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(), &registrationFlowModel); 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")
}
}

View File

@@ -0,0 +1,180 @@
package providers
/*
This provider abstracts keto role management down to what we need for mizu
Keto, in the configuration we use it, is basically a tuple database. Each tuple consists of 4 strings (namespace, object, relation, subjectID) - for example ("workspaces", "sock-shop-workspace", "viewer", "ramiberman")
namespace - used to organize tuples into groups - we currently use "system" for defining admins and "workspaces" for defining workspace permissions
objects - represents something one can have permissions to (files, mizu workspaces etc)
relation - represents the permission (viewer, editor, owner etc) - we currently use only viewer and admin
subject - represents the user or group that has the permission - we currently use usernames
more on keto here: https://www.ory.sh/keto/docs/
*/
import (
"fmt"
"mizuserver/pkg/utils"
ketoClient "github.com/ory/keto-client-go/client"
ketoRead "github.com/ory/keto-client-go/client/read"
ketoWrite "github.com/ory/keto-client-go/client/write"
ketoModels "github.com/ory/keto-client-go/models"
)
const (
ketoHost = "localhost"
ketoReadPort = 4466
ketoWritePort = 4467
)
var (
readClient = getKetoClient(fmt.Sprintf("%s:%d", ketoHost, ketoReadPort))
writeClient = getKetoClient(fmt.Sprintf("%s:%d", ketoHost, ketoWritePort))
systemRoleNamespace = "system"
workspacesRoleNamespace = "workspaces"
systemObject = "system"
AdminRole = "admin"
ViewerRole = "viewer"
)
func GetUserSystemRoles(username string) ([]string, error) {
return getObjectRelationsForSubjectID(systemRoleNamespace, systemObject, username)
}
func CheckIfUserHasSystemRole(username string, role string) (bool, error) {
systemRoles, err := GetUserSystemRoles(username)
if err != nil {
return false, err
}
for _, systemRole := range systemRoles {
if systemRole == role {
return true, nil
}
}
return false, nil
}
func GetUserWorkspaceRole(username string, workspace string) ([]string, error) {
return getObjectRelationsForSubjectID(workspacesRoleNamespace, workspace, username)
}
func SetUserWorkspaceRole(username string, workspace string, role string) error {
return createObjectRelationForSubjectID(workspacesRoleNamespace, workspace, username, role)
}
func SetUserSystemRole(username string, role string) error {
return createObjectRelationForSubjectID(systemRoleNamespace, systemObject, username, role)
}
func DeleteAllUserWorkspaceRoles(username string) error {
return deleteAllNamespacedRelationsForSubjectID(workspacesRoleNamespace, username)
}
func createObjectRelationForSubjectID(namespace string, object string, subjectID string, relation string) error {
tuple := ketoModels.RelationQuery{
Namespace: &namespace,
Object: object,
Relation: relation,
SubjectID: subjectID,
}
_, err := writeClient.Write.CreateRelationTuple(ketoWrite.
NewCreateRelationTupleParams().
WithPayload(&tuple))
if err != nil {
return err
}
return nil
}
func getObjectRelationsForSubjectID(namespace string, object string, subjectID string) ([]string, error) {
relationTuples, err := queryRelationTuples(&namespace, &object, &subjectID, nil)
if err != nil {
return nil, err
}
relations := make([]string, 0)
for _, clientRelation := range relationTuples {
relations = append(relations, *clientRelation.Relation)
}
return utils.UniqueStringSlice(relations), nil
}
func deleteAllNamespacedRelationsForSubjectID(namespace string, subjectID string) error {
relationTuples, err := queryRelationTuples(&namespace, nil, &subjectID, nil)
if err != nil {
return err
}
for _, clientRelation := range relationTuples {
_, err := writeClient.Write.DeleteRelationTuple(ketoWrite.
NewDeleteRelationTupleParams().
WithNamespace(*clientRelation.Namespace).
WithObject(*clientRelation.Object).
WithRelation(*clientRelation.Relation).
WithSubjectID(&clientRelation.SubjectID))
if err != nil {
return err
}
}
return nil
}
func queryRelationTuples(namespace *string, object *string, subjectID *string, role *string) ([]*ketoModels.InternalRelationTuple, error) {
relationTuplesQuery := ketoRead.NewGetRelationTuplesParams()
if namespace != nil {
relationTuplesQuery = relationTuplesQuery.WithNamespace(*namespace)
}
if object != nil {
relationTuplesQuery = relationTuplesQuery.WithObject(object)
}
if subjectID != nil {
relationTuplesQuery = relationTuplesQuery.WithSubjectID(subjectID)
}
if role != nil {
relationTuplesQuery = relationTuplesQuery.WithRelation(role)
}
return recursiveKetoPagingTraverse(relationTuplesQuery, make([]*ketoModels.InternalRelationTuple, 0), "")
}
func recursiveKetoPagingTraverse(queryParams *ketoRead.GetRelationTuplesParams, tuples []*ketoModels.InternalRelationTuple, pagingToken string) ([]*ketoModels.InternalRelationTuple, error) {
params := queryParams
if pagingToken != "" {
params = queryParams.WithPageToken(&pagingToken)
}
clientRelationsResponse, err := readClient.Read.GetRelationTuples(params)
if err != nil {
return nil, err
}
tuples = append(tuples, clientRelationsResponse.Payload.RelationTuples...)
if clientRelationsResponse.Payload.NextPageToken != "" {
return recursiveKetoPagingTraverse(queryParams, tuples, clientRelationsResponse.Payload.NextPageToken)
}
return tuples, nil
}
func getKetoClient(url string) *ketoClient.OryKeto {
return ketoClient.NewHTTPClientWithConfig(nil,
ketoClient.
DefaultTransportConfig().
WithSchemes([]string{"http"}).
WithHost(url))
}

View File

@@ -0,0 +1,16 @@
package routes
import (
"mizuserver/pkg/controllers"
"mizuserver/pkg/middlewares"
"github.com/gin-gonic/gin"
)
func ConfigRoutes(ginApp *gin.Engine) {
routeGroup := ginApp.Group("/config")
routeGroup.Use(middlewares.RequiresAuth())
routeGroup.POST("/tap", middlewares.RequiresAdmin(), controllers.PostTapConfig)
routeGroup.GET("/tap", controllers.GetTapConfig)
}

View File

@@ -2,6 +2,7 @@ package routes
import (
"mizuserver/pkg/controllers"
"mizuserver/pkg/middlewares"
"github.com/gin-gonic/gin"
)
@@ -9,6 +10,7 @@ import (
// EntriesRoutes defines the group of har entries routes.
func EntriesRoutes(ginApp *gin.Engine) {
routeGroup := ginApp.Group("/entries")
routeGroup.Use(middlewares.RequiresAuth())
routeGroup.GET("/", controllers.GetEntries) // get entries (base/thin entries) and metadata
routeGroup.GET("/:id", controllers.GetEntry) // get single (full) entry

View File

@@ -0,0 +1,14 @@
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)
routeGroup.POST("/admin", controllers.SetupAdminUser)
}

View File

@@ -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.

View 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
}

View File

@@ -2,12 +2,14 @@ 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)
}

View 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)
}

View File

@@ -1,17 +1,21 @@
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.GET("/tappersCount", controllers.GetTappersCount)
routeGroup.POST("/tapperStatus", controllers.PostTapperStatus)
routeGroup.GET("/connectedTappersCount", controllers.GetConnectedTappersCount)
routeGroup.GET("/tap", controllers.GetTappingStatus)
routeGroup.GET("/auth", controllers.GetAuthStatus)

View 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)
}

View File

@@ -4,15 +4,15 @@ import (
"encoding/base64"
"encoding/json"
"fmt"
"mizuserver/pkg/har"
"reflect"
"regexp"
"strings"
"github.com/up9inc/mizu/shared/logger"
"github.com/google/martian/har"
"github.com/up9inc/mizu/shared"
jsonpath "github.com/yalp/jsonpath"
"github.com/yalp/jsonpath"
)
type RulesMatched struct {

View 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"`
}

View 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()
}

View 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))
}

View File

@@ -3,10 +3,10 @@ package up9
import (
"bytes"
"compress/zlib"
"encoding/base64"
"encoding/json"
"fmt"
"io/ioutil"
"mizuserver/pkg/har"
"mizuserver/pkg/utils"
"net/http"
"net/url"
@@ -15,7 +15,6 @@ import (
"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"
@@ -243,25 +242,20 @@ func syncEntriesImpl(token string, model string, envPrefix string, uploadInterva
var dataMap map[string]interface{}
err = json.Unmarshal(dataBytes, &dataMap)
var entry tapApi.MizuEntry
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)
harEntry, err := har.NewEntry(entry.Request, entry.Response, entry.StartTime, entry.ElapsedTime)
if err != nil {
continue
}
if entry.ResolvedSource != "" {
harEntry.Request.Headers = append(harEntry.Request.Headers, har.Header{Name: "x-mizu-source", Value: entry.ResolvedSource})
if entry.Source.Name != "" {
harEntry.Request.Headers = append(harEntry.Request.Headers, har.Header{Name: "x-mizu-source", Value: entry.Source.Name})
}
if entry.ResolvedDestination != "" {
harEntry.Request.Headers = append(harEntry.Request.Headers, har.Header{Name: "x-mizu-destination", Value: entry.ResolvedDestination})
harEntry.Request.URL = utils.SetHostname(harEntry.Request.URL, entry.ResolvedDestination)
}
// 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
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)
}
batch = append(batch, *harEntry)

View File

@@ -2,12 +2,13 @@ package utils
import (
"context"
"encoding/json"
"fmt"
"io/ioutil"
"net/http"
"net/url"
"os"
"os/signal"
"reflect"
"syscall"
"time"
@@ -44,17 +45,6 @@ func StartServer(app *gin.Engine) {
}
}
func ReverseSlice(data interface{}) {
value := reflect.ValueOf(data)
valueLen := value.Len()
for i := 0; i <= int((valueLen-1)/2); i++ {
reverseIndex := valueLen - 1 - i
tmp := value.Index(reverseIndex).Interface()
value.Index(reverseIndex).Set(value.Index(i))
value.Index(i).Set(reflect.ValueOf(tmp))
}
}
func CheckErr(e error) {
if e != nil {
logger.Log.Errorf("%v", e)
@@ -71,3 +61,42 @@ func SetHostname(address, newHostname string) string {
return replacedUrl.String()
}
func ReadJsonFile(filePath string, value interface{}) error {
if content, err := ioutil.ReadFile(filePath); err != nil {
return err
} else {
if err = json.Unmarshal(content, value); err != nil {
return err
}
}
return nil
}
func SaveJsonFile(filePath string, value interface{}) error {
if data, err := json.Marshal(value); err != nil {
return err
} else {
if err = ioutil.WriteFile(filePath, data, 0644); err != nil {
return err
}
}
return nil
}
func UniqueStringSlice(s []string) []string {
uniqueSlice := make([]string, 0)
uniqueMap := map[string]bool{}
for _, val := range s {
if uniqueMap[val] == true {
continue
}
uniqueMap[val] = true
uniqueSlice = append(uniqueSlice, val)
}
return uniqueSlice
}

View File

@@ -14,26 +14,28 @@ help: ## This help.
install:
go install mizu.go
build-debug:
export GCLFAGS='-gcflags="all=-N -l"'
${MAKE} build
build: ## Build mizu CLI binary (select platform via GOOS / GOARCH env variables).
go build -ldflags="-X 'github.com/up9inc/mizu/cli/mizu.GitCommitHash=$(COMMIT_HASH)' \
go build ${GCLFAGS} -ldflags="-X 'github.com/up9inc/mizu/cli/mizu.GitCommitHash=$(COMMIT_HASH)' \
-X 'github.com/up9inc/mizu/cli/mizu.Branch=$(GIT_BRANCH)' \
-X 'github.com/up9inc/mizu/cli/mizu.BuildTimestamp=$(BUILD_TIMESTAMP)' \
-X 'github.com/up9inc/mizu/cli/mizu.Platform=$(SUFFIX)' \
-X 'github.com/up9inc/mizu/cli/mizu.SemVer=$(SEM_VER)'" \
-o bin/mizu_$(SUFFIX) mizu.go
-o bin/mizu_$(SUFFIX) mizu.go
(cd bin && shasum -a 256 mizu_${SUFFIX} > mizu_${SUFFIX}.sha256)
build-all: ## Build for all supported platforms.
@echo "Compiling for every OS and Platform"
@mkdir -p bin && sed s/_SEM_VER_/$(SEM_VER)/g README.md.TEMPLATE > bin/README.md
@$(MAKE) build GOOS=darwin GOARCH=amd64
@$(MAKE) build GOOS=linux GOARCH=amd64
@$(MAKE) build GOOS=linux GOARCH=arm64
@$(MAKE) build GOOS=darwin GOARCH=amd64
@$(MAKE) build GOOS=darwin GOARCH=arm64
@$(MAKE) build GOOS=windows GOARCH=amd64
@mv ./bin/mizu_windows_amd64 ./bin/mizu.exe
@# $(MAKE) GOOS=linux GOARCH=386
@# $(MAKE) GOOS=windows GOARCH=386
@# $(MAKE) GOOS=linux GOARCH=arm64
@# $(MAKE) GOOS=windows GOARCH=arm64
@echo "---------"
@find ./bin -ls

View File

@@ -1,23 +1,29 @@
# Mizu release _SEM_VER_
Full changelog for stable release see in [docs](https://github.com/up9inc/mizu/blob/main/docs/CHANGELOG.md)
Download Mizu for your platform
## Download Mizu for your platform
**Mac** (Intel)
**Mac** (x86-64/Intel)
```
curl -Lo mizu https://github.com/up9inc/mizu/releases/download/_SEM_VER_/mizu_darwin_amd64 && chmod 755 mizu
```
**Mac** (Apple M1 silicon)
**Mac** (AArch64/Apple M1 silicon)
```
curl -Lo mizu https://github.com/up9inc/mizu/releases/download/_SEM_VER_/mizu_darwin_arm64 && chmod 755 mizu
```
**Linux**
**Linux** (x86-64)
```
curl -Lo mizu https://github.com/up9inc/mizu/releases/download/_SEM_VER_/mizu_linux_amd64 && chmod 755 mizu
```
**Windows** (Intel 64bit)
**Linux** (AArch64)
```
curl -Lo mizu https://github.com/up9inc/mizu/releases/download/_SEM_VER_/mizu_linux_arm64 && chmod 755 mizu
```
**Windows** (x86-64)
```
curl -LO https://github.com/up9inc/mizu/releases/download/_SEM_VER_/mizu.exe
```

Some files were not shown because too many files have changed in this diff Show More