mirror of
https://github.com/kubeshark/kubeshark.git
synced 2026-02-19 20:40:17 +00:00
Compare commits
54 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
d4436d9f15 | ||
|
|
4e0ff74944 | ||
|
|
366c1d0c6c | ||
|
|
17fa163ee3 | ||
|
|
3644fdb533 | ||
|
|
ab7c4e72c6 | ||
|
|
e25e7925b6 | ||
|
|
80237c8090 | ||
|
|
a310953f05 | ||
|
|
a9e92b60f5 | ||
|
|
35e40cd230 | ||
|
|
2575ad722a | ||
|
|
afd5757315 | ||
|
|
dba8b1f215 | ||
|
|
6dd0ef1268 | ||
|
|
83cfaed1a3 | ||
|
|
41cb9ee12e | ||
|
|
667f0dc87d | ||
|
|
a34c2fc0dc | ||
|
|
7a31263e4a | ||
|
|
7f9fd82c0e | ||
|
|
a37d1f4aeb | ||
|
|
acdbdedd5d | ||
|
|
a9b5eba9d4 | ||
|
|
80201224c6 | ||
|
|
e6e7d8d58b | ||
|
|
bf27e94003 | ||
|
|
2ae0a2400d | ||
|
|
db1f4458c5 | ||
|
|
5d5c11c37c | ||
|
|
b4f3b2c540 | ||
|
|
a427534605 | ||
|
|
1d6ca9d392 | ||
|
|
f74a52d4dc | ||
|
|
6d2e9af5d7 | ||
|
|
e4ff4a0745 | ||
|
|
f9677dbaa1 | ||
|
|
0afab6c068 | ||
|
|
1d1b62ec4f | ||
|
|
e2db5087b8 | ||
|
|
241477fb5c | ||
|
|
c8e5886a96 | ||
|
|
8a8cf4aa77 | ||
|
|
7b73004e85 | ||
|
|
56dc6843e0 | ||
|
|
0409eb239d | ||
|
|
cbe04af801 | ||
|
|
59dec1a547 | ||
|
|
c4afeee5b3 | ||
|
|
8c9b8d3217 | ||
|
|
d705ae3eb6 | ||
|
|
c53b2148d1 | ||
|
|
ca897dd3c7 | ||
|
|
4406919565 |
@@ -2,7 +2,7 @@
|
||||
.dockerignore
|
||||
.editorconfig
|
||||
.gitignore
|
||||
.env.*
|
||||
**/.env*
|
||||
Dockerfile
|
||||
Makefile
|
||||
LICENSE
|
||||
|
||||
4
.github/CODEOWNERS
vendored
Normal file
4
.github/CODEOWNERS
vendored
Normal file
@@ -0,0 +1,4 @@
|
||||
# This is a comment.
|
||||
# Each line is a file pattern followed by one or more owners.
|
||||
|
||||
/ui/ @frontend
|
||||
38
.github/ISSUE_TEMPLATE/bug_report.md
vendored
Normal file
38
.github/ISSUE_TEMPLATE/bug_report.md
vendored
Normal file
@@ -0,0 +1,38 @@
|
||||
---
|
||||
name: Bug report
|
||||
about: Create a report to help us improve
|
||||
title: ''
|
||||
labels: ''
|
||||
assignees: ''
|
||||
|
||||
---
|
||||
|
||||
**Describe the bug**
|
||||
A clear and concise description of what the bug is.
|
||||
|
||||
**To Reproduce**
|
||||
Steps to reproduce the behavior:
|
||||
1. Run mizu <command> '...'
|
||||
2. Click on '....'
|
||||
3. Scroll down to '....'
|
||||
4. See error
|
||||
|
||||
**Expected behavior**
|
||||
A clear and concise description of what you expected to happen.
|
||||
|
||||
**Logs**
|
||||
Upload logs:
|
||||
1. Run the mizu command with `--set dump-logs=true` (e.g `mizu tap --set dump-logs=true`)
|
||||
2. Try to reproduce the issue
|
||||
3. CNTRL+C on terminal tab which runs mizu
|
||||
4. Upload the logs zip file from ~/.mizu/mizu_logs_**.zip
|
||||
|
||||
**Screenshots**
|
||||
If applicable, add screenshots to help explain your problem.
|
||||
|
||||
**Desktop (please complete the following information):**
|
||||
- OS: [e.g. iOS]
|
||||
- Browser [e.g. chrome]
|
||||
|
||||
**Additional context**
|
||||
Add any other context about the problem here.
|
||||
32
.github/workflows/acceptance_tests.yml
vendored
Normal file
32
.github/workflows/acceptance_tests.yml
vendored
Normal file
@@ -0,0 +1,32 @@
|
||||
name: acceptance tests
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
branches:
|
||||
- 'main'
|
||||
push:
|
||||
branches:
|
||||
- 'develop'
|
||||
|
||||
concurrency:
|
||||
group: mizu-acceptance-tests-${{ github.ref }}
|
||||
cancel-in-progress: true
|
||||
|
||||
jobs:
|
||||
run-acceptance-tests:
|
||||
name: Run acceptance tests
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Set up Go 1.16
|
||||
uses: actions/setup-go@v2
|
||||
with:
|
||||
go-version: '^1.16'
|
||||
|
||||
- name: Check out code into the Go module directory
|
||||
uses: actions/checkout@v2
|
||||
|
||||
- name: Setup acceptance test
|
||||
run: source ./acceptanceTests/setup.sh
|
||||
|
||||
- name: Test
|
||||
run: make acceptance-test
|
||||
46
.github/workflows/pr_validation.yml
vendored
Normal file
46
.github/workflows/pr_validation.yml
vendored
Normal file
@@ -0,0 +1,46 @@
|
||||
name: PR validation
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
branches:
|
||||
- 'develop'
|
||||
- 'main'
|
||||
|
||||
concurrency:
|
||||
group: mizu-pr-validation-${{ github.ref }}
|
||||
cancel-in-progress: true
|
||||
|
||||
jobs:
|
||||
build-cli:
|
||||
name: Build CLI
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Set up Go 1.16
|
||||
uses: actions/setup-go@v2
|
||||
with:
|
||||
go-version: '^1.16'
|
||||
|
||||
- name: Check out code into the Go module directory
|
||||
uses: actions/checkout@v2
|
||||
|
||||
- name: Build CLI
|
||||
run: make cli
|
||||
|
||||
build-agent:
|
||||
name: Build Agent
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Set up Go 1.16
|
||||
uses: actions/setup-go@v2
|
||||
with:
|
||||
go-version: '^1.16'
|
||||
|
||||
- name: Check out code into the Go module directory
|
||||
uses: actions/checkout@v2
|
||||
|
||||
- shell: bash
|
||||
run: |
|
||||
sudo apt-get install libpcap-dev
|
||||
|
||||
- name: Build Agent
|
||||
run: make agent
|
||||
7
.github/workflows/publish.yml
vendored
7
.github/workflows/publish.yml
vendored
@@ -1,9 +1,15 @@
|
||||
name: publish
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- 'develop'
|
||||
- 'main'
|
||||
|
||||
concurrency:
|
||||
group: mizu-publish-${{ github.ref }}
|
||||
cancel-in-progress: true
|
||||
|
||||
jobs:
|
||||
docker:
|
||||
runs-on: ubuntu-latest
|
||||
@@ -78,4 +84,3 @@ jobs:
|
||||
tag: ${{ steps.versioning.outputs.version }}
|
||||
prerelease: ${{ github.ref != 'refs/heads/main' }}
|
||||
bodyFile: 'cli/bin/README.md'
|
||||
|
||||
|
||||
56
.github/workflows/tests_validation.yml
vendored
Normal file
56
.github/workflows/tests_validation.yml
vendored
Normal file
@@ -0,0 +1,56 @@
|
||||
name: tests validation
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
branches:
|
||||
- 'develop'
|
||||
- 'main'
|
||||
push:
|
||||
branches:
|
||||
- 'develop'
|
||||
- 'main'
|
||||
|
||||
concurrency:
|
||||
group: mizu-tests-validation-${{ github.ref }}
|
||||
cancel-in-progress: true
|
||||
|
||||
jobs:
|
||||
run-tests-cli:
|
||||
name: Run CLI tests
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Set up Go 1.16
|
||||
uses: actions/setup-go@v2
|
||||
with:
|
||||
go-version: '^1.16'
|
||||
|
||||
- name: Check out code into the Go module directory
|
||||
uses: actions/checkout@v2
|
||||
|
||||
- name: Test
|
||||
run: make test-cli
|
||||
|
||||
- name: Upload coverage to Codecov
|
||||
uses: codecov/codecov-action@v2
|
||||
|
||||
run-tests-agent:
|
||||
name: Run Agent tests
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Set up Go 1.16
|
||||
uses: actions/setup-go@v2
|
||||
with:
|
||||
go-version: '^1.16'
|
||||
|
||||
- name: Check out code into the Go module directory
|
||||
uses: actions/checkout@v2
|
||||
|
||||
- shell: bash
|
||||
run: |
|
||||
sudo apt-get install libpcap-dev
|
||||
|
||||
- name: Test
|
||||
run: make test-agent
|
||||
|
||||
- name: Upload coverage to Codecov
|
||||
uses: codecov/codecov-action@v2
|
||||
7
.gitignore
vendored
7
.gitignore
vendored
@@ -19,3 +19,10 @@ build
|
||||
|
||||
# Mac OS
|
||||
.DS_Store
|
||||
.vscode/
|
||||
|
||||
# Ignore the scripts that are created for development
|
||||
*dev.*
|
||||
|
||||
# Environment variables
|
||||
.env
|
||||
|
||||
18
CONTRIBUTE.md
Normal file
18
CONTRIBUTE.md
Normal file
@@ -0,0 +1,18 @@
|
||||

|
||||
# CONTRIBUTE
|
||||
We welcome code contributions from the community.
|
||||
Please read and follow the guidelines below.
|
||||
|
||||
## Communication
|
||||
* Before starting work on a major feature, please reach out to us via [GitHub](https://github.com/up9inc/mizu), [Slack](https://join.slack.com/share/zt-u6bbs3pg-X1zhQOXOH0yEoqILgH~csw), [email](mailto:mizu@up9.com), etc. We will make sure no one else is already working on it. A _major feature_ is defined as any change that is > 100 LOC altered (not including tests), or changes any user-facing behavior
|
||||
* Small patches and bug fixes don't need prior communication.
|
||||
|
||||
## Contribution requirements
|
||||
* Code style - most of the code is written in Go, please follow [these guidelines](https://golang.org/doc/effective_go)
|
||||
* Go-tools compatible (`go get`, `go test`, etc)
|
||||
* Unit-test coverage can’t go down ..
|
||||
* Code must be usefully commented. Not only for developers on the project, but also for external users of these packages
|
||||
* When reviewing PRs, you are encouraged to use Golang's [code review comments page](https://github.com/golang/go/wiki/CodeReviewComments)
|
||||
|
||||
|
||||
|
||||
@@ -11,7 +11,7 @@ FROM golang:1.16-alpine AS builder
|
||||
# Set necessary environment variables needed for our image.
|
||||
ENV CGO_ENABLED=1 GOOS=linux GOARCH=amd64
|
||||
|
||||
RUN apk add libpcap-dev gcc g++ make
|
||||
RUN apk add libpcap-dev gcc g++ make bash
|
||||
|
||||
# Move to agent working directory (/agent-build).
|
||||
WORKDIR /app/agent-build
|
||||
@@ -19,6 +19,7 @@ WORKDIR /app/agent-build
|
||||
COPY agent/go.mod agent/go.sum ./
|
||||
COPY shared/go.mod shared/go.mod ../shared/
|
||||
COPY tap/go.mod tap/go.mod ../tap/
|
||||
COPY tap/api/go.* ../tap/api/
|
||||
RUN go mod download
|
||||
# cheap trick to make the build faster (As long as go.mod wasn't changes)
|
||||
RUN go list -f '{{.Path}}@{{.Version}}' -m all | sed 1d | grep -e 'go-cache' -e 'sqlite' | xargs go get
|
||||
@@ -38,6 +39,8 @@ RUN go build -ldflags="-s -w \
|
||||
-X 'mizuserver/pkg/version.BuildTimestamp=${BUILD_TIMESTAMP}' \
|
||||
-X 'mizuserver/pkg/version.SemVer=${SEM_VER}'" -o mizuagent .
|
||||
|
||||
COPY build_extensions.sh ..
|
||||
RUN cd .. && /bin/bash build_extensions.sh
|
||||
|
||||
FROM alpine:3.13.5
|
||||
|
||||
@@ -46,6 +49,7 @@ 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/extensions", "extensions"]
|
||||
COPY --from=site-build ["/app/ui-build/build", "site"]
|
||||
|
||||
# gin-gonic runs in debug mode without this
|
||||
|
||||
22
Makefile
22
Makefile
@@ -23,14 +23,18 @@ export SEM_VER?=0.0.0
|
||||
|
||||
ui: ## Build UI.
|
||||
@(cd ui; npm i ; npm run build; )
|
||||
@ls -l ui/build
|
||||
@ls -l ui/build
|
||||
|
||||
cli: ## Build CLI.
|
||||
@echo "building cli"; cd cli && $(MAKE) build
|
||||
|
||||
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
|
||||
|
||||
docker: ## Build and publish agent docker image.
|
||||
@@ -42,6 +46,10 @@ push-docker: ## Build and publish agent docker image.
|
||||
@echo "publishing Docker image .. "
|
||||
./build-push-featurebranch.sh
|
||||
|
||||
build-docker-ci: ## Build agent docker image for CI.
|
||||
@echo "building docker image for ci"
|
||||
./build-agent-ci.sh
|
||||
|
||||
push-cli: ## Build and publish CLI.
|
||||
@echo "publishing CLI .. "
|
||||
@cd cli; $(MAKE) build-all
|
||||
@@ -50,7 +58,6 @@ push-cli: ## Build and publish CLI.
|
||||
gsutil cp -r ./cli/bin/* gs://${BUCKET_PATH}/
|
||||
gsutil setmeta -r -h "Cache-Control:public, max-age=30" gs://${BUCKET_PATH}/\*
|
||||
|
||||
|
||||
clean: clean-ui clean-agent clean-cli clean-docker ## Clean all build artifacts.
|
||||
|
||||
clean-ui: ## Clean UI.
|
||||
@@ -65,3 +72,14 @@ clean-cli: ## Clean CLI.
|
||||
clean-docker:
|
||||
@(echo "DOCKER cleanup - NOT IMPLEMENTED YET " )
|
||||
|
||||
extensions:
|
||||
./build_extensions.sh
|
||||
|
||||
test-cli:
|
||||
@echo "running cli tests"; cd cli && $(MAKE) test
|
||||
|
||||
test-agent:
|
||||
@echo "running agent tests"; cd agent && $(MAKE) test
|
||||
|
||||
acceptance-test:
|
||||
@echo "running acceptance tests"; cd acceptanceTests && $(MAKE) test
|
||||
|
||||
76
README.md
76
README.md
@@ -1,16 +1,19 @@
|
||||

|
||||
|
||||
# The API Traffic Viewer for Kubernetes
|
||||
|
||||
A simple-yet-powerful API traffic viewer for Kubernetes to help you troubleshoot and debug your microservices. Think TCPDump and Chrome Dev Tools combined.
|
||||
A simple-yet-powerful API traffic viewer for Kubernetes enabling you to view all API communication between microservices to help your debug and troubleshoot regressions.
|
||||
|
||||
Think TCPDump and Chrome Dev Tools combined.
|
||||
|
||||

|
||||
|
||||
## Features
|
||||
|
||||
- Simple and powerful CLI
|
||||
- Real time view of all HTTP requests, REST and gRPC API calls
|
||||
- Real-time view of all HTTP requests, REST and gRPC API calls
|
||||
- No installation or code instrumentation
|
||||
- Works completely on premises (on-prem)
|
||||
- Works completely on premises
|
||||
|
||||
## Download
|
||||
|
||||
@@ -32,13 +35,15 @@ https://github.com/up9inc/mizu/releases/latest/download/mizu_linux_amd64 \
|
||||
&& chmod 755 mizu
|
||||
```
|
||||
|
||||
SHA256 checksums are available on the [Releases](https://github.com/up9inc/mizu/releases) page.
|
||||
SHA256 checksums are available on the [Releases](https://github.com/up9inc/mizu/releases) page
|
||||
|
||||
### Development (unstable) Build
|
||||
Pick one from the [Releases](https://github.com/up9inc/mizu/releases) page.
|
||||
Pick one from the [Releases](https://github.com/up9inc/mizu/releases) page
|
||||
|
||||
## Prerequisites
|
||||
1. Set `KUBECONFIG` environment variable to your Kubernetes configuration. If this is not set, Mizu assumes that configuration is at `${HOME}/.kube/config`
|
||||
## Kubeconfig & Permissions
|
||||
While `mizu`most often works out of the box, you can influence its behavior:
|
||||
|
||||
1. [OPTIONAL] Set `KUBECONFIG` environment variable to your Kubernetes configuration. If this is not set, Mizu assumes that configuration is at `${HOME}/.kube/config`
|
||||
2. `mizu` assumes user running the command has permissions to create resources (such as pods, services, namespaces) on your Kubernetes cluster (no worries - `mizu` resources are cleaned up upon termination)
|
||||
|
||||
For detailed list of k8s permissions see [PERMISSIONS](PERMISSIONS.md) document
|
||||
@@ -48,8 +53,8 @@ For detailed list of k8s permissions see [PERMISSIONS](PERMISSIONS.md) document
|
||||
|
||||
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 ..
|
||||
4. Watch the API traffic flowing ..
|
||||
3. Open browser on `http://localhost:8899/mizu` **or** as instructed in the CLI
|
||||
4. Watch the API traffic flowing
|
||||
5. Type ^C to stop
|
||||
|
||||
## Examples
|
||||
@@ -106,44 +111,63 @@ To tap multiple pods using regex -
|
||||
## Configuration
|
||||
|
||||
Mizu can work with config file which should be stored in ${HOME}/.mizu/config.yaml (macOS: ~/.mizu/config.yaml) <br />
|
||||
In case no config file found, defaults will be used. <br />
|
||||
In case of partial configuration defined, all other fields will be used with defaults. <br />
|
||||
You can always override the defaults or config file with CLI flags.
|
||||
In case no config file found, defaults will be used <br />
|
||||
In case of partial configuration defined, all other fields will be used with defaults <br />
|
||||
You can always override the defaults or config file with CLI flags
|
||||
|
||||
To get the default config params run `mizu config` <br />
|
||||
To generate a new config file with default values use `mizu config -r`
|
||||
|
||||
Mizu has several undocumented flags which can be set by using --set flag (e.g., `mizu tap --set dump-logs=true`)
|
||||
* **mizu-resources-namespace**: Type - String, See [Namespace-Restricted Mode](#namespace-restricted-mode)
|
||||
* **telemetry**: Type - Boolean, Reports telemetry
|
||||
* **dump-logs**: Type - Boolean, At the end of the execution it creates a zip file with logs (in .mizu folder)
|
||||
* **kube-config-path**: Type - String, Setting the path to kube config (which isn't in standard path)
|
||||
### Telemetry
|
||||
|
||||
By default, mizu reports usage telemetry. It can be disabled by adding a line of `telemetry: false` in the `${HOME}/.mizu/config.yaml` file
|
||||
|
||||
|
||||
## Advanced Usage
|
||||
|
||||
### Namespace-Restricted Mode
|
||||
|
||||
Some users have permission to only manage resources in one particular namespace assigned to them.
|
||||
Some users have permission to only manage resources in one particular namespace assigned to them
|
||||
By default `mizu tap` creates a new namespace `mizu` for all of its Kubernetes resources. In order to instead install
|
||||
Mizu in an existing namespace, set the `mizu-resources-namespace` config option.
|
||||
Mizu in an existing namespace, set the `mizu-resources-namespace` config option
|
||||
|
||||
If `mizu-resources-namespace` is set to a value other than the default `mizu`, Mizu will operate in a
|
||||
Namespace-Restricted mode. It will only tap pods in `mizu-resources-namespace`. This way Mizu only requires permissions
|
||||
to the namespace set by `mizu-resources-namespace`. The user must set the tapped namespace to the same namespace by
|
||||
using the `--namespace` flag or by setting `tap.namespaces` in the config file.
|
||||
using the `--namespace` flag or by setting `tap.namespaces` in the config file
|
||||
|
||||
Setting `mizu-resources-namespace=mizu` resets Mizu to its default behavior.
|
||||
Setting `mizu-resources-namespace=mizu` resets Mizu to its default behavior
|
||||
|
||||
### User agent filtering
|
||||
|
||||
User-agent filtering (like health checks) - can be configured:
|
||||
User-agent filtering (like health checks) - can be configured using command-line options:
|
||||
|
||||
Any request that contains one of those values in the user-agent header will not be captured
|
||||
|
||||
```bash
|
||||
```shell
|
||||
$ mizu tap "^ca.*" --set ignored-user-agents=kube-probe --set ignored-user-agents=prometheus
|
||||
+carts-66c77f5fbb-fq65r
|
||||
+catalogue-5f4cb7cf5-7zrmn
|
||||
Web interface is now available at http://localhost:8899
|
||||
^C
|
||||
```
|
||||
|
||||
```
|
||||
Any request that contains `User-Agent` header with one of the specified values (`kube-probe` or `prometheus`) will not be captured
|
||||
|
||||
### API Rules validation
|
||||
|
||||
This feature allows you to define set of simple rules, and test the API against them.
|
||||
Such validation may test response for specific JSON fields, headers, etc.
|
||||
|
||||
Please see [API RULES](docs/POLICY_RULES.md) page for more details and syntax.
|
||||
|
||||
|
||||
## How to Run local UI
|
||||
|
||||
- run from mizu/agent `go run main.go --hars-read --hars-dir <folder>`
|
||||
|
||||
- copy Har files into the folder from last command
|
||||
|
||||
- change `MizuWebsocketURL` and `apiURL` in `api.js` file
|
||||
|
||||
- run from mizu/ui - `npm run start`
|
||||
|
||||
- open browser on `localhost:3000`
|
||||
|
||||
15
TESTING.md
Normal file
15
TESTING.md
Normal file
@@ -0,0 +1,15 @@
|
||||

|
||||
# TESTING
|
||||
Testing guidelines for Mizu project
|
||||
|
||||
## Unit-tests
|
||||
* TBD
|
||||
* TBD
|
||||
* TBD
|
||||
|
||||
|
||||
|
||||
## System tests
|
||||
* TBD
|
||||
* TBD
|
||||
* TBD
|
||||
2
acceptanceTests/Makefile
Normal file
2
acceptanceTests/Makefile
Normal file
@@ -0,0 +1,2 @@
|
||||
test: ## Run acceptance tests.
|
||||
@go test ./... -timeout 1h
|
||||
283
acceptanceTests/config_test.go
Normal file
283
acceptanceTests/config_test.go
Normal file
@@ -0,0 +1,283 @@
|
||||
package acceptanceTests
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"gopkg.in/yaml.v3"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"os/exec"
|
||||
"testing"
|
||||
)
|
||||
|
||||
type tapConfig struct {
|
||||
GuiPort uint16 `yaml:"gui-port"`
|
||||
}
|
||||
|
||||
type configStruct struct {
|
||||
Tap tapConfig `yaml:"tap"`
|
||||
}
|
||||
|
||||
func TestConfigRegenerate(t *testing.T) {
|
||||
if testing.Short() {
|
||||
t.Skip("ignored acceptance test")
|
||||
}
|
||||
|
||||
cliPath, cliPathErr := getCliPath()
|
||||
if cliPathErr != nil {
|
||||
t.Errorf("failed to get cli path, err: %v", cliPathErr)
|
||||
return
|
||||
}
|
||||
|
||||
configPath, configPathErr := getConfigPath()
|
||||
if configPathErr != nil {
|
||||
t.Errorf("failed to get config path, err: %v", cliPathErr)
|
||||
return
|
||||
}
|
||||
|
||||
configCmdArgs := getDefaultConfigCommandArgs()
|
||||
|
||||
configCmdArgs = append(configCmdArgs, "-r")
|
||||
|
||||
configCmd := exec.Command(cliPath, configCmdArgs...)
|
||||
t.Logf("running command: %v", configCmd.String())
|
||||
|
||||
t.Cleanup(func() {
|
||||
if err := os.Remove(configPath); err != nil {
|
||||
t.Logf("failed to delete config file, err: %v", err)
|
||||
}
|
||||
})
|
||||
|
||||
if err := configCmd.Start(); err != nil {
|
||||
t.Errorf("failed to start config command, err: %v", err)
|
||||
return
|
||||
}
|
||||
|
||||
if err := configCmd.Wait(); err != nil {
|
||||
t.Errorf("failed to wait config command, err: %v", err)
|
||||
return
|
||||
}
|
||||
|
||||
_, readFileErr := ioutil.ReadFile(configPath)
|
||||
if readFileErr != nil {
|
||||
t.Errorf("failed to read config file, err: %v", readFileErr)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
func TestConfigGuiPort(t *testing.T) {
|
||||
if testing.Short() {
|
||||
t.Skip("ignored acceptance test")
|
||||
}
|
||||
|
||||
tests := []uint16{8898}
|
||||
|
||||
for _, guiPort := range tests {
|
||||
t.Run(fmt.Sprintf("%d", guiPort), func(t *testing.T) {
|
||||
cliPath, cliPathErr := getCliPath()
|
||||
if cliPathErr != nil {
|
||||
t.Errorf("failed to get cli path, err: %v", cliPathErr)
|
||||
return
|
||||
}
|
||||
|
||||
configPath, configPathErr := getConfigPath()
|
||||
if configPathErr != nil {
|
||||
t.Errorf("failed to get config path, err: %v", cliPathErr)
|
||||
return
|
||||
}
|
||||
|
||||
config := configStruct{}
|
||||
config.Tap.GuiPort = guiPort
|
||||
|
||||
configBytes, marshalErr := yaml.Marshal(config)
|
||||
if marshalErr != nil {
|
||||
t.Errorf("failed to marshal config, err: %v", marshalErr)
|
||||
return
|
||||
}
|
||||
|
||||
if writeErr := ioutil.WriteFile(configPath, configBytes, 0644); writeErr != nil {
|
||||
t.Errorf("failed to write config to file, err: %v", writeErr)
|
||||
return
|
||||
}
|
||||
|
||||
tapCmdArgs := getDefaultTapCommandArgs()
|
||||
|
||||
tapNamespace := getDefaultTapNamespace()
|
||||
tapCmdArgs = append(tapCmdArgs, tapNamespace...)
|
||||
|
||||
tapCmd := exec.Command(cliPath, tapCmdArgs...)
|
||||
t.Logf("running command: %v", tapCmd.String())
|
||||
|
||||
t.Cleanup(func() {
|
||||
if err := cleanupCommand(tapCmd); err != nil {
|
||||
t.Logf("failed to cleanup tap command, err: %v", err)
|
||||
}
|
||||
|
||||
if err := os.Remove(configPath); err != nil {
|
||||
t.Logf("failed to delete config file, err: %v", err)
|
||||
}
|
||||
})
|
||||
|
||||
if err := tapCmd.Start(); err != nil {
|
||||
t.Errorf("failed to start tap command, err: %v", err)
|
||||
return
|
||||
}
|
||||
|
||||
apiServerUrl := getApiServerUrl(guiPort)
|
||||
|
||||
if err := waitTapPodsReady(apiServerUrl); err != nil {
|
||||
t.Errorf("failed to start tap pods on time, err: %v", err)
|
||||
return
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestConfigSetGuiPort(t *testing.T) {
|
||||
if testing.Short() {
|
||||
t.Skip("ignored acceptance test")
|
||||
}
|
||||
|
||||
tests := []struct {
|
||||
ConfigFileGuiPort uint16
|
||||
SetGuiPort uint16
|
||||
}{
|
||||
{ConfigFileGuiPort: 8898, SetGuiPort: 8897},
|
||||
}
|
||||
|
||||
for _, guiPortStruct := range tests {
|
||||
t.Run(fmt.Sprintf("%d", guiPortStruct.SetGuiPort), func(t *testing.T) {
|
||||
cliPath, cliPathErr := getCliPath()
|
||||
if cliPathErr != nil {
|
||||
t.Errorf("failed to get cli path, err: %v", cliPathErr)
|
||||
return
|
||||
}
|
||||
|
||||
configPath, configPathErr := getConfigPath()
|
||||
if configPathErr != nil {
|
||||
t.Errorf("failed to get config path, err: %v", cliPathErr)
|
||||
return
|
||||
}
|
||||
|
||||
config := configStruct{}
|
||||
config.Tap.GuiPort = guiPortStruct.ConfigFileGuiPort
|
||||
|
||||
configBytes, marshalErr := yaml.Marshal(config)
|
||||
if marshalErr != nil {
|
||||
t.Errorf("failed to marshal config, err: %v", marshalErr)
|
||||
return
|
||||
}
|
||||
|
||||
if writeErr := ioutil.WriteFile(configPath, configBytes, 0644); writeErr != nil {
|
||||
t.Errorf("failed to write config to file, err: %v", writeErr)
|
||||
return
|
||||
}
|
||||
|
||||
tapCmdArgs := getDefaultTapCommandArgs()
|
||||
|
||||
tapNamespace := getDefaultTapNamespace()
|
||||
tapCmdArgs = append(tapCmdArgs, tapNamespace...)
|
||||
|
||||
tapCmdArgs = append(tapCmdArgs, "--set", fmt.Sprintf("tap.gui-port=%v", guiPortStruct.SetGuiPort))
|
||||
|
||||
tapCmd := exec.Command(cliPath, tapCmdArgs...)
|
||||
t.Logf("running command: %v", tapCmd.String())
|
||||
|
||||
t.Cleanup(func() {
|
||||
if err := cleanupCommand(tapCmd); err != nil {
|
||||
t.Logf("failed to cleanup tap command, err: %v", err)
|
||||
}
|
||||
|
||||
if err := os.Remove(configPath); err != nil {
|
||||
t.Logf("failed to delete config file, err: %v", err)
|
||||
}
|
||||
})
|
||||
|
||||
if err := tapCmd.Start(); err != nil {
|
||||
t.Errorf("failed to start tap command, err: %v", err)
|
||||
return
|
||||
}
|
||||
|
||||
apiServerUrl := getApiServerUrl(guiPortStruct.SetGuiPort)
|
||||
|
||||
if err := waitTapPodsReady(apiServerUrl); err != nil {
|
||||
t.Errorf("failed to start tap pods on time, err: %v", err)
|
||||
return
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestConfigFlagGuiPort(t *testing.T) {
|
||||
if testing.Short() {
|
||||
t.Skip("ignored acceptance test")
|
||||
}
|
||||
|
||||
tests := []struct {
|
||||
ConfigFileGuiPort uint16
|
||||
FlagGuiPort uint16
|
||||
}{
|
||||
{ConfigFileGuiPort: 8898, FlagGuiPort: 8896},
|
||||
}
|
||||
|
||||
for _, guiPortStruct := range tests {
|
||||
t.Run(fmt.Sprintf("%d", guiPortStruct.FlagGuiPort), func(t *testing.T) {
|
||||
cliPath, cliPathErr := getCliPath()
|
||||
if cliPathErr != nil {
|
||||
t.Errorf("failed to get cli path, err: %v", cliPathErr)
|
||||
return
|
||||
}
|
||||
|
||||
configPath, configPathErr := getConfigPath()
|
||||
if configPathErr != nil {
|
||||
t.Errorf("failed to get config path, err: %v", cliPathErr)
|
||||
return
|
||||
}
|
||||
|
||||
config := configStruct{}
|
||||
config.Tap.GuiPort = guiPortStruct.ConfigFileGuiPort
|
||||
|
||||
configBytes, marshalErr := yaml.Marshal(config)
|
||||
if marshalErr != nil {
|
||||
t.Errorf("failed to marshal config, err: %v", marshalErr)
|
||||
return
|
||||
}
|
||||
|
||||
if writeErr := ioutil.WriteFile(configPath, configBytes, 0644); writeErr != nil {
|
||||
t.Errorf("failed to write config to file, err: %v", writeErr)
|
||||
return
|
||||
}
|
||||
|
||||
tapCmdArgs := getDefaultTapCommandArgs()
|
||||
|
||||
tapNamespace := getDefaultTapNamespace()
|
||||
tapCmdArgs = append(tapCmdArgs, tapNamespace...)
|
||||
|
||||
tapCmdArgs = append(tapCmdArgs, "-p", fmt.Sprintf("%v", guiPortStruct.FlagGuiPort))
|
||||
|
||||
tapCmd := exec.Command(cliPath, tapCmdArgs...)
|
||||
t.Logf("running command: %v", tapCmd.String())
|
||||
|
||||
t.Cleanup(func() {
|
||||
if err := cleanupCommand(tapCmd); err != nil {
|
||||
t.Logf("failed to cleanup tap command, err: %v", err)
|
||||
}
|
||||
|
||||
if err := os.Remove(configPath); err != nil {
|
||||
t.Logf("failed to delete config file, err: %v", err)
|
||||
}
|
||||
})
|
||||
|
||||
if err := tapCmd.Start(); err != nil {
|
||||
t.Errorf("failed to start tap command, err: %v", err)
|
||||
return
|
||||
}
|
||||
|
||||
apiServerUrl := getApiServerUrl(guiPortStruct.FlagGuiPort)
|
||||
|
||||
if err := waitTapPodsReady(apiServerUrl); err != nil {
|
||||
t.Errorf("failed to start tap pods on time, err: %v", err)
|
||||
return
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
5
acceptanceTests/go.mod
Normal file
5
acceptanceTests/go.mod
Normal file
@@ -0,0 +1,5 @@
|
||||
module github.com/up9inc/mizu/tests
|
||||
|
||||
go 1.16
|
||||
|
||||
require gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b
|
||||
4
acceptanceTests/go.sum
Normal file
4
acceptanceTests/go.sum
Normal file
@@ -0,0 +1,4 @@
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
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=
|
||||
55
acceptanceTests/setup.sh
Normal file
55
acceptanceTests/setup.sh
Normal file
@@ -0,0 +1,55 @@
|
||||
#!/bin/bash
|
||||
|
||||
PREFIX=$HOME/local/bin
|
||||
VERSION=v1.22.0
|
||||
|
||||
echo "Attempting to install minikube and assorted tools to $PREFIX"
|
||||
|
||||
if ! [ -x "$(command -v kubectl)" ]; then
|
||||
echo "Installing kubectl version $VERSION"
|
||||
curl -LO "https://storage.googleapis.com/kubernetes-release/release/$VERSION/bin/linux/amd64/kubectl"
|
||||
chmod +x kubectl
|
||||
mv kubectl "$PREFIX"
|
||||
else
|
||||
echo "kubetcl is already installed"
|
||||
fi
|
||||
|
||||
if ! [ -x "$(command -v minikube)" ]; then
|
||||
echo "Installing minikube version $VERSION"
|
||||
curl -Lo minikube https://storage.googleapis.com/minikube/releases/$VERSION/minikube-linux-amd64
|
||||
chmod +x minikube
|
||||
mv minikube "$PREFIX"
|
||||
else
|
||||
echo "minikube is already installed"
|
||||
fi
|
||||
|
||||
echo "Starting minikube..."
|
||||
minikube start
|
||||
|
||||
echo "Creating mizu tests namespaces"
|
||||
kubectl create namespace mizu-tests
|
||||
kubectl create namespace mizu-tests2
|
||||
|
||||
echo "Creating httpbin deployments"
|
||||
kubectl create deployment httpbin --image=kennethreitz/httpbin -n mizu-tests
|
||||
kubectl create deployment httpbin2 --image=kennethreitz/httpbin -n mizu-tests
|
||||
|
||||
kubectl create deployment httpbin --image=kennethreitz/httpbin -n mizu-tests2
|
||||
|
||||
echo "Creating httpbin services"
|
||||
kubectl expose deployment httpbin --type=NodePort --port=80 -n mizu-tests
|
||||
kubectl expose deployment httpbin2 --type=NodePort --port=80 -n mizu-tests
|
||||
|
||||
kubectl expose deployment httpbin --type=NodePort --port=80 -n mizu-tests2
|
||||
|
||||
echo "Starting proxy"
|
||||
kubectl proxy --port=8080 &
|
||||
|
||||
echo "Setting minikube docker env"
|
||||
eval $(minikube docker-env)
|
||||
|
||||
echo "Build agent image"
|
||||
make build-docker-ci
|
||||
|
||||
echo "Build cli"
|
||||
make build-cli-ci
|
||||
765
acceptanceTests/tap_test.go
Normal file
765
acceptanceTests/tap_test.go
Normal file
@@ -0,0 +1,765 @@
|
||||
package acceptanceTests
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"os/exec"
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
|
||||
func TestTapAndFetch(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
|
||||
}
|
||||
|
||||
tapCmdArgs := getDefaultTapCommandArgs()
|
||||
|
||||
tapNamespace := getDefaultTapNamespace()
|
||||
tapCmdArgs = append(tapCmdArgs, tapNamespace...)
|
||||
|
||||
tapCmd := exec.Command(cliPath, tapCmdArgs...)
|
||||
t.Logf("running command: %v", tapCmd.String())
|
||||
|
||||
t.Cleanup(func() {
|
||||
if err := cleanupCommand(tapCmd); err != nil {
|
||||
t.Logf("failed to cleanup tap command, err: %v", err)
|
||||
}
|
||||
})
|
||||
|
||||
if err := tapCmd.Start(); err != nil {
|
||||
t.Errorf("failed to start tap command, err: %v", err)
|
||||
return
|
||||
}
|
||||
|
||||
apiServerUrl := getApiServerUrl(defaultApiServerPort)
|
||||
|
||||
if err := waitTapPodsReady(apiServerUrl); err != nil {
|
||||
t.Errorf("failed to start tap pods on time, err: %v", err)
|
||||
return
|
||||
}
|
||||
|
||||
proxyUrl := getProxyUrl(defaultNamespaceName, defaultServiceName)
|
||||
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)
|
||||
|
||||
entriesUrl := fmt.Sprintf("%v/api/entries?limit=%v&operator=lt×tamp=%v", apiServerUrl, entriesCount, timestamp)
|
||||
requestResult, requestErr := executeHttpGetRequest(entriesUrl)
|
||||
if requestErr != nil {
|
||||
return fmt.Errorf("failed to get entries, err: %v", requestErr)
|
||||
}
|
||||
|
||||
entries := requestResult.([]interface{})
|
||||
if len(entries) == 0 {
|
||||
return fmt.Errorf("unexpected entries result - Expected more than 0 entries")
|
||||
}
|
||||
|
||||
entry := entries[0].(map[string]interface{})
|
||||
|
||||
entryUrl := fmt.Sprintf("%v/api/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
|
||||
}
|
||||
|
||||
fetchCmdArgs := getDefaultFetchCommandArgs()
|
||||
fetchCmd := exec.Command(cliPath, fetchCmdArgs...)
|
||||
t.Logf("running command: %v", fetchCmd.String())
|
||||
|
||||
if err := fetchCmd.Start(); err != nil {
|
||||
t.Errorf("failed to start fetch command, err: %v", err)
|
||||
return
|
||||
}
|
||||
|
||||
harCheckFunc := func() error {
|
||||
harBytes, readFileErr := ioutil.ReadFile("./unknown_source.har")
|
||||
if readFileErr != nil {
|
||||
return fmt.Errorf("failed to read har file, err: %v", readFileErr)
|
||||
}
|
||||
|
||||
harEntries, err := getEntriesFromHarBytes(harBytes)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to get entries from har, err: %v", err)
|
||||
}
|
||||
|
||||
if len(harEntries) == 0 {
|
||||
return fmt.Errorf("unexpected har entries result - Expected more than 0 entries")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
if err := retriesExecute(shortRetriesCount, harCheckFunc); err != nil {
|
||||
t.Errorf("%v", err)
|
||||
return
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestTapGuiPort(t *testing.T) {
|
||||
if testing.Short() {
|
||||
t.Skip("ignored acceptance test")
|
||||
}
|
||||
|
||||
tests := []uint16{8898}
|
||||
|
||||
for _, guiPort := range tests {
|
||||
t.Run(fmt.Sprintf("%d", guiPort), func(t *testing.T) {
|
||||
cliPath, cliPathErr := getCliPath()
|
||||
if cliPathErr != nil {
|
||||
t.Errorf("failed to get cli path, err: %v", cliPathErr)
|
||||
return
|
||||
}
|
||||
|
||||
tapCmdArgs := getDefaultTapCommandArgs()
|
||||
|
||||
tapNamespace := getDefaultTapNamespace()
|
||||
tapCmdArgs = append(tapCmdArgs, tapNamespace...)
|
||||
|
||||
tapCmdArgs = append(tapCmdArgs, "-p", fmt.Sprintf("%d", guiPort))
|
||||
|
||||
tapCmd := exec.Command(cliPath, tapCmdArgs...)
|
||||
t.Logf("running command: %v", tapCmd.String())
|
||||
|
||||
t.Cleanup(func() {
|
||||
if err := cleanupCommand(tapCmd); err != nil {
|
||||
t.Logf("failed to cleanup tap command, err: %v", err)
|
||||
}
|
||||
})
|
||||
|
||||
if err := tapCmd.Start(); err != nil {
|
||||
t.Errorf("failed to start tap command, err: %v", err)
|
||||
return
|
||||
}
|
||||
|
||||
apiServerUrl := getApiServerUrl(guiPort)
|
||||
|
||||
if err := waitTapPodsReady(apiServerUrl); err != nil {
|
||||
t.Errorf("failed to start tap pods on time, err: %v", err)
|
||||
return
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestTapAllNamespaces(t *testing.T) {
|
||||
if testing.Short() {
|
||||
t.Skip("ignored acceptance test")
|
||||
}
|
||||
|
||||
expectedPods := []struct{
|
||||
Name string
|
||||
Namespace string
|
||||
}{
|
||||
{Name: "httpbin", Namespace: "mizu-tests"},
|
||||
{Name: "httpbin", Namespace: "mizu-tests2"},
|
||||
}
|
||||
|
||||
cliPath, cliPathErr := getCliPath()
|
||||
if cliPathErr != nil {
|
||||
t.Errorf("failed to get cli path, err: %v", cliPathErr)
|
||||
return
|
||||
}
|
||||
|
||||
tapCmdArgs := getDefaultTapCommandArgs()
|
||||
tapCmdArgs = append(tapCmdArgs, "-A")
|
||||
|
||||
tapCmd := exec.Command(cliPath, tapCmdArgs...)
|
||||
t.Logf("running command: %v", tapCmd.String())
|
||||
|
||||
t.Cleanup(func() {
|
||||
if err := cleanupCommand(tapCmd); err != nil {
|
||||
t.Logf("failed to cleanup tap command, err: %v", err)
|
||||
}
|
||||
})
|
||||
|
||||
if err := tapCmd.Start(); err != nil {
|
||||
t.Errorf("failed to start tap command, err: %v", err)
|
||||
return
|
||||
}
|
||||
|
||||
apiServerUrl := getApiServerUrl(defaultApiServerPort)
|
||||
|
||||
if err := waitTapPodsReady(apiServerUrl); err != nil {
|
||||
t.Errorf("failed to start tap pods on time, err: %v", err)
|
||||
return
|
||||
}
|
||||
|
||||
podsUrl := fmt.Sprintf("%v/api/tapStatus", 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 {
|
||||
podFound := false
|
||||
|
||||
for _, pod := range pods {
|
||||
podNamespace := pod["namespace"].(string)
|
||||
podName := pod["name"].(string)
|
||||
|
||||
if expectedPod.Namespace == podNamespace && strings.Contains(podName, expectedPod.Name) {
|
||||
podFound = true
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if !podFound {
|
||||
t.Errorf("unexpected result - expected pod not found, pod namespace: %v, pod name: %v", expectedPod.Namespace, expectedPod.Name)
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestTapMultipleNamespaces(t *testing.T) {
|
||||
if testing.Short() {
|
||||
t.Skip("ignored acceptance test")
|
||||
}
|
||||
|
||||
expectedPods := []struct{
|
||||
Name string
|
||||
Namespace string
|
||||
}{
|
||||
{Name: "httpbin", Namespace: "mizu-tests"},
|
||||
{Name: "httpbin2", Namespace: "mizu-tests"},
|
||||
{Name: "httpbin", Namespace: "mizu-tests2"},
|
||||
}
|
||||
|
||||
cliPath, cliPathErr := getCliPath()
|
||||
if cliPathErr != nil {
|
||||
t.Errorf("failed to get cli path, err: %v", cliPathErr)
|
||||
return
|
||||
}
|
||||
|
||||
tapCmdArgs := getDefaultTapCommandArgs()
|
||||
var namespacesCmd []string
|
||||
for _, expectedPod := range expectedPods {
|
||||
namespacesCmd = append(namespacesCmd, "-n", expectedPod.Namespace)
|
||||
}
|
||||
tapCmdArgs = append(tapCmdArgs, namespacesCmd...)
|
||||
|
||||
tapCmd := exec.Command(cliPath, tapCmdArgs...)
|
||||
t.Logf("running command: %v", tapCmd.String())
|
||||
|
||||
t.Cleanup(func() {
|
||||
if err := cleanupCommand(tapCmd); err != nil {
|
||||
t.Logf("failed to cleanup tap command, err: %v", err)
|
||||
}
|
||||
})
|
||||
|
||||
if err := tapCmd.Start(); err != nil {
|
||||
t.Errorf("failed to start tap command, err: %v", err)
|
||||
return
|
||||
}
|
||||
|
||||
apiServerUrl := getApiServerUrl(defaultApiServerPort)
|
||||
|
||||
if err := waitTapPodsReady(apiServerUrl); err != nil {
|
||||
t.Errorf("failed to start tap pods on time, err: %v", err)
|
||||
return
|
||||
}
|
||||
|
||||
podsUrl := fmt.Sprintf("%v/api/tapStatus", 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 {
|
||||
podFound := false
|
||||
|
||||
for _, pod := range pods {
|
||||
podNamespace := pod["namespace"].(string)
|
||||
podName := pod["name"].(string)
|
||||
|
||||
if expectedPod.Namespace == podNamespace && strings.Contains(podName, expectedPod.Name) {
|
||||
podFound = true
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if !podFound {
|
||||
t.Errorf("unexpected result - expected pod not found, pod namespace: %v, pod name: %v", expectedPod.Namespace, expectedPod.Name)
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestTapRegex(t *testing.T) {
|
||||
if testing.Short() {
|
||||
t.Skip("ignored acceptance test")
|
||||
}
|
||||
|
||||
regexPodName := "httpbin2"
|
||||
expectedPods := []struct{
|
||||
Name string
|
||||
Namespace string
|
||||
}{
|
||||
{Name: regexPodName, Namespace: "mizu-tests"},
|
||||
}
|
||||
|
||||
cliPath, cliPathErr := getCliPath()
|
||||
if cliPathErr != nil {
|
||||
t.Errorf("failed to get cli path, err: %v", cliPathErr)
|
||||
return
|
||||
}
|
||||
|
||||
tapCmdArgs := getDefaultTapCommandArgsWithRegex(regexPodName)
|
||||
|
||||
tapNamespace := getDefaultTapNamespace()
|
||||
tapCmdArgs = append(tapCmdArgs, tapNamespace...)
|
||||
|
||||
tapCmd := exec.Command(cliPath, tapCmdArgs...)
|
||||
t.Logf("running command: %v", tapCmd.String())
|
||||
|
||||
t.Cleanup(func() {
|
||||
if err := cleanupCommand(tapCmd); err != nil {
|
||||
t.Logf("failed to cleanup tap command, err: %v", err)
|
||||
}
|
||||
})
|
||||
|
||||
if err := tapCmd.Start(); err != nil {
|
||||
t.Errorf("failed to start tap command, err: %v", err)
|
||||
return
|
||||
}
|
||||
|
||||
apiServerUrl := getApiServerUrl(defaultApiServerPort)
|
||||
|
||||
if err := waitTapPodsReady(apiServerUrl); err != nil {
|
||||
t.Errorf("failed to start tap pods on time, err: %v", err)
|
||||
return
|
||||
}
|
||||
|
||||
podsUrl := fmt.Sprintf("%v/api/tapStatus", 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 {
|
||||
podFound := false
|
||||
|
||||
for _, pod := range pods {
|
||||
podNamespace := pod["namespace"].(string)
|
||||
podName := pod["name"].(string)
|
||||
|
||||
if expectedPod.Namespace == podNamespace && strings.Contains(podName, expectedPod.Name) {
|
||||
podFound = true
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if !podFound {
|
||||
t.Errorf("unexpected result - expected pod not found, pod namespace: %v, pod name: %v", expectedPod.Namespace, expectedPod.Name)
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestTapDryRun(t *testing.T) {
|
||||
if testing.Short() {
|
||||
t.Skip("ignored acceptance test")
|
||||
}
|
||||
|
||||
cliPath, cliPathErr := getCliPath()
|
||||
if cliPathErr != nil {
|
||||
t.Errorf("failed to get cli path, err: %v", cliPathErr)
|
||||
return
|
||||
}
|
||||
|
||||
tapCmdArgs := getDefaultTapCommandArgs()
|
||||
|
||||
tapNamespace := getDefaultTapNamespace()
|
||||
tapCmdArgs = append(tapCmdArgs, tapNamespace...)
|
||||
|
||||
tapCmdArgs = append(tapCmdArgs, "--dry-run")
|
||||
|
||||
tapCmd := exec.Command(cliPath, tapCmdArgs...)
|
||||
t.Logf("running command: %v", tapCmd.String())
|
||||
|
||||
if err := tapCmd.Start(); err != nil {
|
||||
t.Errorf("failed to start tap command, err: %v", err)
|
||||
return
|
||||
}
|
||||
|
||||
resultChannel := make(chan string, 1)
|
||||
|
||||
go func() {
|
||||
if err := tapCmd.Wait(); err != nil {
|
||||
resultChannel <- "fail"
|
||||
return
|
||||
}
|
||||
resultChannel <- "success"
|
||||
}()
|
||||
|
||||
go func() {
|
||||
time.Sleep(shortRetriesCount * time.Second)
|
||||
resultChannel <- "fail"
|
||||
}()
|
||||
|
||||
testResult := <- resultChannel
|
||||
if testResult != "success" {
|
||||
t.Errorf("unexpected result - dry run cmd not done")
|
||||
}
|
||||
}
|
||||
|
||||
func TestTapRedact(t *testing.T) {
|
||||
if testing.Short() {
|
||||
t.Skip("ignored acceptance test")
|
||||
}
|
||||
|
||||
cliPath, cliPathErr := getCliPath()
|
||||
if cliPathErr != nil {
|
||||
t.Errorf("failed to get cli path, err: %v", cliPathErr)
|
||||
return
|
||||
}
|
||||
|
||||
tapCmdArgs := getDefaultTapCommandArgs()
|
||||
|
||||
tapNamespace := getDefaultTapNamespace()
|
||||
tapCmdArgs = append(tapCmdArgs, tapNamespace...)
|
||||
|
||||
tapCmd := exec.Command(cliPath, tapCmdArgs...)
|
||||
t.Logf("running command: %v", tapCmd.String())
|
||||
|
||||
t.Cleanup(func() {
|
||||
if err := cleanupCommand(tapCmd); err != nil {
|
||||
t.Logf("failed to cleanup tap command, err: %v", err)
|
||||
}
|
||||
})
|
||||
|
||||
if err := tapCmd.Start(); err != nil {
|
||||
t.Errorf("failed to start tap command, err: %v", err)
|
||||
return
|
||||
}
|
||||
|
||||
apiServerUrl := getApiServerUrl(defaultApiServerPort)
|
||||
|
||||
if err := waitTapPodsReady(apiServerUrl); err != nil {
|
||||
t.Errorf("failed to start tap pods on time, err: %v", err)
|
||||
return
|
||||
}
|
||||
|
||||
proxyUrl := getProxyUrl(defaultNamespaceName, defaultServiceName)
|
||||
requestBody := map[string]string{"User": "Mizu"}
|
||||
for i := 0; i < defaultEntriesCount; i++ {
|
||||
if _, requestErr := executeHttpPostRequest(fmt.Sprintf("%v/post", proxyUrl), requestBody); requestErr != nil {
|
||||
t.Errorf("failed to send proxy request, err: %v", requestErr)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
redactCheckFunc := func() error {
|
||||
timestamp := time.Now().UnixNano() / int64(time.Millisecond)
|
||||
|
||||
entriesUrl := fmt.Sprintf("%v/api/entries?limit=%v&operator=lt×tamp=%v", apiServerUrl, defaultEntriesCount, timestamp)
|
||||
requestResult, requestErr := executeHttpGetRequest(entriesUrl)
|
||||
if requestErr != nil {
|
||||
return fmt.Errorf("failed to get entries, err: %v", requestErr)
|
||||
}
|
||||
|
||||
entries := requestResult.([]interface{})
|
||||
if len(entries) == 0 {
|
||||
return fmt.Errorf("unexpected entries result - Expected more than 0 entries")
|
||||
}
|
||||
|
||||
firstEntry := entries[0].(map[string]interface{})
|
||||
|
||||
entryUrl := fmt.Sprintf("%v/api/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{})["entry"].(map[string]interface{})
|
||||
entryRequest := entry["request"].(map[string]interface{})
|
||||
|
||||
headers := entryRequest["headers"].([]interface{})
|
||||
for _, headerInterface := range headers {
|
||||
header := headerInterface.(map[string]interface{})
|
||||
if header["name"].(string) != "User-Agent" {
|
||||
continue
|
||||
}
|
||||
|
||||
userAgent := header["value"].(string)
|
||||
if userAgent != "[REDACTED]" {
|
||||
return fmt.Errorf("unexpected result - user agent is not redacted")
|
||||
}
|
||||
}
|
||||
|
||||
data := entryRequest["postData"].(map[string]interface{})
|
||||
textDataStr := data["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
|
||||
}
|
||||
}
|
||||
|
||||
func TestTapNoRedact(t *testing.T) {
|
||||
if testing.Short() {
|
||||
t.Skip("ignored acceptance test")
|
||||
}
|
||||
|
||||
cliPath, cliPathErr := getCliPath()
|
||||
if cliPathErr != nil {
|
||||
t.Errorf("failed to get cli path, err: %v", cliPathErr)
|
||||
return
|
||||
}
|
||||
|
||||
tapCmdArgs := getDefaultTapCommandArgs()
|
||||
|
||||
tapNamespace := getDefaultTapNamespace()
|
||||
tapCmdArgs = append(tapCmdArgs, tapNamespace...)
|
||||
|
||||
tapCmdArgs = append(tapCmdArgs, "--no-redact")
|
||||
|
||||
tapCmd := exec.Command(cliPath, tapCmdArgs...)
|
||||
t.Logf("running command: %v", tapCmd.String())
|
||||
|
||||
t.Cleanup(func() {
|
||||
if err := cleanupCommand(tapCmd); err != nil {
|
||||
t.Logf("failed to cleanup tap command, err: %v", err)
|
||||
}
|
||||
})
|
||||
|
||||
if err := tapCmd.Start(); err != nil {
|
||||
t.Errorf("failed to start tap command, err: %v", err)
|
||||
return
|
||||
}
|
||||
|
||||
apiServerUrl := getApiServerUrl(defaultApiServerPort)
|
||||
|
||||
if err := waitTapPodsReady(apiServerUrl); err != nil {
|
||||
t.Errorf("failed to start tap pods on time, err: %v", err)
|
||||
return
|
||||
}
|
||||
|
||||
proxyUrl := getProxyUrl(defaultNamespaceName, defaultServiceName)
|
||||
requestBody := map[string]string{"User": "Mizu"}
|
||||
for i := 0; i < defaultEntriesCount; i++ {
|
||||
if _, requestErr := executeHttpPostRequest(fmt.Sprintf("%v/post", proxyUrl), requestBody); requestErr != nil {
|
||||
t.Errorf("failed to send proxy request, err: %v", requestErr)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
redactCheckFunc := func() error {
|
||||
timestamp := time.Now().UnixNano() / int64(time.Millisecond)
|
||||
|
||||
entriesUrl := fmt.Sprintf("%v/api/entries?limit=%v&operator=lt×tamp=%v", apiServerUrl, defaultEntriesCount, timestamp)
|
||||
requestResult, requestErr := executeHttpGetRequest(entriesUrl)
|
||||
if requestErr != nil {
|
||||
return fmt.Errorf("failed to get entries, err: %v", requestErr)
|
||||
}
|
||||
|
||||
entries := requestResult.([]interface{})
|
||||
if len(entries) == 0 {
|
||||
return fmt.Errorf("unexpected entries result - Expected more than 0 entries")
|
||||
}
|
||||
|
||||
firstEntry := entries[0].(map[string]interface{})
|
||||
|
||||
entryUrl := fmt.Sprintf("%v/api/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{})["entry"].(map[string]interface{})
|
||||
entryRequest := entry["request"].(map[string]interface{})
|
||||
|
||||
headers := entryRequest["headers"].([]interface{})
|
||||
for _, headerInterface := range headers {
|
||||
header := headerInterface.(map[string]interface{})
|
||||
if header["name"].(string) != "User-Agent" {
|
||||
continue
|
||||
}
|
||||
|
||||
userAgent := header["value"].(string)
|
||||
if userAgent == "[REDACTED]" {
|
||||
return fmt.Errorf("unexpected result - user agent is redacted")
|
||||
}
|
||||
}
|
||||
|
||||
data := entryRequest["postData"].(map[string]interface{})
|
||||
textDataStr := data["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
|
||||
}
|
||||
}
|
||||
|
||||
func TestTapRegexMasking(t *testing.T) {
|
||||
if testing.Short() {
|
||||
t.Skip("ignored acceptance test")
|
||||
}
|
||||
|
||||
cliPath, cliPathErr := getCliPath()
|
||||
if cliPathErr != nil {
|
||||
t.Errorf("failed to get cli path, err: %v", cliPathErr)
|
||||
return
|
||||
}
|
||||
|
||||
tapCmdArgs := getDefaultTapCommandArgs()
|
||||
|
||||
tapNamespace := getDefaultTapNamespace()
|
||||
tapCmdArgs = append(tapCmdArgs, tapNamespace...)
|
||||
|
||||
tapCmdArgs = append(tapCmdArgs, "-r", "Mizu")
|
||||
|
||||
tapCmd := exec.Command(cliPath, tapCmdArgs...)
|
||||
t.Logf("running command: %v", tapCmd.String())
|
||||
|
||||
t.Cleanup(func() {
|
||||
if err := cleanupCommand(tapCmd); err != nil {
|
||||
t.Logf("failed to cleanup tap command, err: %v", err)
|
||||
}
|
||||
})
|
||||
|
||||
if err := tapCmd.Start(); err != nil {
|
||||
t.Errorf("failed to start tap command, err: %v", err)
|
||||
return
|
||||
}
|
||||
|
||||
apiServerUrl := getApiServerUrl(defaultApiServerPort)
|
||||
|
||||
if err := waitTapPodsReady(apiServerUrl); err != nil {
|
||||
t.Errorf("failed to start tap pods on time, err: %v", err)
|
||||
return
|
||||
}
|
||||
|
||||
proxyUrl := getProxyUrl(defaultNamespaceName, defaultServiceName)
|
||||
for i := 0; i < defaultEntriesCount; i++ {
|
||||
response, requestErr := http.Post(fmt.Sprintf("%v/post", proxyUrl), "text/plain", bytes.NewBufferString("Mizu"))
|
||||
if _, requestErr = executeHttpRequest(response, requestErr); requestErr != nil {
|
||||
t.Errorf("failed to send proxy request, err: %v", requestErr)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
redactCheckFunc := func() error {
|
||||
timestamp := time.Now().UnixNano() / int64(time.Millisecond)
|
||||
|
||||
entriesUrl := fmt.Sprintf("%v/api/entries?limit=%v&operator=lt×tamp=%v", apiServerUrl, defaultEntriesCount, timestamp)
|
||||
requestResult, requestErr := executeHttpGetRequest(entriesUrl)
|
||||
if requestErr != nil {
|
||||
return fmt.Errorf("failed to get entries, err: %v", requestErr)
|
||||
}
|
||||
|
||||
entries := requestResult.([]interface{})
|
||||
if len(entries) == 0 {
|
||||
return fmt.Errorf("unexpected entries result - Expected more than 0 entries")
|
||||
}
|
||||
|
||||
firstEntry := entries[0].(map[string]interface{})
|
||||
|
||||
entryUrl := fmt.Sprintf("%v/api/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{})["entry"].(map[string]interface{})
|
||||
entryRequest := entry["request"].(map[string]interface{})
|
||||
|
||||
data := entryRequest["postData"].(map[string]interface{})
|
||||
textData := data["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
|
||||
}
|
||||
}
|
||||
205
acceptanceTests/testsUtils.go
Normal file
205
acceptanceTests/testsUtils.go
Normal file
@@ -0,0 +1,205 @@
|
||||
package acceptanceTests
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"os"
|
||||
"os/exec"
|
||||
"path"
|
||||
"syscall"
|
||||
"time"
|
||||
)
|
||||
|
||||
const (
|
||||
longRetriesCount = 100
|
||||
shortRetriesCount = 10
|
||||
defaultApiServerPort = 8899
|
||||
defaultNamespaceName = "mizu-tests"
|
||||
defaultServiceName = "httpbin"
|
||||
defaultEntriesCount = 50
|
||||
)
|
||||
|
||||
func getCliPath() (string, error) {
|
||||
dir, filePathErr := os.Getwd()
|
||||
if filePathErr != nil {
|
||||
return "", filePathErr
|
||||
}
|
||||
|
||||
cliPath := path.Join(dir, "../cli/bin/mizu_ci")
|
||||
return cliPath, nil
|
||||
}
|
||||
|
||||
func getConfigPath() (string, error) {
|
||||
home, homeDirErr := os.UserHomeDir()
|
||||
if homeDirErr != nil {
|
||||
return "", homeDirErr
|
||||
}
|
||||
|
||||
return path.Join(home, ".mizu", "config.yaml"), nil
|
||||
}
|
||||
|
||||
func getProxyUrl(namespace string, service string) string {
|
||||
return fmt.Sprintf("http://localhost:8080/api/v1/namespaces/%v/services/%v/proxy", namespace, service)
|
||||
}
|
||||
|
||||
func getApiServerUrl(port uint16) string {
|
||||
return fmt.Sprintf("http://localhost:%v/mizu", port)
|
||||
}
|
||||
|
||||
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"
|
||||
|
||||
return []string{setFlag, telemetry, setFlag, agentImage, setFlag, imagePullPolicy}
|
||||
}
|
||||
|
||||
func getDefaultTapCommandArgs() []string {
|
||||
tapCommand := "tap"
|
||||
defaultCmdArgs := getDefaultCommandArgs()
|
||||
|
||||
return append([]string{tapCommand}, defaultCmdArgs...)
|
||||
}
|
||||
|
||||
func getDefaultTapCommandArgsWithRegex(regex string) []string {
|
||||
tapCommand := "tap"
|
||||
defaultCmdArgs := getDefaultCommandArgs()
|
||||
|
||||
return append([]string{tapCommand, regex}, defaultCmdArgs...)
|
||||
}
|
||||
|
||||
func getDefaultTapNamespace() []string {
|
||||
return []string{"-n", "mizu-tests"}
|
||||
}
|
||||
|
||||
func getDefaultFetchCommandArgs() []string {
|
||||
fetchCommand := "fetch"
|
||||
defaultCmdArgs := getDefaultCommandArgs()
|
||||
|
||||
return append([]string{fetchCommand}, defaultCmdArgs...)
|
||||
}
|
||||
|
||||
func getDefaultConfigCommandArgs() []string {
|
||||
configCommand := "config"
|
||||
defaultCmdArgs := getDefaultCommandArgs()
|
||||
|
||||
return append([]string{configCommand}, defaultCmdArgs...)
|
||||
}
|
||||
|
||||
func retriesExecute(retriesCount int, executeFunc func() error) error {
|
||||
var lastError error
|
||||
|
||||
for i := 0; i < retriesCount; i++ {
|
||||
if err := executeFunc(); err != nil {
|
||||
lastError = err
|
||||
|
||||
time.Sleep(1 * time.Second)
|
||||
continue
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
return fmt.Errorf("reached max retries count, retries count: %v, last err: %v", retriesCount, lastError)
|
||||
}
|
||||
|
||||
func waitTapPodsReady(apiServerUrl string) error {
|
||||
resolvingUrl := fmt.Sprintf("%v/status/tappersCount", 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")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
return retriesExecute(longRetriesCount, tapPodsReadyFunc)
|
||||
}
|
||||
|
||||
func jsonBytesToInterface(jsonBytes []byte) (interface{}, error) {
|
||||
var result interface{}
|
||||
if parseErr := json.Unmarshal(jsonBytes, &result); parseErr != nil {
|
||||
return nil, parseErr
|
||||
}
|
||||
|
||||
return result, nil
|
||||
}
|
||||
|
||||
func executeHttpRequest(response *http.Response, requestErr error) (interface{}, error) {
|
||||
if requestErr != nil {
|
||||
return nil, requestErr
|
||||
} else if response.StatusCode != 200 {
|
||||
return nil, fmt.Errorf("invalid status code %v", response.StatusCode)
|
||||
}
|
||||
|
||||
defer func() { response.Body.Close() }()
|
||||
|
||||
data, readErr := ioutil.ReadAll(response.Body)
|
||||
if readErr != nil {
|
||||
return nil, readErr
|
||||
}
|
||||
|
||||
return jsonBytesToInterface(data)
|
||||
}
|
||||
|
||||
func executeHttpGetRequest(url string) (interface{}, error) {
|
||||
response, requestErr := http.Get(url)
|
||||
return executeHttpRequest(response, requestErr)
|
||||
}
|
||||
|
||||
func executeHttpPostRequest(url string, body interface{}) (interface{}, error) {
|
||||
requestBody, jsonErr := json.Marshal(body)
|
||||
if jsonErr != nil {
|
||||
return nil, jsonErr
|
||||
}
|
||||
|
||||
response, requestErr := http.Post(url, "application/json", bytes.NewBuffer(requestBody))
|
||||
return executeHttpRequest(response, requestErr)
|
||||
}
|
||||
|
||||
func cleanupCommand(cmd *exec.Cmd) error {
|
||||
if err := cmd.Process.Signal(syscall.SIGQUIT); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := cmd.Wait(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func getEntriesFromHarBytes(harBytes []byte) ([]interface{}, error) {
|
||||
harInterface, convertErr := jsonBytesToInterface(harBytes)
|
||||
if convertErr != nil {
|
||||
return nil, convertErr
|
||||
}
|
||||
|
||||
har := harInterface.(map[string]interface{})
|
||||
harLog := har["log"].(map[string]interface{})
|
||||
harEntries := harLog["entries"].([]interface{})
|
||||
|
||||
return harEntries, nil
|
||||
}
|
||||
|
||||
func getPods(tapStatusInterface interface{}) ([]map[string]interface{}, error) {
|
||||
tapStatus := tapStatusInterface.(map[string]interface{})
|
||||
podsInterface := tapStatus["pods"].([]interface{})
|
||||
|
||||
var pods []map[string]interface{}
|
||||
for _, podInterface := range podsInterface {
|
||||
pods = append(pods, podInterface.(map[string]interface{}))
|
||||
}
|
||||
|
||||
return pods, nil
|
||||
}
|
||||
2
agent/Makefile
Normal file
2
agent/Makefile
Normal file
@@ -0,0 +1,2 @@
|
||||
test: ## Run agent tests.
|
||||
@go test ./... -coverpkg=./... -race -coverprofile=coverage.out -covermode=atomic
|
||||
@@ -3,7 +3,6 @@ module mizuserver
|
||||
go 1.16
|
||||
|
||||
require (
|
||||
github.com/beevik/etree v1.1.0
|
||||
github.com/djherbis/atime v1.0.0
|
||||
github.com/fsnotify/fsnotify v1.4.9
|
||||
github.com/gin-contrib/static v0.0.1
|
||||
@@ -18,8 +17,9 @@ require (
|
||||
github.com/romana/rlog v0.0.0-20171115192701-f018bc92e7d7
|
||||
github.com/up9inc/mizu/shared v0.0.0
|
||||
github.com/up9inc/mizu/tap v0.0.0
|
||||
github.com/up9inc/mizu/tap/api v0.0.0
|
||||
github.com/yalp/jsonpath v0.0.0-20180802001716-5cc68e5049a0
|
||||
go.mongodb.org/mongo-driver v1.5.1
|
||||
go.mongodb.org/mongo-driver v1.7.1
|
||||
gorm.io/driver/sqlite v1.1.4
|
||||
gorm.io/gorm v1.21.8
|
||||
k8s.io/api v0.21.0
|
||||
@@ -30,3 +30,5 @@ require (
|
||||
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
|
||||
|
||||
19
agent/go.sum
19
agent/go.sum
@@ -42,9 +42,6 @@ github.com/NYTimes/gziphandler v0.0.0-20170623195520-56545f4a5d46/go.mod h1:3wb0
|
||||
github.com/PuerkitoBio/purell v1.1.1/go.mod h1:c11w/QuzBsJSee3cPx9rAFu61PvFxuPbtSwDGJws/X0=
|
||||
github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578/go.mod h1:uGdkoq3SwY9Y+13GIhn11/XLaGBb4BfwItxLd5jeuXE=
|
||||
github.com/asaskevich/govalidator v0.0.0-20190424111038-f61b66f89f4a/go.mod h1:lB+ZfQJz7igIIfQNfa7Ml4HSf2uFQQRzpGGRXenZAgY=
|
||||
github.com/aws/aws-sdk-go v1.34.28/go.mod h1:H7NKnBqNVzoTJpGfLrQkkD+ytBA93eiDYi/+8rV9s48=
|
||||
github.com/beevik/etree v1.1.0 h1:T0xke/WvNtMoCqgzPhkX2r4rjY3GDZFi+FjpRZY2Jbs=
|
||||
github.com/beevik/etree v1.1.0/go.mod h1:r8Aw8JqVegEf0w2fDnATrX9VpkMcyFeM0FhwO62wh+A=
|
||||
github.com/bradleyfalzon/tlsx v0.0.0-20170624122154-28fd0e59bac4 h1:NJOOlc6ZJjix0A1rAU+nxruZtR8KboG1848yqpIUo4M=
|
||||
github.com/bradleyfalzon/tlsx v0.0.0-20170624122154-28fd0e59bac4/go.mod h1:DQPxZS994Ld1Y8uwnJT+dRL04XPD0cElP/pHH/zEBHM=
|
||||
github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
|
||||
@@ -101,7 +98,6 @@ github.com/go-playground/validator/v10 v10.2.0/go.mod h1:uOYAAleCW8F/7oMFd6aG0GO
|
||||
github.com/go-playground/validator/v10 v10.4.1/go.mod h1:nlOn6nFhuKACm19sB/8EGNn9GlaMV7XkbRSipzJ0Ii4=
|
||||
github.com/go-playground/validator/v10 v10.5.0 h1:X9rflw/KmpACwT8zdrm1upefpvdy6ur8d1kWyq6sg3E=
|
||||
github.com/go-playground/validator/v10 v10.5.0/go.mod h1:xm76BBt941f7yWdGnI2DVPFFg1UK3YY04qifoXU3lOk=
|
||||
github.com/go-sql-driver/mysql v1.5.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg=
|
||||
github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY=
|
||||
github.com/gobuffalo/attrs v0.0.0-20190224210810-a9411de4debd/go.mod h1:4duuawTqi2wkkpB4ePgWMaai6/Kc6WEz83bhFwpHzj0=
|
||||
github.com/gobuffalo/depgen v0.0.0-20190329151759-d478694a28d3/go.mod h1:3STtPUQYuzV0gBVOY3vy6CfMm/ljR4pABfrTeHNLHUY=
|
||||
@@ -194,8 +190,6 @@ github.com/jinzhu/inflection v1.0.0/go.mod h1:h+uFLlag+Qp1Va5pdKtLDYj+kHp5pxUVkr
|
||||
github.com/jinzhu/now v1.1.1/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8=
|
||||
github.com/jinzhu/now v1.1.2 h1:eVKgfIdy9b6zbWBMgFpfDPoAMifwSZagU9HmEU6zgiI=
|
||||
github.com/jinzhu/now v1.1.2/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8=
|
||||
github.com/jmespath/go-jmespath v0.4.0/go.mod h1:T8mJZnbsbmF+m6zOOFylbeCJqk5+pHWvzYPziyZiYoo=
|
||||
github.com/jmespath/go-jmespath/internal/testify v1.5.1/go.mod h1:L3OGu8Wl2/fWfCI6z80xFu9LTZmf1ZRjMHUOPmWr69U=
|
||||
github.com/joho/godotenv v1.3.0/go.mod h1:7hK45KPybAkOC6peb+G5yklZfMxEjkZhHbwpqxOKXbg=
|
||||
github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU=
|
||||
github.com/json-iterator/go v1.1.9/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
|
||||
@@ -292,8 +286,8 @@ github.com/yalp/jsonpath v0.0.0-20180802001716-5cc68e5049a0/go.mod h1:/LWChgwKmv
|
||||
github.com/youmark/pkcs8 v0.0.0-20181117223130-1be2e3e5546d/go.mod h1:rHwXgn7JulP+udvsHwJoVG1YGAP6VLg4y9I5dyZdqmA=
|
||||
github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
||||
github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
||||
go.mongodb.org/mongo-driver v1.5.1 h1:9nOVLGDfOaZ9R0tBumx/BcuqkbFpyTCU2r/Po7A2azI=
|
||||
go.mongodb.org/mongo-driver v1.5.1/go.mod h1:gRXCHX4Jo7J0IJ1oDQyUxF7jfy19UfxniMS4xxMmUqw=
|
||||
go.mongodb.org/mongo-driver v1.7.1 h1:jwqTeEM3x6L9xDXrCxN0Hbg7vdGfPBOTIkr0+/LYZDA=
|
||||
go.mongodb.org/mongo-driver v1.7.1/go.mod h1:Q4oFMbo1+MSNqICAdYMlC/zSTrwCogR4R8NzkI+yfU8=
|
||||
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=
|
||||
@@ -362,9 +356,8 @@ golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLL
|
||||
golang.org/x/net v0.0.0-20200301022130-244492dfa37a/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-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-20210224082022-3d97a244fca7 h1:OgUuv8lsRpBibGNbSizVwKWlysjaNzmC9gYMhPVfqFM=
|
||||
golang.org/x/net v0.0.0-20210224082022-3d97a244fca7/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
|
||||
golang.org/x/net v0.0.0-20210421230115-4e50805a0758 h1:aEpZnXcAmXkd6AvLb2OPt+EN1Zu/8Ne3pCqPjja5PXY=
|
||||
golang.org/x/net v0.0.0-20210421230115-4e50805a0758/go.mod h1:72T/g9IO56b78aLF+1Kcs5dz7/ng1VjMUvfKvpfy+jM=
|
||||
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
|
||||
golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
||||
golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
||||
@@ -410,9 +403,8 @@ golang.org/x/sys v0.0.0-20200302150141-5c8b2ff67527/go.mod h1:h1NjWce9XRLGQEsW7w
|
||||
golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210225134936-a50acf3fe073 h1:8qxJSnu+7dRq6upnbntrmriWByIakBuct5OM/MdQC1M=
|
||||
golang.org/x/sys v0.0.0-20210225134936-a50acf3fe073/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210420072515-93ed5bcd2bfe h1:WdX7u8s3yOigWAhHEaDl8r9G+4XwFQEQFtBMYyN+kXQ=
|
||||
golang.org/x/sys v0.0.0-20210420072515-93ed5bcd2bfe/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw=
|
||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||
golang.org/x/term v0.0.0-20210220032956-6a3ed077a48d h1:SZxvLBoTP5yHO3Frd4z4vrF+DBX9vMVanchswa69toE=
|
||||
@@ -423,9 +415,8 @@ golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3
|
||||
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
|
||||
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/text v0.3.4/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/text v0.3.5 h1:i6eZZ+zk0SOf0xgBpEpPD18qWcJda6q1sxt3S0kzyUQ=
|
||||
golang.org/x/text v0.3.5/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/text v0.3.6 h1:aRYxNxv6iGQlyVaZmk6ZgYEDa+Jg18DxebPSrd6bg1M=
|
||||
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||
golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||
golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||
|
||||
186
agent/main.go
186
agent/main.go
@@ -4,21 +4,29 @@ import (
|
||||
"encoding/json"
|
||||
"flag"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"log"
|
||||
"mizuserver/pkg/api"
|
||||
"mizuserver/pkg/controllers"
|
||||
"mizuserver/pkg/models"
|
||||
"mizuserver/pkg/routes"
|
||||
"mizuserver/pkg/utils"
|
||||
"net/http"
|
||||
"os"
|
||||
"os/signal"
|
||||
"path"
|
||||
"path/filepath"
|
||||
"plugin"
|
||||
"sort"
|
||||
"strings"
|
||||
|
||||
"github.com/gin-contrib/static"
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/gorilla/websocket"
|
||||
"github.com/romana/rlog"
|
||||
"github.com/up9inc/mizu/shared"
|
||||
"github.com/up9inc/mizu/tap"
|
||||
"mizuserver/pkg/api"
|
||||
"mizuserver/pkg/models"
|
||||
"mizuserver/pkg/routes"
|
||||
"mizuserver/pkg/sensitiveDataFiltering"
|
||||
"mizuserver/pkg/utils"
|
||||
"net/http"
|
||||
"os"
|
||||
"os/signal"
|
||||
"strings"
|
||||
tapApi "github.com/up9inc/mizu/tap/api"
|
||||
)
|
||||
|
||||
var tapperMode = flag.Bool("tap", false, "Run in tapper mode without API")
|
||||
@@ -26,25 +34,32 @@ var apiServerMode = flag.Bool("api-server", false, "Run in API server mode with
|
||||
var standaloneMode = flag.Bool("standalone", false, "Run in standalone tapper and API mode")
|
||||
var apiServerAddress = flag.String("api-server-address", "", "Address of mizu API server")
|
||||
var namespace = flag.String("namespace", "", "Resolve IPs if they belong to resources in this namespace (default is all)")
|
||||
var harsReaderMode = flag.Bool("hars-read", false, "Run in hars-read mode")
|
||||
var harsDir = flag.String("hars-dir", "", "Directory to read hars from")
|
||||
|
||||
var extensions []*tapApi.Extension // global
|
||||
var extensionsMap map[string]*tapApi.Extension // global
|
||||
|
||||
func main() {
|
||||
flag.Parse()
|
||||
loadExtensions()
|
||||
hostMode := os.Getenv(shared.HostModeEnvVar) == "1"
|
||||
tapOpts := &tap.TapOpts{HostMode: hostMode}
|
||||
|
||||
if !*tapperMode && !*apiServerMode && !*standaloneMode {
|
||||
panic("One of the flags --tap, --api or --standalone must be provided")
|
||||
if !*tapperMode && !*apiServerMode && !*standaloneMode && !*harsReaderMode {
|
||||
panic("One of the flags --tap, --api or --standalone or --hars-read must be provided")
|
||||
}
|
||||
|
||||
if *standaloneMode {
|
||||
api.StartResolving(*namespace)
|
||||
|
||||
harOutputChannel, outboundLinkOutputChannel := tap.StartPassiveTapper(tapOpts)
|
||||
filteredHarChannel := make(chan *tap.OutputChannelItem)
|
||||
outputItemsChannel := make(chan *tapApi.OutputChannelItem)
|
||||
filteredOutputItemsChannel := make(chan *tapApi.OutputChannelItem)
|
||||
tap.StartPassiveTapper(tapOpts, outputItemsChannel, extensions)
|
||||
|
||||
go filterHarItems(harOutputChannel, filteredHarChannel, getTrafficFilteringOptions())
|
||||
go api.StartReadingEntries(filteredHarChannel, nil)
|
||||
go api.StartReadingOutbound(outboundLinkOutputChannel)
|
||||
go filterItems(outputItemsChannel, filteredOutputItemsChannel, getTrafficFilteringOptions())
|
||||
go api.StartReadingEntries(filteredOutputItemsChannel, nil, extensionsMap)
|
||||
// go api.StartReadingOutbound(outboundLinkOutputChannel)
|
||||
|
||||
hostApi(nil)
|
||||
} else if *tapperMode {
|
||||
@@ -58,25 +73,33 @@ func main() {
|
||||
rlog.Infof("Filtering for the following authorities: %v", tap.GetFilterIPs())
|
||||
}
|
||||
|
||||
harOutputChannel, outboundLinkOutputChannel := tap.StartPassiveTapper(tapOpts)
|
||||
|
||||
// harOutputChannel, outboundLinkOutputChannel := tap.StartPassiveTapper(tapOpts)
|
||||
filteredOutputItemsChannel := make(chan *tapApi.OutputChannelItem)
|
||||
tap.StartPassiveTapper(tapOpts, filteredOutputItemsChannel, extensions)
|
||||
socketConnection, err := shared.ConnectToSocketServer(*apiServerAddress, shared.DEFAULT_SOCKET_RETRIES, shared.DEFAULT_SOCKET_RETRY_SLEEP_TIME, false)
|
||||
if err != nil {
|
||||
panic(fmt.Sprintf("Error connecting to socket server at %s %v", *apiServerAddress, err))
|
||||
}
|
||||
|
||||
go pipeTapChannelToSocket(socketConnection, harOutputChannel)
|
||||
go pipeOutboundLinksChannelToSocket(socketConnection, outboundLinkOutputChannel)
|
||||
go pipeTapChannelToSocket(socketConnection, filteredOutputItemsChannel)
|
||||
// go pipeOutboundLinksChannelToSocket(socketConnection, outboundLinkOutputChannel)
|
||||
} else if *apiServerMode {
|
||||
api.StartResolving(*namespace)
|
||||
|
||||
socketHarOutChannel := make(chan *tap.OutputChannelItem, 1000)
|
||||
filteredHarChannel := make(chan *tap.OutputChannelItem)
|
||||
outputItemsChannel := make(chan *tapApi.OutputChannelItem)
|
||||
filteredOutputItemsChannel := make(chan *tapApi.OutputChannelItem)
|
||||
|
||||
go filterHarItems(socketHarOutChannel, filteredHarChannel, getTrafficFilteringOptions())
|
||||
go api.StartReadingEntries(filteredHarChannel, nil)
|
||||
go filterItems(outputItemsChannel, filteredOutputItemsChannel, getTrafficFilteringOptions())
|
||||
go api.StartReadingEntries(filteredOutputItemsChannel, nil, extensionsMap)
|
||||
|
||||
hostApi(socketHarOutChannel)
|
||||
hostApi(outputItemsChannel)
|
||||
} else if *harsReaderMode {
|
||||
outputItemsChannel := make(chan *tapApi.OutputChannelItem, 1000)
|
||||
filteredHarChannel := make(chan *tapApi.OutputChannelItem)
|
||||
|
||||
go filterItems(outputItemsChannel, filteredHarChannel, getTrafficFilteringOptions())
|
||||
go api.StartReadingEntries(filteredHarChannel, harsDir, extensionsMap)
|
||||
hostApi(nil)
|
||||
}
|
||||
|
||||
signalChan := make(chan os.Signal, 1)
|
||||
@@ -86,7 +109,50 @@ func main() {
|
||||
rlog.Info("Exiting")
|
||||
}
|
||||
|
||||
func hostApi(socketHarOutputChannel chan<- *tap.OutputChannelItem) {
|
||||
func loadExtensions() {
|
||||
dir, _ := filepath.Abs(filepath.Dir(os.Args[0]))
|
||||
extensionsDir := path.Join(dir, "./extensions/")
|
||||
|
||||
files, err := ioutil.ReadDir(extensionsDir)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
extensions = make([]*tapApi.Extension, len(files))
|
||||
extensionsMap = make(map[string]*tapApi.Extension)
|
||||
for i, file := range files {
|
||||
filename := file.Name()
|
||||
log.Printf("Loading extension: %s\n", 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\n", extension.Path))
|
||||
}
|
||||
dissector.Register(extension)
|
||||
extension.Dissector = dissector
|
||||
extensions[i] = extension
|
||||
extensionsMap[extension.Protocol.Name] = extension
|
||||
}
|
||||
|
||||
sort.Slice(extensions, func(i, j int) bool {
|
||||
return extensions[i].Protocol.Priority < extensions[j].Protocol.Priority
|
||||
})
|
||||
|
||||
for _, extension := range extensions {
|
||||
log.Printf("Extension Properties: %+v\n", extension)
|
||||
}
|
||||
|
||||
controllers.InitExtensionsMap(extensionsMap)
|
||||
}
|
||||
|
||||
func hostApi(socketHarOutputChannel chan<- *tapApi.OutputChannelItem) {
|
||||
app := gin.Default()
|
||||
|
||||
app.GET("/echo", func(c *gin.Context) {
|
||||
@@ -94,9 +160,10 @@ func hostApi(socketHarOutputChannel chan<- *tap.OutputChannelItem) {
|
||||
})
|
||||
|
||||
eventHandlers := api.RoutesEventHandlers{
|
||||
SocketHarOutChannel: socketHarOutputChannel,
|
||||
SocketOutChannel: socketHarOutputChannel,
|
||||
}
|
||||
|
||||
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
|
||||
|
||||
@@ -109,6 +176,17 @@ func hostApi(socketHarOutputChannel chan<- *tap.OutputChannelItem) {
|
||||
utils.StartServer(app)
|
||||
}
|
||||
|
||||
func DisableRootStaticCache() gin.HandlerFunc {
|
||||
return func(c *gin.Context) {
|
||||
if c.Request.RequestURI == "/" {
|
||||
// Disable cache only for the main static route
|
||||
c.Writer.Header().Set("Cache-Control", "no-store")
|
||||
}
|
||||
|
||||
c.Next()
|
||||
}
|
||||
}
|
||||
|
||||
func CORSMiddleware() gin.HandlerFunc {
|
||||
return func(c *gin.Context) {
|
||||
c.Writer.Header().Set("Access-Control-Allow-Origin", "*")
|
||||
@@ -125,20 +203,34 @@ func CORSMiddleware() gin.HandlerFunc {
|
||||
}
|
||||
}
|
||||
|
||||
func parseEnvVar(env string) map[string][]string {
|
||||
var mapOfList map[string][]string
|
||||
|
||||
val, present := os.LookupEnv(env)
|
||||
|
||||
if !present {
|
||||
return mapOfList
|
||||
}
|
||||
|
||||
err := json.Unmarshal([]byte(val), &mapOfList)
|
||||
if err != nil {
|
||||
panic(fmt.Sprintf("env var %s's value of %s is invalid! must be map[string][]string %v", env, mapOfList, err))
|
||||
}
|
||||
return mapOfList
|
||||
}
|
||||
|
||||
func getTapTargets() []string {
|
||||
nodeName := os.Getenv(shared.NodeNameEnvVar)
|
||||
var tappedAddressesPerNodeDict map[string][]string
|
||||
err := json.Unmarshal([]byte(os.Getenv(shared.TappedAddressesPerNodeDictEnvVar)), &tappedAddressesPerNodeDict)
|
||||
if err != nil {
|
||||
panic(fmt.Sprintf("env var %s's value of %s is invalid! must be map[string][]string %v", shared.TappedAddressesPerNodeDictEnvVar, tappedAddressesPerNodeDict, err))
|
||||
}
|
||||
tappedAddressesPerNodeDict := parseEnvVar(shared.TappedAddressesPerNodeDictEnvVar)
|
||||
return tappedAddressesPerNodeDict[nodeName]
|
||||
}
|
||||
|
||||
func getTrafficFilteringOptions() *shared.TrafficFilteringOptions {
|
||||
filteringOptionsJson := os.Getenv(shared.MizuFilteringOptionsEnvVar)
|
||||
if filteringOptionsJson == "" {
|
||||
return nil
|
||||
return &shared.TrafficFilteringOptions{
|
||||
HealthChecksUserAgentHeaders: []string{},
|
||||
}
|
||||
}
|
||||
var filteringOptions shared.TrafficFilteringOptions
|
||||
err := json.Unmarshal([]byte(filteringOptionsJson), &filteringOptions)
|
||||
@@ -149,7 +241,7 @@ func getTrafficFilteringOptions() *shared.TrafficFilteringOptions {
|
||||
return &filteringOptions
|
||||
}
|
||||
|
||||
func filterHarItems(inChannel <-chan *tap.OutputChannelItem, outChannel chan *tap.OutputChannelItem, filterOptions *shared.TrafficFilteringOptions) {
|
||||
func filterItems(inChannel <-chan *tapApi.OutputChannelItem, outChannel chan *tapApi.OutputChannelItem, filterOptions *shared.TrafficFilteringOptions) {
|
||||
for message := range inChannel {
|
||||
if message.ConnectionInfo.IsOutgoing && api.CheckIsServiceIP(message.ConnectionInfo.ServerIP) {
|
||||
continue
|
||||
@@ -159,19 +251,27 @@ func filterHarItems(inChannel <-chan *tap.OutputChannelItem, outChannel chan *ta
|
||||
continue
|
||||
}
|
||||
|
||||
if !filterOptions.DisableRedaction {
|
||||
sensitiveDataFiltering.FilterSensitiveInfoFromHarRequest(message, filterOptions)
|
||||
}
|
||||
// if !filterOptions.DisableRedaction {
|
||||
// sensitiveDataFiltering.FilterSensitiveInfoFromHarRequest(message, filterOptions)
|
||||
// }
|
||||
|
||||
outChannel <- message
|
||||
}
|
||||
}
|
||||
|
||||
func isHealthCheckByUserAgent(message *tap.OutputChannelItem, userAgentsToIgnore []string) bool {
|
||||
for _, header := range message.HarEntry.Request.Headers {
|
||||
if strings.ToLower(header.Name) == "user-agent" {
|
||||
func isHealthCheckByUserAgent(item *tapApi.OutputChannelItem, userAgentsToIgnore []string) bool {
|
||||
if item.Protocol.Name != "http" {
|
||||
return false
|
||||
}
|
||||
|
||||
request := item.Pair.Request.Payload.(map[string]interface{})
|
||||
reqDetails := request["details"].(map[string]interface{})
|
||||
|
||||
for _, header := range reqDetails["headers"].([]interface{}) {
|
||||
h := header.(map[string]interface{})
|
||||
if strings.ToLower(h["name"].(string)) == "user-agent" {
|
||||
for _, userAgent := range userAgentsToIgnore {
|
||||
if strings.Contains(strings.ToLower(header.Value), strings.ToLower(userAgent)) {
|
||||
if strings.Contains(strings.ToLower(h["value"].(string)), strings.ToLower(userAgent)) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
@@ -181,7 +281,7 @@ func isHealthCheckByUserAgent(message *tap.OutputChannelItem, userAgentsToIgnore
|
||||
return false
|
||||
}
|
||||
|
||||
func pipeTapChannelToSocket(connection *websocket.Conn, messageDataChannel <-chan *tap.OutputChannelItem) {
|
||||
func pipeTapChannelToSocket(connection *websocket.Conn, messageDataChannel <-chan *tapApi.OutputChannelItem) {
|
||||
if connection == nil {
|
||||
panic("Websocket connection is nil")
|
||||
}
|
||||
@@ -197,6 +297,8 @@ func pipeTapChannelToSocket(connection *websocket.Conn, messageDataChannel <-cha
|
||||
continue
|
||||
}
|
||||
|
||||
// NOTE: This is where the `*tapApi.OutputChannelItem` leaves the code
|
||||
// and goes into the intermediate WebSocket.
|
||||
err = connection.WriteMessage(websocket.TextMessage, marshaledData)
|
||||
if err != nil {
|
||||
rlog.Infof("error sending message through socket server %s, (%v,%+v)\n", err, err, err)
|
||||
|
||||
@@ -5,6 +5,7 @@ import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"mizuserver/pkg/database"
|
||||
"mizuserver/pkg/holder"
|
||||
"net/url"
|
||||
"os"
|
||||
@@ -13,12 +14,13 @@ import (
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"go.mongodb.org/mongo-driver/bson/primitive"
|
||||
|
||||
"github.com/google/martian/har"
|
||||
"github.com/romana/rlog"
|
||||
"github.com/up9inc/mizu/tap"
|
||||
"go.mongodb.org/mongo-driver/bson/primitive"
|
||||
tapApi "github.com/up9inc/mizu/tap/api"
|
||||
|
||||
"mizuserver/pkg/database"
|
||||
"mizuserver/pkg/models"
|
||||
"mizuserver/pkg/resolver"
|
||||
"mizuserver/pkg/utils"
|
||||
@@ -48,11 +50,11 @@ func StartResolving(namespace string) {
|
||||
holder.SetResolver(res)
|
||||
}
|
||||
|
||||
func StartReadingEntries(harChannel <-chan *tap.OutputChannelItem, workingDir *string) {
|
||||
func StartReadingEntries(harChannel <-chan *tapApi.OutputChannelItem, workingDir *string, extensionsMap map[string]*tapApi.Extension) {
|
||||
if workingDir != nil && *workingDir != "" {
|
||||
startReadingFiles(*workingDir)
|
||||
} else {
|
||||
startReadingChannel(harChannel)
|
||||
startReadingChannel(harChannel, extensionsMap)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -86,29 +88,36 @@ func startReadingFiles(workingDir string) {
|
||||
decErr := json.NewDecoder(bufio.NewReader(file)).Decode(&inputHar)
|
||||
utils.CheckErr(decErr)
|
||||
|
||||
for _, entry := range inputHar.Log.Entries {
|
||||
time.Sleep(time.Millisecond * 250)
|
||||
connectionInfo := &tap.ConnectionInfo{
|
||||
ClientIP: fileInfo.Name(),
|
||||
ClientPort: "",
|
||||
ServerIP: "",
|
||||
ServerPort: "",
|
||||
IsOutgoing: false,
|
||||
}
|
||||
saveHarToDb(entry, connectionInfo)
|
||||
}
|
||||
// for _, entry := range inputHar.Log.Entries {
|
||||
// time.Sleep(time.Millisecond * 250)
|
||||
// // connectionInfo := &tap.ConnectionInfo{
|
||||
// // ClientIP: fileInfo.Name(),
|
||||
// // ClientPort: "",
|
||||
// // ServerIP: "",
|
||||
// // ServerPort: "",
|
||||
// // IsOutgoing: false,
|
||||
// // }
|
||||
// // saveHarToDb(entry, connectionInfo)
|
||||
// }
|
||||
rmErr := os.Remove(inputFilePath)
|
||||
utils.CheckErr(rmErr)
|
||||
}
|
||||
}
|
||||
|
||||
func startReadingChannel(outputItems <-chan *tap.OutputChannelItem) {
|
||||
func startReadingChannel(outputItems <-chan *tapApi.OutputChannelItem, extensionsMap map[string]*tapApi.Extension) {
|
||||
if outputItems == nil {
|
||||
panic("Channel of captured messages is nil")
|
||||
}
|
||||
|
||||
for item := range outputItems {
|
||||
saveHarToDb(item.HarEntry, item.ConnectionInfo)
|
||||
extension := extensionsMap[item.Protocol.Name]
|
||||
resolvedSource, resolvedDestionation := resolveIP(item.ConnectionInfo)
|
||||
mizuEntry := extension.Dissector.Analyze(item, primitive.NewObjectID().Hex(), resolvedSource, resolvedDestionation)
|
||||
baseEntry := extension.Dissector.Summarize(mizuEntry)
|
||||
mizuEntry.EstimatedSizeBytes = getEstimatedEntrySizeBytes(mizuEntry)
|
||||
database.CreateEntry(mizuEntry)
|
||||
baseEntryBytes, _ := models.CreateBaseEntryWebSocketMessage(baseEntry)
|
||||
BroadcastToBrowserClients(baseEntryBytes)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -119,14 +128,7 @@ func StartReadingOutbound(outboundLinkChannel <-chan *tap.OutboundLink) {
|
||||
}
|
||||
}
|
||||
|
||||
func saveHarToDb(entry *har.Entry, connectionInfo *tap.ConnectionInfo) {
|
||||
entryBytes, _ := json.Marshal(entry)
|
||||
serviceName, urlPath := getServiceNameFromUrl(entry.Request.URL)
|
||||
entryId := primitive.NewObjectID().Hex()
|
||||
var (
|
||||
resolvedSource string
|
||||
resolvedDestination string
|
||||
)
|
||||
func resolveIP(connectionInfo *tapApi.ConnectionInfo) (resolvedSource string, resolvedDestination string) {
|
||||
if k8sResolver != nil {
|
||||
unresolvedSource := connectionInfo.ClientIP
|
||||
resolvedSource = k8sResolver.Resolve(unresolvedSource)
|
||||
@@ -145,32 +147,7 @@ func saveHarToDb(entry *har.Entry, connectionInfo *tap.ConnectionInfo) {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
mizuEntry := models.MizuEntry{
|
||||
EntryId: entryId,
|
||||
Entry: string(entryBytes), // simple way to store it and not convert to bytes
|
||||
Service: serviceName,
|
||||
Url: entry.Request.URL,
|
||||
Path: urlPath,
|
||||
Method: entry.Request.Method,
|
||||
Status: entry.Response.Status,
|
||||
RequestSenderIp: connectionInfo.ClientIP,
|
||||
Timestamp: entry.StartedDateTime.UnixNano() / int64(time.Millisecond),
|
||||
ResolvedSource: resolvedSource,
|
||||
ResolvedDestination: resolvedDestination,
|
||||
IsOutgoing: connectionInfo.IsOutgoing,
|
||||
}
|
||||
mizuEntry.EstimatedSizeBytes = getEstimatedEntrySizeBytes(mizuEntry)
|
||||
database.CreateEntry(&mizuEntry)
|
||||
|
||||
baseEntry := models.BaseEntryDetails{}
|
||||
if err := models.GetEntry(&mizuEntry, &baseEntry); err != nil {
|
||||
return
|
||||
}
|
||||
baseEntry.Rules = models.RunValidationRulesState(*entry, serviceName)
|
||||
baseEntry.Latency = entry.Timings.Receive
|
||||
baseEntryBytes, _ := models.CreateBaseEntryWebSocketMessage(&baseEntry)
|
||||
BroadcastToBrowserClients(baseEntryBytes)
|
||||
return resolvedSource, resolvedDestination
|
||||
}
|
||||
|
||||
func getServiceNameFromUrl(inputUrl string) (string, string) {
|
||||
@@ -180,11 +157,14 @@ func getServiceNameFromUrl(inputUrl string) (string, string) {
|
||||
}
|
||||
|
||||
func CheckIsServiceIP(address string) bool {
|
||||
if k8sResolver == nil {
|
||||
return false
|
||||
}
|
||||
return k8sResolver.CheckIsServiceIP(address)
|
||||
}
|
||||
|
||||
// gives a rough estimate of the size this will take up in the db, good enough for maintaining db size limit accurately
|
||||
func getEstimatedEntrySizeBytes(mizuEntry models.MizuEntry) int {
|
||||
func getEstimatedEntrySizeBytes(mizuEntry *tapApi.MizuEntry) int {
|
||||
sizeBytes := len(mizuEntry.Entry)
|
||||
sizeBytes += len(mizuEntry.EntryId)
|
||||
sizeBytes += len(mizuEntry.Service)
|
||||
|
||||
@@ -8,9 +8,10 @@ import (
|
||||
"mizuserver/pkg/up9"
|
||||
"sync"
|
||||
|
||||
tapApi "github.com/up9inc/mizu/tap/api"
|
||||
|
||||
"github.com/romana/rlog"
|
||||
"github.com/up9inc/mizu/shared"
|
||||
"github.com/up9inc/mizu/tap"
|
||||
)
|
||||
|
||||
var browserClientSocketUUIDs = make([]int, 0)
|
||||
@@ -18,7 +19,7 @@ var socketListLock = sync.Mutex{}
|
||||
|
||||
type RoutesEventHandlers struct {
|
||||
EventHandlers
|
||||
SocketHarOutChannel chan<- *tap.OutputChannelItem
|
||||
SocketOutChannel chan<- *tapApi.OutputChannelItem
|
||||
}
|
||||
|
||||
func init() {
|
||||
@@ -28,6 +29,7 @@ func init() {
|
||||
func (h *RoutesEventHandlers) WebSocketConnect(socketId int, isTapper bool) {
|
||||
if isTapper {
|
||||
rlog.Infof("Websocket event - Tapper connected, socket ID: %d", socketId)
|
||||
providers.TapperAdded()
|
||||
} else {
|
||||
rlog.Infof("Websocket event - Browser socket connected, socket ID: %d", socketId)
|
||||
socketListLock.Lock()
|
||||
@@ -39,6 +41,7 @@ func (h *RoutesEventHandlers) WebSocketConnect(socketId int, isTapper bool) {
|
||||
func (h *RoutesEventHandlers) WebSocketDisconnect(socketId int, isTapper bool) {
|
||||
if isTapper {
|
||||
rlog.Infof("Websocket event - Tapper disconnected, socket ID: %d", socketId)
|
||||
providers.TapperRemoved()
|
||||
} else {
|
||||
rlog.Infof("Websocket event - Browser socket disconnected, socket ID: %d", socketId)
|
||||
socketListLock.Lock()
|
||||
@@ -71,7 +74,8 @@ func (h *RoutesEventHandlers) WebSocketMessage(_ int, message []byte) {
|
||||
if err != nil {
|
||||
rlog.Infof("Could not unmarshal message of message type %s %v\n", socketMessageBase.MessageType, err)
|
||||
} else {
|
||||
h.SocketHarOutChannel <- tappedEntryMessage.Data
|
||||
// NOTE: This is where the message comes back from the intermediate WebSocket to code.
|
||||
h.SocketOutChannel <- tappedEntryMessage.Data
|
||||
}
|
||||
case shared.WebSocketMessageTypeUpdateStatus:
|
||||
var statusMessage shared.WebSocketStatusMessage
|
||||
|
||||
@@ -16,8 +16,16 @@ import (
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/google/martian/har"
|
||||
"github.com/romana/rlog"
|
||||
|
||||
tapApi "github.com/up9inc/mizu/tap/api"
|
||||
)
|
||||
|
||||
var extensionsMap map[string]*tapApi.Extension // global
|
||||
|
||||
func InitExtensionsMap(ref map[string]*tapApi.Extension) {
|
||||
extensionsMap = ref
|
||||
}
|
||||
|
||||
func GetEntries(c *gin.Context) {
|
||||
entriesFilter := &models.EntriesFilter{}
|
||||
|
||||
@@ -31,7 +39,7 @@ func GetEntries(c *gin.Context) {
|
||||
|
||||
order := database.OperatorToOrderMapping[entriesFilter.Operator]
|
||||
operatorSymbol := database.OperatorToSymbolMapping[entriesFilter.Operator]
|
||||
var entries []models.MizuEntry
|
||||
var entries []tapApi.MizuEntry
|
||||
database.GetEntriesTable().
|
||||
Order(fmt.Sprintf("timestamp %s", order)).
|
||||
Where(fmt.Sprintf("timestamp %s %v", operatorSymbol, entriesFilter.Timestamp)).
|
||||
@@ -44,9 +52,9 @@ func GetEntries(c *gin.Context) {
|
||||
utils.ReverseSlice(entries)
|
||||
}
|
||||
|
||||
baseEntries := make([]models.BaseEntryDetails, 0)
|
||||
baseEntries := make([]tapApi.BaseEntryDetails, 0)
|
||||
for _, data := range entries {
|
||||
harEntry := models.BaseEntryDetails{}
|
||||
harEntry := tapApi.BaseEntryDetails{}
|
||||
if err := models.GetEntry(&data, &harEntry); err != nil {
|
||||
continue
|
||||
}
|
||||
@@ -80,7 +88,7 @@ func GetHARs(c *gin.Context) {
|
||||
timestampTo = entriesFilter.To
|
||||
}
|
||||
|
||||
var entries []models.MizuEntry
|
||||
var entries []tapApi.MizuEntry
|
||||
database.GetEntriesTable().
|
||||
Where(fmt.Sprintf("timestamp BETWEEN %v AND %v", timestampFrom, timestampTo)).
|
||||
Order(fmt.Sprintf("timestamp %s", order)).
|
||||
@@ -207,7 +215,7 @@ func GetFullEntries(c *gin.Context) {
|
||||
}
|
||||
|
||||
func GetEntry(c *gin.Context) {
|
||||
var entryData models.MizuEntry
|
||||
var entryData tapApi.MizuEntry
|
||||
database.GetEntriesTable().
|
||||
Where(map[string]string{"entryId": c.Param("entryId")}).
|
||||
First(&entryData)
|
||||
@@ -219,20 +227,29 @@ func GetEntry(c *gin.Context) {
|
||||
"msg": "Can't get entry details",
|
||||
})
|
||||
}
|
||||
fullEntryWithPolicy := models.FullEntryWithPolicy{}
|
||||
if err := models.GetEntry(&entryData, &fullEntryWithPolicy); err != nil {
|
||||
c.JSON(http.StatusInternalServerError, map[string]interface{}{
|
||||
"error": true,
|
||||
"msg": "Can't get entry details",
|
||||
})
|
||||
}
|
||||
c.JSON(http.StatusOK, fullEntryWithPolicy)
|
||||
|
||||
// FIXME: Fix the part below
|
||||
// fullEntryWithPolicy := models.FullEntryWithPolicy{}
|
||||
// if err := models.GetEntry(&entryData, &fullEntryWithPolicy); err != nil {
|
||||
// c.JSON(http.StatusInternalServerError, map[string]interface{}{
|
||||
// "error": true,
|
||||
// "msg": "Can't get entry details",
|
||||
// })
|
||||
// }
|
||||
extension := extensionsMap[entryData.ProtocolName]
|
||||
protocol, representation, bodySize, _ := extension.Dissector.Represent(&entryData)
|
||||
c.JSON(http.StatusOK, tapApi.MizuEntryWrapper{
|
||||
Protocol: protocol,
|
||||
Representation: string(representation),
|
||||
BodySize: bodySize,
|
||||
Data: entryData,
|
||||
})
|
||||
}
|
||||
|
||||
func DeleteAllEntries(c *gin.Context) {
|
||||
database.GetEntriesTable().
|
||||
Where("1 = 1").
|
||||
Delete(&models.MizuEntry{})
|
||||
Delete(&tapApi.MizuEntry{})
|
||||
|
||||
c.JSON(http.StatusOK, map[string]string{
|
||||
"msg": "Success",
|
||||
@@ -241,14 +258,7 @@ func DeleteAllEntries(c *gin.Context) {
|
||||
}
|
||||
|
||||
func GetGeneralStats(c *gin.Context) {
|
||||
sqlQuery := "SELECT count(*) as count, min(timestamp) as min, max(timestamp) as max from mizu_entries"
|
||||
var result struct {
|
||||
Count int
|
||||
Min int
|
||||
Max int
|
||||
}
|
||||
database.GetEntriesTable().Raw(sqlQuery).Scan(&result)
|
||||
c.JSON(http.StatusOK, result)
|
||||
c.JSON(http.StatusOK, providers.GetGeneralStats())
|
||||
}
|
||||
|
||||
func GetTappingStatus(c *gin.Context) {
|
||||
|
||||
@@ -30,3 +30,7 @@ func PostTappedPods(c *gin.Context) {
|
||||
api.BroadcastToBrowserClients(jsonBytes)
|
||||
}
|
||||
}
|
||||
|
||||
func GetTappersCount(c *gin.Context) {
|
||||
c.JSON(http.StatusOK, providers.TappersCount)
|
||||
}
|
||||
|
||||
@@ -2,16 +2,18 @@ package database
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"mizuserver/pkg/utils"
|
||||
"time"
|
||||
|
||||
"gorm.io/driver/sqlite"
|
||||
"gorm.io/gorm"
|
||||
"gorm.io/gorm/logger"
|
||||
"mizuserver/pkg/models"
|
||||
"mizuserver/pkg/utils"
|
||||
"time"
|
||||
|
||||
tapApi "github.com/up9inc/mizu/tap/api"
|
||||
)
|
||||
|
||||
const (
|
||||
DBPath = "./entries.db"
|
||||
DBPath = "./entries.db"
|
||||
OrderDesc = "desc"
|
||||
OrderAsc = "asc"
|
||||
LT = "lt"
|
||||
@@ -19,8 +21,8 @@ const (
|
||||
)
|
||||
|
||||
var (
|
||||
DB *gorm.DB
|
||||
IsDBLocked = false
|
||||
DB *gorm.DB
|
||||
IsDBLocked = false
|
||||
OperatorToSymbolMapping = map[string]string{
|
||||
LT: "<",
|
||||
GT: ">",
|
||||
@@ -40,7 +42,7 @@ func GetEntriesTable() *gorm.DB {
|
||||
return DB.Table("mizu_entries")
|
||||
}
|
||||
|
||||
func CreateEntry(entry *models.MizuEntry) {
|
||||
func CreateEntry(entry *tapApi.MizuEntry) {
|
||||
if IsDBLocked {
|
||||
return
|
||||
}
|
||||
@@ -51,14 +53,13 @@ func initDataBase(databasePath string) *gorm.DB {
|
||||
temp, _ := gorm.Open(sqlite.Open(databasePath), &gorm.Config{
|
||||
Logger: &utils.TruncatingLogger{LogLevel: logger.Warn, SlowThreshold: 500 * time.Millisecond},
|
||||
})
|
||||
_ = temp.AutoMigrate(&models.MizuEntry{}) // this will ensure table is created
|
||||
_ = temp.AutoMigrate(&tapApi.MizuEntry{}) // this will ensure table is created
|
||||
return temp
|
||||
}
|
||||
|
||||
|
||||
func GetEntriesFromDb(timestampFrom int64, timestampTo int64) []models.MizuEntry {
|
||||
func GetEntriesFromDb(timestampFrom int64, timestampTo int64) []tapApi.MizuEntry {
|
||||
order := OrderDesc
|
||||
var entries []models.MizuEntry
|
||||
var entries []tapApi.MizuEntry
|
||||
GetEntriesTable().
|
||||
Where(fmt.Sprintf("timestamp BETWEEN %v AND %v", timestampFrom, timestampTo)).
|
||||
Order(fmt.Sprintf("timestamp %s", order)).
|
||||
@@ -70,4 +71,3 @@ func GetEntriesFromDb(timestampFrom int64, timestampTo int64) []models.MizuEntry
|
||||
}
|
||||
return entries
|
||||
}
|
||||
|
||||
|
||||
@@ -1,16 +1,17 @@
|
||||
package database
|
||||
|
||||
import (
|
||||
"log"
|
||||
"os"
|
||||
"strconv"
|
||||
"time"
|
||||
|
||||
"github.com/fsnotify/fsnotify"
|
||||
"github.com/romana/rlog"
|
||||
"github.com/up9inc/mizu/shared"
|
||||
"github.com/up9inc/mizu/shared/debounce"
|
||||
"github.com/up9inc/mizu/shared/units"
|
||||
"log"
|
||||
"mizuserver/pkg/models"
|
||||
"os"
|
||||
"strconv"
|
||||
"time"
|
||||
tapApi "github.com/up9inc/mizu/tap/api"
|
||||
)
|
||||
|
||||
const percentageOfMaxSizeBytesToPrune = 15
|
||||
@@ -99,7 +100,7 @@ func pruneOldEntries(currentFileSize int64) {
|
||||
if bytesToBeRemoved >= amountOfBytesToTrim {
|
||||
break
|
||||
}
|
||||
var entry models.MizuEntry
|
||||
var entry tapApi.MizuEntry
|
||||
err = DB.ScanRows(rows, &entry)
|
||||
if err != nil {
|
||||
rlog.Errorf("Error scanning db row: %v", err)
|
||||
@@ -111,7 +112,7 @@ func pruneOldEntries(currentFileSize int64) {
|
||||
}
|
||||
|
||||
if len(entryIdsToRemove) > 0 {
|
||||
GetEntriesTable().Where(entryIdsToRemove).Delete(models.MizuEntry{})
|
||||
GetEntriesTable().Where(entryIdsToRemove).Delete(tapApi.MizuEntry{})
|
||||
// VACUUM causes sqlite to shrink the db file after rows have been deleted, the db file will not shrink without this
|
||||
DB.Exec("VACUUM")
|
||||
rlog.Errorf("Removed %d rows and cleared %s", len(entryIdsToRemove), units.BytesToHumanReadable(bytesToBeRemoved))
|
||||
|
||||
@@ -3,65 +3,25 @@ package models
|
||||
import (
|
||||
"encoding/json"
|
||||
|
||||
tapApi "github.com/up9inc/mizu/tap/api"
|
||||
|
||||
"mizuserver/pkg/rules"
|
||||
"mizuserver/pkg/utils"
|
||||
"time"
|
||||
|
||||
"github.com/google/martian/har"
|
||||
"github.com/up9inc/mizu/shared"
|
||||
"github.com/up9inc/mizu/tap"
|
||||
)
|
||||
|
||||
type DataUnmarshaler interface {
|
||||
UnmarshalData(*MizuEntry) error
|
||||
}
|
||||
|
||||
func GetEntry(r *MizuEntry, v DataUnmarshaler) error {
|
||||
func GetEntry(r *tapApi.MizuEntry, v tapApi.DataUnmarshaler) error {
|
||||
return v.UnmarshalData(r)
|
||||
}
|
||||
|
||||
type MizuEntry struct {
|
||||
ID uint `gorm:"primarykey"`
|
||||
CreatedAt time.Time
|
||||
UpdatedAt time.Time
|
||||
Entry string `json:"entry,omitempty" gorm:"column:entry"`
|
||||
EntryId string `json:"entryId" gorm:"column:entryId"`
|
||||
Url string `json:"url" gorm:"column:url"`
|
||||
Method string `json:"method" gorm:"column:method"`
|
||||
Status int `json:"status" gorm:"column:status"`
|
||||
RequestSenderIp string `json:"requestSenderIp" gorm:"column:requestSenderIp"`
|
||||
Service string `json:"service" gorm:"column:service"`
|
||||
Timestamp int64 `json:"timestamp" gorm:"column:timestamp"`
|
||||
Path string `json:"path" gorm:"column:path"`
|
||||
ResolvedSource string `json:"resolvedSource,omitempty" gorm:"column:resolvedSource"`
|
||||
ResolvedDestination string `json:"resolvedDestination,omitempty" gorm:"column:resolvedDestination"`
|
||||
IsOutgoing bool `json:"isOutgoing,omitempty" gorm:"column:isOutgoing"`
|
||||
EstimatedSizeBytes int `json:"-" gorm:"column:estimatedSizeBytes"`
|
||||
}
|
||||
|
||||
type BaseEntryDetails struct {
|
||||
Id string `json:"id,omitempty"`
|
||||
Url string `json:"url,omitempty"`
|
||||
RequestSenderIp string `json:"requestSenderIp,omitempty"`
|
||||
Service string `json:"service,omitempty"`
|
||||
Path string `json:"path,omitempty"`
|
||||
StatusCode int `json:"statusCode,omitempty"`
|
||||
Method string `json:"method,omitempty"`
|
||||
Timestamp int64 `json:"timestamp,omitempty"`
|
||||
IsOutgoing bool `json:"isOutgoing,omitempty"`
|
||||
Latency int64 `json:"latency,omitempty"`
|
||||
Rules ApplicableRules `json:"rules,omitempty"`
|
||||
}
|
||||
|
||||
type ApplicableRules struct {
|
||||
Latency int64 `json:"latency,omitempty"`
|
||||
Status bool `json:"status,omitempty"`
|
||||
}
|
||||
|
||||
func NewApplicableRules(status bool, latency int64) ApplicableRules {
|
||||
ar := ApplicableRules{}
|
||||
func NewApplicableRules(status bool, latency int64, number int) tapApi.ApplicableRules {
|
||||
ar := tapApi.ApplicableRules{}
|
||||
ar.Status = status
|
||||
ar.Latency = latency
|
||||
ar.NumberOfRules = number
|
||||
return ar
|
||||
}
|
||||
|
||||
@@ -73,26 +33,7 @@ type FullEntryDetailsExtra struct {
|
||||
har.Entry
|
||||
}
|
||||
|
||||
func (bed *BaseEntryDetails) UnmarshalData(entry *MizuEntry) error {
|
||||
entryUrl := entry.Url
|
||||
service := entry.Service
|
||||
if entry.ResolvedDestination != "" {
|
||||
entryUrl = utils.SetHostname(entryUrl, entry.ResolvedDestination)
|
||||
service = utils.SetHostname(service, entry.ResolvedDestination)
|
||||
}
|
||||
bed.Id = entry.EntryId
|
||||
bed.Url = entryUrl
|
||||
bed.Service = service
|
||||
bed.Path = entry.Path
|
||||
bed.StatusCode = entry.Status
|
||||
bed.Method = entry.Method
|
||||
bed.Timestamp = entry.Timestamp
|
||||
bed.RequestSenderIp = entry.RequestSenderIp
|
||||
bed.IsOutgoing = entry.IsOutgoing
|
||||
return nil
|
||||
}
|
||||
|
||||
func (fed *FullEntryDetails) UnmarshalData(entry *MizuEntry) error {
|
||||
func (fed *FullEntryDetails) UnmarshalData(entry *tapApi.MizuEntry) error {
|
||||
if err := json.Unmarshal([]byte(entry.Entry), &fed.Entry); err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -103,7 +44,7 @@ func (fed *FullEntryDetails) UnmarshalData(entry *MizuEntry) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (fedex *FullEntryDetailsExtra) UnmarshalData(entry *MizuEntry) error {
|
||||
func (fedex *FullEntryDetailsExtra) UnmarshalData(entry *tapApi.MizuEntry) error {
|
||||
if err := json.Unmarshal([]byte(entry.Entry), &fedex.Entry); err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -136,12 +77,12 @@ type HarFetchRequestQuery struct {
|
||||
|
||||
type WebSocketEntryMessage struct {
|
||||
*shared.WebSocketMessageMetadata
|
||||
Data *BaseEntryDetails `json:"data,omitempty"`
|
||||
Data *tapApi.BaseEntryDetails `json:"data,omitempty"`
|
||||
}
|
||||
|
||||
type WebSocketTappedEntryMessage struct {
|
||||
*shared.WebSocketMessageMetadata
|
||||
Data *tap.OutputChannelItem
|
||||
Data *tapApi.OutputChannelItem
|
||||
}
|
||||
|
||||
type WebsocketOutboundLinkMessage struct {
|
||||
@@ -149,7 +90,7 @@ type WebsocketOutboundLinkMessage struct {
|
||||
Data *tap.OutboundLink
|
||||
}
|
||||
|
||||
func CreateBaseEntryWebSocketMessage(base *BaseEntryDetails) ([]byte, error) {
|
||||
func CreateBaseEntryWebSocketMessage(base *tapApi.BaseEntryDetails) ([]byte, error) {
|
||||
message := &WebSocketEntryMessage{
|
||||
WebSocketMessageMetadata: &shared.WebSocketMessageMetadata{
|
||||
MessageType: shared.WebSocketMessageTypeEntry,
|
||||
@@ -159,7 +100,7 @@ func CreateBaseEntryWebSocketMessage(base *BaseEntryDetails) ([]byte, error) {
|
||||
return json.Marshal(message)
|
||||
}
|
||||
|
||||
func CreateWebsocketTappedEntryMessage(base *tap.OutputChannelItem) ([]byte, error) {
|
||||
func CreateWebsocketTappedEntryMessage(base *tapApi.OutputChannelItem) ([]byte, error) {
|
||||
message := &WebSocketTappedEntryMessage{
|
||||
WebSocketMessageMetadata: &shared.WebSocketMessageMetadata{
|
||||
MessageType: shared.WebSocketMessageTypeTappedEntry,
|
||||
@@ -205,7 +146,7 @@ type FullEntryWithPolicy struct {
|
||||
Service string `json:"service"`
|
||||
}
|
||||
|
||||
func (fewp *FullEntryWithPolicy) UnmarshalData(entry *MizuEntry) error {
|
||||
func (fewp *FullEntryWithPolicy) UnmarshalData(entry *tapApi.MizuEntry) error {
|
||||
if err := json.Unmarshal([]byte(entry.Entry), &fewp.Entry); err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -216,9 +157,9 @@ func (fewp *FullEntryWithPolicy) UnmarshalData(entry *MizuEntry) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func RunValidationRulesState(harEntry har.Entry, service string) ApplicableRules {
|
||||
func RunValidationRulesState(harEntry har.Entry, service string) tapApi.ApplicableRules {
|
||||
numberOfRules, resultPolicyToSend := rules.MatchRequestPolicy(harEntry, service)
|
||||
statusPolicyToSend, latency := rules.PassedValidationRules(resultPolicyToSend, numberOfRules)
|
||||
ar := NewApplicableRules(statusPolicyToSend, latency)
|
||||
statusPolicyToSend, latency, numberOfRules := rules.PassedValidationRules(resultPolicyToSend, numberOfRules)
|
||||
ar := NewApplicableRules(statusPolicyToSend, latency, numberOfRules)
|
||||
return ar
|
||||
}
|
||||
|
||||
36
agent/pkg/providers/stats_provider.go
Normal file
36
agent/pkg/providers/stats_provider.go
Normal file
@@ -0,0 +1,36 @@
|
||||
package providers
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
"time"
|
||||
)
|
||||
|
||||
type GeneralStats struct {
|
||||
EntriesCount int
|
||||
FirstEntryTimestamp int
|
||||
LastEntryTimestamp int
|
||||
}
|
||||
|
||||
var generalStats = GeneralStats{}
|
||||
|
||||
func ResetGeneralStats() {
|
||||
generalStats = GeneralStats{}
|
||||
}
|
||||
|
||||
func GetGeneralStats() GeneralStats {
|
||||
return generalStats
|
||||
}
|
||||
|
||||
func EntryAdded() {
|
||||
generalStats.EntriesCount++
|
||||
|
||||
currentTimestamp := int(time.Now().Unix())
|
||||
|
||||
if reflect.Value.IsZero(reflect.ValueOf(generalStats.FirstEntryTimestamp)) {
|
||||
generalStats.FirstEntryTimestamp = currentTimestamp
|
||||
}
|
||||
|
||||
generalStats.LastEntryTimestamp = currentTimestamp
|
||||
}
|
||||
|
||||
|
||||
35
agent/pkg/providers/stats_provider_test.go
Normal file
35
agent/pkg/providers/stats_provider_test.go
Normal file
@@ -0,0 +1,35 @@
|
||||
package providers_test
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"mizuserver/pkg/providers"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestNoEntryAddedCount(t *testing.T) {
|
||||
entriesStats := providers.GetGeneralStats()
|
||||
|
||||
if entriesStats.EntriesCount != 0 {
|
||||
t.Errorf("unexpected result - expected: %v, actual: %v", 0, entriesStats.EntriesCount)
|
||||
}
|
||||
}
|
||||
|
||||
func TestEntryAddedCount(t *testing.T) {
|
||||
tests := []int{1, 5, 10, 100, 500, 1000}
|
||||
|
||||
for _, entriesCount := range tests {
|
||||
t.Run(fmt.Sprintf("%d", entriesCount), func(t *testing.T) {
|
||||
for i := 0; i < entriesCount; i++ {
|
||||
providers.EntryAdded()
|
||||
}
|
||||
|
||||
entriesStats := providers.GetGeneralStats()
|
||||
|
||||
if entriesStats.EntriesCount != entriesCount {
|
||||
t.Errorf("unexpected result - expected: %v, actual: %v", entriesCount, entriesStats.EntriesCount)
|
||||
}
|
||||
|
||||
t.Cleanup(providers.ResetGeneralStats)
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -4,14 +4,18 @@ import (
|
||||
"github.com/patrickmn/go-cache"
|
||||
"github.com/up9inc/mizu/shared"
|
||||
"github.com/up9inc/mizu/tap"
|
||||
"sync"
|
||||
"time"
|
||||
)
|
||||
|
||||
const tlsLinkRetainmentTime = time.Minute * 15
|
||||
|
||||
var (
|
||||
TapStatus shared.TapStatus
|
||||
TappersCount int
|
||||
TapStatus shared.TapStatus
|
||||
RecentTLSLinks = cache.New(tlsLinkRetainmentTime, tlsLinkRetainmentTime)
|
||||
|
||||
tappersCountLock = sync.Mutex{}
|
||||
)
|
||||
|
||||
func GetAllRecentTLSAddresses() []string {
|
||||
@@ -26,3 +30,15 @@ func GetAllRecentTLSAddresses() []string {
|
||||
|
||||
return recentTLSLinks
|
||||
}
|
||||
|
||||
func TapperAdded() {
|
||||
tappersCountLock.Lock()
|
||||
TappersCount++
|
||||
tappersCountLock.Unlock()
|
||||
}
|
||||
|
||||
func TapperRemoved() {
|
||||
tappersCountLock.Lock()
|
||||
TappersCount--
|
||||
tappersCountLock.Unlock()
|
||||
}
|
||||
|
||||
@@ -4,10 +4,11 @@ import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
|
||||
"github.com/romana/rlog"
|
||||
k8serrors "k8s.io/apimachinery/pkg/api/errors"
|
||||
|
||||
"github.com/orcaman/concurrent-map"
|
||||
cmap "github.com/orcaman/concurrent-map"
|
||||
corev1 "k8s.io/api/core/v1"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/watch"
|
||||
|
||||
@@ -9,4 +9,6 @@ func StatusRoutes(ginApp *gin.Engine) {
|
||||
routeGroup := ginApp.Group("/status")
|
||||
|
||||
routeGroup.POST("/tappedPods", controllers.PostTappedPods)
|
||||
|
||||
routeGroup.GET("/tappersCount", controllers.GetTappersCount)
|
||||
}
|
||||
|
||||
@@ -92,19 +92,19 @@ func MatchRequestPolicy(harEntry har.Entry, service string) (int, []RulesMatched
|
||||
return len(enforcePolicy.Rules), resultPolicyToSend
|
||||
}
|
||||
|
||||
func PassedValidationRules(rulesMatched []RulesMatched, numberOfRules int) (bool, int64) {
|
||||
func PassedValidationRules(rulesMatched []RulesMatched, numberOfRules int) (bool, int64, int) {
|
||||
if len(rulesMatched) == 0 {
|
||||
return false, 0
|
||||
return false, 0, 0
|
||||
}
|
||||
for _, rule := range rulesMatched {
|
||||
if rule.Matched == false {
|
||||
return false, -1
|
||||
return false, -1, len(rulesMatched)
|
||||
}
|
||||
}
|
||||
for _, rule := range rulesMatched {
|
||||
if strings.ToLower(rule.Rule.Type) == "latency" {
|
||||
return true, rule.Rule.Latency
|
||||
return true, rule.Rule.Latency, len(rulesMatched)
|
||||
}
|
||||
}
|
||||
return true, -1
|
||||
return true, -1, len(rulesMatched)
|
||||
}
|
||||
|
||||
@@ -1,198 +0,0 @@
|
||||
package sensitiveDataFiltering
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"encoding/xml"
|
||||
"errors"
|
||||
"fmt"
|
||||
"github.com/up9inc/mizu/tap"
|
||||
"net/url"
|
||||
"strings"
|
||||
|
||||
"github.com/beevik/etree"
|
||||
"github.com/google/martian/har"
|
||||
"github.com/up9inc/mizu/shared"
|
||||
)
|
||||
|
||||
func FilterSensitiveInfoFromHarRequest(harOutputItem *tap.OutputChannelItem, options *shared.TrafficFilteringOptions) {
|
||||
harOutputItem.HarEntry.Request.Headers = filterHarHeaders(harOutputItem.HarEntry.Request.Headers)
|
||||
harOutputItem.HarEntry.Response.Headers = filterHarHeaders(harOutputItem.HarEntry.Response.Headers)
|
||||
|
||||
harOutputItem.HarEntry.Request.Cookies = make([]har.Cookie, 0, 0)
|
||||
harOutputItem.HarEntry.Response.Cookies = make([]har.Cookie, 0, 0)
|
||||
|
||||
harOutputItem.HarEntry.Request.URL = filterUrl(harOutputItem.HarEntry.Request.URL)
|
||||
for i, queryString := range harOutputItem.HarEntry.Request.QueryString {
|
||||
if isFieldNameSensitive(queryString.Name) {
|
||||
harOutputItem.HarEntry.Request.QueryString[i].Value = maskedFieldPlaceholderValue
|
||||
}
|
||||
}
|
||||
|
||||
if harOutputItem.HarEntry.Request.PostData != nil {
|
||||
requestContentType := getContentTypeHeaderValue(harOutputItem.HarEntry.Request.Headers)
|
||||
filteredRequestBody, err := filterHttpBody([]byte(harOutputItem.HarEntry.Request.PostData.Text), requestContentType, options)
|
||||
if err == nil {
|
||||
harOutputItem.HarEntry.Request.PostData.Text = string(filteredRequestBody)
|
||||
}
|
||||
}
|
||||
if harOutputItem.HarEntry.Response.Content != nil {
|
||||
responseContentType := getContentTypeHeaderValue(harOutputItem.HarEntry.Response.Headers)
|
||||
filteredResponseBody, err := filterHttpBody(harOutputItem.HarEntry.Response.Content.Text, responseContentType, options)
|
||||
if err == nil {
|
||||
harOutputItem.HarEntry.Response.Content.Text = filteredResponseBody
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func filterHarHeaders(headers []har.Header) []har.Header {
|
||||
newHeaders := make([]har.Header, 0)
|
||||
for i, header := range headers {
|
||||
if strings.ToLower(header.Name) == "cookie" {
|
||||
continue
|
||||
} else if isFieldNameSensitive(header.Name) {
|
||||
newHeaders = append(newHeaders, har.Header{Name: header.Name, Value: maskedFieldPlaceholderValue})
|
||||
headers[i].Value = maskedFieldPlaceholderValue
|
||||
} else {
|
||||
newHeaders = append(newHeaders, header)
|
||||
}
|
||||
}
|
||||
return newHeaders
|
||||
}
|
||||
|
||||
func getContentTypeHeaderValue(headers []har.Header) string {
|
||||
for _, header := range headers {
|
||||
if strings.ToLower(header.Name) == "content-type" {
|
||||
return header.Value
|
||||
}
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func isFieldNameSensitive(fieldName string) bool {
|
||||
name := strings.ToLower(fieldName)
|
||||
name = strings.ReplaceAll(name, "_", "")
|
||||
name = strings.ReplaceAll(name, "-", "")
|
||||
name = strings.ReplaceAll(name, " ", "")
|
||||
|
||||
for _, sensitiveField := range personallyIdentifiableDataFields {
|
||||
if strings.Contains(name, sensitiveField) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
func filterHttpBody(bytes []byte, contentType string, options *shared.TrafficFilteringOptions) ([]byte, error) {
|
||||
mimeType := strings.Split(contentType, ";")[0]
|
||||
switch strings.ToLower(mimeType) {
|
||||
case "application/json":
|
||||
return filterJsonBody(bytes)
|
||||
case "text/html":
|
||||
fallthrough
|
||||
case "application/xhtml+xml":
|
||||
fallthrough
|
||||
case "text/xml":
|
||||
fallthrough
|
||||
case "application/xml":
|
||||
return filterXmlEtree(bytes)
|
||||
case "text/plain":
|
||||
if options != nil && options.PlainTextMaskingRegexes != nil {
|
||||
return filterPlainText(bytes, options), nil
|
||||
}
|
||||
}
|
||||
return bytes, nil
|
||||
}
|
||||
|
||||
func filterPlainText(bytes []byte, options *shared.TrafficFilteringOptions) []byte {
|
||||
for _, regex := range options.PlainTextMaskingRegexes {
|
||||
bytes = regex.ReplaceAll(bytes, []byte(maskedFieldPlaceholderValue))
|
||||
}
|
||||
return bytes
|
||||
}
|
||||
|
||||
func filterXmlEtree(bytes []byte) ([]byte, error) {
|
||||
if !IsValidXML(bytes) {
|
||||
return nil, errors.New("Invalid XML")
|
||||
}
|
||||
xmlDoc := etree.NewDocument()
|
||||
err := xmlDoc.ReadFromBytes(bytes)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
} else {
|
||||
filterXmlElement(xmlDoc.Root())
|
||||
}
|
||||
return xmlDoc.WriteToBytes()
|
||||
}
|
||||
|
||||
func IsValidXML(data []byte) bool {
|
||||
return xml.Unmarshal(data, new(interface{})) == nil
|
||||
}
|
||||
|
||||
func filterXmlElement(element *etree.Element) {
|
||||
for i, attribute := range element.Attr {
|
||||
if isFieldNameSensitive(attribute.Key) {
|
||||
element.Attr[i].Value = maskedFieldPlaceholderValue
|
||||
}
|
||||
}
|
||||
if element.ChildElements() == nil || len(element.ChildElements()) == 0 {
|
||||
if isFieldNameSensitive(element.Tag) {
|
||||
element.SetText(maskedFieldPlaceholderValue)
|
||||
}
|
||||
} else {
|
||||
for _, element := range element.ChildElements() {
|
||||
filterXmlElement(element)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func filterJsonBody(bytes []byte) ([]byte, error) {
|
||||
var bodyJsonMap map[string] interface{}
|
||||
err := json.Unmarshal(bytes ,&bodyJsonMap)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
filterJsonMap(bodyJsonMap)
|
||||
return json.Marshal(bodyJsonMap)
|
||||
}
|
||||
|
||||
func filterJsonMap(jsonMap map[string] interface{}) {
|
||||
for key, value := range jsonMap {
|
||||
if value == nil {
|
||||
return
|
||||
}
|
||||
nestedMap, isNested := value.(map[string] interface{})
|
||||
if isNested {
|
||||
filterJsonMap(nestedMap)
|
||||
} else {
|
||||
if isFieldNameSensitive(key) {
|
||||
jsonMap[key] = maskedFieldPlaceholderValue
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// receives string representing url, returns string url without sensitive query param values (http://service/api?userId=bob&password=123&type=login -> http://service/api?userId=[REDACTED]&password=[REDACTED]&type=login)
|
||||
func filterUrl(originalUrl string) string {
|
||||
parsedUrl, err := url.Parse(originalUrl)
|
||||
if err != nil {
|
||||
return fmt.Sprintf("http://%s", maskedFieldPlaceholderValue)
|
||||
} else {
|
||||
if len(parsedUrl.RawQuery) > 0 {
|
||||
newQueryArgs := make([]string, 0)
|
||||
for urlQueryParamName, urlQueryParamValues := range parsedUrl.Query() {
|
||||
newValues := urlQueryParamValues
|
||||
if isFieldNameSensitive(urlQueryParamName) {
|
||||
newValues = []string {maskedFieldPlaceholderValue}
|
||||
}
|
||||
for _, paramValue := range newValues {
|
||||
newQueryArgs = append(newQueryArgs, fmt.Sprintf("%s=%s", urlQueryParamName, paramValue))
|
||||
}
|
||||
}
|
||||
|
||||
parsedUrl.RawQuery = strings.Join(newQueryArgs, "&")
|
||||
}
|
||||
|
||||
return parsedUrl.String()
|
||||
}
|
||||
}
|
||||
BIN
assets/validation-example1.png
Normal file
BIN
assets/validation-example1.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 55 KiB |
BIN
assets/validation-example2.png
Normal file
BIN
assets/validation-example2.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 43 KiB |
15
build-agent-ci.sh
Executable file
15
build-agent-ci.sh
Executable file
@@ -0,0 +1,15 @@
|
||||
#!/bin/bash
|
||||
set -e
|
||||
|
||||
GCP_PROJECT=up9-docker-hub
|
||||
REPOSITORY=gcr.io/$GCP_PROJECT
|
||||
SERVER_NAME=mizu
|
||||
GIT_BRANCH=ci
|
||||
|
||||
DOCKER_REPO=$REPOSITORY/$SERVER_NAME/$GIT_BRANCH
|
||||
SEM_VER=${SEM_VER=0.0.0}
|
||||
|
||||
DOCKER_TAGGED_BUILD="$DOCKER_REPO:$SEM_VER"
|
||||
|
||||
echo "building $DOCKER_TAGGED_BUILD"
|
||||
docker build -t ${DOCKER_TAGGED_BUILD} --build-arg SEM_VER=${SEM_VER} --build-arg BUILD_TIMESTAMP=${BUILD_TIMESTAMP} --build-arg GIT_BRANCH=${GIT_BRANCH} --build-arg COMMIT_HASH=${COMMIT_HASH} .
|
||||
@@ -1,12 +1,14 @@
|
||||
#!/bin/bash
|
||||
set -e
|
||||
|
||||
SERVER_NAME=mizu
|
||||
GCP_PROJECT=up9-docker-hub
|
||||
REPOSITORY=gcr.io/$GCP_PROJECT
|
||||
SERVER_NAME=mizu
|
||||
GIT_BRANCH=$(git branch | grep \* | cut -d ' ' -f2 | tr '[:upper:]' '[:lower:]')
|
||||
SEM_VER=${SEM_VER=0.0.0}
|
||||
|
||||
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' ]
|
||||
@@ -21,6 +23,6 @@ docker build $DOCKER_TAGS_ARGS --build-arg SEM_VER=${SEM_VER} --build-arg BUILD_
|
||||
|
||||
for DOCKER_TAG in "${DOCKER_TAGGED_BUILDS[@]}"
|
||||
do
|
||||
echo pushing "$DOCKER_TAG"
|
||||
docker push "$DOCKER_TAG"
|
||||
echo pushing "$DOCKER_TAG"
|
||||
docker push "$DOCKER_TAG"
|
||||
done
|
||||
|
||||
12
build_extensions.sh
Executable file
12
build_extensions.sh
Executable file
@@ -0,0 +1,12 @@
|
||||
#!/bin/bash
|
||||
|
||||
for f in tap/extensions/*; do
|
||||
if [ -d "$f" ]; then
|
||||
extension=$(basename $f) && \
|
||||
cd tap/extensions/${extension} && \
|
||||
go build -buildmode=plugin -o ../${extension}.so . && \
|
||||
cd ../../.. && \
|
||||
mkdir -p agent/build/extensions && \
|
||||
cp tap/extensions/${extension}.so agent/build/extensions
|
||||
fi
|
||||
done
|
||||
@@ -24,7 +24,7 @@ build: ## Build mizu CLI binary (select platform via GOOS / GOARCH env variables
|
||||
|
||||
build-all: ## Build for all supported platforms.
|
||||
@echo "Compiling for every OS and Platform"
|
||||
@mkdir -p bin && echo "SHA256 checksums available for compiled binaries \n\nRun \`shasum -a 256 -c mizu_OS_ARCH.sha256\` to verify\n\n" > bin/README.md
|
||||
@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=darwin GOARCH=arm64
|
||||
@@ -39,3 +39,6 @@ build-all: ## Build for all supported platforms.
|
||||
clean: ## Clean all build artifacts.
|
||||
go clean
|
||||
rm -rf ./bin/*
|
||||
|
||||
test: ## Run cli tests.
|
||||
@go test ./... -coverpkg=./... -race -coverprofile=coverage.out -covermode=atomic
|
||||
|
||||
20
cli/README.md.TEMPLATE
Normal file
20
cli/README.md.TEMPLATE
Normal file
@@ -0,0 +1,20 @@
|
||||
# Mizu release _SEM_VER_
|
||||
|
||||
Download Mizu for your platform
|
||||
|
||||
**Mac** (on Intel chip)
|
||||
```
|
||||
curl -Lo mizu https://github.com/up9inc/mizu/releases/download/_SEM_VER_/mizu_darwin_amd64 && chmod 755 mizu
|
||||
```
|
||||
|
||||
**Linux**
|
||||
```
|
||||
curl -Lo mizu https://github.com/up9inc/mizu/releases/download/_SEM_VER_/mizu_linux_amd64 && chmod 755 mizu
|
||||
```
|
||||
|
||||
|
||||
### Checksums
|
||||
SHA256 checksums available for compiled binaries.
|
||||
Run `shasum -a 256 -c mizu_OS_ARCH.sha256` to verify.
|
||||
|
||||
|
||||
178
cli/apiserver/provider.go
Normal file
178
cli/apiserver/provider.go
Normal file
@@ -0,0 +1,178 @@
|
||||
package apiserver
|
||||
|
||||
import (
|
||||
"archive/zip"
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"github.com/up9inc/mizu/cli/config"
|
||||
"github.com/up9inc/mizu/cli/logger"
|
||||
"github.com/up9inc/mizu/cli/uiUtils"
|
||||
"github.com/up9inc/mizu/shared"
|
||||
"io/ioutil"
|
||||
core "k8s.io/api/core/v1"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"time"
|
||||
)
|
||||
|
||||
type apiServerProvider struct {
|
||||
url string
|
||||
isReady bool
|
||||
retries int
|
||||
}
|
||||
|
||||
var Provider = apiServerProvider{retries: config.GetIntEnvConfig(config.ApiServerRetries, 20)}
|
||||
|
||||
func (provider *apiServerProvider) InitAndTestConnection(url string) error {
|
||||
healthUrl := fmt.Sprintf("%s/", url)
|
||||
retriesLeft := provider.retries
|
||||
for retriesLeft > 0 {
|
||||
if response, err := http.Get(healthUrl); err != nil {
|
||||
logger.Log.Debugf("[ERROR] failed connecting to api server %v", err)
|
||||
} else if response.StatusCode != 200 {
|
||||
responseBody := ""
|
||||
data, readErr := ioutil.ReadAll(response.Body)
|
||||
if readErr == nil {
|
||||
responseBody = string(data)
|
||||
}
|
||||
|
||||
logger.Log.Debugf("can't connect to api server yet, response status code: %v, body: %v", response.StatusCode, responseBody)
|
||||
|
||||
response.Body.Close()
|
||||
} else {
|
||||
logger.Log.Debugf("connection test to api server passed successfully")
|
||||
break
|
||||
}
|
||||
retriesLeft -= 1
|
||||
time.Sleep(time.Second)
|
||||
}
|
||||
|
||||
if retriesLeft == 0 {
|
||||
provider.isReady = false
|
||||
return fmt.Errorf("couldn't reach the api server after %v retries", provider.retries)
|
||||
}
|
||||
provider.url = url
|
||||
provider.isReady = true
|
||||
return nil
|
||||
}
|
||||
|
||||
func (provider *apiServerProvider) ReportTappedPods(pods []core.Pod) error {
|
||||
if !provider.isReady {
|
||||
return fmt.Errorf("trying to reach api server when not initialized yet")
|
||||
}
|
||||
tappedPodsUrl := fmt.Sprintf("%s/status/tappedPods", provider.url)
|
||||
|
||||
podInfos := make([]shared.PodInfo, 0)
|
||||
for _, pod := range pods {
|
||||
podInfos = append(podInfos, shared.PodInfo{Name: pod.Name, Namespace: pod.Namespace})
|
||||
}
|
||||
tapStatus := shared.TapStatus{Pods: podInfos}
|
||||
|
||||
if jsonValue, err := json.Marshal(tapStatus); err != nil {
|
||||
return fmt.Errorf("failed Marshal the tapped pods %w", err)
|
||||
} else {
|
||||
if response, err := http.Post(tappedPodsUrl, "application/json", bytes.NewBuffer(jsonValue)); err != nil {
|
||||
return fmt.Errorf("failed sending to API server the tapped pods %w", err)
|
||||
} else if response.StatusCode != 200 {
|
||||
return fmt.Errorf("failed sending to API server the tapped pods, response status code %v", response.StatusCode)
|
||||
} else {
|
||||
logger.Log.Debugf("Reported to server API about %d taped pods successfully", len(podInfos))
|
||||
return nil
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (provider *apiServerProvider) RequestAnalysis(analysisDestination string, sleepIntervalSec int) error {
|
||||
if !provider.isReady {
|
||||
return fmt.Errorf("trying to reach api server when not initialized yet")
|
||||
}
|
||||
urlPath := fmt.Sprintf("%s/api/uploadEntries?dest=%s&interval=%v", provider.url, url.QueryEscape(analysisDestination), sleepIntervalSec)
|
||||
u, parseErr := url.ParseRequestURI(urlPath)
|
||||
if parseErr != nil {
|
||||
logger.Log.Fatal("Failed parsing the URL (consider changing the analysis dest URL), err: %v", parseErr)
|
||||
}
|
||||
|
||||
logger.Log.Debugf("Analysis url %v", u.String())
|
||||
if response, requestErr := http.Get(u.String()); requestErr != nil {
|
||||
return fmt.Errorf("failed to notify agent for analysis, err: %w", requestErr)
|
||||
} else if response.StatusCode != 200 {
|
||||
return fmt.Errorf("failed to notify agent for analysis, status code: %v", response.StatusCode)
|
||||
} else {
|
||||
logger.Log.Infof(uiUtils.Purple, "Traffic is uploading to UP9 for further analysis")
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
func (provider *apiServerProvider) GetGeneralStats() (map[string]interface{}, error) {
|
||||
if !provider.isReady {
|
||||
return nil, fmt.Errorf("trying to reach api server when not initialized yet")
|
||||
}
|
||||
generalStatsUrl := fmt.Sprintf("%s/api/generalStats", provider.url)
|
||||
|
||||
response, requestErr := http.Get(generalStatsUrl)
|
||||
if requestErr != nil {
|
||||
return nil, fmt.Errorf("failed to get general stats for telemetry, err: %w", requestErr)
|
||||
} else if response.StatusCode != 200 {
|
||||
return nil, fmt.Errorf("failed to get general stats for telemetry, status code: %v", response.StatusCode)
|
||||
}
|
||||
|
||||
defer func() { _ = response.Body.Close() }()
|
||||
|
||||
data, readErr := ioutil.ReadAll(response.Body)
|
||||
if readErr != nil {
|
||||
return nil, fmt.Errorf("failed to read general stats for telemetry, err: %v", readErr)
|
||||
}
|
||||
|
||||
var generalStats map[string]interface{}
|
||||
if parseErr := json.Unmarshal(data, &generalStats); parseErr != nil {
|
||||
return nil, fmt.Errorf("failed to parse general stats for telemetry, err: %v", parseErr)
|
||||
}
|
||||
return generalStats, nil
|
||||
}
|
||||
|
||||
func (provider *apiServerProvider) GetHars(fromTimestamp int, toTimestamp int) (*zip.Reader, error) {
|
||||
if !provider.isReady {
|
||||
return nil, fmt.Errorf("trying to reach api server when not initialized yet")
|
||||
}
|
||||
resp, err := http.Get(fmt.Sprintf("%s/api/har?from=%v&to=%v", provider.url, fromTimestamp, toTimestamp))
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed getting har from api server %w", err)
|
||||
}
|
||||
|
||||
defer func() { _ = resp.Body.Close() }()
|
||||
|
||||
body, err := ioutil.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed reading hars %w", err)
|
||||
}
|
||||
|
||||
zipReader, err := zip.NewReader(bytes.NewReader(body), int64(len(body)))
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed craeting zip reader %w", err)
|
||||
}
|
||||
return zipReader, nil
|
||||
}
|
||||
|
||||
func (provider *apiServerProvider) GetVersion() (string, error) {
|
||||
if !provider.isReady {
|
||||
return "", fmt.Errorf("trying to reach api server when not initialized yet")
|
||||
}
|
||||
versionUrl, _ := url.Parse(fmt.Sprintf("%s/metadata/version", provider.url))
|
||||
req := &http.Request{
|
||||
Method: http.MethodGet,
|
||||
URL: versionUrl,
|
||||
}
|
||||
statusResp, err := http.DefaultClient.Do(req)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
defer statusResp.Body.Close()
|
||||
|
||||
versionResponse := &shared.VersionResponse{}
|
||||
if err := json.NewDecoder(statusResp.Body).Decode(&versionResponse); err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
return versionResponse.SemVer, nil
|
||||
}
|
||||
70
cli/cmd/common.go
Normal file
70
cli/cmd/common.go
Normal file
@@ -0,0 +1,70 @@
|
||||
package cmd
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"log"
|
||||
"os"
|
||||
"os/exec"
|
||||
"os/signal"
|
||||
"runtime"
|
||||
"syscall"
|
||||
|
||||
"github.com/up9inc/mizu/cli/config"
|
||||
"github.com/up9inc/mizu/cli/config/configStructs"
|
||||
"github.com/up9inc/mizu/cli/errormessage"
|
||||
"github.com/up9inc/mizu/cli/kubernetes"
|
||||
"github.com/up9inc/mizu/cli/logger"
|
||||
"github.com/up9inc/mizu/cli/mizu"
|
||||
"github.com/up9inc/mizu/cli/uiUtils"
|
||||
)
|
||||
|
||||
func GetApiServerUrl() string {
|
||||
return fmt.Sprintf("http://%s", kubernetes.GetMizuApiServerProxiedHostAndPath(config.Config.Tap.GuiPort))
|
||||
}
|
||||
|
||||
func startProxyReportErrorIfAny(kubernetesProvider *kubernetes.Provider, cancel context.CancelFunc) {
|
||||
err := kubernetes.StartProxy(kubernetesProvider, config.Config.Tap.GuiPort, config.Config.MizuResourcesNamespace, mizu.ApiServerPodName)
|
||||
if err != nil {
|
||||
logger.Log.Errorf(uiUtils.Error, fmt.Sprintf("Error occured while running k8s proxy %v\n"+
|
||||
"Try setting different port by using --%s", errormessage.FormatError(err), configStructs.GuiPortTapName))
|
||||
cancel()
|
||||
}
|
||||
|
||||
logger.Log.Debugf("proxy ended")
|
||||
}
|
||||
|
||||
func waitForFinish(ctx context.Context, cancel context.CancelFunc) {
|
||||
logger.Log.Debugf("waiting for finish...")
|
||||
sigChan := make(chan os.Signal, 1)
|
||||
signal.Notify(sigChan, syscall.SIGINT, syscall.SIGTERM, syscall.SIGQUIT)
|
||||
|
||||
// block until ctx cancel is called or termination signal is received
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
logger.Log.Debugf("ctx done")
|
||||
break
|
||||
case <-sigChan:
|
||||
logger.Log.Debugf("Got termination signal, canceling execution...")
|
||||
cancel()
|
||||
}
|
||||
}
|
||||
|
||||
func openBrowser(url string) {
|
||||
var err error
|
||||
|
||||
switch runtime.GOOS {
|
||||
case "linux":
|
||||
err = exec.Command("xdg-open", url).Start()
|
||||
case "windows":
|
||||
err = exec.Command("rundll32", "url.dll,FileProtocolHandler", url).Start()
|
||||
case "darwin":
|
||||
err = exec.Command("open", url).Start()
|
||||
default:
|
||||
err = fmt.Errorf("unsupported platform")
|
||||
}
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
}
|
||||
@@ -2,32 +2,36 @@ package cmd
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/creasty/defaults"
|
||||
"github.com/spf13/cobra"
|
||||
"github.com/up9inc/mizu/cli/mizu"
|
||||
"github.com/up9inc/mizu/cli/config"
|
||||
"github.com/up9inc/mizu/cli/config/configStructs"
|
||||
"github.com/up9inc/mizu/cli/logger"
|
||||
"github.com/up9inc/mizu/cli/telemetry"
|
||||
"github.com/up9inc/mizu/cli/uiUtils"
|
||||
"io/ioutil"
|
||||
)
|
||||
|
||||
var regenerateFile bool
|
||||
|
||||
var configCmd = &cobra.Command{
|
||||
Use: "config",
|
||||
Short: "Generate config with default values",
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
template, err := mizu.GetConfigWithDefaults()
|
||||
go telemetry.ReportRun("config", config.Config.Config)
|
||||
|
||||
template, err := config.GetConfigWithDefaults()
|
||||
if err != nil {
|
||||
mizu.Log.Errorf("Failed generating config with defaults %v", err)
|
||||
logger.Log.Errorf("Failed generating config with defaults %v", err)
|
||||
return nil
|
||||
}
|
||||
if regenerateFile {
|
||||
if config.Config.Config.Regenerate {
|
||||
data := []byte(template)
|
||||
if err := ioutil.WriteFile(mizu.GetConfigFilePath(), data, 0644); err != nil {
|
||||
mizu.Log.Errorf("Failed writing config %v", err)
|
||||
if err := ioutil.WriteFile(config.Config.ConfigFilePath, data, 0644); err != nil {
|
||||
logger.Log.Errorf("Failed writing config %v", err)
|
||||
return nil
|
||||
}
|
||||
mizu.Log.Infof(fmt.Sprintf("Template File written to %s", fmt.Sprintf(uiUtils.Purple, mizu.GetConfigFilePath())))
|
||||
logger.Log.Infof(fmt.Sprintf("Template File written to %s", fmt.Sprintf(uiUtils.Purple, config.Config.ConfigFilePath)))
|
||||
} else {
|
||||
mizu.Log.Debugf("Writing template config.\n%v", template)
|
||||
logger.Log.Debugf("Writing template config.\n%v", template)
|
||||
fmt.Printf("%v", template)
|
||||
}
|
||||
return nil
|
||||
@@ -36,5 +40,9 @@ var configCmd = &cobra.Command{
|
||||
|
||||
func init() {
|
||||
rootCmd.AddCommand(configCmd)
|
||||
configCmd.Flags().BoolVarP(®enerateFile, "regenerate", "r", false, fmt.Sprintf("Regenerate the config file with default values %s", mizu.GetConfigFilePath()))
|
||||
|
||||
defaultConfig := config.ConfigStruct{}
|
||||
defaults.Set(&defaultConfig)
|
||||
|
||||
configCmd.Flags().BoolP(configStructs.RegenerateConfigName, "r", defaultConfig.Config.Regenerate, fmt.Sprintf("Regenerate the config file with default values to path %s or to chosen path using --%s", defaultConfig.ConfigFilePath, config.ConfigFilePathCommandName))
|
||||
}
|
||||
|
||||
@@ -3,16 +3,27 @@ package cmd
|
||||
import (
|
||||
"github.com/creasty/defaults"
|
||||
"github.com/spf13/cobra"
|
||||
"github.com/up9inc/mizu/cli/mizu"
|
||||
"github.com/up9inc/mizu/cli/mizu/configStructs"
|
||||
"github.com/up9inc/mizu/cli/apiserver"
|
||||
"github.com/up9inc/mizu/cli/config"
|
||||
"github.com/up9inc/mizu/cli/config/configStructs"
|
||||
"github.com/up9inc/mizu/cli/logger"
|
||||
"github.com/up9inc/mizu/cli/mizu/version"
|
||||
"github.com/up9inc/mizu/cli/telemetry"
|
||||
"github.com/up9inc/mizu/cli/uiUtils"
|
||||
)
|
||||
|
||||
var fetchCmd = &cobra.Command{
|
||||
Use: "fetch",
|
||||
Short: "Download recorded traffic to files",
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
go mizu.ReportRun("fetch", mizu.Config.Fetch)
|
||||
if isCompatible, err := mizu.CheckVersionCompatibility(mizu.Config.Fetch.GuiPort); err != nil {
|
||||
go telemetry.ReportRun("fetch", config.Config.Fetch)
|
||||
|
||||
if err := apiserver.Provider.InitAndTestConnection(GetApiServerUrl()); err != nil {
|
||||
logger.Log.Errorf(uiUtils.Error, "Couldn't connect to API server, make sure one running")
|
||||
return nil
|
||||
}
|
||||
|
||||
if isCompatible, err := version.CheckVersionCompatibility(); err != nil {
|
||||
return err
|
||||
} else if !isCompatible {
|
||||
return nil
|
||||
|
||||
@@ -1,95 +1,25 @@
|
||||
package cmd
|
||||
|
||||
import (
|
||||
"archive/zip"
|
||||
"bytes"
|
||||
"fmt"
|
||||
"github.com/up9inc/mizu/cli/kubernetes"
|
||||
"github.com/up9inc/mizu/cli/mizu"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"log"
|
||||
"net/http"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"github.com/up9inc/mizu/cli/apiserver"
|
||||
"github.com/up9inc/mizu/cli/config"
|
||||
"github.com/up9inc/mizu/cli/logger"
|
||||
"github.com/up9inc/mizu/cli/mizu/fsUtils"
|
||||
"github.com/up9inc/mizu/cli/uiUtils"
|
||||
)
|
||||
|
||||
func RunMizuFetch() {
|
||||
mizuProxiedUrl := kubernetes.GetMizuApiServerProxiedHostAndPath(mizu.Config.Fetch.GuiPort)
|
||||
resp, err := http.Get(fmt.Sprintf("http://%s/api/har?from=%v&to=%v", mizuProxiedUrl, mizu.Config.Fetch.FromTimestamp, mizu.Config.Fetch.ToTimestamp))
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
if err := apiserver.Provider.InitAndTestConnection(GetApiServerUrl()); err != nil {
|
||||
logger.Log.Errorf(uiUtils.Error, "Couldn't connect to API server, check logs")
|
||||
}
|
||||
|
||||
defer func() { _ = resp.Body.Close() }()
|
||||
|
||||
body, err := ioutil.ReadAll(resp.Body)
|
||||
zipReader, err := apiserver.Provider.GetHars(config.Config.Fetch.FromTimestamp, config.Config.Fetch.ToTimestamp)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
logger.Log.Errorf("Failed fetch data from API server %v", err)
|
||||
return
|
||||
}
|
||||
|
||||
zipReader, err := zip.NewReader(bytes.NewReader(body), int64(len(body)))
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
if err := fsUtils.Unzip(zipReader, config.Config.Fetch.Directory); err != nil {
|
||||
logger.Log.Debugf("[ERROR] failed unzip %v", err)
|
||||
}
|
||||
|
||||
_ = Unzip(zipReader, mizu.Config.Fetch.Directory)
|
||||
}
|
||||
|
||||
func Unzip(reader *zip.Reader, dest string) error {
|
||||
dest, _ = filepath.Abs(dest)
|
||||
_ = os.MkdirAll(dest, os.ModePerm)
|
||||
|
||||
// Closure to address file descriptors issue with all the deferred .Close() methods
|
||||
extractAndWriteFile := func(f *zip.File) error {
|
||||
rc, err := f.Open()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer func() {
|
||||
if err := rc.Close(); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}()
|
||||
|
||||
path := filepath.Join(dest, f.Name)
|
||||
|
||||
// Check for ZipSlip (Directory traversal)
|
||||
if !strings.HasPrefix(path, filepath.Clean(dest)+string(os.PathSeparator)) {
|
||||
return fmt.Errorf("illegal file path: %s", path)
|
||||
}
|
||||
|
||||
if f.FileInfo().IsDir() {
|
||||
_ = os.MkdirAll(path, f.Mode())
|
||||
} else {
|
||||
_ = os.MkdirAll(filepath.Dir(path), f.Mode())
|
||||
mizu.Log.Infof("writing HAR file [ %v ]", path)
|
||||
f, err := os.OpenFile(path, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, f.Mode())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer func() {
|
||||
if err := f.Close(); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
mizu.Log.Info(" done")
|
||||
}()
|
||||
|
||||
_, err = io.Copy(f, rc)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
for _, f := range reader.File {
|
||||
err := extractAndWriteFile(f)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -2,38 +2,38 @@ package cmd
|
||||
|
||||
import (
|
||||
"context"
|
||||
"github.com/creasty/defaults"
|
||||
"github.com/spf13/cobra"
|
||||
"github.com/up9inc/mizu/cli/fsUtils"
|
||||
"github.com/up9inc/mizu/cli/config"
|
||||
"github.com/up9inc/mizu/cli/config/configStructs"
|
||||
"github.com/up9inc/mizu/cli/errormessage"
|
||||
"github.com/up9inc/mizu/cli/kubernetes"
|
||||
"github.com/up9inc/mizu/cli/mizu"
|
||||
"os"
|
||||
"path"
|
||||
"github.com/up9inc/mizu/cli/logger"
|
||||
"github.com/up9inc/mizu/cli/mizu/fsUtils"
|
||||
"github.com/up9inc/mizu/cli/telemetry"
|
||||
)
|
||||
|
||||
var filePath string
|
||||
|
||||
var logsCmd = &cobra.Command{
|
||||
Use: "logs",
|
||||
Short: "Create a zip file with logs for Github issue or troubleshoot",
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
kubernetesProvider, err := kubernetes.NewProvider(mizu.Config.View.KubeConfigPath)
|
||||
go telemetry.ReportRun("logs", config.Config.Logs)
|
||||
|
||||
kubernetesProvider, err := kubernetes.NewProvider(config.Config.KubeConfigPath())
|
||||
if err != nil {
|
||||
logger.Log.Error(err)
|
||||
return nil
|
||||
}
|
||||
ctx, _ := context.WithCancel(context.Background())
|
||||
|
||||
if filePath == "" {
|
||||
pwd, err := os.Getwd()
|
||||
if err != nil {
|
||||
mizu.Log.Errorf("Failed to get PWD, %v (try using `mizu logs -f <full path dest zip file>)`", err)
|
||||
return nil
|
||||
}
|
||||
filePath = path.Join(pwd, "mizu_logs.zip")
|
||||
if validationErr := config.Config.Logs.Validate(); validationErr != nil {
|
||||
return errormessage.FormatError(validationErr)
|
||||
}
|
||||
mizu.Log.Debugf("Using file path %s", filePath)
|
||||
|
||||
if err := fsUtils.DumpLogs(kubernetesProvider, ctx, filePath); err != nil {
|
||||
mizu.Log.Errorf("Failed dump logs %v", err)
|
||||
logger.Log.Debugf("Using file path %s", config.Config.Logs.FilePath())
|
||||
|
||||
if dumpLogsErr := fsUtils.DumpLogs(kubernetesProvider, ctx, config.Config.Logs.FilePath()); dumpLogsErr != nil {
|
||||
logger.Log.Errorf("Failed dump logs %v", dumpLogsErr)
|
||||
}
|
||||
|
||||
return nil
|
||||
@@ -42,5 +42,9 @@ var logsCmd = &cobra.Command{
|
||||
|
||||
func init() {
|
||||
rootCmd.AddCommand(logsCmd)
|
||||
logsCmd.Flags().StringVarP(&filePath, "file", "f", "", "Path for zip file (default current <pwd>\\mizu_logs.zip)")
|
||||
|
||||
defaultLogsConfig := configStructs.LogsConfig{}
|
||||
defaults.Set(&defaultLogsConfig)
|
||||
|
||||
logsCmd.Flags().StringP(configStructs.FileLogsName, "f", defaultLogsConfig.FileStr, "Path for zip file (default current <pwd>\\mizu_logs.zip)")
|
||||
}
|
||||
|
||||
@@ -2,9 +2,15 @@ package cmd
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/creasty/defaults"
|
||||
"github.com/spf13/cobra"
|
||||
"github.com/up9inc/mizu/cli/fsUtils"
|
||||
"github.com/up9inc/mizu/cli/config"
|
||||
"github.com/up9inc/mizu/cli/logger"
|
||||
"github.com/up9inc/mizu/cli/mizu"
|
||||
"github.com/up9inc/mizu/cli/mizu/fsUtils"
|
||||
"github.com/up9inc/mizu/cli/mizu/version"
|
||||
"github.com/up9inc/mizu/cli/uiUtils"
|
||||
"time"
|
||||
)
|
||||
|
||||
var rootCmd = &cobra.Command{
|
||||
@@ -13,24 +19,42 @@ var rootCmd = &cobra.Command{
|
||||
Long: `A web traffic viewer for kubernetes
|
||||
Further info is available at https://github.com/up9inc/mizu`,
|
||||
PersistentPreRunE: func(cmd *cobra.Command, args []string) error {
|
||||
if err := fsUtils.EnsureDir(mizu.GetMizuFolderPath()); err != nil {
|
||||
mizu.Log.Errorf("Failed to use mizu folder, %v", err)
|
||||
if err := config.InitConfig(cmd); err != nil {
|
||||
logger.Log.Fatal(err)
|
||||
}
|
||||
mizu.InitLogger()
|
||||
if err := mizu.InitConfig(cmd); err != nil {
|
||||
mizu.Log.Fatal(err)
|
||||
}
|
||||
|
||||
return nil
|
||||
},
|
||||
}
|
||||
|
||||
func init() {
|
||||
rootCmd.PersistentFlags().StringSlice(mizu.SetCommandName, []string{}, fmt.Sprintf("Override values using --%s", mizu.SetCommandName))
|
||||
defaultConfig := config.ConfigStruct{}
|
||||
defaults.Set(&defaultConfig)
|
||||
|
||||
rootCmd.PersistentFlags().StringSlice(config.SetCommandName, []string{}, fmt.Sprintf("Override values using --%s", config.SetCommandName))
|
||||
rootCmd.PersistentFlags().String(config.ConfigFilePathCommandName, defaultConfig.ConfigFilePath, fmt.Sprintf("Override config file path using --%s", config.ConfigFilePathCommandName))
|
||||
}
|
||||
|
||||
func printNewVersionIfNeeded(versionChan chan string) {
|
||||
select {
|
||||
case versionMsg := <-versionChan:
|
||||
if versionMsg != "" {
|
||||
logger.Log.Infof(uiUtils.Yellow, versionMsg)
|
||||
}
|
||||
case <-time.After(2 * time.Second):
|
||||
}
|
||||
}
|
||||
|
||||
// Execute adds all child commands to the root command and sets flags appropriately.
|
||||
// This is called by main.main(). It only needs to happen once to the tapCmd.
|
||||
func Execute() {
|
||||
if err := fsUtils.EnsureDir(mizu.GetMizuFolderPath()); err != nil {
|
||||
logger.Log.Errorf("Failed to use mizu folder, %v", err)
|
||||
}
|
||||
logger.InitLogger()
|
||||
|
||||
versionChan := make(chan string)
|
||||
defer printNewVersionIfNeeded(versionChan)
|
||||
go version.CheckNewerVersion(versionChan)
|
||||
|
||||
cobra.CheckErr(rootCmd.Execute())
|
||||
}
|
||||
|
||||
@@ -2,13 +2,15 @@ package cmd
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"github.com/up9inc/mizu/cli/config"
|
||||
"github.com/up9inc/mizu/cli/config/configStructs"
|
||||
"github.com/up9inc/mizu/cli/logger"
|
||||
"github.com/up9inc/mizu/cli/telemetry"
|
||||
"os"
|
||||
|
||||
"github.com/creasty/defaults"
|
||||
"github.com/spf13/cobra"
|
||||
"github.com/up9inc/mizu/cli/errormessage"
|
||||
"github.com/up9inc/mizu/cli/mizu"
|
||||
"github.com/up9inc/mizu/cli/mizu/configStructs"
|
||||
"github.com/up9inc/mizu/cli/uiUtils"
|
||||
)
|
||||
|
||||
@@ -20,31 +22,27 @@ var tapCmd = &cobra.Command{
|
||||
Long: `Record the ingoing traffic of a kubernetes pod.
|
||||
Supported protocols are HTTP and gRPC.`,
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
go mizu.ReportRun("tap", mizu.Config.Tap)
|
||||
go telemetry.ReportRun("tap", config.Config.Tap)
|
||||
RunMizuTap()
|
||||
return nil
|
||||
},
|
||||
PreRunE: func(cmd *cobra.Command, args []string) error {
|
||||
if len(args) == 1 {
|
||||
mizu.Config.Tap.PodRegexStr = args[0]
|
||||
config.Config.Tap.PodRegexStr = args[0]
|
||||
} else if len(args) > 1 {
|
||||
return errors.New("unexpected number of arguments")
|
||||
}
|
||||
|
||||
if err := mizu.Config.Validate(); err != nil {
|
||||
if err := config.Config.Tap.Validate(); err != nil {
|
||||
return errormessage.FormatError(err)
|
||||
}
|
||||
|
||||
if err := mizu.Config.Tap.Validate(); err != nil {
|
||||
return errormessage.FormatError(err)
|
||||
}
|
||||
logger.Log.Infof("Mizu will store up to %s of traffic, old traffic will be cleared once the limit is reached.", config.Config.Tap.HumanMaxEntriesDBSize)
|
||||
|
||||
mizu.Log.Infof("Mizu will store up to %s of traffic, old traffic will be cleared once the limit is reached.", mizu.Config.Tap.HumanMaxEntriesDBSize)
|
||||
|
||||
if mizu.Config.Tap.Analysis {
|
||||
mizu.Log.Infof(analysisMessageToConfirm)
|
||||
if config.Config.Tap.Analysis {
|
||||
logger.Log.Infof(analysisMessageToConfirm)
|
||||
if !uiUtils.AskForConfirmation("Would you like to proceed [Y/n]: ") {
|
||||
mizu.Log.Infof("You can always run mizu without analysis, aborting")
|
||||
logger.Log.Infof("You can always run mizu without analysis, aborting")
|
||||
os.Exit(0)
|
||||
}
|
||||
}
|
||||
@@ -60,10 +58,10 @@ func init() {
|
||||
defaults.Set(&defaultTapConfig)
|
||||
|
||||
tapCmd.Flags().Uint16P(configStructs.GuiPortTapName, "p", defaultTapConfig.GuiPort, "Provide a custom port for the web interface webserver")
|
||||
tapCmd.Flags().StringArrayP(configStructs.NamespacesTapName, "n", defaultTapConfig.Namespaces, "Namespaces selector")
|
||||
tapCmd.Flags().StringSliceP(configStructs.NamespacesTapName, "n", defaultTapConfig.Namespaces, "Namespaces selector")
|
||||
tapCmd.Flags().Bool(configStructs.AnalysisTapName, defaultTapConfig.Analysis, "Uploads traffic to UP9 for further analysis (Beta)")
|
||||
tapCmd.Flags().BoolP(configStructs.AllNamespacesTapName, "A", defaultTapConfig.AllNamespaces, "Tap all namespaces")
|
||||
tapCmd.Flags().StringArrayP(configStructs.PlainTextFilterRegexesTapName, "r", defaultTapConfig.PlainTextFilterRegexes, "List of regex expressions that are used to filter matching values from text/plain http bodies")
|
||||
tapCmd.Flags().StringSliceP(configStructs.PlainTextFilterRegexesTapName, "r", defaultTapConfig.PlainTextFilterRegexes, "List of regex expressions that are used to filter matching values from text/plain http bodies")
|
||||
tapCmd.Flags().Bool(configStructs.DisableRedactionTapName, defaultTapConfig.DisableRedaction, "Disables redaction of potentially sensitive request/response headers and body values")
|
||||
tapCmd.Flags().String(configStructs.HumanMaxEntriesDBSizeTapName, defaultTapConfig.HumanMaxEntriesDBSize, "Override the default max entries db size")
|
||||
tapCmd.Flags().String(configStructs.DirectionTapName, defaultTapConfig.Direction, "Record traffic that goes in this direction (relative to the tapped pod): in/any")
|
||||
|
||||
@@ -1,26 +1,23 @@
|
||||
package cmd
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"github.com/up9inc/mizu/cli/fsUtils"
|
||||
"github.com/up9inc/mizu/cli/goUtils"
|
||||
"github.com/up9inc/mizu/cli/mizu/configStructs"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"os"
|
||||
"os/signal"
|
||||
"path"
|
||||
"regexp"
|
||||
"strings"
|
||||
"syscall"
|
||||
"time"
|
||||
|
||||
"github.com/up9inc/mizu/cli/apiserver"
|
||||
"github.com/up9inc/mizu/cli/config"
|
||||
"github.com/up9inc/mizu/cli/config/configStructs"
|
||||
"github.com/up9inc/mizu/cli/errormessage"
|
||||
"github.com/up9inc/mizu/cli/kubernetes"
|
||||
"github.com/up9inc/mizu/cli/logger"
|
||||
"github.com/up9inc/mizu/cli/mizu"
|
||||
"github.com/up9inc/mizu/cli/mizu/fsUtils"
|
||||
"github.com/up9inc/mizu/cli/mizu/goUtils"
|
||||
"github.com/up9inc/mizu/cli/telemetry"
|
||||
"github.com/up9inc/mizu/cli/uiUtils"
|
||||
"github.com/up9inc/mizu/shared"
|
||||
"github.com/up9inc/mizu/shared/debounce"
|
||||
@@ -46,21 +43,20 @@ var state tapState
|
||||
func RunMizuTap() {
|
||||
mizuApiFilteringOptions, err := getMizuApiFilteringOptions()
|
||||
if err != nil {
|
||||
mizu.Log.Errorf(uiUtils.Error, fmt.Sprintf("Error parsing regex-masking: %v", errormessage.FormatError(err)))
|
||||
logger.Log.Errorf(uiUtils.Error, fmt.Sprintf("Error parsing regex-masking: %v", errormessage.FormatError(err)))
|
||||
return
|
||||
}
|
||||
var mizuValidationRules string
|
||||
if mizu.Config.Tap.EnforcePolicyFile != "" {
|
||||
mizuValidationRules, err = readValidationRules(mizu.Config.Tap.EnforcePolicyFile)
|
||||
if config.Config.Tap.EnforcePolicyFile != "" {
|
||||
mizuValidationRules, err = readValidationRules(config.Config.Tap.EnforcePolicyFile)
|
||||
if err != nil {
|
||||
mizu.Log.Errorf(uiUtils.Error, fmt.Sprintf("Error reading policy file: %v", errormessage.FormatError(err)))
|
||||
logger.Log.Errorf(uiUtils.Error, fmt.Sprintf("Error reading policy file: %v", errormessage.FormatError(err)))
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
kubernetesProvider, err := kubernetes.NewProvider(mizu.Config.KubeConfigPath)
|
||||
kubernetesProvider, err := kubernetes.NewProvider(config.Config.KubeConfigPath())
|
||||
if err != nil {
|
||||
mizu.Log.Error(err)
|
||||
logger.Log.Error(err)
|
||||
return
|
||||
}
|
||||
|
||||
@@ -69,41 +65,49 @@ func RunMizuTap() {
|
||||
|
||||
targetNamespaces := getNamespaces(kubernetesProvider)
|
||||
|
||||
if config.Config.IsNsRestrictedMode() {
|
||||
if len(targetNamespaces) != 1 || !mizu.Contains(targetNamespaces, config.Config.MizuResourcesNamespace) {
|
||||
logger.Log.Errorf("Not supported mode. Mizu can't resolve IPs in other namespaces when running in namespace restricted mode.\n"+
|
||||
"You can use the same namespace for --%s and --%s", configStructs.NamespacesTapName, config.MizuResourcesNamespaceConfigName)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
var namespacesStr string
|
||||
if targetNamespaces[0] != mizu.K8sAllNamespaces {
|
||||
if !mizu.Contains(targetNamespaces, mizu.K8sAllNamespaces) {
|
||||
namespacesStr = fmt.Sprintf("namespaces \"%s\"", strings.Join(targetNamespaces, "\", \""))
|
||||
} else {
|
||||
namespacesStr = "all namespaces"
|
||||
}
|
||||
mizu.CheckNewerVersion()
|
||||
mizu.Log.Infof("Tapping pods in %s", namespacesStr)
|
||||
|
||||
logger.Log.Infof("Tapping pods in %s", namespacesStr)
|
||||
|
||||
if err, _ := updateCurrentlyTappedPods(kubernetesProvider, ctx, targetNamespaces); err != nil {
|
||||
mizu.Log.Errorf(uiUtils.Error, fmt.Sprintf("Error getting pods by regex: %v", errormessage.FormatError(err)))
|
||||
logger.Log.Errorf(uiUtils.Error, fmt.Sprintf("Error getting pods by regex: %v", errormessage.FormatError(err)))
|
||||
return
|
||||
}
|
||||
|
||||
if len(state.currentlyTappedPods) == 0 {
|
||||
var suggestionStr string
|
||||
if targetNamespaces[0] != mizu.K8sAllNamespaces {
|
||||
if !mizu.Contains(targetNamespaces, mizu.K8sAllNamespaces) {
|
||||
suggestionStr = ". Select a different namespace with -n or tap all namespaces with -A"
|
||||
}
|
||||
mizu.Log.Warningf(uiUtils.Warning, fmt.Sprintf("Did not find any pods matching the regex argument%s", suggestionStr))
|
||||
logger.Log.Warningf(uiUtils.Warning, fmt.Sprintf("Did not find any pods matching the regex argument%s", suggestionStr))
|
||||
}
|
||||
|
||||
if mizu.Config.Tap.DryRun {
|
||||
if config.Config.Tap.DryRun {
|
||||
return
|
||||
}
|
||||
|
||||
nodeToTappedPodIPMap := getNodeHostToTappedPodIpsMap(state.currentlyTappedPods)
|
||||
|
||||
defer cleanUpMizuResources(kubernetesProvider)
|
||||
defer finishMizuExecution(kubernetesProvider)
|
||||
if err := createMizuResources(ctx, kubernetesProvider, nodeToTappedPodIPMap, mizuApiFilteringOptions, mizuValidationRules); err != nil {
|
||||
mizu.Log.Errorf(uiUtils.Error, fmt.Sprintf("Error creating resources: %v", errormessage.FormatError(err)))
|
||||
logger.Log.Errorf(uiUtils.Error, fmt.Sprintf("Error creating resources: %v", errormessage.FormatError(err)))
|
||||
return
|
||||
}
|
||||
|
||||
go goUtils.HandleExcWrapper(createProxyToApiServerPod, ctx, kubernetesProvider, cancel)
|
||||
go goUtils.HandleExcWrapper(watchApiServerPod, ctx, kubernetesProvider, cancel)
|
||||
go goUtils.HandleExcWrapper(watchPodsForTapping, ctx, kubernetesProvider, targetNamespaces, cancel)
|
||||
|
||||
//block until exit signal or error
|
||||
@@ -120,7 +124,7 @@ func readValidationRules(file string) (string, error) {
|
||||
}
|
||||
|
||||
func createMizuResources(ctx context.Context, kubernetesProvider *kubernetes.Provider, nodeToTappedPodIPMap map[string][]string, mizuApiFilteringOptions *shared.TrafficFilteringOptions, mizuValidationRules string) error {
|
||||
if !mizu.Config.IsNsRestrictedMode() {
|
||||
if !config.Config.IsNsRestrictedMode() {
|
||||
if err := createMizuNamespace(ctx, kubernetesProvider); err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -135,7 +139,7 @@ func createMizuResources(ctx context.Context, kubernetesProvider *kubernetes.Pro
|
||||
}
|
||||
|
||||
if err := createMizuConfigmap(ctx, kubernetesProvider, mizuValidationRules); err != nil {
|
||||
mizu.Log.Warningf(uiUtils.Warning, fmt.Sprintf("Failed to create resources required for policy validation. Mizu will not validate policy rules. error: %v\n", errormessage.FormatError(err)))
|
||||
logger.Log.Warningf(uiUtils.Warning, fmt.Sprintf("Failed to create resources required for policy validation. Mizu will not validate policy rules. error: %v\n", errormessage.FormatError(err)))
|
||||
state.doNotRemoveConfigMap = true
|
||||
} else if mizuValidationRules == "" {
|
||||
state.doNotRemoveConfigMap = true
|
||||
@@ -145,12 +149,12 @@ func createMizuResources(ctx context.Context, kubernetesProvider *kubernetes.Pro
|
||||
}
|
||||
|
||||
func createMizuConfigmap(ctx context.Context, kubernetesProvider *kubernetes.Provider, data string) error {
|
||||
err := kubernetesProvider.CreateConfigMap(ctx, mizu.Config.MizuResourcesNamespace, mizu.ConfigMapName, data)
|
||||
err := kubernetesProvider.CreateConfigMap(ctx, config.Config.MizuResourcesNamespace, mizu.ConfigMapName, data)
|
||||
return err
|
||||
}
|
||||
|
||||
func createMizuNamespace(ctx context.Context, kubernetesProvider *kubernetes.Provider) error {
|
||||
_, err := kubernetesProvider.CreateNamespace(ctx, mizu.Config.MizuResourcesNamespace)
|
||||
_, err := kubernetesProvider.CreateNamespace(ctx, config.Config.MizuResourcesNamespace)
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -159,7 +163,7 @@ func createMizuApiServer(ctx context.Context, kubernetesProvider *kubernetes.Pro
|
||||
|
||||
state.mizuServiceAccountExists, err = createRBACIfNecessary(ctx, kubernetesProvider)
|
||||
if err != nil {
|
||||
mizu.Log.Warningf(uiUtils.Warning, fmt.Sprintf("Failed to ensure the resources required for IP resolving. Mizu will not resolve target IPs to names. error: %v", errormessage.FormatError(err)))
|
||||
logger.Log.Warningf(uiUtils.Warning, fmt.Sprintf("Failed to ensure the resources required for IP resolving. Mizu will not resolve target IPs to names. error: %v", errormessage.FormatError(err)))
|
||||
}
|
||||
|
||||
var serviceAccountName string
|
||||
@@ -170,25 +174,27 @@ func createMizuApiServer(ctx context.Context, kubernetesProvider *kubernetes.Pro
|
||||
}
|
||||
|
||||
opts := &kubernetes.ApiServerOptions{
|
||||
Namespace: mizu.Config.MizuResourcesNamespace,
|
||||
Namespace: config.Config.MizuResourcesNamespace,
|
||||
PodName: mizu.ApiServerPodName,
|
||||
PodImage: mizu.Config.AgentImage,
|
||||
PodImage: config.Config.AgentImage,
|
||||
ServiceAccountName: serviceAccountName,
|
||||
IsNamespaceRestricted: mizu.Config.IsNsRestrictedMode(),
|
||||
IsNamespaceRestricted: config.Config.IsNsRestrictedMode(),
|
||||
MizuApiFilteringOptions: mizuApiFilteringOptions,
|
||||
MaxEntriesDBSizeBytes: mizu.Config.Tap.MaxEntriesDBSizeBytes(),
|
||||
MaxEntriesDBSizeBytes: config.Config.Tap.MaxEntriesDBSizeBytes(),
|
||||
Resources: config.Config.Tap.ApiServerResources,
|
||||
ImagePullPolicy: config.Config.ImagePullPolicy(),
|
||||
}
|
||||
_, err = kubernetesProvider.CreateMizuApiServerPod(ctx, opts)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
mizu.Log.Debugf("Successfully created API server pod: %s", mizu.ApiServerPodName)
|
||||
logger.Log.Debugf("Successfully created API server pod: %s", mizu.ApiServerPodName)
|
||||
|
||||
state.apiServerService, err = kubernetesProvider.CreateService(ctx, mizu.Config.MizuResourcesNamespace, mizu.ApiServerPodName, mizu.ApiServerPodName)
|
||||
state.apiServerService, err = kubernetesProvider.CreateService(ctx, config.Config.MizuResourcesNamespace, mizu.ApiServerPodName, mizu.ApiServerPodName)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
mizu.Log.Debugf("Successfully created service: %s", mizu.ApiServerPodName)
|
||||
logger.Log.Debugf("Successfully created service: %s", mizu.ApiServerPodName)
|
||||
|
||||
return nil
|
||||
}
|
||||
@@ -196,9 +202,9 @@ func createMizuApiServer(ctx context.Context, kubernetesProvider *kubernetes.Pro
|
||||
func getMizuApiFilteringOptions() (*shared.TrafficFilteringOptions, error) {
|
||||
var compiledRegexSlice []*shared.SerializableRegexp
|
||||
|
||||
if mizu.Config.Tap.PlainTextFilterRegexes != nil && len(mizu.Config.Tap.PlainTextFilterRegexes) > 0 {
|
||||
if config.Config.Tap.PlainTextFilterRegexes != nil && len(config.Config.Tap.PlainTextFilterRegexes) > 0 {
|
||||
compiledRegexSlice = make([]*shared.SerializableRegexp, 0)
|
||||
for _, regexStr := range mizu.Config.Tap.PlainTextFilterRegexes {
|
||||
for _, regexStr := range config.Config.Tap.PlainTextFilterRegexes {
|
||||
compiledRegex, err := shared.CompileRegexToSerializableRegexp(regexStr)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@@ -209,8 +215,8 @@ func getMizuApiFilteringOptions() (*shared.TrafficFilteringOptions, error) {
|
||||
|
||||
return &shared.TrafficFilteringOptions{
|
||||
PlainTextMaskingRegexes: compiledRegexSlice,
|
||||
HealthChecksUserAgentHeaders: mizu.Config.Tap.HealthChecksUserAgentHeaders,
|
||||
DisableRedaction: mizu.Config.Tap.DisableRedaction,
|
||||
HealthChecksUserAgentHeaders: config.Config.Tap.HealthChecksUserAgentHeaders,
|
||||
DisableRedaction: config.Config.Tap.DisableRedaction,
|
||||
}, nil
|
||||
}
|
||||
|
||||
@@ -225,20 +231,21 @@ func updateMizuTappers(ctx context.Context, kubernetesProvider *kubernetes.Provi
|
||||
|
||||
if err := kubernetesProvider.ApplyMizuTapperDaemonSet(
|
||||
ctx,
|
||||
mizu.Config.MizuResourcesNamespace,
|
||||
config.Config.MizuResourcesNamespace,
|
||||
mizu.TapperDaemonSetName,
|
||||
mizu.Config.AgentImage,
|
||||
config.Config.AgentImage,
|
||||
mizu.TapperPodName,
|
||||
fmt.Sprintf("%s.%s.svc.cluster.local", state.apiServerService.Name, state.apiServerService.Namespace),
|
||||
nodeToTappedPodIPMap,
|
||||
serviceAccountName,
|
||||
mizu.Config.Tap.TapOutgoing(),
|
||||
config.Config.Tap.TapperResources,
|
||||
config.Config.ImagePullPolicy(),
|
||||
); err != nil {
|
||||
return err
|
||||
}
|
||||
mizu.Log.Debugf("Successfully created %v tappers", len(nodeToTappedPodIPMap))
|
||||
logger.Log.Debugf("Successfully created %v tappers", len(nodeToTappedPodIPMap))
|
||||
} else {
|
||||
if err := kubernetesProvider.RemoveDaemonSet(ctx, mizu.Config.MizuResourcesNamespace, mizu.TapperDaemonSetName); err != nil {
|
||||
if err := kubernetesProvider.RemoveDaemonSet(ctx, config.Config.MizuResourcesNamespace, mizu.TapperDaemonSetName); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
@@ -246,70 +253,77 @@ func updateMizuTappers(ctx context.Context, kubernetesProvider *kubernetes.Provi
|
||||
return nil
|
||||
}
|
||||
|
||||
func cleanUpMizuResources(kubernetesProvider *kubernetes.Provider) {
|
||||
|
||||
func finishMizuExecution(kubernetesProvider *kubernetes.Provider) {
|
||||
telemetry.ReportAPICalls()
|
||||
removalCtx, cancel := context.WithTimeout(context.Background(), cleanupTimeout)
|
||||
defer cancel()
|
||||
dumpLogsIfNeeded(kubernetesProvider, removalCtx)
|
||||
cleanUpMizuResources(kubernetesProvider, removalCtx, cancel)
|
||||
}
|
||||
|
||||
if mizu.Config.DumpLogs {
|
||||
mizuDir := mizu.GetMizuFolderPath()
|
||||
filePath = path.Join(mizuDir, fmt.Sprintf("mizu_logs_%s.zip", time.Now().Format("2006_01_02__15_04_05")))
|
||||
if err := fsUtils.DumpLogs(kubernetesProvider, removalCtx, filePath); err != nil {
|
||||
mizu.Log.Errorf("Failed dump logs %v", err)
|
||||
}
|
||||
func dumpLogsIfNeeded(kubernetesProvider *kubernetes.Provider, removalCtx context.Context) {
|
||||
if !config.Config.DumpLogs {
|
||||
return
|
||||
}
|
||||
mizuDir := mizu.GetMizuFolderPath()
|
||||
filePath := path.Join(mizuDir, fmt.Sprintf("mizu_logs_%s.zip", time.Now().Format("2006_01_02__15_04_05")))
|
||||
if err := fsUtils.DumpLogs(kubernetesProvider, removalCtx, filePath); err != nil {
|
||||
logger.Log.Errorf("Failed dump logs %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
mizu.Log.Infof("\nRemoving mizu resources\n")
|
||||
func cleanUpMizuResources(kubernetesProvider *kubernetes.Provider, removalCtx context.Context, cancel context.CancelFunc) {
|
||||
logger.Log.Infof("\nRemoving mizu resources\n")
|
||||
|
||||
if !mizu.Config.IsNsRestrictedMode() {
|
||||
if err := kubernetesProvider.RemoveNamespace(removalCtx, mizu.Config.MizuResourcesNamespace); err != nil {
|
||||
mizu.Log.Errorf(uiUtils.Error, fmt.Sprintf("Error removing Namespace %s: %v", mizu.Config.MizuResourcesNamespace, errormessage.FormatError(err)))
|
||||
if !config.Config.IsNsRestrictedMode() {
|
||||
if err := kubernetesProvider.RemoveNamespace(removalCtx, config.Config.MizuResourcesNamespace); err != nil {
|
||||
logger.Log.Errorf(uiUtils.Error, fmt.Sprintf("Error removing Namespace %s: %v", config.Config.MizuResourcesNamespace, errormessage.FormatError(err)))
|
||||
return
|
||||
}
|
||||
} else {
|
||||
if err := kubernetesProvider.RemovePod(removalCtx, mizu.Config.MizuResourcesNamespace, mizu.ApiServerPodName); err != nil {
|
||||
mizu.Log.Errorf(uiUtils.Error, fmt.Sprintf("Error removing Pod %s in namespace %s: %v", mizu.ApiServerPodName, mizu.Config.MizuResourcesNamespace, errormessage.FormatError(err)))
|
||||
if err := kubernetesProvider.RemovePod(removalCtx, config.Config.MizuResourcesNamespace, mizu.ApiServerPodName); err != nil {
|
||||
logger.Log.Errorf(uiUtils.Error, fmt.Sprintf("Error removing Pod %s in namespace %s: %v", mizu.ApiServerPodName, config.Config.MizuResourcesNamespace, errormessage.FormatError(err)))
|
||||
}
|
||||
|
||||
if err := kubernetesProvider.RemoveService(removalCtx, mizu.Config.MizuResourcesNamespace, mizu.ApiServerPodName); err != nil {
|
||||
mizu.Log.Errorf(uiUtils.Error, fmt.Sprintf("Error removing Service %s in namespace %s: %v", mizu.ApiServerPodName, mizu.Config.MizuResourcesNamespace, errormessage.FormatError(err)))
|
||||
if err := kubernetesProvider.RemoveService(removalCtx, config.Config.MizuResourcesNamespace, mizu.ApiServerPodName); err != nil {
|
||||
logger.Log.Errorf(uiUtils.Error, fmt.Sprintf("Error removing Service %s in namespace %s: %v", mizu.ApiServerPodName, config.Config.MizuResourcesNamespace, errormessage.FormatError(err)))
|
||||
}
|
||||
|
||||
if err := kubernetesProvider.RemoveDaemonSet(removalCtx, mizu.Config.MizuResourcesNamespace, mizu.TapperDaemonSetName); err != nil {
|
||||
mizu.Log.Errorf(uiUtils.Error, fmt.Sprintf("Error removing DaemonSet %s in namespace %s: %v", mizu.TapperDaemonSetName, mizu.Config.MizuResourcesNamespace, errormessage.FormatError(err)))
|
||||
if err := kubernetesProvider.RemoveDaemonSet(removalCtx, config.Config.MizuResourcesNamespace, mizu.TapperDaemonSetName); err != nil {
|
||||
logger.Log.Errorf(uiUtils.Error, fmt.Sprintf("Error removing DaemonSet %s in namespace %s: %v", mizu.TapperDaemonSetName, config.Config.MizuResourcesNamespace, errormessage.FormatError(err)))
|
||||
}
|
||||
|
||||
if !state.doNotRemoveConfigMap {
|
||||
if err := kubernetesProvider.RemoveConfigMap(removalCtx, mizu.Config.MizuResourcesNamespace, mizu.ConfigMapName); err != nil {
|
||||
mizu.Log.Errorf(uiUtils.Error, fmt.Sprintf("Error removing ConfigMap %s in namespace %s: %v", mizu.ConfigMapName, mizu.Config.MizuResourcesNamespace, errormessage.FormatError(err)))
|
||||
if err := kubernetesProvider.RemoveConfigMap(removalCtx, config.Config.MizuResourcesNamespace, mizu.ConfigMapName); err != nil {
|
||||
logger.Log.Errorf(uiUtils.Error, fmt.Sprintf("Error removing ConfigMap %s in namespace %s: %v", mizu.ConfigMapName, config.Config.MizuResourcesNamespace, errormessage.FormatError(err)))
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
if state.mizuServiceAccountExists {
|
||||
if !mizu.Config.IsNsRestrictedMode() {
|
||||
if !config.Config.IsNsRestrictedMode() {
|
||||
if err := kubernetesProvider.RemoveNonNamespacedResources(removalCtx, mizu.ClusterRoleName, mizu.ClusterRoleBindingName); err != nil {
|
||||
mizu.Log.Errorf(uiUtils.Error, fmt.Sprintf("Error removing non-namespaced resources: %v", errormessage.FormatError(err)))
|
||||
logger.Log.Errorf(uiUtils.Error, fmt.Sprintf("Error removing non-namespaced resources: %v", errormessage.FormatError(err)))
|
||||
return
|
||||
}
|
||||
} else {
|
||||
if err := kubernetesProvider.RemoveServicAccount(removalCtx, mizu.Config.MizuResourcesNamespace, mizu.ServiceAccountName); err != nil {
|
||||
mizu.Log.Errorf(uiUtils.Error, fmt.Sprintf("Error removing Service Account %s in namespace %s: %v", mizu.ServiceAccountName, mizu.Config.MizuResourcesNamespace, errormessage.FormatError(err)))
|
||||
if err := kubernetesProvider.RemoveServicAccount(removalCtx, config.Config.MizuResourcesNamespace, mizu.ServiceAccountName); err != nil {
|
||||
logger.Log.Errorf(uiUtils.Error, fmt.Sprintf("Error removing Service Account %s in namespace %s: %v", mizu.ServiceAccountName, config.Config.MizuResourcesNamespace, errormessage.FormatError(err)))
|
||||
return
|
||||
}
|
||||
|
||||
if err := kubernetesProvider.RemoveRole(removalCtx, mizu.Config.MizuResourcesNamespace, mizu.RoleName); err != nil {
|
||||
mizu.Log.Errorf(uiUtils.Error, fmt.Sprintf("Error removing Role %s in namespace %s: %v", mizu.RoleName, mizu.Config.MizuResourcesNamespace, errormessage.FormatError(err)))
|
||||
if err := kubernetesProvider.RemoveRole(removalCtx, config.Config.MizuResourcesNamespace, mizu.RoleName); err != nil {
|
||||
logger.Log.Errorf(uiUtils.Error, fmt.Sprintf("Error removing Role %s in namespace %s: %v", mizu.RoleName, config.Config.MizuResourcesNamespace, errormessage.FormatError(err)))
|
||||
}
|
||||
|
||||
if err := kubernetesProvider.RemoveRoleBinding(removalCtx, mizu.Config.MizuResourcesNamespace, mizu.RoleBindingName); err != nil {
|
||||
mizu.Log.Errorf(uiUtils.Error, fmt.Sprintf("Error removing RoleBinding %s in namespace %s: %v", mizu.RoleBindingName, mizu.Config.MizuResourcesNamespace, errormessage.FormatError(err)))
|
||||
if err := kubernetesProvider.RemoveRoleBinding(removalCtx, config.Config.MizuResourcesNamespace, mizu.RoleBindingName); err != nil {
|
||||
logger.Log.Errorf(uiUtils.Error, fmt.Sprintf("Error removing RoleBinding %s in namespace %s: %v", mizu.RoleBindingName, config.Config.MizuResourcesNamespace, errormessage.FormatError(err)))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if !mizu.Config.IsNsRestrictedMode() {
|
||||
if !config.Config.IsNsRestrictedMode() {
|
||||
waitUntilNamespaceDeleted(removalCtx, cancel, kubernetesProvider)
|
||||
}
|
||||
}
|
||||
@@ -320,65 +334,44 @@ func waitUntilNamespaceDeleted(ctx context.Context, cancel context.CancelFunc, k
|
||||
waitForFinish(ctx, cancel)
|
||||
}()
|
||||
|
||||
if err := kubernetesProvider.WaitUtilNamespaceDeleted(ctx, mizu.Config.MizuResourcesNamespace); err != nil {
|
||||
if err := kubernetesProvider.WaitUtilNamespaceDeleted(ctx, config.Config.MizuResourcesNamespace); err != nil {
|
||||
switch {
|
||||
case ctx.Err() == context.Canceled:
|
||||
// Do nothing. User interrupted the wait.
|
||||
logger.Log.Debugf("Do nothing. User interrupted the wait")
|
||||
case err == wait.ErrWaitTimeout:
|
||||
mizu.Log.Errorf(uiUtils.Error, fmt.Sprintf("Timeout while removing Namespace %s", mizu.Config.MizuResourcesNamespace))
|
||||
logger.Log.Errorf(uiUtils.Error, fmt.Sprintf("Timeout while removing Namespace %s", config.Config.MizuResourcesNamespace))
|
||||
default:
|
||||
mizu.Log.Errorf(uiUtils.Error, fmt.Sprintf("Error while waiting for Namespace %s to be deleted: %v", mizu.Config.MizuResourcesNamespace, errormessage.FormatError(err)))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func reportTappedPods() {
|
||||
mizuProxiedUrl := kubernetes.GetMizuApiServerProxiedHostAndPath(mizu.Config.Tap.GuiPort)
|
||||
tappedPodsUrl := fmt.Sprintf("http://%s/status/tappedPods", mizuProxiedUrl)
|
||||
|
||||
podInfos := make([]shared.PodInfo, 0)
|
||||
for _, pod := range state.currentlyTappedPods {
|
||||
podInfos = append(podInfos, shared.PodInfo{Name: pod.Name, Namespace: pod.Namespace})
|
||||
}
|
||||
tapStatus := shared.TapStatus{Pods: podInfos}
|
||||
|
||||
if jsonValue, err := json.Marshal(tapStatus); err != nil {
|
||||
mizu.Log.Debugf("[ERROR] failed Marshal the tapped pods %v", err)
|
||||
} else {
|
||||
if response, err := http.Post(tappedPodsUrl, "application/json", bytes.NewBuffer(jsonValue)); err != nil {
|
||||
mizu.Log.Debugf("[ERROR] failed sending to API server the tapped pods %v", err)
|
||||
} else if response.StatusCode != 200 {
|
||||
mizu.Log.Debugf("[ERROR] failed sending to API server the tapped pods, response status code %v", response.StatusCode)
|
||||
} else {
|
||||
mizu.Log.Debugf("Reported to server API about %d taped pods successfully", len(podInfos))
|
||||
logger.Log.Errorf(uiUtils.Error, fmt.Sprintf("Error while waiting for Namespace %s to be deleted: %v", config.Config.MizuResourcesNamespace, errormessage.FormatError(err)))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func watchPodsForTapping(ctx context.Context, kubernetesProvider *kubernetes.Provider, targetNamespaces []string, cancel context.CancelFunc) {
|
||||
added, modified, removed, errorChan := kubernetes.FilteredWatch(ctx, kubernetesProvider, targetNamespaces, mizu.Config.Tap.PodRegex())
|
||||
added, modified, removed, errorChan := kubernetes.FilteredWatch(ctx, kubernetesProvider, targetNamespaces, config.Config.Tap.PodRegex())
|
||||
|
||||
restartTappers := func() {
|
||||
err, changeFound := updateCurrentlyTappedPods(kubernetesProvider, ctx, targetNamespaces)
|
||||
if err != nil {
|
||||
mizu.Log.Errorf(uiUtils.Error, fmt.Sprintf("Failed to update currently tapped pods: %v", err))
|
||||
logger.Log.Errorf(uiUtils.Error, fmt.Sprintf("Failed to update currently tapped pods: %v", err))
|
||||
cancel()
|
||||
}
|
||||
|
||||
if !changeFound {
|
||||
mizu.Log.Debugf("Nothing changed update tappers not needed")
|
||||
logger.Log.Debugf("Nothing changed update tappers not needed")
|
||||
return
|
||||
}
|
||||
|
||||
reportTappedPods()
|
||||
if err := apiserver.Provider.ReportTappedPods(state.currentlyTappedPods); err != nil {
|
||||
logger.Log.Debugf("[Error] failed update tapped pods %v", err)
|
||||
}
|
||||
|
||||
nodeToTappedPodIPMap := getNodeHostToTappedPodIpsMap(state.currentlyTappedPods)
|
||||
if err != nil {
|
||||
mizu.Log.Errorf(uiUtils.Error, fmt.Sprintf("Error building node to ips map: %v", errormessage.FormatError(err)))
|
||||
logger.Log.Errorf(uiUtils.Error, fmt.Sprintf("Error building node to ips map: %v", errormessage.FormatError(err)))
|
||||
cancel()
|
||||
}
|
||||
if err := updateMizuTappers(ctx, kubernetesProvider, nodeToTappedPodIPMap); err != nil {
|
||||
mizu.Log.Errorf(uiUtils.Error, fmt.Sprintf("Error updating daemonset: %v", errormessage.FormatError(err)))
|
||||
logger.Log.Errorf(uiUtils.Error, fmt.Sprintf("Error updating daemonset: %v", errormessage.FormatError(err)))
|
||||
cancel()
|
||||
}
|
||||
}
|
||||
@@ -387,13 +380,13 @@ func watchPodsForTapping(ctx context.Context, kubernetesProvider *kubernetes.Pro
|
||||
for {
|
||||
select {
|
||||
case pod := <-added:
|
||||
mizu.Log.Debugf("Added matching pod %s, ns: %s", pod.Name, pod.Namespace)
|
||||
logger.Log.Debugf("Added matching pod %s, ns: %s", pod.Name, pod.Namespace)
|
||||
restartTappersDebouncer.SetOn()
|
||||
case pod := <-removed:
|
||||
mizu.Log.Debugf("Removed matching pod %s, ns: %s", pod.Name, pod.Namespace)
|
||||
logger.Log.Debugf("Removed matching pod %s, ns: %s", pod.Name, pod.Namespace)
|
||||
restartTappersDebouncer.SetOn()
|
||||
case pod := <-modified:
|
||||
mizu.Log.Debugf("Modified matching pod %s, ns: %s, phase: %s, ip: %s", pod.Name, pod.Namespace, pod.Status.Phase, pod.Status.PodIP)
|
||||
logger.Log.Debugf("Modified matching pod %s, ns: %s, phase: %s, ip: %s", pod.Name, pod.Namespace, pod.Status.Phase, pod.Status.PodIP)
|
||||
// Act only if the modified pod has already obtained an IP address.
|
||||
// After filtering for IPs, on a normal pod restart this includes the following events:
|
||||
// - Pod deletion
|
||||
@@ -405,13 +398,13 @@ func watchPodsForTapping(ctx context.Context, kubernetesProvider *kubernetes.Pro
|
||||
}
|
||||
|
||||
case err := <-errorChan:
|
||||
mizu.Log.Debugf("Watching pods loop, got error %v, stopping `restart tappers debouncer`", err)
|
||||
logger.Log.Debugf("Watching pods loop, got error %v, stopping `restart tappers debouncer`", err)
|
||||
restartTappersDebouncer.Cancel()
|
||||
// TODO: Does this also perform cleanup?
|
||||
cancel()
|
||||
|
||||
case <-ctx.Done():
|
||||
mizu.Log.Debugf("Watching pods loop, context done, stopping `restart tappers debouncer`")
|
||||
logger.Log.Debugf("Watching pods loop, context done, stopping `restart tappers debouncer`")
|
||||
restartTappersDebouncer.Cancel()
|
||||
return
|
||||
}
|
||||
@@ -420,18 +413,18 @@ func watchPodsForTapping(ctx context.Context, kubernetesProvider *kubernetes.Pro
|
||||
|
||||
func updateCurrentlyTappedPods(kubernetesProvider *kubernetes.Provider, ctx context.Context, targetNamespaces []string) (error, bool) {
|
||||
changeFound := false
|
||||
if matchingPods, err := kubernetesProvider.ListAllRunningPodsMatchingRegex(ctx, mizu.Config.Tap.PodRegex(), targetNamespaces); err != nil {
|
||||
if matchingPods, err := kubernetesProvider.ListAllRunningPodsMatchingRegex(ctx, config.Config.Tap.PodRegex(), targetNamespaces); err != nil {
|
||||
return err, false
|
||||
} else {
|
||||
podsToTap := excludeMizuPods(matchingPods)
|
||||
addedPods, removedPods := getPodArrayDiff(state.currentlyTappedPods, podsToTap)
|
||||
for _, addedPod := range addedPods {
|
||||
changeFound = true
|
||||
mizu.Log.Infof(uiUtils.Green, fmt.Sprintf("+%s", addedPod.Name))
|
||||
logger.Log.Infof(uiUtils.Green, fmt.Sprintf("+%s", addedPod.Name))
|
||||
}
|
||||
for _, removedPod := range removedPods {
|
||||
changeFound = true
|
||||
mizu.Log.Infof(uiUtils.Red, fmt.Sprintf("-%s", removedPod.Name))
|
||||
logger.Log.Infof(uiUtils.Red, fmt.Sprintf("-%s", removedPod.Name))
|
||||
}
|
||||
state.currentlyTappedPods = podsToTap
|
||||
}
|
||||
@@ -477,88 +470,75 @@ func getMissingPods(pods1 []core.Pod, pods2 []core.Pod) []core.Pod {
|
||||
return missingPods
|
||||
}
|
||||
|
||||
func createProxyToApiServerPod(ctx context.Context, kubernetesProvider *kubernetes.Provider, cancel context.CancelFunc) {
|
||||
func watchApiServerPod(ctx context.Context, kubernetesProvider *kubernetes.Provider, cancel context.CancelFunc) {
|
||||
podExactRegex := regexp.MustCompile(fmt.Sprintf("^%s$", mizu.ApiServerPodName))
|
||||
added, modified, removed, errorChan := kubernetes.FilteredWatch(ctx, kubernetesProvider, []string{mizu.Config.MizuResourcesNamespace}, podExactRegex)
|
||||
added, modified, removed, errorChan := kubernetes.FilteredWatch(ctx, kubernetesProvider, []string{config.Config.MizuResourcesNamespace}, podExactRegex)
|
||||
isPodReady := false
|
||||
timeAfter := time.After(25 * time.Second)
|
||||
for {
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
mizu.Log.Debugf("Watching API Server pod loop, ctx done")
|
||||
logger.Log.Debugf("Watching API Server pod loop, ctx done")
|
||||
return
|
||||
case <-added:
|
||||
mizu.Log.Debugf("Watching API Server pod loop, added")
|
||||
logger.Log.Debugf("Watching API Server pod loop, added")
|
||||
continue
|
||||
case <-removed:
|
||||
mizu.Log.Infof("%s removed", mizu.ApiServerPodName)
|
||||
logger.Log.Infof("%s removed", mizu.ApiServerPodName)
|
||||
cancel()
|
||||
return
|
||||
case modifiedPod := <-modified:
|
||||
if modifiedPod == nil {
|
||||
mizu.Log.Debugf("Watching API Server pod loop, modifiedPod with nil")
|
||||
logger.Log.Debugf("Watching API Server pod loop, modifiedPod with nil")
|
||||
continue
|
||||
}
|
||||
mizu.Log.Debugf("Watching API Server pod loop, modified: %v", modifiedPod.Status.Phase)
|
||||
logger.Log.Debugf("Watching API Server pod loop, modified: %v", modifiedPod.Status.Phase)
|
||||
if modifiedPod.Status.Phase == core.PodRunning && !isPodReady {
|
||||
isPodReady = true
|
||||
go startProxyReportErrorIfAny(kubernetesProvider, cancel)
|
||||
mizu.Log.Infof("Mizu is available at http://%s\n", kubernetes.GetMizuApiServerProxiedHostAndPath(mizu.Config.Tap.GuiPort))
|
||||
time.Sleep(time.Second * 5) // Waiting to be sure the proxy is ready
|
||||
requestForAnalysis()
|
||||
reportTappedPods()
|
||||
|
||||
url := GetApiServerUrl()
|
||||
if err := apiserver.Provider.InitAndTestConnection(url); err != nil {
|
||||
logger.Log.Errorf(uiUtils.Error, "Couldn't connect to API server, check logs")
|
||||
cancel()
|
||||
break
|
||||
}
|
||||
logger.Log.Infof("Mizu is available at %s\n", url)
|
||||
openBrowser(url)
|
||||
requestForAnalysisIfNeeded()
|
||||
if err := apiserver.Provider.ReportTappedPods(state.currentlyTappedPods); err != nil {
|
||||
logger.Log.Debugf("[Error] failed update tapped pods %v", err)
|
||||
}
|
||||
}
|
||||
case <-timeAfter:
|
||||
if !isPodReady {
|
||||
mizu.Log.Errorf(uiUtils.Error, "Mizu API server was not ready in time")
|
||||
logger.Log.Errorf(uiUtils.Error, "Mizu API server was not ready in time")
|
||||
cancel()
|
||||
}
|
||||
case <-errorChan:
|
||||
mizu.Log.Debugf("[ERROR] Agent creation, watching %v namespace", mizu.Config.MizuResourcesNamespace)
|
||||
logger.Log.Debugf("[ERROR] Agent creation, watching %v namespace", config.Config.MizuResourcesNamespace)
|
||||
cancel()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func startProxyReportErrorIfAny(kubernetesProvider *kubernetes.Provider, cancel context.CancelFunc) {
|
||||
err := kubernetes.StartProxy(kubernetesProvider, mizu.Config.Tap.GuiPort, mizu.Config.MizuResourcesNamespace, mizu.ApiServerPodName)
|
||||
if err != nil {
|
||||
mizu.Log.Errorf(uiUtils.Error, fmt.Sprintf("Error occured while running k8s proxy %v\n"+
|
||||
"Try setting different port by using --%s", errormessage.FormatError(err), configStructs.GuiPortTapName))
|
||||
cancel()
|
||||
}
|
||||
}
|
||||
|
||||
func requestForAnalysis() {
|
||||
if !mizu.Config.Tap.Analysis {
|
||||
func requestForAnalysisIfNeeded() {
|
||||
if !config.Config.Tap.Analysis {
|
||||
return
|
||||
}
|
||||
|
||||
mizuProxiedUrl := kubernetes.GetMizuApiServerProxiedHostAndPath(mizu.Config.Tap.GuiPort)
|
||||
urlPath := fmt.Sprintf("http://%s/api/uploadEntries?dest=%s&interval=%v", mizuProxiedUrl, url.QueryEscape(mizu.Config.Tap.AnalysisDestination), mizu.Config.Tap.SleepIntervalSec)
|
||||
u, parseErr := url.ParseRequestURI(urlPath)
|
||||
if parseErr != nil {
|
||||
mizu.Log.Fatal("Failed parsing the URL (consider changing the analysis dest URL), err: %v", parseErr)
|
||||
}
|
||||
|
||||
mizu.Log.Debugf("Sending get request to %v", u.String())
|
||||
if response, requestErr := http.Get(u.String()); requestErr != nil {
|
||||
mizu.Log.Errorf("Failed to notify agent for analysis, err: %v", requestErr)
|
||||
} else if response.StatusCode != 200 {
|
||||
mizu.Log.Errorf("Failed to notify agent for analysis, status code: %v", response.StatusCode)
|
||||
} else {
|
||||
mizu.Log.Infof(uiUtils.Purple, "Traffic is uploading to UP9 for further analysis")
|
||||
if err := apiserver.Provider.RequestAnalysis(config.Config.Tap.AnalysisDestination, config.Config.Tap.SleepIntervalSec); err != nil {
|
||||
logger.Log.Debugf("[Error] failed requesting for analysis %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func createRBACIfNecessary(ctx context.Context, kubernetesProvider *kubernetes.Provider) (bool, error) {
|
||||
if !mizu.Config.IsNsRestrictedMode() {
|
||||
err := kubernetesProvider.CreateMizuRBAC(ctx, mizu.Config.MizuResourcesNamespace, mizu.ServiceAccountName, mizu.ClusterRoleName, mizu.ClusterRoleBindingName, mizu.RBACVersion)
|
||||
if !config.Config.IsNsRestrictedMode() {
|
||||
err := kubernetesProvider.CreateMizuRBAC(ctx, config.Config.MizuResourcesNamespace, mizu.ServiceAccountName, mizu.ClusterRoleName, mizu.ClusterRoleBindingName, mizu.RBACVersion)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
} else {
|
||||
err := kubernetesProvider.CreateMizuRBACNamespaceRestricted(ctx, mizu.Config.MizuResourcesNamespace, mizu.ServiceAccountName, mizu.RoleName, mizu.RoleBindingName, mizu.RBACVersion)
|
||||
err := kubernetesProvider.CreateMizuRBACNamespaceRestricted(ctx, config.Config.MizuResourcesNamespace, mizu.ServiceAccountName, mizu.RoleName, mizu.RoleBindingName, mizu.RBACVersion)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
@@ -579,24 +559,11 @@ func getNodeHostToTappedPodIpsMap(tappedPods []core.Pod) map[string][]string {
|
||||
return nodeToTappedPodIPMap
|
||||
}
|
||||
|
||||
func waitForFinish(ctx context.Context, cancel context.CancelFunc) {
|
||||
sigChan := make(chan os.Signal, 1)
|
||||
signal.Notify(sigChan, syscall.SIGINT, syscall.SIGTERM, syscall.SIGQUIT)
|
||||
|
||||
// block until ctx cancel is called or termination signal is received
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
break
|
||||
case <-sigChan:
|
||||
cancel()
|
||||
}
|
||||
}
|
||||
|
||||
func getNamespaces(kubernetesProvider *kubernetes.Provider) []string {
|
||||
if mizu.Config.Tap.AllNamespaces {
|
||||
if config.Config.Tap.AllNamespaces {
|
||||
return []string{mizu.K8sAllNamespaces}
|
||||
} else if len(mizu.Config.Tap.Namespaces) > 0 {
|
||||
return mizu.Config.Tap.Namespaces
|
||||
} else if len(config.Config.Tap.Namespaces) > 0 {
|
||||
return mizu.Unique(config.Config.Tap.Namespaces)
|
||||
} else {
|
||||
return []string{kubernetesProvider.CurrentNamespace()}
|
||||
}
|
||||
|
||||
@@ -1,27 +1,31 @@
|
||||
package cmd
|
||||
|
||||
import (
|
||||
"github.com/up9inc/mizu/cli/config"
|
||||
"github.com/up9inc/mizu/cli/config/configStructs"
|
||||
"github.com/up9inc/mizu/cli/logger"
|
||||
"github.com/up9inc/mizu/cli/telemetry"
|
||||
"strconv"
|
||||
"time"
|
||||
|
||||
"github.com/creasty/defaults"
|
||||
"github.com/spf13/cobra"
|
||||
"github.com/up9inc/mizu/cli/mizu"
|
||||
"github.com/up9inc/mizu/cli/mizu/configStructs"
|
||||
)
|
||||
|
||||
var versionCmd = &cobra.Command{
|
||||
Use: "version",
|
||||
Short: "Print version info",
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
go mizu.ReportRun("version", mizu.Config.Version)
|
||||
if mizu.Config.Version.DebugInfo {
|
||||
go telemetry.ReportRun("version", config.Config.Version)
|
||||
|
||||
if config.Config.Version.DebugInfo {
|
||||
timeStampInt, _ := strconv.ParseInt(mizu.BuildTimestamp, 10, 0)
|
||||
mizu.Log.Infof("Version: %s \nBranch: %s (%s)", mizu.SemVer, mizu.Branch, mizu.GitCommitHash)
|
||||
mizu.Log.Infof("Build Time: %s (%s)", mizu.BuildTimestamp, time.Unix(timeStampInt, 0))
|
||||
logger.Log.Infof("Version: %s \nBranch: %s (%s)", mizu.SemVer, mizu.Branch, mizu.GitCommitHash)
|
||||
logger.Log.Infof("Build Time: %s (%s)", mizu.BuildTimestamp, time.Unix(timeStampInt, 0))
|
||||
|
||||
} else {
|
||||
mizu.Log.Infof("Version: %s (%s)", mizu.SemVer, mizu.Branch)
|
||||
logger.Log.Infof("Version: %s (%s)", mizu.SemVer, mizu.Branch)
|
||||
}
|
||||
return nil
|
||||
},
|
||||
|
||||
@@ -3,15 +3,16 @@ package cmd
|
||||
import (
|
||||
"github.com/creasty/defaults"
|
||||
"github.com/spf13/cobra"
|
||||
"github.com/up9inc/mizu/cli/mizu"
|
||||
"github.com/up9inc/mizu/cli/mizu/configStructs"
|
||||
"github.com/up9inc/mizu/cli/config"
|
||||
"github.com/up9inc/mizu/cli/config/configStructs"
|
||||
"github.com/up9inc/mizu/cli/telemetry"
|
||||
)
|
||||
|
||||
var viewCmd = &cobra.Command{
|
||||
Use: "view",
|
||||
Short: "Open GUI in browser",
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
go mizu.ReportRun("view", mizu.Config.View)
|
||||
go telemetry.ReportRun("view", config.Config.View)
|
||||
runMizuView()
|
||||
return nil
|
||||
},
|
||||
@@ -24,5 +25,4 @@ func init() {
|
||||
defaults.Set(&defaultViewConfig)
|
||||
|
||||
viewCmd.Flags().Uint16P(configStructs.GuiPortViewName, "p", defaultViewConfig.GuiPort, "Provide a custom port for the web interface webserver")
|
||||
viewCmd.Flags().StringP(configStructs.KubeConfigPathViewName, "k", defaultViewConfig.KubeConfigPath, "Path to kube-config file")
|
||||
}
|
||||
|
||||
@@ -3,46 +3,58 @@ package cmd
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"github.com/up9inc/mizu/cli/kubernetes"
|
||||
"github.com/up9inc/mizu/cli/mizu"
|
||||
"net/http"
|
||||
|
||||
"github.com/up9inc/mizu/cli/apiserver"
|
||||
"github.com/up9inc/mizu/cli/config"
|
||||
"github.com/up9inc/mizu/cli/kubernetes"
|
||||
"github.com/up9inc/mizu/cli/logger"
|
||||
"github.com/up9inc/mizu/cli/mizu"
|
||||
"github.com/up9inc/mizu/cli/mizu/version"
|
||||
"github.com/up9inc/mizu/cli/uiUtils"
|
||||
)
|
||||
|
||||
func runMizuView() {
|
||||
kubernetesProvider, err := kubernetes.NewProvider(mizu.Config.View.KubeConfigPath)
|
||||
kubernetesProvider, err := kubernetes.NewProvider(config.Config.KubeConfigPath())
|
||||
if err != nil {
|
||||
mizu.Log.Error(err)
|
||||
logger.Log.Error(err)
|
||||
return
|
||||
}
|
||||
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
defer cancel()
|
||||
|
||||
exists, err := kubernetesProvider.DoesServicesExist(ctx, mizu.Config.MizuResourcesNamespace, mizu.ApiServerPodName)
|
||||
exists, err := kubernetesProvider.DoesServicesExist(ctx, config.Config.MizuResourcesNamespace, mizu.ApiServerPodName)
|
||||
if err != nil {
|
||||
mizu.Log.Errorf("Failed to found mizu service %v", err)
|
||||
logger.Log.Errorf("Failed to found mizu service %v", err)
|
||||
cancel()
|
||||
return
|
||||
}
|
||||
if !exists {
|
||||
mizu.Log.Infof("%s service not found, you should run `mizu tap` command first", mizu.ApiServerPodName)
|
||||
logger.Log.Infof("%s service not found, you should run `mizu tap` command first", mizu.ApiServerPodName)
|
||||
cancel()
|
||||
return
|
||||
}
|
||||
|
||||
mizuProxiedUrl := kubernetes.GetMizuApiServerProxiedHostAndPath(mizu.Config.View.GuiPort)
|
||||
_, err = http.Get(fmt.Sprintf("http://%s/", mizuProxiedUrl))
|
||||
if err == nil {
|
||||
mizu.Log.Infof("Found a running service %s and open port %d", mizu.ApiServerPodName, mizu.Config.View.GuiPort)
|
||||
url := GetApiServerUrl()
|
||||
|
||||
response, err := http.Get(fmt.Sprintf("%s/", url))
|
||||
if err == nil && response.StatusCode == 200 {
|
||||
logger.Log.Infof("Found a running service %s and open port %d", mizu.ApiServerPodName, config.Config.View.GuiPort)
|
||||
return
|
||||
}
|
||||
mizu.Log.Debugf("Found service %s, creating k8s proxy", mizu.ApiServerPodName)
|
||||
|
||||
logger.Log.Infof("Establishing connection to k8s cluster...")
|
||||
go startProxyReportErrorIfAny(kubernetesProvider, cancel)
|
||||
|
||||
mizu.Log.Infof("Mizu is available at http://%s\n", kubernetes.GetMizuApiServerProxiedHostAndPath(mizu.Config.View.GuiPort))
|
||||
if isCompatible, err := mizu.CheckVersionCompatibility(mizu.Config.View.GuiPort); err != nil {
|
||||
mizu.Log.Errorf("Failed to check versions compatibility %v", err)
|
||||
if err := apiserver.Provider.InitAndTestConnection(GetApiServerUrl()); err != nil {
|
||||
logger.Log.Errorf(uiUtils.Error, "Couldn't connect to API server, check logs")
|
||||
return
|
||||
}
|
||||
|
||||
logger.Log.Infof("Mizu is available at %s\n", url)
|
||||
openBrowser(url)
|
||||
if isCompatible, err := version.CheckVersionCompatibility(); err != nil {
|
||||
logger.Log.Errorf("Failed to check versions compatibility %v", err)
|
||||
cancel()
|
||||
return
|
||||
} else if !isCompatible {
|
||||
|
||||
329
cli/config/config.go
Normal file
329
cli/config/config.go
Normal file
@@ -0,0 +1,329 @@
|
||||
package config
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"github.com/up9inc/mizu/cli/logger"
|
||||
"github.com/up9inc/mizu/cli/mizu"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"reflect"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/creasty/defaults"
|
||||
"github.com/spf13/cobra"
|
||||
"github.com/spf13/pflag"
|
||||
"github.com/up9inc/mizu/cli/uiUtils"
|
||||
"gopkg.in/yaml.v3"
|
||||
)
|
||||
|
||||
const (
|
||||
Separator = "="
|
||||
SetCommandName = "set"
|
||||
FieldNameTag = "yaml"
|
||||
ReadonlyTag = "readonly"
|
||||
)
|
||||
|
||||
var (
|
||||
Config = ConfigStruct{}
|
||||
cmdName string
|
||||
)
|
||||
|
||||
func InitConfig(cmd *cobra.Command) error {
|
||||
cmdName = cmd.Name()
|
||||
|
||||
if err := defaults.Set(&Config); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
configFilePathFlag := cmd.Flags().Lookup(ConfigFilePathCommandName)
|
||||
configFilePath := configFilePathFlag.Value.String()
|
||||
if err := mergeConfigFile(configFilePath); err != nil {
|
||||
if configFilePathFlag.Changed || !os.IsNotExist(err) {
|
||||
return fmt.Errorf("invalid config, %w\n"+
|
||||
"you can regenerate the file by removing it (%v) and using `mizu config -r`", err, configFilePath)
|
||||
}
|
||||
}
|
||||
|
||||
cmd.Flags().Visit(initFlag)
|
||||
|
||||
finalConfigPrettified, _ := uiUtils.PrettyJson(Config)
|
||||
logger.Log.Debugf("Init config finished\n Final config: %v", finalConfigPrettified)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func GetConfigWithDefaults() (string, error) {
|
||||
defaultConf := ConfigStruct{}
|
||||
if err := defaults.Set(&defaultConf); err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
configElem := reflect.ValueOf(&defaultConf).Elem()
|
||||
setZeroForReadonlyFields(configElem)
|
||||
|
||||
return uiUtils.PrettyYaml(defaultConf)
|
||||
}
|
||||
|
||||
func mergeConfigFile(configFilePath string) error {
|
||||
reader, openErr := os.Open(configFilePath)
|
||||
if openErr != nil {
|
||||
return openErr
|
||||
}
|
||||
|
||||
buf, readErr := ioutil.ReadAll(reader)
|
||||
if readErr != nil {
|
||||
return readErr
|
||||
}
|
||||
|
||||
if err := yaml.Unmarshal(buf, &Config); err != nil {
|
||||
return err
|
||||
}
|
||||
logger.Log.Debugf("Found config file, merged to default options")
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func initFlag(f *pflag.Flag) {
|
||||
configElemValue := reflect.ValueOf(&Config).Elem()
|
||||
|
||||
var flagPath []string
|
||||
if mizu.Contains([]string{ConfigFilePathCommandName}, f.Name) {
|
||||
flagPath = []string{f.Name}
|
||||
} else {
|
||||
flagPath = []string{cmdName, f.Name}
|
||||
}
|
||||
|
||||
sliceValue, isSliceValue := f.Value.(pflag.SliceValue)
|
||||
if !isSliceValue {
|
||||
if err := mergeFlagValue(configElemValue, flagPath, strings.Join(flagPath, "."), f.Value.String()); err != nil {
|
||||
logger.Log.Warningf(uiUtils.Warning, err)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
if f.Name == SetCommandName {
|
||||
if err := mergeSetFlag(configElemValue, sliceValue.GetSlice()); err != nil {
|
||||
logger.Log.Warningf(uiUtils.Warning, err)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
if err := mergeFlagValues(configElemValue, flagPath, strings.Join(flagPath, "."), sliceValue.GetSlice()); err != nil {
|
||||
logger.Log.Warningf(uiUtils.Warning, err)
|
||||
}
|
||||
}
|
||||
|
||||
func mergeSetFlag(configElemValue reflect.Value, setValues []string) error {
|
||||
var setErrors []string
|
||||
setMap := map[string][]string{}
|
||||
|
||||
for _, setValue := range setValues {
|
||||
if !strings.Contains(setValue, Separator) {
|
||||
setErrors = append(setErrors, fmt.Sprintf("Ignoring set argument %s (set argument format: <flag name>=<flag value>)", setValue))
|
||||
continue
|
||||
}
|
||||
|
||||
split := strings.SplitN(setValue, Separator, 2)
|
||||
argumentKey, argumentValue := split[0], split[1]
|
||||
|
||||
setMap[argumentKey] = append(setMap[argumentKey], argumentValue)
|
||||
}
|
||||
|
||||
for argumentKey, argumentValues := range setMap {
|
||||
flagPath := strings.Split(argumentKey, ".")
|
||||
|
||||
if len(argumentValues) > 1 {
|
||||
if err := mergeFlagValues(configElemValue, flagPath, argumentKey, argumentValues); err != nil {
|
||||
setErrors = append(setErrors, fmt.Sprintf("%v", err))
|
||||
}
|
||||
} else {
|
||||
if err := mergeFlagValue(configElemValue, flagPath, argumentKey, argumentValues[0]); err != nil {
|
||||
setErrors = append(setErrors, fmt.Sprintf("%v", err))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if len(setErrors) > 0 {
|
||||
return fmt.Errorf(strings.Join(setErrors, "\n"))
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func mergeFlagValue(configElemValue reflect.Value, flagPath []string, fullFlagName string, flagValue string) error {
|
||||
mergeFunction := func(flagName string, currentFieldStruct reflect.StructField, currentFieldElemValue reflect.Value, currentElemValue reflect.Value) error {
|
||||
currentFieldKind := currentFieldStruct.Type.Kind()
|
||||
|
||||
if currentFieldKind == reflect.Slice {
|
||||
return mergeFlagValues(currentElemValue, []string{flagName}, fullFlagName, []string{flagValue})
|
||||
}
|
||||
|
||||
parsedValue, err := getParsedValue(currentFieldKind, flagValue)
|
||||
if err != nil {
|
||||
return fmt.Errorf("invalid value %s for flag name %s, expected %s", flagValue, flagName, currentFieldKind)
|
||||
}
|
||||
|
||||
currentFieldElemValue.Set(parsedValue)
|
||||
return nil
|
||||
}
|
||||
|
||||
return mergeFlag(configElemValue, flagPath, fullFlagName, mergeFunction)
|
||||
}
|
||||
|
||||
func mergeFlagValues(configElemValue reflect.Value, flagPath []string, fullFlagName string, flagValues []string) error {
|
||||
mergeFunction := func(flagName string, currentFieldStruct reflect.StructField, currentFieldElemValue reflect.Value, currentElemValue reflect.Value) error {
|
||||
currentFieldKind := currentFieldStruct.Type.Kind()
|
||||
|
||||
if currentFieldKind != reflect.Slice {
|
||||
return fmt.Errorf("invalid values %s for flag name %s, expected %s", strings.Join(flagValues, ","), flagName, currentFieldKind)
|
||||
}
|
||||
|
||||
flagValueKind := currentFieldStruct.Type.Elem().Kind()
|
||||
|
||||
parsedValues := reflect.MakeSlice(reflect.SliceOf(currentFieldStruct.Type.Elem()), 0, 0)
|
||||
for _, flagValue := range flagValues {
|
||||
parsedValue, err := getParsedValue(flagValueKind, flagValue)
|
||||
if err != nil {
|
||||
return fmt.Errorf("invalid value %s for flag name %s, expected %s", flagValue, flagName, flagValueKind)
|
||||
}
|
||||
|
||||
parsedValues = reflect.Append(parsedValues, parsedValue)
|
||||
}
|
||||
|
||||
currentFieldElemValue.Set(parsedValues)
|
||||
return nil
|
||||
}
|
||||
|
||||
return mergeFlag(configElemValue, flagPath, fullFlagName, mergeFunction)
|
||||
}
|
||||
|
||||
func mergeFlag(currentElemValue reflect.Value, currentFlagPath []string, fullFlagName string, mergeFunction func(flagName string, currentFieldStruct reflect.StructField, currentFieldElemValue reflect.Value, currentElemValue reflect.Value) error) error {
|
||||
if len(currentFlagPath) == 0 {
|
||||
return fmt.Errorf("flag \"%s\" not found", fullFlagName)
|
||||
}
|
||||
|
||||
for i := 0; i < currentElemValue.NumField(); i++ {
|
||||
currentFieldStruct := currentElemValue.Type().Field(i)
|
||||
currentFieldElemValue := currentElemValue.FieldByName(currentFieldStruct.Name)
|
||||
|
||||
if currentFieldStruct.Type.Kind() == reflect.Struct && getFieldNameByTag(currentFieldStruct) == currentFlagPath[0] {
|
||||
return mergeFlag(currentFieldElemValue, currentFlagPath[1:], fullFlagName, mergeFunction)
|
||||
}
|
||||
|
||||
if len(currentFlagPath) > 1 || getFieldNameByTag(currentFieldStruct) != currentFlagPath[0] {
|
||||
continue
|
||||
}
|
||||
|
||||
return mergeFunction(currentFlagPath[0], currentFieldStruct, currentFieldElemValue, currentElemValue)
|
||||
}
|
||||
|
||||
return fmt.Errorf("flag \"%s\" not found", fullFlagName)
|
||||
}
|
||||
|
||||
func getFieldNameByTag(field reflect.StructField) string {
|
||||
return strings.Split(field.Tag.Get(FieldNameTag), ",")[0]
|
||||
}
|
||||
|
||||
func getParsedValue(kind reflect.Kind, value string) (reflect.Value, error) {
|
||||
switch kind {
|
||||
case reflect.String:
|
||||
return reflect.ValueOf(value), nil
|
||||
case reflect.Bool:
|
||||
boolArgumentValue, err := strconv.ParseBool(value)
|
||||
if err != nil {
|
||||
break
|
||||
}
|
||||
|
||||
return reflect.ValueOf(boolArgumentValue), nil
|
||||
case reflect.Int:
|
||||
intArgumentValue, err := strconv.ParseInt(value, 10, 64)
|
||||
if err != nil {
|
||||
break
|
||||
}
|
||||
|
||||
return reflect.ValueOf(int(intArgumentValue)), nil
|
||||
case reflect.Int8:
|
||||
intArgumentValue, err := strconv.ParseInt(value, 10, 8)
|
||||
if err != nil {
|
||||
break
|
||||
}
|
||||
|
||||
return reflect.ValueOf(int8(intArgumentValue)), nil
|
||||
case reflect.Int16:
|
||||
intArgumentValue, err := strconv.ParseInt(value, 10, 16)
|
||||
if err != nil {
|
||||
break
|
||||
}
|
||||
|
||||
return reflect.ValueOf(int16(intArgumentValue)), nil
|
||||
case reflect.Int32:
|
||||
intArgumentValue, err := strconv.ParseInt(value, 10, 32)
|
||||
if err != nil {
|
||||
break
|
||||
}
|
||||
|
||||
return reflect.ValueOf(int32(intArgumentValue)), nil
|
||||
case reflect.Int64:
|
||||
intArgumentValue, err := strconv.ParseInt(value, 10, 64)
|
||||
if err != nil {
|
||||
break
|
||||
}
|
||||
|
||||
return reflect.ValueOf(intArgumentValue), nil
|
||||
case reflect.Uint:
|
||||
uintArgumentValue, err := strconv.ParseUint(value, 10, 64)
|
||||
if err != nil {
|
||||
break
|
||||
}
|
||||
|
||||
return reflect.ValueOf(uint(uintArgumentValue)), nil
|
||||
case reflect.Uint8:
|
||||
uintArgumentValue, err := strconv.ParseUint(value, 10, 8)
|
||||
if err != nil {
|
||||
break
|
||||
}
|
||||
|
||||
return reflect.ValueOf(uint8(uintArgumentValue)), nil
|
||||
case reflect.Uint16:
|
||||
uintArgumentValue, err := strconv.ParseUint(value, 10, 16)
|
||||
if err != nil {
|
||||
break
|
||||
}
|
||||
|
||||
return reflect.ValueOf(uint16(uintArgumentValue)), nil
|
||||
case reflect.Uint32:
|
||||
uintArgumentValue, err := strconv.ParseUint(value, 10, 32)
|
||||
if err != nil {
|
||||
break
|
||||
}
|
||||
|
||||
return reflect.ValueOf(uint32(uintArgumentValue)), nil
|
||||
case reflect.Uint64:
|
||||
uintArgumentValue, err := strconv.ParseUint(value, 10, 64)
|
||||
if err != nil {
|
||||
break
|
||||
}
|
||||
|
||||
return reflect.ValueOf(uintArgumentValue), nil
|
||||
}
|
||||
|
||||
return reflect.ValueOf(nil), errors.New("value to parse does not match type")
|
||||
}
|
||||
|
||||
func setZeroForReadonlyFields(currentElem reflect.Value) {
|
||||
for i := 0; i < currentElem.NumField(); i++ {
|
||||
currentField := currentElem.Type().Field(i)
|
||||
currentFieldByName := currentElem.FieldByName(currentField.Name)
|
||||
|
||||
if currentField.Type.Kind() == reflect.Struct {
|
||||
setZeroForReadonlyFields(currentFieldByName)
|
||||
continue
|
||||
}
|
||||
|
||||
if _, ok := currentField.Tag.Lookup(ReadonlyTag); ok {
|
||||
currentFieldByName.Set(reflect.Zero(currentField.Type))
|
||||
}
|
||||
}
|
||||
}
|
||||
60
cli/config/configStruct.go
Normal file
60
cli/config/configStruct.go
Normal file
@@ -0,0 +1,60 @@
|
||||
package config
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/up9inc/mizu/cli/config/configStructs"
|
||||
"github.com/up9inc/mizu/cli/mizu"
|
||||
v1 "k8s.io/api/core/v1"
|
||||
"k8s.io/client-go/util/homedir"
|
||||
"os"
|
||||
"path"
|
||||
"path/filepath"
|
||||
)
|
||||
|
||||
const (
|
||||
MizuResourcesNamespaceConfigName = "mizu-resources-namespace"
|
||||
ConfigFilePathCommandName = "config-path"
|
||||
)
|
||||
|
||||
type ConfigStruct struct {
|
||||
Tap configStructs.TapConfig `yaml:"tap"`
|
||||
Fetch configStructs.FetchConfig `yaml:"fetch"`
|
||||
Version configStructs.VersionConfig `yaml:"version"`
|
||||
View configStructs.ViewConfig `yaml:"view"`
|
||||
Logs configStructs.LogsConfig `yaml:"logs"`
|
||||
Config configStructs.ConfigConfig `yaml:"config,omitempty"`
|
||||
AgentImage string `yaml:"agent-image,omitempty" readonly:""`
|
||||
ImagePullPolicyStr string `yaml:"image-pull-policy" default:"Always"`
|
||||
MizuResourcesNamespace string `yaml:"mizu-resources-namespace" default:"mizu"`
|
||||
Telemetry bool `yaml:"telemetry" default:"true"`
|
||||
DumpLogs bool `yaml:"dump-logs" default:"false"`
|
||||
KubeConfigPathStr string `yaml:"kube-config-path"`
|
||||
ConfigFilePath string `yaml:"config-path,omitempty" readonly:""`
|
||||
}
|
||||
|
||||
func (config *ConfigStruct) SetDefaults() {
|
||||
config.AgentImage = fmt.Sprintf("gcr.io/up9-docker-hub/mizu/%s:%s", mizu.Branch, mizu.SemVer)
|
||||
config.ConfigFilePath = path.Join(mizu.GetMizuFolderPath(), "config.yaml")
|
||||
}
|
||||
|
||||
func (config *ConfigStruct) ImagePullPolicy() v1.PullPolicy {
|
||||
return v1.PullPolicy(config.ImagePullPolicyStr)
|
||||
}
|
||||
|
||||
func (config *ConfigStruct) IsNsRestrictedMode() bool {
|
||||
return config.MizuResourcesNamespace != "mizu" // Notice "mizu" string must match the default MizuResourcesNamespace
|
||||
}
|
||||
|
||||
func (config *ConfigStruct) KubeConfigPath() string {
|
||||
if config.KubeConfigPathStr != "" {
|
||||
return config.KubeConfigPathStr
|
||||
}
|
||||
|
||||
envKubeConfigPath := os.Getenv("KUBECONFIG")
|
||||
if envKubeConfigPath != "" {
|
||||
return envKubeConfigPath
|
||||
}
|
||||
|
||||
home := homedir.HomeDir()
|
||||
return filepath.Join(home, ".kube", "config")
|
||||
}
|
||||
9
cli/config/configStructs/configConfig.go
Normal file
9
cli/config/configStructs/configConfig.go
Normal file
@@ -0,0 +1,9 @@
|
||||
package configStructs
|
||||
|
||||
const (
|
||||
RegenerateConfigName = "regenerate"
|
||||
)
|
||||
|
||||
type ConfigConfig struct {
|
||||
Regenerate bool `yaml:"regenerate,omitempty" default:"false" readonly:""`
|
||||
}
|
||||
35
cli/config/configStructs/logsConfig.go
Normal file
35
cli/config/configStructs/logsConfig.go
Normal file
@@ -0,0 +1,35 @@
|
||||
package configStructs
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"path"
|
||||
)
|
||||
|
||||
const (
|
||||
FileLogsName = "file"
|
||||
)
|
||||
|
||||
type LogsConfig struct {
|
||||
FileStr string `yaml:"file"`
|
||||
}
|
||||
|
||||
func (config *LogsConfig) Validate() error {
|
||||
if config.FileStr == "" {
|
||||
_, err := os.Getwd()
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to get PWD, %v (try using `mizu logs -f <full path dest zip file>)`", err)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (config *LogsConfig) FilePath() string {
|
||||
if config.FileStr == "" {
|
||||
pwd, _ := os.Getwd()
|
||||
return path.Join(pwd, "mizu_logs.zip")
|
||||
}
|
||||
|
||||
return config.FileStr
|
||||
}
|
||||
@@ -10,15 +10,12 @@ import (
|
||||
)
|
||||
|
||||
const (
|
||||
AnalysisDestinationTapName = "dest"
|
||||
SleepIntervalSecTapName = "upload-interval"
|
||||
GuiPortTapName = "gui-port"
|
||||
NamespacesTapName = "namespaces"
|
||||
AnalysisTapName = "analysis"
|
||||
AllNamespacesTapName = "all-namespaces"
|
||||
PlainTextFilterRegexesTapName = "regex-masking"
|
||||
DisableRedactionTapName = "no-redact"
|
||||
IgnoredUserAgentsTapName = "ignored-user-agents"
|
||||
HumanMaxEntriesDBSizeTapName = "max-entries-db-size"
|
||||
DirectionTapName = "direction"
|
||||
DryRunTapName = "dry-run"
|
||||
@@ -26,20 +23,29 @@ const (
|
||||
)
|
||||
|
||||
type TapConfig struct {
|
||||
AnalysisDestination string `yaml:"dest" default:"up9.app"`
|
||||
SleepIntervalSec int `yaml:"upload-interval" default:"10"`
|
||||
PodRegexStr string `yaml:"regex" default:".*"`
|
||||
GuiPort uint16 `yaml:"gui-port" default:"8899"`
|
||||
Namespaces []string `yaml:"namespaces"`
|
||||
Analysis bool `yaml:"analysis" default:"false"`
|
||||
AllNamespaces bool `yaml:"all-namespaces" default:"false"`
|
||||
PlainTextFilterRegexes []string `yaml:"regex-masking"`
|
||||
HealthChecksUserAgentHeaders []string `yaml:"ignored-user-agents" default:"[]"`
|
||||
DisableRedaction bool `yaml:"no-redact" default:"false"`
|
||||
HumanMaxEntriesDBSize string `yaml:"max-entries-db-size" default:"200MB"`
|
||||
Direction string `yaml:"direction" default:"in"`
|
||||
DryRun bool `yaml:"dry-run" default:"false"`
|
||||
EnforcePolicyFile string `yaml:"test-rules"`
|
||||
AnalysisDestination string `yaml:"dest" default:"up9.app"`
|
||||
SleepIntervalSec int `yaml:"upload-interval" default:"10"`
|
||||
PodRegexStr string `yaml:"regex" default:".*"`
|
||||
GuiPort uint16 `yaml:"gui-port" default:"8899"`
|
||||
Namespaces []string `yaml:"namespaces"`
|
||||
Analysis bool `yaml:"analysis" default:"false"`
|
||||
AllNamespaces bool `yaml:"all-namespaces" default:"false"`
|
||||
PlainTextFilterRegexes []string `yaml:"regex-masking"`
|
||||
HealthChecksUserAgentHeaders []string `yaml:"ignored-user-agents"`
|
||||
DisableRedaction bool `yaml:"no-redact" default:"false"`
|
||||
HumanMaxEntriesDBSize string `yaml:"max-entries-db-size" default:"200MB"`
|
||||
Direction string `yaml:"direction" default:"in"`
|
||||
DryRun bool `yaml:"dry-run" default:"false"`
|
||||
EnforcePolicyFile string `yaml:"test-rules"`
|
||||
ApiServerResources Resources `yaml:"api-server-resources"`
|
||||
TapperResources Resources `yaml:"tapper-resources"`
|
||||
}
|
||||
|
||||
type Resources struct {
|
||||
CpuLimit string `yaml:"cpu-limit" default:"750m"`
|
||||
MemoryLimit string `yaml:"memory-limit" default:"1Gi"`
|
||||
CpuRequests string `yaml:"cpu-requests" default:"50m"`
|
||||
MemoryRequests string `yaml:"memory-requests" default:"50Mi"`
|
||||
}
|
||||
|
||||
func (config *TapConfig) PodRegex() *regexp.Regexp {
|
||||
@@ -47,15 +53,6 @@ func (config *TapConfig) PodRegex() *regexp.Regexp {
|
||||
return podRegex
|
||||
}
|
||||
|
||||
func (config *TapConfig) TapOutgoing() bool {
|
||||
directionLowerCase := strings.ToLower(config.Direction)
|
||||
if directionLowerCase == "any" {
|
||||
return true
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
func (config *TapConfig) MaxEntriesDBSizeBytes() int64 {
|
||||
maxEntriesDBSizeBytes, _ := units.HumanReadableToBytes(config.HumanMaxEntriesDBSize)
|
||||
return maxEntriesDBSizeBytes
|
||||
9
cli/config/configStructs/viewConfig.go
Normal file
9
cli/config/configStructs/viewConfig.go
Normal file
@@ -0,0 +1,9 @@
|
||||
package configStructs
|
||||
|
||||
const (
|
||||
GuiPortViewName = "gui-port"
|
||||
)
|
||||
|
||||
type ViewConfig struct {
|
||||
GuiPort uint16 `yaml:"gui-port" default:"8899"`
|
||||
}
|
||||
385
cli/config/config_internal_test.go
Normal file
385
cli/config/config_internal_test.go
Normal file
@@ -0,0 +1,385 @@
|
||||
package config
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"reflect"
|
||||
"testing"
|
||||
)
|
||||
|
||||
type ConfigMock struct {
|
||||
SectionMock SectionMock `yaml:"section"`
|
||||
Test string `yaml:"test"`
|
||||
StringField string `yaml:"string-field"`
|
||||
IntField int `yaml:"int-field"`
|
||||
BoolField bool `yaml:"bool-field"`
|
||||
UintField uint `yaml:"uint-field"`
|
||||
StringSliceField []string `yaml:"string-slice-field"`
|
||||
IntSliceField []int `yaml:"int-slice-field"`
|
||||
BoolSliceField []bool `yaml:"bool-slice-field"`
|
||||
UintSliceField []uint `yaml:"uint-slice-field"`
|
||||
}
|
||||
|
||||
type SectionMock struct {
|
||||
Test string `yaml:"test"`
|
||||
}
|
||||
|
||||
type FieldSetValues struct {
|
||||
SetValues []string
|
||||
FieldName string
|
||||
FieldValue interface{}
|
||||
}
|
||||
|
||||
func TestMergeSetFlagNoSeparator(t *testing.T) {
|
||||
tests := []struct {
|
||||
Name string
|
||||
SetValues []string
|
||||
}{
|
||||
{Name: "empty value", SetValues: []string{""}},
|
||||
{Name: "single char", SetValues: []string{"t"}},
|
||||
{Name: "combine empty value and single char", SetValues: []string{"", "t"}},
|
||||
{Name: "two values without separator", SetValues: []string{"test", "test:true"}},
|
||||
{Name: "four values without separator", SetValues: []string{"test", "test:true", "testing!", "true"}},
|
||||
}
|
||||
|
||||
for _, test := range tests {
|
||||
t.Run(test.Name, func(t *testing.T) {
|
||||
configMock := ConfigMock{}
|
||||
configMockElemValue := reflect.ValueOf(&configMock).Elem()
|
||||
|
||||
err := mergeSetFlag(configMockElemValue, test.SetValues)
|
||||
|
||||
if err == nil {
|
||||
t.Errorf("unexpected unhandled error - SetValues: %v", test.SetValues)
|
||||
return
|
||||
}
|
||||
|
||||
for i := 0; i < configMockElemValue.NumField(); i++ {
|
||||
currentField := configMockElemValue.Type().Field(i)
|
||||
currentFieldByName := configMockElemValue.FieldByName(currentField.Name)
|
||||
|
||||
if !currentFieldByName.IsZero() {
|
||||
t.Errorf("unexpected value with not default value - SetValues: %v", test.SetValues)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestMergeSetFlagInvalidFlagName(t *testing.T) {
|
||||
tests := []struct {
|
||||
Name string
|
||||
SetValues []string
|
||||
}{
|
||||
{Name: "invalid flag name", SetValues: []string{"invalid_flag=true"}},
|
||||
{Name: "invalid flag name inside section struct", SetValues: []string{"section.invalid_flag=test"}},
|
||||
{Name: "flag name is a struct", SetValues: []string{"section=test"}},
|
||||
{Name: "empty flag name", SetValues: []string{"=true"}},
|
||||
{Name: "four tests combined", SetValues: []string{"invalid_flag=true", "config.invalid_flag=test", "section=test", "=true"}},
|
||||
}
|
||||
|
||||
for _, test := range tests {
|
||||
t.Run(test.Name, func(t *testing.T) {
|
||||
configMock := ConfigMock{}
|
||||
configMockElemValue := reflect.ValueOf(&configMock).Elem()
|
||||
|
||||
err := mergeSetFlag(configMockElemValue, test.SetValues)
|
||||
|
||||
if err == nil {
|
||||
t.Errorf("unexpected unhandled error - SetValues: %v", test.SetValues)
|
||||
return
|
||||
}
|
||||
|
||||
for i := 0; i < configMockElemValue.NumField(); i++ {
|
||||
currentField := configMockElemValue.Type().Field(i)
|
||||
currentFieldByName := configMockElemValue.FieldByName(currentField.Name)
|
||||
|
||||
if !currentFieldByName.IsZero() {
|
||||
t.Errorf("unexpected case - SetValues: %v", test.SetValues)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestMergeSetFlagInvalidFlagValue(t *testing.T) {
|
||||
tests := []struct {
|
||||
Name string
|
||||
SetValues []string
|
||||
}{
|
||||
{Name: "bool value to int field", SetValues: []string{"int-field=true"}},
|
||||
{Name: "int value to bool field", SetValues: []string{"bool-field:5"}},
|
||||
{Name: "int value to uint field", SetValues: []string{"uint-field=-1"}},
|
||||
{Name: "bool value to int slice field", SetValues: []string{"int-slice-field=true"}},
|
||||
{Name: "int value to bool slice field", SetValues: []string{"bool-slice-field=5"}},
|
||||
{Name: "int value to uint slice field", SetValues: []string{"uint-slice-field=-1"}},
|
||||
{Name: "int slice value to int field", SetValues: []string{"int-field=6", "int-field=66"}},
|
||||
}
|
||||
|
||||
for _, test := range tests {
|
||||
t.Run(test.Name, func(t *testing.T) {
|
||||
configMock := ConfigMock{}
|
||||
configMockElemValue := reflect.ValueOf(&configMock).Elem()
|
||||
|
||||
err := mergeSetFlag(configMockElemValue, test.SetValues)
|
||||
|
||||
if err == nil {
|
||||
t.Errorf("unexpected unhandled error - SetValues: %v", test.SetValues)
|
||||
return
|
||||
}
|
||||
|
||||
for i := 0; i < configMockElemValue.NumField(); i++ {
|
||||
currentField := configMockElemValue.Type().Field(i)
|
||||
currentFieldByName := configMockElemValue.FieldByName(currentField.Name)
|
||||
|
||||
if !currentFieldByName.IsZero() {
|
||||
t.Errorf("unexpected case - SetValues: %v", test.SetValues)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestMergeSetFlagNotSliceValues(t *testing.T) {
|
||||
tests := []struct {
|
||||
Name string
|
||||
FieldsSetValues []FieldSetValues
|
||||
}{
|
||||
{Name: "string field", FieldsSetValues: []FieldSetValues{{SetValues: []string{"string-field=test"}, FieldName: "StringField", FieldValue: "test"}}},
|
||||
{Name: "int field", FieldsSetValues: []FieldSetValues{{SetValues: []string{"int-field=6"}, FieldName: "IntField", FieldValue: 6}}},
|
||||
{Name: "bool field", FieldsSetValues: []FieldSetValues{{SetValues: []string{"bool-field=true"}, FieldName: "BoolField", FieldValue: true}}},
|
||||
{Name: "uint field", FieldsSetValues: []FieldSetValues{{SetValues: []string{"uint-field=6"}, FieldName: "UintField", FieldValue: uint(6)}}},
|
||||
{Name: "four fields combined", FieldsSetValues: []FieldSetValues {
|
||||
{SetValues: []string{"string-field=test"}, FieldName: "StringField", FieldValue: "test"},
|
||||
{SetValues: []string{"int-field=6"}, FieldName: "IntField", FieldValue: 6},
|
||||
{SetValues: []string{"bool-field=true"}, FieldName: "BoolField", FieldValue: true},
|
||||
{SetValues: []string{"uint-field=6"}, FieldName: "UintField", FieldValue: uint(6)},
|
||||
}},
|
||||
}
|
||||
|
||||
for _, test := range tests {
|
||||
t.Run(test.Name, func(t *testing.T) {
|
||||
configMock := ConfigMock{}
|
||||
configMockElemValue := reflect.ValueOf(&configMock).Elem()
|
||||
|
||||
var setValues []string
|
||||
for _, fieldSetValues := range test.FieldsSetValues {
|
||||
setValues = append(setValues, fieldSetValues.SetValues...)
|
||||
}
|
||||
|
||||
err := mergeSetFlag(configMockElemValue, setValues)
|
||||
|
||||
if err != nil {
|
||||
t.Errorf("unexpected error result - err: %v", err)
|
||||
return
|
||||
}
|
||||
|
||||
for _, fieldSetValues := range test.FieldsSetValues {
|
||||
fieldValue := configMockElemValue.FieldByName(fieldSetValues.FieldName).Interface()
|
||||
if fieldValue != fieldSetValues.FieldValue {
|
||||
t.Errorf("unexpected result - expected: %v, actual: %v", fieldSetValues.FieldValue, fieldValue)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestMergeSetFlagSliceValues(t *testing.T) {
|
||||
tests := []struct {
|
||||
Name string
|
||||
FieldsSetValues []FieldSetValues
|
||||
}{
|
||||
{Name: "string slice field single value", FieldsSetValues: []FieldSetValues{{SetValues: []string{"string-slice-field=test"}, FieldName: "StringSliceField", FieldValue: []string{"test"}}}},
|
||||
{Name: "int slice field single value", FieldsSetValues: []FieldSetValues{{SetValues: []string{"int-slice-field=6"}, FieldName: "IntSliceField", FieldValue: []int{6}}}},
|
||||
{Name: "bool slice field single value", FieldsSetValues: []FieldSetValues{{SetValues: []string{"bool-slice-field=true"}, FieldName: "BoolSliceField", FieldValue: []bool{true}}}},
|
||||
{Name: "uint slice field single value", FieldsSetValues: []FieldSetValues{{SetValues: []string{"uint-slice-field=6"}, FieldName: "UintSliceField", FieldValue: []uint{uint(6)}}}},
|
||||
{Name: "four single value fields combined", FieldsSetValues: []FieldSetValues{
|
||||
{SetValues: []string{"string-slice-field=test"}, FieldName: "StringSliceField", FieldValue: []string{"test"}},
|
||||
{SetValues: []string{"int-slice-field=6"}, FieldName: "IntSliceField", FieldValue: []int{6}},
|
||||
{SetValues: []string{"bool-slice-field=true"}, FieldName: "BoolSliceField", FieldValue: []bool{true}},
|
||||
{SetValues: []string{"uint-slice-field=6"}, FieldName: "UintSliceField", FieldValue: []uint{uint(6)}},
|
||||
}},
|
||||
{Name: "string slice field two values", FieldsSetValues: []FieldSetValues{{SetValues: []string{"string-slice-field=test", "string-slice-field=test2"}, FieldName: "StringSliceField", FieldValue: []string{"test", "test2"}}}},
|
||||
{Name: "int slice field two values", FieldsSetValues: []FieldSetValues{{SetValues: []string{"int-slice-field=6", "int-slice-field=66"}, FieldName: "IntSliceField", FieldValue: []int{6, 66}}}},
|
||||
{Name: "bool slice field two values", FieldsSetValues: []FieldSetValues{{SetValues: []string{"bool-slice-field=true", "bool-slice-field=false"}, FieldName: "BoolSliceField", FieldValue: []bool{true, false}}}},
|
||||
{Name: "uint slice field two values", FieldsSetValues: []FieldSetValues{{SetValues: []string{"uint-slice-field=6", "uint-slice-field=66"}, FieldName: "UintSliceField", FieldValue: []uint{uint(6), uint(66)}}}},
|
||||
{Name: "four two values fields combined", FieldsSetValues: []FieldSetValues{
|
||||
{SetValues: []string{"string-slice-field=test", "string-slice-field=test2"}, FieldName: "StringSliceField", FieldValue: []string{"test", "test2"}},
|
||||
{SetValues: []string{"int-slice-field=6", "int-slice-field=66"}, FieldName: "IntSliceField", FieldValue: []int{6, 66}},
|
||||
{SetValues: []string{"bool-slice-field=true", "bool-slice-field=false"}, FieldName: "BoolSliceField", FieldValue: []bool{true, false}},
|
||||
{SetValues: []string{"uint-slice-field=6", "uint-slice-field=66"}, FieldName: "UintSliceField", FieldValue: []uint{uint(6), uint(66)}},
|
||||
}},
|
||||
}
|
||||
|
||||
for _, test := range tests {
|
||||
t.Run(test.Name, func(t *testing.T) {
|
||||
configMock := ConfigMock{}
|
||||
configMockElemValue := reflect.ValueOf(&configMock).Elem()
|
||||
|
||||
var setValues []string
|
||||
for _, fieldSetValues := range test.FieldsSetValues {
|
||||
setValues = append(setValues, fieldSetValues.SetValues...)
|
||||
}
|
||||
|
||||
err := mergeSetFlag(configMockElemValue, setValues)
|
||||
|
||||
if err != nil {
|
||||
t.Errorf("unexpected error result - err: %v", err)
|
||||
return
|
||||
}
|
||||
|
||||
for _, fieldSetValues := range test.FieldsSetValues {
|
||||
fieldValue := configMockElemValue.FieldByName(fieldSetValues.FieldName).Interface()
|
||||
if !reflect.DeepEqual(fieldValue, fieldSetValues.FieldValue) {
|
||||
t.Errorf("unexpected result - expected: %v, actual: %v", fieldSetValues.FieldValue, fieldValue)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestMergeSetFlagMixValues(t *testing.T) {
|
||||
tests := []struct {
|
||||
Name string
|
||||
FieldsSetValues []FieldSetValues
|
||||
}{
|
||||
{Name: "single value all fields", FieldsSetValues: []FieldSetValues{
|
||||
{SetValues: []string{"string-slice-field=test"}, FieldName: "StringSliceField", FieldValue: []string{"test"}},
|
||||
{SetValues: []string{"int-slice-field=6"}, FieldName: "IntSliceField", FieldValue: []int{6}},
|
||||
{SetValues: []string{"bool-slice-field=true"}, FieldName: "BoolSliceField", FieldValue: []bool{true}},
|
||||
{SetValues: []string{"uint-slice-field=6"}, FieldName: "UintSliceField", FieldValue: []uint{uint(6)}},
|
||||
{SetValues: []string{"string-field=test"}, FieldName: "StringField", FieldValue: "test"},
|
||||
{SetValues: []string{"int-field=6"}, FieldName: "IntField", FieldValue: 6},
|
||||
{SetValues: []string{"bool-field=true"}, FieldName: "BoolField", FieldValue: true},
|
||||
{SetValues: []string{"uint-field=6"}, FieldName: "UintField", FieldValue: uint(6)},
|
||||
}},
|
||||
{Name: "two values slice fields and single value fields", FieldsSetValues: []FieldSetValues{
|
||||
{SetValues: []string{"string-slice-field=test", "string-slice-field=test2"}, FieldName: "StringSliceField", FieldValue: []string{"test", "test2"}},
|
||||
{SetValues: []string{"int-slice-field=6", "int-slice-field=66"}, FieldName: "IntSliceField", FieldValue: []int{6, 66}},
|
||||
{SetValues: []string{"bool-slice-field=true", "bool-slice-field=false"}, FieldName: "BoolSliceField", FieldValue: []bool{true, false}},
|
||||
{SetValues: []string{"uint-slice-field=6", "uint-slice-field=66"}, FieldName: "UintSliceField", FieldValue: []uint{uint(6), uint(66)}},
|
||||
{SetValues: []string{"string-field=test"}, FieldName: "StringField", FieldValue: "test"},
|
||||
{SetValues: []string{"int-field=6"}, FieldName: "IntField", FieldValue: 6},
|
||||
{SetValues: []string{"bool-field=true"}, FieldName: "BoolField", FieldValue: true},
|
||||
{SetValues: []string{"uint-field=6"}, FieldName: "UintField", FieldValue: uint(6)},
|
||||
}},
|
||||
}
|
||||
|
||||
for _, test := range tests {
|
||||
t.Run(test.Name, func(t *testing.T) {
|
||||
configMock := ConfigMock{}
|
||||
configMockElemValue := reflect.ValueOf(&configMock).Elem()
|
||||
|
||||
var setValues []string
|
||||
for _, fieldSetValues := range test.FieldsSetValues {
|
||||
setValues = append(setValues, fieldSetValues.SetValues...)
|
||||
}
|
||||
|
||||
err := mergeSetFlag(configMockElemValue, setValues)
|
||||
|
||||
if err != nil {
|
||||
t.Errorf("unexpected error result - err: %v", err)
|
||||
return
|
||||
}
|
||||
|
||||
for _, fieldSetValues := range test.FieldsSetValues {
|
||||
fieldValue := configMockElemValue.FieldByName(fieldSetValues.FieldName).Interface()
|
||||
if !reflect.DeepEqual(fieldValue, fieldSetValues.FieldValue) {
|
||||
t.Errorf("unexpected result - expected: %v, actual: %v", fieldSetValues.FieldValue, fieldValue)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetParsedValueValidValue(t *testing.T) {
|
||||
tests := []struct {
|
||||
StringValue string
|
||||
Kind reflect.Kind
|
||||
ActualValue interface{}
|
||||
}{
|
||||
{StringValue: "test", Kind: reflect.String, ActualValue: "test"},
|
||||
{StringValue: "123", Kind: reflect.String, ActualValue: "123"},
|
||||
{StringValue: "true", Kind: reflect.Bool, ActualValue: true},
|
||||
{StringValue: "false", Kind: reflect.Bool, ActualValue: false},
|
||||
{StringValue: "6", Kind: reflect.Int, ActualValue: 6},
|
||||
{StringValue: "-6", Kind: reflect.Int, ActualValue: -6},
|
||||
{StringValue: "6", Kind: reflect.Int8, ActualValue: int8(6)},
|
||||
{StringValue: "-6", Kind: reflect.Int8, ActualValue: int8(-6)},
|
||||
{StringValue: "6", Kind: reflect.Int16, ActualValue: int16(6)},
|
||||
{StringValue: "-6", Kind: reflect.Int16, ActualValue: int16(-6)},
|
||||
{StringValue: "6", Kind: reflect.Int32, ActualValue: int32(6)},
|
||||
{StringValue: "-6", Kind: reflect.Int32, ActualValue: int32(-6)},
|
||||
{StringValue: "6", Kind: reflect.Int64, ActualValue: int64(6)},
|
||||
{StringValue: "-6", Kind: reflect.Int64, ActualValue: int64(-6)},
|
||||
{StringValue: "6", Kind: reflect.Uint, ActualValue: uint(6)},
|
||||
{StringValue: "66", Kind: reflect.Uint, ActualValue: uint(66)},
|
||||
{StringValue: "6", Kind: reflect.Uint8, ActualValue: uint8(6)},
|
||||
{StringValue: "66", Kind: reflect.Uint8, ActualValue: uint8(66)},
|
||||
{StringValue: "6", Kind: reflect.Uint16, ActualValue: uint16(6)},
|
||||
{StringValue: "66", Kind: reflect.Uint16, ActualValue: uint16(66)},
|
||||
{StringValue: "6", Kind: reflect.Uint32, ActualValue: uint32(6)},
|
||||
{StringValue: "66", Kind: reflect.Uint32, ActualValue: uint32(66)},
|
||||
{StringValue: "6", Kind: reflect.Uint64, ActualValue: uint64(6)},
|
||||
{StringValue: "66", Kind: reflect.Uint64, ActualValue: uint64(66)},
|
||||
}
|
||||
|
||||
for _, test := range tests {
|
||||
t.Run(fmt.Sprintf("%v %v", test.Kind, test.StringValue), func(t *testing.T) {
|
||||
parsedValue, err := getParsedValue(test.Kind, test.StringValue)
|
||||
|
||||
if err != nil {
|
||||
t.Errorf("unexpected error result - err: %v", err)
|
||||
return
|
||||
}
|
||||
|
||||
if parsedValue.Interface() != test.ActualValue {
|
||||
t.Errorf("unexpected result - expected: %v, actual: %v", test.ActualValue, parsedValue)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetParsedValueInvalidValue(t *testing.T) {
|
||||
tests := []struct {
|
||||
StringValue string
|
||||
Kind reflect.Kind
|
||||
}{
|
||||
{StringValue: "test", Kind: reflect.Bool},
|
||||
{StringValue: "123", Kind: reflect.Bool},
|
||||
{StringValue: "test", Kind: reflect.Int},
|
||||
{StringValue: "true", Kind: reflect.Int},
|
||||
{StringValue: "test", Kind: reflect.Int8},
|
||||
{StringValue: "true", Kind: reflect.Int8},
|
||||
{StringValue: "test", Kind: reflect.Int16},
|
||||
{StringValue: "true", Kind: reflect.Int16},
|
||||
{StringValue: "test", Kind: reflect.Int32},
|
||||
{StringValue: "true", Kind: reflect.Int32},
|
||||
{StringValue: "test", Kind: reflect.Int64},
|
||||
{StringValue: "true", Kind: reflect.Int64},
|
||||
{StringValue: "test", Kind: reflect.Uint},
|
||||
{StringValue: "-6", Kind: reflect.Uint},
|
||||
{StringValue: "test", Kind: reflect.Uint8},
|
||||
{StringValue: "-6", Kind: reflect.Uint8},
|
||||
{StringValue: "test", Kind: reflect.Uint16},
|
||||
{StringValue: "-6", Kind: reflect.Uint16},
|
||||
{StringValue: "test", Kind: reflect.Uint32},
|
||||
{StringValue: "-6", Kind: reflect.Uint32},
|
||||
{StringValue: "test", Kind: reflect.Uint64},
|
||||
{StringValue: "-6", Kind: reflect.Uint64},
|
||||
}
|
||||
|
||||
for _, test := range tests {
|
||||
t.Run(fmt.Sprintf("%v %v", test.Kind, test.StringValue), func(t *testing.T) {
|
||||
parsedValue, err := getParsedValue(test.Kind, test.StringValue)
|
||||
|
||||
if err == nil {
|
||||
t.Errorf("unexpected unhandled error - stringValue: %v, Kind: %v", test.StringValue, test.Kind)
|
||||
return
|
||||
}
|
||||
|
||||
if parsedValue != reflect.ValueOf(nil) {
|
||||
t.Errorf("unexpected parsed value - parsedValue: %v", parsedValue)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
43
cli/config/config_test.go
Normal file
43
cli/config/config_test.go
Normal file
@@ -0,0 +1,43 @@
|
||||
package config_test
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/up9inc/mizu/cli/config"
|
||||
"reflect"
|
||||
"strings"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestConfigWriteIgnoresReadonlyFields(t *testing.T) {
|
||||
var readonlyFields []string
|
||||
|
||||
configElem := reflect.ValueOf(&config.ConfigStruct{}).Elem()
|
||||
getFieldsWithReadonlyTag(configElem, &readonlyFields)
|
||||
|
||||
configWithDefaults, _ := config.GetConfigWithDefaults()
|
||||
for _, readonlyField := range readonlyFields {
|
||||
t.Run(readonlyField, func(t *testing.T) {
|
||||
readonlyFieldToCheck := fmt.Sprintf("\n%s:", readonlyField)
|
||||
if strings.Contains(configWithDefaults, readonlyFieldToCheck) {
|
||||
t.Errorf("unexpected result - readonly field: %v, config: %v", readonlyField, configWithDefaults)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func getFieldsWithReadonlyTag(currentElem reflect.Value, readonlyFields *[]string) {
|
||||
for i := 0; i < currentElem.NumField(); i++ {
|
||||
currentField := currentElem.Type().Field(i)
|
||||
currentFieldByName := currentElem.FieldByName(currentField.Name)
|
||||
|
||||
if currentField.Type.Kind() == reflect.Struct {
|
||||
getFieldsWithReadonlyTag(currentFieldByName, readonlyFields)
|
||||
continue
|
||||
}
|
||||
|
||||
if _, ok := currentField.Tag.Lookup(config.ReadonlyTag); ok {
|
||||
fieldNameByTag := strings.Split(currentField.Tag.Get(config.FieldNameTag), ",")[0]
|
||||
*readonlyFields = append(*readonlyFields, fieldNameByTag)
|
||||
}
|
||||
}
|
||||
}
|
||||
24
cli/config/envConfig.go
Normal file
24
cli/config/envConfig.go
Normal file
@@ -0,0 +1,24 @@
|
||||
package config
|
||||
|
||||
import (
|
||||
"os"
|
||||
"strconv"
|
||||
)
|
||||
|
||||
const (
|
||||
ApiServerRetries = "API_SERVER_RETRIES"
|
||||
)
|
||||
|
||||
func GetIntEnvConfig(key string, defaultValue int) int {
|
||||
value := os.Getenv(key)
|
||||
if value == "" {
|
||||
return defaultValue
|
||||
}
|
||||
|
||||
intValue, err := strconv.Atoi(value)
|
||||
if err != nil {
|
||||
return defaultValue
|
||||
}
|
||||
|
||||
return intValue
|
||||
}
|
||||
@@ -3,9 +3,7 @@ package errormessage
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
|
||||
"github.com/up9inc/mizu/cli/mizu"
|
||||
|
||||
"github.com/up9inc/mizu/cli/config"
|
||||
regexpsyntax "regexp/syntax"
|
||||
|
||||
k8serrors "k8s.io/apimachinery/pkg/api/errors"
|
||||
@@ -20,9 +18,9 @@ func FormatError(err error) error {
|
||||
"supply the required permission or control Mizu's access to namespaces by setting %s "+
|
||||
"in the config file or setting the tapped namespace with --%s %s=<NAMEPSACE>",
|
||||
err,
|
||||
mizu.MizuResourcesNamespaceConfigName,
|
||||
mizu.SetCommandName,
|
||||
mizu.MizuResourcesNamespaceConfigName)
|
||||
config.MizuResourcesNamespaceConfigName,
|
||||
config.SetCommandName,
|
||||
config.MizuResourcesNamespaceConfigName)
|
||||
} else if syntaxError, isSyntaxError := asRegexSyntaxError(err); isSyntaxError {
|
||||
errorNew = fmt.Errorf("regex %s is invalid: %w", syntaxError.Expr, err)
|
||||
} else {
|
||||
|
||||
@@ -1,58 +0,0 @@
|
||||
package fsUtils
|
||||
|
||||
import (
|
||||
"archive/zip"
|
||||
"context"
|
||||
"fmt"
|
||||
"github.com/up9inc/mizu/cli/kubernetes"
|
||||
"github.com/up9inc/mizu/cli/mizu"
|
||||
"os"
|
||||
"regexp"
|
||||
)
|
||||
|
||||
func DumpLogs(provider *kubernetes.Provider, ctx context.Context, filePath string) error {
|
||||
podExactRegex := regexp.MustCompile("^" + mizu.MizuResourcesPrefix)
|
||||
pods, err := provider.ListAllPodsMatchingRegex(ctx, podExactRegex, []string{mizu.Config.MizuResourcesNamespace})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if len(pods) == 0 {
|
||||
return fmt.Errorf("no mizu pods found in namespace %s", mizu.Config.MizuResourcesNamespace)
|
||||
}
|
||||
|
||||
newZipFile, err := os.Create(filePath)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer newZipFile.Close()
|
||||
zipWriter := zip.NewWriter(newZipFile)
|
||||
defer zipWriter.Close()
|
||||
|
||||
for _, pod := range pods {
|
||||
logs, err := provider.GetPodLogs(pod.Namespace, pod.Name, ctx)
|
||||
if err != nil {
|
||||
mizu.Log.Errorf("Failed to get logs, %v", err)
|
||||
continue
|
||||
} else {
|
||||
mizu.Log.Debugf("Successfully read log length %d for pod: %s.%s", len(logs), pod.Namespace, pod.Name)
|
||||
}
|
||||
if err := AddStrToZip(zipWriter, logs, fmt.Sprintf("%s.%s.log", pod.Namespace, pod.Name)); err != nil {
|
||||
mizu.Log.Errorf("Failed write logs, %v", err)
|
||||
} else {
|
||||
mizu.Log.Infof("Successfully added log length %d from pod: %s.%s", len(logs), pod.Namespace, pod.Name)
|
||||
}
|
||||
}
|
||||
if err := AddFileToZip(zipWriter, mizu.GetConfigFilePath()); err != nil {
|
||||
mizu.Log.Debugf("Failed write file, %v", err)
|
||||
} else {
|
||||
mizu.Log.Infof("Successfully added file %s", mizu.GetConfigFilePath())
|
||||
}
|
||||
if err := AddFileToZip(zipWriter, mizu.GetLogFilePath()); err != nil {
|
||||
mizu.Log.Debugf("Failed write file, %v", err)
|
||||
} else {
|
||||
mizu.Log.Infof("Successfully added file %s", mizu.GetLogFilePath())
|
||||
}
|
||||
mizu.Log.Infof("You can find the zip with all logs in %s\n", filePath)
|
||||
return nil
|
||||
}
|
||||
@@ -4,6 +4,7 @@ go 1.16
|
||||
|
||||
require (
|
||||
github.com/creasty/defaults v1.5.1
|
||||
github.com/denisbrodbeck/machineid v1.0.1
|
||||
github.com/google/go-github/v37 v37.0.0
|
||||
github.com/gorilla/websocket v1.4.2
|
||||
github.com/op/go-logging v0.0.0-20160315200505-970db520ece7
|
||||
|
||||
@@ -88,6 +88,8 @@ github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSs
|
||||
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/denisbrodbeck/machineid v1.0.1 h1:geKr9qtkB876mXguW2X6TU4ZynleN6ezuMSRhl4D7AQ=
|
||||
github.com/denisbrodbeck/machineid v1.0.1/go.mod h1:dJUwb7PTidGDeYyUBmXZ2GphQBbjJCrnectwCyxcUSI=
|
||||
github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ=
|
||||
github.com/dgryski/go-sip13 v0.0.0-20181026042036-e10d5fee7954/go.mod h1:vAd38F8PWV+bWy6jNmig1y/TA+kYO4g3RSRF0IAv0no=
|
||||
github.com/docker/distribution v2.7.1+incompatible/go.mod h1:J2gT2udsDAN96Uj4KfcMRqY0/ypR+oyYUYmja8H+y+w=
|
||||
@@ -412,7 +414,6 @@ github.com/vektah/gqlparser v1.1.2/go.mod h1:1ycwN7Ij5njmMkPPAOaRFY4rET2Enx7IkVv
|
||||
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/go.mod h1:/LWChgwKmvncFJFHJ7Gvn9wZArjbV5/FppcK2fKk/tI=
|
||||
github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
||||
github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
||||
go.etcd.io/bbolt v1.3.2/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU=
|
||||
|
||||
@@ -7,14 +7,17 @@ import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"regexp"
|
||||
"strconv"
|
||||
|
||||
"github.com/up9inc/mizu/cli/config/configStructs"
|
||||
"github.com/up9inc/mizu/cli/logger"
|
||||
|
||||
"io"
|
||||
|
||||
"github.com/up9inc/mizu/cli/mizu"
|
||||
"github.com/up9inc/mizu/shared"
|
||||
"io"
|
||||
core "k8s.io/api/core/v1"
|
||||
rbac "k8s.io/api/rbac/v1"
|
||||
k8serrors "k8s.io/apimachinery/pkg/api/errors"
|
||||
@@ -36,7 +39,6 @@ import (
|
||||
"k8s.io/client-go/tools/clientcmd"
|
||||
_ "k8s.io/client-go/tools/portforward"
|
||||
watchtools "k8s.io/client-go/tools/watch"
|
||||
"k8s.io/client-go/util/homedir"
|
||||
)
|
||||
|
||||
type Provider struct {
|
||||
@@ -55,13 +57,23 @@ func NewProvider(kubeConfigPath string) (*Provider, error) {
|
||||
restClientConfig, err := kubernetesConfig.ClientConfig()
|
||||
if err != nil {
|
||||
if clientcmd.IsEmptyConfig(err) {
|
||||
return nil, fmt.Errorf("Couldn't find the kube config file, or file is empty. Try adding '--kube-config=<path to kube config file>'\n")
|
||||
return nil, fmt.Errorf("couldn't find the kube config file, or file is empty (%s)\n"+
|
||||
"you can set alternative kube config file path by adding the kube-config-path field to the mizu config file, err: %w", kubeConfigPath, err)
|
||||
}
|
||||
if clientcmd.IsConfigurationInvalid(err) {
|
||||
return nil, fmt.Errorf("Invalid kube config file. Try using a different config with '--kube-config=<path to kube config file>'\n")
|
||||
return nil, fmt.Errorf("invalid kube config file (%s)\n"+
|
||||
"you can set alternative kube config file path by adding the kube-config-path field to the mizu config file, err: %w", kubeConfigPath, err)
|
||||
}
|
||||
|
||||
return nil, fmt.Errorf("error while using kube config (%s)\n"+
|
||||
"you can set alternative kube config file path by adding the kube-config-path field to the mizu config file, err: %w", kubeConfigPath, err)
|
||||
}
|
||||
|
||||
clientSet, err := getClientSet(restClientConfig)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error while using kube config (%s)\n"+
|
||||
"you can set alternative kube config file path by adding the kube-config-path field to the mizu config file, err: %w", kubeConfigPath, err)
|
||||
}
|
||||
clientSet := getClientSet(restClientConfig)
|
||||
|
||||
return &Provider{
|
||||
clientSet: clientSet,
|
||||
@@ -140,6 +152,8 @@ type ApiServerOptions struct {
|
||||
IsNamespaceRestricted bool
|
||||
MizuApiFilteringOptions *shared.TrafficFilteringOptions
|
||||
MaxEntriesDBSizeBytes int64
|
||||
Resources configStructs.Resources
|
||||
ImagePullPolicy core.PullPolicy
|
||||
}
|
||||
|
||||
func (provider *Provider) CreateMizuApiServerPod(ctx context.Context, opts *ApiServerOptions) (*core.Pod, error) {
|
||||
@@ -152,19 +166,19 @@ func (provider *Provider) CreateMizuApiServerPod(ctx context.Context, opts *ApiS
|
||||
configMapOptional := true
|
||||
configMapVolumeName.Optional = &configMapOptional
|
||||
|
||||
cpuLimit, err := resource.ParseQuantity("750m")
|
||||
cpuLimit, err := resource.ParseQuantity(opts.Resources.CpuLimit)
|
||||
if err != nil {
|
||||
return nil, errors.New(fmt.Sprintf("invalid cpu limit for %s container", opts.PodName))
|
||||
}
|
||||
memLimit, err := resource.ParseQuantity("512Mi")
|
||||
memLimit, err := resource.ParseQuantity(opts.Resources.MemoryLimit)
|
||||
if err != nil {
|
||||
return nil, errors.New(fmt.Sprintf("invalid memory limit for %s container", opts.PodName))
|
||||
}
|
||||
cpuRequests, err := resource.ParseQuantity("50m")
|
||||
cpuRequests, err := resource.ParseQuantity(opts.Resources.CpuRequests)
|
||||
if err != nil {
|
||||
return nil, errors.New(fmt.Sprintf("invalid cpu request for %s container", opts.PodName))
|
||||
}
|
||||
memRequests, err := resource.ParseQuantity("50Mi")
|
||||
memRequests, err := resource.ParseQuantity(opts.Resources.MemoryRequests)
|
||||
if err != nil {
|
||||
return nil, errors.New(fmt.Sprintf("invalid memory request for %s container", opts.PodName))
|
||||
}
|
||||
@@ -185,7 +199,7 @@ func (provider *Provider) CreateMizuApiServerPod(ctx context.Context, opts *ApiS
|
||||
{
|
||||
Name: opts.PodName,
|
||||
Image: opts.PodImage,
|
||||
ImagePullPolicy: core.PullAlways,
|
||||
ImagePullPolicy: opts.ImagePullPolicy,
|
||||
VolumeMounts: []core.VolumeMount{
|
||||
{
|
||||
Name: mizu.ConfigMapName,
|
||||
@@ -561,11 +575,11 @@ func (provider *Provider) CreateConfigMap(ctx context.Context, namespace string,
|
||||
return nil
|
||||
}
|
||||
|
||||
func (provider *Provider) ApplyMizuTapperDaemonSet(ctx context.Context, namespace string, daemonSetName string, podImage string, tapperPodName string, apiServerPodIp string, nodeToTappedPodIPMap map[string][]string, serviceAccountName string, tapOutgoing bool) error {
|
||||
mizu.Log.Debugf("Applying %d tapper deamonsets, ns: %s, daemonSetName: %s, podImage: %s, tapperPodName: %s", len(nodeToTappedPodIPMap), namespace, daemonSetName, podImage, tapperPodName)
|
||||
func (provider *Provider) ApplyMizuTapperDaemonSet(ctx context.Context, namespace string, daemonSetName string, podImage string, tapperPodName string, apiServerPodIp string, nodeToTappedPodIPMap map[string][]string, serviceAccountName string, resources configStructs.Resources, imagePullPolicy core.PullPolicy) error {
|
||||
logger.Log.Debugf("Applying %d tapper daemon sets, ns: %s, daemonSetName: %s, podImage: %s, tapperPodName: %s", len(nodeToTappedPodIPMap), namespace, daemonSetName, podImage, tapperPodName)
|
||||
|
||||
if len(nodeToTappedPodIPMap) == 0 {
|
||||
return fmt.Errorf("Daemon set %s must tap at least 1 pod", daemonSetName)
|
||||
return fmt.Errorf("daemon set %s must tap at least 1 pod", daemonSetName)
|
||||
}
|
||||
|
||||
nodeToTappedPodIPMapJsonStr, err := json.Marshal(nodeToTappedPodIPMap)
|
||||
@@ -578,15 +592,13 @@ func (provider *Provider) ApplyMizuTapperDaemonSet(ctx context.Context, namespac
|
||||
"-i", "any",
|
||||
"--tap",
|
||||
"--api-server-address", fmt.Sprintf("ws://%s/wsTapper", apiServerPodIp),
|
||||
}
|
||||
if tapOutgoing {
|
||||
mizuCmd = append(mizuCmd, "--anydirection")
|
||||
"--nodefrag",
|
||||
}
|
||||
|
||||
agentContainer := applyconfcore.Container()
|
||||
agentContainer.WithName(tapperPodName)
|
||||
agentContainer.WithImage(podImage)
|
||||
agentContainer.WithImagePullPolicy(core.PullAlways)
|
||||
agentContainer.WithImagePullPolicy(imagePullPolicy)
|
||||
agentContainer.WithSecurityContext(applyconfcore.SecurityContext().WithPrivileged(true))
|
||||
agentContainer.WithCommand(mizuCmd...)
|
||||
agentContainer.WithEnv(
|
||||
@@ -600,19 +612,19 @@ func (provider *Provider) ApplyMizuTapperDaemonSet(ctx context.Context, namespac
|
||||
),
|
||||
),
|
||||
)
|
||||
cpuLimit, err := resource.ParseQuantity("500m")
|
||||
cpuLimit, err := resource.ParseQuantity(resources.CpuLimit)
|
||||
if err != nil {
|
||||
return errors.New(fmt.Sprintf("invalid cpu limit for %s container", tapperPodName))
|
||||
}
|
||||
memLimit, err := resource.ParseQuantity("1Gi")
|
||||
memLimit, err := resource.ParseQuantity(resources.MemoryLimit)
|
||||
if err != nil {
|
||||
return errors.New(fmt.Sprintf("invalid memory limit for %s container", tapperPodName))
|
||||
}
|
||||
cpuRequests, err := resource.ParseQuantity("50m")
|
||||
cpuRequests, err := resource.ParseQuantity(resources.CpuRequests)
|
||||
if err != nil {
|
||||
return errors.New(fmt.Sprintf("invalid cpu request for %s container", tapperPodName))
|
||||
}
|
||||
memRequests, err := resource.ParseQuantity("50Mi")
|
||||
memRequests, err := resource.ParseQuantity(resources.MemoryRequests)
|
||||
if err != nil {
|
||||
return errors.New(fmt.Sprintf("invalid memory request for %s container", tapperPodName))
|
||||
}
|
||||
@@ -727,25 +739,27 @@ func (provider *Provider) GetPodLogs(namespace string, podName string, ctx conte
|
||||
return str, nil
|
||||
}
|
||||
|
||||
func getClientSet(config *restclient.Config) *kubernetes.Clientset {
|
||||
func (provider *Provider) GetNamespaceEvents(namespace string, ctx context.Context) (string, error) {
|
||||
eventsOpts := metav1.ListOptions{}
|
||||
eventList, err := provider.clientSet.CoreV1().Events(namespace).List(ctx, eventsOpts)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("error getting events on ns: %s, %w", namespace, err)
|
||||
}
|
||||
|
||||
return eventList.String(), nil
|
||||
}
|
||||
|
||||
func getClientSet(config *restclient.Config) (*kubernetes.Clientset, error) {
|
||||
clientSet, err := kubernetes.NewForConfig(config)
|
||||
if err != nil {
|
||||
panic(err.Error())
|
||||
return nil, err
|
||||
}
|
||||
return clientSet
|
||||
|
||||
return clientSet, nil
|
||||
}
|
||||
|
||||
func loadKubernetesConfiguration(kubeConfigPath string) clientcmd.ClientConfig {
|
||||
if kubeConfigPath == "" {
|
||||
kubeConfigPath = os.Getenv("KUBECONFIG")
|
||||
}
|
||||
|
||||
if kubeConfigPath == "" {
|
||||
home := homedir.HomeDir()
|
||||
kubeConfigPath = filepath.Join(home, ".kube", "config")
|
||||
}
|
||||
|
||||
mizu.Log.Debugf("Using kube config %s", kubeConfigPath)
|
||||
logger.Log.Debugf("Using kube config %s", kubeConfigPath)
|
||||
configPathList := filepath.SplitList(kubeConfigPath)
|
||||
configLoadingRules := &clientcmd.ClientConfigLoadingRules{}
|
||||
if len(configPathList) <= 1 {
|
||||
|
||||
@@ -2,7 +2,7 @@ package kubernetes
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/up9inc/mizu/cli/mizu"
|
||||
"github.com/up9inc/mizu/cli/logger"
|
||||
"k8s.io/kubectl/pkg/proxy"
|
||||
"net"
|
||||
"net/http"
|
||||
@@ -14,7 +14,7 @@ const k8sProxyApiPrefix = "/"
|
||||
const mizuServicePort = 80
|
||||
|
||||
func StartProxy(kubernetesProvider *Provider, mizuPort uint16, mizuNamespace string, mizuServiceName string) error {
|
||||
mizu.Log.Debugf("Starting proxy. namespace: [%v], service name: [%s], port: [%v]", mizuNamespace, mizuServiceName, mizuPort)
|
||||
logger.Log.Debugf("Starting proxy. namespace: [%v], service name: [%s], port: [%v]", mizuNamespace, mizuServiceName, mizuPort)
|
||||
filter := &proxy.FilterServer{
|
||||
AcceptPaths: proxy.MakeRegexpArrayOrDie(proxy.DefaultPathAcceptRE),
|
||||
RejectPaths: proxy.MakeRegexpArrayOrDie(proxy.DefaultPathRejectRE),
|
||||
@@ -39,6 +39,7 @@ func StartProxy(kubernetesProvider *Provider, mizuPort uint16, mizuNamespace str
|
||||
server := http.Server{
|
||||
Handler: mux,
|
||||
}
|
||||
|
||||
return server.Serve(l)
|
||||
}
|
||||
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
package mizu
|
||||
package logger
|
||||
|
||||
import (
|
||||
"github.com/op/go-logging"
|
||||
"github.com/up9inc/mizu/cli/mizu"
|
||||
"os"
|
||||
"path"
|
||||
)
|
||||
@@ -13,7 +14,7 @@ var format = logging.MustStringFormatter(
|
||||
)
|
||||
|
||||
func GetLogFilePath() string {
|
||||
return path.Join(GetMizuFolderPath(), "mizu_cli.log")
|
||||
return path.Join(mizu.GetMizuFolderPath(), "mizu_cli.log")
|
||||
}
|
||||
|
||||
func InitLogger() {
|
||||
@@ -34,5 +35,5 @@ func InitLogger() {
|
||||
logging.SetBackend(backend1Leveled, backend2Formatter)
|
||||
|
||||
Log.Debugf("\n\n\n")
|
||||
Log.Debugf("Running mizu version %v", SemVer)
|
||||
Log.Debugf("Running mizu version %v", mizu.SemVer)
|
||||
}
|
||||
@@ -2,7 +2,7 @@ package main
|
||||
|
||||
import (
|
||||
"github.com/up9inc/mizu/cli/cmd"
|
||||
"github.com/up9inc/mizu/cli/goUtils"
|
||||
"github.com/up9inc/mizu/cli/mizu/goUtils"
|
||||
)
|
||||
|
||||
func main() {
|
||||
|
||||
@@ -1,284 +0,0 @@
|
||||
package mizu
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"path"
|
||||
"reflect"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/creasty/defaults"
|
||||
"github.com/spf13/cobra"
|
||||
"github.com/spf13/pflag"
|
||||
"github.com/up9inc/mizu/cli/mizu/configStructs"
|
||||
"github.com/up9inc/mizu/cli/uiUtils"
|
||||
"gopkg.in/yaml.v3"
|
||||
)
|
||||
|
||||
const (
|
||||
Separator = "="
|
||||
SetCommandName = "set"
|
||||
)
|
||||
|
||||
var allowedSetFlags = []string{
|
||||
AgentImageConfigName,
|
||||
MizuResourcesNamespaceConfigName,
|
||||
TelemetryConfigName,
|
||||
DumpLogsConfigName,
|
||||
KubeConfigPathName,
|
||||
configStructs.AnalysisDestinationTapName,
|
||||
configStructs.SleepIntervalSecTapName,
|
||||
configStructs.IgnoredUserAgentsTapName,
|
||||
}
|
||||
|
||||
var Config = ConfigStruct{}
|
||||
|
||||
func (config *ConfigStruct) Validate() error {
|
||||
if config.IsNsRestrictedMode() {
|
||||
if config.Tap.AllNamespaces || len(config.Tap.Namespaces) != 1 || config.Tap.Namespaces[0] != config.MizuResourcesNamespace {
|
||||
return fmt.Errorf("Not supported mode. Mizu can't resolve IPs in other namespaces when running in namespace restricted mode.\n"+
|
||||
"You can use the same namespace for --%s and --%s", configStructs.NamespacesTapName, MizuResourcesNamespaceConfigName)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func InitConfig(cmd *cobra.Command) error {
|
||||
if err := defaults.Set(&Config); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := mergeConfigFile(); err != nil {
|
||||
return fmt.Errorf("invalid config %w\n"+
|
||||
"you can regenerate the file using `mizu config -r` or just remove it %v", err, GetConfigFilePath())
|
||||
}
|
||||
|
||||
cmd.Flags().Visit(initFlag)
|
||||
|
||||
finalConfigPrettified, _ := uiUtils.PrettyJson(Config)
|
||||
Log.Debugf("Init config finished\n Final config: %v", finalConfigPrettified)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func GetConfigWithDefaults() (string, error) {
|
||||
defaultConf := ConfigStruct{}
|
||||
if err := defaults.Set(&defaultConf); err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
// TODO: change to generic solution
|
||||
defaultConf.AgentImage = ""
|
||||
|
||||
return uiUtils.PrettyYaml(defaultConf)
|
||||
}
|
||||
|
||||
func GetConfigFilePath() string {
|
||||
return path.Join(GetMizuFolderPath(), "config.yaml")
|
||||
}
|
||||
|
||||
func mergeConfigFile() error {
|
||||
reader, openErr := os.Open(GetConfigFilePath())
|
||||
if openErr != nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
buf, readErr := ioutil.ReadAll(reader)
|
||||
if readErr != nil {
|
||||
return readErr
|
||||
}
|
||||
|
||||
if err := yaml.Unmarshal(buf, &Config); err != nil {
|
||||
return err
|
||||
}
|
||||
Log.Debugf("Found config file, merged to default options")
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func initFlag(f *pflag.Flag) {
|
||||
configElem := reflect.ValueOf(&Config).Elem()
|
||||
|
||||
sliceValue, isSliceValue := f.Value.(pflag.SliceValue)
|
||||
if !isSliceValue {
|
||||
mergeFlagValue(configElem, f.Name, f.Value.String())
|
||||
return
|
||||
}
|
||||
|
||||
if f.Name == SetCommandName {
|
||||
mergeSetFlag(sliceValue.GetSlice())
|
||||
return
|
||||
}
|
||||
|
||||
mergeFlagValues(configElem, f.Name, sliceValue.GetSlice())
|
||||
}
|
||||
|
||||
func mergeSetFlag(setValues []string) {
|
||||
configElem := reflect.ValueOf(&Config).Elem()
|
||||
|
||||
for _, setValue := range setValues {
|
||||
if !strings.Contains(setValue, Separator) {
|
||||
Log.Warningf(uiUtils.Warning, fmt.Sprintf("Ignoring set argument %s (set argument format: <flag name>=<flag value>)", setValue))
|
||||
}
|
||||
|
||||
split := strings.SplitN(setValue, Separator, 2)
|
||||
if len(split) != 2 {
|
||||
Log.Warningf(uiUtils.Warning, fmt.Sprintf("Ignoring set argument %s (set argument format: <flag name>=<flag value>)", setValue))
|
||||
}
|
||||
|
||||
argumentKey, argumentValue := split[0], split[1]
|
||||
|
||||
if !Contains(allowedSetFlags, argumentKey) {
|
||||
Log.Warningf(uiUtils.Warning, fmt.Sprintf("Ignoring set argument %s, flag name must be one of the following: \"%s\"", setValue, strings.Join(allowedSetFlags, "\", \"")))
|
||||
}
|
||||
|
||||
mergeFlagValue(configElem, argumentKey, argumentValue)
|
||||
}
|
||||
}
|
||||
|
||||
func mergeFlagValue(currentElem reflect.Value, flagKey string, flagValue string) {
|
||||
for i := 0; i < currentElem.NumField(); i++ {
|
||||
currentField := currentElem.Type().Field(i)
|
||||
currentFieldByName := currentElem.FieldByName(currentField.Name)
|
||||
|
||||
if currentField.Type.Kind() == reflect.Struct {
|
||||
mergeFlagValue(currentFieldByName, flagKey, flagValue)
|
||||
continue
|
||||
}
|
||||
|
||||
if currentField.Tag.Get("yaml") != flagKey {
|
||||
continue
|
||||
}
|
||||
|
||||
flagValueKind := currentField.Type.Kind()
|
||||
|
||||
parsedValue, err := getParsedValue(flagValueKind, flagValue)
|
||||
if err != nil {
|
||||
Log.Warningf(uiUtils.Red, fmt.Sprintf("Invalid value %v for flag name %s, expected %s", flagValue, flagKey, flagValueKind))
|
||||
return
|
||||
}
|
||||
|
||||
currentFieldByName.Set(parsedValue)
|
||||
}
|
||||
}
|
||||
|
||||
func mergeFlagValues(currentElem reflect.Value, flagKey string, flagValues []string) {
|
||||
for i := 0; i < currentElem.NumField(); i++ {
|
||||
currentField := currentElem.Type().Field(i)
|
||||
currentFieldByName := currentElem.FieldByName(currentField.Name)
|
||||
|
||||
if currentField.Type.Kind() == reflect.Struct {
|
||||
mergeFlagValues(currentFieldByName, flagKey, flagValues)
|
||||
continue
|
||||
}
|
||||
|
||||
if currentField.Tag.Get("yaml") != flagKey {
|
||||
continue
|
||||
}
|
||||
|
||||
flagValueKind := currentField.Type.Elem().Kind()
|
||||
|
||||
parsedValues := reflect.MakeSlice(reflect.SliceOf(currentField.Type.Elem()), 0, 0)
|
||||
for _, flagValue := range flagValues {
|
||||
parsedValue, err := getParsedValue(flagValueKind, flagValue)
|
||||
if err != nil {
|
||||
Log.Warningf(uiUtils.Red, fmt.Sprintf("Invalid value %v for flag name %s, expected %s", flagValue, flagKey, flagValueKind))
|
||||
return
|
||||
}
|
||||
|
||||
parsedValues = reflect.Append(parsedValues, parsedValue)
|
||||
}
|
||||
|
||||
currentFieldByName.Set(parsedValues)
|
||||
}
|
||||
}
|
||||
|
||||
func getParsedValue(kind reflect.Kind, value string) (reflect.Value, error) {
|
||||
switch kind {
|
||||
case reflect.String:
|
||||
return reflect.ValueOf(value), nil
|
||||
case reflect.Bool:
|
||||
boolArgumentValue, err := strconv.ParseBool(value)
|
||||
if err != nil {
|
||||
break
|
||||
}
|
||||
|
||||
return reflect.ValueOf(boolArgumentValue), nil
|
||||
case reflect.Int:
|
||||
intArgumentValue, err := strconv.ParseInt(value, 10, 64)
|
||||
if err != nil {
|
||||
break
|
||||
}
|
||||
|
||||
return reflect.ValueOf(int(intArgumentValue)), nil
|
||||
case reflect.Int8:
|
||||
intArgumentValue, err := strconv.ParseInt(value, 10, 8)
|
||||
if err != nil {
|
||||
break
|
||||
}
|
||||
|
||||
return reflect.ValueOf(int8(intArgumentValue)), nil
|
||||
case reflect.Int16:
|
||||
intArgumentValue, err := strconv.ParseInt(value, 10, 16)
|
||||
if err != nil {
|
||||
break
|
||||
}
|
||||
|
||||
return reflect.ValueOf(int16(intArgumentValue)), nil
|
||||
case reflect.Int32:
|
||||
intArgumentValue, err := strconv.ParseInt(value, 10, 32)
|
||||
if err != nil {
|
||||
break
|
||||
}
|
||||
|
||||
return reflect.ValueOf(int32(intArgumentValue)), nil
|
||||
case reflect.Int64:
|
||||
intArgumentValue, err := strconv.ParseInt(value, 10, 64)
|
||||
if err != nil {
|
||||
break
|
||||
}
|
||||
|
||||
return reflect.ValueOf(intArgumentValue), nil
|
||||
case reflect.Uint:
|
||||
uintArgumentValue, err := strconv.ParseUint(value, 10, 64)
|
||||
if err != nil {
|
||||
break
|
||||
}
|
||||
|
||||
return reflect.ValueOf(uint(uintArgumentValue)), nil
|
||||
case reflect.Uint8:
|
||||
uintArgumentValue, err := strconv.ParseUint(value, 10, 8)
|
||||
if err != nil {
|
||||
break
|
||||
}
|
||||
|
||||
return reflect.ValueOf(uint8(uintArgumentValue)), nil
|
||||
case reflect.Uint16:
|
||||
uintArgumentValue, err := strconv.ParseUint(value, 10, 16)
|
||||
if err != nil {
|
||||
break
|
||||
}
|
||||
|
||||
return reflect.ValueOf(uint16(uintArgumentValue)), nil
|
||||
case reflect.Uint32:
|
||||
uintArgumentValue, err := strconv.ParseUint(value, 10, 32)
|
||||
if err != nil {
|
||||
break
|
||||
}
|
||||
|
||||
return reflect.ValueOf(uint32(uintArgumentValue)), nil
|
||||
case reflect.Uint64:
|
||||
uintArgumentValue, err := strconv.ParseUint(value, 10, 64)
|
||||
if err != nil {
|
||||
break
|
||||
}
|
||||
|
||||
return reflect.ValueOf(uintArgumentValue), nil
|
||||
}
|
||||
|
||||
return reflect.ValueOf(nil), errors.New("value to parse does not match type")
|
||||
}
|
||||
@@ -1,35 +0,0 @@
|
||||
package mizu
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/up9inc/mizu/cli/mizu/configStructs"
|
||||
)
|
||||
|
||||
const (
|
||||
AgentImageConfigName = "agent-image"
|
||||
MizuResourcesNamespaceConfigName = "mizu-resources-namespace"
|
||||
TelemetryConfigName = "telemetry"
|
||||
DumpLogsConfigName = "dump-logs"
|
||||
KubeConfigPathName = "kube-config-path"
|
||||
)
|
||||
|
||||
type ConfigStruct struct {
|
||||
Tap configStructs.TapConfig `yaml:"tap"`
|
||||
Fetch configStructs.FetchConfig `yaml:"fetch"`
|
||||
Version configStructs.VersionConfig `yaml:"version"`
|
||||
View configStructs.ViewConfig `yaml:"view"`
|
||||
AgentImage string `yaml:"agent-image,omitempty"`
|
||||
MizuResourcesNamespace string `yaml:"mizu-resources-namespace" default:"mizu"`
|
||||
Telemetry bool `yaml:"telemetry" default:"true"`
|
||||
DumpLogs bool `yaml:"dump-logs" default:"false"`
|
||||
KubeConfigPath string `yaml:"kube-config-path" default:""`
|
||||
}
|
||||
|
||||
func (config *ConfigStruct) SetDefaults() {
|
||||
config.AgentImage = fmt.Sprintf("gcr.io/up9-docker-hub/mizu/%s:%s", Branch, SemVer)
|
||||
}
|
||||
|
||||
func (config *ConfigStruct) IsNsRestrictedMode() bool {
|
||||
return config.MizuResourcesNamespace != "mizu" // Notice "mizu" string must match the default MizuResourcesNamespace
|
||||
}
|
||||
@@ -1,11 +0,0 @@
|
||||
package configStructs
|
||||
|
||||
const (
|
||||
GuiPortViewName = "gui-port"
|
||||
KubeConfigPathViewName = "kube-config"
|
||||
)
|
||||
|
||||
type ViewConfig struct {
|
||||
GuiPort uint16 `yaml:"gui-port" default:"8899"`
|
||||
KubeConfigPath string `yaml:"kube-config"`
|
||||
}
|
||||
77
cli/mizu/fsUtils/mizuLogsUtils.go
Normal file
77
cli/mizu/fsUtils/mizuLogsUtils.go
Normal file
@@ -0,0 +1,77 @@
|
||||
package fsUtils
|
||||
|
||||
import (
|
||||
"archive/zip"
|
||||
"context"
|
||||
"fmt"
|
||||
"github.com/up9inc/mizu/cli/config"
|
||||
"github.com/up9inc/mizu/cli/kubernetes"
|
||||
"github.com/up9inc/mizu/cli/logger"
|
||||
"github.com/up9inc/mizu/cli/mizu"
|
||||
"os"
|
||||
"regexp"
|
||||
)
|
||||
|
||||
func DumpLogs(provider *kubernetes.Provider, ctx context.Context, filePath string) error {
|
||||
podExactRegex := regexp.MustCompile("^" + mizu.MizuResourcesPrefix)
|
||||
pods, err := provider.ListAllPodsMatchingRegex(ctx, podExactRegex, []string{config.Config.MizuResourcesNamespace})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if len(pods) == 0 {
|
||||
return fmt.Errorf("no mizu pods found in namespace %s", config.Config.MizuResourcesNamespace)
|
||||
}
|
||||
|
||||
newZipFile, err := os.Create(filePath)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer newZipFile.Close()
|
||||
zipWriter := zip.NewWriter(newZipFile)
|
||||
defer zipWriter.Close()
|
||||
|
||||
for _, pod := range pods {
|
||||
logs, err := provider.GetPodLogs(pod.Namespace, pod.Name, ctx)
|
||||
if err != nil {
|
||||
logger.Log.Errorf("Failed to get logs, %v", err)
|
||||
continue
|
||||
} else {
|
||||
logger.Log.Debugf("Successfully read log length %d for pod: %s.%s", len(logs), pod.Namespace, pod.Name)
|
||||
}
|
||||
|
||||
if err := AddStrToZip(zipWriter, logs, fmt.Sprintf("%s.%s.log", pod.Namespace, pod.Name)); err != nil {
|
||||
logger.Log.Errorf("Failed write logs, %v", err)
|
||||
} else {
|
||||
logger.Log.Debugf("Successfully added log length %d from pod: %s.%s", len(logs), pod.Namespace, pod.Name)
|
||||
}
|
||||
}
|
||||
|
||||
events, err := provider.GetNamespaceEvents(config.Config.MizuResourcesNamespace, ctx)
|
||||
if err != nil {
|
||||
logger.Log.Debugf("Failed to get k8b events, %v", err)
|
||||
} else {
|
||||
logger.Log.Debugf("Successfully read events for k8b namespace: %s", config.Config.MizuResourcesNamespace)
|
||||
}
|
||||
|
||||
if err := AddStrToZip(zipWriter, events, fmt.Sprintf("%s_events.log", config.Config.MizuResourcesNamespace)); err != nil {
|
||||
logger.Log.Debugf("Failed write logs, %v", err)
|
||||
} else {
|
||||
logger.Log.Debugf("Successfully added events for k8b namespace: %s", config.Config.MizuResourcesNamespace)
|
||||
}
|
||||
|
||||
if err := AddFileToZip(zipWriter, config.Config.ConfigFilePath); err != nil {
|
||||
logger.Log.Debugf("Failed write file, %v", err)
|
||||
} else {
|
||||
logger.Log.Debugf("Successfully added file %s", config.Config.ConfigFilePath)
|
||||
}
|
||||
|
||||
if err := AddFileToZip(zipWriter, logger.GetLogFilePath()); err != nil {
|
||||
logger.Log.Debugf("Failed write file, %v", err)
|
||||
} else {
|
||||
logger.Log.Debugf("Successfully added file %s", logger.GetLogFilePath())
|
||||
}
|
||||
|
||||
logger.Log.Infof("You can find the zip file with all logs in %s\n", filePath)
|
||||
return nil
|
||||
}
|
||||
@@ -3,9 +3,11 @@ package fsUtils
|
||||
import (
|
||||
"archive/zip"
|
||||
"fmt"
|
||||
"github.com/up9inc/mizu/cli/logger"
|
||||
"io"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
)
|
||||
|
||||
func AddFileToZip(zipWriter *zip.Writer, filename string) error {
|
||||
@@ -53,3 +55,60 @@ func AddStrToZip(writer *zip.Writer, logs string, fileName string) error {
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func Unzip(reader *zip.Reader, dest string) error {
|
||||
dest, _ = filepath.Abs(dest)
|
||||
_ = os.MkdirAll(dest, os.ModePerm)
|
||||
|
||||
// Closure to address file descriptors issue with all the deferred .Close() methods
|
||||
extractAndWriteFile := func(f *zip.File) error {
|
||||
rc, err := f.Open()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer func() {
|
||||
if err := rc.Close(); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}()
|
||||
|
||||
path := filepath.Join(dest, f.Name)
|
||||
|
||||
// Check for ZipSlip (Directory traversal)
|
||||
if !strings.HasPrefix(path, filepath.Clean(dest)+string(os.PathSeparator)) {
|
||||
return fmt.Errorf("illegal file path: %s", path)
|
||||
}
|
||||
|
||||
if f.FileInfo().IsDir() {
|
||||
_ = os.MkdirAll(path, f.Mode())
|
||||
} else {
|
||||
_ = os.MkdirAll(filepath.Dir(path), f.Mode())
|
||||
logger.Log.Infof("writing HAR file [ %v ]", path)
|
||||
f, err := os.OpenFile(path, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, f.Mode())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer func() {
|
||||
if err := f.Close(); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
logger.Log.Info(" done")
|
||||
}()
|
||||
|
||||
_, err = io.Copy(f, rc)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
for _, f := range reader.File {
|
||||
err := extractAndWriteFile(f)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
@@ -1,7 +1,7 @@
|
||||
package goUtils
|
||||
|
||||
import (
|
||||
"github.com/up9inc/mizu/cli/mizu"
|
||||
"github.com/up9inc/mizu/cli/logger"
|
||||
"reflect"
|
||||
"runtime/debug"
|
||||
)
|
||||
@@ -10,7 +10,7 @@ func HandleExcWrapper(fn interface{}, params ...interface{}) (result []reflect.V
|
||||
defer func() {
|
||||
if panicMessage := recover(); panicMessage != nil {
|
||||
stack := debug.Stack()
|
||||
mizu.Log.Fatalf("Unhandled panic: %v\n stack: %s", panicMessage, stack)
|
||||
logger.Log.Fatalf("Unhandled panic: %v\n stack: %s", panicMessage, stack)
|
||||
}
|
||||
}()
|
||||
f := reflect.ValueOf(fn)
|
||||
@@ -9,3 +9,17 @@ func Contains(slice []string, containsValue string) bool {
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
func Unique(slice []string) []string {
|
||||
keys := make(map[string]bool)
|
||||
var list []string
|
||||
|
||||
for _, entry := range slice {
|
||||
if _, value := keys[entry]; !value {
|
||||
keys[entry] = true
|
||||
list = append(list, entry)
|
||||
}
|
||||
}
|
||||
|
||||
return list
|
||||
}
|
||||
|
||||
130
cli/mizu/sliceUtils_test.go
Normal file
130
cli/mizu/sliceUtils_test.go
Normal file
@@ -0,0 +1,130 @@
|
||||
package mizu_test
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/up9inc/mizu/cli/mizu"
|
||||
"reflect"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestContainsExists(t *testing.T) {
|
||||
tests := []struct {
|
||||
Slice []string
|
||||
ContainsValue string
|
||||
Expected bool
|
||||
}{
|
||||
{Slice: []string{"apple", "orange", "banana", "grapes"}, ContainsValue: "apple", Expected: true},
|
||||
{Slice: []string{"apple", "orange", "banana", "grapes"}, ContainsValue: "orange", Expected: true},
|
||||
{Slice: []string{"apple", "orange", "banana", "grapes"}, ContainsValue: "banana", Expected: true},
|
||||
{Slice: []string{"apple", "orange", "banana", "grapes"}, ContainsValue: "grapes", Expected: true},
|
||||
}
|
||||
|
||||
for _, test := range tests {
|
||||
t.Run(test.ContainsValue, func(t *testing.T) {
|
||||
actual := mizu.Contains(test.Slice, test.ContainsValue)
|
||||
if actual != test.Expected {
|
||||
t.Errorf("unexpected result - Expected: %v, actual: %v", test.Expected, actual)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestContainsNotExists(t *testing.T) {
|
||||
tests := []struct {
|
||||
Slice []string
|
||||
ContainsValue string
|
||||
Expected bool
|
||||
}{
|
||||
{Slice: []string{"apple", "orange", "banana", "grapes"}, ContainsValue: "cat", Expected: false},
|
||||
{Slice: []string{"apple", "orange", "banana", "grapes"}, ContainsValue: "dog", Expected: false},
|
||||
{Slice: []string{"apple", "orange", "banana", "grapes"}, ContainsValue: "apples", Expected: false},
|
||||
{Slice: []string{"apple", "orange", "banana", "grapes"}, ContainsValue: "rapes", Expected: false},
|
||||
}
|
||||
|
||||
for _, test := range tests {
|
||||
t.Run(test.ContainsValue, func(t *testing.T) {
|
||||
actual := mizu.Contains(test.Slice, test.ContainsValue)
|
||||
if actual != test.Expected {
|
||||
t.Errorf("unexpected result - Expected: %v, actual: %v", test.Expected, actual)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestContainsEmptySlice(t *testing.T) {
|
||||
tests := []struct {
|
||||
Slice []string
|
||||
ContainsValue string
|
||||
Expected bool
|
||||
}{
|
||||
{Slice: []string{}, ContainsValue: "cat", Expected: false},
|
||||
{Slice: []string{}, ContainsValue: "dog", Expected: false},
|
||||
}
|
||||
|
||||
for _, test := range tests {
|
||||
t.Run(test.ContainsValue, func(t *testing.T) {
|
||||
actual := mizu.Contains(test.Slice, test.ContainsValue)
|
||||
if actual != test.Expected {
|
||||
t.Errorf("unexpected result - Expected: %v, actual: %v", test.Expected, actual)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestContainsNilSlice(t *testing.T) {
|
||||
tests := []struct {
|
||||
Slice []string
|
||||
ContainsValue string
|
||||
Expected bool
|
||||
}{
|
||||
{Slice: nil, ContainsValue: "cat", Expected: false},
|
||||
{Slice: nil, ContainsValue: "dog", Expected: false},
|
||||
}
|
||||
|
||||
for _, test := range tests {
|
||||
t.Run(test.ContainsValue, func(t *testing.T) {
|
||||
actual := mizu.Contains(test.Slice, test.ContainsValue)
|
||||
if actual != test.Expected {
|
||||
t.Errorf("unexpected result - Expected: %v, actual: %v", test.Expected, actual)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestUniqueNoDuplicateValues(t *testing.T) {
|
||||
tests := []struct {
|
||||
Slice []string
|
||||
Expected []string
|
||||
}{
|
||||
{Slice: []string{"apple", "orange", "banana", "grapes"}, Expected: []string{"apple", "orange", "banana", "grapes"}},
|
||||
{Slice: []string{"dog", "cat", "mouse"}, Expected: []string{"dog", "cat", "mouse"}},
|
||||
}
|
||||
|
||||
for index, test := range tests {
|
||||
t.Run(fmt.Sprintf("%v", index), func(t *testing.T) {
|
||||
actual := mizu.Unique(test.Slice)
|
||||
if !reflect.DeepEqual(test.Expected, actual) {
|
||||
t.Errorf("unexpected result - Expected: %v, actual: %v", test.Expected, actual)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestUniqueDuplicateValues(t *testing.T) {
|
||||
tests := []struct {
|
||||
Slice []string
|
||||
Expected []string
|
||||
}{
|
||||
{Slice: []string{"apple", "apple", "orange", "orange", "banana", "banana", "grapes", "grapes"}, Expected: []string{"apple", "orange", "banana", "grapes"}},
|
||||
{Slice: []string{"dog", "cat", "cat", "mouse"}, Expected: []string{"dog", "cat", "mouse"}},
|
||||
}
|
||||
|
||||
for index, test := range tests {
|
||||
t.Run(fmt.Sprintf("%v", index), func(t *testing.T) {
|
||||
actual := mizu.Unique(test.Slice)
|
||||
if !reflect.DeepEqual(test.Expected, actual) {
|
||||
t.Errorf("unexpected result - Expected: %v, actual: %v", test.Expected, actual)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -1,36 +0,0 @@
|
||||
package mizu
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"net/http"
|
||||
)
|
||||
|
||||
const telemetryUrl = "https://us-east4-up9-prod.cloudfunctions.net/mizu-telemetry"
|
||||
|
||||
func ReportRun(cmd string, args interface{}) {
|
||||
if !Config.Telemetry {
|
||||
Log.Debugf("not reporting due to config value")
|
||||
return
|
||||
}
|
||||
|
||||
argsBytes, _ := json.Marshal(args)
|
||||
argsMap := map[string]string{
|
||||
"telemetry_type": "execution",
|
||||
"cmd": cmd,
|
||||
"args": string(argsBytes),
|
||||
"component": "mizu_cli",
|
||||
"BuildTimestamp": BuildTimestamp,
|
||||
"Branch": Branch,
|
||||
"version": SemVer}
|
||||
argsMap["message"] = fmt.Sprintf("mizu %v - %v", argsMap["cmd"], string(argsBytes))
|
||||
|
||||
jsonValue, _ := json.Marshal(argsMap)
|
||||
|
||||
if resp, err := http.Post(telemetryUrl, "application/json", bytes.NewBuffer(jsonValue)); err != nil {
|
||||
Log.Debugf("error sending telemetry err: %v, response %v", err, resp)
|
||||
} else {
|
||||
Log.Debugf("Successfully reported telemetry")
|
||||
}
|
||||
}
|
||||
83
cli/mizu/version/versionCheck.go
Normal file
83
cli/mizu/version/versionCheck.go
Normal file
@@ -0,0 +1,83 @@
|
||||
package version
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"github.com/up9inc/mizu/cli/apiserver"
|
||||
"github.com/up9inc/mizu/cli/logger"
|
||||
"github.com/up9inc/mizu/cli/mizu"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"time"
|
||||
|
||||
"github.com/google/go-github/v37/github"
|
||||
"github.com/up9inc/mizu/cli/uiUtils"
|
||||
"github.com/up9inc/mizu/shared/semver"
|
||||
)
|
||||
|
||||
func CheckVersionCompatibility() (bool, error) {
|
||||
apiSemVer, err := apiserver.Provider.GetVersion()
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
if semver.SemVersion(apiSemVer).Major() == semver.SemVersion(mizu.SemVer).Major() &&
|
||||
semver.SemVersion(apiSemVer).Minor() == semver.SemVersion(mizu.SemVer).Minor() {
|
||||
return true, nil
|
||||
}
|
||||
|
||||
logger.Log.Errorf(uiUtils.Red, fmt.Sprintf("cli version (%s) is not compatible with api version (%s)", mizu.SemVer, apiSemVer))
|
||||
return false, nil
|
||||
}
|
||||
|
||||
func CheckNewerVersion(versionChan chan string) {
|
||||
logger.Log.Debugf("Checking for newer version...")
|
||||
start := time.Now()
|
||||
client := github.NewClient(nil)
|
||||
latestRelease, _, err := client.Repositories.GetLatestRelease(context.Background(), "up9inc", "mizu")
|
||||
if err != nil {
|
||||
logger.Log.Debugf("[ERROR] Failed to get latest release")
|
||||
versionChan <- ""
|
||||
return
|
||||
}
|
||||
|
||||
versionFileUrl := ""
|
||||
for _, asset := range latestRelease.Assets {
|
||||
if *asset.Name == "version.txt" {
|
||||
versionFileUrl = *asset.BrowserDownloadURL
|
||||
break
|
||||
}
|
||||
}
|
||||
if versionFileUrl == "" {
|
||||
logger.Log.Debugf("[ERROR] Version file not found in the latest release")
|
||||
versionChan <- ""
|
||||
return
|
||||
}
|
||||
|
||||
res, err := http.Get(versionFileUrl)
|
||||
if err != nil {
|
||||
logger.Log.Debugf("[ERROR] Failed to get the version file %v", err)
|
||||
versionChan <- ""
|
||||
return
|
||||
}
|
||||
|
||||
data, err := ioutil.ReadAll(res.Body)
|
||||
res.Body.Close()
|
||||
if err != nil {
|
||||
logger.Log.Debugf("[ERROR] Failed to read the version file -> %v", err)
|
||||
versionChan <- ""
|
||||
return
|
||||
}
|
||||
gitHubVersion := string(data)
|
||||
gitHubVersion = gitHubVersion[:len(gitHubVersion)-1]
|
||||
|
||||
gitHubVersionSemVer := semver.SemVersion(gitHubVersion)
|
||||
currentSemVer := semver.SemVersion(mizu.SemVer)
|
||||
logger.Log.Debugf("Finished version validation, github version %v, current version %v, took %v", gitHubVersion, currentSemVer, time.Since(start))
|
||||
|
||||
if gitHubVersionSemVer.GreaterThan(currentSemVer) {
|
||||
versionChan <- fmt.Sprintf("Update available! %v -> %v (%v)", mizu.SemVer, gitHubVersion, *latestRelease.HTMLURL)
|
||||
} else {
|
||||
versionChan <- ""
|
||||
}
|
||||
}
|
||||
@@ -1,93 +0,0 @@
|
||||
package mizu
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"time"
|
||||
|
||||
"github.com/google/go-github/v37/github"
|
||||
"github.com/up9inc/mizu/cli/uiUtils"
|
||||
"github.com/up9inc/mizu/shared"
|
||||
"github.com/up9inc/mizu/shared/semver"
|
||||
)
|
||||
|
||||
func getApiVersion(port uint16) (string, error) {
|
||||
versionUrl, _ := url.Parse(fmt.Sprintf("http://localhost:%d/mizu/metadata/version", port))
|
||||
req := &http.Request{
|
||||
Method: http.MethodGet,
|
||||
URL: versionUrl,
|
||||
}
|
||||
statusResp, err := http.DefaultClient.Do(req)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
defer statusResp.Body.Close()
|
||||
|
||||
versionResponse := &shared.VersionResponse{}
|
||||
if err := json.NewDecoder(statusResp.Body).Decode(&versionResponse); err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
return versionResponse.SemVer, nil
|
||||
}
|
||||
|
||||
func CheckVersionCompatibility(port uint16) (bool, error) {
|
||||
apiSemVer, err := getApiVersion(port)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
if semver.SemVersion(apiSemVer).Major() == semver.SemVersion(SemVer).Major() &&
|
||||
semver.SemVersion(apiSemVer).Minor() == semver.SemVersion(SemVer).Minor() {
|
||||
return true, nil
|
||||
}
|
||||
|
||||
Log.Errorf(uiUtils.Red, fmt.Sprintf("cli version (%s) is not compatible with api version (%s)", SemVer, apiSemVer))
|
||||
return false, nil
|
||||
}
|
||||
|
||||
func CheckNewerVersion() {
|
||||
Log.Debugf("Checking for newer version...")
|
||||
start := time.Now()
|
||||
client := github.NewClient(nil)
|
||||
latestRelease, _, err := client.Repositories.GetLatestRelease(context.Background(), "up9inc", "mizu")
|
||||
if err != nil {
|
||||
Log.Debugf("[ERROR] Failed to get latest release")
|
||||
return
|
||||
}
|
||||
|
||||
versionFileUrl := ""
|
||||
for _, asset := range latestRelease.Assets {
|
||||
if *asset.Name == "version.txt" {
|
||||
versionFileUrl = *asset.BrowserDownloadURL
|
||||
break
|
||||
}
|
||||
}
|
||||
if versionFileUrl == "" {
|
||||
Log.Debugf("[ERROR] Version file not found in the latest release")
|
||||
return
|
||||
}
|
||||
|
||||
res, err := http.Get(versionFileUrl)
|
||||
if err != nil {
|
||||
Log.Debugf("[ERROR] Failed to get the version file %v", err)
|
||||
return
|
||||
}
|
||||
|
||||
data, err := ioutil.ReadAll(res.Body)
|
||||
res.Body.Close()
|
||||
if err != nil {
|
||||
Log.Debugf("[ERROR] Failed to read the version file -> %v", err)
|
||||
return
|
||||
}
|
||||
gitHubVersion := string(data)
|
||||
gitHubVersion = gitHubVersion[:len(gitHubVersion)-1]
|
||||
Log.Debugf("Finished version validation, took %v", time.Since(start))
|
||||
if SemVer < gitHubVersion {
|
||||
Log.Infof(uiUtils.Yellow, fmt.Sprintf("Update available! %v -> %v (%v)", SemVer, gitHubVersion, *latestRelease.HTMLURL))
|
||||
}
|
||||
}
|
||||
93
cli/telemetry/telemetry.go
Normal file
93
cli/telemetry/telemetry.go
Normal file
@@ -0,0 +1,93 @@
|
||||
package telemetry
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"github.com/denisbrodbeck/machineid"
|
||||
"github.com/up9inc/mizu/cli/apiserver"
|
||||
"github.com/up9inc/mizu/cli/config"
|
||||
"github.com/up9inc/mizu/cli/logger"
|
||||
"github.com/up9inc/mizu/cli/mizu"
|
||||
"net/http"
|
||||
)
|
||||
|
||||
const telemetryUrl = "https://us-east4-up9-prod.cloudfunctions.net/mizu-telemetry"
|
||||
|
||||
func ReportRun(cmd string, args interface{}) {
|
||||
if !shouldRunTelemetry() {
|
||||
logger.Log.Debugf("not reporting telemetry")
|
||||
return
|
||||
}
|
||||
|
||||
argsBytes, _ := json.Marshal(args)
|
||||
argsMap := map[string]interface{}{
|
||||
"cmd": cmd,
|
||||
"args": string(argsBytes),
|
||||
}
|
||||
|
||||
if err := sendTelemetry("Execution", argsMap); err != nil {
|
||||
logger.Log.Debug(err)
|
||||
return
|
||||
}
|
||||
|
||||
logger.Log.Debugf("successfully reported telemetry for cmd %v", cmd)
|
||||
}
|
||||
|
||||
func ReportAPICalls() {
|
||||
if !shouldRunTelemetry() {
|
||||
logger.Log.Debugf("not reporting telemetry")
|
||||
return
|
||||
}
|
||||
|
||||
generalStats, err := apiserver.Provider.GetGeneralStats()
|
||||
if err != nil {
|
||||
logger.Log.Debugf("[ERROR] failed get general stats from api server %v", err)
|
||||
return
|
||||
}
|
||||
|
||||
argsMap := map[string]interface{}{
|
||||
"apiCallsCount": generalStats["EntriesCount"],
|
||||
"firstAPICallTimestamp": generalStats["FirstEntryTimestamp"],
|
||||
"lastAPICallTimestamp": generalStats["LastEntryTimestamp"],
|
||||
}
|
||||
|
||||
if err := sendTelemetry("APICalls", argsMap); err != nil {
|
||||
logger.Log.Debug(err)
|
||||
return
|
||||
}
|
||||
|
||||
logger.Log.Debugf("successfully reported telemetry of api calls")
|
||||
}
|
||||
|
||||
func shouldRunTelemetry() bool {
|
||||
if !config.Config.Telemetry {
|
||||
return false
|
||||
}
|
||||
|
||||
if mizu.Branch != "main" && mizu.Branch != "develop" {
|
||||
return false
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
func sendTelemetry(telemetryType string, argsMap map[string]interface{}) error {
|
||||
argsMap["telemetryType"] = telemetryType
|
||||
argsMap["component"] = "mizu_cli"
|
||||
argsMap["buildTimestamp"] = mizu.BuildTimestamp
|
||||
argsMap["branch"] = mizu.Branch
|
||||
argsMap["version"] = mizu.SemVer
|
||||
|
||||
if machineId, err := machineid.ProtectedID("mizu"); err == nil {
|
||||
argsMap["machineId"] = machineId
|
||||
}
|
||||
|
||||
jsonValue, _ := json.Marshal(argsMap)
|
||||
|
||||
if resp, err := http.Post(telemetryUrl, "application/json", bytes.NewBuffer(jsonValue)); err != nil {
|
||||
return fmt.Errorf("ERROR: failed sending telemetry, err: %v, response %v", err, resp)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
8
codecov.yml
Normal file
8
codecov.yml
Normal file
@@ -0,0 +1,8 @@
|
||||
coverage:
|
||||
status:
|
||||
project:
|
||||
default:
|
||||
threshold: 1%
|
||||
patch:
|
||||
default:
|
||||
enabled: no
|
||||
78
docs/POLICY_RULES.md
Normal file
78
docs/POLICY_RULES.md
Normal file
@@ -0,0 +1,78 @@
|
||||
|
||||
# API rules validation
|
||||
|
||||
This feature allows you to define set of simple rules, and test the API against them.
|
||||
Such validation may test response for specific JSON fields, headers, etc.
|
||||
|
||||
## Examples
|
||||
|
||||
|
||||
Example 1: HTTP request (REST API call) that didn’t pass validation is highlighted in red
|
||||
|
||||

|
||||
|
||||
- - -
|
||||
|
||||
|
||||
Example 2: Details pane shows the validation rule details and whether it passed or failed
|
||||
|
||||

|
||||
|
||||
|
||||
## How to use
|
||||
To use this feature - create simple rules file (see details below) and pass this file as parameter to `mizu tap` command. For example, if rules are stored in file named `rules.yaml` — run the following command:
|
||||
|
||||
|
||||
```shell
|
||||
mizu tap --test-rules rules.yaml PODNAME
|
||||
```
|
||||
|
||||
|
||||
|
||||
## Rules file structure
|
||||
|
||||
The structure of the test-rules-file is:
|
||||
|
||||
* `name`: string, name of the rule
|
||||
* `type`: string, type of the rule, must be `json` or `header` or `latency`
|
||||
* `key`: string, [jsonpath](https://code.google.com/archive/p/jsonpath/wikis/Javascript.wiki) used only in `json` or `header` type
|
||||
* `value`: string, [regex](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Regular_Expressions) used only in `json` or `header` type
|
||||
* `service`: string, [regex](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Regular_Expressions) service name to filter
|
||||
* `path`: string, [regex](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Regular_Expressions) URL path to filter
|
||||
* `latency`: integer, time in ms of the expected latency.
|
||||
|
||||
|
||||
### For example:
|
||||
|
||||
```yaml
|
||||
rules:
|
||||
- name: holy-in-name-property
|
||||
type: json
|
||||
key: "$.name"
|
||||
value: "Holy"
|
||||
service: "catalogue.*"
|
||||
path: "catalogue.*"
|
||||
- name: content-length-header
|
||||
type: header
|
||||
key: "Content-Le.*"
|
||||
value: "(\\d+(?:\\.\\d+)?)"
|
||||
- name: latency-test
|
||||
type: latency
|
||||
latency: 1
|
||||
service: "carts.*"
|
||||
```
|
||||
|
||||
### Explanation:
|
||||
|
||||
* First rule `holy-in-name-property`:
|
||||
|
||||
> This rule will be applied to all request made to `catalogue.*` services with `catalogue.*` on the URL path with a json response containing a `$.name` field. If the value of `$.name` is `Holy` than is marked as success, marked as failure otherwise.
|
||||
|
||||
* Second rule `content-length-header`:
|
||||
|
||||
> This rule will be applied to all request that has `Content-Le.*` on header. If the value of `Content-Le.*` is `(\\d+(?:\\.\\d+)?)` (number), will be marked as success, marked as failure otherwise.
|
||||
|
||||
* Third rule `latency-test`:
|
||||
|
||||
> This rule will be applied to all request made to `carts.*` services. If the latency of the response is greater than `1` will be marked as failure, marked as success otherwise.
|
||||
|
||||
@@ -5,7 +5,7 @@ import (
|
||||
"io/ioutil"
|
||||
"strings"
|
||||
|
||||
yaml "gopkg.in/yaml.v3"
|
||||
"gopkg.in/yaml.v3"
|
||||
)
|
||||
|
||||
type WebSocketMessageType string
|
||||
|
||||
@@ -26,3 +26,23 @@ func (v SemVersion) Patch() string {
|
||||
_, _, patch := v.Breakdown()
|
||||
return patch
|
||||
}
|
||||
|
||||
func (v SemVersion) GreaterThan(v2 SemVersion) bool {
|
||||
if v.Major() > v2.Major() {
|
||||
return true
|
||||
} else if v.Major() < v2.Major() {
|
||||
return false
|
||||
}
|
||||
|
||||
if v.Minor() > v2.Minor() {
|
||||
return true
|
||||
} else if v.Minor() < v2.Minor() {
|
||||
return false
|
||||
}
|
||||
|
||||
if v.Patch() > v2.Patch() {
|
||||
return true
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
176
tap/api/api.go
Normal file
176
tap/api/api.go
Normal file
@@ -0,0 +1,176 @@
|
||||
package api
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"plugin"
|
||||
"sync"
|
||||
"time"
|
||||
)
|
||||
|
||||
type Protocol struct {
|
||||
Name string `json:"name"`
|
||||
LongName string `json:"long_name"`
|
||||
Abbreviation string `json:"abbreviation"`
|
||||
Version string `json:"version"`
|
||||
BackgroundColor string `json:"background_color"`
|
||||
ForegroundColor string `json:"foreground_color"`
|
||||
FontSize int8 `json:"font_size"`
|
||||
ReferenceLink string `json:"reference_link"`
|
||||
Ports []string `json:"ports"`
|
||||
Priority uint8 `json:"priority"`
|
||||
}
|
||||
|
||||
type Extension struct {
|
||||
Protocol Protocol
|
||||
Path string
|
||||
Plug *plugin.Plugin
|
||||
Dissector Dissector
|
||||
MatcherMap *sync.Map
|
||||
}
|
||||
|
||||
type ConnectionInfo struct {
|
||||
ClientIP string
|
||||
ClientPort string
|
||||
ServerIP string
|
||||
ServerPort string
|
||||
IsOutgoing bool
|
||||
}
|
||||
|
||||
type TcpID struct {
|
||||
SrcIP string
|
||||
DstIP string
|
||||
SrcPort string
|
||||
DstPort string
|
||||
Ident string
|
||||
}
|
||||
|
||||
type CounterPair struct {
|
||||
Request uint
|
||||
Response uint
|
||||
}
|
||||
|
||||
type GenericMessage struct {
|
||||
IsRequest bool `json:"is_request"`
|
||||
CaptureTime time.Time `json:"capture_time"`
|
||||
Payload interface{} `json:"payload"`
|
||||
}
|
||||
|
||||
type RequestResponsePair struct {
|
||||
Request GenericMessage `json:"request"`
|
||||
Response GenericMessage `json:"response"`
|
||||
}
|
||||
|
||||
// `Protocol` is modified in the later stages of data propagation. Therefore it's not a pointer.
|
||||
type OutputChannelItem struct {
|
||||
Protocol Protocol
|
||||
Timestamp int64
|
||||
ConnectionInfo *ConnectionInfo
|
||||
Pair *RequestResponsePair
|
||||
}
|
||||
|
||||
type SuperTimer struct {
|
||||
CaptureTime time.Time
|
||||
}
|
||||
|
||||
type Dissector interface {
|
||||
Register(*Extension)
|
||||
Ping()
|
||||
Dissect(b *bufio.Reader, isClient bool, tcpID *TcpID, counterPair *CounterPair, superTimer *SuperTimer, emitter Emitter) error
|
||||
Analyze(item *OutputChannelItem, entryId string, resolvedSource string, resolvedDestination string) *MizuEntry
|
||||
Summarize(entry *MizuEntry) *BaseEntryDetails
|
||||
Represent(entry *MizuEntry) (protocol Protocol, object []byte, bodySize int64, err error)
|
||||
}
|
||||
|
||||
type Emitting struct {
|
||||
OutputChannel chan *OutputChannelItem
|
||||
}
|
||||
|
||||
type Emitter interface {
|
||||
Emit(item *OutputChannelItem)
|
||||
}
|
||||
|
||||
func (e *Emitting) Emit(item *OutputChannelItem) {
|
||||
e.OutputChannel <- item
|
||||
}
|
||||
|
||||
type MizuEntry struct {
|
||||
ID uint `gorm:"primarykey"`
|
||||
CreatedAt time.Time
|
||||
UpdatedAt time.Time
|
||||
ProtocolName string `json:"protocol_key" gorm:"column:protocolKey"`
|
||||
ProtocolVersion string `json:"protocol_version" gorm:"column:protocolVersion"`
|
||||
Entry string `json:"entry,omitempty" gorm:"column:entry"`
|
||||
EntryId string `json:"entryId" gorm:"column:entryId"`
|
||||
Url string `json:"url" gorm:"column:url"`
|
||||
Method string `json:"method" gorm:"column:method"`
|
||||
Status int `json:"status" gorm:"column:status"`
|
||||
RequestSenderIp string `json:"requestSenderIp" gorm:"column:requestSenderIp"`
|
||||
Service string `json:"service" gorm:"column:service"`
|
||||
Timestamp int64 `json:"timestamp" gorm:"column:timestamp"`
|
||||
ElapsedTime int64 `json:"elapsedTime" gorm:"column:elapsedTime"`
|
||||
Path string `json:"path" gorm:"column:path"`
|
||||
ResolvedSource string `json:"resolvedSource,omitempty" gorm:"column:resolvedSource"`
|
||||
ResolvedDestination string `json:"resolvedDestination,omitempty" gorm:"column:resolvedDestination"`
|
||||
SourceIp string `json:"sourceIp,omitempty" gorm:"column:sourceIp"`
|
||||
DestinationIp string `json:"destinationIp,omitempty" gorm:"column:destinationIp"`
|
||||
SourcePort string `json:"sourcePort,omitempty" gorm:"column:sourcePort"`
|
||||
DestinationPort string `json:"destinationPort,omitempty" gorm:"column:destinationPort"`
|
||||
IsOutgoing bool `json:"isOutgoing,omitempty" gorm:"column:isOutgoing"`
|
||||
EstimatedSizeBytes int `json:"-" gorm:"column:estimatedSizeBytes"`
|
||||
}
|
||||
|
||||
type MizuEntryWrapper struct {
|
||||
Protocol Protocol `json:"protocol"`
|
||||
Representation string `json:"representation"`
|
||||
BodySize int64 `json:"bodySize"`
|
||||
Data MizuEntry `json:"data"`
|
||||
}
|
||||
|
||||
type BaseEntryDetails struct {
|
||||
Id string `json:"id,omitempty"`
|
||||
Protocol Protocol `json:"protocol,omitempty"`
|
||||
Url string `json:"url,omitempty"`
|
||||
RequestSenderIp string `json:"request_sender_ip,omitempty"`
|
||||
Service string `json:"service,omitempty"`
|
||||
Summary string `json:"summary,omitempty"`
|
||||
StatusCode int `json:"status_code"`
|
||||
Method string `json:"method,omitempty"`
|
||||
Timestamp int64 `json:"timestamp,omitempty"`
|
||||
SourceIp string `json:"source_ip,omitempty"`
|
||||
DestinationIp string `json:"destination_ip,omitempty"`
|
||||
SourcePort string `json:"source_port,omitempty"`
|
||||
DestinationPort string `json:"destination_port,omitempty"`
|
||||
IsOutgoing bool `json:"isOutgoing,omitempty"`
|
||||
Latency int64 `json:"latency,omitempty"`
|
||||
Rules ApplicableRules `json:"rules,omitempty"`
|
||||
}
|
||||
|
||||
type ApplicableRules struct {
|
||||
Latency int64 `json:"latency,omitempty"`
|
||||
Status bool `json:"status,omitempty"`
|
||||
NumberOfRules int `json:"numberOfRules,omitempty"`
|
||||
}
|
||||
|
||||
type DataUnmarshaler interface {
|
||||
UnmarshalData(*MizuEntry) error
|
||||
}
|
||||
|
||||
func (bed *BaseEntryDetails) UnmarshalData(entry *MizuEntry) error {
|
||||
entryUrl := entry.Url
|
||||
service := entry.Service
|
||||
bed.Id = entry.EntryId
|
||||
bed.Url = entryUrl
|
||||
bed.Service = service
|
||||
bed.Summary = entry.Path
|
||||
bed.StatusCode = entry.Status
|
||||
bed.Method = entry.Method
|
||||
bed.Timestamp = entry.Timestamp
|
||||
bed.RequestSenderIp = entry.RequestSenderIp
|
||||
bed.IsOutgoing = entry.IsOutgoing
|
||||
return nil
|
||||
}
|
||||
|
||||
const (
|
||||
TABLE string = "table"
|
||||
BODY string = "body"
|
||||
)
|
||||
3
tap/api/go.mod
Normal file
3
tap/api/go.mod
Normal file
@@ -0,0 +1,3 @@
|
||||
module github.com/up9inc/mizu/tap/api
|
||||
|
||||
go 1.16
|
||||
@@ -1,11 +1,12 @@
|
||||
package tap
|
||||
|
||||
import (
|
||||
"github.com/romana/rlog"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/google/gopacket/reassembly"
|
||||
"github.com/romana/rlog"
|
||||
"github.com/up9inc/mizu/tap/api"
|
||||
)
|
||||
|
||||
type CleanerStats struct {
|
||||
@@ -17,7 +18,6 @@ type CleanerStats struct {
|
||||
type Cleaner struct {
|
||||
assembler *reassembly.Assembler
|
||||
assemblerMutex *sync.Mutex
|
||||
matcher *requestResponseMatcher
|
||||
cleanPeriod time.Duration
|
||||
connectionTimeout time.Duration
|
||||
stats CleanerStats
|
||||
@@ -32,13 +32,15 @@ func (cl *Cleaner) clean() {
|
||||
flushed, closed := cl.assembler.FlushCloseOlderThan(startCleanTime.Add(-cl.connectionTimeout))
|
||||
cl.assemblerMutex.Unlock()
|
||||
|
||||
deleted := cl.matcher.deleteOlderThan(startCleanTime.Add(-cl.connectionTimeout))
|
||||
for _, extension := range extensions {
|
||||
deleted := deleteOlderThan(extension.MatcherMap, startCleanTime.Add(-cl.connectionTimeout))
|
||||
cl.stats.deleted += deleted
|
||||
}
|
||||
|
||||
cl.statsMutex.Lock()
|
||||
rlog.Debugf("Assembler Stats after cleaning %s", cl.assembler.Dump())
|
||||
cl.stats.flushed += flushed
|
||||
cl.stats.closed += closed
|
||||
cl.stats.deleted += deleted
|
||||
cl.statsMutex.Unlock()
|
||||
}
|
||||
|
||||
@@ -70,3 +72,25 @@ func (cl *Cleaner) dumpStats() CleanerStats {
|
||||
|
||||
return stats
|
||||
}
|
||||
|
||||
func deleteOlderThan(matcherMap *sync.Map, t time.Time) int {
|
||||
numDeleted := 0
|
||||
|
||||
if matcherMap == nil {
|
||||
return numDeleted
|
||||
}
|
||||
|
||||
matcherMap.Range(func(key interface{}, value interface{}) bool {
|
||||
message, _ := value.(*api.GenericMessage)
|
||||
// TODO: Investigate the reason why `request` is `nil` in some rare occasion
|
||||
if message != nil {
|
||||
if message.CaptureTime.Before(t) {
|
||||
matcherMap.Delete(key)
|
||||
numDeleted++
|
||||
}
|
||||
}
|
||||
return true
|
||||
})
|
||||
|
||||
return numDeleted
|
||||
}
|
||||
|
||||
9
tap/extensions/amqp/go.mod
Normal file
9
tap/extensions/amqp/go.mod
Normal file
@@ -0,0 +1,9 @@
|
||||
module github.com/up9inc/mizu/tap/extensions/amqp
|
||||
|
||||
go 1.16
|
||||
|
||||
require (
|
||||
github.com/up9inc/mizu/tap/api v0.0.0
|
||||
)
|
||||
|
||||
replace github.com/up9inc/mizu/tap/api v0.0.0 => ../../api
|
||||
664
tap/extensions/amqp/helpers.go
Normal file
664
tap/extensions/amqp/helpers.go
Normal file
@@ -0,0 +1,664 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"strconv"
|
||||
"time"
|
||||
|
||||
"github.com/up9inc/mizu/tap/api"
|
||||
)
|
||||
|
||||
var connectionMethodMap = map[int]string{
|
||||
10: "connection start",
|
||||
11: "connection start-ok",
|
||||
20: "connection secure",
|
||||
21: "connection secure-ok",
|
||||
30: "connection tune",
|
||||
31: "connection tune-ok",
|
||||
40: "connection open",
|
||||
41: "connection open-ok",
|
||||
50: "connection close",
|
||||
51: "connection close-ok",
|
||||
60: "connection blocked",
|
||||
61: "connection unblocked",
|
||||
}
|
||||
|
||||
var channelMethodMap = map[int]string{
|
||||
10: "channel open",
|
||||
11: "channel open-ok",
|
||||
20: "channel flow",
|
||||
21: "channel flow-ok",
|
||||
40: "channel close",
|
||||
41: "channel close-ok",
|
||||
}
|
||||
|
||||
var exchangeMethodMap = map[int]string{
|
||||
10: "exchange declare",
|
||||
11: "exchange declare-ok",
|
||||
20: "exchange delete",
|
||||
21: "exchange delete-ok",
|
||||
30: "exchange bind",
|
||||
31: "exchange bind-ok",
|
||||
40: "exchange unbind",
|
||||
51: "exchange unbind-ok",
|
||||
}
|
||||
|
||||
var queueMethodMap = map[int]string{
|
||||
10: "queue declare",
|
||||
11: "queue declare-ok",
|
||||
20: "queue bind",
|
||||
21: "queue bind-ok",
|
||||
50: "queue unbind",
|
||||
51: "queue unbind-ok",
|
||||
30: "queue purge",
|
||||
31: "queue purge-ok",
|
||||
40: "queue delete",
|
||||
41: "queue delete-ok",
|
||||
}
|
||||
|
||||
var basicMethodMap = map[int]string{
|
||||
10: "basic qos",
|
||||
11: "basic qos-ok",
|
||||
20: "basic consume",
|
||||
21: "basic consume-ok",
|
||||
30: "basic cancel",
|
||||
31: "basic cancel-ok",
|
||||
40: "basic publish",
|
||||
50: "basic return",
|
||||
60: "basic deliver",
|
||||
70: "basic get",
|
||||
71: "basic get-ok",
|
||||
72: "basic get-empty",
|
||||
80: "basic ack",
|
||||
90: "basic reject",
|
||||
100: "basic recover-async",
|
||||
110: "basic recover",
|
||||
111: "basic recover-ok",
|
||||
120: "basic nack",
|
||||
}
|
||||
|
||||
var txMethodMap = map[int]string{
|
||||
10: "tx select",
|
||||
11: "tx select-ok",
|
||||
20: "tx commit",
|
||||
21: "tx commit-ok",
|
||||
30: "tx rollback",
|
||||
31: "tx rollback-ok",
|
||||
}
|
||||
|
||||
type AMQPWrapper struct {
|
||||
Method string `json:"method"`
|
||||
Url string `json:"url"`
|
||||
Details interface{} `json:"details"`
|
||||
}
|
||||
|
||||
func emitAMQP(event interface{}, _type string, method string, connectionInfo *api.ConnectionInfo, captureTime time.Time, emitter api.Emitter) {
|
||||
request := &api.GenericMessage{
|
||||
IsRequest: true,
|
||||
CaptureTime: captureTime,
|
||||
Payload: AMQPPayload{
|
||||
Data: &AMQPWrapper{
|
||||
Method: method,
|
||||
Url: "",
|
||||
Details: event,
|
||||
},
|
||||
},
|
||||
}
|
||||
item := &api.OutputChannelItem{
|
||||
Protocol: protocol,
|
||||
Timestamp: captureTime.UnixNano() / int64(time.Millisecond),
|
||||
ConnectionInfo: connectionInfo,
|
||||
Pair: &api.RequestResponsePair{
|
||||
Request: *request,
|
||||
Response: api.GenericMessage{},
|
||||
},
|
||||
}
|
||||
emitter.Emit(item)
|
||||
}
|
||||
|
||||
func representProperties(properties map[string]interface{}, rep []interface{}) ([]interface{}, string, string) {
|
||||
contentType := ""
|
||||
contentEncoding := ""
|
||||
deliveryMode := ""
|
||||
priority := ""
|
||||
correlationId := ""
|
||||
replyTo := ""
|
||||
expiration := ""
|
||||
messageId := ""
|
||||
timestamp := ""
|
||||
_type := ""
|
||||
userId := ""
|
||||
appId := ""
|
||||
|
||||
if properties["ContentType"] != nil {
|
||||
contentType = properties["ContentType"].(string)
|
||||
}
|
||||
if properties["ContentEncoding"] != nil {
|
||||
contentEncoding = properties["ContentEncoding"].(string)
|
||||
}
|
||||
if properties["Delivery Mode"] != nil {
|
||||
deliveryMode = fmt.Sprintf("%g", properties["DeliveryMode"].(float64))
|
||||
}
|
||||
if properties["Priority"] != nil {
|
||||
priority = fmt.Sprintf("%g", properties["Priority"].(float64))
|
||||
}
|
||||
if properties["CorrelationId"] != nil {
|
||||
correlationId = properties["CorrelationId"].(string)
|
||||
}
|
||||
if properties["ReplyTo"] != nil {
|
||||
replyTo = properties["ReplyTo"].(string)
|
||||
}
|
||||
if properties["Expiration"] != nil {
|
||||
expiration = properties["Expiration"].(string)
|
||||
}
|
||||
if properties["MessageId"] != nil {
|
||||
messageId = properties["MessageId"].(string)
|
||||
}
|
||||
if properties["Timestamp"] != nil {
|
||||
timestamp = properties["Timestamp"].(string)
|
||||
}
|
||||
if properties["Type"] != nil {
|
||||
_type = properties["Type"].(string)
|
||||
}
|
||||
if properties["UserId"] != nil {
|
||||
userId = properties["UserId"].(string)
|
||||
}
|
||||
if properties["AppId"] != nil {
|
||||
appId = properties["AppId"].(string)
|
||||
}
|
||||
|
||||
props, _ := json.Marshal([]map[string]string{
|
||||
{
|
||||
"name": "Content Type",
|
||||
"value": contentType,
|
||||
},
|
||||
{
|
||||
"name": "Content Encoding",
|
||||
"value": contentEncoding,
|
||||
},
|
||||
{
|
||||
"name": "Delivery Mode",
|
||||
"value": deliveryMode,
|
||||
},
|
||||
{
|
||||
"name": "Priority",
|
||||
"value": priority,
|
||||
},
|
||||
{
|
||||
"name": "Correlation ID",
|
||||
"value": correlationId,
|
||||
},
|
||||
{
|
||||
"name": "Reply To",
|
||||
"value": replyTo,
|
||||
},
|
||||
{
|
||||
"name": "Expiration",
|
||||
"value": expiration,
|
||||
},
|
||||
{
|
||||
"name": "Message ID",
|
||||
"value": messageId,
|
||||
},
|
||||
{
|
||||
"name": "Timestamp",
|
||||
"value": timestamp,
|
||||
},
|
||||
{
|
||||
"name": "Type",
|
||||
"value": _type,
|
||||
},
|
||||
{
|
||||
"name": "User ID",
|
||||
"value": userId,
|
||||
},
|
||||
{
|
||||
"name": "App ID",
|
||||
"value": appId,
|
||||
},
|
||||
})
|
||||
rep = append(rep, map[string]string{
|
||||
"type": api.TABLE,
|
||||
"title": "Properties",
|
||||
"data": string(props),
|
||||
})
|
||||
|
||||
return rep, contentType, contentEncoding
|
||||
}
|
||||
|
||||
func representBasicPublish(event map[string]interface{}) []interface{} {
|
||||
rep := make([]interface{}, 0)
|
||||
|
||||
details, _ := json.Marshal([]map[string]string{
|
||||
{
|
||||
"name": "Exchange",
|
||||
"value": event["Exchange"].(string),
|
||||
},
|
||||
{
|
||||
"name": "Routing Key",
|
||||
"value": event["RoutingKey"].(string),
|
||||
},
|
||||
{
|
||||
"name": "Mandatory",
|
||||
"value": strconv.FormatBool(event["Mandatory"].(bool)),
|
||||
},
|
||||
{
|
||||
"name": "Immediate",
|
||||
"value": strconv.FormatBool(event["Immediate"].(bool)),
|
||||
},
|
||||
})
|
||||
rep = append(rep, map[string]string{
|
||||
"type": api.TABLE,
|
||||
"title": "Details",
|
||||
"data": string(details),
|
||||
})
|
||||
|
||||
properties := event["Properties"].(map[string]interface{})
|
||||
rep, contentType, _ := representProperties(properties, rep)
|
||||
|
||||
if properties["Headers"] != nil {
|
||||
headers := make([]map[string]string, 0)
|
||||
for name, value := range properties["Headers"].(map[string]interface{}) {
|
||||
headers = append(headers, map[string]string{
|
||||
"name": name,
|
||||
"value": value.(string),
|
||||
})
|
||||
}
|
||||
headersMarshaled, _ := json.Marshal(headers)
|
||||
rep = append(rep, map[string]string{
|
||||
"type": api.TABLE,
|
||||
"title": "Headers",
|
||||
"data": string(headersMarshaled),
|
||||
})
|
||||
}
|
||||
|
||||
if event["Body"] != nil {
|
||||
rep = append(rep, map[string]string{
|
||||
"type": api.BODY,
|
||||
"title": "Body",
|
||||
"encoding": "base64",
|
||||
"mime_type": contentType,
|
||||
"data": event["Body"].(string),
|
||||
})
|
||||
}
|
||||
|
||||
return rep
|
||||
}
|
||||
|
||||
func representBasicDeliver(event map[string]interface{}) []interface{} {
|
||||
rep := make([]interface{}, 0)
|
||||
|
||||
consumerTag := ""
|
||||
deliveryTag := ""
|
||||
redelivered := ""
|
||||
|
||||
if event["ConsumerTag"] != nil {
|
||||
consumerTag = event["ConsumerTag"].(string)
|
||||
}
|
||||
if event["DeliveryTag"] != nil {
|
||||
deliveryTag = fmt.Sprintf("%g", event["DeliveryTag"].(float64))
|
||||
}
|
||||
if event["Redelivered"] != nil {
|
||||
redelivered = strconv.FormatBool(event["Redelivered"].(bool))
|
||||
}
|
||||
|
||||
details, _ := json.Marshal([]map[string]string{
|
||||
{
|
||||
"name": "Consumer Tag",
|
||||
"value": consumerTag,
|
||||
},
|
||||
{
|
||||
"name": "Delivery Tag",
|
||||
"value": deliveryTag,
|
||||
},
|
||||
{
|
||||
"name": "Redelivered",
|
||||
"value": redelivered,
|
||||
},
|
||||
{
|
||||
"name": "Exchange",
|
||||
"value": event["Exchange"].(string),
|
||||
},
|
||||
{
|
||||
"name": "Routing Key",
|
||||
"value": event["RoutingKey"].(string),
|
||||
},
|
||||
})
|
||||
rep = append(rep, map[string]string{
|
||||
"type": api.TABLE,
|
||||
"title": "Details",
|
||||
"data": string(details),
|
||||
})
|
||||
|
||||
properties := event["Properties"].(map[string]interface{})
|
||||
rep, contentType, _ := representProperties(properties, rep)
|
||||
|
||||
if properties["Headers"] != nil {
|
||||
headers := make([]map[string]string, 0)
|
||||
for name, value := range properties["Headers"].(map[string]interface{}) {
|
||||
headers = append(headers, map[string]string{
|
||||
"name": name,
|
||||
"value": value.(string),
|
||||
})
|
||||
}
|
||||
headersMarshaled, _ := json.Marshal(headers)
|
||||
rep = append(rep, map[string]string{
|
||||
"type": api.TABLE,
|
||||
"title": "Headers",
|
||||
"data": string(headersMarshaled),
|
||||
})
|
||||
}
|
||||
|
||||
if event["Body"] != nil {
|
||||
rep = append(rep, map[string]string{
|
||||
"type": api.BODY,
|
||||
"title": "Body",
|
||||
"encoding": "base64",
|
||||
"mime_type": contentType,
|
||||
"data": event["Body"].(string),
|
||||
})
|
||||
}
|
||||
|
||||
return rep
|
||||
}
|
||||
|
||||
func representQueueDeclare(event map[string]interface{}) []interface{} {
|
||||
rep := make([]interface{}, 0)
|
||||
|
||||
details, _ := json.Marshal([]map[string]string{
|
||||
{
|
||||
"name": "Queue",
|
||||
"value": event["Queue"].(string),
|
||||
},
|
||||
{
|
||||
"name": "Passive",
|
||||
"value": strconv.FormatBool(event["Passive"].(bool)),
|
||||
},
|
||||
{
|
||||
"name": "Durable",
|
||||
"value": strconv.FormatBool(event["Durable"].(bool)),
|
||||
},
|
||||
{
|
||||
"name": "Exclusive",
|
||||
"value": strconv.FormatBool(event["Exclusive"].(bool)),
|
||||
},
|
||||
{
|
||||
"name": "Auto Delete",
|
||||
"value": strconv.FormatBool(event["AutoDelete"].(bool)),
|
||||
},
|
||||
{
|
||||
"name": "NoWait",
|
||||
"value": strconv.FormatBool(event["NoWait"].(bool)),
|
||||
},
|
||||
})
|
||||
rep = append(rep, map[string]string{
|
||||
"type": api.TABLE,
|
||||
"title": "Details",
|
||||
"data": string(details),
|
||||
})
|
||||
|
||||
if event["Arguments"] != nil {
|
||||
headers := make([]map[string]string, 0)
|
||||
for name, value := range event["Arguments"].(map[string]interface{}) {
|
||||
headers = append(headers, map[string]string{
|
||||
"name": name,
|
||||
"value": value.(string),
|
||||
})
|
||||
}
|
||||
headersMarshaled, _ := json.Marshal(headers)
|
||||
rep = append(rep, map[string]string{
|
||||
"type": api.TABLE,
|
||||
"title": "Arguments",
|
||||
"data": string(headersMarshaled),
|
||||
})
|
||||
}
|
||||
|
||||
return rep
|
||||
}
|
||||
|
||||
func representExchangeDeclare(event map[string]interface{}) []interface{} {
|
||||
rep := make([]interface{}, 0)
|
||||
|
||||
details, _ := json.Marshal([]map[string]string{
|
||||
{
|
||||
"name": "Exchange",
|
||||
"value": event["Exchange"].(string),
|
||||
},
|
||||
{
|
||||
"name": "Type",
|
||||
"value": event["Type"].(string),
|
||||
},
|
||||
{
|
||||
"name": "Passive",
|
||||
"value": strconv.FormatBool(event["Passive"].(bool)),
|
||||
},
|
||||
{
|
||||
"name": "Durable",
|
||||
"value": strconv.FormatBool(event["Durable"].(bool)),
|
||||
},
|
||||
{
|
||||
"name": "Auto Delete",
|
||||
"value": strconv.FormatBool(event["AutoDelete"].(bool)),
|
||||
},
|
||||
{
|
||||
"name": "Internal",
|
||||
"value": strconv.FormatBool(event["Internal"].(bool)),
|
||||
},
|
||||
{
|
||||
"name": "NoWait",
|
||||
"value": strconv.FormatBool(event["NoWait"].(bool)),
|
||||
},
|
||||
})
|
||||
rep = append(rep, map[string]string{
|
||||
"type": api.TABLE,
|
||||
"title": "Details",
|
||||
"data": string(details),
|
||||
})
|
||||
|
||||
if event["Arguments"] != nil {
|
||||
headers := make([]map[string]string, 0)
|
||||
for name, value := range event["Arguments"].(map[string]interface{}) {
|
||||
headers = append(headers, map[string]string{
|
||||
"name": name,
|
||||
"value": value.(string),
|
||||
})
|
||||
}
|
||||
headersMarshaled, _ := json.Marshal(headers)
|
||||
rep = append(rep, map[string]string{
|
||||
"type": api.TABLE,
|
||||
"title": "Arguments",
|
||||
"data": string(headersMarshaled),
|
||||
})
|
||||
}
|
||||
|
||||
return rep
|
||||
}
|
||||
|
||||
func representConnectionStart(event map[string]interface{}) []interface{} {
|
||||
rep := make([]interface{}, 0)
|
||||
|
||||
details, _ := json.Marshal([]map[string]string{
|
||||
{
|
||||
"name": "Version Major",
|
||||
"value": fmt.Sprintf("%g", event["VersionMajor"].(float64)),
|
||||
},
|
||||
{
|
||||
"name": "Version Minor",
|
||||
"value": fmt.Sprintf("%g", event["VersionMinor"].(float64)),
|
||||
},
|
||||
{
|
||||
"name": "Mechanisms",
|
||||
"value": event["Mechanisms"].(string),
|
||||
},
|
||||
{
|
||||
"name": "Locales",
|
||||
"value": event["Locales"].(string),
|
||||
},
|
||||
})
|
||||
rep = append(rep, map[string]string{
|
||||
"type": api.TABLE,
|
||||
"title": "Details",
|
||||
"data": string(details),
|
||||
})
|
||||
|
||||
if event["ServerProperties"] != nil {
|
||||
headers := make([]map[string]string, 0)
|
||||
for name, value := range event["ServerProperties"].(map[string]interface{}) {
|
||||
var outcome string
|
||||
switch value.(type) {
|
||||
case string:
|
||||
outcome = value.(string)
|
||||
break
|
||||
case map[string]interface{}:
|
||||
x, _ := json.Marshal(value)
|
||||
outcome = string(x)
|
||||
break
|
||||
default:
|
||||
panic("Unknown data type for the server property!")
|
||||
}
|
||||
headers = append(headers, map[string]string{
|
||||
"name": name,
|
||||
"value": outcome,
|
||||
})
|
||||
}
|
||||
headersMarshaled, _ := json.Marshal(headers)
|
||||
rep = append(rep, map[string]string{
|
||||
"type": api.TABLE,
|
||||
"title": "Server Properties",
|
||||
"data": string(headersMarshaled),
|
||||
})
|
||||
}
|
||||
|
||||
return rep
|
||||
}
|
||||
|
||||
func representConnectionClose(event map[string]interface{}) []interface{} {
|
||||
rep := make([]interface{}, 0)
|
||||
|
||||
details, _ := json.Marshal([]map[string]string{
|
||||
{
|
||||
"name": "Reply Code",
|
||||
"value": fmt.Sprintf("%g", event["ReplyCode"].(float64)),
|
||||
},
|
||||
{
|
||||
"name": "Reply Text",
|
||||
"value": event["ReplyText"].(string),
|
||||
},
|
||||
{
|
||||
"name": "Class ID",
|
||||
"value": fmt.Sprintf("%g", event["ClassId"].(float64)),
|
||||
},
|
||||
{
|
||||
"name": "Method ID",
|
||||
"value": fmt.Sprintf("%g", event["MethodId"].(float64)),
|
||||
},
|
||||
})
|
||||
rep = append(rep, map[string]string{
|
||||
"type": api.TABLE,
|
||||
"title": "Details",
|
||||
"data": string(details),
|
||||
})
|
||||
|
||||
return rep
|
||||
}
|
||||
|
||||
func representQueueBind(event map[string]interface{}) []interface{} {
|
||||
rep := make([]interface{}, 0)
|
||||
|
||||
details, _ := json.Marshal([]map[string]string{
|
||||
{
|
||||
"name": "Queue",
|
||||
"value": event["Queue"].(string),
|
||||
},
|
||||
{
|
||||
"name": "Exchange",
|
||||
"value": event["Exchange"].(string),
|
||||
},
|
||||
{
|
||||
"name": "RoutingKey",
|
||||
"value": event["RoutingKey"].(string),
|
||||
},
|
||||
{
|
||||
"name": "NoWait",
|
||||
"value": strconv.FormatBool(event["NoWait"].(bool)),
|
||||
},
|
||||
})
|
||||
rep = append(rep, map[string]string{
|
||||
"type": api.TABLE,
|
||||
"title": "Details",
|
||||
"data": string(details),
|
||||
})
|
||||
|
||||
if event["Arguments"] != nil {
|
||||
headers := make([]map[string]string, 0)
|
||||
for name, value := range event["Arguments"].(map[string]interface{}) {
|
||||
headers = append(headers, map[string]string{
|
||||
"name": name,
|
||||
"value": value.(string),
|
||||
})
|
||||
}
|
||||
headersMarshaled, _ := json.Marshal(headers)
|
||||
rep = append(rep, map[string]string{
|
||||
"type": api.TABLE,
|
||||
"title": "Arguments",
|
||||
"data": string(headersMarshaled),
|
||||
})
|
||||
}
|
||||
|
||||
return rep
|
||||
}
|
||||
|
||||
func representBasicConsume(event map[string]interface{}) []interface{} {
|
||||
rep := make([]interface{}, 0)
|
||||
|
||||
details, _ := json.Marshal([]map[string]string{
|
||||
{
|
||||
"name": "Queue",
|
||||
"value": event["Queue"].(string),
|
||||
},
|
||||
{
|
||||
"name": "Consumer Tag",
|
||||
"value": event["ConsumerTag"].(string),
|
||||
},
|
||||
{
|
||||
"name": "No Local",
|
||||
"value": strconv.FormatBool(event["NoLocal"].(bool)),
|
||||
},
|
||||
{
|
||||
"name": "No Ack",
|
||||
"value": strconv.FormatBool(event["NoAck"].(bool)),
|
||||
},
|
||||
{
|
||||
"name": "Exclusive",
|
||||
"value": strconv.FormatBool(event["Exclusive"].(bool)),
|
||||
},
|
||||
{
|
||||
"name": "NoWait",
|
||||
"value": strconv.FormatBool(event["NoWait"].(bool)),
|
||||
},
|
||||
})
|
||||
rep = append(rep, map[string]string{
|
||||
"type": api.TABLE,
|
||||
"title": "Details",
|
||||
"data": string(details),
|
||||
})
|
||||
|
||||
if event["Arguments"] != nil {
|
||||
headers := make([]map[string]string, 0)
|
||||
for name, value := range event["Arguments"].(map[string]interface{}) {
|
||||
headers = append(headers, map[string]string{
|
||||
"name": name,
|
||||
"value": value.(string),
|
||||
})
|
||||
}
|
||||
headersMarshaled, _ := json.Marshal(headers)
|
||||
rep = append(rep, map[string]string{
|
||||
"type": api.TABLE,
|
||||
"title": "Arguments",
|
||||
"data": string(headersMarshaled),
|
||||
})
|
||||
}
|
||||
|
||||
return rep
|
||||
}
|
||||
344
tap/extensions/amqp/main.go
Normal file
344
tap/extensions/amqp/main.go
Normal file
@@ -0,0 +1,344 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"log"
|
||||
"strconv"
|
||||
|
||||
"github.com/up9inc/mizu/tap/api"
|
||||
)
|
||||
|
||||
var protocol api.Protocol = api.Protocol{
|
||||
Name: "amqp",
|
||||
LongName: "Advanced Message Queuing Protocol 0-9-1",
|
||||
Abbreviation: "AMQP",
|
||||
Version: "0-9-1",
|
||||
BackgroundColor: "#ff6600",
|
||||
ForegroundColor: "#ffffff",
|
||||
FontSize: 12,
|
||||
ReferenceLink: "https://www.rabbitmq.com/amqp-0-9-1-reference.html",
|
||||
Ports: []string{"5671", "5672"},
|
||||
Priority: 1,
|
||||
}
|
||||
|
||||
func init() {
|
||||
log.Println("Initializing AMQP extension...")
|
||||
}
|
||||
|
||||
type dissecting string
|
||||
|
||||
func (d dissecting) Register(extension *api.Extension) {
|
||||
extension.Protocol = protocol
|
||||
}
|
||||
|
||||
func (d dissecting) Ping() {
|
||||
log.Printf("pong %s\n", protocol.Name)
|
||||
}
|
||||
|
||||
const amqpRequest string = "amqp_request"
|
||||
|
||||
func (d dissecting) Dissect(b *bufio.Reader, isClient bool, tcpID *api.TcpID, counterPair *api.CounterPair, superTimer *api.SuperTimer, emitter api.Emitter) error {
|
||||
r := AmqpReader{b}
|
||||
|
||||
var remaining int
|
||||
var header *HeaderFrame
|
||||
var body []byte
|
||||
|
||||
connectionInfo := &api.ConnectionInfo{
|
||||
ClientIP: tcpID.SrcIP,
|
||||
ClientPort: tcpID.SrcPort,
|
||||
ServerIP: tcpID.DstIP,
|
||||
ServerPort: tcpID.DstPort,
|
||||
IsOutgoing: true,
|
||||
}
|
||||
|
||||
eventBasicPublish := &BasicPublish{
|
||||
Exchange: "",
|
||||
RoutingKey: "",
|
||||
Mandatory: false,
|
||||
Immediate: false,
|
||||
Body: nil,
|
||||
Properties: Properties{},
|
||||
}
|
||||
|
||||
eventBasicDeliver := &BasicDeliver{
|
||||
ConsumerTag: "",
|
||||
DeliveryTag: 0,
|
||||
Redelivered: false,
|
||||
Exchange: "",
|
||||
RoutingKey: "",
|
||||
Properties: Properties{},
|
||||
Body: nil,
|
||||
}
|
||||
|
||||
var lastMethodFrameMessage Message
|
||||
|
||||
for {
|
||||
frame, err := r.ReadFrame()
|
||||
if err == io.EOF {
|
||||
// We must read until we see an EOF... very important!
|
||||
return errors.New("AMQP EOF")
|
||||
} else if err != nil {
|
||||
// TODO: Causes ignoring some methods. Return only in case of a certain error. But what?
|
||||
return err
|
||||
}
|
||||
|
||||
switch f := frame.(type) {
|
||||
case *HeartbeatFrame:
|
||||
// drop
|
||||
|
||||
case *HeaderFrame:
|
||||
// start content state
|
||||
header = f
|
||||
remaining = int(header.Size)
|
||||
switch lastMethodFrameMessage.(type) {
|
||||
case *BasicPublish:
|
||||
eventBasicPublish.Properties = header.Properties
|
||||
case *BasicDeliver:
|
||||
eventBasicDeliver.Properties = header.Properties
|
||||
default:
|
||||
}
|
||||
|
||||
case *BodyFrame:
|
||||
// continue until terminated
|
||||
body = append(body, f.Body...)
|
||||
remaining -= len(f.Body)
|
||||
switch lastMethodFrameMessage.(type) {
|
||||
case *BasicPublish:
|
||||
eventBasicPublish.Body = f.Body
|
||||
emitAMQP(*eventBasicPublish, amqpRequest, basicMethodMap[40], connectionInfo, superTimer.CaptureTime, emitter)
|
||||
case *BasicDeliver:
|
||||
eventBasicDeliver.Body = f.Body
|
||||
emitAMQP(*eventBasicDeliver, amqpRequest, basicMethodMap[60], connectionInfo, superTimer.CaptureTime, emitter)
|
||||
default:
|
||||
}
|
||||
|
||||
case *MethodFrame:
|
||||
lastMethodFrameMessage = f.Method
|
||||
switch m := f.Method.(type) {
|
||||
case *BasicPublish:
|
||||
eventBasicPublish.Exchange = m.Exchange
|
||||
eventBasicPublish.RoutingKey = m.RoutingKey
|
||||
eventBasicPublish.Mandatory = m.Mandatory
|
||||
eventBasicPublish.Immediate = m.Immediate
|
||||
|
||||
case *QueueBind:
|
||||
eventQueueBind := &QueueBind{
|
||||
Queue: m.Queue,
|
||||
Exchange: m.Exchange,
|
||||
RoutingKey: m.RoutingKey,
|
||||
NoWait: m.NoWait,
|
||||
Arguments: m.Arguments,
|
||||
}
|
||||
emitAMQP(*eventQueueBind, amqpRequest, queueMethodMap[20], connectionInfo, superTimer.CaptureTime, emitter)
|
||||
|
||||
case *BasicConsume:
|
||||
eventBasicConsume := &BasicConsume{
|
||||
Queue: m.Queue,
|
||||
ConsumerTag: m.ConsumerTag,
|
||||
NoLocal: m.NoLocal,
|
||||
NoAck: m.NoAck,
|
||||
Exclusive: m.Exclusive,
|
||||
NoWait: m.NoWait,
|
||||
Arguments: m.Arguments,
|
||||
}
|
||||
emitAMQP(*eventBasicConsume, amqpRequest, basicMethodMap[20], connectionInfo, superTimer.CaptureTime, emitter)
|
||||
|
||||
case *BasicDeliver:
|
||||
eventBasicDeliver.ConsumerTag = m.ConsumerTag
|
||||
eventBasicDeliver.DeliveryTag = m.DeliveryTag
|
||||
eventBasicDeliver.Redelivered = m.Redelivered
|
||||
eventBasicDeliver.Exchange = m.Exchange
|
||||
eventBasicDeliver.RoutingKey = m.RoutingKey
|
||||
|
||||
case *QueueDeclare:
|
||||
eventQueueDeclare := &QueueDeclare{
|
||||
Queue: m.Queue,
|
||||
Passive: m.Passive,
|
||||
Durable: m.Durable,
|
||||
AutoDelete: m.AutoDelete,
|
||||
Exclusive: m.Exclusive,
|
||||
NoWait: m.NoWait,
|
||||
Arguments: m.Arguments,
|
||||
}
|
||||
emitAMQP(*eventQueueDeclare, amqpRequest, queueMethodMap[10], connectionInfo, superTimer.CaptureTime, emitter)
|
||||
|
||||
case *ExchangeDeclare:
|
||||
eventExchangeDeclare := &ExchangeDeclare{
|
||||
Exchange: m.Exchange,
|
||||
Type: m.Type,
|
||||
Passive: m.Passive,
|
||||
Durable: m.Durable,
|
||||
AutoDelete: m.AutoDelete,
|
||||
Internal: m.Internal,
|
||||
NoWait: m.NoWait,
|
||||
Arguments: m.Arguments,
|
||||
}
|
||||
emitAMQP(*eventExchangeDeclare, amqpRequest, exchangeMethodMap[10], connectionInfo, superTimer.CaptureTime, emitter)
|
||||
|
||||
case *ConnectionStart:
|
||||
eventConnectionStart := &ConnectionStart{
|
||||
VersionMajor: m.VersionMajor,
|
||||
VersionMinor: m.VersionMinor,
|
||||
ServerProperties: m.ServerProperties,
|
||||
Mechanisms: m.Mechanisms,
|
||||
Locales: m.Locales,
|
||||
}
|
||||
emitAMQP(*eventConnectionStart, amqpRequest, connectionMethodMap[10], connectionInfo, superTimer.CaptureTime, emitter)
|
||||
|
||||
case *ConnectionClose:
|
||||
eventConnectionClose := &ConnectionClose{
|
||||
ReplyCode: m.ReplyCode,
|
||||
ReplyText: m.ReplyText,
|
||||
ClassId: m.ClassId,
|
||||
MethodId: m.MethodId,
|
||||
}
|
||||
emitAMQP(*eventConnectionClose, amqpRequest, connectionMethodMap[50], connectionInfo, superTimer.CaptureTime, emitter)
|
||||
|
||||
default:
|
||||
|
||||
}
|
||||
|
||||
default:
|
||||
// log.Printf("unexpected frame: %+v\n", f)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (d dissecting) Analyze(item *api.OutputChannelItem, entryId string, resolvedSource string, resolvedDestination string) *api.MizuEntry {
|
||||
request := item.Pair.Request.Payload.(map[string]interface{})
|
||||
reqDetails := request["details"].(map[string]interface{})
|
||||
service := "amqp"
|
||||
if resolvedDestination != "" {
|
||||
service = resolvedDestination
|
||||
} else if resolvedSource != "" {
|
||||
service = resolvedSource
|
||||
}
|
||||
|
||||
summary := ""
|
||||
switch request["method"] {
|
||||
case basicMethodMap[40]:
|
||||
summary = reqDetails["Exchange"].(string)
|
||||
break
|
||||
case basicMethodMap[60]:
|
||||
summary = reqDetails["Exchange"].(string)
|
||||
break
|
||||
case exchangeMethodMap[10]:
|
||||
summary = reqDetails["Exchange"].(string)
|
||||
break
|
||||
case queueMethodMap[10]:
|
||||
summary = reqDetails["Queue"].(string)
|
||||
break
|
||||
case connectionMethodMap[10]:
|
||||
summary = fmt.Sprintf(
|
||||
"%s.%s",
|
||||
strconv.Itoa(int(reqDetails["VersionMajor"].(float64))),
|
||||
strconv.Itoa(int(reqDetails["VersionMinor"].(float64))),
|
||||
)
|
||||
break
|
||||
case connectionMethodMap[50]:
|
||||
summary = reqDetails["ReplyText"].(string)
|
||||
break
|
||||
case queueMethodMap[20]:
|
||||
summary = reqDetails["Queue"].(string)
|
||||
break
|
||||
case basicMethodMap[20]:
|
||||
summary = reqDetails["Queue"].(string)
|
||||
break
|
||||
}
|
||||
|
||||
request["url"] = summary
|
||||
entryBytes, _ := json.Marshal(item.Pair)
|
||||
return &api.MizuEntry{
|
||||
ProtocolName: protocol.Name,
|
||||
ProtocolVersion: protocol.Version,
|
||||
EntryId: entryId,
|
||||
Entry: string(entryBytes),
|
||||
Url: fmt.Sprintf("%s%s", service, summary),
|
||||
Method: request["method"].(string),
|
||||
Status: 0,
|
||||
RequestSenderIp: item.ConnectionInfo.ClientIP,
|
||||
Service: service,
|
||||
Timestamp: item.Timestamp,
|
||||
ElapsedTime: 0,
|
||||
Path: summary,
|
||||
ResolvedSource: resolvedSource,
|
||||
ResolvedDestination: resolvedDestination,
|
||||
SourceIp: item.ConnectionInfo.ClientIP,
|
||||
DestinationIp: item.ConnectionInfo.ServerIP,
|
||||
SourcePort: item.ConnectionInfo.ClientPort,
|
||||
DestinationPort: item.ConnectionInfo.ServerPort,
|
||||
IsOutgoing: item.ConnectionInfo.IsOutgoing,
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func (d dissecting) Summarize(entry *api.MizuEntry) *api.BaseEntryDetails {
|
||||
return &api.BaseEntryDetails{
|
||||
Id: entry.EntryId,
|
||||
Protocol: protocol,
|
||||
Url: entry.Url,
|
||||
RequestSenderIp: entry.RequestSenderIp,
|
||||
Service: entry.Service,
|
||||
Summary: entry.Path,
|
||||
StatusCode: entry.Status,
|
||||
Method: entry.Method,
|
||||
Timestamp: entry.Timestamp,
|
||||
SourceIp: entry.SourceIp,
|
||||
DestinationIp: entry.DestinationIp,
|
||||
SourcePort: entry.SourcePort,
|
||||
DestinationPort: entry.DestinationPort,
|
||||
IsOutgoing: entry.IsOutgoing,
|
||||
Latency: 0,
|
||||
Rules: api.ApplicableRules{
|
||||
Latency: 0,
|
||||
Status: false,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func (d dissecting) Represent(entry *api.MizuEntry) (p api.Protocol, object []byte, bodySize int64, err error) {
|
||||
p = protocol
|
||||
bodySize = 0
|
||||
var root map[string]interface{}
|
||||
json.Unmarshal([]byte(entry.Entry), &root)
|
||||
representation := make(map[string]interface{}, 0)
|
||||
request := root["request"].(map[string]interface{})["payload"].(map[string]interface{})
|
||||
var repRequest []interface{}
|
||||
details := request["details"].(map[string]interface{})
|
||||
switch request["method"].(string) {
|
||||
case basicMethodMap[40]:
|
||||
repRequest = representBasicPublish(details)
|
||||
break
|
||||
case basicMethodMap[60]:
|
||||
repRequest = representBasicDeliver(details)
|
||||
break
|
||||
case queueMethodMap[10]:
|
||||
repRequest = representQueueDeclare(details)
|
||||
break
|
||||
case exchangeMethodMap[10]:
|
||||
repRequest = representExchangeDeclare(details)
|
||||
break
|
||||
case connectionMethodMap[10]:
|
||||
repRequest = representConnectionStart(details)
|
||||
break
|
||||
case connectionMethodMap[50]:
|
||||
repRequest = representConnectionClose(details)
|
||||
break
|
||||
case queueMethodMap[20]:
|
||||
repRequest = representQueueBind(details)
|
||||
break
|
||||
case basicMethodMap[20]:
|
||||
repRequest = representBasicConsume(details)
|
||||
break
|
||||
}
|
||||
representation["request"] = repRequest
|
||||
object, err = json.Marshal(representation)
|
||||
return
|
||||
}
|
||||
|
||||
var Dissector dissecting
|
||||
460
tap/extensions/amqp/read.go
Normal file
460
tap/extensions/amqp/read.go
Normal file
@@ -0,0 +1,460 @@
|
||||
// Copyright (c) 2012, Sean Treadway, SoundCloud Ltd.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
// Source code and contact info at http://github.com/streadway/amqp
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/binary"
|
||||
"errors"
|
||||
"io"
|
||||
"time"
|
||||
)
|
||||
|
||||
/*
|
||||
Reads a frame from an input stream and returns an interface that can be cast into
|
||||
one of the following:
|
||||
|
||||
MethodFrame
|
||||
PropertiesFrame
|
||||
BodyFrame
|
||||
HeartbeatFrame
|
||||
|
||||
2.3.5 frame Details
|
||||
|
||||
All frames consist of a header (7 octets), a payload of arbitrary size, and a
|
||||
'frame-end' octet that detects malformed frames:
|
||||
|
||||
0 1 3 7 size+7 size+8
|
||||
+------+---------+-------------+ +------------+ +-----------+
|
||||
| type | channel | size | | payload | | frame-end |
|
||||
+------+---------+-------------+ +------------+ +-----------+
|
||||
octet short long size octets octet
|
||||
|
||||
To read a frame, we:
|
||||
1. Read the header and check the frame type and channel.
|
||||
2. Depending on the frame type, we read the payload and process it.
|
||||
3. Read the frame end octet.
|
||||
|
||||
In realistic implementations where performance is a concern, we would use
|
||||
“read-ahead buffering” or
|
||||
|
||||
“gathering reads” to avoid doing three separate system calls to read a frame.
|
||||
*/
|
||||
func (r *AmqpReader) ReadFrame() (frame frame, err error) {
|
||||
var scratch [7]byte
|
||||
|
||||
if _, err = io.ReadFull(r.R, scratch[:7]); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
typ := uint8(scratch[0])
|
||||
channel := binary.BigEndian.Uint16(scratch[1:3])
|
||||
size := binary.BigEndian.Uint32(scratch[3:7])
|
||||
|
||||
if size > 1000000 {
|
||||
return nil, ErrMaxSize
|
||||
}
|
||||
|
||||
switch typ {
|
||||
case frameMethod:
|
||||
if frame, err = r.parseMethodFrame(channel, size); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
case frameHeader:
|
||||
if frame, err = r.parseHeaderFrame(channel, size); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
case frameBody:
|
||||
if frame, err = r.parseBodyFrame(channel, size); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
case frameHeartbeat:
|
||||
if frame, err = r.parseHeartbeatFrame(channel, size); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
default:
|
||||
return nil, ErrFrame
|
||||
}
|
||||
|
||||
if _, err = io.ReadFull(r.R, scratch[:1]); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if scratch[0] != frameEnd {
|
||||
return nil, ErrFrame
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func readShortstr(r io.Reader) (v string, err error) {
|
||||
var length uint8
|
||||
if err = binary.Read(r, binary.BigEndian, &length); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
bytes := make([]byte, length)
|
||||
if _, err = io.ReadFull(r, bytes); err != nil {
|
||||
return
|
||||
}
|
||||
return string(bytes), nil
|
||||
}
|
||||
|
||||
func readLongstr(r io.Reader) (v string, err error) {
|
||||
var length uint32
|
||||
if err = binary.Read(r, binary.BigEndian, &length); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
// slices can't be longer than max int32 value
|
||||
if length > (^uint32(0) >> 1) {
|
||||
return
|
||||
}
|
||||
|
||||
bytes := make([]byte, length)
|
||||
if _, err = io.ReadFull(r, bytes); err != nil {
|
||||
return
|
||||
}
|
||||
return string(bytes), nil
|
||||
}
|
||||
|
||||
func readDecimal(r io.Reader) (v Decimal, err error) {
|
||||
if err = binary.Read(r, binary.BigEndian, &v.Scale); err != nil {
|
||||
return
|
||||
}
|
||||
if err = binary.Read(r, binary.BigEndian, &v.Value); err != nil {
|
||||
return
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func readFloat32(r io.Reader) (v float32, err error) {
|
||||
if err = binary.Read(r, binary.BigEndian, &v); err != nil {
|
||||
return
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func readFloat64(r io.Reader) (v float64, err error) {
|
||||
if err = binary.Read(r, binary.BigEndian, &v); err != nil {
|
||||
return
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func readTimestamp(r io.Reader) (v time.Time, err error) {
|
||||
var sec int64
|
||||
if err = binary.Read(r, binary.BigEndian, &sec); err != nil {
|
||||
return
|
||||
}
|
||||
return time.Unix(sec, 0), nil
|
||||
}
|
||||
|
||||
/*
|
||||
'A': []interface{}
|
||||
'D': Decimal
|
||||
'F': Table
|
||||
'I': int32
|
||||
'S': string
|
||||
'T': time.Time
|
||||
'V': nil
|
||||
'b': byte
|
||||
'd': float64
|
||||
'f': float32
|
||||
'l': int64
|
||||
's': int16
|
||||
't': bool
|
||||
'x': []byte
|
||||
*/
|
||||
func readField(r io.Reader) (v interface{}, err error) {
|
||||
var typ byte
|
||||
if err = binary.Read(r, binary.BigEndian, &typ); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
switch typ {
|
||||
case 't':
|
||||
var value uint8
|
||||
if err = binary.Read(r, binary.BigEndian, &value); err != nil {
|
||||
return
|
||||
}
|
||||
return (value != 0), nil
|
||||
|
||||
case 'b':
|
||||
var value [1]byte
|
||||
if _, err = io.ReadFull(r, value[0:1]); err != nil {
|
||||
return
|
||||
}
|
||||
return value[0], nil
|
||||
|
||||
case 's':
|
||||
var value int16
|
||||
if err = binary.Read(r, binary.BigEndian, &value); err != nil {
|
||||
return
|
||||
}
|
||||
return value, nil
|
||||
|
||||
case 'I':
|
||||
var value int32
|
||||
if err = binary.Read(r, binary.BigEndian, &value); err != nil {
|
||||
return
|
||||
}
|
||||
return value, nil
|
||||
|
||||
case 'l':
|
||||
var value int64
|
||||
if err = binary.Read(r, binary.BigEndian, &value); err != nil {
|
||||
return
|
||||
}
|
||||
return value, nil
|
||||
|
||||
case 'f':
|
||||
var value float32
|
||||
if err = binary.Read(r, binary.BigEndian, &value); err != nil {
|
||||
return
|
||||
}
|
||||
return value, nil
|
||||
|
||||
case 'd':
|
||||
var value float64
|
||||
if err = binary.Read(r, binary.BigEndian, &value); err != nil {
|
||||
return
|
||||
}
|
||||
return value, nil
|
||||
|
||||
case 'D':
|
||||
return readDecimal(r)
|
||||
|
||||
case 'S':
|
||||
return readLongstr(r)
|
||||
|
||||
case 'A':
|
||||
return readArray(r)
|
||||
|
||||
case 'T':
|
||||
return readTimestamp(r)
|
||||
|
||||
case 'F':
|
||||
return readTable(r)
|
||||
|
||||
case 'x':
|
||||
var len int32
|
||||
if err = binary.Read(r, binary.BigEndian, &len); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
value := make([]byte, len)
|
||||
if _, err = io.ReadFull(r, value); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return value, err
|
||||
|
||||
case 'V':
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
return nil, ErrSyntax
|
||||
}
|
||||
|
||||
/*
|
||||
Field tables are long strings that contain packed name-value pairs. The
|
||||
name-value pairs are encoded as short string defining the name, and octet
|
||||
defining the values type and then the value itself. The valid field types for
|
||||
tables are an extension of the native integer, bit, string, and timestamp
|
||||
types, and are shown in the grammar. Multi-octet integer fields are always
|
||||
held in network byte order.
|
||||
*/
|
||||
func readTable(r io.Reader) (table Table, err error) {
|
||||
var nested bytes.Buffer
|
||||
var str string
|
||||
|
||||
if str, err = readLongstr(r); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
nested.Write([]byte(str))
|
||||
|
||||
table = make(Table)
|
||||
|
||||
for nested.Len() > 0 {
|
||||
var key string
|
||||
var value interface{}
|
||||
|
||||
if key, err = readShortstr(&nested); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
if value, err = readField(&nested); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
table[key] = value
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func readArray(r io.Reader) ([]interface{}, error) {
|
||||
var (
|
||||
size uint32
|
||||
err error
|
||||
)
|
||||
|
||||
if err = binary.Read(r, binary.BigEndian, &size); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var (
|
||||
lim = &io.LimitedReader{R: r, N: int64(size)}
|
||||
arr = []interface{}{}
|
||||
field interface{}
|
||||
)
|
||||
|
||||
for {
|
||||
if field, err = readField(lim); err != nil {
|
||||
if err == io.EOF {
|
||||
break
|
||||
}
|
||||
return nil, err
|
||||
}
|
||||
arr = append(arr, field)
|
||||
}
|
||||
|
||||
return arr, nil
|
||||
}
|
||||
|
||||
// Checks if this bit mask matches the flags bitset
|
||||
func hasProperty(mask uint16, prop int) bool {
|
||||
return int(mask)&prop > 0
|
||||
}
|
||||
|
||||
func (r *AmqpReader) parseHeaderFrame(channel uint16, size uint32) (frame frame, err error) {
|
||||
hf := &HeaderFrame{
|
||||
ChannelId: channel,
|
||||
}
|
||||
|
||||
if err = binary.Read(r.R, binary.BigEndian, &hf.ClassId); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
if err = binary.Read(r.R, binary.BigEndian, &hf.weight); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
if err = binary.Read(r.R, binary.BigEndian, &hf.Size); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
var flags uint16
|
||||
|
||||
if err = binary.Read(r.R, binary.BigEndian, &flags); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
if hasProperty(flags, flagContentType) {
|
||||
if hf.Properties.ContentType, err = readShortstr(r.R); err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
if hasProperty(flags, flagContentEncoding) {
|
||||
if hf.Properties.ContentEncoding, err = readShortstr(r.R); err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
if hasProperty(flags, flagHeaders) {
|
||||
if hf.Properties.Headers, err = readTable(r.R); err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
if hasProperty(flags, flagDeliveryMode) {
|
||||
if err = binary.Read(r.R, binary.BigEndian, &hf.Properties.DeliveryMode); err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
if hasProperty(flags, flagPriority) {
|
||||
if err = binary.Read(r.R, binary.BigEndian, &hf.Properties.Priority); err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
if hasProperty(flags, flagCorrelationId) {
|
||||
if hf.Properties.CorrelationId, err = readShortstr(r.R); err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
if hasProperty(flags, flagReplyTo) {
|
||||
if hf.Properties.ReplyTo, err = readShortstr(r.R); err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
if hasProperty(flags, flagExpiration) {
|
||||
if hf.Properties.Expiration, err = readShortstr(r.R); err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
if hasProperty(flags, flagMessageId) {
|
||||
if hf.Properties.MessageId, err = readShortstr(r.R); err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
if hasProperty(flags, flagTimestamp) {
|
||||
if hf.Properties.Timestamp, err = readTimestamp(r.R); err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
if hasProperty(flags, flagType) {
|
||||
if hf.Properties.Type, err = readShortstr(r.R); err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
if hasProperty(flags, flagUserId) {
|
||||
if hf.Properties.UserId, err = readShortstr(r.R); err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
if hasProperty(flags, flagAppId) {
|
||||
if hf.Properties.AppId, err = readShortstr(r.R); err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
if hasProperty(flags, flagReserved1) {
|
||||
if hf.Properties.reserved1, err = readShortstr(r.R); err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
return hf, nil
|
||||
}
|
||||
|
||||
func (r *AmqpReader) parseBodyFrame(channel uint16, size uint32) (frame frame, err error) {
|
||||
bf := &BodyFrame{
|
||||
ChannelId: channel,
|
||||
Body: make([]byte, size),
|
||||
}
|
||||
|
||||
if _, err = io.ReadFull(r.R, bf.Body); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return bf, nil
|
||||
}
|
||||
|
||||
var errHeartbeatPayload = errors.New("Heartbeats should not have a payload")
|
||||
|
||||
func (r *AmqpReader) parseHeartbeatFrame(channel uint16, size uint32) (frame frame, err error) {
|
||||
hf := &HeartbeatFrame{
|
||||
ChannelId: channel,
|
||||
}
|
||||
|
||||
if size > 0 {
|
||||
return nil, errHeartbeatPayload
|
||||
}
|
||||
|
||||
return hf, nil
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user