diff --git a/.devcontainer/Dockerfile b/.devcontainer/Dockerfile new file mode 100644 index 0000000000..8168ea8465 --- /dev/null +++ b/.devcontainer/Dockerfile @@ -0,0 +1,13 @@ +ARG VARIANT=22 +FROM mcr.microsoft.com/vscode/devcontainers/javascript-node:${VARIANT} + +# [Optional] Uncomment this section to install additional OS packages. +# RUN apt-get update && export DEBIAN_FRONTEND=noninteractive \ +# && apt-get -y install --no-install-recommends + +# [Optional] Uncomment if you want to install an additional version of node using nvm +# ARG EXTRA_NODE_VERSION=10 +# RUN su node -c "source /usr/local/share/nvm/nvm.sh && nvm install ${EXTRA_NODE_VERSION}" + +# [Optional] Uncomment if you want to install more global node modules +# RUN su node -c "npm install -g " diff --git a/api/test/docker/apisix_config.yaml b/.devcontainer/apisix_conf.yml similarity index 54% rename from api/test/docker/apisix_config.yaml rename to .devcontainer/apisix_conf.yml index 4f7f51add8..94532cf578 100644 --- a/api/test/docker/apisix_config.yaml +++ b/.devcontainer/apisix_conf.yml @@ -14,46 +14,29 @@ # See the License for the specific language governing permissions and # limitations under the License. # -# If you want to set the specified configuration value, you can set the new -# in this file. For example if you want to specify the etcd address: -# - -deployment: - admin: - allow_admin: - - 0.0.0.0/0 - etcd: - host: - - "http://etcd:2379" - resync_delay: 0 apisix: - id: "apisix-server1" - enable_control: true - control: - ip: "0.0.0.0" - port: 9090 + node_listen: 9080 # APISIX listening port + enable_ipv6: false + proxy_mode: http&stream stream_proxy: - only: false tcp: - - addr: 10090 - - addr: 10091 - - addr: 10092 - - addr: 10093 - tls: true + - 9100 udp: - - 10095 + - 9200 -nginx_config: - error_log_level: "debug" +deployment: + admin: + allow_admin: # https://nginx.org/en/docs/http/ngx_http_access_module.html#allow + - 0.0.0.0/0 # We need to restrict ip access rules for security. 0.0.0.0/0 is for test. -plugin_attr: - server-info: - report_interval: 60 - report_ttl: 3600 - prometheus: - export_uri: /apisix/prometheus/metrics - enable_export_server: true - export_addr: - ip: "0.0.0.0" - port: 9091 + admin_key: + - name: "admin" + key: edd1c9f034335f136f87ad84b625c8f1 + role: admin # admin: manage all configuration data + + etcd: + host: # it's possible to define multiple etcd hosts addresses of the same etcd cluster. + - "http://etcd:2379" # multiple etcd address + prefix: "/apisix" # apisix configurations prefix + timeout: 30 # 30 seconds diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json new file mode 100644 index 0000000000..ead684d118 --- /dev/null +++ b/.devcontainer/devcontainer.json @@ -0,0 +1,57 @@ +{ + "$schema": "https://raw.githubusercontent.com/devcontainers/spec/refs/heads/main/schemas/devContainer.base.schema.json", + "name": "APISIX Dashboard Dev Environment", + "dockerComposeFile": [ + "./docker-compose.yml" + ], + "service": "apisix-dashboard", + "workspaceFolder": "/workspace", + "shutdownAction": "stopCompose", + "postCreateCommand": "pnpm i && echo '\nUse `pnpm dev` to continue'", + "forwardPorts": [ + 9080, + 9180, + 9100, + 9200, + 5173, + 5174, + 4173 + ], + "portsAttributes": { + "5173": { + "label": "APISIX Dashboard", + "onAutoForward": "ignore" + }, + "5174": { + "label": "APISIX Dashboard HMR WS", + "onAutoForward": "silent" + }, + "9180": { + "label": "APISIX Admin API Port", + "onAutoForward": "notify" + }, + "9100": { + "label": "APISIX Stream Proxy TCP Port", + "onAutoForward": "silent" + }, + "9200": { + "label": "APISIX Stream Proxy UDP Port", + "onAutoForward": "silent" + } + }, + "customizations": { + "vscode": { + "extensions": [ + "vunguyentuan.vscode-css-variables", + "dbaeumer.vscode-eslint", + "drKnoxy.eslint-disable-snippets", + "esbenp.prettier-vscode", + "christian-kohler.path-intellisense", + "lokalise.i18n-ally", + "formulahendry.auto-close-tag", + "formulahendry.auto-rename-tag", + "github.vscode-pull-request-github" + ] + } + } +} diff --git a/.devcontainer/docker-compose.yml b/.devcontainer/docker-compose.yml new file mode 100644 index 0000000000..901bb2a678 --- /dev/null +++ b/.devcontainer/docker-compose.yml @@ -0,0 +1,52 @@ +services: + apisix-dashboard: + build: + context: .. + dockerfile: .devcontainer/Dockerfile + command: sleep infinity + volumes: + - ..:/workspace:cached + networks: + - apisix + ports: + - '5173:5173' + - '5174:5174' + + apisix: + image: 'apache/apisix:3.12.0-debian' + restart: always + volumes: + - ./apisix_conf.yml:/usr/local/apisix/conf/config.yaml:ro + depends_on: + - etcd + ports: + - '9180:9180/tcp' + - '9080:9080/tcp' + - '9091:9091/tcp' + - '9443:9443/tcp' + networks: + - apisix + + etcd: + image: bitnami/etcd:3.4.9 + user: root + restart: always + volumes: + - etcd_data:/etcd_data + environment: + ETCD_DATA_DIR: /etcd_data + ETCD_ENABLE_V2: 'true' + ALLOW_NONE_AUTHENTICATION: 'yes' + ETCD_ADVERTISE_CLIENT_URLS: 'http://etcd:2379' + ETCD_LISTEN_CLIENT_URLS: 'http://0.0.0.0:2379' + ports: + - '2379:2379/tcp' + networks: + - apisix + +networks: + apisix: + driver: bridge + +volumes: + etcd_data: diff --git a/.github/workflows/auto-build-rpm.yml b/.github/workflows/auto-build-rpm.yml deleted file mode 100644 index 63ecc2e93e..0000000000 --- a/.github/workflows/auto-build-rpm.yml +++ /dev/null @@ -1,86 +0,0 @@ -name: Auto Build RPM - -on: - push: - branches: [master, 'release/**'] - paths-ignore: - - 'docs/**' - pull_request: - branches: [master] - paths-ignore: - - 'docs/**' - -jobs: - auto_build_rpm: - name: Auto Build RPM package - runs-on: ubuntu-latest - - services: - etcd: - image: bitnami/etcd:3.5.2 - ports: - - 2379:2379 - - 2380:2380 - env: - ALLOW_NONE_AUTHENTICATION: yes - ETCD_ADVERTISE_CLIENT_URLS: http://0.0.0.0:2379 - - steps: - - name: Check out code - uses: actions/checkout@v3 - - - uses: docker/setup-buildx-action@v2 - - - uses: actions/cache@v3 - with: - path: /tmp/.buildx-cache - key: ${{ runner.os }}-buildx-apisixdashboard-${{ github.sha }} - restore-keys: | - ${{ runner.os }}-buildx-apisixdashboard- - ${{ runner.os }}-buildx- - - - name: Extract branch name - id: branch_env - shell: bash - run: | - echo "##[set-output name=version;]$(echo ${GITHUB_REF##*/})" - - - name: Build rpm package - run: | - export VERSION=${{ steps.branch_env.outputs.version }} - sudo gem install --no-document fpm - git clone https://github.com/api7/apisix-build-tools.git - - # move codes under build tool - mkdir ./apisix-build-tools/apisix-dashboard - for dir in `ls|grep -v "^apisix-build-tools$"`;do cp -r $dir ./apisix-build-tools/apisix-dashboard/;done - - cd apisix-build-tools - export checkout=master - if [ "$VERSION" != "merge" && "$VERSION" != "master" ];then - export checkout=release/${VERSION} - fi - make package type=rpm app=dashboard version=${VERSION} checkout=${checkout} image_base=centos image_tag=7 local_code_path=./apisix-dashboard buildx=True - - - name: Run centos7 docker and mapping apisix into container - run: | - docker run -itd -v $PWD:/apisix-dashboard --name centos7Instance --net="host" docker.io/centos:7 /bin/bash - - - name: Install rpm package - run: | - export VERSION=${{ steps.branch_env.outputs.version }} - docker exec centos7Instance bash -c "cd apisix-dashboard && yum install -y ./apisix-build-tools/output/apisix-dashboard-${VERSION}-0.el7.x86_64.rpm" - docker logs centos7Instance - # Dependencies are attached with rpm, so revert `make deps` - docker exec centos7Instance bash -c "cd /usr/local/apisix/dashboard/ && nohup ./manager-api &" - - - name: Run test cases - run: | - api/test/shell/manager_smoking.sh -s true - - - name: Publish Artifact - if: ${{ startsWith(github.ref, 'refs/heads/release/') }} - uses: actions/upload-artifact@v3 - with: - name: "rpm" - path: "./apisix-build-tools/output/apisix-dashboard-${{ steps.branch_env.outputs.version }}-0.el7.x86_64.rpm" diff --git a/.github/workflows/backend-cli-test.yml b/.github/workflows/backend-cli-test.yml deleted file mode 100644 index bde9502def..0000000000 --- a/.github/workflows/backend-cli-test.yml +++ /dev/null @@ -1,58 +0,0 @@ -name: Backend CLI Test - -on: - push: - branches: - - master - paths: - - 'api/**' - pull_request: - branches: - - master - paths: - - 'api/**' - -jobs: - run-test: - runs-on: ubuntu-latest - strategy: - matrix: - etcd: [3.4.20] - - steps: - - uses: actions/checkout@v3 - with: - submodules: recursive - - - name: setup go - uses: actions/setup-go@v3 - with: - go-version: '1.19' - - - name: cache etcd - id: cache-etcd - uses: actions/cache@v3 - with: - path: | - ${{ github.workspace }}/api/etcd-v${{ matrix.etcd }}-linux-amd64 - key: etcd-v${{ matrix.etcd }}-linux-amd64 - - - name: download etcd - if: ${{ steps.cache-etcd.outputs.cache-hit != 'true' }} - working-directory: ./api - run: | - wget https://github.com/etcd-io/etcd/releases/download/v${{ matrix.etcd }}/etcd-v${{ matrix.etcd }}-linux-amd64.tar.gz - tar zxvf etcd-v${{ matrix.etcd }}-linux-amd64.tar.gz - - - name: install bats - run: | - git clone https://github.com/sstephenson/bats.git && cd bats - sudo ./install.sh /usr/local - - - name: run test - working-directory: ./api - run: chmod +x ./test/shell/cli_test.sh && sudo ./test/shell/cli_test.sh - - - name: tmate debugger - if: ${{ failure() }} - uses: ./.github/actions/tmate-action diff --git a/.github/workflows/backend-e2e-test.yml b/.github/workflows/backend-e2e-test.yml deleted file mode 100644 index 85ec37d55c..0000000000 --- a/.github/workflows/backend-e2e-test.yml +++ /dev/null @@ -1,96 +0,0 @@ -name: Backend E2E Test - -on: - push: - branches: - - master - paths: - - 'api/**' - pull_request: - branches: - - master - paths: - - 'api/**' - -concurrency: - group: ${{ github.workflow }}-${{ github.ref == 'refs/heads/master' && github.run_number || github.ref }} - cancel-in-progress: true - -jobs: - backend-e2e-test: - runs-on: ubuntu-latest - - steps: - - uses: actions/checkout@v3 - with: - submodules: recursive - - - name: setup go - uses: actions/setup-go@v3 - with: - go-version: "1.19" - - - uses: docker/setup-buildx-action@v2 - - - uses: actions/cache@v3 - with: - path: /tmp/.buildx-cache - key: ${{ runner.os }}-buildx-apisixdashboard-${{ github.sha }} - restore-keys: | - ${{ runner.os }}-buildx-apisixdashboard- - ${{ runner.os }}-buildx- - - - name: Modify conf.yaml Configure for use by the manage-api cluster - run: | - sed -i 's/127.0.0.1:2379/etcd:2379/' ./api/conf/conf.yaml - sed -i 's@127.0.0.1@0.0.0.0/0@' ./api/conf/conf.yaml - sed -i 's@0.0.0.0/0:9000@127.0.0.1:9000@' ./api/conf/conf.yaml - sed -i 's/enabled: false/enabled: true/' ./api/conf/conf.yaml - - - name: build docker images - working-directory: ./api/test/docker - continue-on-error: true - run: | - docker buildx bake --load \ - -f docker-compose.yaml \ - --set *.cache-from=type=local,src=/tmp/.buildx-cache \ - --set *.cache-to=type=local,dest=/tmp/.buildx-cache - - - name: build and start grpc_server_example - working-directory: ./api/test/docker - run: | - wget https://github.com/api7/grpc_server_example/archive/refs/tags/20210819.tar.gz - tar -xzvf 20210819.tar.gz && cd grpc_server_example-20210819 - docker build -t grpc_server_example:latest . - - - name: run docker compose - working-directory: ./api/test/docker - run: | - docker-compose up -d - sleep 5 - docker logs docker_managerapi_1 - - - name: install ginkgo cli - run: go install -mod=mod github.com/onsi/ginkgo/v2/ginkgo@latest - - - name: run test - working-directory: ./api/test/e2e - run: | - go mod download - ginkgo -r --flake-attempts=3 -v - - - name: stop docker compose - working-directory: ./api/test/docker - run: | - docker-compose down - sleep 10 - - - name: output test coverage - working-directory: ./api/test/testdata - run: | - go tool cover -func=./integrationcover.out - - - name: upload coverage profile - working-directory: ./api/test/testdata - run: | - bash <(curl -s https://codecov.io/bash) -f ./integrationcover.out -F backend-e2e-test-ginkgo diff --git a/.github/workflows/backend-unit-test.yml b/.github/workflows/backend-unit-test.yml deleted file mode 100644 index ef9bdede5d..0000000000 --- a/.github/workflows/backend-unit-test.yml +++ /dev/null @@ -1,74 +0,0 @@ -name: Backend Unit Test - -on: - push: - branches: - - master - paths: - - 'api/**' - pull_request: - branches: - - master - paths: - - 'api/**' - -jobs: - run-test: - runs-on: ubuntu-latest - - services: - etcd: - image: bitnami/etcd:3.5.2 - ports: - - 2379:2379 - - 2380:2380 - env: - ALLOW_NONE_AUTHENTICATION: yes - - steps: - - uses: actions/checkout@v3 - - - name: setup go - uses: actions/setup-go@v3 - with: - go-version: "1.18" - - - uses: actions/cache@v3 - with: - path: | - ~/.cache/go-build - ~/go/pkg/mod - key: ${{ runner.os }}-go-${{ hashFiles('**/go.sum') }} - restore-keys: | - ${{ runner.os }}-go- - - - name: run test - run: | - make api-test - - - name: upload coverage profile - working-directory: ./api - run: | - bash <(curl -s https://codecov.io/bash) -F backend-unit-test - - - name: run with custom port - working-directory: ./api - run: | - export GO111MOUDULE=on - export APISIX_CONF_PATH=$PWD/conf - sed -i 's/9000/8088/' conf/conf.yaml - go build -o ./manager-api ./main.go - ./manager-api > ./api.log 2>&1 & - sleep 2 - cat ./api.log - cat conf/conf.yaml - - - name: run with custom port - working-directory: ./api - run: | - curl http://127.0.0.1:8088/apisix/admin/user/login -X POST -i -d '{"username":"admin", "password": "admin"}' - code=$(curl -k -i -m 20 -o /dev/null -s -w %{http_code} http://127.0.0.1:8088/apisix/admin/user/login -X POST -i -d '{"username":"admin", "password": "admin"}') - if [ ! $code -eq 200 ]; then - echo "failed: failed to custom port" - exit 1 - fi diff --git a/.github/workflows/deploy-with-docker.yml b/.github/workflows/deploy-with-docker.yml deleted file mode 100644 index 5568ed37f4..0000000000 --- a/.github/workflows/deploy-with-docker.yml +++ /dev/null @@ -1,75 +0,0 @@ -name: Test and Deploy with Docker - -on: - push: - branches: - - master - paths-ignore: - - 'docs/**' - pull_request: - branches: - - master - paths-ignore: - - 'docs/**' - -concurrency: - group: ${{ github.workflow }}-${{ github.ref == 'refs/heads/master' && github.run_number || github.ref }} - cancel-in-progress: true - -jobs: - build: - runs-on: ubuntu-latest - - steps: - - uses: actions/checkout@v3 - with: - submodules: recursive - - - uses: docker/setup-buildx-action@v2 - - - uses: actions/cache@v3 - with: - path: /tmp/.buildx-cache - key: ${{ runner.os }}-buildx-apisixdashboard-${{ github.sha }} - restore-keys: | - ${{ runner.os }}-buildx-apisixdashboard- - ${{ runner.os }}-buildx- - - - name: Build Docker Image - uses: docker/build-push-action@v2 - with: - load: true - tags: dashboard:ci - context: . - cache-from: | - type=local,src=/tmp/.buildx-cache - cache-to: | - type=local,dest=/tmp/.buildx-cache - - - name: Modify Config - run: | - sed -i 's/127.0.0.1:2379/172.16.238.10:2379/' api/conf/conf.yaml - sed -i 's@127.0.0.1@0.0.0.0/0@' ./api/conf/conf.yaml - - # At present, the docker CI is still running the code of the master branch - # So we need to configure `listen.host` so that it can be accessed outside docker - sed -i '/port: 9000/i\ host: 0.0.0.0' ./api/conf/conf.yaml - - - name: build docker images - working-directory: ./api/test/docker-deploy - continue-on-error: true - run: | - docker buildx bake --load \ - -f docker-compose.yaml \ - --set *.cache-from=type=local,src=/tmp/.buildx-cache \ - --set *.cache-to=type=local,dest=/tmp/.buildx-cache - - - name: Run Docker Compose - working-directory: ./api/test/docker-deploy - run: | - docker-compose up -d - sleep 5 - docker logs docker-deploy_managerapi_1 - - - name: Run Test - run: api/test/shell/manager_smoking.sh -s false diff --git a/.github/workflows/frontend-e2e-test.yml b/.github/workflows/frontend-e2e-test.yml deleted file mode 100644 index 3a9830f46a..0000000000 --- a/.github/workflows/frontend-e2e-test.yml +++ /dev/null @@ -1,108 +0,0 @@ -name: Frontend e2e test - -on: - push: - branches: - - master - paths-ignore: - - 'docs/**' - pull_request: - branches: - - master - paths-ignore: - - 'docs/**' - -concurrency: - group: ${{ github.workflow }}-${{ github.ref == 'refs/heads/master' && github.run_number || github.ref }} - cancel-in-progress: true - -env: - CYPRESS_CACHE_FOLDER: cypress/cache -defaults: - run: - working-directory: web - -jobs: - web-e2e: - name: Frontend e2e test - strategy: - fail-fast: false - matrix: - folderPrefix: ['consumer', 'route', 'plugin', 'rest'] - runs-on: ubuntu-latest - services: - etcd: - image: bitnami/etcd:3.5.2 - ports: - - 2379:2379 - - 2380:2380 - env: - ALLOW_NONE_AUTHENTICATION: yes - - steps: - - name: Checkout - uses: actions/checkout@v3 - with: - submodules: recursive - - - name: Setup Node.js environment - uses: actions/setup-node@v3 - with: - node-version: 16.x - cache: 'yarn' - cache-dependency-path: web/yarn.lock - - - name: Setup golang environment - uses: actions/setup-go@v3 - with: - go-version: '1.18' - - - uses: actions/cache@v3 - with: - path: | - ~/.cache/go-build - ~/go/pkg/mod - key: ${{ runner.os }}-go-${{ hashFiles('**/go.sum') }} - restore-keys: | - ${{ runner.os }}-go- - - - name: Start manager-api - working-directory: ./ - run: | - make dag-lib - cd api - sed -i 's@# - dubbo-proxy@- dubbo-proxy@' ./conf/conf.yaml - nohup go run ./main.go & - - - name: Cache Cypress binary - uses: actions/cache@v3 - id: cypress-binary-cache - with: - path: '*/cypress/cache' - key: cypress-${{ runner.os }}-cypress-cache0-${{ hashFiles('**/package.json') }} - restore-keys: | - cypress-${{ runner.os }}-cypress-cache0 - - - name: Install dependencies - run: yarn - - - name: Start frontend then test - run: | - yarn start-server-and-test 'cross-env SERVE_ENV=test UMI_UI=none MOCK=none SERVE_URL_TEST=http://localhost:9000 yarn start' http-get://localhost:8000 'cross-env CYPRESS_SERVE_ENV=test SERVE_URL_TEST=http://localhost:9000 yarn cypress run --spec "**/e2e/${{matrix.folderPrefix}}/**.cy.js"' - - - name: Report e2e coverage - run: npx nyc report --reporter=text-summary - - - name: Upload coverage to Codecov - run: | - bash <(curl -s https://codecov.io/bash) -f ./coverage/coverage-final.json -F frontend-e2e-test - - - name: Archive code coverage results - uses: actions/upload-artifact@v3 - if: always() - with: - name: cypress-report-${{matrix.folderPrefix}} - path: | - web/cypress/videos - web/cypress/screenshots - retention-days: 5 diff --git a/.github/workflows/go-lint.yml b/.github/workflows/go-lint.yml deleted file mode 100644 index fd36206a28..0000000000 --- a/.github/workflows/go-lint.yml +++ /dev/null @@ -1,54 +0,0 @@ -name: go-lint -on: - push: - branches: - - master - paths: - - 'api/**' - pull_request: - branches: - - master - paths: - - 'api/**' - -jobs: - golangci: - runs-on: ubuntu-latest - needs: go-filter - if: needs.go-filter.outputs.matches == 'true' - steps: - - uses: actions/checkout@v3 - - - name: golangci-lint - uses: golangci/golangci-lint-action@v3 - with: - version: latest - working-directory: api - args: --tests=false - only-new-issues: true - gofmt: - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v3 - - name: setup go - uses: actions/setup-go@v3 - with: - go-version: '1.18' - - - uses: actions/cache@v3 - with: - path: | - ~/.cache/go-build - ~/go/pkg/mod - key: ${{ runner.os }}-go-${{ hashFiles('**/go.sum') }} - restore-keys: | - ${{ runner.os }}-go- - - - name: check gofmt - run: | - diffs=`gofmt -l ${{ needs.go-filter.outputs.files }}` - if [[ -n $diffs ]]; then - echo "Files are not formatted by gofmt:" - echo $diffs - exit 1 - fi diff --git a/.github/workflows/license-checker.yml b/.github/workflows/license-checker.yml deleted file mode 100644 index 87e53281cd..0000000000 --- a/.github/workflows/license-checker.yml +++ /dev/null @@ -1,20 +0,0 @@ -name: License checker - -on: - push: - branches: - - master - pull_request: - branches: - - master - -jobs: - check-license: - runs-on: ubuntu-latest - - steps: - - uses: actions/checkout@v3 - - - name: run license check - run: | - make license-check diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml deleted file mode 100644 index 334a66626d..0000000000 --- a/.github/workflows/lint.yml +++ /dev/null @@ -1,55 +0,0 @@ -name: โ„๏ธ Lint - -on: [pull_request] - -jobs: - markdownlint: - name: ๐Ÿ‡ Markdown - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v3 - - name: ๐Ÿš€ Use Node.js - uses: actions/setup-node@v3 - with: - node-version: '16.x' - - run: npm install -g markdownlint-cli@0.25.0 - - run: markdownlint '**/*.md' --ignore node_modules - misspell: - name: runner/misspell - runs-on: ubuntu-latest - steps: - - name: Check out code. - uses: actions/checkout@v3 - - name: Install - run: | - wget -O - -q https://git.io/misspell | sh -s -- -b . - - name: Misspell - run: | - find . -type f -maxdepth 1 | xargs ./misspell -error - find . -name "*.go" -type f | xargs ./misspell -error - find docs -type f | xargs ./misspell -error - find web/src web/cypress -type f | xargs ./misspell -error - yamllint: - name: ๐Ÿ YAML - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v3 - - uses: actions/setup-python@v4 - with: - python-version: '3.x' # Version range or exact version of a Python version to use, using SemVer's version range syntax - architecture: 'x64' # optional x64 or x86. Defaults to x64 if not specified - - name: Install dependencies - run: | - python -m pip install --upgrade pip - pip install yamllint - - name: ๐Ÿงน YAML Lint - run: | - # return non-zero exit code on warnings - yamllint --strict . - trailing-whitespace: - name: Trailing whitespace - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v3 - - name: Check for trailing whitespace - run: "! git grep -EIn $'[ \t]+$'" diff --git a/.github/workflows/make-build.yaml b/.github/workflows/make-build.yaml deleted file mode 100644 index 9ebce94679..0000000000 --- a/.github/workflows/make-build.yaml +++ /dev/null @@ -1,72 +0,0 @@ -# Test make build command to see if ManagerAPI and Web are ok to build - -name: make build - -on: - push: - branches: - - master - paths-ignore: - - 'docs/**' - pull_request: - branches: - - master - paths-ignore: - - 'docs/**' - -concurrency: - group: ${{ github.workflow }}-${{ github.ref == 'refs/heads/master' && github.run_number || github.ref }} - cancel-in-progress: true - -jobs: - make-build-test: - name: make build test - runs-on: ubuntu-latest - - services: - etcd: - image: bitnami/etcd:3.5.2 - ports: - - 2379:2379 - - 2380:2380 - env: - ALLOW_NONE_AUTHENTICATION: yes - - steps: - - name: Checkout - uses: actions/checkout@v3 - with: - submodules: recursive - - - name: Setup Node.js environment - uses: actions/setup-node@v3 - with: - node-version: 16.x - cache: 'yarn' - cache-dependency-path: web/yarn.lock - - - name: Setup golang environment - uses: actions/setup-go@v3 - with: - go-version: '1.18' - - - uses: actions/cache@v3 - with: - path: | - ~/.cache/go-build - ~/go/pkg/mod - key: ${{ runner.os }}-go-${{ hashFiles('**/go.sum') }} - restore-keys: | - ${{ runner.os }}-go- - - - name: make build - run: make build - - - name: start dashboard - working-directory: ./output - run: | - ./manager-api > ./api.log 2>&1 & - sleep 5 - - - name: check - run: api/test/shell/manager_smoking.sh -s true diff --git a/.github/workflows/release-test.yml b/.github/workflows/release-test.yml deleted file mode 100644 index 0e11793e9e..0000000000 --- a/.github/workflows/release-test.yml +++ /dev/null @@ -1,98 +0,0 @@ -name: Release Test - -on: - push: - branches: - - master - paths-ignore: - - '.github/**' - - '.git/**' - - '.gitattributes' - - '.vscode/**' - - '.gitignore' - - 'docs/**' - - 'api/internal/core/store/validate_mock.go' - - 'api/internal/core/storage/storage_mock.go' - pull_request: - branches: - - master - paths-ignore: - - '.github/**' - - '.git/**' - - '.gitattributes' - - '.vscode/**' - - '.gitignore' - - 'docs/**' - - 'api/internal/core/store/validate_mock.go' - - 'api/internal/core/storage/storage_mock.go' - -jobs: - run-test: - runs-on: ubuntu-latest - - services: - etcd: - image: bitnami/etcd:3.5.2 - ports: - - 2379:2379 - - 2380:2380 - env: - ALLOW_NONE_AUTHENTICATION: yes - - steps: - - uses: actions/checkout@v3 - - - name: setup go - uses: actions/setup-go@v3 - with: - go-version: '1.19' - - - uses: actions/cache@v3 - with: - path: | - ~/.cache/go-build - ~/go/pkg/mod - key: ${{ runner.os }}-go-${{ hashFiles('**/go.sum') }} - restore-keys: | - ${{ runner.os }}-go- - - - name: release and build - run: | - VERSION=$(cat ./api/VERSION) - mkdir release - git clean -Xdf - tar -zcvf release/apache-apisix-dashboard-${VERSION}-src.tgz \ - --exclude .github \ - --exclude .git \ - --exclude .gitattributes \ - --exclude .idea \ - --exclude .vscode \ - --exclude .gitignore \ - --exclude .DS_Store \ - --exclude docs \ - --exclude release \ - --exclude api/internal/core/store/validate_mock.go \ - --exclude api/internal/core/storage/storage_mock.go \ - . - - cd release - tar -zxvf apache-apisix-dashboard-${VERSION}-src.tgz - ls -l - export GO111MOUDULE=on - api/build.sh - mkdir -p ./output/logs - cd output - ./manager-api > ./api.log 2>&1 & - sleep 3 - cat ./api.log - cat ./logs/error.log - cat conf/conf.yaml - - - name: run test - run: | - curl http://127.0.0.1:9000/apisix/admin/user/login -X POST -i -d '{"username":"admin", "password": "admin"}' - code=$(curl -k -i -m 20 -o /dev/null -s -w %{http_code} http://127.0.0.1:9000/apisix/admin/user/login -X POST -i -d '{"username":"admin", "password": "admin"}') - if [ ! $code -eq 200 ]; then - echo "failed: failed to custom port" - exit 1 - fi diff --git a/.github/workflows/test-frontend-multiple-node-build.yml b/.github/workflows/test-frontend-multiple-node-build.yml deleted file mode 100644 index 61d1cf25c8..0000000000 --- a/.github/workflows/test-frontend-multiple-node-build.yml +++ /dev/null @@ -1,54 +0,0 @@ -# This is a basic test to build the dashboard - -name: Test building web in multiple node version - -# Controls when the action will run. Triggers the workflow on push or pull request -# events but only for the master branch -on: - push: - branches: - - master - pull_request: - branches: - - master - -concurrency: - group: ${{ github.workflow }}-${{ github.ref == 'refs/heads/master' && github.run_number || github.ref }} - cancel-in-progress: true - -# A workflow run is made up of one or more jobs that can run sequentially or in parallel -jobs: - # This workflow contains a single job called "build" - build: - # The type of runner that the job will run on - runs-on: ubuntu-latest - - strategy: - matrix: - node-version: [14.x, 16.x] - - # Steps represent a sequence of tasks that will be executed as part of the job - steps: - # Checks-out your repository under $GITHUB_WORKSPACE, so your job can access it - - uses: actions/checkout@v3 - with: - submodules: recursive - - name: Use Node.js ${{ matrix.node-version }} - uses: actions/setup-node@v3 - with: - node-version: ${{ matrix.node-version }} - cache: 'yarn' - cache-dependency-path: web/yarn.lock - - # Install dependencies - - name: Install dependencies - working-directory: web - run: CYPRESS_INSTALL_BINARY=0 yarn - - - name: Lint - working-directory: web - run: yarn run lint:js && yarn run lint:style - - - name: Build the Dashboard - working-directory: web - run: yarn build diff --git a/.gitignore b/.gitignore index 2a34cd128d..15c6f2f74e 100644 --- a/.gitignore +++ b/.gitignore @@ -1,73 +1,26 @@ -# See https://help.github.com/articles/ignoring-files/ for more about ignoring files. - -# dependencies -**/node_modules -# roadhog-api-doc ignore -/src/utils/request-temp.js -_roadhog-api-doc +# Logs +logs +*.log +npm-debug.log* +yarn-debug.log* +yarn-error.log* +pnpm-debug.log* +lerna-debug.log* -# production +node_modules dist +dist-ssr +*.local -# misc -.DS_Store -npm-debug.log* -yarn-error.log - -/coverage +# Editor directories and files +.vscode/* +!.vscode/extensions.json .idea -package-lock.json -*bak - -# visual studio code -.history -*.log -functions/* -.temp/** - -# umi -.umi -.umi-production - -# screenshot -screenshot -.firebase -.eslintcache - -build - -/compose/**/*.log -/compose/**/nginx.pid -/compose/etcd_data -manager-api -grpc-server-example/ -api/test/docker/*tar.gz* - -output -apisix_logs -default.etcd -api/build-tools/apisix -/*.zip -.githash -.backup.yaml -Dockerfile-apisix - -# backend unit test output -api/coverage.txt -api/coverage.html -api/dag-to-lua/ - -# frontend e2e test output -web/.nyc_output -web/coverage -web/cypress/screenshots/ -web/cypress/videos/ -web/cypress/downloads/ -web/public/monaco-editor/ - -# vim -*.swp -*.swo +.DS_Store +*.suo +*.ntvs* +*.njsproj +*.sln +*.sw? -# .env -.env* +/.pnpm-store diff --git a/.husky/pre-commit b/.husky/pre-commit new file mode 100644 index 0000000000..20c5a9a7ab --- /dev/null +++ b/.husky/pre-commit @@ -0,0 +1,2 @@ +pnpm lint-staged +git update-index --again diff --git a/.vscode/settings.json b/.vscode/settings.json index e521849f2c..47713122f9 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -1,8 +1,37 @@ { "eslint.validate": [ - "javascript", - "javascriptreact", - { "language": "typescript", "autoFix": true }, - { "language": "typescriptreact", "autoFix": true } - ] + "typescriptreact", + "typescript", + ], + "files.readonlyInclude": { + "**/routeTree.gen.ts": true + }, + "files.watcherExclude": { + "**/routeTree.gen.ts": true + }, + "search.exclude": { + "**/routeTree.gen.ts": true + }, + "cssVariables.lookupFiles": [ + "**/*.css", + "**/*.scss", + "**/*.sass", + "**/*.less", + "node_modules/@mantine/core/styles.css" + ], + "i18n-ally.sortKeys": true, + "i18n-ally.localesPaths": [ + "src/locales", + ], + "i18n-ally.keystyle": "nested", + "i18n-ally.enabledFrameworks": [ + "react-i18next" + ], + "i18n-ally.pathMatcher": "{locale}/{namespaces}.json", + "i18n-ally.editor.preferEditor": true, + "i18n-ally.translate.saveAsCandidates": true, + "typescript.tsdk": "node_modules/typescript/lib", + "editor.codeActionsOnSave": { + "source.fixAll.eslint": "always" + }, } diff --git a/Dockerfile b/Dockerfile deleted file mode 100644 index 0aed27d616..0000000000 --- a/Dockerfile +++ /dev/null @@ -1,68 +0,0 @@ -# -# Licensed to the Apache Software Foundation (ASF) under one or more -# contributor license agreements. See the NOTICE file distributed with -# this work for additional information regarding copyright ownership. -# The ASF licenses this file to You under the Apache License, Version 2.0 -# (the "License"); you may not use this file except in compliance with -# the License. You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -# -FROM alpine:latest as pre-build - -COPY . /usr/local/apisix-dashboard - -RUN set -x \ - && apk add --no-cache --virtual .builddeps git \ - && cd /usr/local/apisix-dashboard && git clean -Xdf \ - && rm -f ./.githash && git log --pretty=format:"%h" -1 > ./.githash - -FROM golang:1.19 as api-builder - -ARG ENABLE_PROXY=false - -WORKDIR /usr/local/apisix-dashboard - -COPY --from=pre-build /usr/local/apisix-dashboard . - -RUN if [ "$ENABLE_PROXY" = "true" ] ; then go env -w GOPROXY=https://goproxy.io,direct ; fi \ - && go env -w GO111MODULE=on \ - && CGO_ENABLED=0 ./api/build.sh - -FROM node:16-alpine as fe-builder - -ARG ENABLE_PROXY=false - -WORKDIR /usr/local/apisix-dashboard - -COPY --from=pre-build /usr/local/apisix-dashboard . - -WORKDIR /usr/local/apisix-dashboard/web - -RUN if [ "$ENABLE_PROXY" = "true" ] ; then yarn config set registry https://registry.npmmirror.com/ ; fi \ - && yarn install \ - && yarn build - -FROM alpine:latest as prod - -ARG ENABLE_PROXY=false - -RUN if [ "$ENABLE_PROXY" = "true" ] ; then sed -i 's/dl-cdn.alpinelinux.org/mirrors.ustc.edu.cn/g' /etc/apk/repositories ; fi - -WORKDIR /usr/local/apisix-dashboard - -COPY --from=api-builder /usr/local/apisix-dashboard/output/ ./ - -COPY --from=fe-builder /usr/local/apisix-dashboard/output/ ./ - -RUN mkdir logs - -EXPOSE 9000 - -CMD [ "/usr/local/apisix-dashboard/manager-api" ] diff --git a/Makefile b/Makefile deleted file mode 100644 index 38462c83ff..0000000000 --- a/Makefile +++ /dev/null @@ -1,132 +0,0 @@ -# -# Licensed to the Apache Software Foundation (ASF) under one or more -# contributor license agreements. See the NOTICE file distributed with -# this work for additional information regarding copyright ownership. -# The ASF licenses this file to You under the Apache License, Version 2.0 -# (the "License"); you may not use this file except in compliance with -# the License. You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -# - -SHELL := /bin/bash -o pipefail -UNAME ?= $(shell uname) -YARN_EXEC ?= $(shell which yarn) -GO_EXEC ?= $(shell which go) - -VERSION ?= latest -RELEASE_SRC = apache-apisix-dashboard-${VERSION}-src - -export GO111MODULE=on - -### help: Show Makefile rules -.PHONY: help -help: - @echo Makefile rules: - @echo - @grep -E '^### [-A-Za-z0-9_]+:' Makefile | sed 's/###/ /' - - -### build: Build the Apache APISIX Dashboard, including web and manager-api -.PHONY: build -build: web-default api-default - api/build.sh && cd ./web && export CYPRESS_INSTALL_BINARY=0 && yarn install --ignore-scripts && yarn build && mkdir -p ../output/logs - - -.PHONY: web-default -web-default: -ifeq ("$(wildcard $(YARN_EXEC))", "") - @echo "ERROR: Need to install yarn first" - exit 1 -endif - - -.PHONY: api-default -api-default: -ifeq ("$(wildcard $(GO_EXEC))", "") - @echo "ERROR: Need to install golang 1.15+ first" - exit 1 -endif - - -### dag-lib: Download the dag-lib -.PHONY: dag-lib -dag-lib: -ifeq ("$(wildcard api/dag-to-lua/dag-to-lua.lua)", "") - curl -Lso /tmp/v1.1.tar.gz https://github.com/api7/dag-to-lua/archive/v1.1.tar.gz - tar -zxvf /tmp/v1.1.tar.gz -C /tmp - mkdir ./api/dag-to-lua - cp -r /tmp/dag-to-lua-1.1/lib/* ./api/dag-to-lua -endif - - -### api-test: Run the tests of manager-api -.PHONY: api-test -api-test: api-default dag-lib - cd api/ && APISIX_API_WORKDIR=$$PWD ENV=test go test -v -count=1 -race -cover -coverprofile=coverage.txt -covermode=atomic ./... && go tool cover -html=coverage.txt -o coverage.html - - -### api-run: Run the manager-api in develop mode -.PHONY: api-run -api-run: api-default - api/build.sh --dry-run - - -### api-stop: Stop the manager-api -api-stop: - cd api && go run -ldflags "${GOLDFLAGS}" ./main.go stop - -### go-lint: Lint Go source codes -.PHONY: go-lint -go-lint: ## Run the golangci-lint application (install if not found) - @#Brew - MacOS - @if [ "$(shell command -v golangci-lint)" = "" ] && [ "$(shell command -v brew)" != "" ] && [ "$(UNAME)" = "Darwin" ]; then brew install golangci-lint; fi; - @#has sudo - @if [ "$(shell command -v golangci-lint)" = "" ]; then curl -sSfL https://raw.githubusercontent.com/golangci/golangci-lint/master/install.sh | sh -s v1.32.0 && sudo cp ./bin/golangci-lint $(go env GOPATH)/bin/; fi; - @echo "running golangci-lint..." - @cd api && golangci-lint run --tests=false ./... - - -### license-check: Check source codes for Apache License -.PHONY: license-check -license-check: -ifeq ("$(wildcard .actions/openwhisk-utilities/scancode/scanCode.py)", "") - git clone https://github.com/apache/openwhisk-utilities.git .actions/openwhisk-utilities - cp .actions/ASF* .actions/openwhisk-utilities/scancode/ -endif - .actions/openwhisk-utilities/scancode/scanCode.py --config .actions/ASF-Release.cfg ./ - - -### release-src: Package source codes for release -.PHONY: release-src -release-src: - ./utils/version-check.sh ${VERSION} - git clean -Xdf - rm -f ./.githash && git log --pretty=format:"%h" -1 > ./.githash - tar -zcvf $(RELEASE_SRC).tgz \ - --exclude .github \ - --exclude .git \ - --exclude .gitattributes \ - --exclude .idea \ - --exclude .vscode \ - --exclude .gitignore \ - --exclude .DS_Store \ - --exclude docs \ - --exclude release \ - --exclude api/internal/core/store/validate_mock.go \ - --exclude api/internal/core/storage/storage_mock.go \ - . - - gpg --batch --yes --armor --detach-sig $(RELEASE_SRC).tgz - shasum -a 512 $(RELEASE_SRC).tgz > $(RELEASE_SRC).tgz.sha512 - - mkdir -p release - mv $(RELEASE_SRC).tgz release/$(RELEASE_SRC).tgz - mv $(RELEASE_SRC).tgz.asc release/$(RELEASE_SRC).tgz.asc - mv $(RELEASE_SRC).tgz.sha512 release/$(RELEASE_SRC).tgz.sha512 diff --git a/README.md b/README.md index e572a73bde..da9844432b 100644 --- a/README.md +++ b/README.md @@ -1,109 +1,54 @@ - - -# Apache APISIX Dashboard - -[![License](https://img.shields.io/badge/License-Apache%202.0-blue.svg)](https://github.com/apache/apisix-dashboard/blob/master/LICENSE) -[![Go Report Card](https://goreportcard.com/badge/github.com/apache/apisix-dashboard)](https://goreportcard.com/report/github.com/apache/apisix-dashboard) -[![DockerHub](https://img.shields.io/docker/pulls/apache/apisix-dashboard.svg)](https://hub.docker.com/r/apache/apisix-dashboard) -[![Cypress.io](https://img.shields.io/badge/tested%20with-Cypress-04C38E.svg)](https://www.cypress.io/) -[![Slack](https://badgen.net/badge/Slack/Join%20Apache%20APISIX?icon=slack)](https://apisix.apache.org/slack) - -

- Website โ€ข - Docs โ€ข - Twitter -

- -- The master version should be used with Apache APISIX master version. - -- The latest released version is [3.0.0](https://apisix.apache.org/downloads/) and is compatible with [Apache APISIX 3.0.x](https://apisix.apache.org/downloads/). - -## What's Apache APISIX Dashboard - -The Apache APISIX Dashboard is designed to make it as easy as possible for users to operate [Apache APISIX](https://github.com/apache/apisix) through a frontend interface. - -The Dashboard is the control plane and performs all parameter checks; Apache APISIX mixes data and control planes and will evolve to a pure data plane. - -Note: Currently the Dashboard does not have complete coverage of Apache APISIX features, [visit here](https://github.com/apache/apisix-dashboard/milestones) to view the milestones. - -![architecture](./docs/assets/images/architecture.png) - -## Demo - -[Online Playground](https://apisix-dashboard.apiseven.com/) - -```text -Username: admin -Password: admin +# React + TypeScript + Vite + +This template provides a minimal setup to get React working in Vite with HMR and some ESLint rules. + +Currently, two official plugins are available: + +- [@vitejs/plugin-react](https://github.com/vitejs/vite-plugin-react/blob/main/packages/plugin-react) uses [Babel](https://babeljs.io/) for Fast Refresh +- [@vitejs/plugin-react-swc](https://github.com/vitejs/vite-plugin-react/blob/main/packages/plugin-react-swc) uses [SWC](https://swc.rs/) for Fast Refresh + +## Expanding the ESLint configuration + +If you are developing a production application, we recommend updating the configuration to enable type-aware lint rules: + +```js +export default tseslint.config({ + extends: [ + // Remove ...tseslint.configs.recommended and replace with this + ...tseslint.configs.recommendedTypeChecked, + // Alternatively, use this for stricter rules + ...tseslint.configs.strictTypeChecked, + // Optionally, add this for stylistic rules + ...tseslint.configs.stylisticTypeChecked, + ], + languageOptions: { + // other options... + parserOptions: { + project: ['./tsconfig.node.json', './tsconfig.app.json'], + tsconfigRootDir: import.meta.dirname, + }, + }, +}) ``` -## Works with APISIX Ingress Controller - -Currently, APISIX Ingress Controller automatically manipulates some APISIX resources, which is not very compatible with APISIX Dashboard. In addition, users should not modify resources labeled `managed-by: apisix-ingress-controllers` via APISIX Dashboard. - -## Project structure - -```text -. -โ”œโ”€โ”€ CHANGELOG.md -โ”œโ”€โ”€ CODE_OF_CONDUCT.md -โ”œโ”€โ”€ CONTRIBUTING.md -โ”œโ”€โ”€ Dockerfile -โ”œโ”€โ”€ LICENSE -โ”œโ”€โ”€ Makefile -โ”œโ”€โ”€ NOTICE -โ”œโ”€โ”€ README.md -โ”œโ”€โ”€ api -โ”œโ”€โ”€ docs -โ”œโ”€โ”€ licenses -โ””โ”€โ”€ web +You can also install [eslint-plugin-react-x](https://github.com/Rel1cx/eslint-react/tree/main/packages/plugins/eslint-plugin-react-x) and [eslint-plugin-react-dom](https://github.com/Rel1cx/eslint-react/tree/main/packages/plugins/eslint-plugin-react-dom) for React-specific lint rules: + +```js +// eslint.config.js +import reactX from 'eslint-plugin-react-x' +import reactDom from 'eslint-plugin-react-dom' + +export default tseslint.config({ + plugins: { + // Add the react-x and react-dom plugins + 'react-x': reactX, + 'react-dom': reactDom, + }, + rules: { + // other rules... + // Enable its recommended typescript rules + ...reactX.configs['recommended-typescript'].rules, + ...reactDom.configs.recommended.rules, + }, +}) ``` - -1. The `api` directory is used to store the `Manager API` source codes, which is used to manage `etcd` and provide APIs to the frontend interface. -2. The `web` directory is used to store the frontend source codes. - -## Build then launch - -Support the following ways currently. - -- [Docker, RPM, Source Codes](./docs/en/latest/install.md) -- [Rebuild docker image](./docs/en/latest/deploy-with-docker.md) - -## Development - -Pull requests are encouraged and always welcome. [Pick an issue](https://github.com/apache/apisix-dashboard/issues?q=is%3Aopen+is%3Aissue+label%3A%22good+first+issue%22) and help us out! - -Please refer to the [Development Guide](./docs/en/latest/develop.md). - -## User Guide - -Please refer to the [User Guide](./docs/en/latest/USER_GUIDE.md). - -## Contributing - -Please refer to the [Contribution Guide](./CONTRIBUTING.md) for a more detailed information. - -## FAQ - -Please refer to the [FAQ](./docs/en/latest/FAQ.md) for more known issues. - -## License - -[Apache License 2.0](./LICENSE) diff --git a/api/README.md b/api/README.md deleted file mode 100644 index e574fe0f85..0000000000 --- a/api/README.md +++ /dev/null @@ -1,48 +0,0 @@ - - -# manager-api - -This is a backend project which the dashboard depends on, implemented by Golang. - -## Installation - -[Please refer to the doc](../README.md) - -## Project structure - -```text -โ”œโ”€โ”€ README.md -โ”œโ”€โ”€ VERSION -โ”œโ”€โ”€ build-tools -โ”œโ”€โ”€ build.sh -โ”œโ”€โ”€ cmd -โ”œโ”€โ”€ conf -โ”œโ”€โ”€ entry.sh -โ”œโ”€โ”€ go.mod -โ”œโ”€โ”€ go.sum -โ”œโ”€โ”€ internal -โ”œโ”€โ”€ run.sh -โ””โ”€โ”€ test -``` - -1. The `cmd` directory is the project entrance. -2. The `internal` directory contains the main logic of manager-api. -3. The `conf` directory contains the default configuration file. -4. The `test` directory contains E2E test cases. diff --git a/api/VERSION b/api/VERSION deleted file mode 100644 index 4a36342fca..0000000000 --- a/api/VERSION +++ /dev/null @@ -1 +0,0 @@ -3.0.0 diff --git a/api/build.sh b/api/build.sh deleted file mode 100755 index db74cb20c1..0000000000 --- a/api/build.sh +++ /dev/null @@ -1,51 +0,0 @@ -#!/usr/bin/env bash -# -# Licensed to the Apache Software Foundation (ASF) under one or more -# contributor license agreements. See the NOTICE file distributed with -# this work for additional information regarding copyright ownership. -# The ASF licenses this file to You under the Apache License, Version 2.0 -# (the "License"); you may not use this file except in compliance with -# the License. You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -# -set -e - -VERSION=$(cat ./api/VERSION) -GITHASH=$(cat ./.githash 2> /dev/null || HASH="ref: HEAD"; while [[ $HASH == ref\:* ]]; do HASH="$(cat ".git/$(echo $HASH | cut -d \ -f 2)")"; done; echo ${HASH:0:7}) - -GOLDFLAGS="-X github.com/apisix/manager-api/internal/utils.version=${VERSION} -X github.com/apisix/manager-api/internal/utils.gitHash=${GITHASH}" - -# Enter dry-run mode -if [ "$1" == "--dry-run" ]; then - cd ./api && go run -ldflags "${GOLDFLAGS}" ./main.go - exit 0 -fi - -set -x -export ENV=local -pwd=$(pwd) - -rm -rf output && mkdir -p output/conf && mkdir -p output/dag-to-lua - -# get dag-to-lua lib -if [[ ! -f "dag-to-lua-1.1/lib/dag-to-lua.lua" ]]; then - wget https://github.com/api7/dag-to-lua/archive/v1.1.tar.gz -P /tmp - tar -zxvf /tmp/v1.1.tar.gz -C /tmp - cp -r /tmp/dag-to-lua-1.1/lib/* ./output/dag-to-lua -fi - -# build -cd ./api && go build -o ../output/manager-api -ldflags "${GOLDFLAGS}" ./main.go && cd .. - -cp ./api/conf/schema.json ./output/conf/schema.json -cp ./api/conf/customize_schema.json ./output/conf/customize_schema.json -cp ./api/conf/conf*.yaml ./output/conf/ - -echo "Build the Manager API successfully" diff --git a/api/cmd/root.go b/api/cmd/root.go deleted file mode 100644 index 4d3e033615..0000000000 --- a/api/cmd/root.go +++ /dev/null @@ -1,147 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one or more - * contributor license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright ownership. - * The ASF licenses this file to You under the Apache License, Version 2.0 - * (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package cmd - -import ( - "context" - "fmt" - "os" - "os/signal" - "syscall" - "time" - - "github.com/spf13/cobra" - - "github.com/apisix/manager-api/internal/conf" - "github.com/apisix/manager-api/internal/core/server" - "github.com/apisix/manager-api/internal/core/storage" - "github.com/apisix/manager-api/internal/core/store" - "github.com/apisix/manager-api/internal/log" -) - -var rootCmd = &cobra.Command{ - Use: "manager-api", - Short: "Apache APISIX Manager API", - RunE: func(cmd *cobra.Command, args []string) error { - err := manageAPI() - return err - }, -} - -func init() { - rootCmd.PersistentFlags().StringVarP(&conf.ConfigFile, "config", "c", "", "config file") - rootCmd.PersistentFlags().StringVarP(&conf.WorkDir, "work-dir", "p", ".", "current work directory") - - rootCmd.AddCommand( - newVersionCommand(), - ) -} - -func Execute() { - if err := rootCmd.Execute(); err != nil { - _, _ = fmt.Fprintln(os.Stderr, err) - } -} - -func manageAPI() error { - conf.InitConf() - log.InitLogger() - - s, err := server.NewServer(&server.Options{}) - if err != nil { - return err - } - - // start Manager API server - errSig := make(chan error, 5) - s.Start(errSig) - - // start etcd connection checker - stopEtcdConnectionChecker := etcdConnectionChecker() - - // Signal received to the process externally. - quit := make(chan os.Signal, 1) - signal.Notify(quit, syscall.SIGINT, syscall.SIGTERM) - - select { - case sig := <-quit: - log.Infof("The Manager API server receive %s and start shutting down", sig.String()) - stopEtcdConnectionChecker() - s.Stop() - log.Infof("See you next time!") - case err := <-errSig: - log.Errorf("The Manager API server start failed: %s", err.Error()) - return err - } - return nil -} - -func etcdConnectionChecker() context.CancelFunc { - ctx, cancel := context.WithCancel(context.TODO()) - unavailableTimes := 0 - - go func() { - etcdClient := storage.GenEtcdStorage().GetClient() - for { - select { - case <-time.Tick(10 * time.Second): - sCtx, sCancel := context.WithTimeout(ctx, 5*time.Second) - err := etcdClient.Sync(sCtx) - sCancel() - if err != nil { - unavailableTimes++ - log.Errorf("etcd connection loss detected, times: %d", unavailableTimes) - continue - } - - // After multiple failures, the connection is restored - if unavailableTimes >= 1 { - log.Warnf("etcd connection recovered, but after several connection losses, reinitializing stores, times: %d", unavailableTimes) - unavailableTimes = 0 - - // When this happens, force a full re-initialization of the store - store.RangeStore(func(key store.HubKey, store *store.GenericStore) bool { - log.Warnf("etcd store reinitializing: resource: %s", key) - if err := store.Init(); err != nil { - log.Errorf("etcd store reinitialize failed: resource: %s, error: %s", key, err) - } - return true - }) - } else { - log.Info("etcd connection is fine") - } - case <-ctx.Done(): - return - } - } - }() - - // Timed re-initialization when etcd watch actively exits - go func() { - for { - select { - case <-time.Tick(2 * time.Minute): - err := store.ReInit() - if err != nil { - log.Errorf("resource re-initialize failed, err: %v", err) - } - } - } - }() - - return cancel -} diff --git a/api/cmd/version.go b/api/cmd/version.go deleted file mode 100644 index c064502bd3..0000000000 --- a/api/cmd/version.go +++ /dev/null @@ -1,33 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one or more - * contributor license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright ownership. - * The ASF licenses this file to You under the Apache License, Version 2.0 - * (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package cmd - -import ( - "github.com/spf13/cobra" - - "github.com/apisix/manager-api/internal/utils" -) - -func newVersionCommand() *cobra.Command { - return &cobra.Command{ - Use: "version", - Short: "show manager-api version", - Run: func(cmd *cobra.Command, args []string) { - utils.PrintVersion() - }, - } -} diff --git a/api/conf/conf.yaml b/api/conf/conf.yaml deleted file mode 100644 index a74986de77..0000000000 --- a/api/conf/conf.yaml +++ /dev/null @@ -1,174 +0,0 @@ -# -# Licensed to the Apache Software Foundation (ASF) under one or more -# contributor license agreements. See the NOTICE file distributed with -# this work for additional information regarding copyright ownership. -# The ASF licenses this file to You under the Apache License, Version 2.0 -# (the "License"); you may not use this file except in compliance with -# the License. You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -# - -# yamllint disable rule:comments-indentation -conf: - listen: - # host: 127.0.0.1 # the address on which the `Manager API` should listen. - # The default value is 0.0.0.0, if want to specify, please enable it. - # This value accepts IPv4, IPv6, and hostname. - port: 9000 # The port on which the `Manager API` should listen. - - # ssl: - # host: 127.0.0.1 # the address on which the `Manager API` should listen for HTTPS. - # The default value is 0.0.0.0, if want to specify, please enable it. - # port: 9001 # The port on which the `Manager API` should listen for HTTPS. - # cert: "/tmp/cert/example.crt" # Path of your SSL cert. - # key: "/tmp/cert/example.key" # Path of your SSL key. - - allow_list: # If we don't set any IP list, then any IP access is allowed by default. - - 127.0.0.1 # The rules are checked in sequence until the first match is found. - - ::1 # In this example, access is allowed only for IPv4 network 127.0.0.1, and for IPv6 network ::1. - # It also support CIDR like 192.168.1.0/24 and 2001:0db8::/32 - etcd: - endpoints: # supports defining multiple etcd host addresses for an etcd cluster - - 127.0.0.1:2379 - # yamllint disable rule:comments-indentation - # etcd basic auth info - # username: "root" # ignore etcd username if not enable etcd auth - # password: "123456" # ignore etcd password if not enable etcd auth - mtls: - key_file: "" # Path of your self-signed client side key - cert_file: "" # Path of your self-signed client side cert - ca_file: "" # Path of your self-signed ca cert, the CA is used to sign callers' certificates - # prefix: /apisix # apisix config's prefix in etcd, /apisix by default - log: - error_log: - level: warn # supports levels, lower to higher: debug, info, warn, error, panic, fatal - file_path: - logs/error.log # supports relative path, absolute path, standard output - # such as: logs/error.log, /tmp/logs/error.log, /dev/stdout, /dev/stderr - # such as absolute path on Windows: winfile:///C:\error.log - access_log: - file_path: - logs/access.log # supports relative path, absolute path, standard output - # such as: logs/access.log, /tmp/logs/access.log, /dev/stdout, /dev/stderr - # such as absolute path on Windows: winfile:///C:\access.log - # log example: 2020-12-09T16:38:09.039+0800 INFO filter/logging.go:46 /apisix/admin/routes/r1 {"status": 401, "host": "127.0.0.1:9000", "query": "asdfsafd=adf&a=a", "requestId": "3d50ecb8-758c-46d1-af5b-cd9d1c820156", "latency": 0, "remoteIP": "127.0.0.1", "method": "PUT", "errs": []} - max_cpu: 0 # supports tweaking with the number of OS threads are going to be used for parallelism. Default value: 0 [will use max number of available cpu cores considering hyperthreading (if any)]. If the value is negative, is will not touch the existing parallelism profile. - # security: - # access_control_allow_origin: "http://httpbin.org" - # access_control_allow_credentials: true # support using custom cors configration - # access_control_allow_headers: "Authorization" - # access_control-allow_methods: "*" - # x_frame_options: "deny" - # content_security_policy: "default-src 'self'; script-src 'self' 'unsafe-eval' 'unsafe-inline'; style-src 'self' 'unsafe-inline'; img-src 'self' data:; frame-src xx.xx.xx.xx:3000" # You can set frame-src to provide content for your grafana panel. - -authentication: - secret: - secret # secret for jwt token generation. - # NOTE: Highly recommended to modify this value to protect `manager api`. - # if it's default value, when `manager api` start, it will generate a random string to replace it. - expire_time: 3600 # jwt token expire time, in second - users: # yamllint enable rule:comments-indentation - - username: admin # username and password for login `manager api` - password: admin - - username: user - password: user - -oidc: - enabled: false - expire_time: 3600 - client_id: dashboard - client_secret: dashboard - auth_url: http://172.17.0.1:8080/auth/realms/master/protocol/openid-connect/auth - token_url: http://172.17.0.1:8080/auth/realms/master/protocol/openid-connect/token - user_info_url: http://172.17.0.1:8080/auth/realms/master/protocol/openid-connect/userinfo - redirect_url: http://127.0.0.1:9000/apisix/admin/oidc/callback - scope: openid - -plugins: - - api-breaker - - authz-casbin - - authz-casdoor - - authz-keycloak - - aws-lambda - - azure-functions - - basic-auth - # - batch-requests - - clickhouse-logger - - client-control - - consumer-restriction - - cors - - csrf - - datadog - # - dubbo-proxy - - echo - - error-log-logger - # - example-plugin - - ext-plugin-post-req - - ext-plugin-post-resp - - ext-plugin-pre-req - - fault-injection - - file-logger - - forward-auth - - google-cloud-logging - - grpc-transcode - - grpc-web - - gzip - - hmac-auth - - http-logger - - ip-restriction - - jwt-auth - - kafka-logger - - kafka-proxy - - key-auth - - ldap-auth - - limit-conn - - limit-count - - limit-req - - loggly - # - log-rotate - - mocking - # - node-status - - opa - - openid-connect - - opentelemetry - - openwhisk - - prometheus - - proxy-cache - - proxy-control - - proxy-mirror - - proxy-rewrite - - public-api - - real-ip - - redirect - - referer-restriction - - request-id - - request-validation - - response-rewrite - - rocketmq-logger - - server-info - - serverless-post-function - - serverless-pre-function - - skywalking - - skywalking-logger - - sls-logger - - splunk-hec-logging - - syslog - - tcp-logger - - traffic-split - - ua-restriction - - udp-logger - - uri-blocker - - wolf-rbac - - zipkin - - elasticsearch-logge - - openfunction - - tencent-cloud-cls - - ai - - cas-auth diff --git a/api/conf/customize_schema.json b/api/conf/customize_schema.json deleted file mode 100644 index bde2563183..0000000000 --- a/api/conf/customize_schema.json +++ /dev/null @@ -1,33 +0,0 @@ -{ - "main": { - "system_config": { - "properties": { - "config_name": { - "maxLength":100, - "minLength":1, - "pattern":"^[a-zA-Z0-9_]+$", - "type":"string" - }, - "desc": { - "maxLength":256, - "type":"string" - }, - "payload": { - "type":"object", - "minProperties":1 - }, - "create_time": { - "type":"integer" - }, - "update_time": { - "type":"integer" - } - }, - "required": [ - "config_name", - "payload" - ], - "type":"object" - } - } -} diff --git a/api/conf/schema.json b/api/conf/schema.json deleted file mode 100644 index 0d24afed3b..0000000000 --- a/api/conf/schema.json +++ /dev/null @@ -1,11197 +0,0 @@ -{ - "main": { - "consumer": { - "properties": { - "create_time": { - "type": "integer" - }, - "desc": { - "maxLength": 256, - "type": "string" - }, - "group_id": { - "anyOf": [ - { - "maxLength": 64, - "minLength": 1, - "pattern": "^[a-zA-Z0-9-_.]+$", - "type": "string" - }, - { - "minimum": 1, - "type": "integer" - } - ] - }, - "labels": { - "description": "key/value pairs to specify attributes", - "patternProperties": { - ".*": { - "description": "value of label", - "maxLength": 64, - "minLength": 1, - "pattern": "^\\S+$", - "type": "string" - } - }, - "type": "object" - }, - "plugins": { - "type": "object" - }, - "update_time": { - "type": "integer" - }, - "username": { - "maxLength": 100, - "minLength": 1, - "pattern": "^[a-zA-Z0-9_]+$", - "type": "string" - } - }, - "required": [ - "username" - ], - "type": "object" - }, - "global_rule": { - "properties": { - "create_time": { - "type": "integer" - }, - "id": { - "anyOf": [ - { - "maxLength": 64, - "minLength": 1, - "pattern": "^[a-zA-Z0-9-_.]+$", - "type": "string" - }, - { - "minimum": 1, - "type": "integer" - } - ] - }, - "plugins": { - "type": "object" - }, - "update_time": { - "type": "integer" - } - }, - "required": [ - "plugins" - ], - "type": "object" - }, - "plugin_config": { - "properties": { - "create_time": { - "type": "integer" - }, - "desc": { - "maxLength": 256, - "type": "string" - }, - "id": { - "anyOf": [ - { - "maxLength": 64, - "minLength": 1, - "pattern": "^[a-zA-Z0-9-_.]+$", - "type": "string" - }, - { - "minimum": 1, - "type": "integer" - } - ] - }, - "labels": { - "description": "key/value pairs to specify attributes", - "patternProperties": { - ".*": { - "description": "value of label", - "maxLength": 64, - "minLength": 1, - "pattern": "^\\S+$", - "type": "string" - } - }, - "type": "object" - }, - "plugins": { - "type": "object" - }, - "update_time": { - "type": "integer" - } - }, - "required": [ - "id", - "plugins" - ], - "type": "object" - }, - "plugins": { - "items": { - "properties": { - "name": { - "minLength": 1, - "type": "string" - }, - "stream": { - "type": "boolean" - } - }, - "required": [ - "name" - ], - "type": "object" - }, - "type": "array" - }, - "proto": { - "properties": { - "content": { - "maxLength": 1048576, - "minLength": 1, - "type": "string" - }, - "create_time": { - "type": "integer" - }, - "desc": { - "maxLength": 256, - "type": "string" - }, - "id": { - "anyOf": [ - { - "maxLength": 64, - "minLength": 1, - "pattern": "^[a-zA-Z0-9-_.]+$", - "type": "string" - }, - { - "minimum": 1, - "type": "integer" - } - ] - }, - "update_time": { - "type": "integer" - } - }, - "required": [ - "content" - ], - "type": "object" - }, - "route": { - "allOf": [ - { - "oneOf": [ - { - "required": [ - "uri" - ] - }, - { - "required": [ - "uris" - ] - } - ] - }, - { - "oneOf": [ - { - "not": { - "anyOf": [ - { - "required": [ - "host" - ] - }, - { - "required": [ - "hosts" - ] - } - ] - } - }, - { - "required": [ - "host" - ] - }, - { - "required": [ - "hosts" - ] - } - ] - }, - { - "oneOf": [ - { - "not": { - "anyOf": [ - { - "required": [ - "remote_addr" - ] - }, - { - "required": [ - "remote_addrs" - ] - } - ] - } - }, - { - "required": [ - "remote_addr" - ] - }, - { - "required": [ - "remote_addrs" - ] - } - ] - } - ], - "anyOf": [ - { - "required": [ - "plugins", - "uri" - ] - }, - { - "required": [ - "upstream", - "uri" - ] - }, - { - "required": [ - "upstream_id", - "uri" - ] - }, - { - "required": [ - "service_id", - "uri" - ] - }, - { - "required": [ - "plugins", - "uris" - ] - }, - { - "required": [ - "upstream", - "uris" - ] - }, - { - "required": [ - "upstream_id", - "uris" - ] - }, - { - "required": [ - "service_id", - "uris" - ] - }, - { - "required": [ - "script", - "uri" - ] - }, - { - "required": [ - "script", - "uris" - ] - } - ], - "not": { - "anyOf": [ - { - "required": [ - "plugins", - "script" - ] - }, - { - "required": [ - "plugin_config_id", - "script" - ] - } - ] - }, - "properties": { - "create_time": { - "type": "integer" - }, - "desc": { - "maxLength": 256, - "type": "string" - }, - "enable_websocket": { - "description": "enable websocket for request", - "type": "boolean" - }, - "filter_func": { - "minLength": 10, - "pattern": "^function", - "type": "string" - }, - "host": { - "pattern": "^\\*?[0-9a-zA-Z-._\\[\\]:]+$", - "type": "string" - }, - "hosts": { - "items": { - "pattern": "^\\*?[0-9a-zA-Z-._\\[\\]:]+$", - "type": "string" - }, - "minItems": 1, - "type": "array", - "uniqueItems": true - }, - "id": { - "anyOf": [ - { - "maxLength": 64, - "minLength": 1, - "pattern": "^[a-zA-Z0-9-_.]+$", - "type": "string" - }, - { - "minimum": 1, - "type": "integer" - } - ] - }, - "labels": { - "description": "key/value pairs to specify attributes", - "patternProperties": { - ".*": { - "description": "value of label", - "maxLength": 64, - "minLength": 1, - "pattern": "^\\S+$", - "type": "string" - } - }, - "type": "object" - }, - "methods": { - "items": { - "description": "HTTP method", - "enum": [ - "CONNECT", - "DELETE", - "GET", - "HEAD", - "OPTIONS", - "PATCH", - "POST", - "PURGE", - "PUT", - "TRACE" - ], - "type": "string" - }, - "type": "array", - "uniqueItems": true - }, - "name": { - "maxLength": 100, - "minLength": 1, - "type": "string" - }, - "plugin_config_id": { - "anyOf": [ - { - "maxLength": 64, - "minLength": 1, - "pattern": "^[a-zA-Z0-9-_.]+$", - "type": "string" - }, - { - "minimum": 1, - "type": "integer" - } - ] - }, - "plugins": { - "type": "object" - }, - "priority": { - "default": 0, - "type": "integer" - }, - "remote_addr": { - "anyOf": [ - { - "format": "ipv4", - "title": "IPv4", - "type": "string" - }, - { - "pattern": "^([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])\\.([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])\\.([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])\\.([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])/([12]?[0-9]|3[0-2])$", - "title": "IPv4/CIDR", - "type": "string" - }, - { - "format": "ipv6", - "title": "IPv6", - "type": "string" - }, - { - "pattern": "^([a-fA-F0-9]{0,4}:){1,8}(:[a-fA-F0-9]{0,4}){0,8}([a-fA-F0-9]{0,4})?/[0-9]{1,3}$", - "title": "IPv6/CIDR", - "type": "string" - } - ], - "description": "client IP", - "type": "string" - }, - "remote_addrs": { - "items": { - "anyOf": [ - { - "format": "ipv4", - "title": "IPv4", - "type": "string" - }, - { - "pattern": "^([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])\\.([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])\\.([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])\\.([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])/([12]?[0-9]|3[0-2])$", - "title": "IPv4/CIDR", - "type": "string" - }, - { - "format": "ipv6", - "title": "IPv6", - "type": "string" - }, - { - "pattern": "^([a-fA-F0-9]{0,4}:){1,8}(:[a-fA-F0-9]{0,4}){0,8}([a-fA-F0-9]{0,4})?/[0-9]{1,3}$", - "title": "IPv6/CIDR", - "type": "string" - } - ], - "description": "client IP", - "type": "string" - }, - "minItems": 1, - "type": "array", - "uniqueItems": true - }, - "script": { - "maxLength": 102400, - "minLength": 10, - "type": "string" - }, - "script_id": { - "anyOf": [ - { - "maxLength": 64, - "minLength": 1, - "pattern": "^[a-zA-Z0-9-_.]+$", - "type": "string" - }, - { - "minimum": 1, - "type": "integer" - } - ] - }, - "service_id": { - "anyOf": [ - { - "maxLength": 64, - "minLength": 1, - "pattern": "^[a-zA-Z0-9-_.]+$", - "type": "string" - }, - { - "minimum": 1, - "type": "integer" - } - ] - }, - "status": { - "default": 1, - "description": "route status, 1 to enable, 0 to disable", - "enum": [ - 0, - 1 - ], - "type": "integer" - }, - "timeout": { - "properties": { - "connect": { - "exclusiveMinimum": 0, - "type": "number" - }, - "read": { - "exclusiveMinimum": 0, - "type": "number" - }, - "send": { - "exclusiveMinimum": 0, - "type": "number" - } - }, - "required": [ - "connect", - "read", - "send" - ], - "type": "object" - }, - "update_time": { - "type": "integer" - }, - "upstream": { - "oneOf": [ - { - "required": [ - "nodes", - "type" - ] - }, - { - "required": [ - "discovery_type", - "service_name", - "type" - ] - } - ], - "properties": { - "checks": { - "anyOf": [ - { - "required": [ - "active" - ] - }, - { - "required": [ - "active", - "passive" - ] - } - ], - "properties": { - "active": { - "properties": { - "concurrency": { - "default": 10, - "type": "integer" - }, - "healthy": { - "properties": { - "http_statuses": { - "default": [ - 200, - 302 - ], - "items": { - "maximum": 599, - "minimum": 200, - "type": "integer" - }, - "minItems": 1, - "type": "array", - "uniqueItems": true - }, - "interval": { - "default": 1, - "minimum": 1, - "type": "integer" - }, - "successes": { - "default": 2, - "maximum": 254, - "minimum": 1, - "type": "integer" - } - }, - "type": "object" - }, - "host": { - "pattern": "^\\*?[0-9a-zA-Z-._\\[\\]:]+$", - "type": "string" - }, - "http_path": { - "default": "/", - "type": "string" - }, - "https_verify_certificate": { - "default": true, - "type": "boolean" - }, - "port": { - "maximum": 65535, - "minimum": 1, - "type": "integer" - }, - "req_headers": { - "items": { - "type": "string", - "uniqueItems": true - }, - "minItems": 1, - "type": "array" - }, - "timeout": { - "default": 1, - "type": "number" - }, - "type": { - "default": "http", - "enum": [ - "http", - "https", - "tcp" - ], - "type": "string" - }, - "unhealthy": { - "properties": { - "http_failures": { - "default": 5, - "maximum": 254, - "minimum": 1, - "type": "integer" - }, - "http_statuses": { - "default": [ - 404, - 429, - 500, - 501, - 502, - 503, - 504, - 505 - ], - "items": { - "maximum": 599, - "minimum": 200, - "type": "integer" - }, - "minItems": 1, - "type": "array", - "uniqueItems": true - }, - "interval": { - "default": 1, - "minimum": 1, - "type": "integer" - }, - "tcp_failures": { - "default": 2, - "maximum": 254, - "minimum": 1, - "type": "integer" - }, - "timeouts": { - "default": 3, - "maximum": 254, - "minimum": 1, - "type": "integer" - } - }, - "type": "object" - } - }, - "type": "object" - }, - "passive": { - "properties": { - "healthy": { - "properties": { - "http_statuses": { - "default": [ - 200, - 201, - 202, - 203, - 204, - 205, - 206, - 207, - 208, - 226, - 300, - 301, - 302, - 303, - 304, - 305, - 306, - 307, - 308 - ], - "items": { - "maximum": 599, - "minimum": 200, - "type": "integer" - }, - "minItems": 1, - "type": "array", - "uniqueItems": true - }, - "successes": { - "default": 5, - "maximum": 254, - "minimum": 0, - "type": "integer" - } - }, - "type": "object" - }, - "type": { - "default": "http", - "enum": [ - "http", - "https", - "tcp" - ], - "type": "string" - }, - "unhealthy": { - "properties": { - "http_failures": { - "default": 5, - "maximum": 254, - "minimum": 0, - "type": "integer" - }, - "http_statuses": { - "default": [ - 429, - 500, - 503 - ], - "items": { - "maximum": 599, - "minimum": 200, - "type": "integer" - }, - "minItems": 1, - "type": "array", - "uniqueItems": true - }, - "tcp_failures": { - "default": 2, - "maximum": 254, - "minimum": 0, - "type": "integer" - }, - "timeouts": { - "default": 7, - "maximum": 254, - "minimum": 0, - "type": "integer" - } - }, - "type": "object" - } - }, - "type": "object" - } - }, - "type": "object" - }, - "create_time": { - "type": "integer" - }, - "desc": { - "maxLength": 256, - "type": "string" - }, - "discovery_args": { - "properties": { - "group_name": { - "description": "group name", - "type": "string" - }, - "namespace_id": { - "description": "namespace id", - "type": "string" - } - }, - "type": "object" - }, - "discovery_type": { - "description": "discovery type", - "type": "string" - }, - "hash_on": { - "default": "vars", - "enum": [ - "consumer", - "cookie", - "header", - "vars", - "vars_combinations" - ], - "type": "string" - }, - "id": { - "anyOf": [ - { - "maxLength": 64, - "minLength": 1, - "pattern": "^[a-zA-Z0-9-_.]+$", - "type": "string" - }, - { - "minimum": 1, - "type": "integer" - } - ] - }, - "keepalive_pool": { - "properties": { - "idle_timeout": { - "default": 60, - "minimum": 0, - "type": "number" - }, - "requests": { - "default": 1000, - "minimum": 1, - "type": "integer" - }, - "size": { - "default": 320, - "minimum": 1, - "type": "integer" - } - }, - "type": "object" - }, - "key": { - "description": "the key of chash for dynamic load balancing", - "type": "string" - }, - "labels": { - "description": "key/value pairs to specify attributes", - "patternProperties": { - ".*": { - "description": "value of label", - "maxLength": 64, - "minLength": 1, - "pattern": "^\\S+$", - "type": "string" - } - }, - "type": "object" - }, - "name": { - "maxLength": 100, - "minLength": 1, - "type": "string" - }, - "nodes": { - "anyOf": [ - { - "patternProperties": { - ".*": { - "description": "weight of node", - "minimum": 0, - "type": "integer" - } - }, - "type": "object" - }, - { - "items": { - "properties": { - "host": { - "pattern": "^\\*?[0-9a-zA-Z-._\\[\\]:]+$", - "type": "string" - }, - "metadata": { - "description": "metadata of node", - "type": "object" - }, - "port": { - "description": "port of node", - "minimum": 1, - "type": "integer" - }, - "priority": { - "default": 0, - "description": "priority of node", - "type": "integer" - }, - "weight": { - "description": "weight of node", - "minimum": 0, - "type": "integer" - } - }, - "required": [ - "host", - "port", - "weight" - ], - "type": "object" - }, - "type": "array" - } - ] - }, - "pass_host": { - "default": "pass", - "description": "mod of host passing", - "enum": [ - "node", - "pass", - "rewrite" - ], - "type": "string" - }, - "retries": { - "minimum": 0, - "type": "integer" - }, - "retry_timeout": { - "minimum": 0, - "type": "number" - }, - "scheme": { - "default": "http", - "description": "The scheme of the upstream. For L7 proxy, it can be one of grpc/grpcs/http/https. For L4 proxy, it can be one of tcp/tls/udp. For specific protocols, it can be kafka.", - "enum": [ - "grpc", - "grpcs", - "http", - "https", - "kafka", - "tcp", - "tls", - "udp" - ] - }, - "service_name": { - "maxLength": 256, - "minLength": 1, - "type": "string" - }, - "timeout": { - "properties": { - "connect": { - "exclusiveMinimum": 0, - "type": "number" - }, - "read": { - "exclusiveMinimum": 0, - "type": "number" - }, - "send": { - "exclusiveMinimum": 0, - "type": "number" - } - }, - "required": [ - "connect", - "read", - "send" - ], - "type": "object" - }, - "tls": { - "dependencies": { - "client_cert": { - "not": { - "required": [ - "client_cert_id" - ] - }, - "required": [ - "client_key" - ] - }, - "client_cert_id": { - "not": { - "required": [ - "client_client", - "client_key" - ] - } - }, - "client_key": { - "not": { - "required": [ - "client_cert_id" - ] - }, - "required": [ - "client_cert" - ] - } - }, - "properties": { - "client_cert": { - "maxLength": 65536, - "minLength": 128, - "type": "string" - }, - "client_cert_id": { - "anyOf": [ - { - "maxLength": 64, - "minLength": 1, - "pattern": "^[a-zA-Z0-9-_.]+$", - "type": "string" - }, - { - "minimum": 1, - "type": "integer" - } - ] - }, - "client_key": { - "maxLength": 65536, - "minLength": 128, - "type": "string" - }, - "verify": { - "default": false, - "description": "Turn on server certificate verification, currently only kafka upstream is supported", - "type": "boolean" - } - }, - "type": "object" - }, - "type": { - "description": "algorithms of load balancing", - "type": "string" - }, - "update_time": { - "type": "integer" - }, - "upstream_host": { - "pattern": "^\\*?[0-9a-zA-Z-._\\[\\]:]+$", - "type": "string" - } - }, - "type": "object" - }, - "upstream_id": { - "anyOf": [ - { - "maxLength": 64, - "minLength": 1, - "pattern": "^[a-zA-Z0-9-_.]+$", - "type": "string" - }, - { - "minimum": 1, - "type": "integer" - } - ] - }, - "uri": { - "maxLength": 4096, - "minLength": 1, - "type": "string" - }, - "uris": { - "items": { - "description": "HTTP uri", - "type": "string" - }, - "minItems": 1, - "type": "array", - "uniqueItems": true - }, - "vars": { - "type": "array" - } - }, - "type": "object" - }, - "service": { - "properties": { - "create_time": { - "type": "integer" - }, - "desc": { - "maxLength": 256, - "type": "string" - }, - "enable_websocket": { - "description": "enable websocket for request", - "type": "boolean" - }, - "hosts": { - "items": { - "pattern": "^\\*?[0-9a-zA-Z-._\\[\\]:]+$", - "type": "string" - }, - "minItems": 1, - "type": "array", - "uniqueItems": true - }, - "id": { - "anyOf": [ - { - "maxLength": 64, - "minLength": 1, - "pattern": "^[a-zA-Z0-9-_.]+$", - "type": "string" - }, - { - "minimum": 1, - "type": "integer" - } - ] - }, - "labels": { - "description": "key/value pairs to specify attributes", - "patternProperties": { - ".*": { - "description": "value of label", - "maxLength": 64, - "minLength": 1, - "pattern": "^\\S+$", - "type": "string" - } - }, - "type": "object" - }, - "name": { - "maxLength": 100, - "minLength": 1, - "type": "string" - }, - "plugins": { - "type": "object" - }, - "script": { - "maxLength": 102400, - "minLength": 10, - "type": "string" - }, - "update_time": { - "type": "integer" - }, - "upstream": { - "oneOf": [ - { - "required": [ - "nodes", - "type" - ] - }, - { - "required": [ - "discovery_type", - "service_name", - "type" - ] - } - ], - "properties": { - "checks": { - "anyOf": [ - { - "required": [ - "active" - ] - }, - { - "required": [ - "active", - "passive" - ] - } - ], - "properties": { - "active": { - "properties": { - "concurrency": { - "default": 10, - "type": "integer" - }, - "healthy": { - "properties": { - "http_statuses": { - "default": [ - 200, - 302 - ], - "items": { - "maximum": 599, - "minimum": 200, - "type": "integer" - }, - "minItems": 1, - "type": "array", - "uniqueItems": true - }, - "interval": { - "default": 1, - "minimum": 1, - "type": "integer" - }, - "successes": { - "default": 2, - "maximum": 254, - "minimum": 1, - "type": "integer" - } - }, - "type": "object" - }, - "host": { - "pattern": "^\\*?[0-9a-zA-Z-._\\[\\]:]+$", - "type": "string" - }, - "http_path": { - "default": "/", - "type": "string" - }, - "https_verify_certificate": { - "default": true, - "type": "boolean" - }, - "port": { - "maximum": 65535, - "minimum": 1, - "type": "integer" - }, - "req_headers": { - "items": { - "type": "string", - "uniqueItems": true - }, - "minItems": 1, - "type": "array" - }, - "timeout": { - "default": 1, - "type": "number" - }, - "type": { - "default": "http", - "enum": [ - "http", - "https", - "tcp" - ], - "type": "string" - }, - "unhealthy": { - "properties": { - "http_failures": { - "default": 5, - "maximum": 254, - "minimum": 1, - "type": "integer" - }, - "http_statuses": { - "default": [ - 404, - 429, - 500, - 501, - 502, - 503, - 504, - 505 - ], - "items": { - "maximum": 599, - "minimum": 200, - "type": "integer" - }, - "minItems": 1, - "type": "array", - "uniqueItems": true - }, - "interval": { - "default": 1, - "minimum": 1, - "type": "integer" - }, - "tcp_failures": { - "default": 2, - "maximum": 254, - "minimum": 1, - "type": "integer" - }, - "timeouts": { - "default": 3, - "maximum": 254, - "minimum": 1, - "type": "integer" - } - }, - "type": "object" - } - }, - "type": "object" - }, - "passive": { - "properties": { - "healthy": { - "properties": { - "http_statuses": { - "default": [ - 200, - 201, - 202, - 203, - 204, - 205, - 206, - 207, - 208, - 226, - 300, - 301, - 302, - 303, - 304, - 305, - 306, - 307, - 308 - ], - "items": { - "maximum": 599, - "minimum": 200, - "type": "integer" - }, - "minItems": 1, - "type": "array", - "uniqueItems": true - }, - "successes": { - "default": 5, - "maximum": 254, - "minimum": 0, - "type": "integer" - } - }, - "type": "object" - }, - "type": { - "default": "http", - "enum": [ - "http", - "https", - "tcp" - ], - "type": "string" - }, - "unhealthy": { - "properties": { - "http_failures": { - "default": 5, - "maximum": 254, - "minimum": 0, - "type": "integer" - }, - "http_statuses": { - "default": [ - 429, - 500, - 503 - ], - "items": { - "maximum": 599, - "minimum": 200, - "type": "integer" - }, - "minItems": 1, - "type": "array", - "uniqueItems": true - }, - "tcp_failures": { - "default": 2, - "maximum": 254, - "minimum": 0, - "type": "integer" - }, - "timeouts": { - "default": 7, - "maximum": 254, - "minimum": 0, - "type": "integer" - } - }, - "type": "object" - } - }, - "type": "object" - } - }, - "type": "object" - }, - "create_time": { - "type": "integer" - }, - "desc": { - "maxLength": 256, - "type": "string" - }, - "discovery_args": { - "properties": { - "group_name": { - "description": "group name", - "type": "string" - }, - "namespace_id": { - "description": "namespace id", - "type": "string" - } - }, - "type": "object" - }, - "discovery_type": { - "description": "discovery type", - "type": "string" - }, - "hash_on": { - "default": "vars", - "enum": [ - "consumer", - "cookie", - "header", - "vars", - "vars_combinations" - ], - "type": "string" - }, - "id": { - "anyOf": [ - { - "maxLength": 64, - "minLength": 1, - "pattern": "^[a-zA-Z0-9-_.]+$", - "type": "string" - }, - { - "minimum": 1, - "type": "integer" - } - ] - }, - "keepalive_pool": { - "properties": { - "idle_timeout": { - "default": 60, - "minimum": 0, - "type": "number" - }, - "requests": { - "default": 1000, - "minimum": 1, - "type": "integer" - }, - "size": { - "default": 320, - "minimum": 1, - "type": "integer" - } - }, - "type": "object" - }, - "key": { - "description": "the key of chash for dynamic load balancing", - "type": "string" - }, - "labels": { - "description": "key/value pairs to specify attributes", - "patternProperties": { - ".*": { - "description": "value of label", - "maxLength": 64, - "minLength": 1, - "pattern": "^\\S+$", - "type": "string" - } - }, - "type": "object" - }, - "name": { - "maxLength": 100, - "minLength": 1, - "type": "string" - }, - "nodes": { - "anyOf": [ - { - "patternProperties": { - ".*": { - "description": "weight of node", - "minimum": 0, - "type": "integer" - } - }, - "type": "object" - }, - { - "items": { - "properties": { - "host": { - "pattern": "^\\*?[0-9a-zA-Z-._\\[\\]:]+$", - "type": "string" - }, - "metadata": { - "description": "metadata of node", - "type": "object" - }, - "port": { - "description": "port of node", - "minimum": 1, - "type": "integer" - }, - "priority": { - "default": 0, - "description": "priority of node", - "type": "integer" - }, - "weight": { - "description": "weight of node", - "minimum": 0, - "type": "integer" - } - }, - "required": [ - "host", - "port", - "weight" - ], - "type": "object" - }, - "type": "array" - } - ] - }, - "pass_host": { - "default": "pass", - "description": "mod of host passing", - "enum": [ - "node", - "pass", - "rewrite" - ], - "type": "string" - }, - "retries": { - "minimum": 0, - "type": "integer" - }, - "retry_timeout": { - "minimum": 0, - "type": "number" - }, - "scheme": { - "default": "http", - "description": "The scheme of the upstream. For L7 proxy, it can be one of grpc/grpcs/http/https. For L4 proxy, it can be one of tcp/tls/udp. For specific protocols, it can be kafka.", - "enum": [ - "grpc", - "grpcs", - "http", - "https", - "kafka", - "tcp", - "tls", - "udp" - ] - }, - "service_name": { - "maxLength": 256, - "minLength": 1, - "type": "string" - }, - "timeout": { - "properties": { - "connect": { - "exclusiveMinimum": 0, - "type": "number" - }, - "read": { - "exclusiveMinimum": 0, - "type": "number" - }, - "send": { - "exclusiveMinimum": 0, - "type": "number" - } - }, - "required": [ - "connect", - "read", - "send" - ], - "type": "object" - }, - "tls": { - "dependencies": { - "client_cert": { - "not": { - "required": [ - "client_cert_id" - ] - }, - "required": [ - "client_key" - ] - }, - "client_cert_id": { - "not": { - "required": [ - "client_client", - "client_key" - ] - } - }, - "client_key": { - "not": { - "required": [ - "client_cert_id" - ] - }, - "required": [ - "client_cert" - ] - } - }, - "properties": { - "client_cert": { - "maxLength": 65536, - "minLength": 128, - "type": "string" - }, - "client_cert_id": { - "anyOf": [ - { - "maxLength": 64, - "minLength": 1, - "pattern": "^[a-zA-Z0-9-_.]+$", - "type": "string" - }, - { - "minimum": 1, - "type": "integer" - } - ] - }, - "client_key": { - "maxLength": 65536, - "minLength": 128, - "type": "string" - }, - "verify": { - "default": false, - "description": "Turn on server certificate verification, currently only kafka upstream is supported", - "type": "boolean" - } - }, - "type": "object" - }, - "type": { - "description": "algorithms of load balancing", - "type": "string" - }, - "update_time": { - "type": "integer" - }, - "upstream_host": { - "pattern": "^\\*?[0-9a-zA-Z-._\\[\\]:]+$", - "type": "string" - } - }, - "type": "object" - }, - "upstream_id": { - "anyOf": [ - { - "maxLength": 64, - "minLength": 1, - "pattern": "^[a-zA-Z0-9-_.]+$", - "type": "string" - }, - { - "minimum": 1, - "type": "integer" - } - ] - } - }, - "type": "object" - }, - "ssl": { - "properties": { - "cert": { - "maxLength": 65536, - "minLength": 128, - "type": "string" - }, - "certs": { - "items": { - "maxLength": 65536, - "minLength": 128, - "type": "string" - }, - "type": "array" - }, - "client": { - "properties": { - "ca": { - "maxLength": 65536, - "minLength": 128, - "type": "string" - }, - "depth": { - "default": 1, - "minimum": 0, - "type": "integer" - } - }, - "required": [ - "ca" - ], - "type": "object" - }, - "create_time": { - "type": "integer" - }, - "exptime": { - "minimum": 1588262400, - "type": "integer" - }, - "id": { - "anyOf": [ - { - "maxLength": 64, - "minLength": 1, - "pattern": "^[a-zA-Z0-9-_.]+$", - "type": "string" - }, - { - "minimum": 1, - "type": "integer" - } - ] - }, - "key": { - "maxLength": 65536, - "minLength": 128, - "type": "string" - }, - "keys": { - "items": { - "maxLength": 65536, - "minLength": 128, - "type": "string" - }, - "type": "array" - }, - "labels": { - "description": "key/value pairs to specify attributes", - "patternProperties": { - ".*": { - "description": "value of label", - "maxLength": 64, - "minLength": 1, - "pattern": "^\\S+$", - "type": "string" - } - }, - "type": "object" - }, - "sni": { - "pattern": "^\\*?[0-9a-zA-Z-._\\[\\]:]+$", - "type": "string" - }, - "snis": { - "items": { - "pattern": "^\\*?[0-9a-zA-Z-._\\[\\]:]+$", - "type": "string" - }, - "minItems": 1, - "type": "array" - }, - "status": { - "default": 1, - "description": "ssl status, 1 to enable, 0 to disable", - "enum": [ - 0, - 1 - ], - "type": "integer" - }, - "type": { - "default": "server", - "description": "ssl certificate type, server to server certificate, client to client certificate for upstream", - "enum": [ - "client", - "server" - ], - "type": "string" - }, - "update_time": { - "type": "integer" - }, - "validity_end": { - "type": "integer" - }, - "validity_start": { - "type": "integer" - } - }, - "if": { - "properties": { - "type": { - "enum": [ - "server" - ] - } - } - }, - "else": { - "required": [ - "cert", - "key" - ] - }, - "then": { - "oneOf": [ - { - "required": [ - "cert", - "key", - "sni" - ] - }, - { - "required": [ - "cert", - "key", - "snis" - ] - } - ] - }, - "type": "object" - }, - "stream_route": { - "properties": { - "create_time": { - "type": "integer" - }, - "desc": { - "maxLength": 256, - "type": "string" - }, - "id": { - "anyOf": [ - { - "maxLength": 64, - "minLength": 1, - "pattern": "^[a-zA-Z0-9-_.]+$", - "type": "string" - }, - { - "minimum": 1, - "type": "integer" - } - ] - }, - "plugins": { - "type": "object" - }, - "protocol": { - "properties": { - "conf": { - "description": "protocol-specific configuration", - "type": "object" - }, - "logger": { - "items": { - "dependencies": { - "name": [ - "conf" - ] - }, - "properties": { - "conf": { - "description": "logger plugin configuration", - "type": "object" - }, - "filter": { - "description": "logger filter rules", - "type": "array" - }, - "name": { - "type": "string" - } - } - }, - "type": "array" - }, - "name": { - "type": "string" - }, - "superior_id": { - "anyOf": [ - { - "maxLength": 64, - "minLength": 1, - "pattern": "^[a-zA-Z0-9-_.]+$", - "type": "string" - }, - { - "minimum": 1, - "type": "integer" - } - ] - } - }, - "required": [ - "name" - ], - "type": "object" - }, - "remote_addr": { - "anyOf": [ - { - "format": "ipv4", - "title": "IPv4", - "type": "string" - }, - { - "pattern": "^([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])\\.([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])\\.([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])\\.([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])/([12]?[0-9]|3[0-2])$", - "title": "IPv4/CIDR", - "type": "string" - }, - { - "format": "ipv6", - "title": "IPv6", - "type": "string" - }, - { - "pattern": "^([a-fA-F0-9]{0,4}:){1,8}(:[a-fA-F0-9]{0,4}){0,8}([a-fA-F0-9]{0,4})?/[0-9]{1,3}$", - "title": "IPv6/CIDR", - "type": "string" - } - ], - "description": "client IP", - "type": "string" - }, - "server_addr": { - "anyOf": [ - { - "format": "ipv4", - "title": "IPv4", - "type": "string" - }, - { - "pattern": "^([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])\\.([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])\\.([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])\\.([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])/([12]?[0-9]|3[0-2])$", - "title": "IPv4/CIDR", - "type": "string" - }, - { - "format": "ipv6", - "title": "IPv6", - "type": "string" - }, - { - "pattern": "^([a-fA-F0-9]{0,4}:){1,8}(:[a-fA-F0-9]{0,4}){0,8}([a-fA-F0-9]{0,4})?/[0-9]{1,3}$", - "title": "IPv6/CIDR", - "type": "string" - } - ], - "description": "server IP", - "type": "string" - }, - "server_port": { - "description": "server port", - "type": "integer" - }, - "sni": { - "description": "server name indication", - "pattern": "^\\*?[0-9a-zA-Z-._\\[\\]:]+$", - "type": "string" - }, - "update_time": { - "type": "integer" - }, - "upstream": { - "oneOf": [ - { - "required": [ - "nodes", - "type" - ] - }, - { - "required": [ - "discovery_type", - "service_name", - "type" - ] - } - ], - "properties": { - "checks": { - "anyOf": [ - { - "required": [ - "active" - ] - }, - { - "required": [ - "active", - "passive" - ] - } - ], - "properties": { - "active": { - "properties": { - "concurrency": { - "default": 10, - "type": "integer" - }, - "healthy": { - "properties": { - "http_statuses": { - "default": [ - 200, - 302 - ], - "items": { - "maximum": 599, - "minimum": 200, - "type": "integer" - }, - "minItems": 1, - "type": "array", - "uniqueItems": true - }, - "interval": { - "default": 1, - "minimum": 1, - "type": "integer" - }, - "successes": { - "default": 2, - "maximum": 254, - "minimum": 1, - "type": "integer" - } - }, - "type": "object" - }, - "host": { - "pattern": "^\\*?[0-9a-zA-Z-._\\[\\]:]+$", - "type": "string" - }, - "http_path": { - "default": "/", - "type": "string" - }, - "https_verify_certificate": { - "default": true, - "type": "boolean" - }, - "port": { - "maximum": 65535, - "minimum": 1, - "type": "integer" - }, - "req_headers": { - "items": { - "type": "string", - "uniqueItems": true - }, - "minItems": 1, - "type": "array" - }, - "timeout": { - "default": 1, - "type": "number" - }, - "type": { - "default": "http", - "enum": [ - "http", - "https", - "tcp" - ], - "type": "string" - }, - "unhealthy": { - "properties": { - "http_failures": { - "default": 5, - "maximum": 254, - "minimum": 1, - "type": "integer" - }, - "http_statuses": { - "default": [ - 404, - 429, - 500, - 501, - 502, - 503, - 504, - 505 - ], - "items": { - "maximum": 599, - "minimum": 200, - "type": "integer" - }, - "minItems": 1, - "type": "array", - "uniqueItems": true - }, - "interval": { - "default": 1, - "minimum": 1, - "type": "integer" - }, - "tcp_failures": { - "default": 2, - "maximum": 254, - "minimum": 1, - "type": "integer" - }, - "timeouts": { - "default": 3, - "maximum": 254, - "minimum": 1, - "type": "integer" - } - }, - "type": "object" - } - }, - "type": "object" - }, - "passive": { - "properties": { - "healthy": { - "properties": { - "http_statuses": { - "default": [ - 200, - 201, - 202, - 203, - 204, - 205, - 206, - 207, - 208, - 226, - 300, - 301, - 302, - 303, - 304, - 305, - 306, - 307, - 308 - ], - "items": { - "maximum": 599, - "minimum": 200, - "type": "integer" - }, - "minItems": 1, - "type": "array", - "uniqueItems": true - }, - "successes": { - "default": 5, - "maximum": 254, - "minimum": 0, - "type": "integer" - } - }, - "type": "object" - }, - "type": { - "default": "http", - "enum": [ - "http", - "https", - "tcp" - ], - "type": "string" - }, - "unhealthy": { - "properties": { - "http_failures": { - "default": 5, - "maximum": 254, - "minimum": 0, - "type": "integer" - }, - "http_statuses": { - "default": [ - 429, - 500, - 503 - ], - "items": { - "maximum": 599, - "minimum": 200, - "type": "integer" - }, - "minItems": 1, - "type": "array", - "uniqueItems": true - }, - "tcp_failures": { - "default": 2, - "maximum": 254, - "minimum": 0, - "type": "integer" - }, - "timeouts": { - "default": 7, - "maximum": 254, - "minimum": 0, - "type": "integer" - } - }, - "type": "object" - } - }, - "type": "object" - } - }, - "type": "object" - }, - "create_time": { - "type": "integer" - }, - "desc": { - "maxLength": 256, - "type": "string" - }, - "discovery_args": { - "properties": { - "group_name": { - "description": "group name", - "type": "string" - }, - "namespace_id": { - "description": "namespace id", - "type": "string" - } - }, - "type": "object" - }, - "discovery_type": { - "description": "discovery type", - "type": "string" - }, - "hash_on": { - "default": "vars", - "enum": [ - "consumer", - "cookie", - "header", - "vars", - "vars_combinations" - ], - "type": "string" - }, - "id": { - "anyOf": [ - { - "maxLength": 64, - "minLength": 1, - "pattern": "^[a-zA-Z0-9-_.]+$", - "type": "string" - }, - { - "minimum": 1, - "type": "integer" - } - ] - }, - "keepalive_pool": { - "properties": { - "idle_timeout": { - "default": 60, - "minimum": 0, - "type": "number" - }, - "requests": { - "default": 1000, - "minimum": 1, - "type": "integer" - }, - "size": { - "default": 320, - "minimum": 1, - "type": "integer" - } - }, - "type": "object" - }, - "key": { - "description": "the key of chash for dynamic load balancing", - "type": "string" - }, - "labels": { - "description": "key/value pairs to specify attributes", - "patternProperties": { - ".*": { - "description": "value of label", - "maxLength": 64, - "minLength": 1, - "pattern": "^\\S+$", - "type": "string" - } - }, - "type": "object" - }, - "name": { - "maxLength": 100, - "minLength": 1, - "type": "string" - }, - "nodes": { - "anyOf": [ - { - "patternProperties": { - ".*": { - "description": "weight of node", - "minimum": 0, - "type": "integer" - } - }, - "type": "object" - }, - { - "items": { - "properties": { - "host": { - "pattern": "^\\*?[0-9a-zA-Z-._\\[\\]:]+$", - "type": "string" - }, - "metadata": { - "description": "metadata of node", - "type": "object" - }, - "port": { - "description": "port of node", - "minimum": 1, - "type": "integer" - }, - "priority": { - "default": 0, - "description": "priority of node", - "type": "integer" - }, - "weight": { - "description": "weight of node", - "minimum": 0, - "type": "integer" - } - }, - "required": [ - "host", - "port", - "weight" - ], - "type": "object" - }, - "type": "array" - } - ] - }, - "pass_host": { - "default": "pass", - "description": "mod of host passing", - "enum": [ - "node", - "pass", - "rewrite" - ], - "type": "string" - }, - "retries": { - "minimum": 0, - "type": "integer" - }, - "retry_timeout": { - "minimum": 0, - "type": "number" - }, - "scheme": { - "default": "http", - "description": "The scheme of the upstream. For L7 proxy, it can be one of grpc/grpcs/http/https. For L4 proxy, it can be one of tcp/tls/udp. For specific protocols, it can be kafka.", - "enum": [ - "grpc", - "grpcs", - "http", - "https", - "kafka", - "tcp", - "tls", - "udp" - ] - }, - "service_name": { - "maxLength": 256, - "minLength": 1, - "type": "string" - }, - "timeout": { - "properties": { - "connect": { - "exclusiveMinimum": 0, - "type": "number" - }, - "read": { - "exclusiveMinimum": 0, - "type": "number" - }, - "send": { - "exclusiveMinimum": 0, - "type": "number" - } - }, - "required": [ - "connect", - "read", - "send" - ], - "type": "object" - }, - "tls": { - "dependencies": { - "client_cert": { - "not": { - "required": [ - "client_cert_id" - ] - }, - "required": [ - "client_key" - ] - }, - "client_cert_id": { - "not": { - "required": [ - "client_client", - "client_key" - ] - } - }, - "client_key": { - "not": { - "required": [ - "client_cert_id" - ] - }, - "required": [ - "client_cert" - ] - } - }, - "properties": { - "client_cert": { - "maxLength": 65536, - "minLength": 128, - "type": "string" - }, - "client_cert_id": { - "anyOf": [ - { - "maxLength": 64, - "minLength": 1, - "pattern": "^[a-zA-Z0-9-_.]+$", - "type": "string" - }, - { - "minimum": 1, - "type": "integer" - } - ] - }, - "client_key": { - "maxLength": 65536, - "minLength": 128, - "type": "string" - }, - "verify": { - "default": false, - "description": "Turn on server certificate verification, currently only kafka upstream is supported", - "type": "boolean" - } - }, - "type": "object" - }, - "type": { - "description": "algorithms of load balancing", - "type": "string" - }, - "update_time": { - "type": "integer" - }, - "upstream_host": { - "pattern": "^\\*?[0-9a-zA-Z-._\\[\\]:]+$", - "type": "string" - } - }, - "type": "object" - }, - "upstream_id": { - "anyOf": [ - { - "maxLength": 64, - "minLength": 1, - "pattern": "^[a-zA-Z0-9-_.]+$", - "type": "string" - }, - { - "minimum": 1, - "type": "integer" - } - ] - } - }, - "type": "object" - }, - "upstream": { - "oneOf": [ - { - "required": [ - "nodes", - "type" - ] - }, - { - "required": [ - "discovery_type", - "service_name", - "type" - ] - } - ], - "properties": { - "checks": { - "anyOf": [ - { - "required": [ - "active" - ] - }, - { - "required": [ - "active", - "passive" - ] - } - ], - "properties": { - "active": { - "properties": { - "concurrency": { - "default": 10, - "type": "integer" - }, - "healthy": { - "properties": { - "http_statuses": { - "default": [ - 200, - 302 - ], - "items": { - "maximum": 599, - "minimum": 200, - "type": "integer" - }, - "minItems": 1, - "type": "array", - "uniqueItems": true - }, - "interval": { - "default": 1, - "minimum": 1, - "type": "integer" - }, - "successes": { - "default": 2, - "maximum": 254, - "minimum": 1, - "type": "integer" - } - }, - "type": "object" - }, - "host": { - "pattern": "^\\*?[0-9a-zA-Z-._\\[\\]:]+$", - "type": "string" - }, - "http_path": { - "default": "/", - "type": "string" - }, - "https_verify_certificate": { - "default": true, - "type": "boolean" - }, - "port": { - "maximum": 65535, - "minimum": 1, - "type": "integer" - }, - "req_headers": { - "items": { - "type": "string", - "uniqueItems": true - }, - "minItems": 1, - "type": "array" - }, - "timeout": { - "default": 1, - "type": "number" - }, - "type": { - "default": "http", - "enum": [ - "http", - "https", - "tcp" - ], - "type": "string" - }, - "unhealthy": { - "properties": { - "http_failures": { - "default": 5, - "maximum": 254, - "minimum": 1, - "type": "integer" - }, - "http_statuses": { - "default": [ - 404, - 429, - 500, - 501, - 502, - 503, - 504, - 505 - ], - "items": { - "maximum": 599, - "minimum": 200, - "type": "integer" - }, - "minItems": 1, - "type": "array", - "uniqueItems": true - }, - "interval": { - "default": 1, - "minimum": 1, - "type": "integer" - }, - "tcp_failures": { - "default": 2, - "maximum": 254, - "minimum": 1, - "type": "integer" - }, - "timeouts": { - "default": 3, - "maximum": 254, - "minimum": 1, - "type": "integer" - } - }, - "type": "object" - } - }, - "type": "object" - }, - "passive": { - "properties": { - "healthy": { - "properties": { - "http_statuses": { - "default": [ - 200, - 201, - 202, - 203, - 204, - 205, - 206, - 207, - 208, - 226, - 300, - 301, - 302, - 303, - 304, - 305, - 306, - 307, - 308 - ], - "items": { - "maximum": 599, - "minimum": 200, - "type": "integer" - }, - "minItems": 1, - "type": "array", - "uniqueItems": true - }, - "successes": { - "default": 5, - "maximum": 254, - "minimum": 0, - "type": "integer" - } - }, - "type": "object" - }, - "type": { - "default": "http", - "enum": [ - "http", - "https", - "tcp" - ], - "type": "string" - }, - "unhealthy": { - "properties": { - "http_failures": { - "default": 5, - "maximum": 254, - "minimum": 0, - "type": "integer" - }, - "http_statuses": { - "default": [ - 429, - 500, - 503 - ], - "items": { - "maximum": 599, - "minimum": 200, - "type": "integer" - }, - "minItems": 1, - "type": "array", - "uniqueItems": true - }, - "tcp_failures": { - "default": 2, - "maximum": 254, - "minimum": 0, - "type": "integer" - }, - "timeouts": { - "default": 7, - "maximum": 254, - "minimum": 0, - "type": "integer" - } - }, - "type": "object" - } - }, - "type": "object" - } - }, - "type": "object" - }, - "create_time": { - "type": "integer" - }, - "desc": { - "maxLength": 256, - "type": "string" - }, - "discovery_args": { - "properties": { - "group_name": { - "description": "group name", - "type": "string" - }, - "namespace_id": { - "description": "namespace id", - "type": "string" - } - }, - "type": "object" - }, - "discovery_type": { - "description": "discovery type", - "type": "string" - }, - "hash_on": { - "default": "vars", - "enum": [ - "consumer", - "cookie", - "header", - "vars", - "vars_combinations" - ], - "type": "string" - }, - "id": { - "anyOf": [ - { - "maxLength": 64, - "minLength": 1, - "pattern": "^[a-zA-Z0-9-_.]+$", - "type": "string" - }, - { - "minimum": 1, - "type": "integer" - } - ] - }, - "keepalive_pool": { - "properties": { - "idle_timeout": { - "default": 60, - "minimum": 0, - "type": "number" - }, - "requests": { - "default": 1000, - "minimum": 1, - "type": "integer" - }, - "size": { - "default": 320, - "minimum": 1, - "type": "integer" - } - }, - "type": "object" - }, - "key": { - "description": "the key of chash for dynamic load balancing", - "type": "string" - }, - "labels": { - "description": "key/value pairs to specify attributes", - "patternProperties": { - ".*": { - "description": "value of label", - "maxLength": 64, - "minLength": 1, - "pattern": "^\\S+$", - "type": "string" - } - }, - "type": "object" - }, - "name": { - "maxLength": 100, - "minLength": 1, - "type": "string" - }, - "nodes": { - "anyOf": [ - { - "patternProperties": { - ".*": { - "description": "weight of node", - "minimum": 0, - "type": "integer" - } - }, - "type": "object" - }, - { - "items": { - "properties": { - "host": { - "pattern": "^\\*?[0-9a-zA-Z-._\\[\\]:]+$", - "type": "string" - }, - "metadata": { - "description": "metadata of node", - "type": "object" - }, - "port": { - "description": "port of node", - "minimum": 1, - "type": "integer" - }, - "priority": { - "default": 0, - "description": "priority of node", - "type": "integer" - }, - "weight": { - "description": "weight of node", - "minimum": 0, - "type": "integer" - } - }, - "required": [ - "host", - "port", - "weight" - ], - "type": "object" - }, - "type": "array" - } - ] - }, - "pass_host": { - "default": "pass", - "description": "mod of host passing", - "enum": [ - "node", - "pass", - "rewrite" - ], - "type": "string" - }, - "retries": { - "minimum": 0, - "type": "integer" - }, - "retry_timeout": { - "minimum": 0, - "type": "number" - }, - "scheme": { - "default": "http", - "description": "The scheme of the upstream. For L7 proxy, it can be one of grpc/grpcs/http/https. For L4 proxy, it can be one of tcp/tls/udp. For specific protocols, it can be kafka.", - "enum": [ - "grpc", - "grpcs", - "http", - "https", - "kafka", - "tcp", - "tls", - "udp" - ] - }, - "service_name": { - "maxLength": 256, - "minLength": 1, - "type": "string" - }, - "timeout": { - "properties": { - "connect": { - "exclusiveMinimum": 0, - "type": "number" - }, - "read": { - "exclusiveMinimum": 0, - "type": "number" - }, - "send": { - "exclusiveMinimum": 0, - "type": "number" - } - }, - "required": [ - "connect", - "read", - "send" - ], - "type": "object" - }, - "tls": { - "dependencies": { - "client_cert": { - "not": { - "required": [ - "client_cert_id" - ] - }, - "required": [ - "client_key" - ] - }, - "client_cert_id": { - "not": { - "required": [ - "client_client", - "client_key" - ] - } - }, - "client_key": { - "not": { - "required": [ - "client_cert_id" - ] - }, - "required": [ - "client_cert" - ] - } - }, - "properties": { - "client_cert": { - "maxLength": 65536, - "minLength": 128, - "type": "string" - }, - "client_cert_id": { - "anyOf": [ - { - "maxLength": 64, - "minLength": 1, - "pattern": "^[a-zA-Z0-9-_.]+$", - "type": "string" - }, - { - "minimum": 1, - "type": "integer" - } - ] - }, - "client_key": { - "maxLength": 65536, - "minLength": 128, - "type": "string" - }, - "verify": { - "default": false, - "description": "Turn on server certificate verification, currently only kafka upstream is supported", - "type": "boolean" - } - }, - "type": "object" - }, - "type": { - "description": "algorithms of load balancing", - "type": "string" - }, - "update_time": { - "type": "integer" - }, - "upstream_host": { - "pattern": "^\\*?[0-9a-zA-Z-._\\[\\]:]+$", - "type": "string" - } - }, - "type": "object" - }, - "upstream_hash_header_schema": { - "pattern": "^[a-zA-Z0-9-_]+$", - "type": "string" - }, - "upstream_hash_vars_schema": { - "pattern": "^((uri|server_name|server_addr|request_uri|remote_port|remote_addr|query_string|host|hostname)|arg_[0-9a-zA-z_-]+)$", - "type": "string" - } - }, - "plugins": { - "ai": { - "priority": 22900, - "schema": { - "$comment": "this is a mark for our injected plugin schema", - "properties": { - "_meta": { - "properties": { - "disable": { - "type": "boolean" - }, - "error_response": { - "oneOf": [ - { - "type": "string" - }, - { - "type": "object" - } - ] - }, - "filter": { - "description": "filter determines whether the plugin needs to be executed at runtime", - "type": "array" - }, - "priority": { - "description": "priority of plugins by customized order", - "type": "integer" - } - }, - "type": "object" - } - } - }, - "scope": "global", - "version": 0.1 - }, - "api-breaker": { - "priority": 1005, - "schema": { - "$comment": "this is a mark for our injected plugin schema", - "properties": { - "_meta": { - "properties": { - "disable": { - "type": "boolean" - }, - "error_response": { - "oneOf": [ - { - "type": "string" - }, - { - "type": "object" - } - ] - }, - "filter": { - "description": "filter determines whether the plugin needs to be executed at runtime", - "type": "array" - }, - "priority": { - "description": "priority of plugins by customized order", - "type": "integer" - } - }, - "type": "object" - }, - "break_response_body": { - "type": "string" - }, - "break_response_code": { - "maximum": 599, - "minimum": 200, - "type": "integer" - }, - "break_response_headers": { - "items": { - "properties": { - "key": { - "minLength": 1, - "type": "string" - }, - "value": { - "minLength": 1, - "type": "string" - } - }, - "required": [ - "key", - "value" - ], - "type": "object" - }, - "type": "array" - }, - "healthy": { - "default": { - "http_statuses": [ - 200 - ], - "successes": 3 - }, - "properties": { - "http_statuses": { - "default": [ - 200 - ], - "items": { - "maximum": 499, - "minimum": 200, - "type": "integer" - }, - "minItems": 1, - "type": "array", - "uniqueItems": true - }, - "successes": { - "default": 3, - "minimum": 1, - "type": "integer" - } - }, - "type": "object" - }, - "max_breaker_sec": { - "default": 300, - "minimum": 3, - "type": "integer" - }, - "unhealthy": { - "default": { - "failures": 3, - "http_statuses": [ - 500 - ] - }, - "properties": { - "failures": { - "default": 3, - "minimum": 1, - "type": "integer" - }, - "http_statuses": { - "default": [ - 500 - ], - "items": { - "maximum": 599, - "minimum": 500, - "type": "integer" - }, - "minItems": 1, - "type": "array", - "uniqueItems": true - } - }, - "type": "object" - } - }, - "required": [ - "break_response_code" - ], - "type": "object" - }, - "version": 0.1 - }, - "authz-casbin": { - "metadata_schema": { - "properties": { - "model": { - "type": "string" - }, - "policy": { - "type": "string" - } - }, - "required": [ - "model", - "policy" - ], - "type": "object" - }, - "priority": 2560, - "schema": { - "$comment": "this is a mark for our injected plugin schema", - "oneOf": [ - { - "required": [ - "model_path", - "policy_path", - "username" - ] - }, - { - "required": [ - "model", - "policy", - "username" - ] - } - ], - "properties": { - "_meta": { - "properties": { - "disable": { - "type": "boolean" - }, - "error_response": { - "oneOf": [ - { - "type": "string" - }, - { - "type": "object" - } - ] - }, - "filter": { - "description": "filter determines whether the plugin needs to be executed at runtime", - "type": "array" - }, - "priority": { - "description": "priority of plugins by customized order", - "type": "integer" - } - }, - "type": "object" - }, - "model": { - "type": "string" - }, - "model_path": { - "type": "string" - }, - "policy": { - "type": "string" - }, - "policy_path": { - "type": "string" - }, - "username": { - "type": "string" - } - }, - "type": "object" - }, - "version": 0.1 - }, - "authz-casdoor": { - "priority": 2559, - "schema": { - "$comment": "this is a mark for our injected plugin schema", - "properties": { - "_meta": { - "properties": { - "disable": { - "type": "boolean" - }, - "error_response": { - "oneOf": [ - { - "type": "string" - }, - { - "type": "object" - } - ] - }, - "filter": { - "description": "filter determines whether the plugin needs to be executed at runtime", - "type": "array" - }, - "priority": { - "description": "priority of plugins by customized order", - "type": "integer" - } - }, - "type": "object" - }, - "callback_url": { - "pattern": "^[^%?]+[^/]$", - "type": "string" - }, - "client_id": { - "type": "string" - }, - "client_secret": { - "type": "string" - }, - "endpoint_addr": { - "pattern": "^[^%?]+[^/]$", - "type": "string" - } - }, - "required": [ - "callback_url", - "client_id", - "client_secret", - "endpoint_addr" - ], - "type": "object" - }, - "version": 0.1 - }, - "authz-keycloak": { - "priority": 2000, - "schema": { - "$comment": "this is a mark for our injected plugin schema", - "allOf": [ - { - "anyOf": [ - { - "required": [ - "discovery" - ] - }, - { - "required": [ - "token_endpoint" - ] - } - ] - }, - { - "anyOf": [ - { - "properties": { - "lazy_load_paths": { - "enum": [ - false - ] - } - } - }, - { - "anyOf": [ - { - "required": [ - "discovery" - ] - }, - { - "required": [ - "resource_registration_endpoint" - ] - } - ], - "properties": { - "lazy_load_paths": { - "enum": [ - true - ] - } - } - } - ] - } - ], - "properties": { - "_meta": { - "properties": { - "disable": { - "type": "boolean" - }, - "error_response": { - "oneOf": [ - { - "type": "string" - }, - { - "type": "object" - } - ] - }, - "filter": { - "description": "filter determines whether the plugin needs to be executed at runtime", - "type": "array" - }, - "priority": { - "description": "priority of plugins by customized order", - "type": "integer" - } - }, - "type": "object" - }, - "access_denied_redirect_uri": { - "maxLength": 2048, - "minLength": 1, - "type": "string" - }, - "access_token_expires_in": { - "default": 300, - "minimum": 1, - "type": "integer" - }, - "access_token_expires_leeway": { - "default": 0, - "minimum": 0, - "type": "integer" - }, - "cache_ttl_seconds": { - "default": 86400, - "minimum": 1, - "type": "integer" - }, - "client_id": { - "maxLength": 100, - "minLength": 1, - "type": "string" - }, - "client_secret": { - "maxLength": 100, - "minLength": 1, - "type": "string" - }, - "discovery": { - "maxLength": 4096, - "minLength": 1, - "type": "string" - }, - "grant_type": { - "default": "urn:ietf:params:oauth:grant-type:uma-ticket", - "enum": [ - "urn:ietf:params:oauth:grant-type:uma-ticket" - ], - "maxLength": 100, - "minLength": 1, - "type": "string" - }, - "http_method_as_scope": { - "default": false, - "type": "boolean" - }, - "keepalive": { - "default": true, - "type": "boolean" - }, - "keepalive_pool": { - "default": 5, - "minimum": 1, - "type": "integer" - }, - "keepalive_timeout": { - "default": 60000, - "minimum": 1000, - "type": "integer" - }, - "lazy_load_paths": { - "default": false, - "type": "boolean" - }, - "password_grant_token_generation_incoming_uri": { - "maxLength": 4096, - "minLength": 1, - "type": "string" - }, - "permissions": { - "default": {}, - "items": { - "maxLength": 100, - "minLength": 1, - "type": "string" - }, - "type": "array", - "uniqueItems": true - }, - "policy_enforcement_mode": { - "default": "ENFORCING", - "enum": [ - "ENFORCING", - "PERMISSIVE" - ], - "type": "string" - }, - "refresh_token_expires_in": { - "default": 3600, - "minimum": 1, - "type": "integer" - }, - "refresh_token_expires_leeway": { - "default": 0, - "minimum": 0, - "type": "integer" - }, - "resource_registration_endpoint": { - "maxLength": 4096, - "minLength": 1, - "type": "string" - }, - "ssl_verify": { - "default": true, - "type": "boolean" - }, - "timeout": { - "default": 3000, - "minimum": 1000, - "type": "integer" - }, - "token_endpoint": { - "maxLength": 4096, - "minLength": 1, - "type": "string" - } - }, - "required": [ - "client_id" - ], - "type": "object" - }, - "version": 0.1 - }, - "aws-lambda": { - "priority": -1899, - "schema": { - "$comment": "this is a mark for our injected plugin schema", - "properties": { - "_meta": { - "properties": { - "disable": { - "type": "boolean" - }, - "error_response": { - "oneOf": [ - { - "type": "string" - }, - { - "type": "object" - } - ] - }, - "filter": { - "description": "filter determines whether the plugin needs to be executed at runtime", - "type": "array" - }, - "priority": { - "description": "priority of plugins by customized order", - "type": "integer" - } - }, - "type": "object" - }, - "authorization": { - "properties": { - "apikey": { - "type": "string" - }, - "iam": { - "properties": { - "accesskey": { - "description": "access key id from from aws iam console", - "type": "string" - }, - "aws_region": { - "default": "us-east-1", - "description": "the aws region that is receiving the request", - "type": "string" - }, - "secretkey": { - "description": "secret access key from from aws iam console", - "type": "string" - }, - "service": { - "default": "execute-api", - "description": "the service that is receiving the request", - "type": "string" - } - }, - "required": [ - "accesskey", - "secretkey" - ], - "type": "object" - } - }, - "type": "object" - }, - "function_uri": { - "type": "string" - }, - "keepalive": { - "default": true, - "type": "boolean" - }, - "keepalive_pool": { - "default": 5, - "minimum": 1, - "type": "integer" - }, - "keepalive_timeout": { - "default": 60000, - "minimum": 1000, - "type": "integer" - }, - "ssl_verify": { - "default": true, - "type": "boolean" - }, - "timeout": { - "default": 3000, - "minimum": 100, - "type": "integer" - } - }, - "required": [ - "function_uri" - ], - "type": "object" - }, - "version": 0.1 - }, - "azure-functions": { - "metadata_schema": { - "properties": { - "master_apikey": { - "default": "", - "type": "string" - }, - "master_clientid": { - "default": "", - "type": "string" - } - }, - "type": "object" - }, - "priority": -1900, - "schema": { - "$comment": "this is a mark for our injected plugin schema", - "properties": { - "_meta": { - "properties": { - "disable": { - "type": "boolean" - }, - "error_response": { - "oneOf": [ - { - "type": "string" - }, - { - "type": "object" - } - ] - }, - "filter": { - "description": "filter determines whether the plugin needs to be executed at runtime", - "type": "array" - }, - "priority": { - "description": "priority of plugins by customized order", - "type": "integer" - } - }, - "type": "object" - }, - "authorization": { - "properties": { - "apikey": { - "type": "string" - }, - "clientid": { - "type": "string" - } - }, - "type": "object" - }, - "function_uri": { - "type": "string" - }, - "keepalive": { - "default": true, - "type": "boolean" - }, - "keepalive_pool": { - "default": 5, - "minimum": 1, - "type": "integer" - }, - "keepalive_timeout": { - "default": 60000, - "minimum": 1000, - "type": "integer" - }, - "ssl_verify": { - "default": true, - "type": "boolean" - }, - "timeout": { - "default": 3000, - "minimum": 100, - "type": "integer" - } - }, - "required": [ - "function_uri" - ], - "type": "object" - }, - "version": 0.1 - }, - "basic-auth": { - "consumer_schema": { - "properties": { - "password": { - "type": "string" - }, - "username": { - "type": "string" - } - }, - "required": [ - "password", - "username" - ], - "title": "work with consumer object", - "type": "object" - }, - "priority": 2520, - "schema": { - "$comment": "this is a mark for our injected plugin schema", - "properties": { - "_meta": { - "properties": { - "disable": { - "type": "boolean" - }, - "error_response": { - "oneOf": [ - { - "type": "string" - }, - { - "type": "object" - } - ] - }, - "filter": { - "description": "filter determines whether the plugin needs to be executed at runtime", - "type": "array" - }, - "priority": { - "description": "priority of plugins by customized order", - "type": "integer" - } - }, - "type": "object" - }, - "hide_credentials": { - "default": false, - "type": "boolean" - } - }, - "title": "work with route or service object", - "type": "object" - }, - "type": "auth", - "version": 0.1 - }, - "batch-requests": { - "metadata_schema": { - "properties": { - "max_body_size": { - "default": 1048576, - "description": "max pipeline body size in bytes", - "exclusiveMinimum": 0, - "type": "integer" - } - }, - "type": "object" - }, - "priority": 4010, - "schema": { - "$comment": "this is a mark for our injected plugin schema", - "properties": { - "_meta": { - "properties": { - "disable": { - "type": "boolean" - }, - "error_response": { - "oneOf": [ - { - "type": "string" - }, - { - "type": "object" - } - ] - }, - "filter": { - "description": "filter determines whether the plugin needs to be executed at runtime", - "type": "array" - }, - "priority": { - "description": "priority of plugins by customized order", - "type": "integer" - } - }, - "type": "object" - } - }, - "type": "object" - }, - "scope": "global", - "version": 0.1 - }, - "cas-auth": { - "priority": 2597, - "schema": { - "$comment": "this is a mark for our injected plugin schema", - "properties": { - "_meta": { - "properties": { - "disable": { - "type": "boolean" - }, - "error_response": { - "oneOf": [ - { - "type": "string" - }, - { - "type": "object" - } - ] - }, - "filter": { - "description": "filter determines whether the plugin needs to be executed at runtime", - "type": "array" - }, - "priority": { - "description": "priority of plugins by customized order", - "type": "integer" - } - }, - "type": "object" - }, - "cas_callback_uri": { - "type": "string" - }, - "idp_uri": { - "type": "string" - }, - "logout_uri": { - "type": "string" - } - }, - "required": [ - "cas_callback_uri", - "idp_uri", - "logout_uri" - ], - "type": "object" - }, - "version": 0.1 - }, - "clickhouse-logger": { - "metadata_schema": { - "properties": { - "log_format": { - "default": { - "@timestamp": "$time_iso8601", - "client_ip": "$remote_addr", - "host": "$host" - }, - "type": "object" - } - }, - "type": "object" - }, - "priority": 398, - "schema": { - "$comment": "this is a mark for our injected plugin schema", - "oneOf": [ - { - "required": [ - "database", - "endpoint_addr", - "logtable", - "password", - "user" - ] - }, - { - "required": [ - "database", - "endpoint_addrs", - "logtable", - "password", - "user" - ] - } - ], - "properties": { - "_meta": { - "properties": { - "disable": { - "type": "boolean" - }, - "error_response": { - "oneOf": [ - { - "type": "string" - }, - { - "type": "object" - } - ] - }, - "filter": { - "description": "filter determines whether the plugin needs to be executed at runtime", - "type": "array" - }, - "priority": { - "description": "priority of plugins by customized order", - "type": "integer" - } - }, - "type": "object" - }, - "batch_max_size": { - "default": 1000, - "minimum": 1, - "type": "integer" - }, - "buffer_duration": { - "default": 60, - "minimum": 1, - "type": "integer" - }, - "database": { - "default": "", - "type": "string" - }, - "endpoint_addr": { - "pattern": "^[^\\/]+:\\/\\/([\\da-zA-Z.-]+|\\[[\\da-fA-F:]+\\])(:\\d+)?", - "type": "string" - }, - "endpoint_addrs": { - "items": { - "pattern": "^[^\\/]+:\\/\\/([\\da-zA-Z.-]+|\\[[\\da-fA-F:]+\\])(:\\d+)?", - "type": "string" - }, - "minItems": 1, - "type": "array" - }, - "inactive_timeout": { - "default": 5, - "minimum": 1, - "type": "integer" - }, - "logtable": { - "default": "", - "type": "string" - }, - "max_retry_count": { - "default": 0, - "minimum": 0, - "type": "integer" - }, - "name": { - "default": "clickhouse-logger", - "type": "string" - }, - "password": { - "default": "", - "type": "string" - }, - "retry_delay": { - "default": 1, - "minimum": 0, - "type": "integer" - }, - "ssl_verify": { - "default": true, - "type": "boolean" - }, - "timeout": { - "default": 3, - "minimum": 1, - "type": "integer" - }, - "user": { - "default": "", - "type": "string" - } - }, - "type": "object" - }, - "version": 0.1 - }, - "client-control": { - "priority": 22000, - "schema": { - "$comment": "this is a mark for our injected plugin schema", - "properties": { - "_meta": { - "properties": { - "disable": { - "type": "boolean" - }, - "error_response": { - "oneOf": [ - { - "type": "string" - }, - { - "type": "object" - } - ] - }, - "filter": { - "description": "filter determines whether the plugin needs to be executed at runtime", - "type": "array" - }, - "priority": { - "description": "priority of plugins by customized order", - "type": "integer" - } - }, - "type": "object" - }, - "max_body_size": { - "minimum": 0, - "type": "integer" - } - }, - "type": "object" - }, - "version": 0.1 - }, - "consumer-restriction": { - "priority": 2400, - "schema": { - "$comment": "this is a mark for our injected plugin schema", - "anyOf": [ - { - "required": [ - "blacklist" - ] - }, - { - "required": [ - "whitelist" - ] - }, - { - "required": [ - "allowed_by_methods" - ] - } - ], - "properties": { - "_meta": { - "properties": { - "disable": { - "type": "boolean" - }, - "error_response": { - "oneOf": [ - { - "type": "string" - }, - { - "type": "object" - } - ] - }, - "filter": { - "description": "filter determines whether the plugin needs to be executed at runtime", - "type": "array" - }, - "priority": { - "description": "priority of plugins by customized order", - "type": "integer" - } - }, - "type": "object" - }, - "allowed_by_methods": { - "items": { - "properties": { - "methods": { - "items": { - "description": "HTTP method", - "enum": [ - "CONNECT", - "DELETE", - "GET", - "HEAD", - "OPTIONS", - "PATCH", - "POST", - "PURGE", - "PUT", - "TRACE" - ], - "type": "string" - }, - "minItems": 1, - "type": "array" - }, - "user": { - "type": "string" - } - }, - "type": "object" - }, - "type": "array" - }, - "blacklist": { - "items": { - "type": "string" - }, - "minItems": 1, - "type": "array" - }, - "rejected_code": { - "default": 403, - "minimum": 200, - "type": "integer" - }, - "rejected_msg": { - "type": "string" - }, - "type": { - "default": "consumer_name", - "enum": [ - "consumer_name", - "route_id", - "service_id" - ], - "type": "string" - }, - "whitelist": { - "items": { - "type": "string" - }, - "minItems": 1, - "type": "array" - } - }, - "type": "object" - }, - "version": 0.1 - }, - "cors": { - "metadata_schema": { - "properties": { - "allow_origins": { - "additionalProperties": { - "pattern": "^(\\*|\\*\\*|null|\\w+://[^,]+(,\\w+://[^,]+)*)$", - "type": "string" - }, - "type": "object" - } - }, - "type": "object" - }, - "priority": 4000, - "schema": { - "$comment": "this is a mark for our injected plugin schema", - "properties": { - "_meta": { - "properties": { - "disable": { - "type": "boolean" - }, - "error_response": { - "oneOf": [ - { - "type": "string" - }, - { - "type": "object" - } - ] - }, - "filter": { - "description": "filter determines whether the plugin needs to be executed at runtime", - "type": "array" - }, - "priority": { - "description": "priority of plugins by customized order", - "type": "integer" - } - }, - "type": "object" - }, - "allow_credential": { - "default": false, - "description": "allow client append credential. according to CORS specification,if you set this option to 'true', you can not use '*' for other options.", - "type": "boolean" - }, - "allow_headers": { - "default": "*", - "description": "you can use '*' to allow all header when no credentials,'**' to allow forcefully(it will bring some security risks, be carefully),multiple header use ',' to split. default: *.", - "type": "string" - }, - "allow_methods": { - "default": "*", - "description": "you can use '*' to allow all methods when no credentials,'**' to allow forcefully(it will bring some security risks, be carefully),multiple method use ',' to split. default: *.", - "type": "string" - }, - "allow_origins": { - "default": "*", - "description": "you can use '*' to allow all origins when no credentials,'**' to allow forcefully(it will bring some security risks, be carefully),multiple origin use ',' to split. default: *.", - "pattern": "^(\\*|\\*\\*|null|\\w+://[^,]+(,\\w+://[^,]+)*)$", - "type": "string" - }, - "allow_origins_by_metadata": { - "description": "set allowed origins by referencing origins in plugin metadata", - "items": { - "maxLength": 4096, - "minLength": 1, - "type": "string" - }, - "minItems": 1, - "type": "array", - "uniqueItems": true - }, - "allow_origins_by_regex": { - "description": "you can use regex to allow specific origins when no credentials,for example use [.*\\.test.com] to allow a.test.com and b.test.com", - "items": { - "maxLength": 4096, - "minLength": 1, - "type": "string" - }, - "minItems": 1, - "type": "array", - "uniqueItems": true - }, - "expose_headers": { - "default": "*", - "description": "you can use '*' to expose all header when no credentials,'**' to allow forcefully(it will bring some security risks, be carefully),multiple header use ',' to split. default: *.", - "type": "string" - }, - "max_age": { - "default": 5, - "description": "maximum number of seconds the results can be cached.-1 means no cached, the max value is depend on browser,more details plz check MDN. default: 5.", - "type": "integer" - } - }, - "type": "object" - }, - "version": 0.1 - }, - "csrf": { - "priority": 2980, - "schema": { - "$comment": "this is a mark for our injected plugin schema", - "properties": { - "_meta": { - "properties": { - "disable": { - "type": "boolean" - }, - "error_response": { - "oneOf": [ - { - "type": "string" - }, - { - "type": "object" - } - ] - }, - "filter": { - "description": "filter determines whether the plugin needs to be executed at runtime", - "type": "array" - }, - "priority": { - "description": "priority of plugins by customized order", - "type": "integer" - } - }, - "type": "object" - }, - "expires": { - "default": 7200, - "description": "expires time(s) for csrf token", - "type": "integer" - }, - "key": { - "description": "use to generate csrf token", - "type": "string" - }, - "name": { - "default": "apisix-csrf-token", - "description": "the csrf token name", - "type": "string" - } - }, - "required": [ - "key" - ], - "type": "object" - }, - "version": 0.1 - }, - "datadog": { - "metadata_schema": { - "properties": { - "constant_tags": { - "default": [ - "source:apisix" - ], - "items": { - "type": "string" - }, - "type": "array" - }, - "host": { - "default": "127.0.0.1", - "type": "string" - }, - "namespace": { - "default": "apisix", - "type": "string" - }, - "port": { - "default": 8125, - "minimum": 0, - "type": "integer" - } - }, - "type": "object" - }, - "priority": 495, - "schema": { - "$comment": "this is a mark for our injected plugin schema", - "properties": { - "_meta": { - "properties": { - "disable": { - "type": "boolean" - }, - "error_response": { - "oneOf": [ - { - "type": "string" - }, - { - "type": "object" - } - ] - }, - "filter": { - "description": "filter determines whether the plugin needs to be executed at runtime", - "type": "array" - }, - "priority": { - "description": "priority of plugins by customized order", - "type": "integer" - } - }, - "type": "object" - }, - "batch_max_size": { - "default": 1000, - "minimum": 1, - "type": "integer" - }, - "buffer_duration": { - "default": 60, - "minimum": 1, - "type": "integer" - }, - "inactive_timeout": { - "default": 5, - "minimum": 1, - "type": "integer" - }, - "max_retry_count": { - "default": 0, - "minimum": 0, - "type": "integer" - }, - "name": { - "default": "datadog", - "type": "string" - }, - "prefer_name": { - "default": true, - "type": "boolean" - }, - "retry_delay": { - "default": 1, - "minimum": 0, - "type": "integer" - } - }, - "type": "object" - }, - "version": 0.1 - }, - "dubbo-proxy": { - "priority": 507, - "schema": { - "$comment": "this is a mark for our injected plugin schema", - "properties": { - "_meta": { - "properties": { - "disable": { - "type": "boolean" - }, - "error_response": { - "oneOf": [ - { - "type": "string" - }, - { - "type": "object" - } - ] - }, - "filter": { - "description": "filter determines whether the plugin needs to be executed at runtime", - "type": "array" - }, - "priority": { - "description": "priority of plugins by customized order", - "type": "integer" - } - }, - "type": "object" - }, - "method": { - "minLength": 1, - "type": "string" - }, - "service_name": { - "minLength": 1, - "type": "string" - }, - "service_version": { - "pattern": "^\\d+\\.\\d+\\.\\d+", - "type": "string" - } - }, - "required": [ - "service_name", - "service_version" - ], - "type": "object" - }, - "version": 0.1 - }, - "echo": { - "priority": 412, - "schema": { - "$comment": "this is a mark for our injected plugin schema", - "anyOf": [ - { - "required": [ - "before_body" - ] - }, - { - "required": [ - "body" - ] - }, - { - "required": [ - "after_body" - ] - } - ], - "minProperties": 1, - "properties": { - "_meta": { - "properties": { - "disable": { - "type": "boolean" - }, - "error_response": { - "oneOf": [ - { - "type": "string" - }, - { - "type": "object" - } - ] - }, - "filter": { - "description": "filter determines whether the plugin needs to be executed at runtime", - "type": "array" - }, - "priority": { - "description": "priority of plugins by customized order", - "type": "integer" - } - }, - "type": "object" - }, - "after_body": { - "description": "body after the modification of filter phase.", - "type": "string" - }, - "before_body": { - "description": "body before the filter phase.", - "type": "string" - }, - "body": { - "description": "body to replace upstream response.", - "type": "string" - }, - "headers": { - "description": "new headers for response", - "minProperties": 1, - "type": "object" - } - }, - "type": "object" - }, - "version": 0.1 - }, - "elasticsearch-logger": { - "metadata_schema": { - "properties": { - "log_format": { - "default": { - "@timestamp": "$time_iso8601", - "client_ip": "$remote_addr", - "host": "$host" - }, - "type": "object" - } - }, - "type": "object" - }, - "priority": 413, - "schema": { - "$comment": "this is a mark for our injected plugin schema", - "properties": { - "_meta": { - "properties": { - "disable": { - "type": "boolean" - }, - "error_response": { - "oneOf": [ - { - "type": "string" - }, - { - "type": "object" - } - ] - }, - "filter": { - "description": "filter determines whether the plugin needs to be executed at runtime", - "type": "array" - }, - "priority": { - "description": "priority of plugins by customized order", - "type": "integer" - } - }, - "type": "object" - }, - "auth": { - "properties": { - "password": { - "minLength": 1, - "type": "string" - }, - "username": { - "minLength": 1, - "type": "string" - } - }, - "required": [ - "password", - "username" - ], - "type": "object" - }, - "batch_max_size": { - "default": 1000, - "minimum": 1, - "type": "integer" - }, - "buffer_duration": { - "default": 60, - "minimum": 1, - "type": "integer" - }, - "endpoint_addr": { - "pattern": "[^/]$", - "type": "string" - }, - "field": { - "properties": { - "index": { - "type": "string" - }, - "type": { - "type": "string" - } - }, - "required": [ - "index" - ], - "type": "object" - }, - "inactive_timeout": { - "default": 5, - "minimum": 1, - "type": "integer" - }, - "max_retry_count": { - "default": 0, - "minimum": 0, - "type": "integer" - }, - "name": { - "default": "elasticsearch-logger", - "type": "string" - }, - "retry_delay": { - "default": 1, - "minimum": 0, - "type": "integer" - }, - "ssl_verify": { - "default": true, - "type": "boolean" - }, - "timeout": { - "default": 10, - "minimum": 1, - "type": "integer" - } - }, - "required": [ - "endpoint_addr", - "field" - ], - "type": "object" - }, - "version": 0.1 - }, - "error-log-logger": { - "metadata_schema": { - "oneOf": [ - { - "required": [ - "skywalking" - ] - }, - { - "required": [ - "tcp" - ] - }, - { - "required": [ - "clickhouse" - ] - }, - { - "required": [ - "host", - "port" - ] - } - ], - "properties": { - "batch_max_size": { - "default": 1000, - "minimum": 0, - "type": "integer" - }, - "buffer_duration": { - "default": 60, - "minimum": 1, - "type": "integer" - }, - "clickhouse": { - "properties": { - "database": { - "default": "", - "type": "string" - }, - "endpoint_addr": { - "1": { - "pattern": "^[^\\/]+:\\/\\/([\\da-zA-Z.-]+|\\[[\\da-fA-F:]+\\])(:\\d+)?", - "type": "string" - }, - "default": "http://127.0.0.1:8123" - }, - "logtable": { - "default": "", - "type": "string" - }, - "password": { - "default": "", - "type": "string" - }, - "user": { - "default": "default", - "type": "string" - } - }, - "required": [ - "database", - "endpoint_addr", - "logtable", - "password", - "user" - ], - "type": "object" - }, - "inactive_timeout": { - "default": 3, - "minimum": 1, - "type": "integer" - }, - "keepalive": { - "default": 30, - "minimum": 1, - "type": "integer" - }, - "level": { - "default": "WARN", - "enum": [ - "ALERT", - "CRIT", - "DEBUG", - "EMERG", - "ERR", - "ERROR", - "INFO", - "NOTICE", - "STDERR", - "WARN" - ], - "type": "string" - }, - "max_retry_count": { - "default": 0, - "minimum": 0, - "type": "integer" - }, - "name": { - "default": "error-log-logger", - "type": "string" - }, - "retry_delay": { - "default": 1, - "minimum": 0, - "type": "integer" - }, - "skywalking": { - "properties": { - "endpoint_addr": { - "default": "http://127.0.0.1:12900/v3/logs" - }, - "service_instance_name": { - "default": "APISIX Service Instance", - "type": "string" - }, - "service_name": { - "default": "APISIX", - "type": "string" - } - }, - "type": "object" - }, - "tcp": { - "properties": { - "host": { - "pattern": "^\\*?[0-9a-zA-Z-._\\[\\]:]+$", - "type": "string" - }, - "port": { - "minimum": 0, - "type": "integer" - }, - "tls": { - "default": false, - "type": "boolean" - }, - "tls_server_name": { - "type": "string" - } - }, - "required": [ - "host", - "port" - ], - "type": "object" - }, - "timeout": { - "default": 3, - "minimum": 1, - "type": "integer" - } - }, - "type": "object" - }, - "priority": 1091, - "schema": { - "$comment": "this is a mark for our injected plugin schema", - "properties": { - "_meta": { - "properties": { - "disable": { - "type": "boolean" - }, - "error_response": { - "oneOf": [ - { - "type": "string" - }, - { - "type": "object" - } - ] - }, - "filter": { - "description": "filter determines whether the plugin needs to be executed at runtime", - "type": "array" - }, - "priority": { - "description": "priority of plugins by customized order", - "type": "integer" - } - }, - "type": "object" - } - }, - "type": "object" - }, - "scope": "global", - "version": 0.1 - }, - "example-plugin": { - "metadata_schema": { - "properties": { - "ikey": { - "minimum": 0, - "type": "number" - }, - "skey": { - "type": "string" - } - }, - "required": [ - "ikey", - "skey" - ], - "type": "object" - }, - "priority": 0, - "schema": { - "$comment": "this is a mark for our injected plugin schema", - "properties": { - "_meta": { - "properties": { - "disable": { - "type": "boolean" - }, - "error_response": { - "oneOf": [ - { - "type": "string" - }, - { - "type": "object" - } - ] - }, - "filter": { - "description": "filter determines whether the plugin needs to be executed at runtime", - "type": "array" - }, - "priority": { - "description": "priority of plugins by customized order", - "type": "integer" - } - }, - "type": "object" - }, - "i": { - "minimum": 0, - "type": "number" - }, - "ip": { - "type": "string" - }, - "port": { - "type": "integer" - }, - "s": { - "type": "string" - }, - "t": { - "minItems": 1, - "type": "array" - } - }, - "required": [ - "i" - ], - "type": "object" - }, - "version": 0.1 - }, - "ext-plugin-post-req": { - "priority": -3000, - "schema": { - "$comment": "this is a mark for our injected plugin schema", - "properties": { - "_meta": { - "properties": { - "disable": { - "type": "boolean" - }, - "error_response": { - "oneOf": [ - { - "type": "string" - }, - { - "type": "object" - } - ] - }, - "filter": { - "description": "filter determines whether the plugin needs to be executed at runtime", - "type": "array" - }, - "priority": { - "description": "priority of plugins by customized order", - "type": "integer" - } - }, - "type": "object" - }, - "allow_degradation": { - "default": false, - "type": "boolean" - }, - "conf": { - "items": { - "properties": { - "name": { - "maxLength": 128, - "minLength": 1, - "type": "string" - }, - "value": { - "type": "string" - } - }, - "required": [ - "name", - "value" - ], - "type": "object" - }, - "minItems": 1, - "type": "array" - } - }, - "type": "object" - }, - "version": 0.1 - }, - "ext-plugin-post-resp": { - "priority": -4000, - "schema": { - "$comment": "this is a mark for our injected plugin schema", - "properties": { - "_meta": { - "properties": { - "disable": { - "type": "boolean" - }, - "error_response": { - "oneOf": [ - { - "type": "string" - }, - { - "type": "object" - } - ] - }, - "filter": { - "description": "filter determines whether the plugin needs to be executed at runtime", - "type": "array" - }, - "priority": { - "description": "priority of plugins by customized order", - "type": "integer" - } - }, - "type": "object" - }, - "allow_degradation": { - "default": false, - "type": "boolean" - }, - "conf": { - "items": { - "properties": { - "name": { - "maxLength": 128, - "minLength": 1, - "type": "string" - }, - "value": { - "type": "string" - } - }, - "required": [ - "name", - "value" - ], - "type": "object" - }, - "minItems": 1, - "type": "array" - } - }, - "type": "object" - }, - "version": 0.1 - }, - "ext-plugin-pre-req": { - "priority": 12000, - "schema": { - "$comment": "this is a mark for our injected plugin schema", - "properties": { - "_meta": { - "properties": { - "disable": { - "type": "boolean" - }, - "error_response": { - "oneOf": [ - { - "type": "string" - }, - { - "type": "object" - } - ] - }, - "filter": { - "description": "filter determines whether the plugin needs to be executed at runtime", - "type": "array" - }, - "priority": { - "description": "priority of plugins by customized order", - "type": "integer" - } - }, - "type": "object" - }, - "allow_degradation": { - "default": false, - "type": "boolean" - }, - "conf": { - "items": { - "properties": { - "name": { - "maxLength": 128, - "minLength": 1, - "type": "string" - }, - "value": { - "type": "string" - } - }, - "required": [ - "name", - "value" - ], - "type": "object" - }, - "minItems": 1, - "type": "array" - } - }, - "type": "object" - }, - "version": 0.1 - }, - "fault-injection": { - "priority": 11000, - "schema": { - "$comment": "this is a mark for our injected plugin schema", - "minProperties": 1, - "properties": { - "_meta": { - "properties": { - "disable": { - "type": "boolean" - }, - "error_response": { - "oneOf": [ - { - "type": "string" - }, - { - "type": "object" - } - ] - }, - "filter": { - "description": "filter determines whether the plugin needs to be executed at runtime", - "type": "array" - }, - "priority": { - "description": "priority of plugins by customized order", - "type": "integer" - } - }, - "type": "object" - }, - "abort": { - "properties": { - "body": { - "minLength": 0, - "type": "string" - }, - "http_status": { - "minimum": 200, - "type": "integer" - }, - "percentage": { - "maximum": 100, - "minimum": 0, - "type": "integer" - }, - "vars": { - "items": { - "type": "array" - }, - "maxItems": 20, - "type": "array" - } - }, - "required": [ - "http_status" - ], - "type": "object" - }, - "delay": { - "properties": { - "duration": { - "minimum": 0, - "type": "number" - }, - "percentage": { - "maximum": 100, - "minimum": 0, - "type": "integer" - }, - "vars": { - "items": { - "type": "array" - }, - "maxItems": 20, - "type": "array" - } - }, - "required": [ - "duration" - ], - "type": "object" - } - }, - "type": "object" - }, - "version": 0.1 - }, - "file-logger": { - "metadata_schema": { - "properties": { - "log_format": { - "default": { - "@timestamp": "$time_iso8601", - "client_ip": "$remote_addr", - "host": "$host" - }, - "type": "object" - } - }, - "type": "object" - }, - "priority": 399, - "schema": { - "$comment": "this is a mark for our injected plugin schema", - "properties": { - "_meta": { - "properties": { - "disable": { - "type": "boolean" - }, - "error_response": { - "oneOf": [ - { - "type": "string" - }, - { - "type": "object" - } - ] - }, - "filter": { - "description": "filter determines whether the plugin needs to be executed at runtime", - "type": "array" - }, - "priority": { - "description": "priority of plugins by customized order", - "type": "integer" - } - }, - "type": "object" - }, - "path": { - "type": "string" - } - }, - "required": [ - "path" - ], - "type": "object" - }, - "version": 0.1 - }, - "forward-auth": { - "priority": 2002, - "schema": { - "$comment": "this is a mark for our injected plugin schema", - "properties": { - "_meta": { - "properties": { - "disable": { - "type": "boolean" - }, - "error_response": { - "oneOf": [ - { - "type": "string" - }, - { - "type": "object" - } - ] - }, - "filter": { - "description": "filter determines whether the plugin needs to be executed at runtime", - "type": "array" - }, - "priority": { - "description": "priority of plugins by customized order", - "type": "integer" - } - }, - "type": "object" - }, - "client_headers": { - "default": {}, - "description": "authorization response header that will be sent tothe client when authorizing failed", - "items": { - "type": "string" - }, - "type": "array" - }, - "keepalive": { - "default": true, - "type": "boolean" - }, - "keepalive_pool": { - "default": 5, - "minimum": 1, - "type": "integer" - }, - "keepalive_timeout": { - "default": 60000, - "minimum": 1000, - "type": "integer" - }, - "request_headers": { - "default": {}, - "description": "client request header that will be sent to the authorization service", - "items": { - "type": "string" - }, - "type": "array" - }, - "request_method": { - "default": "GET", - "description": "the method for client to request the authorization service", - "enum": [ - "GET", - "POST" - ], - "type": "string" - }, - "ssl_verify": { - "default": true, - "type": "boolean" - }, - "timeout": { - "default": 3000, - "description": "timeout in milliseconds", - "maximum": 60000, - "minimum": 1, - "type": "integer" - }, - "upstream_headers": { - "default": {}, - "description": "authorization response header that will be sent to the upstream", - "items": { - "type": "string" - }, - "type": "array" - }, - "uri": { - "type": "string" - } - }, - "required": [ - "uri" - ], - "type": "object" - }, - "version": 0.1 - }, - "google-cloud-logging": { - "priority": 407, - "schema": { - "$comment": "this is a mark for our injected plugin schema", - "oneOf": [ - { - "required": [ - "auth_config" - ] - }, - { - "required": [ - "auth_file" - ] - } - ], - "properties": { - "_meta": { - "properties": { - "disable": { - "type": "boolean" - }, - "error_response": { - "oneOf": [ - { - "type": "string" - }, - { - "type": "object" - } - ] - }, - "filter": { - "description": "filter determines whether the plugin needs to be executed at runtime", - "type": "array" - }, - "priority": { - "description": "priority of plugins by customized order", - "type": "integer" - } - }, - "type": "object" - }, - "auth_config": { - "properties": { - "entries_uri": { - "default": "https://logging.googleapis.com/v2/entries:write", - "type": "string" - }, - "private_key": { - "type": "string" - }, - "project_id": { - "type": "string" - }, - "scopes": { - "default": [ - "https://www.googleapis.com/auth/cloud-platform", - "https://www.googleapis.com/auth/logging.admin", - "https://www.googleapis.com/auth/logging.read", - "https://www.googleapis.com/auth/logging.write" - ], - "items": { - "description": "Google OAuth2 Authorization Scopes", - "type": "string" - }, - "minItems": 1, - "type": "array", - "uniqueItems": true - }, - "token_uri": { - "default": "https://oauth2.googleapis.com/token", - "type": "string" - } - }, - "required": [ - "private_key", - "project_id", - "token_uri" - ], - "type": "object" - }, - "auth_file": { - "type": "string" - }, - "batch_max_size": { - "default": 1000, - "minimum": 1, - "type": "integer" - }, - "buffer_duration": { - "default": 60, - "minimum": 1, - "type": "integer" - }, - "inactive_timeout": { - "default": 5, - "minimum": 1, - "type": "integer" - }, - "log_id": { - "default": "apisix.apache.org%2Flogs", - "type": "string" - }, - "max_retry_count": { - "default": 0, - "minimum": 0, - "type": "integer" - }, - "name": { - "default": "google-cloud-logging", - "type": "string" - }, - "resource": { - "default": { - "type": "global" - }, - "properties": { - "labels": { - "type": "object" - }, - "type": { - "type": "string" - } - }, - "required": [ - "type" - ], - "type": "object" - }, - "retry_delay": { - "default": 1, - "minimum": 0, - "type": "integer" - }, - "ssl_verify": { - "default": true, - "type": "boolean" - } - }, - "type": "object" - }, - "version": 0.1 - }, - "grpc-transcode": { - "priority": 506, - "schema": { - "$comment": "this is a mark for our injected plugin schema", - "additionalProperties": true, - "properties": { - "_meta": { - "properties": { - "disable": { - "type": "boolean" - }, - "error_response": { - "oneOf": [ - { - "type": "string" - }, - { - "type": "object" - } - ] - }, - "filter": { - "description": "filter determines whether the plugin needs to be executed at runtime", - "type": "array" - }, - "priority": { - "description": "priority of plugins by customized order", - "type": "integer" - } - }, - "type": "object" - }, - "deadline": { - "default": 0, - "description": "deadline for grpc, millisecond", - "type": "number" - }, - "method": { - "description": "the method name in the grpc service.", - "type": "string" - }, - "pb_option": { - "default": [ - "auto_default_values", - "disable_hooks", - "enum_as_name", - "int64_as_number" - ], - "items": { - "anyOf": [ - { - "description": "enum as result", - "enum": [ - "int64_as_hexstring", - "int64_as_number", - "int64_as_string" - ], - "type": "string" - }, - { - "description": "int64 as result", - "enum": [ - "enum_as_name", - "enum_as_value" - ], - "type": "string" - }, - { - "description": "default values option", - "enum": [ - "auto_default_values", - "no_default_values", - "use_default_metatable", - "use_default_values" - ], - "type": "string" - }, - { - "description": "hooks option", - "enum": [ - "disable_hooks", - "enable_hooks" - ], - "type": "string" - } - ], - "type": "string" - }, - "minItems": 1, - "type": "array" - }, - "proto_id": { - "anyOf": [ - { - "maxLength": 64, - "minLength": 1, - "pattern": "^[a-zA-Z0-9-_.]+$", - "type": "string" - }, - { - "minimum": 1, - "type": "integer" - } - ] - }, - "service": { - "description": "the grpc service name", - "type": "string" - } - }, - "required": [ - "method", - "proto_id", - "service" - ], - "type": "object" - }, - "version": 0.1 - }, - "grpc-web": { - "priority": 505, - "schema": { - "$comment": "this is a mark for our injected plugin schema", - "properties": { - "_meta": { - "properties": { - "disable": { - "type": "boolean" - }, - "error_response": { - "oneOf": [ - { - "type": "string" - }, - { - "type": "object" - } - ] - }, - "filter": { - "description": "filter determines whether the plugin needs to be executed at runtime", - "type": "array" - }, - "priority": { - "description": "priority of plugins by customized order", - "type": "integer" - } - }, - "type": "object" - } - }, - "type": "object" - }, - "version": 0.1 - }, - "gzip": { - "priority": 995, - "schema": { - "$comment": "this is a mark for our injected plugin schema", - "properties": { - "_meta": { - "properties": { - "disable": { - "type": "boolean" - }, - "error_response": { - "oneOf": [ - { - "type": "string" - }, - { - "type": "object" - } - ] - }, - "filter": { - "description": "filter determines whether the plugin needs to be executed at runtime", - "type": "array" - }, - "priority": { - "description": "priority of plugins by customized order", - "type": "integer" - } - }, - "type": "object" - }, - "buffers": { - "default": { - "number": 32, - "size": 4096 - }, - "properties": { - "number": { - "default": 32, - "minimum": 1, - "type": "integer" - }, - "size": { - "default": 4096, - "minimum": 1, - "type": "integer" - } - }, - "type": "object" - }, - "comp_level": { - "default": 1, - "maximum": 9, - "minimum": 1, - "type": "integer" - }, - "http_version": { - "default": 1.1, - "enum": [ - 1, - 1.1 - ] - }, - "min_length": { - "default": 20, - "minimum": 1, - "type": "integer" - }, - "types": { - "anyOf": [ - { - "items": { - "minLength": 1, - "type": "string" - }, - "minItems": 1, - "type": "array" - }, - { - "enum": [ - "*" - ] - } - ], - "default": [ - "text/html" - ] - }, - "vary": { - "type": "boolean" - } - }, - "type": "object" - }, - "version": 0.1 - }, - "hmac-auth": { - "consumer_schema": { - "properties": { - "access_key": { - "maxLength": 256, - "minLength": 1, - "type": "string" - }, - "algorithm": { - "default": "hmac-sha256", - "enum": [ - "hmac-sha1", - "hmac-sha256", - "hmac-sha512" - ], - "type": "string" - }, - "clock_skew": { - "default": 0, - "type": "integer" - }, - "encode_uri_params": { - "default": true, - "title": "Whether to escape the uri parameter", - "type": "boolean" - }, - "keep_headers": { - "default": false, - "title": "whether to keep the http request header", - "type": "boolean" - }, - "max_req_body": { - "default": 524288, - "title": "Max request body size", - "type": "integer" - }, - "secret_key": { - "maxLength": 256, - "minLength": 1, - "type": "string" - }, - "signed_headers": { - "items": { - "maxLength": 50, - "minLength": 1, - "type": "string" - }, - "type": "array" - }, - "validate_request_body": { - "default": false, - "title": "A boolean value telling the plugin to enable body validation", - "type": "boolean" - } - }, - "required": [ - "access_key", - "secret_key" - ], - "title": "work with consumer object", - "type": "object" - }, - "priority": 2530, - "schema": { - "$comment": "this is a mark for our injected plugin schema", - "properties": { - "_meta": { - "properties": { - "disable": { - "type": "boolean" - }, - "error_response": { - "oneOf": [ - { - "type": "string" - }, - { - "type": "object" - } - ] - }, - "filter": { - "description": "filter determines whether the plugin needs to be executed at runtime", - "type": "array" - }, - "priority": { - "description": "priority of plugins by customized order", - "type": "integer" - } - }, - "type": "object" - } - }, - "title": "work with route or service object", - "type": "object" - }, - "type": "auth", - "version": 0.1 - }, - "http-logger": { - "metadata_schema": { - "properties": { - "log_format": { - "default": { - "@timestamp": "$time_iso8601", - "client_ip": "$remote_addr", - "host": "$host" - }, - "type": "object" - } - }, - "type": "object" - }, - "priority": 410, - "schema": { - "$comment": "this is a mark for our injected plugin schema", - "properties": { - "_meta": { - "properties": { - "disable": { - "type": "boolean" - }, - "error_response": { - "oneOf": [ - { - "type": "string" - }, - { - "type": "object" - } - ] - }, - "filter": { - "description": "filter determines whether the plugin needs to be executed at runtime", - "type": "array" - }, - "priority": { - "description": "priority of plugins by customized order", - "type": "integer" - } - }, - "type": "object" - }, - "auth_header": { - "type": "string" - }, - "batch_max_size": { - "default": 1000, - "minimum": 1, - "type": "integer" - }, - "buffer_duration": { - "default": 60, - "minimum": 1, - "type": "integer" - }, - "concat_method": { - "default": "json", - "enum": [ - "json", - "new_line" - ], - "type": "string" - }, - "inactive_timeout": { - "default": 5, - "minimum": 1, - "type": "integer" - }, - "include_req_body": { - "default": false, - "type": "boolean" - }, - "include_resp_body": { - "default": false, - "type": "boolean" - }, - "include_resp_body_expr": { - "items": { - "type": "array" - }, - "minItems": 1, - "type": "array" - }, - "max_retry_count": { - "default": 0, - "minimum": 0, - "type": "integer" - }, - "name": { - "default": "http logger", - "type": "string" - }, - "retry_delay": { - "default": 1, - "minimum": 0, - "type": "integer" - }, - "ssl_verify": { - "default": false, - "type": "boolean" - }, - "timeout": { - "default": 3, - "minimum": 1, - "type": "integer" - }, - "uri": { - "pattern": "^[^\\/]+:\\/\\/([\\da-zA-Z.-]+|\\[[\\da-fA-F:]+\\])(:\\d+)?", - "type": "string" - } - }, - "required": [ - "uri" - ], - "type": "object" - }, - "version": 0.1 - }, - "ip-restriction": { - "priority": 3000, - "schema": { - "$comment": "this is a mark for our injected plugin schema", - "oneOf": [ - { - "required": [ - "whitelist" - ] - }, - { - "required": [ - "blacklist" - ] - } - ], - "properties": { - "_meta": { - "properties": { - "disable": { - "type": "boolean" - }, - "error_response": { - "oneOf": [ - { - "type": "string" - }, - { - "type": "object" - } - ] - }, - "filter": { - "description": "filter determines whether the plugin needs to be executed at runtime", - "type": "array" - }, - "priority": { - "description": "priority of plugins by customized order", - "type": "integer" - } - }, - "type": "object" - }, - "blacklist": { - "items": { - "anyOf": [ - { - "format": "ipv4", - "title": "IPv4", - "type": "string" - }, - { - "pattern": "^([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])\\.([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])\\.([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])\\.([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])/([12]?[0-9]|3[0-2])$", - "title": "IPv4/CIDR", - "type": "string" - }, - { - "format": "ipv6", - "title": "IPv6", - "type": "string" - }, - { - "pattern": "^([a-fA-F0-9]{0,4}:){1,8}(:[a-fA-F0-9]{0,4}){0,8}([a-fA-F0-9]{0,4})?/[0-9]{1,3}$", - "title": "IPv6/CIDR", - "type": "string" - } - ] - }, - "minItems": 1, - "type": "array" - }, - "message": { - "default": "Your IP address is not allowed", - "maxLength": 1024, - "minLength": 1, - "type": "string" - }, - "whitelist": { - "items": { - "anyOf": [ - { - "format": "ipv4", - "title": "IPv4", - "type": "string" - }, - { - "pattern": "^([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])\\.([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])\\.([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])\\.([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])/([12]?[0-9]|3[0-2])$", - "title": "IPv4/CIDR", - "type": "string" - }, - { - "format": "ipv6", - "title": "IPv6", - "type": "string" - }, - { - "pattern": "^([a-fA-F0-9]{0,4}:){1,8}(:[a-fA-F0-9]{0,4}){0,8}([a-fA-F0-9]{0,4})?/[0-9]{1,3}$", - "title": "IPv6/CIDR", - "type": "string" - } - ] - }, - "minItems": 1, - "type": "array" - } - }, - "type": "object" - }, - "version": 0.1 - }, - "jwt-auth": { - "consumer_schema": { - "dependencies": { - "algorithm": { - "oneOf": [ - { - "properties": { - "algorithm": { - "default": "HS256", - "enum": [ - "HS256", - "HS512" - ] - } - } - }, - { - "properties": { - "algorithm": { - "enum": [ - "ES256", - "RS256" - ] - }, - "private_key": { - "type": "string" - }, - "public_key": { - "type": "string" - } - }, - "required": [ - "private_key", - "public_key" - ] - }, - { - "properties": { - "algorithm": { - "enum": [ - "ES256", - "RS256" - ] - }, - "vault": { - "properties": {}, - "type": "object" - } - }, - "required": [ - "vault" - ] - } - ] - } - }, - "properties": { - "algorithm": { - "default": "HS256", - "enum": [ - "ES256", - "HS256", - "HS512", - "RS256" - ], - "type": "string" - }, - "base64_secret": { - "default": false, - "type": "boolean" - }, - "exp": { - "default": 86400, - "minimum": 1, - "type": "integer" - }, - "key": { - "type": "string" - }, - "lifetime_grace_period": { - "default": 0, - "minimum": 0, - "type": "integer" - }, - "secret": { - "type": "string" - }, - "vault": { - "properties": {}, - "type": "object" - } - }, - "required": [ - "key" - ], - "type": "object" - }, - "priority": 2510, - "schema": { - "$comment": "this is a mark for our injected plugin schema", - "properties": { - "_meta": { - "properties": { - "disable": { - "type": "boolean" - }, - "error_response": { - "oneOf": [ - { - "type": "string" - }, - { - "type": "object" - } - ] - }, - "filter": { - "description": "filter determines whether the plugin needs to be executed at runtime", - "type": "array" - }, - "priority": { - "description": "priority of plugins by customized order", - "type": "integer" - } - }, - "type": "object" - }, - "cookie": { - "default": "jwt", - "type": "string" - }, - "header": { - "default": "authorization", - "type": "string" - }, - "query": { - "default": "jwt", - "type": "string" - } - }, - "type": "object" - }, - "type": "auth", - "version": 0.1 - }, - "kafka-logger": { - "metadata_schema": { - "properties": { - "log_format": { - "default": { - "@timestamp": "$time_iso8601", - "client_ip": "$remote_addr", - "host": "$host" - }, - "type": "object" - } - }, - "type": "object" - }, - "priority": 403, - "schema": { - "$comment": "this is a mark for our injected plugin schema", - "oneOf": [ - { - "required": [ - "broker_list", - "kafka_topic" - ] - }, - { - "required": [ - "brokers", - "kafka_topic" - ] - } - ], - "properties": { - "_meta": { - "properties": { - "disable": { - "type": "boolean" - }, - "error_response": { - "oneOf": [ - { - "type": "string" - }, - { - "type": "object" - } - ] - }, - "filter": { - "description": "filter determines whether the plugin needs to be executed at runtime", - "type": "array" - }, - "priority": { - "description": "priority of plugins by customized order", - "type": "integer" - } - }, - "type": "object" - }, - "batch_max_size": { - "default": 1000, - "minimum": 1, - "type": "integer" - }, - "broker_list": { - "minProperties": 1, - "patternProperties": { - ".*": { - "description": "the port of kafka broker", - "maximum": 65535, - "minimum": 1, - "type": "integer" - } - }, - "type": "object" - }, - "brokers": { - "items": { - "properties": { - "host": { - "description": "the host of kafka broker", - "type": "string" - }, - "port": { - "description": "the port of kafka broker", - "maximum": 65535, - "minimum": 1, - "type": "integer" - }, - "sasl_config": { - "description": "sasl config", - "properties": { - "mechanism": { - "default": "PLAIN", - "enum": [ - "PLAIN" - ], - "type": "string" - }, - "password": { - "description": "password", - "type": "string" - }, - "user": { - "description": "user", - "type": "string" - } - }, - "required": [ - "password", - "user" - ], - "type": "object" - } - }, - "required": [ - "host", - "port" - ], - "type": "object" - }, - "minItems": 1, - "type": "array", - "uniqueItems": true - }, - "buffer_duration": { - "default": 60, - "minimum": 1, - "type": "integer" - }, - "cluster_name": { - "default": 1, - "minimum": 1, - "type": "integer" - }, - "inactive_timeout": { - "default": 5, - "minimum": 1, - "type": "integer" - }, - "include_req_body": { - "default": false, - "type": "boolean" - }, - "include_req_body_expr": { - "items": { - "type": "array" - }, - "minItems": 1, - "type": "array" - }, - "include_resp_body": { - "default": false, - "type": "boolean" - }, - "include_resp_body_expr": { - "items": { - "type": "array" - }, - "minItems": 1, - "type": "array" - }, - "kafka_topic": { - "type": "string" - }, - "key": { - "type": "string" - }, - "max_retry_count": { - "default": 0, - "minimum": 0, - "type": "integer" - }, - "meta_format": { - "default": "default", - "enum": [ - "default", - "origin" - ], - "type": "string" - }, - "name": { - "default": "kafka logger", - "type": "string" - }, - "producer_batch_num": { - "default": 200, - "minimum": 1, - "type": "integer" - }, - "producer_batch_size": { - "default": 1048576, - "minimum": 0, - "type": "integer" - }, - "producer_max_buffering": { - "default": 50000, - "minimum": 1, - "type": "integer" - }, - "producer_time_linger": { - "default": 1, - "minimum": 1, - "type": "integer" - }, - "producer_type": { - "default": "async", - "enum": [ - "async", - "sync" - ], - "type": "string" - }, - "required_acks": { - "default": 1, - "enum": [ - -1, - 0, - 1 - ], - "type": "integer" - }, - "retry_delay": { - "default": 1, - "minimum": 0, - "type": "integer" - }, - "timeout": { - "default": 3, - "minimum": 1, - "type": "integer" - } - }, - "type": "object" - }, - "version": 0.1 - }, - "kafka-proxy": { - "priority": 508, - "schema": { - "$comment": "this is a mark for our injected plugin schema", - "properties": { - "_meta": { - "properties": { - "disable": { - "type": "boolean" - }, - "error_response": { - "oneOf": [ - { - "type": "string" - }, - { - "type": "object" - } - ] - }, - "filter": { - "description": "filter determines whether the plugin needs to be executed at runtime", - "type": "array" - }, - "priority": { - "description": "priority of plugins by customized order", - "type": "integer" - } - }, - "type": "object" - }, - "sasl": { - "properties": { - "password": { - "type": "string" - }, - "username": { - "type": "string" - } - }, - "required": [ - "password", - "username" - ], - "type": "object" - } - }, - "type": "object" - }, - "version": 0.1 - }, - "key-auth": { - "consumer_schema": { - "properties": { - "key": { - "type": "string" - } - }, - "required": [ - "key" - ], - "type": "object" - }, - "priority": 2500, - "schema": { - "$comment": "this is a mark for our injected plugin schema", - "properties": { - "_meta": { - "properties": { - "disable": { - "type": "boolean" - }, - "error_response": { - "oneOf": [ - { - "type": "string" - }, - { - "type": "object" - } - ] - }, - "filter": { - "description": "filter determines whether the plugin needs to be executed at runtime", - "type": "array" - }, - "priority": { - "description": "priority of plugins by customized order", - "type": "integer" - } - }, - "type": "object" - }, - "header": { - "default": "apikey", - "type": "string" - }, - "hide_credentials": { - "default": false, - "type": "boolean" - }, - "query": { - "default": "apikey", - "type": "string" - } - }, - "type": "object" - }, - "type": "auth", - "version": 0.1 - }, - "ldap-auth": { - "consumer_schema": { - "properties": { - "user_dn": { - "type": "string" - } - }, - "required": [ - "user_dn" - ], - "title": "work with consumer object", - "type": "object" - }, - "priority": 2540, - "schema": { - "$comment": "this is a mark for our injected plugin schema", - "properties": { - "_meta": { - "properties": { - "disable": { - "type": "boolean" - }, - "error_response": { - "oneOf": [ - { - "type": "string" - }, - { - "type": "object" - } - ] - }, - "filter": { - "description": "filter determines whether the plugin needs to be executed at runtime", - "type": "array" - }, - "priority": { - "description": "priority of plugins by customized order", - "type": "integer" - } - }, - "type": "object" - }, - "base_dn": { - "type": "string" - }, - "ldap_uri": { - "type": "string" - }, - "tls_verify": { - "default": false, - "type": "boolean" - }, - "uid": { - "default": "cn", - "type": "string" - }, - "use_tls": { - "default": false, - "type": "boolean" - } - }, - "required": [ - "base_dn", - "ldap_uri" - ], - "title": "work with route or service object", - "type": "object" - }, - "type": "auth", - "version": 0.1 - }, - "limit-conn": { - "priority": 1003, - "schema": { - "$comment": "this is a mark for our injected plugin schema", - "properties": { - "_meta": { - "properties": { - "disable": { - "type": "boolean" - }, - "error_response": { - "oneOf": [ - { - "type": "string" - }, - { - "type": "object" - } - ] - }, - "filter": { - "description": "filter determines whether the plugin needs to be executed at runtime", - "type": "array" - }, - "priority": { - "description": "priority of plugins by customized order", - "type": "integer" - } - }, - "type": "object" - }, - "allow_degradation": { - "default": false, - "type": "boolean" - }, - "burst": { - "minimum": 0, - "type": "integer" - }, - "conn": { - "exclusiveMinimum": 0, - "type": "integer" - }, - "default_conn_delay": { - "exclusiveMinimum": 0, - "type": "number" - }, - "key": { - "type": "string" - }, - "key_type": { - "default": "var", - "enum": [ - "var", - "var_combination" - ], - "type": "string" - }, - "only_use_default_delay": { - "default": false, - "type": "boolean" - }, - "rejected_code": { - "default": 503, - "maximum": 599, - "minimum": 200, - "type": "integer" - }, - "rejected_msg": { - "minLength": 1, - "type": "string" - } - }, - "required": [ - "burst", - "conn", - "default_conn_delay", - "key" - ], - "type": "object" - }, - "version": 0.1 - }, - "limit-count": { - "priority": 1002, - "schema": { - "$comment": "this is a mark for our injected plugin schema", - "else": { - "if": { - "properties": { - "policy": { - "enum": [ - "redis-cluster" - ] - } - } - }, - "then": { - "properties": { - "redis_cluster_name": { - "type": "string" - }, - "redis_cluster_nodes": { - "items": { - "maxLength": 100, - "minLength": 2, - "type": "string" - }, - "minItems": 2, - "type": "array" - }, - "redis_password": { - "minLength": 0, - "type": "string" - }, - "redis_timeout": { - "default": 1000, - "minimum": 1, - "type": "integer" - } - }, - "required": [ - "redis_cluster_name", - "redis_cluster_nodes" - ] - } - }, - "properties": { - "_meta": { - "properties": { - "disable": { - "type": "boolean" - }, - "error_response": { - "oneOf": [ - { - "type": "string" - }, - { - "type": "object" - } - ] - }, - "filter": { - "description": "filter determines whether the plugin needs to be executed at runtime", - "type": "array" - }, - "priority": { - "description": "priority of plugins by customized order", - "type": "integer" - } - }, - "type": "object" - }, - "allow_degradation": { - "default": false, - "type": "boolean" - }, - "count": { - "exclusiveMinimum": 0, - "type": "integer" - }, - "group": { - "type": "string" - }, - "key": { - "default": "remote_addr", - "type": "string" - }, - "key_type": { - "default": "var", - "enum": [ - "constant", - "var", - "var_combination" - ], - "type": "string" - }, - "policy": { - "default": "local", - "enum": [ - "local", - "redis", - "redis-cluster" - ], - "type": "string" - }, - "rejected_code": { - "default": 503, - "maximum": 599, - "minimum": 200, - "type": "integer" - }, - "rejected_msg": { - "minLength": 1, - "type": "string" - }, - "show_limit_quota_header": { - "default": true, - "type": "boolean" - }, - "time_window": { - "exclusiveMinimum": 0, - "type": "integer" - } - }, - "required": [ - "count", - "time_window" - ], - "if": { - "properties": { - "policy": { - "enum": [ - "redis" - ] - } - } - }, - "then": { - "properties": { - "redis_database": { - "default": 0, - "minimum": 0, - "type": "integer" - }, - "redis_host": { - "minLength": 2, - "type": "string" - }, - "redis_password": { - "minLength": 0, - "type": "string" - }, - "redis_port": { - "default": 6379, - "minimum": 1, - "type": "integer" - }, - "redis_timeout": { - "default": 1000, - "minimum": 1, - "type": "integer" - } - }, - "required": [ - "redis_host" - ] - }, - "type": "object" - }, - "version": 0.4 - }, - "limit-req": { - "priority": 1001, - "schema": { - "$comment": "this is a mark for our injected plugin schema", - "properties": { - "_meta": { - "properties": { - "disable": { - "type": "boolean" - }, - "error_response": { - "oneOf": [ - { - "type": "string" - }, - { - "type": "object" - } - ] - }, - "filter": { - "description": "filter determines whether the plugin needs to be executed at runtime", - "type": "array" - }, - "priority": { - "description": "priority of plugins by customized order", - "type": "integer" - } - }, - "type": "object" - }, - "allow_degradation": { - "default": false, - "type": "boolean" - }, - "burst": { - "minimum": 0, - "type": "number" - }, - "key": { - "type": "string" - }, - "key_type": { - "default": "var", - "enum": [ - "var", - "var_combination" - ], - "type": "string" - }, - "nodelay": { - "default": false, - "type": "boolean" - }, - "rate": { - "exclusiveMinimum": 0, - "type": "number" - }, - "rejected_code": { - "default": 503, - "maximum": 599, - "minimum": 200, - "type": "integer" - }, - "rejected_msg": { - "minLength": 1, - "type": "string" - } - }, - "required": [ - "burst", - "key", - "rate" - ], - "type": "object" - }, - "version": 0.1 - }, - "log-rotate": { - "priority": 100, - "schema": { - "$comment": "this is a mark for our injected plugin schema", - "properties": { - "_meta": { - "properties": { - "disable": { - "type": "boolean" - }, - "error_response": { - "oneOf": [ - { - "type": "string" - }, - { - "type": "object" - } - ] - }, - "filter": { - "description": "filter determines whether the plugin needs to be executed at runtime", - "type": "array" - }, - "priority": { - "description": "priority of plugins by customized order", - "type": "integer" - } - }, - "type": "object" - } - }, - "type": "object" - }, - "scope": "global", - "version": 0.1 - }, - "loggly": { - "metadata_schema": { - "properties": { - "host": { - "default": "logs-01.loggly.com", - "type": "string" - }, - "log_format": { - "type": "object" - }, - "port": { - "default": 514, - "type": "integer" - }, - "protocol": { - "default": "syslog", - "enum": [ - "http", - "https", - "syslog" - ], - "type": "string" - }, - "timeout": { - "default": 5000, - "minimum": 1, - "type": "integer" - } - }, - "type": "object" - }, - "priority": 411, - "schema": { - "$comment": "this is a mark for our injected plugin schema", - "properties": { - "_meta": { - "properties": { - "disable": { - "type": "boolean" - }, - "error_response": { - "oneOf": [ - { - "type": "string" - }, - { - "type": "object" - } - ] - }, - "filter": { - "description": "filter determines whether the plugin needs to be executed at runtime", - "type": "array" - }, - "priority": { - "description": "priority of plugins by customized order", - "type": "integer" - } - }, - "type": "object" - }, - "batch_max_size": { - "default": 1000, - "minimum": 1, - "type": "integer" - }, - "buffer_duration": { - "default": 60, - "minimum": 1, - "type": "integer" - }, - "customer_token": { - "type": "string" - }, - "inactive_timeout": { - "default": 5, - "minimum": 1, - "type": "integer" - }, - "include_req_body": { - "default": false, - "type": "boolean" - }, - "include_resp_body": { - "default": false, - "type": "boolean" - }, - "include_resp_body_expr": { - "items": { - "type": "array" - }, - "minItems": 1, - "type": "array" - }, - "max_retry_count": { - "default": 0, - "minimum": 0, - "type": "integer" - }, - "name": { - "default": "loggly", - "type": "string" - }, - "retry_delay": { - "default": 1, - "minimum": 0, - "type": "integer" - }, - "severity": { - "default": "INFO", - "description": "base severity log level", - "enum": [ - "ALERT", - "CRIT", - "DEBUG", - "EMEGR", - "ERR", - "INFO", - "NOTICE", - "WARNING", - "alert", - "crit", - "debug", - "emegr", - "err", - "info", - "notice", - "warning" - ], - "type": "string" - }, - "severity_map": { - "additionalProperties": false, - "description": "upstream response code vs syslog severity mapping", - "patternProperties": { - "^[1-5][0-9]{2}$": { - "description": "keys are HTTP status code, values are severity", - "enum": [ - "ALERT", - "CRIT", - "DEBUG", - "EMEGR", - "ERR", - "INFO", - "NOTICE", - "WARNING", - "alert", - "crit", - "debug", - "emegr", - "err", - "info", - "notice", - "warning" - ], - "type": "string" - } - }, - "type": "object" - }, - "ssl_verify": { - "default": true, - "type": "boolean" - }, - "tags": { - "default": [ - "apisix" - ], - "items": { - "pattern": "^(?!tag=)[ -~]*", - "type": "string" - }, - "minItems": 1, - "type": "array" - } - }, - "required": [ - "customer_token" - ], - "type": "object" - }, - "version": 0.1 - }, - "mocking": { - "priority": 10900, - "schema": { - "$comment": "this is a mark for our injected plugin schema", - "anyOf": [ - { - "required": [ - "response_example" - ] - }, - { - "required": [ - "response_schema" - ] - } - ], - "properties": { - "_meta": { - "properties": { - "disable": { - "type": "boolean" - }, - "error_response": { - "oneOf": [ - { - "type": "string" - }, - { - "type": "object" - } - ] - }, - "filter": { - "description": "filter determines whether the plugin needs to be executed at runtime", - "type": "array" - }, - "priority": { - "description": "priority of plugins by customized order", - "type": "integer" - } - }, - "type": "object" - }, - "content_type": { - "default": "application/json;charset=utf8", - "type": "string" - }, - "delay": { - "default": 0, - "type": "integer" - }, - "response_example": { - "type": "string" - }, - "response_schema": { - "type": "object" - }, - "response_status": { - "default": 200, - "minimum": 100, - "type": "integer" - }, - "with_mock_header": { - "default": true, - "type": "boolean" - } - }, - "type": "object" - }, - "version": 0.1 - }, - "node-status": { - "priority": 1000, - "schema": { - "$comment": "this is a mark for our injected plugin schema", - "properties": { - "_meta": { - "properties": { - "disable": { - "type": "boolean" - }, - "error_response": { - "oneOf": [ - { - "type": "string" - }, - { - "type": "object" - } - ] - }, - "filter": { - "description": "filter determines whether the plugin needs to be executed at runtime", - "type": "array" - }, - "priority": { - "description": "priority of plugins by customized order", - "type": "integer" - } - }, - "type": "object" - } - }, - "type": "object" - }, - "scope": "global", - "version": 0.1 - }, - "opa": { - "priority": 2001, - "schema": { - "$comment": "this is a mark for our injected plugin schema", - "properties": { - "_meta": { - "properties": { - "disable": { - "type": "boolean" - }, - "error_response": { - "oneOf": [ - { - "type": "string" - }, - { - "type": "object" - } - ] - }, - "filter": { - "description": "filter determines whether the plugin needs to be executed at runtime", - "type": "array" - }, - "priority": { - "description": "priority of plugins by customized order", - "type": "integer" - } - }, - "type": "object" - }, - "host": { - "type": "string" - }, - "keepalive": { - "default": true, - "type": "boolean" - }, - "keepalive_pool": { - "default": 5, - "minimum": 1, - "type": "integer" - }, - "keepalive_timeout": { - "default": 60000, - "minimum": 1000, - "type": "integer" - }, - "policy": { - "type": "string" - }, - "ssl_verify": { - "default": true, - "type": "boolean" - }, - "timeout": { - "default": 3000, - "description": "timeout in milliseconds", - "maximum": 60000, - "minimum": 1, - "type": "integer" - }, - "with_consumer": { - "default": false, - "type": "boolean" - }, - "with_route": { - "default": false, - "type": "boolean" - }, - "with_service": { - "default": false, - "type": "boolean" - } - }, - "required": [ - "host", - "policy" - ], - "type": "object" - }, - "version": 0.1 - }, - "openfunction": { - "priority": -1902, - "schema": { - "$comment": "this is a mark for our injected plugin schema", - "properties": { - "_meta": { - "properties": { - "disable": { - "type": "boolean" - }, - "error_response": { - "oneOf": [ - { - "type": "string" - }, - { - "type": "object" - } - ] - }, - "filter": { - "description": "filter determines whether the plugin needs to be executed at runtime", - "type": "array" - }, - "priority": { - "description": "priority of plugins by customized order", - "type": "integer" - } - }, - "type": "object" - }, - "authorization": { - "service_token": { - "type": "string" - } - }, - "function_uri": { - "type": "string" - }, - "keepalive": { - "default": true, - "type": "boolean" - }, - "keepalive_pool": { - "default": 5, - "minimum": 1, - "type": "integer" - }, - "keepalive_timeout": { - "default": 60000, - "minimum": 1000, - "type": "integer" - }, - "ssl_verify": { - "default": true, - "type": "boolean" - }, - "timeout": { - "default": 3000, - "minimum": 100, - "type": "integer" - } - }, - "required": [ - "function_uri" - ], - "type": "object" - }, - "version": 0.1 - }, - "openid-connect": { - "priority": 2599, - "schema": { - "$comment": "this is a mark for our injected plugin schema", - "properties": { - "_meta": { - "properties": { - "disable": { - "type": "boolean" - }, - "error_response": { - "oneOf": [ - { - "type": "string" - }, - { - "type": "object" - } - ] - }, - "filter": { - "description": "filter determines whether the plugin needs to be executed at runtime", - "type": "array" - }, - "priority": { - "description": "priority of plugins by customized order", - "type": "integer" - } - }, - "type": "object" - }, - "access_token_in_authorization_header": { - "default": false, - "description": "Whether the access token should be added in the Authorization header as opposed to the X-Access-Token header.", - "type": "boolean" - }, - "bearer_only": { - "default": false, - "type": "boolean" - }, - "client_id": { - "type": "string" - }, - "client_secret": { - "type": "string" - }, - "discovery": { - "type": "string" - }, - "introspection_endpoint": { - "type": "string" - }, - "introspection_endpoint_auth_method": { - "default": "client_secret_basic", - "type": "string" - }, - "logout_path": { - "default": "/logout", - "type": "string" - }, - "post_logout_redirect_uri": { - "description": "the URI will be redirect when request logout_path", - "type": "string" - }, - "public_key": { - "type": "string" - }, - "realm": { - "default": "apisix", - "type": "string" - }, - "redirect_uri": { - "description": "use ngx.var.request_uri if not configured", - "type": "string" - }, - "scope": { - "default": "openid", - "type": "string" - }, - "session": { - "additionalProperties": false, - "properties": { - "secret": { - "description": "the key used for the encrypt and HMAC calculation", - "minLength": 16, - "type": "string" - } - }, - "required": [ - "secret" - ], - "type": "object" - }, - "set_access_token_header": { - "default": true, - "description": "Whether the access token should be added as a header to the request for downstream", - "type": "boolean" - }, - "set_id_token_header": { - "default": true, - "description": "Whether the ID token should be added in the X-ID-Token header to the request for downstream.", - "type": "boolean" - }, - "set_refresh_token_header": { - "default": false, - "description": "Whether the refresh token should be added in the X-Refresh-Token header to the request for downstream.", - "type": "boolean" - }, - "set_userinfo_header": { - "default": true, - "description": "Whether the user info token should be added in the X-Userinfo header to the request for downstream.", - "type": "boolean" - }, - "ssl_verify": { - "default": false, - "type": "boolean" - }, - "timeout": { - "default": 3, - "description": "timeout in seconds", - "minimum": 1, - "type": "integer" - }, - "token_signing_alg_values_expected": { - "type": "string" - }, - "use_pkce": { - "default": false, - "description": "when set to true the PKEC(Proof Key for Code Exchange) will be used.", - "type": "boolean" - } - }, - "required": [ - "client_id", - "client_secret", - "discovery" - ], - "type": "object" - }, - "version": 0.2 - }, - "opentelemetry": { - "priority": 12009, - "schema": { - "$comment": "this is a mark for our injected plugin schema", - "properties": { - "_meta": { - "properties": { - "disable": { - "type": "boolean" - }, - "error_response": { - "oneOf": [ - { - "type": "string" - }, - { - "type": "object" - } - ] - }, - "filter": { - "description": "filter determines whether the plugin needs to be executed at runtime", - "type": "array" - }, - "priority": { - "description": "priority of plugins by customized order", - "type": "integer" - } - }, - "type": "object" - }, - "additional_attributes": { - "items": { - "minLength": 1, - "type": "string" - }, - "type": "array" - }, - "additional_header_prefix_attributes": { - "items": { - "minLength": 1, - "type": "string" - }, - "type": "array" - }, - "sampler": { - "default": { - "name": "always_off", - "options": { - "fraction": 0, - "root": { - "name": "always_off" - } - } - }, - "properties": { - "name": { - "default": "always_off", - "enum": [ - "always_off", - "always_on", - "parent_base", - "trace_id_ratio" - ], - "title": "sampling strategy", - "type": "string" - }, - "options": { - "default": { - "fraction": 0, - "root": { - "name": "always_off" - } - }, - "properties": { - "fraction": { - "default": 0, - "title": "trace_id_ratio fraction", - "type": "number" - }, - "root": { - "default": { - "name": "always_off", - "options": { - "fraction": 0 - } - }, - "properties": { - "name": { - "default": "always_off", - "enum": [ - "always_off", - "always_on", - "trace_id_ratio" - ], - "title": "sampling strategy", - "type": "string" - }, - "options": { - "default": { - "fraction": 0 - }, - "properties": { - "fraction": { - "default": 0, - "title": "trace_id_ratio fraction parameter", - "type": "number" - } - }, - "type": "object" - } - }, - "title": "parent_base root sampler", - "type": "object" - } - }, - "type": "object" - } - }, - "type": "object" - } - }, - "type": "object" - }, - "version": 0.1 - }, - "openwhisk": { - "priority": -1901, - "schema": { - "$comment": "this is a mark for our injected plugin schema", - "properties": { - "_meta": { - "properties": { - "disable": { - "type": "boolean" - }, - "error_response": { - "oneOf": [ - { - "type": "string" - }, - { - "type": "object" - } - ] - }, - "filter": { - "description": "filter determines whether the plugin needs to be executed at runtime", - "type": "array" - }, - "priority": { - "description": "priority of plugins by customized order", - "type": "integer" - } - }, - "type": "object" - }, - "action": { - "maxLength": 256, - "pattern": "\\A([\\w]|[\\w][\\w@ .-]*[\\w@.-]+)\\z", - "type": "string" - }, - "api_host": { - "type": "string" - }, - "keepalive": { - "default": true, - "type": "boolean" - }, - "keepalive_pool": { - "default": 5, - "minimum": 1, - "type": "integer" - }, - "keepalive_timeout": { - "default": 60000, - "minimum": 1000, - "type": "integer" - }, - "namespace": { - "maxLength": 256, - "pattern": "\\A([\\w]|[\\w][\\w@ .-]*[\\w@.-]+)\\z", - "type": "string" - }, - "package": { - "maxLength": 256, - "pattern": "\\A([\\w]|[\\w][\\w@ .-]*[\\w@.-]+)\\z", - "type": "string" - }, - "result": { - "default": true, - "type": "boolean" - }, - "service_token": { - "type": "string" - }, - "ssl_verify": { - "default": true, - "type": "boolean" - }, - "timeout": { - "default": 3000, - "description": "timeout in milliseconds", - "maximum": 60000, - "minimum": 1, - "type": "integer" - } - }, - "required": [ - "action", - "api_host", - "namespace", - "service_token" - ], - "type": "object" - }, - "version": 0.1 - }, - "prometheus": { - "priority": 500, - "schema": { - "$comment": "this is a mark for our injected plugin schema", - "properties": { - "_meta": { - "properties": { - "disable": { - "type": "boolean" - }, - "error_response": { - "oneOf": [ - { - "type": "string" - }, - { - "type": "object" - } - ] - }, - "filter": { - "description": "filter determines whether the plugin needs to be executed at runtime", - "type": "array" - }, - "priority": { - "description": "priority of plugins by customized order", - "type": "integer" - } - }, - "type": "object" - }, - "prefer_name": { - "default": false, - "type": "boolean" - } - }, - "type": "object" - }, - "version": 0.2 - }, - "proxy-cache": { - "priority": 1009, - "schema": { - "$comment": "this is a mark for our injected plugin schema", - "properties": { - "_meta": { - "properties": { - "disable": { - "type": "boolean" - }, - "error_response": { - "oneOf": [ - { - "type": "string" - }, - { - "type": "object" - } - ] - }, - "filter": { - "description": "filter determines whether the plugin needs to be executed at runtime", - "type": "array" - }, - "priority": { - "description": "priority of plugins by customized order", - "type": "integer" - } - }, - "type": "object" - }, - "cache_bypass": { - "items": { - "pattern": "(^[^\\$].+$|^\\$[0-9a-zA-Z_]+$)", - "type": "string" - }, - "minItems": 1, - "type": "array" - }, - "cache_control": { - "default": false, - "type": "boolean" - }, - "cache_http_status": { - "default": [ - 200, - 301, - 404 - ], - "items": { - "description": "http response status", - "maximum": 599, - "minimum": 200, - "type": "integer" - }, - "minItems": 1, - "type": "array", - "uniqueItems": true - }, - "cache_key": { - "default": [ - "$host", - "$request_uri" - ], - "items": { - "description": "a key for caching", - "pattern": "(^[^\\$].+$|^\\$[0-9a-zA-Z_]+$)", - "type": "string" - }, - "minItems": 1, - "type": "array" - }, - "cache_method": { - "default": [ - "GET", - "HEAD" - ], - "items": { - "description": "supported http method", - "enum": [ - "GET", - "HEAD", - "POST" - ], - "type": "string" - }, - "minItems": 1, - "type": "array", - "uniqueItems": true - }, - "cache_strategy": { - "default": "disk", - "enum": [ - "disk", - "memory" - ], - "type": "string" - }, - "cache_ttl": { - "default": 300, - "minimum": 1, - "type": "integer" - }, - "cache_zone": { - "default": "disk_cache_one", - "maxLength": 100, - "minLength": 1, - "type": "string" - }, - "hide_cache_headers": { - "default": false, - "type": "boolean" - }, - "no_cache": { - "items": { - "pattern": "(^[^\\$].+$|^\\$[0-9a-zA-Z_]+$)", - "type": "string" - }, - "minItems": 1, - "type": "array" - } - }, - "type": "object" - }, - "version": 0.2 - }, - "proxy-control": { - "priority": 21990, - "schema": { - "$comment": "this is a mark for our injected plugin schema", - "properties": { - "_meta": { - "properties": { - "disable": { - "type": "boolean" - }, - "error_response": { - "oneOf": [ - { - "type": "string" - }, - { - "type": "object" - } - ] - }, - "filter": { - "description": "filter determines whether the plugin needs to be executed at runtime", - "type": "array" - }, - "priority": { - "description": "priority of plugins by customized order", - "type": "integer" - } - }, - "type": "object" - }, - "request_buffering": { - "default": true, - "type": "boolean" - } - }, - "type": "object" - }, - "version": 0.1 - }, - "proxy-mirror": { - "priority": 1010, - "schema": { - "$comment": "this is a mark for our injected plugin schema", - "properties": { - "_meta": { - "properties": { - "disable": { - "type": "boolean" - }, - "error_response": { - "oneOf": [ - { - "type": "string" - }, - { - "type": "object" - } - ] - }, - "filter": { - "description": "filter determines whether the plugin needs to be executed at runtime", - "type": "array" - }, - "priority": { - "description": "priority of plugins by customized order", - "type": "integer" - } - }, - "type": "object" - }, - "host": { - "pattern": "^http(s)?:\\/\\/([\\da-zA-Z.-]+|\\[[\\da-fA-F:]+\\])(:\\d+)?$", - "type": "string" - }, - "path": { - "pattern": "^/[^?&]+$", - "type": "string" - }, - "sample_ratio": { - "default": 1, - "maximum": 1, - "minimum": 0.00001, - "type": "number" - } - }, - "required": [ - "host" - ], - "type": "object" - }, - "version": 0.1 - }, - "proxy-rewrite": { - "priority": 1008, - "schema": { - "$comment": "this is a mark for our injected plugin schema", - "minProperties": 1, - "properties": { - "_meta": { - "properties": { - "disable": { - "type": "boolean" - }, - "error_response": { - "oneOf": [ - { - "type": "string" - }, - { - "type": "object" - } - ] - }, - "filter": { - "description": "filter determines whether the plugin needs to be executed at runtime", - "type": "array" - }, - "priority": { - "description": "priority of plugins by customized order", - "type": "integer" - } - }, - "type": "object" - }, - "headers": { - "description": "new headers for request", - "minProperties": 1, - "type": "object" - }, - "host": { - "description": "new host for upstream", - "pattern": "^[0-9a-zA-Z-.]+(:\\d{1,5})?$", - "type": "string" - }, - "method": { - "description": "proxy route method", - "enum": [ - "COPY", - "DELETE", - "GET", - "HEAD", - "LOCK", - "MKCOL", - "MOVE", - "OPTIONS", - "PATCH", - "POST", - "PROPFIND", - "PUT", - "TRACE", - "UNLOCK" - ], - "type": "string" - }, - "regex_uri": { - "description": "new uri that substitute from client uri for upstream, lower priority than uri property", - "items": { - "description": "regex uri", - "type": "string" - }, - "maxItems": 2, - "minItems": 2, - "type": "array" - }, - "uri": { - "description": "new uri for upstream", - "maxLength": 4096, - "minLength": 1, - "pattern": "^\\/.*", - "type": "string" - }, - "use_real_request_uri_unsafe": { - "default": false, - "description": "use real_request_uri instead, THIS IS VERY UNSAFE.", - "type": "boolean" - } - }, - "type": "object" - }, - "version": 0.1 - }, - "public-api": { - "priority": 501, - "schema": { - "$comment": "this is a mark for our injected plugin schema", - "properties": { - "_meta": { - "properties": { - "disable": { - "type": "boolean" - }, - "error_response": { - "oneOf": [ - { - "type": "string" - }, - { - "type": "object" - } - ] - }, - "filter": { - "description": "filter determines whether the plugin needs to be executed at runtime", - "type": "array" - }, - "priority": { - "description": "priority of plugins by customized order", - "type": "integer" - } - }, - "type": "object" - }, - "uri": { - "type": "string" - } - }, - "type": "object" - }, - "version": 0.1 - }, - "real-ip": { - "priority": 23000, - "schema": { - "$comment": "this is a mark for our injected plugin schema", - "properties": { - "_meta": { - "properties": { - "disable": { - "type": "boolean" - }, - "error_response": { - "oneOf": [ - { - "type": "string" - }, - { - "type": "object" - } - ] - }, - "filter": { - "description": "filter determines whether the plugin needs to be executed at runtime", - "type": "array" - }, - "priority": { - "description": "priority of plugins by customized order", - "type": "integer" - } - }, - "type": "object" - }, - "recursive": { - "default": false, - "type": "boolean" - }, - "source": { - "minLength": 1, - "type": "string" - }, - "trusted_addresses": { - "items": { - "anyOf": [ - { - "format": "ipv4", - "title": "IPv4", - "type": "string" - }, - { - "pattern": "^([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])\\.([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])\\.([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])\\.([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])/([12]?[0-9]|3[0-2])$", - "title": "IPv4/CIDR", - "type": "string" - }, - { - "format": "ipv6", - "title": "IPv6", - "type": "string" - }, - { - "pattern": "^([a-fA-F0-9]{0,4}:){1,8}(:[a-fA-F0-9]{0,4}){0,8}([a-fA-F0-9]{0,4})?/[0-9]{1,3}$", - "title": "IPv6/CIDR", - "type": "string" - } - ] - }, - "minItems": 1, - "type": "array" - } - }, - "required": [ - "source" - ], - "type": "object" - }, - "version": 0.1 - }, - "redirect": { - "priority": 900, - "schema": { - "$comment": "this is a mark for our injected plugin schema", - "oneOf": [ - { - "required": [ - "uri" - ] - }, - { - "required": [ - "regex_uri" - ] - }, - { - "required": [ - "http_to_https" - ] - } - ], - "properties": { - "_meta": { - "properties": { - "disable": { - "type": "boolean" - }, - "error_response": { - "oneOf": [ - { - "type": "string" - }, - { - "type": "object" - } - ] - }, - "filter": { - "description": "filter determines whether the plugin needs to be executed at runtime", - "type": "array" - }, - "priority": { - "description": "priority of plugins by customized order", - "type": "integer" - } - }, - "type": "object" - }, - "append_query_string": { - "default": false, - "type": "boolean" - }, - "encode_uri": { - "default": false, - "type": "boolean" - }, - "http_to_https": { - "type": "boolean" - }, - "regex_uri": { - "description": "params for generating new uri that substitute from client uri, first param is regular expression, the second one is uri template", - "items": { - "description": "regex uri", - "type": "string" - }, - "maxItems": 2, - "minItems": 2, - "type": "array" - }, - "ret_code": { - "default": 302, - "minimum": 200, - "type": "integer" - }, - "uri": { - "minLength": 2, - "pattern": "(\\\\\\$[0-9a-zA-Z_]+)|\\$\\{([0-9a-zA-Z_]+)\\}|\\$([0-9a-zA-Z_]+)|(\\$|[^$\\\\]+)", - "type": "string" - } - }, - "type": "object" - }, - "version": 0.1 - }, - "referer-restriction": { - "priority": 2990, - "schema": { - "$comment": "this is a mark for our injected plugin schema", - "oneOf": [ - { - "required": [ - "whitelist" - ] - }, - { - "required": [ - "blacklist" - ] - } - ], - "properties": { - "_meta": { - "properties": { - "disable": { - "type": "boolean" - }, - "error_response": { - "oneOf": [ - { - "type": "string" - }, - { - "type": "object" - } - ] - }, - "filter": { - "description": "filter determines whether the plugin needs to be executed at runtime", - "type": "array" - }, - "priority": { - "description": "priority of plugins by customized order", - "type": "integer" - } - }, - "type": "object" - }, - "blacklist": { - "items": { - "pattern": "^\\*?[0-9a-zA-Z-._\\[\\]:]+$", - "type": "string" - }, - "minItems": 1, - "type": "array" - }, - "bypass_missing": { - "default": false, - "type": "boolean" - }, - "message": { - "default": "Your referer host is not allowed", - "maxLength": 1024, - "minLength": 1, - "type": "string" - }, - "whitelist": { - "items": { - "pattern": "^\\*?[0-9a-zA-Z-._\\[\\]:]+$", - "type": "string" - }, - "minItems": 1, - "type": "array" - } - }, - "type": "object" - }, - "version": 0.1 - }, - "request-id": { - "priority": 12015, - "schema": { - "$comment": "this is a mark for our injected plugin schema", - "properties": { - "_meta": { - "properties": { - "disable": { - "type": "boolean" - }, - "error_response": { - "oneOf": [ - { - "type": "string" - }, - { - "type": "object" - } - ] - }, - "filter": { - "description": "filter determines whether the plugin needs to be executed at runtime", - "type": "array" - }, - "priority": { - "description": "priority of plugins by customized order", - "type": "integer" - } - }, - "type": "object" - }, - "algorithm": { - "default": "uuid", - "enum": [ - "nanoid", - "snowflake", - "uuid" - ], - "type": "string" - }, - "header_name": { - "default": "X-Request-Id", - "type": "string" - }, - "include_in_response": { - "default": true, - "type": "boolean" - } - }, - "type": "object" - }, - "version": 0.1 - }, - "request-validation": { - "priority": 2800, - "schema": { - "$comment": "this is a mark for our injected plugin schema", - "anyOf": [ - { - "required": [ - "header_schema" - ] - }, - { - "required": [ - "body_schema" - ] - } - ], - "properties": { - "_meta": { - "properties": { - "disable": { - "type": "boolean" - }, - "error_response": { - "oneOf": [ - { - "type": "string" - }, - { - "type": "object" - } - ] - }, - "filter": { - "description": "filter determines whether the plugin needs to be executed at runtime", - "type": "array" - }, - "priority": { - "description": "priority of plugins by customized order", - "type": "integer" - } - }, - "type": "object" - }, - "body_schema": { - "type": "object" - }, - "header_schema": { - "type": "object" - }, - "rejected_code": { - "default": 400, - "maximum": 599, - "minimum": 200, - "type": "integer" - }, - "rejected_msg": { - "maxLength": 256, - "minLength": 1, - "type": "string" - } - }, - "type": "object" - }, - "type": "validation", - "version": 0.1 - }, - "response-rewrite": { - "priority": 899, - "schema": { - "$comment": "this is a mark for our injected plugin schema", - "dependencies": { - "body": { - "not": { - "required": [ - "filters" - ] - } - }, - "filters": { - "not": { - "required": [ - "body" - ] - } - } - }, - "properties": { - "_meta": { - "properties": { - "disable": { - "type": "boolean" - }, - "error_response": { - "oneOf": [ - { - "type": "string" - }, - { - "type": "object" - } - ] - }, - "filter": { - "description": "filter determines whether the plugin needs to be executed at runtime", - "type": "array" - }, - "priority": { - "description": "priority of plugins by customized order", - "type": "integer" - } - }, - "type": "object" - }, - "body": { - "description": "new body for response", - "type": "string" - }, - "body_base64": { - "default": false, - "description": "whether new body for response need base64 decode before return", - "type": "boolean" - }, - "filters": { - "description": "a group of filters that modify response bodyby replacing one specified string by another", - "items": { - "description": "filter that modifies response body", - "properties": { - "options": { - "default": "jo", - "description": "regex options", - "type": "string" - }, - "regex": { - "description": "match pattern on response body", - "minLength": 1, - "type": "string" - }, - "replace": { - "description": "regex substitution content", - "type": "string" - }, - "scope": { - "default": "once", - "description": "regex substitution range", - "enum": [ - "global", - "once" - ], - "type": "string" - } - }, - "required": [ - "regex", - "replace" - ], - "type": "object" - }, - "minItems": 1, - "type": "array" - }, - "headers": { - "anyOf": [ - { - "minProperties": 1, - "patternProperties": { - "^[^:]+$": { - "oneOf": [ - { - "type": "string" - }, - { - "type": "number" - } - ] - } - }, - "type": "object" - }, - { - "properties": { - "add": { - "items": { - "pattern": "^[^:]+:[^:]+[^/]$", - "type": "string" - }, - "minItems": 1, - "type": "array" - }, - "remove": { - "items": { - "pattern": "^[^:]+$", - "type": "string" - }, - "minItems": 1, - "type": "array" - }, - "set": { - "minProperties": 1, - "patternProperties": { - "^[^:]+$": { - "oneOf": [ - { - "type": "string" - }, - { - "type": "number" - } - ] - } - }, - "type": "object" - } - } - } - ], - "description": "new headers for response" - }, - "status_code": { - "description": "new status code for response", - "maximum": 598, - "minimum": 200, - "type": "integer" - }, - "vars": { - "type": "array" - } - }, - "type": "object" - }, - "version": 0.1 - }, - "rocketmq-logger": { - "metadata_schema": { - "properties": { - "log_format": { - "default": { - "@timestamp": "$time_iso8601", - "client_ip": "$remote_addr", - "host": "$host" - }, - "type": "object" - } - }, - "type": "object" - }, - "priority": 402, - "schema": { - "$comment": "this is a mark for our injected plugin schema", - "properties": { - "_meta": { - "properties": { - "disable": { - "type": "boolean" - }, - "error_response": { - "oneOf": [ - { - "type": "string" - }, - { - "type": "object" - } - ] - }, - "filter": { - "description": "filter determines whether the plugin needs to be executed at runtime", - "type": "array" - }, - "priority": { - "description": "priority of plugins by customized order", - "type": "integer" - } - }, - "type": "object" - }, - "access_key": { - "default": "", - "type": "string" - }, - "batch_max_size": { - "default": 1000, - "minimum": 1, - "type": "integer" - }, - "buffer_duration": { - "default": 60, - "minimum": 1, - "type": "integer" - }, - "inactive_timeout": { - "default": 5, - "minimum": 1, - "type": "integer" - }, - "include_req_body": { - "default": false, - "type": "boolean" - }, - "include_req_body_expr": { - "items": { - "type": "array" - }, - "minItems": 1, - "type": "array" - }, - "include_resp_body": { - "default": false, - "type": "boolean" - }, - "include_resp_body_expr": { - "items": { - "type": "array" - }, - "minItems": 1, - "type": "array" - }, - "key": { - "type": "string" - }, - "max_retry_count": { - "default": 0, - "minimum": 0, - "type": "integer" - }, - "meta_format": { - "default": "default", - "enum": [ - "default", - "origin" - ], - "type": "string" - }, - "name": { - "default": "rocketmq logger", - "type": "string" - }, - "nameserver_list": { - "items": { - "type": "string" - }, - "minItems": 1, - "type": "array" - }, - "retry_delay": { - "default": 1, - "minimum": 0, - "type": "integer" - }, - "secret_key": { - "default": "", - "type": "string" - }, - "tag": { - "type": "string" - }, - "timeout": { - "default": 3, - "minimum": 1, - "type": "integer" - }, - "topic": { - "type": "string" - }, - "use_tls": { - "default": false, - "type": "boolean" - } - }, - "required": [ - "nameserver_list", - "topic" - ], - "type": "object" - }, - "version": 0.1 - }, - "server-info": { - "priority": 990, - "schema": { - "$comment": "this is a mark for our injected plugin schema", - "properties": { - "_meta": { - "properties": { - "disable": { - "type": "boolean" - }, - "error_response": { - "oneOf": [ - { - "type": "string" - }, - { - "type": "object" - } - ] - }, - "filter": { - "description": "filter determines whether the plugin needs to be executed at runtime", - "type": "array" - }, - "priority": { - "description": "priority of plugins by customized order", - "type": "integer" - } - }, - "type": "object" - } - }, - "type": "object" - }, - "scope": "global", - "version": 0.1 - }, - "serverless-post-function": { - "priority": -2000, - "schema": { - "$comment": "this is a mark for our injected plugin schema", - "properties": { - "_meta": { - "properties": { - "disable": { - "type": "boolean" - }, - "error_response": { - "oneOf": [ - { - "type": "string" - }, - { - "type": "object" - } - ] - }, - "filter": { - "description": "filter determines whether the plugin needs to be executed at runtime", - "type": "array" - }, - "priority": { - "description": "priority of plugins by customized order", - "type": "integer" - } - }, - "type": "object" - }, - "functions": { - "items": { - "type": "string" - }, - "minItems": 1, - "type": "array" - }, - "phase": { - "default": "access", - "enum": [ - "access", - "before_proxy", - "body_filter", - "header_filter", - "log", - "rewrite" - ], - "type": "string" - } - }, - "required": [ - "functions" - ], - "type": "object" - }, - "version": 0.1 - }, - "serverless-pre-function": { - "priority": 10000, - "schema": { - "$comment": "this is a mark for our injected plugin schema", - "properties": { - "_meta": { - "properties": { - "disable": { - "type": "boolean" - }, - "error_response": { - "oneOf": [ - { - "type": "string" - }, - { - "type": "object" - } - ] - }, - "filter": { - "description": "filter determines whether the plugin needs to be executed at runtime", - "type": "array" - }, - "priority": { - "description": "priority of plugins by customized order", - "type": "integer" - } - }, - "type": "object" - }, - "functions": { - "items": { - "type": "string" - }, - "minItems": 1, - "type": "array" - }, - "phase": { - "default": "access", - "enum": [ - "access", - "before_proxy", - "body_filter", - "header_filter", - "log", - "rewrite" - ], - "type": "string" - } - }, - "required": [ - "functions" - ], - "type": "object" - }, - "version": 0.1 - }, - "skywalking": { - "priority": 12010, - "schema": { - "$comment": "this is a mark for our injected plugin schema", - "properties": { - "_meta": { - "properties": { - "disable": { - "type": "boolean" - }, - "error_response": { - "oneOf": [ - { - "type": "string" - }, - { - "type": "object" - } - ] - }, - "filter": { - "description": "filter determines whether the plugin needs to be executed at runtime", - "type": "array" - }, - "priority": { - "description": "priority of plugins by customized order", - "type": "integer" - } - }, - "type": "object" - }, - "sample_ratio": { - "default": 1, - "maximum": 1, - "minimum": 0.00001, - "type": "number" - } - }, - "type": "object" - }, - "version": 0.1 - }, - "skywalking-logger": { - "metadata_schema": { - "properties": { - "log_format": { - "default": { - "@timestamp": "$time_iso8601", - "client_ip": "$remote_addr", - "host": "$host" - }, - "type": "object" - } - }, - "type": "object" - }, - "priority": 408, - "schema": { - "$comment": "this is a mark for our injected plugin schema", - "properties": { - "_meta": { - "properties": { - "disable": { - "type": "boolean" - }, - "error_response": { - "oneOf": [ - { - "type": "string" - }, - { - "type": "object" - } - ] - }, - "filter": { - "description": "filter determines whether the plugin needs to be executed at runtime", - "type": "array" - }, - "priority": { - "description": "priority of plugins by customized order", - "type": "integer" - } - }, - "type": "object" - }, - "batch_max_size": { - "default": 1000, - "minimum": 1, - "type": "integer" - }, - "buffer_duration": { - "default": 60, - "minimum": 1, - "type": "integer" - }, - "endpoint_addr": { - "pattern": "^[^\\/]+:\\/\\/([\\da-zA-Z.-]+|\\[[\\da-fA-F:]+\\])(:\\d+)?", - "type": "string" - }, - "inactive_timeout": { - "default": 5, - "minimum": 1, - "type": "integer" - }, - "include_req_body": { - "default": false, - "type": "boolean" - }, - "max_retry_count": { - "default": 0, - "minimum": 0, - "type": "integer" - }, - "name": { - "default": "skywalking logger", - "type": "string" - }, - "retry_delay": { - "default": 1, - "minimum": 0, - "type": "integer" - }, - "service_instance_name": { - "default": "APISIX Instance Name", - "type": "string" - }, - "service_name": { - "default": "APISIX", - "type": "string" - }, - "timeout": { - "default": 3, - "minimum": 1, - "type": "integer" - } - }, - "required": [ - "endpoint_addr" - ], - "type": "object" - }, - "version": 0.1 - }, - "sls-logger": { - "priority": 406, - "schema": { - "$comment": "this is a mark for our injected plugin schema", - "properties": { - "_meta": { - "properties": { - "disable": { - "type": "boolean" - }, - "error_response": { - "oneOf": [ - { - "type": "string" - }, - { - "type": "object" - } - ] - }, - "filter": { - "description": "filter determines whether the plugin needs to be executed at runtime", - "type": "array" - }, - "priority": { - "description": "priority of plugins by customized order", - "type": "integer" - } - }, - "type": "object" - }, - "access_key_id": { - "type": "string" - }, - "access_key_secret": { - "type": "string" - }, - "batch_max_size": { - "default": 1000, - "minimum": 1, - "type": "integer" - }, - "buffer_duration": { - "default": 60, - "minimum": 1, - "type": "integer" - }, - "host": { - "type": "string" - }, - "inactive_timeout": { - "default": 5, - "minimum": 1, - "type": "integer" - }, - "include_req_body": { - "default": false, - "type": "boolean" - }, - "logstore": { - "type": "string" - }, - "max_retry_count": { - "default": 0, - "minimum": 0, - "type": "integer" - }, - "name": { - "default": "sls-logger", - "type": "string" - }, - "port": { - "type": "integer" - }, - "project": { - "type": "string" - }, - "retry_delay": { - "default": 1, - "minimum": 0, - "type": "integer" - }, - "timeout": { - "default": 5000, - "minimum": 1, - "type": "integer" - } - }, - "required": [ - "access_key_id", - "access_key_secret", - "host", - "logstore", - "port", - "project" - ], - "type": "object" - }, - "version": 0.1 - }, - "splunk-hec-logging": { - "priority": 409, - "schema": { - "$comment": "this is a mark for our injected plugin schema", - "properties": { - "_meta": { - "properties": { - "disable": { - "type": "boolean" - }, - "error_response": { - "oneOf": [ - { - "type": "string" - }, - { - "type": "object" - } - ] - }, - "filter": { - "description": "filter determines whether the plugin needs to be executed at runtime", - "type": "array" - }, - "priority": { - "description": "priority of plugins by customized order", - "type": "integer" - } - }, - "type": "object" - }, - "batch_max_size": { - "default": 1000, - "minimum": 1, - "type": "integer" - }, - "buffer_duration": { - "default": 60, - "minimum": 1, - "type": "integer" - }, - "endpoint": { - "properties": { - "channel": { - "type": "string" - }, - "timeout": { - "default": 10, - "minimum": 1, - "type": "integer" - }, - "token": { - "type": "string" - }, - "uri": { - "pattern": "^[^\\/]+:\\/\\/([\\da-zA-Z.-]+|\\[[\\da-fA-F:]+\\])(:\\d+)?", - "type": "string" - } - }, - "required": [ - "token", - "uri" - ], - "type": "object" - }, - "inactive_timeout": { - "default": 5, - "minimum": 1, - "type": "integer" - }, - "max_retry_count": { - "default": 0, - "minimum": 0, - "type": "integer" - }, - "name": { - "default": "splunk-hec-logging", - "type": "string" - }, - "retry_delay": { - "default": 1, - "minimum": 0, - "type": "integer" - }, - "ssl_verify": { - "default": true, - "type": "boolean" - } - }, - "required": [ - "endpoint" - ], - "type": "object" - }, - "version": 0.1 - }, - "syslog": { - "priority": 401, - "schema": { - "$comment": "this is a mark for our injected plugin schema", - "properties": { - "_meta": { - "properties": { - "disable": { - "type": "boolean" - }, - "error_response": { - "oneOf": [ - { - "type": "string" - }, - { - "type": "object" - } - ] - }, - "filter": { - "description": "filter determines whether the plugin needs to be executed at runtime", - "type": "array" - }, - "priority": { - "description": "priority of plugins by customized order", - "type": "integer" - } - }, - "type": "object" - }, - "batch_max_size": { - "default": 1000, - "minimum": 1, - "type": "integer" - }, - "buffer_duration": { - "default": 60, - "minimum": 1, - "type": "integer" - }, - "drop_limit": { - "default": 1048576, - "type": "integer" - }, - "flush_limit": { - "default": 4096, - "minimum": 1, - "type": "integer" - }, - "host": { - "type": "string" - }, - "inactive_timeout": { - "default": 5, - "minimum": 1, - "type": "integer" - }, - "include_req_body": { - "default": false, - "type": "boolean" - }, - "max_retry_count": { - "default": 0, - "minimum": 0, - "type": "integer" - }, - "name": { - "default": "http sys logger", - "type": "string" - }, - "pool_size": { - "default": 5, - "minimum": 5, - "type": "integer" - }, - "port": { - "type": "integer" - }, - "retry_delay": { - "default": 1, - "minimum": 0, - "type": "integer" - }, - "sock_type": { - "default": "tcp", - "enum": [ - "tcp", - "udp" - ], - "type": "string" - }, - "timeout": { - "default": 3000, - "minimum": 1, - "type": "integer" - }, - "tls": { - "default": false, - "type": "boolean" - } - }, - "required": [ - "host", - "port" - ], - "type": "object" - }, - "version": 0.1 - }, - "tcp-logger": { - "priority": 405, - "schema": { - "$comment": "this is a mark for our injected plugin schema", - "properties": { - "_meta": { - "properties": { - "disable": { - "type": "boolean" - }, - "error_response": { - "oneOf": [ - { - "type": "string" - }, - { - "type": "object" - } - ] - }, - "filter": { - "description": "filter determines whether the plugin needs to be executed at runtime", - "type": "array" - }, - "priority": { - "description": "priority of plugins by customized order", - "type": "integer" - } - }, - "type": "object" - }, - "batch_max_size": { - "default": 1000, - "minimum": 1, - "type": "integer" - }, - "buffer_duration": { - "default": 60, - "minimum": 1, - "type": "integer" - }, - "host": { - "type": "string" - }, - "inactive_timeout": { - "default": 5, - "minimum": 1, - "type": "integer" - }, - "include_req_body": { - "default": false, - "type": "boolean" - }, - "max_retry_count": { - "default": 0, - "minimum": 0, - "type": "integer" - }, - "name": { - "default": "tcp logger", - "type": "string" - }, - "port": { - "minimum": 0, - "type": "integer" - }, - "retry_delay": { - "default": 1, - "minimum": 0, - "type": "integer" - }, - "timeout": { - "default": 1000, - "minimum": 1, - "type": "integer" - }, - "tls": { - "default": false, - "type": "boolean" - }, - "tls_options": { - "type": "string" - } - }, - "required": [ - "host", - "port" - ], - "type": "object" - }, - "version": 0.1 - }, - "tencent-cloud-cls": { - "metadata_schema": { - "properties": { - "log_format": { - "default": { - "@timestamp": "$time_iso8601", - "client_ip": "$remote_addr", - "host": "$host" - }, - "type": "object" - } - }, - "type": "object" - }, - "priority": 397, - "schema": { - "$comment": "this is a mark for our injected plugin schema", - "properties": { - "_meta": { - "properties": { - "disable": { - "type": "boolean" - }, - "error_response": { - "oneOf": [ - { - "type": "string" - }, - { - "type": "object" - } - ] - }, - "filter": { - "description": "filter determines whether the plugin needs to be executed at runtime", - "type": "array" - }, - "priority": { - "description": "priority of plugins by customized order", - "type": "integer" - } - }, - "type": "object" - }, - "batch_max_size": { - "default": 1000, - "minimum": 1, - "type": "integer" - }, - "buffer_duration": { - "default": 60, - "minimum": 1, - "type": "integer" - }, - "cls_host": { - "type": "string" - }, - "cls_topic": { - "type": "string" - }, - "global_tag": { - "type": "object" - }, - "inactive_timeout": { - "default": 5, - "minimum": 1, - "type": "integer" - }, - "include_req_body": { - "default": false, - "type": "boolean" - }, - "include_resp_body": { - "default": false, - "type": "boolean" - }, - "max_retry_count": { - "default": 0, - "minimum": 0, - "type": "integer" - }, - "name": { - "default": "tencent-cloud-cls", - "type": "string" - }, - "retry_delay": { - "default": 1, - "minimum": 0, - "type": "integer" - }, - "sample_ratio": { - "default": 1, - "maximum": 1, - "minimum": 0.00001, - "type": "number" - }, - "secret_id": { - "type": "string" - }, - "secret_key": { - "type": "string" - } - }, - "required": [ - "cls_host", - "cls_topic", - "secret_id", - "secret_key" - ], - "type": "object" - }, - "version": 0.1 - }, - "traffic-split": { - "priority": 966, - "schema": { - "$comment": "this is a mark for our injected plugin schema", - "properties": { - "_meta": { - "properties": { - "disable": { - "type": "boolean" - }, - "error_response": { - "oneOf": [ - { - "type": "string" - }, - { - "type": "object" - } - ] - }, - "filter": { - "description": "filter determines whether the plugin needs to be executed at runtime", - "type": "array" - }, - "priority": { - "description": "priority of plugins by customized order", - "type": "integer" - } - }, - "type": "object" - }, - "rules": { - "items": { - "properties": { - "match": { - "items": { - "properties": { - "vars": { - "type": "array" - } - }, - "type": "object" - }, - "type": "array" - }, - "weighted_upstreams": { - "default": [ - { - "weight": 1 - } - ], - "items": { - "properties": { - "upstream": { - "oneOf": [ - { - "required": [ - "nodes", - "type" - ] - }, - { - "required": [ - "discovery_type", - "service_name", - "type" - ] - } - ], - "properties": { - "checks": { - "anyOf": [ - { - "required": [ - "active" - ] - }, - { - "required": [ - "active", - "passive" - ] - } - ], - "properties": { - "active": { - "properties": { - "concurrency": { - "default": 10, - "type": "integer" - }, - "healthy": { - "properties": { - "http_statuses": { - "default": [ - 200, - 302 - ], - "items": { - "maximum": 599, - "minimum": 200, - "type": "integer" - }, - "minItems": 1, - "type": "array", - "uniqueItems": true - }, - "interval": { - "default": 1, - "minimum": 1, - "type": "integer" - }, - "successes": { - "default": 2, - "maximum": 254, - "minimum": 1, - "type": "integer" - } - }, - "type": "object" - }, - "host": { - "pattern": "^\\*?[0-9a-zA-Z-._\\[\\]:]+$", - "type": "string" - }, - "http_path": { - "default": "/", - "type": "string" - }, - "https_verify_certificate": { - "default": true, - "type": "boolean" - }, - "port": { - "maximum": 65535, - "minimum": 1, - "type": "integer" - }, - "req_headers": { - "items": { - "type": "string", - "uniqueItems": true - }, - "minItems": 1, - "type": "array" - }, - "timeout": { - "default": 1, - "type": "number" - }, - "type": { - "default": "http", - "enum": [ - "http", - "https", - "tcp" - ], - "type": "string" - }, - "unhealthy": { - "properties": { - "http_failures": { - "default": 5, - "maximum": 254, - "minimum": 1, - "type": "integer" - }, - "http_statuses": { - "default": [ - 404, - 429, - 500, - 501, - 502, - 503, - 504, - 505 - ], - "items": { - "maximum": 599, - "minimum": 200, - "type": "integer" - }, - "minItems": 1, - "type": "array", - "uniqueItems": true - }, - "interval": { - "default": 1, - "minimum": 1, - "type": "integer" - }, - "tcp_failures": { - "default": 2, - "maximum": 254, - "minimum": 1, - "type": "integer" - }, - "timeouts": { - "default": 3, - "maximum": 254, - "minimum": 1, - "type": "integer" - } - }, - "type": "object" - } - }, - "type": "object" - }, - "passive": { - "properties": { - "healthy": { - "properties": { - "http_statuses": { - "default": [ - 200, - 201, - 202, - 203, - 204, - 205, - 206, - 207, - 208, - 226, - 300, - 301, - 302, - 303, - 304, - 305, - 306, - 307, - 308 - ], - "items": { - "maximum": 599, - "minimum": 200, - "type": "integer" - }, - "minItems": 1, - "type": "array", - "uniqueItems": true - }, - "successes": { - "default": 5, - "maximum": 254, - "minimum": 0, - "type": "integer" - } - }, - "type": "object" - }, - "type": { - "default": "http", - "enum": [ - "http", - "https", - "tcp" - ], - "type": "string" - }, - "unhealthy": { - "properties": { - "http_failures": { - "default": 5, - "maximum": 254, - "minimum": 0, - "type": "integer" - }, - "http_statuses": { - "default": [ - 429, - 500, - 503 - ], - "items": { - "maximum": 599, - "minimum": 200, - "type": "integer" - }, - "minItems": 1, - "type": "array", - "uniqueItems": true - }, - "tcp_failures": { - "default": 2, - "maximum": 254, - "minimum": 0, - "type": "integer" - }, - "timeouts": { - "default": 7, - "maximum": 254, - "minimum": 0, - "type": "integer" - } - }, - "type": "object" - } - }, - "type": "object" - } - }, - "type": "object" - }, - "create_time": { - "type": "integer" - }, - "desc": { - "maxLength": 256, - "type": "string" - }, - "discovery_args": { - "properties": { - "group_name": { - "description": "group name", - "type": "string" - }, - "namespace_id": { - "description": "namespace id", - "type": "string" - } - }, - "type": "object" - }, - "discovery_type": { - "description": "discovery type", - "type": "string" - }, - "hash_on": { - "default": "vars", - "enum": [ - "consumer", - "cookie", - "header", - "vars", - "vars_combinations" - ], - "type": "string" - }, - "id": { - "anyOf": [ - { - "maxLength": 64, - "minLength": 1, - "pattern": "^[a-zA-Z0-9-_.]+$", - "type": "string" - }, - { - "minimum": 1, - "type": "integer" - } - ] - }, - "keepalive_pool": { - "properties": { - "idle_timeout": { - "default": 60, - "minimum": 0, - "type": "number" - }, - "requests": { - "default": 1000, - "minimum": 1, - "type": "integer" - }, - "size": { - "default": 320, - "minimum": 1, - "type": "integer" - } - }, - "type": "object" - }, - "key": { - "description": "the key of chash for dynamic load balancing", - "type": "string" - }, - "labels": { - "description": "key/value pairs to specify attributes", - "patternProperties": { - ".*": { - "description": "value of label", - "maxLength": 64, - "minLength": 1, - "pattern": "^\\S+$", - "type": "string" - } - }, - "type": "object" - }, - "name": { - "maxLength": 100, - "minLength": 1, - "type": "string" - }, - "nodes": { - "anyOf": [ - { - "patternProperties": { - ".*": { - "description": "weight of node", - "minimum": 0, - "type": "integer" - } - }, - "type": "object" - }, - { - "items": { - "properties": { - "host": { - "pattern": "^\\*?[0-9a-zA-Z-._\\[\\]:]+$", - "type": "string" - }, - "metadata": { - "description": "metadata of node", - "type": "object" - }, - "port": { - "description": "port of node", - "minimum": 1, - "type": "integer" - }, - "priority": { - "default": 0, - "description": "priority of node", - "type": "integer" - }, - "weight": { - "description": "weight of node", - "minimum": 0, - "type": "integer" - } - }, - "required": [ - "host", - "port", - "weight" - ], - "type": "object" - }, - "type": "array" - } - ] - }, - "pass_host": { - "default": "pass", - "description": "mod of host passing", - "enum": [ - "node", - "pass", - "rewrite" - ], - "type": "string" - }, - "retries": { - "minimum": 0, - "type": "integer" - }, - "retry_timeout": { - "minimum": 0, - "type": "number" - }, - "scheme": { - "default": "http", - "description": "The scheme of the upstream. For L7 proxy, it can be one of grpc/grpcs/http/https. For L4 proxy, it can be one of tcp/tls/udp. For specific protocols, it can be kafka.", - "enum": [ - "grpc", - "grpcs", - "http", - "https", - "kafka", - "tcp", - "tls", - "udp" - ] - }, - "service_name": { - "maxLength": 256, - "minLength": 1, - "type": "string" - }, - "timeout": { - "properties": { - "connect": { - "exclusiveMinimum": 0, - "type": "number" - }, - "read": { - "exclusiveMinimum": 0, - "type": "number" - }, - "send": { - "exclusiveMinimum": 0, - "type": "number" - } - }, - "required": [ - "connect", - "read", - "send" - ], - "type": "object" - }, - "tls": { - "dependencies": { - "client_cert": { - "not": { - "required": [ - "client_cert_id" - ] - }, - "required": [ - "client_key" - ] - }, - "client_cert_id": { - "not": { - "required": [ - "client_client", - "client_key" - ] - } - }, - "client_key": { - "not": { - "required": [ - "client_cert_id" - ] - }, - "required": [ - "client_cert" - ] - } - }, - "properties": { - "client_cert": { - "maxLength": 65536, - "minLength": 128, - "type": "string" - }, - "client_cert_id": { - "anyOf": [ - { - "maxLength": 64, - "minLength": 1, - "pattern": "^[a-zA-Z0-9-_.]+$", - "type": "string" - }, - { - "minimum": 1, - "type": "integer" - } - ] - }, - "client_key": { - "maxLength": 65536, - "minLength": 128, - "type": "string" - }, - "verify": { - "default": false, - "description": "Turn on server certificate verification, currently only kafka upstream is supported", - "type": "boolean" - } - }, - "type": "object" - }, - "type": { - "description": "algorithms of load balancing", - "type": "string" - }, - "update_time": { - "type": "integer" - }, - "upstream_host": { - "pattern": "^\\*?[0-9a-zA-Z-._\\[\\]:]+$", - "type": "string" - } - }, - "type": "object" - }, - "upstream_id": { - "anyOf": [ - { - "maxLength": 64, - "minLength": 1, - "pattern": "^[a-zA-Z0-9-_.]+$", - "type": "string" - }, - { - "minimum": 1, - "type": "integer" - } - ] - }, - "weight": { - "default": 1, - "description": "used to split traffic between differentupstreams for plugin configuration", - "minimum": 0, - "type": "integer" - } - }, - "type": "object" - }, - "maxItems": 20, - "minItems": 1, - "type": "array" - } - }, - "type": "object" - }, - "type": "array" - } - }, - "type": "object" - }, - "version": 0.1 - }, - "ua-restriction": { - "priority": 2999, - "schema": { - "$comment": "this is a mark for our injected plugin schema", - "properties": { - "_meta": { - "properties": { - "disable": { - "type": "boolean" - }, - "error_response": { - "oneOf": [ - { - "type": "string" - }, - { - "type": "object" - } - ] - }, - "filter": { - "description": "filter determines whether the plugin needs to be executed at runtime", - "type": "array" - }, - "priority": { - "description": "priority of plugins by customized order", - "type": "integer" - } - }, - "type": "object" - }, - "allowlist": { - "items": { - "minLength": 1, - "type": "string" - }, - "minItems": 1, - "type": "array" - }, - "bypass_missing": { - "default": false, - "type": "boolean" - }, - "denylist": { - "items": { - "minLength": 1, - "type": "string" - }, - "minItems": 1, - "type": "array" - }, - "message": { - "default": "Not allowed", - "maxLength": 1024, - "minLength": 1, - "type": "string" - } - }, - "type": "object" - }, - "version": 0.1 - }, - "udp-logger": { - "priority": 400, - "schema": { - "$comment": "this is a mark for our injected plugin schema", - "properties": { - "_meta": { - "properties": { - "disable": { - "type": "boolean" - }, - "error_response": { - "oneOf": [ - { - "type": "string" - }, - { - "type": "object" - } - ] - }, - "filter": { - "description": "filter determines whether the plugin needs to be executed at runtime", - "type": "array" - }, - "priority": { - "description": "priority of plugins by customized order", - "type": "integer" - } - }, - "type": "object" - }, - "batch_max_size": { - "default": 1000, - "minimum": 1, - "type": "integer" - }, - "buffer_duration": { - "default": 60, - "minimum": 1, - "type": "integer" - }, - "host": { - "type": "string" - }, - "inactive_timeout": { - "default": 5, - "minimum": 1, - "type": "integer" - }, - "include_req_body": { - "default": false, - "type": "boolean" - }, - "max_retry_count": { - "default": 0, - "minimum": 0, - "type": "integer" - }, - "name": { - "default": "udp logger", - "type": "string" - }, - "port": { - "minimum": 0, - "type": "integer" - }, - "retry_delay": { - "default": 1, - "minimum": 0, - "type": "integer" - }, - "timeout": { - "default": 3, - "minimum": 1, - "type": "integer" - } - }, - "required": [ - "host", - "port" - ], - "type": "object" - }, - "version": 0.1 - }, - "uri-blocker": { - "priority": 2900, - "schema": { - "$comment": "this is a mark for our injected plugin schema", - "properties": { - "_meta": { - "properties": { - "disable": { - "type": "boolean" - }, - "error_response": { - "oneOf": [ - { - "type": "string" - }, - { - "type": "object" - } - ] - }, - "filter": { - "description": "filter determines whether the plugin needs to be executed at runtime", - "type": "array" - }, - "priority": { - "description": "priority of plugins by customized order", - "type": "integer" - } - }, - "type": "object" - }, - "block_rules": { - "items": { - "maxLength": 4096, - "minLength": 1, - "type": "string" - }, - "type": "array", - "uniqueItems": true - }, - "case_insensitive": { - "default": false, - "type": "boolean" - }, - "rejected_code": { - "default": 403, - "minimum": 200, - "type": "integer" - }, - "rejected_msg": { - "minLength": 1, - "type": "string" - } - }, - "required": [ - "block_rules" - ], - "type": "object" - }, - "version": 0.1 - }, - "wolf-rbac": { - "priority": 2555, - "schema": { - "$comment": "this is a mark for our injected plugin schema", - "properties": { - "_meta": { - "properties": { - "disable": { - "type": "boolean" - }, - "error_response": { - "oneOf": [ - { - "type": "string" - }, - { - "type": "object" - } - ] - }, - "filter": { - "description": "filter determines whether the plugin needs to be executed at runtime", - "type": "array" - }, - "priority": { - "description": "priority of plugins by customized order", - "type": "integer" - } - }, - "type": "object" - }, - "appid": { - "default": "unset", - "type": "string" - }, - "header_prefix": { - "default": "X-", - "type": "string" - }, - "server": { - "default": "http://127.0.0.1:12180", - "type": "string" - } - }, - "type": "object" - }, - "type": "auth", - "version": 0.1 - }, - "workflow": { - "priority": 1006, - "schema": { - "$comment": "this is a mark for our injected plugin schema", - "properties": { - "_meta": { - "properties": { - "disable": { - "type": "boolean" - }, - "error_response": { - "oneOf": [ - { - "type": "string" - }, - { - "type": "object" - } - ] - }, - "filter": { - "description": "filter determines whether the plugin needs to be executed at runtime", - "type": "array" - }, - "priority": { - "description": "priority of plugins by customized order", - "type": "integer" - } - }, - "type": "object" - }, - "rules": { - "items": { - "properties": { - "actions": { - "items": { - "minItems": 1, - "type": "array" - }, - "type": "array" - }, - "case": { - "items": { - "anyOf": [ - { - "type": "array" - }, - { - "type": "string" - } - ] - }, - "minItems": 1, - "type": "array" - } - }, - "required": [ - "actions", - "case" - ], - "type": "object" - }, - "type": "array" - } - }, - "type": "object" - }, - "version": 0.1 - }, - "zipkin": { - "priority": 12011, - "schema": { - "$comment": "this is a mark for our injected plugin schema", - "properties": { - "_meta": { - "properties": { - "disable": { - "type": "boolean" - }, - "error_response": { - "oneOf": [ - { - "type": "string" - }, - { - "type": "object" - } - ] - }, - "filter": { - "description": "filter determines whether the plugin needs to be executed at runtime", - "type": "array" - }, - "priority": { - "description": "priority of plugins by customized order", - "type": "integer" - } - }, - "type": "object" - }, - "endpoint": { - "type": "string" - }, - "sample_ratio": { - "maximum": 1, - "minimum": 0.00001, - "type": "number" - }, - "server_addr": { - "description": "default is $server_addr, you can specify your external ip address", - "pattern": "^[0-9]{1,3}.[0-9]{1,3}.[0-9]{1,3}.[0-9]{1,3}$", - "type": "string" - }, - "service_name": { - "default": "APISIX", - "description": "service name for zipkin reporter", - "type": "string" - }, - "span_version": { - "default": 2, - "enum": [ - 1, - 2 - ] - } - }, - "required": [ - "endpoint", - "sample_ratio" - ], - "type": "object" - }, - "version": 0.1 - } - }, - "stream_plugins": { - "ip-restriction": { - "priority": 3000, - "schema": { - "$comment": "this is a mark for our injected plugin schema", - "oneOf": [ - { - "required": [ - "whitelist" - ] - }, - { - "required": [ - "blacklist" - ] - } - ], - "properties": { - "_meta": { - "properties": { - "disable": { - "type": "boolean" - }, - "error_response": { - "oneOf": [ - { - "type": "string" - }, - { - "type": "object" - } - ] - }, - "filter": { - "description": "filter determines whether the plugin needs to be executed at runtime", - "type": "array" - }, - "priority": { - "description": "priority of plugins by customized order", - "type": "integer" - } - }, - "type": "object" - }, - "blacklist": { - "items": { - "anyOf": [ - { - "format": "ipv4", - "title": "IPv4", - "type": "string" - }, - { - "pattern": "^([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])\\.([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])\\.([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])\\.([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])/([12]?[0-9]|3[0-2])$", - "title": "IPv4/CIDR", - "type": "string" - }, - { - "format": "ipv6", - "title": "IPv6", - "type": "string" - }, - { - "pattern": "^([a-fA-F0-9]{0,4}:){1,8}(:[a-fA-F0-9]{0,4}){0,8}([a-fA-F0-9]{0,4})?/[0-9]{1,3}$", - "title": "IPv6/CIDR", - "type": "string" - } - ] - }, - "minItems": 1, - "type": "array" - }, - "message": { - "default": "Your IP address is not allowed", - "maxLength": 1024, - "minLength": 1, - "type": "string" - }, - "whitelist": { - "items": { - "anyOf": [ - { - "format": "ipv4", - "title": "IPv4", - "type": "string" - }, - { - "pattern": "^([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])\\.([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])\\.([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])\\.([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])/([12]?[0-9]|3[0-2])$", - "title": "IPv4/CIDR", - "type": "string" - }, - { - "format": "ipv6", - "title": "IPv6", - "type": "string" - }, - { - "pattern": "^([a-fA-F0-9]{0,4}:){1,8}(:[a-fA-F0-9]{0,4}){0,8}([a-fA-F0-9]{0,4})?/[0-9]{1,3}$", - "title": "IPv6/CIDR", - "type": "string" - } - ] - }, - "minItems": 1, - "type": "array" - } - }, - "type": "object" - }, - "version": 0.1 - }, - "limit-conn": { - "priority": 1003, - "schema": { - "$comment": "this is a mark for our injected plugin schema", - "properties": { - "_meta": { - "properties": { - "disable": { - "type": "boolean" - }, - "error_response": { - "oneOf": [ - { - "type": "string" - }, - { - "type": "object" - } - ] - }, - "filter": { - "description": "filter determines whether the plugin needs to be executed at runtime", - "type": "array" - }, - "priority": { - "description": "priority of plugins by customized order", - "type": "integer" - } - }, - "type": "object" - }, - "burst": { - "minimum": 0, - "type": "integer" - }, - "conn": { - "exclusiveMinimum": 0, - "type": "integer" - }, - "default_conn_delay": { - "exclusiveMinimum": 0, - "type": "number" - }, - "key": { - "type": "string" - }, - "key_type": { - "default": "var", - "enum": [ - "var", - "var_combination" - ], - "type": "string" - }, - "only_use_default_delay": { - "default": false, - "type": "boolean" - } - }, - "required": [ - "burst", - "conn", - "default_conn_delay", - "key" - ], - "type": "object" - }, - "version": 0.1 - }, - "mqtt-proxy": { - "priority": 1000, - "schema": { - "$comment": "this is a mark for our injected plugin schema", - "properties": { - "_meta": { - "properties": { - "disable": { - "type": "boolean" - }, - "error_response": { - "oneOf": [ - { - "type": "string" - }, - { - "type": "object" - } - ] - }, - "filter": { - "description": "filter determines whether the plugin needs to be executed at runtime", - "type": "array" - }, - "priority": { - "description": "priority of plugins by customized order", - "type": "integer" - } - }, - "type": "object" - }, - "protocol_level": { - "type": "integer" - }, - "protocol_name": { - "type": "string" - } - }, - "required": [ - "protocol_level", - "protocol_name" - ], - "type": "object" - }, - "version": 0.1 - }, - "prometheus": { - "priority": 500, - "schema": { - "$comment": "this is a mark for our injected plugin schema", - "properties": { - "_meta": { - "properties": { - "disable": { - "type": "boolean" - }, - "error_response": { - "oneOf": [ - { - "type": "string" - }, - { - "type": "object" - } - ] - }, - "filter": { - "description": "filter determines whether the plugin needs to be executed at runtime", - "type": "array" - }, - "priority": { - "description": "priority of plugins by customized order", - "type": "integer" - } - }, - "type": "object" - }, - "prefer_name": { - "default": false, - "type": "boolean" - } - }, - "type": "object" - }, - "version": 0.1 - }, - "syslog": { - "metadata_schema": { - "properties": { - "log_format": { - "default": { - "@timestamp": "$time_iso8601", - "client_ip": "$remote_addr", - "host": "$host" - }, - "type": "object" - } - }, - "type": "object" - }, - "priority": 401, - "schema": { - "$comment": "this is a mark for our injected plugin schema", - "properties": { - "_meta": { - "properties": { - "disable": { - "type": "boolean" - }, - "error_response": { - "oneOf": [ - { - "type": "string" - }, - { - "type": "object" - } - ] - }, - "filter": { - "description": "filter determines whether the plugin needs to be executed at runtime", - "type": "array" - }, - "priority": { - "description": "priority of plugins by customized order", - "type": "integer" - } - }, - "type": "object" - }, - "batch_max_size": { - "default": 1000, - "minimum": 1, - "type": "integer" - }, - "buffer_duration": { - "default": 60, - "minimum": 1, - "type": "integer" - }, - "drop_limit": { - "default": 1048576, - "type": "integer" - }, - "flush_limit": { - "default": 4096, - "minimum": 1, - "type": "integer" - }, - "host": { - "type": "string" - }, - "inactive_timeout": { - "default": 5, - "minimum": 1, - "type": "integer" - }, - "max_retry_count": { - "default": 0, - "minimum": 0, - "type": "integer" - }, - "name": { - "default": "stream sys logger", - "type": "string" - }, - "pool_size": { - "default": 5, - "minimum": 5, - "type": "integer" - }, - "port": { - "type": "integer" - }, - "retry_delay": { - "default": 1, - "minimum": 0, - "type": "integer" - }, - "sock_type": { - "default": "tcp", - "enum": [ - "tcp", - "udp" - ], - "type": "string" - }, - "timeout": { - "default": 3000, - "minimum": 1, - "type": "integer" - }, - "tls": { - "default": false, - "type": "boolean" - } - }, - "required": [ - "host", - "port" - ], - "type": "object" - }, - "version": 0.1 - } - } -} diff --git a/api/entry.sh b/api/entry.sh deleted file mode 100755 index 886e47ca02..0000000000 --- a/api/entry.sh +++ /dev/null @@ -1,21 +0,0 @@ -#!/bin/sh -# -# Licensed to the Apache Software Foundation (ASF) under one or more -# contributor license agreements. See the NOTICE file distributed with -# this work for additional information regarding copyright ownership. -# The ASF licenses this file to You under the Apache License, Version 2.0 -# (the "License"); you may not use this file except in compliance with -# the License. You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -# - -export ENV=prod - -exec ./manager-api diff --git a/api/go.mod b/api/go.mod deleted file mode 100644 index 6bb8f3cbf5..0000000000 --- a/api/go.mod +++ /dev/null @@ -1,92 +0,0 @@ -module github.com/apisix/manager-api - -go 1.19 - -require ( - github.com/coreos/go-oidc/v3 v3.3.0 - github.com/evanphx/json-patch/v5 v5.1.0 - github.com/getkin/kin-openapi v0.33.0 - github.com/gin-contrib/gzip v0.0.3 - github.com/gin-contrib/static v0.0.0-20200916080430-d45d9a37d28e - github.com/gin-gonic/gin v1.9.0 - github.com/golang-jwt/jwt v3.2.2+incompatible - github.com/gorilla/sessions v1.2.1 - github.com/juliangruber/go-intersect v1.1.0 - github.com/pkg/errors v0.9.1 - github.com/satori/go.uuid v1.2.0 - github.com/shiningrush/droplet v0.3.0 - github.com/shiningrush/droplet/wrapper/gin v0.3.0 - github.com/sony/sonyflake v1.0.0 - github.com/spf13/cobra v1.6.1 - github.com/spf13/viper v1.8.1 - github.com/stretchr/testify v1.8.2 - github.com/tidwall/gjson v1.14.4 - github.com/xeipuuv/gojsonschema v1.2.0 - github.com/yuin/gopher-lua v0.0.0-20200816102855-ee81675732da - go.etcd.io/etcd/client/pkg/v3 v3.5.5 - go.etcd.io/etcd/client/v3 v3.5.5 - go.uber.org/zap v1.17.0 - golang.org/x/oauth2 v0.0.0-20220822191816-0ebed06d0094 -) - -require ( - github.com/bytedance/sonic v1.8.0 // indirect - github.com/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311 // indirect - github.com/coreos/go-semver v0.3.0 // indirect - github.com/coreos/go-systemd/v22 v22.3.2 // indirect - github.com/davecgh/go-spew v1.1.1 // indirect - github.com/fsnotify/fsnotify v1.4.9 // indirect - github.com/ghodss/yaml v1.0.0 // indirect - github.com/gin-contrib/sse v0.1.0 // indirect - github.com/go-openapi/jsonpointer v0.19.5 // indirect - github.com/go-openapi/swag v0.19.5 // indirect - github.com/go-playground/locales v0.14.1 // indirect - github.com/go-playground/universal-translator v0.18.1 // indirect - github.com/go-playground/validator/v10 v10.11.2 // indirect - github.com/goccy/go-json v0.10.0 // indirect - github.com/gogo/protobuf v1.3.2 // indirect - github.com/golang/protobuf v1.5.2 // indirect - github.com/gorilla/securecookie v1.1.1 // indirect - github.com/hashicorp/hcl v1.0.0 // indirect - github.com/inconshreveable/mousetrap v1.0.1 // indirect - github.com/json-iterator/go v1.1.12 // indirect - github.com/klauspost/cpuid/v2 v2.0.9 // indirect - github.com/leodido/go-urn v1.2.1 // indirect - github.com/magiconair/properties v1.8.5 // indirect - github.com/mailru/easyjson v0.0.0-20190626092158-b2ccc519800e // indirect - github.com/mattn/go-isatty v0.0.17 // indirect - github.com/mitchellh/mapstructure v1.4.1 // indirect - github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect - github.com/modern-go/reflect2 v1.0.2 // indirect - github.com/pelletier/go-toml v1.9.3 // indirect - github.com/pelletier/go-toml/v2 v2.0.6 // indirect - github.com/pmezard/go-difflib v1.0.0 // indirect - github.com/spf13/afero v1.6.0 // indirect - github.com/spf13/cast v1.3.1 // indirect - github.com/spf13/jwalterweatherman v1.1.0 // indirect - github.com/spf13/pflag v1.0.5 // indirect - github.com/stretchr/objx v0.5.0 // indirect - github.com/subosito/gotenv v1.2.0 // indirect - github.com/tidwall/match v1.1.1 // indirect - github.com/tidwall/pretty v1.2.0 // indirect - github.com/twitchyliquid64/golang-asm v0.15.1 // indirect - github.com/ugorji/go/codec v1.2.9 // indirect - github.com/xeipuuv/gojsonpointer v0.0.0-20180127040702-4e3ac2762d5f // indirect - github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415 // indirect - go.etcd.io/etcd/api/v3 v3.5.5 // indirect - go.uber.org/atomic v1.7.0 // indirect - go.uber.org/multierr v1.6.0 // indirect - golang.org/x/arch v0.0.0-20210923205945-b76863e36670 // indirect - golang.org/x/crypto v0.5.0 // indirect - golang.org/x/net v0.7.0 // indirect - golang.org/x/sys v0.5.0 // indirect - golang.org/x/text v0.7.0 // indirect - google.golang.org/appengine v1.6.7 // indirect - google.golang.org/genproto v0.0.0-20220616135557-88e70c0c3a90 // indirect - google.golang.org/grpc v1.47.0 // indirect - google.golang.org/protobuf v1.28.1 // indirect - gopkg.in/ini.v1 v1.62.0 // indirect - gopkg.in/square/go-jose.v2 v2.6.0 // indirect - gopkg.in/yaml.v2 v2.4.0 // indirect - gopkg.in/yaml.v3 v3.0.1 // indirect -) diff --git a/api/go.sum b/api/go.sum deleted file mode 100644 index 18ae650a7d..0000000000 --- a/api/go.sum +++ /dev/null @@ -1,987 +0,0 @@ -cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= -cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= -cloud.google.com/go v0.38.0/go.mod h1:990N+gfupTy94rShfmMCWGDn0LpTmnzTp2qbd1dvSRU= -cloud.google.com/go v0.44.1/go.mod h1:iSa0KzasP4Uvy3f1mN/7PiObzGgflwredwwASm/v6AU= -cloud.google.com/go v0.44.2/go.mod h1:60680Gw3Yr4ikxnPRS/oxxkBccT6SA1yMk63TGekxKY= -cloud.google.com/go v0.45.1/go.mod h1:RpBamKRgapWJb87xiFSdk4g1CME7QZg3uwTez+TSTjc= -cloud.google.com/go v0.46.3/go.mod h1:a6bKKbmY7er1mI7TEI4lsAkts/mkhTSZK8w33B4RAg0= -cloud.google.com/go v0.50.0/go.mod h1:r9sluTvynVuxRIOHXQEHMFffphuXHOMZMycpNR5e6To= -cloud.google.com/go v0.52.0/go.mod h1:pXajvRH/6o3+F9jDHZWQ5PbGhn+o8w9qiu/CffaVdO4= -cloud.google.com/go v0.53.0/go.mod h1:fp/UouUEsRkN6ryDKNW/Upv/JBKnv6WDthjR6+vze6M= -cloud.google.com/go v0.54.0/go.mod h1:1rq2OEkV3YMf6n/9ZvGWI3GWw0VoqH/1x2nd8Is/bPc= -cloud.google.com/go v0.56.0/go.mod h1:jr7tqZxxKOVYizybht9+26Z/gUq7tiRzu+ACVAMbKVk= -cloud.google.com/go v0.57.0/go.mod h1:oXiQ6Rzq3RAkkY7N6t3TcE6jE+CIBBbA36lwQ1JyzZs= -cloud.google.com/go v0.62.0/go.mod h1:jmCYTdRCQuc1PHIIJ/maLInMho30T/Y0M4hTdTShOYc= -cloud.google.com/go v0.65.0/go.mod h1:O5N8zS7uWy9vkA9vayVHs65eM1ubvY4h553ofrNHObY= -cloud.google.com/go v0.72.0/go.mod h1:M+5Vjvlc2wnp6tjzE102Dw08nGShTscUx2nZMufOKPI= -cloud.google.com/go v0.74.0/go.mod h1:VV1xSbzvo+9QJOxLDaJfTjx5e+MePCpCWwvftOeQmWk= -cloud.google.com/go v0.78.0/go.mod h1:QjdrLG0uq+YwhjoVOLsS1t7TW8fs36kLs4XO5R5ECHg= -cloud.google.com/go v0.79.0/go.mod h1:3bzgcEeQlzbuEAYu4mrWhKqWjmpprinYgKJLgKHnbb8= -cloud.google.com/go v0.81.0/go.mod h1:mk/AM35KwGk/Nm2YSeZbxXdrNK3KZOYHmLkOqC2V6E0= -cloud.google.com/go v0.83.0/go.mod h1:Z7MJUsANfY0pYPdw0lbnivPx4/vhy/e2FEkSkF7vAVY= -cloud.google.com/go v0.84.0/go.mod h1:RazrYuxIK6Kb7YrzzhPoLmCVzl7Sup4NrbKPg8KHSUM= -cloud.google.com/go v0.87.0/go.mod h1:TpDYlFy7vuLzZMMZ+B6iRiELaY7z/gJPaqbMx6mlWcY= -cloud.google.com/go v0.90.0/go.mod h1:kRX0mNRHe0e2rC6oNakvwQqzyDmg57xJ+SZU1eT2aDQ= -cloud.google.com/go v0.93.3/go.mod h1:8utlLll2EF5XMAV15woO4lSbWQlk8rer9aLOfLh7+YI= -cloud.google.com/go v0.94.1/go.mod h1:qAlAugsXlC+JWO+Bke5vCtc9ONxjQT3drlTTnAplMW4= -cloud.google.com/go v0.97.0/go.mod h1:GF7l59pYBVlXQIBLx3a761cZ41F9bBH3JUlihCt2Udc= -cloud.google.com/go v0.99.0/go.mod h1:w0Xx2nLzqWJPuozYQX+hFfCSI8WioryfRDzkoI/Y2ZA= -cloud.google.com/go v0.100.2/go.mod h1:4Xra9TjzAeYHrl5+oeLlzbM2k3mjVhZh4UqTZ//w99A= -cloud.google.com/go v0.102.0/go.mod h1:oWcCzKlqJ5zgHQt9YsaeTY9KzIvjyy0ArmiBUgpQ+nc= -cloud.google.com/go/bigquery v1.0.1/go.mod h1:i/xbL2UlR5RvWAURpBYZTtm/cXjCha9lbfbpx4poX+o= -cloud.google.com/go/bigquery v1.3.0/go.mod h1:PjpwJnslEMmckchkHFfq+HTD2DmtT67aNFKH1/VBDHE= -cloud.google.com/go/bigquery v1.4.0/go.mod h1:S8dzgnTigyfTmLBfrtrhyYhwRxG72rYxvftPBK2Dvzc= -cloud.google.com/go/bigquery v1.5.0/go.mod h1:snEHRnqQbz117VIFhE8bmtwIDY80NLUZUMb4Nv6dBIg= -cloud.google.com/go/bigquery v1.7.0/go.mod h1://okPTzCYNXSlb24MZs83e2Do+h+VXtc4gLoIoXIAPc= -cloud.google.com/go/bigquery v1.8.0/go.mod h1:J5hqkt3O0uAFnINi6JXValWIb1v0goeZM77hZzJN/fQ= -cloud.google.com/go/compute v0.1.0/go.mod h1:GAesmwr110a34z04OlxYkATPBEfVhkymfTBXtfbBFow= -cloud.google.com/go/compute v1.3.0/go.mod h1:cCZiE1NHEtai4wiufUhW8I8S1JKkAnhnQJWM7YD99wM= -cloud.google.com/go/compute v1.5.0/go.mod h1:9SMHyhJlzhlkJqrPAc839t2BZFTSk6Jdj6mkzQJeu0M= -cloud.google.com/go/compute v1.6.0/go.mod h1:T29tfhtVbq1wvAPo0E3+7vhgmkOYeXjhFvz/FMzPu0s= -cloud.google.com/go/compute v1.6.1/go.mod h1:g85FgpzFvNULZ+S8AYq87axRKuf2Kh7deLqV/jJ3thU= -cloud.google.com/go/compute v1.7.0/go.mod h1:435lt8av5oL9P3fv1OEzSbSUe+ybHXGMPQHHZWZxy9U= -cloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7TKLgZqpHYE= -cloud.google.com/go/datastore v1.1.0/go.mod h1:umbIZjpQpHh4hmRpGhH4tLFup+FVzqBi1b3c64qFpCk= -cloud.google.com/go/firestore v1.1.0/go.mod h1:ulACoGHTpvq5r8rxGJ4ddJZBZqakUQqClKRT5SZwBmk= -cloud.google.com/go/iam v0.3.0/go.mod h1:XzJPvDayI+9zsASAFO68Hk07u3z+f+JrT2xXNdp4bnY= -cloud.google.com/go/pubsub v1.0.1/go.mod h1:R0Gpsv3s54REJCy4fxDixWD93lHJMoZTyQ2kNxGRt3I= -cloud.google.com/go/pubsub v1.1.0/go.mod h1:EwwdRX2sKPjnvnqCa270oGRyludottCI76h+R3AArQw= -cloud.google.com/go/pubsub v1.2.0/go.mod h1:jhfEVHT8odbXTkndysNHCcx0awwzvfOlguIAii9o8iA= -cloud.google.com/go/pubsub v1.3.1/go.mod h1:i+ucay31+CNRpDW4Lu78I4xXG+O1r/MAHgjpRVR+TSU= -cloud.google.com/go/storage v1.0.0/go.mod h1:IhtSnM/ZTZV8YYJWCY8RULGVqBDmpoyjwiyrjsg+URw= -cloud.google.com/go/storage v1.5.0/go.mod h1:tpKbwo567HUNpVclU5sGELwQWBDZ8gh0ZeosJ0Rtdos= -cloud.google.com/go/storage v1.6.0/go.mod h1:N7U0C8pVQ/+NIKOBQyamJIeKQKkZ+mxpohlUTyfDhBk= -cloud.google.com/go/storage v1.8.0/go.mod h1:Wv1Oy7z6Yz3DshWRJFhqM/UCfaWIRTdp0RXyy7KQOVs= -cloud.google.com/go/storage v1.10.0/go.mod h1:FLPqc6j+Ki4BU591ie1oL6qBQGu2Bl/tZ9ullr3+Kg0= -cloud.google.com/go/storage v1.22.1/go.mod h1:S8N1cAStu7BOeFfE8KAQzmyyLkK8p/vmRq6kuBTW58Y= -dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU= -github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= -github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= -github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU= -github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= -github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= -github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= -github.com/alecthomas/units v0.0.0-20190717042225-c3de453c63f4/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= -github.com/alecthomas/units v0.0.0-20190924025748-f65c72e2690d/go.mod h1:rBZYJk541a8SKzHPHnH3zbiI+7dagKZ0cgpgrD7Fyho= -github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY= -github.com/armon/circbuf v0.0.0-20150827004946-bbbad097214e/go.mod h1:3U/XgcO3hCbHZ8TKRvWD2dDTCfh9M9ya+I9JpbB7O8o= -github.com/armon/go-metrics v0.0.0-20180917152333-f0300d1749da/go.mod h1:Q73ZrmVTwzkszR9V5SSuryQ31EELlFMUz1kKyl939pY= -github.com/armon/go-radix v0.0.0-20180808171621-7fddfc383310/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8= -github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q= -github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8= -github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= -github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs= -github.com/bketelsen/crypt v0.0.4/go.mod h1:aI6NrJ0pMGgvZKL1iVgXLnfIFJtfV+bKCoqOes/6LfM= -github.com/bmizerany/assert v0.0.0-20160611221934-b7ed37b82869 h1:DDGfHa7BWjL4YnC6+E63dPcxHo2sUxDIu8g3QgEJdRY= -github.com/bmizerany/assert v0.0.0-20160611221934-b7ed37b82869/go.mod h1:Ekp36dRnpXw/yCqJaO+ZrUyxD+3VXMFFr56k5XYrpB4= -github.com/bytedance/sonic v1.5.0/go.mod h1:ED5hyg4y6t3/9Ku1R6dU/4KyJ48DZ4jPhfY1O2AihPM= -github.com/bytedance/sonic v1.8.0 h1:ea0Xadu+sHlu7x5O3gKhRpQ1IKiMrSiHttPF0ybECuA= -github.com/bytedance/sonic v1.8.0/go.mod h1:i736AoUSYt75HyZLoJW9ERYxcy6eaN6h4BZXU064P/U= -github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= -github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc= -github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= -github.com/chenzhuoyu/base64x v0.0.0-20211019084208-fb5309c8db06/go.mod h1:DH46F32mSOjUmXrMHnKwZdA8wcEefY7UVqBKYGjpdQY= -github.com/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311 h1:qSGYFH7+jGhDF8vLC+iwCD4WpbV1EBDSzWkJODFLams= -github.com/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311/go.mod h1:b583jCggY9gE99b6G5LEC39OIiVsWj+R97kbl5odCEk= -github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI= -github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI= -github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU= -github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= -github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= -github.com/cncf/udpa/go v0.0.0-20200629203442-efcf912fb354/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk= -github.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk= -github.com/cncf/udpa/go v0.0.0-20210930031921-04548b0d99d4/go.mod h1:6pvJx4me5XPnfI9Z40ddWsdw2W/uZgQLFXToKeRcDiI= -github.com/cncf/xds/go v0.0.0-20210312221358-fbca930ec8ed/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= -github.com/cncf/xds/go v0.0.0-20210805033703-aa0b78936158/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= -github.com/cncf/xds/go v0.0.0-20210922020428-25de7278fc84/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= -github.com/cncf/xds/go v0.0.0-20211001041855-01bcc9b48dfe/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= -github.com/cncf/xds/go v0.0.0-20211011173535-cb28da3451f1/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= -github.com/coreos/go-oidc/v3 v3.3.0 h1:Y1LV3mP+QT3MEycATZpAiwfyN+uxZLqVbAHJUuOJEe4= -github.com/coreos/go-oidc/v3 v3.3.0/go.mod h1:eHUXhZtXPQLgEaDrOVTgwbgmz1xGOkJNye6h3zkD2Pw= -github.com/coreos/go-semver v0.3.0 h1:wkHLiw0WNATZnSG7epLsujiMCgPAc9xhjJ4tgnAxmfM= -github.com/coreos/go-semver v0.3.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk= -github.com/coreos/go-systemd/v22 v22.3.2 h1:D9/bQk5vlXQFZ6Kwuu6zaiXJ9oTPe68++AzAJc1DzSI= -github.com/coreos/go-systemd/v22 v22.3.2/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc= -github.com/cpuguy83/go-md2man/v2 v2.0.2/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= -github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= -github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/deckarep/golang-set v1.7.1 h1:SCQV0S6gTtp6itiFrTqI+pfmJ4LN85S1YzhDf9rTHJQ= -github.com/deckarep/golang-set v1.7.1/go.mod h1:93vsz/8Wt4joVM7c2AVqh+YRMiUSc14yDtF28KmMOgQ= -github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk= -github.com/elazarl/go-bindata-assetfs v1.0.0/go.mod h1:v+YaWX3bdea5J/mo8dSETolEo7R71Vk1u8bnjau5yw4= -github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= -github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= -github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98= -github.com/envoyproxy/go-control-plane v0.9.7/go.mod h1:cwu0lG7PUMfa9snN8LXBig5ynNVH9qI8YYLbd1fK2po= -github.com/envoyproxy/go-control-plane v0.9.9-0.20201210154907-fd9021fe5dad/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk= -github.com/envoyproxy/go-control-plane v0.9.9-0.20210217033140-668b12f5399d/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk= -github.com/envoyproxy/go-control-plane v0.9.9-0.20210512163311-63b5d3c536b0/go.mod h1:hliV/p42l8fGbc6Y9bQ70uLwIvmJyVE5k4iMKlh8wCQ= -github.com/envoyproxy/go-control-plane v0.9.10-0.20210907150352-cf90f659a021/go.mod h1:AFq3mo9L8Lqqiid3OhADV3RfLJnjiw63cSpi+fDTRC0= -github.com/envoyproxy/go-control-plane v0.10.2-0.20220325020618-49ff273808a1/go.mod h1:KJwIaB5Mv44NWtYuAOFCVOjcI94vtpEz2JU/D2v6IjE= -github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= -github.com/evanphx/json-patch/v5 v5.1.0 h1:B0aXl1o/1cP8NbviYiBMkcHBtUjIJ1/Ccg6b+SwCLQg= -github.com/evanphx/json-patch/v5 v5.1.0/go.mod h1:G79N1coSVB93tBe7j6PhzjmR3/2VvlbKOFpnXhI9Bw4= -github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4= -github.com/fsnotify/fsnotify v1.4.9 h1:hsms1Qyu0jgnwNXIxa+/V/PDsU6CfLf6CNO8H7IWoS4= -github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ= -github.com/getkin/kin-openapi v0.33.0 h1:KccukV3/1h95R0OP7vfWB3KVy9lxA5i8i3BwlA3tRpE= -github.com/getkin/kin-openapi v0.33.0/go.mod h1:ZJSfy1PxJv2QQvH9EdBj3nupRTVvV42mkW6zKUlRBwk= -github.com/ghodss/yaml v1.0.0 h1:wQHKEahhL6wmXdzwWG11gIVCkOv05bNOh+Rxn0yngAk= -github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= -github.com/gin-contrib/gzip v0.0.3 h1:etUaeesHhEORpZMp18zoOhepboiWnFtXrBZxszWUn4k= -github.com/gin-contrib/gzip v0.0.3/go.mod h1:YxxswVZIqOvcHEQpsSn+QF5guQtO1dCfy0shBPy4jFc= -github.com/gin-contrib/sse v0.1.0 h1:Y/yl/+YNO8GZSjAhjMsSuLt29uWRFHdHYUb5lYOV9qE= -github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI= -github.com/gin-contrib/static v0.0.0-20200916080430-d45d9a37d28e h1:8bZpGwoPxkaivQPrAbWl+7zjjUcbFUnYp7yQcx2r2N0= -github.com/gin-contrib/static v0.0.0-20200916080430-d45d9a37d28e/go.mod h1:VhW/Ch/3FhimwZb8Oj+qJmdMmoB8r7lmJ5auRjm50oQ= -github.com/gin-gonic/gin v1.5.0/go.mod h1:Nd6IXA8m5kNZdNEHMBd93KT+mdY3+bewLgRvmCsR2Do= -github.com/gin-gonic/gin v1.6.3/go.mod h1:75u5sXoLsGZoRN5Sgbi1eraJ4GU3++wFwWzhwvtwp4M= -github.com/gin-gonic/gin v1.7.0/go.mod h1:jD2toBW3GZUr5UMcdrwQA10I7RuaFOl/SGeDjXkfUtY= -github.com/gin-gonic/gin v1.9.0 h1:OjyFBKICoexlu99ctXNR2gg+c5pKrKMuyjgARg9qeY8= -github.com/gin-gonic/gin v1.9.0/go.mod h1:W1Me9+hsUSyj3CePGrd1/QrKJMSJ1Tu/0hFEH89961k= -github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU= -github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= -github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= -github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= -github.com/go-kit/kit v0.9.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= -github.com/go-kit/log v0.1.0/go.mod h1:zbhenjAZHb184qTLMA9ZjW7ThYL0H2mk7Q6pNt4vbaY= -github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE= -github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk= -github.com/go-logfmt/logfmt v0.5.0/go.mod h1:wCYkCAKZfumFQihp8CzCvQ3paCTfi41vtzG1KdI/P7A= -github.com/go-openapi/jsonpointer v0.19.5 h1:gZr+CIYByUqjcgeLXnQu2gHYQC9o73G2XUeOFYEICuY= -github.com/go-openapi/jsonpointer v0.19.5/go.mod h1:Pl9vOtqEWErmShwVjC8pYs9cog34VGT37dQOVbmoatg= -github.com/go-openapi/swag v0.19.5 h1:lTz6Ys4CmqqCQmZPBlbQENR1/GucA2bzYTE12Pw4tFY= -github.com/go-openapi/swag v0.19.5/go.mod h1:POnQmlKehdgb5mhVOsnJFsivZCEZ/vjK9gh66Z9tfKk= -github.com/go-playground/assert/v2 v2.0.1/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4= -github.com/go-playground/assert/v2 v2.2.0 h1:JvknZsQTYeFEAhQwI4qEt9cyV5ONwRHC+lYKSsYSR8s= -github.com/go-playground/locales v0.12.1/go.mod h1:IUMDtCfWo/w/mtMfIE/IG2K+Ey3ygWanZIBtBW0W2TM= -github.com/go-playground/locales v0.13.0/go.mod h1:taPMhCMXrRLJO55olJkUXHZBHCxTMfnGwq/HNwmWNS8= -github.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/oXslEjJA= -github.com/go-playground/locales v0.14.1/go.mod h1:hxrqLVvrK65+Rwrd5Fc6F2O76J/NuW9t0sjnWqG1slY= -github.com/go-playground/universal-translator v0.16.0/go.mod h1:1AnU7NaIRDWWzGEKwgtJRd2xk99HeFyHw3yid4rvQIY= -github.com/go-playground/universal-translator v0.17.0/go.mod h1:UkSxE5sNxxRwHyU+Scu5vgOQjsIJAF8j9muTVoKLVtA= -github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJnYK9S473LQFuzCbDbfSFY= -github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY= -github.com/go-playground/validator/v10 v10.2.0/go.mod h1:uOYAAleCW8F/7oMFd6aG0GOhaH6EGOAJShg8Id5JGkI= -github.com/go-playground/validator/v10 v10.3.0/go.mod h1:uOYAAleCW8F/7oMFd6aG0GOhaH6EGOAJShg8Id5JGkI= -github.com/go-playground/validator/v10 v10.4.1/go.mod h1:nlOn6nFhuKACm19sB/8EGNn9GlaMV7XkbRSipzJ0Ii4= -github.com/go-playground/validator/v10 v10.11.2 h1:q3SHpufmypg+erIExEKUmsgmhDTyhcJ38oeKGACXohU= -github.com/go-playground/validator/v10 v10.11.2/go.mod h1:NieE624vt4SCTJtD87arVLvdmjPAeV8BQlHtMnw9D7s= -github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= -github.com/goccy/go-json v0.10.0 h1:mXKd9Qw4NuzShiRlOXKews24ufknHO7gx30lsDyokKA= -github.com/goccy/go-json v0.10.0/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I= -github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= -github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= -github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= -github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= -github.com/golang-jwt/jwt v3.2.2+incompatible h1:IfV12K8xAKAnZqdXVzCZ+TOjboZ2keLg81eXfW3O+oY= -github.com/golang-jwt/jwt v3.2.2+incompatible/go.mod h1:8pz2t5EyA70fFQQSrl6XZXzqecmYZeUEB8OUGHkxJ+I= -github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= -github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= -github.com/golang/groupcache v0.0.0-20191227052852-215e87163ea7/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= -github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= -github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= -github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= -github.com/golang/mock v1.3.1/go.mod h1:sBzyDLLjw3U8JLTeZvSv8jJB+tU5PVekmnlKIyFUx0Y= -github.com/golang/mock v1.4.0/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= -github.com/golang/mock v1.4.1/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= -github.com/golang/mock v1.4.3/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= -github.com/golang/mock v1.4.4/go.mod h1:l3mdAwkq5BuhzHwde/uurv3sEJeZMXNpwsxVWU71h+4= -github.com/golang/mock v1.5.0/go.mod h1:CWnOUgYIOo4TcNZ0wHX3YZCqsaM1I1Jvs6v3mP3KVu8= -github.com/golang/mock v1.6.0/go.mod h1:p6yTPP+5HYm5mzsMV8JkE6ZKdX+/wYM6Hr+LicevLPs= -github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= -github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= -github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= -github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= -github.com/golang/protobuf v1.3.4/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= -github.com/golang/protobuf v1.3.5/go.mod h1:6O5/vntMXwX2lRkT1hjjk0nAC1IDOTvTlVgjlRvqsdk= -github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8= -github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA= -github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs= -github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w= -github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0= -github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8= -github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= -github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= -github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= -github.com/golang/protobuf v1.5.1/go.mod h1:DopwsBzvsk0Fs44TXzsVbJyPhcCPeIwnvohx4u74HPM= -github.com/golang/protobuf v1.5.2 h1:ROPKBNFfQgOUMifHyP+KYbvpjbdoFNs+aK7DXlji0Tw= -github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= -github.com/golang/snappy v0.0.3/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= -github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= -github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= -github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= -github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= -github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= -github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/go-cmp v0.4.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/go-cmp v0.5.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/go-cmp v0.5.3/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/go-cmp v0.5.7/go.mod h1:n+brtR0CgQNWTVd5ZUFpTBC8YFBDLK/h/bpaJ8/DtOE= -github.com/google/go-cmp v0.5.8 h1:e6P7q2lk1O+qJJb4BtCQXlK8vWEO8V1ZeuEdJNOqZyg= -github.com/google/go-cmp v0.5.8/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= -github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= -github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs= -github.com/google/martian/v3 v3.0.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0= -github.com/google/martian/v3 v3.1.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0= -github.com/google/martian/v3 v3.2.1/go.mod h1:oBOf6HBosgwRXnUGWUB05QECsc6uvmMiJ3+6W4l/CUk= -github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= -github.com/google/pprof v0.0.0-20190515194954-54271f7e092f/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= -github.com/google/pprof v0.0.0-20191218002539-d4f498aebedc/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= -github.com/google/pprof v0.0.0-20200212024743-f11f1df84d12/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= -github.com/google/pprof v0.0.0-20200229191704-1ebb73c60ed3/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= -github.com/google/pprof v0.0.0-20200430221834-fc25d7d30c6d/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= -github.com/google/pprof v0.0.0-20200708004538-1a94d8640e99/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= -github.com/google/pprof v0.0.0-20201023163331-3e6fc7fc9c4c/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= -github.com/google/pprof v0.0.0-20201203190320-1bf35d6f28c2/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= -github.com/google/pprof v0.0.0-20210122040257-d980be63207e/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= -github.com/google/pprof v0.0.0-20210226084205-cbba55b83ad5/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= -github.com/google/pprof v0.0.0-20210601050228-01bbb1931b22/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= -github.com/google/pprof v0.0.0-20210609004039-a478d1d731e9/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= -github.com/google/pprof v0.0.0-20210720184732-4bb14d4b1be1/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= -github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= -github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= -github.com/googleapis/enterprise-certificate-proxy v0.0.0-20220520183353-fd19c99a87aa/go.mod h1:17drOmN3MwGY7t0e+Ei9b45FFGA3fBs3x36SsCg1hq8= -github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg= -github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk= -github.com/googleapis/gax-go/v2 v2.1.0/go.mod h1:Q3nei7sK6ybPYH7twZdmQpAd1MKb7pfu6SK+H1/DsU0= -github.com/googleapis/gax-go/v2 v2.1.1/go.mod h1:hddJymUZASv3XPyGkUpKj8pPO47Rmb0eJc8R6ouapiM= -github.com/googleapis/gax-go/v2 v2.2.0/go.mod h1:as02EH8zWkzwUoLbBaFeQ+arQaj/OthfcblKl4IGNaM= -github.com/googleapis/gax-go/v2 v2.3.0/go.mod h1:b8LNqSzNabLiUpXKkY7HAR5jr6bIT99EXz9pXxye9YM= -github.com/googleapis/gax-go/v2 v2.4.0/go.mod h1:XOTVJ59hdnfJLIP/dh8n5CGryZR2LxK9wbMD5+iXC6c= -github.com/googleapis/go-type-adapters v1.0.0/go.mod h1:zHW75FOG2aur7gAO2B+MLby+cLsWGBF62rFAi7WjWO4= -github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1 h1:EGx4pi6eqNxGaHF6qqu48+N2wcFQ5qg5FXgOdqsJ5d8= -github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY= -github.com/gorilla/securecookie v1.1.1 h1:miw7JPhV+b/lAHSXz4qd/nN9jRiAFV5FwjeKyCS8BvQ= -github.com/gorilla/securecookie v1.1.1/go.mod h1:ra0sb63/xPlUeL+yeDciTfxMRAA+MP+HVt/4epWDjd4= -github.com/gorilla/sessions v1.2.1 h1:DHd3rPN5lE3Ts3D8rKkQ8x/0kqfeNmBAaiSi+o7FsgI= -github.com/gorilla/sessions v1.2.1/go.mod h1:dk2InVEVJ0sfLlnXv9EAgkf6ecYs/i80K/zI+bUmuGM= -github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0/go.mod h1:8NvIoxWQoOIhqOTXgfV/d3M/q6VIi02HzZEHgUlZvzk= -github.com/grpc-ecosystem/grpc-gateway v1.16.0/go.mod h1:BDjrQk3hbvj6Nolgz8mAMFbcEtjT1g+wF4CSlocrBnw= -github.com/hashicorp/consul/api v1.1.0/go.mod h1:VmuI/Lkw1nC05EYQWNKwWGbkg+FbDBtguAZLlVdkD9Q= -github.com/hashicorp/consul/sdk v0.1.1/go.mod h1:VKf9jXwCTEY1QZP2MOLRhb5i/I/ssyNV1vwHyQBF0x8= -github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= -github.com/hashicorp/go-cleanhttp v0.5.1/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80= -github.com/hashicorp/go-immutable-radix v1.0.0/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60= -github.com/hashicorp/go-msgpack v0.5.3/go.mod h1:ahLV/dePpqEmjfWmKiqvPkv/twdG7iPBM1vqhUKIvfM= -github.com/hashicorp/go-multierror v1.0.0/go.mod h1:dHtQlpGsu+cZNNAkkCN/P3hoUDHhCYQXV3UM06sGGrk= -github.com/hashicorp/go-rootcerts v1.0.0/go.mod h1:K6zTfqpRlCUIjkwsN4Z+hiSfzSTQa6eBIzfwKfwNnHU= -github.com/hashicorp/go-sockaddr v1.0.0/go.mod h1:7Xibr9yA9JjQq1JpNB2Vw7kxv8xerXegt+ozgdvDeDU= -github.com/hashicorp/go-syslog v1.0.0/go.mod h1:qPfqrKkXGihmCqbJM2mZgkZGvKG1dFdvsLplgctolz4= -github.com/hashicorp/go-uuid v1.0.0/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= -github.com/hashicorp/go-uuid v1.0.1/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= -github.com/hashicorp/go.net v0.0.1/go.mod h1:hjKkEWcCURg++eb33jQU7oqQcI9XDCnUzHA0oac0k90= -github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= -github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= -github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4= -github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ= -github.com/hashicorp/logutils v1.0.0/go.mod h1:QIAnNjmIWmVIIkWDTG1z5v++HQmx9WQRO+LraFDTW64= -github.com/hashicorp/mdns v1.0.0/go.mod h1:tL+uN++7HEJ6SQLQ2/p+z2pH24WQKWjBPkE0mNTz8vQ= -github.com/hashicorp/memberlist v0.1.3/go.mod h1:ajVTdAv/9Im8oMAAj5G31PhhMCZJV2pPBoIllUwCN7I= -github.com/hashicorp/serf v0.8.2/go.mod h1:6hOLApaqBFA1NXqRQAsxw9QxuDEvNxSQRwA/JwenrHc= -github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= -github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= -github.com/inconshreveable/mousetrap v1.0.1 h1:U3uMjPSQEBMNp1lFxmllqCPM6P5u/Xq7Pgzkat/bFNc= -github.com/inconshreveable/mousetrap v1.0.1/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= -github.com/jessevdk/go-flags v1.4.0/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI= -github.com/jpillora/backoff v1.0.0/go.mod h1:J/6gKK9jxlEcS3zixgDgUAsiuZ7yrSoa/FX5e0EB2j4= -github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= -github.com/json-iterator/go v1.1.7/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= -github.com/json-iterator/go v1.1.9/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= -github.com/json-iterator/go v1.1.10/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= -github.com/json-iterator/go v1.1.11/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= -github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM= -github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= -github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU= -github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk= -github.com/jtolds/gls v4.20.0+incompatible h1:xdiiI2gbIgH/gLH7ADydsJ1uDOEzR8yvV7C0MuV77Wo= -github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU= -github.com/juliangruber/go-intersect v1.1.0 h1:sc+y5dCjMMx0pAdYk/N6KBm00tD/f3tq+Iox7dYDUrY= -github.com/juliangruber/go-intersect v1.1.0/go.mod h1:WMau+1kAmnlQnKiikekNJbtGtfmILU/mMU6H7AgKbWQ= -github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w= -github.com/julienschmidt/httprouter v1.3.0/go.mod h1:JR6WtHb+2LUe8TCKY3cZOxFyyO8IZAc4RVcycCCAKdM= -github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8= -github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= -github.com/klauspost/cpuid/v2 v2.0.9 h1:lgaqFMSdTdQYdZ04uHyN2d/eKdOMyi2YLSvlQIBFYa4= -github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg= -github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= -github.com/konsorten/go-windows-terminal-sequences v1.0.3/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= -github.com/kr/fs v0.1.0/go.mod h1:FFnZGqtBN9Gxj7eW1uZ42v5BccTP0vu6NEaFoC2HwRg= -github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc= -github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= -github.com/kr/pretty v0.2.0/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= -github.com/kr/pretty v0.3.0 h1:WgNl7dwNpEZ6jJ9k1snq4pZsg7DOEN8hP9Xw0Tsjwk0= -github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= -github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= -github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= -github.com/leodido/go-urn v1.1.0/go.mod h1:+cyI34gQWZcE1eQU7NVgKkkzdXDQHr1dBMtdAPozLkw= -github.com/leodido/go-urn v1.2.0/go.mod h1:+8+nEpDfqqsY+g338gtMEUOtuK+4dEMhiQEgxpxOKII= -github.com/leodido/go-urn v1.2.1 h1:BqpAaACuzVSgi/VLzGZIobT2z4v53pjosyNd9Yv6n/w= -github.com/leodido/go-urn v1.2.1/go.mod h1:zt4jvISO2HfUBqxjfIshjdMTYS56ZS/qv49ictyFfxY= -github.com/magiconair/properties v1.8.5 h1:b6kJs+EmPFMYGkow9GiUyCyOvIwYetYJ3fSaWak/Gls= -github.com/magiconair/properties v1.8.5/go.mod h1:y3VJvCyxH9uVvJTWEGAELF3aiYNyPKd5NZ3oSwXrF60= -github.com/mailru/easyjson v0.0.0-20190614124828-94de47d64c63/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= -github.com/mailru/easyjson v0.0.0-20190626092158-b2ccc519800e h1:hB2xlXdHp/pmPZq0y3QnmWAArdw9PqbmotexnWx/FU8= -github.com/mailru/easyjson v0.0.0-20190626092158-b2ccc519800e/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= -github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU= -github.com/mattn/go-isatty v0.0.3/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4= -github.com/mattn/go-isatty v0.0.9/go.mod h1:YNRxwqDuOph6SZLI9vUUz6OYw3QyUt7WiY2yME+cCiQ= -github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU= -github.com/mattn/go-isatty v0.0.17 h1:BTarxUcIeDqL27Mc+vyvdWYSL28zpIhv3RoTdsLMPng= -github.com/mattn/go-isatty v0.0.17/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= -github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= -github.com/miekg/dns v1.0.14/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg= -github.com/mitchellh/cli v1.0.0/go.mod h1:hNIlj7HEI86fIcpObd7a0FcrxTWetlwJDGcceTlRvqc= -github.com/mitchellh/go-homedir v1.0.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= -github.com/mitchellh/go-testing-interface v1.0.0/go.mod h1:kRemZodwjscx+RGhAo8eIhFbs2+BFgRtFPeD/KE+zxI= -github.com/mitchellh/gox v0.4.0/go.mod h1:Sd9lOJ0+aimLBi73mGofS1ycjY8lL3uZM3JPS42BGNg= -github.com/mitchellh/iochan v1.0.0/go.mod h1:JwYml1nuB7xOzsp52dPpHFffvOCDupsG0QubkSMEySY= -github.com/mitchellh/mapstructure v0.0.0-20160808181253-ca63d7c062ee/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= -github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= -github.com/mitchellh/mapstructure v1.4.1 h1:CpVNEelQCZBooIPDn+AR3NpivK/TIKU8bDxdASFVQag= -github.com/mitchellh/mapstructure v1.4.1/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= -github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= -github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg= -github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= -github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= -github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= -github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M= -github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= -github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= -github.com/mwitkow/go-conntrack v0.0.0-20190716064945-2f068394615f/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= -github.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc= -github.com/pelletier/go-toml v1.9.3 h1:zeC5b1GviRUyKYd6OJPvBU/mcVDVoL1OhT17FCt5dSQ= -github.com/pelletier/go-toml v1.9.3/go.mod h1:u1nR/EPcESfeI/szUZKdtJ0xRNbUoANCkoOuaOx1Y+c= -github.com/pelletier/go-toml/v2 v2.0.6 h1:nrzqCb7j9cDFj2coyLNLaZuJTLjWjlaz6nvTvIwycIU= -github.com/pelletier/go-toml/v2 v2.0.6/go.mod h1:eumQOmlWiOPt5WriQQqoM5y18pDHwha2N+QD+EUNTek= -github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= -github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= -github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= -github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= -github.com/pkg/sftp v1.10.1/go.mod h1:lYOWFsE0bwd1+KfKJaKeuokY15vzFx25BLbzYYoAxZI= -github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= -github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= -github.com/posener/complete v1.1.1/go.mod h1:em0nMJCgc9GFtwrmVmEMR/ZL6WyhyjMBndrE9hABlRI= -github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw= -github.com/prometheus/client_golang v1.0.0/go.mod h1:db9x61etRT2tGnBNRi70OPL5FsnadC4Ky3P0J6CfImo= -github.com/prometheus/client_golang v1.7.1/go.mod h1:PY5Wy2awLA44sXw4AOSfFBetzPP4j5+D6mVACh+pe2M= -github.com/prometheus/client_golang v1.11.1/go.mod h1:Z6t4BnS23TR94PD6BsDNk8yVqroYurpAkEiz0P2BEV0= -github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo= -github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= -github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= -github.com/prometheus/client_model v0.2.0/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= -github.com/prometheus/common v0.4.1/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4= -github.com/prometheus/common v0.10.0/go.mod h1:Tlit/dnDKsSWFlCLTWaA1cyBgKHSMdTB80sz/V91rCo= -github.com/prometheus/common v0.26.0/go.mod h1:M7rCNAaPfAosfx8veZJCuw84e35h3Cfd9VFqTh1DIvc= -github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= -github.com/prometheus/procfs v0.0.2/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA= -github.com/prometheus/procfs v0.1.3/go.mod h1:lV6e/gmhEcM9IjHGsFOCxxuZ+z1YqCvr4OA4YeYWdaU= -github.com/prometheus/procfs v0.6.0/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1xBZuNvfVA= -github.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6LYCDYWNEvQ= -github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= -github.com/rogpeppe/go-internal v1.8.0 h1:FCbCCtXNOY3UtUuHUYaghJg4y7Fd14rXifAYUAtL9R8= -github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= -github.com/ryanuber/columnize v0.0.0-20160712163229-9b3edd62028f/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts= -github.com/satori/go.uuid v1.2.0 h1:0uYX9dsZ2yD7q2RtLRtPSdGDWzjeM3TbMJP9utgA0ww= -github.com/satori/go.uuid v1.2.0/go.mod h1:dA0hQrYB0VpLJoorglMZABFdXlWrHn1NEOzdhQKdks0= -github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg65j358z/aeFdxmN0P9QXhEzd20vsDc= -github.com/shiningrush/droplet v0.2.4/go.mod h1:akW2vIeamvMD6zj6wIBfzYn6StGXBxwlW3gA+hcHu5M= -github.com/shiningrush/droplet v0.3.0 h1:Pqm+TmpiZLwMfNH3XEYn59nsY3Jh5ioc8Btp+JCHSRo= -github.com/shiningrush/droplet v0.3.0/go.mod h1:gUZc/LriGh+fIzGeYTbGSoHv2kNcDg2BhgXVh5YSOaM= -github.com/shiningrush/droplet/wrapper/gin v0.3.0 h1:QVj6qkxef0WDDECRJVD9/UP/GnPDHtWSEUrEvfUOOwc= -github.com/shiningrush/droplet/wrapper/gin v0.3.0/go.mod h1:Y3nOchWIqL8rGi9GR9wR8yOtRFct/xsXSf7NydvWzVw= -github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo= -github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE= -github.com/sirupsen/logrus v1.6.0/go.mod h1:7uNnSEd1DgxDLC74fIahvMZmmYsHGZGEOFrfsX/uA88= -github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d h1:zE9ykElWQ6/NYmHa3jpm/yHnI4xSofP+UP6SpjHcSeM= -github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc= -github.com/smartystreets/goconvey v1.6.4 h1:fv0U8FUIMPNf1L9lnHLvLhgicrIVChEkdzIKYqbNC9s= -github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA= -github.com/sony/sonyflake v1.0.0 h1:MpU6Ro7tfXwgn2l5eluf9xQvQJDROTBImNCfRXn/YeM= -github.com/sony/sonyflake v1.0.0/go.mod h1:Jv3cfhf/UFtolOTTRd3q4Nl6ENqM+KfyZ5PseKfZGF4= -github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA= -github.com/spf13/afero v1.6.0 h1:xoax2sJ2DT8S8xA2paPFjDCScCNeWsg75VG0DLRreiY= -github.com/spf13/afero v1.6.0/go.mod h1:Ai8FlHk4v/PARR026UzYexafAt9roJ7LcLMAmO6Z93I= -github.com/spf13/cast v1.3.1 h1:nFm6S0SMdyzrzcmThSipiEubIDy8WEXKNZ0UOgiRpng= -github.com/spf13/cast v1.3.1/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE= -github.com/spf13/cobra v1.6.1 h1:o94oiPyS4KD1mPy2fmcYYHHfCxLqYjJOhGsCHFZtEzA= -github.com/spf13/cobra v1.6.1/go.mod h1:IOw/AERYS7UzyrGinqmz6HLUo219MORXGxhbaJUqzrY= -github.com/spf13/jwalterweatherman v1.1.0 h1:ue6voC5bR5F8YxI5S67j9i582FU4Qvo2bmqnqMYADFk= -github.com/spf13/jwalterweatherman v1.1.0/go.mod h1:aNWZUN0dPAAO/Ljvb5BEdw96iTZ0EXowPYD95IqWIGo= -github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= -github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= -github.com/spf13/viper v1.8.1 h1:Kq1fyeebqsBfbjZj4EL7gj2IO0mMaiyjYUWcUsl2O44= -github.com/spf13/viper v1.8.1/go.mod h1:o0Pch8wJ9BVSWGQMbra6iw0oQ5oktSIBaujf1rJH9Ns= -github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= -github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= -github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= -github.com/stretchr/objx v0.5.0 h1:1zr/of2m5FGMsad5YfcqgdqdWrIhu+EBEJRhR1U7z/c= -github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= -github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= -github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= -github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= -github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= -github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= -github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= -github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= -github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= -github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= -github.com/stretchr/testify v1.8.2 h1:+h33VjcLVPDHtOdpUCuF+7gSuG3yGIftsP1YvFihtJ8= -github.com/stretchr/testify v1.8.2/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= -github.com/subosito/gotenv v1.2.0 h1:Slr1R9HxAlEKefgq5jn9U+DnETlIUa6HfgEzj0g5d7s= -github.com/subosito/gotenv v1.2.0/go.mod h1:N0PQaV/YGNqwC0u51sEeR/aUtSLEXKX9iv69rRypqCw= -github.com/tidwall/gjson v1.14.4 h1:uo0p8EbA09J7RQaflQ1aBRffTR7xedD2bcIVSYxLnkM= -github.com/tidwall/gjson v1.14.4/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk= -github.com/tidwall/match v1.1.1 h1:+Ho715JplO36QYgwN9PGYNhgZvoUSc9X2c80KVTi+GA= -github.com/tidwall/match v1.1.1/go.mod h1:eRSPERbgtNPcGhD8UCthc6PmLEQXEWd3PRB5JTxsfmM= -github.com/tidwall/pretty v1.2.0 h1:RWIZEg2iJ8/g6fDDYzMpobmaoGh5OLl4AXtGUGPcqCs= -github.com/tidwall/pretty v1.2.0/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU= -github.com/twitchyliquid64/golang-asm v0.15.1 h1:SU5vSMR7hnwNxj24w34ZyCi/FmDZTkS4MhqMhdFk5YI= -github.com/twitchyliquid64/golang-asm v0.15.1/go.mod h1:a1lVb/DtPvCB8fslRZhAngC2+aY1QWCk3Cedj/Gdt08= -github.com/ugorji/go v1.1.7/go.mod h1:kZn38zHttfInRq0xu/PH0az30d+z6vm202qpg1oXVMw= -github.com/ugorji/go/codec v1.1.7/go.mod h1:Ax+UKWsSmolVDwsd+7N3ZtXu+yMGCf907BLYF3GoBXY= -github.com/ugorji/go/codec v1.2.9 h1:rmenucSohSTiyL09Y+l2OCk+FrMxGMzho2+tjr5ticU= -github.com/ugorji/go/codec v1.2.9/go.mod h1:UNopzCgEMSXjBc6AOMqYvWC1ktqTAfzJZUZgYf6w6lg= -github.com/xeipuuv/gojsonpointer v0.0.0-20180127040702-4e3ac2762d5f h1:J9EGpcZtP0E/raorCMxlFGSTBrsSlaDGf3jU/qvAE2c= -github.com/xeipuuv/gojsonpointer v0.0.0-20180127040702-4e3ac2762d5f/go.mod h1:N2zxlSyiKSe5eX1tZViRH5QA0qijqEDrYZiPEAiq3wU= -github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415 h1:EzJWgHovont7NscjpAxXsDA8S8BMYve8Y5+7cuRE7R0= -github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415/go.mod h1:GwrjFmJcFw6At/Gs6z4yjiIwzuJ1/+UwLxMQDVQXShQ= -github.com/xeipuuv/gojsonschema v1.2.0 h1:LhYJRs+L4fBtjZUfuSZIKGeVu0QRy8e5Xi7D17UxZ74= -github.com/xeipuuv/gojsonschema v1.2.0/go.mod h1:anYRn/JVcOK2ZgGU+IjEV4nwlhoK5sQluxsYJ78Id3Y= -github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= -github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= -github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= -github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= -github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= -github.com/yuin/gopher-lua v0.0.0-20200816102855-ee81675732da h1:NimzV1aGyq29m5ukMK0AMWEhFaL/lrEOaephfuoiARg= -github.com/yuin/gopher-lua v0.0.0-20200816102855-ee81675732da/go.mod h1:E1AXubJBdNmFERAOucpDIxNzeGfLzg0mYh+UfMWdChA= -go.etcd.io/etcd/api/v3 v3.5.0/go.mod h1:cbVKeC6lCfl7j/8jBhAK6aIYO9XOjdptoxU/nLQcPvs= -go.etcd.io/etcd/api/v3 v3.5.5 h1:BX4JIbQ7hl7+jL+g+2j5UAr0o1bctCm6/Ct+ArBGkf0= -go.etcd.io/etcd/api/v3 v3.5.5/go.mod h1:KFtNaxGDw4Yx/BA4iPPwevUTAuqcsPxzyX8PHydchN8= -go.etcd.io/etcd/client/pkg/v3 v3.5.0/go.mod h1:IJHfcCEKxYu1Os13ZdwCwIUTUVGYTSAM3YSwc9/Ac1g= -go.etcd.io/etcd/client/pkg/v3 v3.5.5 h1:9S0JUVvmrVl7wCF39iTQthdaaNIiAaQbmK75ogO6GU8= -go.etcd.io/etcd/client/pkg/v3 v3.5.5/go.mod h1:ggrwbk069qxpKPq8/FKkQ3Xq9y39kbFR4LnKszpRXeQ= -go.etcd.io/etcd/client/v2 v2.305.0/go.mod h1:h9puh54ZTgAKtEbut2oe9P4L/oqKCVB6xsXlzd7alYQ= -go.etcd.io/etcd/client/v3 v3.5.5 h1:q++2WTJbUgpQu4B6hCuT7VkdwaTP7Qz6Daak3WzbrlI= -go.etcd.io/etcd/client/v3 v3.5.5/go.mod h1:aApjR4WGlSumpnJ2kloS75h6aHUmAyaPLjHMxpc7E7c= -go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU= -go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8= -go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= -go.opencensus.io v0.22.3/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= -go.opencensus.io v0.22.4/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= -go.opencensus.io v0.22.5/go.mod h1:5pWMHQbX5EPX2/62yrJeAkowc+lfs/XD7Uxpq3pI6kk= -go.opencensus.io v0.23.0/go.mod h1:XItmlyltB5F7CS4xOC1DcqMoFqwtC6OG2xF7mCv7P7E= -go.opentelemetry.io/proto/otlp v0.7.0/go.mod h1:PqfVotwruBrMGOCsRd/89rSnXhoiJIqeYNgFYFoEGnI= -go.uber.org/atomic v1.7.0 h1:ADUqmZGgLDDfbSL9ZmPxKTybcoEYHgpYfELNoN+7hsw= -go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc= -go.uber.org/multierr v1.6.0 h1:y6IPFStTAIT5Ytl7/XYmHvzXQ7S3g/IeZW9hyZ5thw4= -go.uber.org/multierr v1.6.0/go.mod h1:cdWPpRnG4AhwMwsgIHip0KRBQjJy5kYEpYjJxpXp9iU= -go.uber.org/zap v1.17.0 h1:MTjgFu6ZLKvY6Pvaqk97GlxNBuMpV4Hy/3P6tRGlI2U= -go.uber.org/zap v1.17.0/go.mod h1:MXVU+bhUf/A7Xi2HNOnopQOrmycQ5Ih87HtOu4q5SSo= -golang.org/x/arch v0.0.0-20210923205945-b76863e36670 h1:18EFjUmQOcUvxNYSkA6jO9VAiXCnxFY6NyDX0bHDmkU= -golang.org/x/arch v0.0.0-20210923205945-b76863e36670/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8= -golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= -golang.org/x/crypto v0.0.0-20181029021203-45a5f77698d3/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= -golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= -golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= -golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= -golang.org/x/crypto v0.0.0-20190820162420-60c769a6c586/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= -golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= -golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= -golang.org/x/crypto v0.5.0 h1:U/0M97KRkSFvyD/3FSmdP5W5swImpNgle/EHFhOsQPE= -golang.org/x/crypto v0.5.0/go.mod h1:NK/OQwhpMQP3MwtdjgLlYHnH9ebylxKWv3e0fK+mkQU= -golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= -golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= -golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8= -golang.org/x/exp v0.0.0-20190829153037-c13cbed26979/go.mod h1:86+5VVa7VpoJ4kLfm080zCjGlMRFzhUhsZKEZO7MGek= -golang.org/x/exp v0.0.0-20191030013958-a1ab85dbe136/go.mod h1:JXzH8nQsPlswgeRAPE3MuO9GYsAcnJvJ4vnMwN/5qkY= -golang.org/x/exp v0.0.0-20191129062945-2f5052295587/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= -golang.org/x/exp v0.0.0-20191227195350-da58074b4299/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= -golang.org/x/exp v0.0.0-20200119233911-0405dc783f0a/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= -golang.org/x/exp v0.0.0-20200207192155-f17229e696bd/go.mod h1:J/WKrq2StrnmMY6+EHIKF9dgMWnmCNThgcyBT1FY9mM= -golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6/go.mod h1:3jZMyOhIsHpP37uCMkUooju7aAi5cS1Q23tOzKc+0MU= -golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js= -golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= -golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= -golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= -golang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= -golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= -golang.org/x/lint v0.0.0-20190409202823-959b441ac422/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= -golang.org/x/lint v0.0.0-20190909230951-414d861bb4ac/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= -golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= -golang.org/x/lint v0.0.0-20191125180803-fdd1cda4f05f/go.mod h1:5qLYkcX4OjUUV8bRuDixDT3tpyyb+LUpUlRWLxfhWrs= -golang.org/x/lint v0.0.0-20200130185559-910be7a94367/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= -golang.org/x/lint v0.0.0-20200302205851-738671d3881b/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= -golang.org/x/lint v0.0.0-20201208152925-83fdc39ff7b5/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= -golang.org/x/lint v0.0.0-20210508222113-6edffad5e616/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= -golang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE= -golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o= -golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc= -golang.org/x/mod v0.1.0/go.mod h1:0QHyrYULN0/3qlju5TqG8bIK38QM8yzMo5ekMj3DlcY= -golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= -golang.org/x/mod v0.1.1-0.20191107180719-034126e5016b/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= -golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= -golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= -golang.org/x/mod v0.4.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= -golang.org/x/mod v0.4.1/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= -golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= -golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= -golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= -golang.org/x/net v0.0.0-20181023162649-9b4f9f5ad519/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= -golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= -golang.org/x/net v0.0.0-20181201002055-351d144fa1fc/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= -golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= -golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= -golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= -golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= -golang.org/x/net v0.0.0-20190501004415-9ce7a6920f09/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= -golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= -golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks= -golang.org/x/net v0.0.0-20190613194153-d28f0bde5980/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20190628185345-da137c7871d7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20190724013045-ca1201d0de80/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20191209160850-c0dbc17a3553/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20200114155413-6afb5195e5aa/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20200222125558-5a598a2470a0/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20200301022130-244492dfa37a/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20200324143707-d3edc9973b7e/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= -golang.org/x/net v0.0.0-20200501053045-e0ff5e5a1de5/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= -golang.org/x/net v0.0.0-20200506145744-7e3656a0809f/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= -golang.org/x/net v0.0.0-20200513185701-a91f0712d120/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= -golang.org/x/net v0.0.0-20200520182314-0ba52f642ac2/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= -golang.org/x/net v0.0.0-20200625001655-4c5254603344/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= -golang.org/x/net v0.0.0-20200707034311-ab3426394381/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= -golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= -golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= -golang.org/x/net v0.0.0-20201031054903-ff519b6c9102/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= -golang.org/x/net v0.0.0-20201110031124-69a78807bb2b/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= -golang.org/x/net v0.0.0-20201209123823-ac852fbbde11/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= -golang.org/x/net v0.0.0-20210119194325-5f4716e94777/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= -golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= -golang.org/x/net v0.0.0-20210316092652-d523dce5a7f4/go.mod h1:RBQZq4jEuRlivfhVLdyRGr576XBO4/greRjx4P4O3yc= -golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM= -golang.org/x/net v0.0.0-20210503060351-7fd8e65b6420/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= -golang.org/x/net v0.0.0-20220127200216-cd36cc0744dd/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= -golang.org/x/net v0.0.0-20220225172249-27dd8689420f/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= -golang.org/x/net v0.0.0-20220325170049-de3da57026de/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= -golang.org/x/net v0.0.0-20220412020605-290c469a71a5/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= -golang.org/x/net v0.0.0-20220425223048-2871e0cb64e4/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= -golang.org/x/net v0.0.0-20220607020251-c690dde0001d/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= -golang.org/x/net v0.0.0-20220624214902-1bab6f366d9e/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= -golang.org/x/net v0.0.0-20220826154423-83b083e8dc8b/go.mod h1:YDH+HFinaLZZlnHAfSS6ZXJJ9M9t4Dl22yv3iI2vPwk= -golang.org/x/net v0.7.0 h1:rJrUqqhjsgNp7KqAIc25s9pZnjU7TUcSY7HcVZjdn1g= -golang.org/x/net v0.7.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= -golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= -golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= -golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= -golang.org/x/oauth2 v0.0.0-20191202225959-858c2ad4c8b6/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= -golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= -golang.org/x/oauth2 v0.0.0-20200902213428-5d25da1a8d43/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= -golang.org/x/oauth2 v0.0.0-20201109201403-9fd604954f58/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= -golang.org/x/oauth2 v0.0.0-20201208152858-08078c50e5b5/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= -golang.org/x/oauth2 v0.0.0-20210218202405-ba52d332ba99/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= -golang.org/x/oauth2 v0.0.0-20210220000619-9bb904979d93/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= -golang.org/x/oauth2 v0.0.0-20210313182246-cd4f82c27b84/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= -golang.org/x/oauth2 v0.0.0-20210402161424-2e8d93401602/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= -golang.org/x/oauth2 v0.0.0-20210514164344-f6687ab2804c/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= -golang.org/x/oauth2 v0.0.0-20210628180205-a41e5a781914/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= -golang.org/x/oauth2 v0.0.0-20210805134026-6f1e6394065a/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= -golang.org/x/oauth2 v0.0.0-20210819190943-2bc19b11175f/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= -golang.org/x/oauth2 v0.0.0-20211104180415-d3ed0bb246c8/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= -golang.org/x/oauth2 v0.0.0-20220223155221-ee480838109b/go.mod h1:DAh4E804XQdzx2j+YRIaUnCqCV2RuMz24cGBJ5QYIrc= -golang.org/x/oauth2 v0.0.0-20220309155454-6242fa91716a/go.mod h1:DAh4E804XQdzx2j+YRIaUnCqCV2RuMz24cGBJ5QYIrc= -golang.org/x/oauth2 v0.0.0-20220411215720-9780585627b5/go.mod h1:DAh4E804XQdzx2j+YRIaUnCqCV2RuMz24cGBJ5QYIrc= -golang.org/x/oauth2 v0.0.0-20220608161450-d0670ef3b1eb/go.mod h1:jaDAt6Dkxork7LmZnYtzbRWj0W47D86a3TGe0YHBvmE= -golang.org/x/oauth2 v0.0.0-20220822191816-0ebed06d0094 h1:2o1E+E8TpNLklK9nHiPiK1uzIYrIHt+cQx3ynCwq9V8= -golang.org/x/oauth2 v0.0.0-20220822191816-0ebed06d0094/go.mod h1:h4gKUeWbJ4rQPri7E0u6Gs4e9Ri2zaLxzw5DI5XGrYg= -golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20220601150217-0de741cfad7f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sys v0.0.0-20180823144017-11551d06cbcc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20181026203630-95b1ffbd15a5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20190204203706-41f3e6584952/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190813064441-fde4db37ae7a/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20191001151750-bb3f8db39f24/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20191228213918-04cbcbbfeed8/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200106162015-b016eb3dc98e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200113162924-86b910548bc1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200122134326-e047566fdf82/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200202164722-d101bd2416d5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200212091648-12a6c2dcc1e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200302150141-5c8b2ff67527/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200331124033-c3d80250170d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200501052902-10377860bb8e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200511232937-7e40ca221e25/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200515095857-1151b9dac4a9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200523222454-059865788121/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200615200032-f1bc736245b1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200625212154-ddb9806d33ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200803210538-64077c9b5642/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200905004654-be1d3432aa8f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200909081042-eff7692f9009/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-20201201145000-ef89a241ccb3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210104204734-6f8348627aad/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210119212857-b64e53b001e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210220050731-9a76102bfb43/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210305230114-8fe3ee5dd75b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210315160823-c6e025ad8005/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210320140829-1e4c9ba3b0c4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210403161142-5e06dd20ab57/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20210514084401-e8d321eab015/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20210603081109-ebe580a85c40/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20210603125802-9665404d3644/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20210616094352-59db8d763f22/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20210806184541-e5e7981a1069/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20210823070655-63515b42dcdf/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20210908233432-aa78b53d3365/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20211124211545-fe61309f8881/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20211210111614-af8b64212486/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220128215802-99c3d69c2c27/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220209214540-3681064d5158/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220227234510-4e6760a101f9/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220328115105-d36c6a25d886/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220412211240-33da011f77ad/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220502124256-b6088ccd6cba/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220503163025-988cb79eb6c6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220610221304-9f5ed59c137d/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220728004956-3c1f35247d10/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.5.0 h1:MUK/U/4lj1t1oPg0HfuXDN/Z1wv31ZJ/YcPiGccS4DU= -golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= -golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= -golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= -golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= -golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= -golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= -golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= -golang.org/x/text v0.3.4/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= -golang.org/x/text v0.3.5/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= -golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= -golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= -golang.org/x/text v0.7.0 h1:4BRB4x83lYWy72KwLD/qYDuTu7q9PjSagHvijDw7cLo= -golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= -golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= -golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= -golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= -golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= -golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= -golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= -golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= -golang.org/x/tools v0.0.0-20190312151545-0bb0c0a6e846/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= -golang.org/x/tools v0.0.0-20190312170243-e65039ee4138/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= -golang.org/x/tools v0.0.0-20190328211700-ab21143f2384/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= -golang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= -golang.org/x/tools v0.0.0-20190506145303-2d16b83fe98c/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= -golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= -golang.org/x/tools v0.0.0-20190606124116-d0a3d012864b/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= -golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= -golang.org/x/tools v0.0.0-20190628153133-6cdbf07be9d0/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= -golang.org/x/tools v0.0.0-20190816200558-6889da9d5479/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20190911174233-4f2ddba30aff/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20191012152004-8de300cfc20a/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20191112195655-aa38f8e97acc/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20191113191852-77e3bb0ad9e7/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20191115202509-3a792d9c32b2/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20191125144606-a911d9008d1f/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20191130070609-6e064ea0cf2d/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20191216173652-a0e659d51361/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= -golang.org/x/tools v0.0.0-20191227053925-7b8e75db28f4/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= -golang.org/x/tools v0.0.0-20200117161641-43d50277825c/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= -golang.org/x/tools v0.0.0-20200122220014-bf1340f18c4a/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= -golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= -golang.org/x/tools v0.0.0-20200204074204-1cc6d1ef6c74/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= -golang.org/x/tools v0.0.0-20200207183749-b753a1ba74fa/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= -golang.org/x/tools v0.0.0-20200212150539-ea181f53ac56/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= -golang.org/x/tools v0.0.0-20200224181240-023911ca70b2/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= -golang.org/x/tools v0.0.0-20200227222343-706bc42d1f0d/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= -golang.org/x/tools v0.0.0-20200304193943-95d2e580d8eb/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw= -golang.org/x/tools v0.0.0-20200312045724-11d5b4c81c7d/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw= -golang.org/x/tools v0.0.0-20200331025713-a30bf2db82d4/go.mod h1:Sl4aGygMT6LrqrWclx+PTx3U+LnKx/seiNR+3G19Ar8= -golang.org/x/tools v0.0.0-20200501065659-ab2804fb9c9d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= -golang.org/x/tools v0.0.0-20200512131952-2bc93b1c0c88/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= -golang.org/x/tools v0.0.0-20200515010526-7d3b6ebf133d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= -golang.org/x/tools v0.0.0-20200618134242-20370b0cb4b2/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= -golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= -golang.org/x/tools v0.0.0-20200729194436-6467de6f59a7/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= -golang.org/x/tools v0.0.0-20200804011535-6c149bb5ef0d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= -golang.org/x/tools v0.0.0-20200825202427-b303f430e36d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= -golang.org/x/tools v0.0.0-20200904185747-39188db58858/go.mod h1:Cj7w3i3Rnn0Xh82ur9kSqwfTHTeVxaDqrfMjpcNT6bE= -golang.org/x/tools v0.0.0-20201110124207-079ba7bd75cd/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= -golang.org/x/tools v0.0.0-20201201161351-ac6f37ff4c2a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= -golang.org/x/tools v0.0.0-20201208233053-a543418bbed2/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= -golang.org/x/tools v0.0.0-20210105154028-b0ab187a4818/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= -golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= -golang.org/x/tools v0.1.0/go.mod h1:xkSsbof2nBLbhDlRMhhhyNLN/zl3eTqcnHD5viDpcZ0= -golang.org/x/tools v0.1.1/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= -golang.org/x/tools v0.1.2/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= -golang.org/x/tools v0.1.3/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= -golang.org/x/tools v0.1.4/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= -golang.org/x/tools v0.1.5/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= -golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -golang.org/x/xerrors v0.0.0-20220411194840-2f41105eb62f/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -golang.org/x/xerrors v0.0.0-20220517211312-f3a8303e98df/go.mod h1:K8+ghG5WaK9qNqU5K3HdILfMLy1f3aNYFI/wnl100a8= -golang.org/x/xerrors v0.0.0-20220609144429-65e65417b02f/go.mod h1:K8+ghG5WaK9qNqU5K3HdILfMLy1f3aNYFI/wnl100a8= -google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE= -google.golang.org/api v0.7.0/go.mod h1:WtwebWUNSVBH/HAw79HIFXZNqEvBhG+Ra+ax0hx3E3M= -google.golang.org/api v0.8.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg= -google.golang.org/api v0.9.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg= -google.golang.org/api v0.13.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= -google.golang.org/api v0.14.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= -google.golang.org/api v0.15.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= -google.golang.org/api v0.17.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= -google.golang.org/api v0.18.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= -google.golang.org/api v0.19.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= -google.golang.org/api v0.20.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= -google.golang.org/api v0.22.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= -google.golang.org/api v0.24.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE= -google.golang.org/api v0.28.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE= -google.golang.org/api v0.29.0/go.mod h1:Lcubydp8VUV7KeIHD9z2Bys/sm/vGKnG1UHuDBSrHWM= -google.golang.org/api v0.30.0/go.mod h1:QGmEvQ87FHZNiUVJkT14jQNYJ4ZJjdRF23ZXz5138Fc= -google.golang.org/api v0.35.0/go.mod h1:/XrVsuzM0rZmrsbjJutiuftIzeuTQcEeaYcSk/mQ1dg= -google.golang.org/api v0.36.0/go.mod h1:+z5ficQTmoYpPn8LCUNVpK5I7hwkpjbcgqA7I34qYtE= -google.golang.org/api v0.40.0/go.mod h1:fYKFpnQN0DsDSKRVRcQSDQNtqWPfM9i+zNPxepjRCQ8= -google.golang.org/api v0.41.0/go.mod h1:RkxM5lITDfTzmyKFPt+wGrCJbVfniCr2ool8kTBzRTU= -google.golang.org/api v0.43.0/go.mod h1:nQsDGjRXMo4lvh5hP0TKqF244gqhGcr/YSIykhUk/94= -google.golang.org/api v0.44.0/go.mod h1:EBOGZqzyhtvMDoxwS97ctnh0zUmYY6CxqXsc1AvkYD8= -google.golang.org/api v0.47.0/go.mod h1:Wbvgpq1HddcWVtzsVLyfLp8lDg6AA241LmgIL59tHXo= -google.golang.org/api v0.48.0/go.mod h1:71Pr1vy+TAZRPkPs/xlCf5SsU8WjuAWv1Pfjbtukyy4= -google.golang.org/api v0.50.0/go.mod h1:4bNT5pAuq5ji4SRZm+5QIkjny9JAyVD/3gaSihNefaw= -google.golang.org/api v0.51.0/go.mod h1:t4HdrdoNgyN5cbEfm7Lum0lcLDLiise1F8qDKX00sOU= -google.golang.org/api v0.54.0/go.mod h1:7C4bFFOvVDGXjfDTAsgGwDgAxRDeQ4X8NvUedIt6z3k= -google.golang.org/api v0.55.0/go.mod h1:38yMfeP1kfjsl8isn0tliTjIb1rJXcQi4UXlbqivdVE= -google.golang.org/api v0.56.0/go.mod h1:38yMfeP1kfjsl8isn0tliTjIb1rJXcQi4UXlbqivdVE= -google.golang.org/api v0.57.0/go.mod h1:dVPlbZyBo2/OjBpmvNdpn2GRm6rPy75jyU7bmhdrMgI= -google.golang.org/api v0.61.0/go.mod h1:xQRti5UdCmoCEqFxcz93fTl338AVqDgyaDRuOZ3hg9I= -google.golang.org/api v0.63.0/go.mod h1:gs4ij2ffTRXwuzzgJl/56BdwJaA194ijkfn++9tDuPo= -google.golang.org/api v0.67.0/go.mod h1:ShHKP8E60yPsKNw/w8w+VYaj9H6buA5UqDp8dhbQZ6g= -google.golang.org/api v0.70.0/go.mod h1:Bs4ZM2HGifEvXwd50TtW70ovgJffJYw2oRCOFU/SkfA= -google.golang.org/api v0.71.0/go.mod h1:4PyU6e6JogV1f9eA4voyrTY2batOLdgZ5qZ5HOCc4j8= -google.golang.org/api v0.74.0/go.mod h1:ZpfMZOVRMywNyvJFeqL9HRWBgAuRfSjJFpe9QtRRyDs= -google.golang.org/api v0.75.0/go.mod h1:pU9QmyHLnzlpar1Mjt4IbapUCy8J+6HD6GeELN69ljA= -google.golang.org/api v0.78.0/go.mod h1:1Sg78yoMLOhlQTeF+ARBoytAcH1NNyyl390YMy6rKmw= -google.golang.org/api v0.80.0/go.mod h1:xY3nI94gbvBrE0J6NHXhxOmW97HG7Khjkku6AFB3Hyg= -google.golang.org/api v0.84.0/go.mod h1:NTsGnUFJMYROtiquksZHBWtHfeMC7iYthki7Eq3pa8o= -google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= -google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= -google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= -google.golang.org/appengine v1.6.1/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww/cMBSeb0= -google.golang.org/appengine v1.6.5/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= -google.golang.org/appengine v1.6.6/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= -google.golang.org/appengine v1.6.7 h1:FZR1q0exgwxzPzp/aF+VccGrSfxfPpkBqjIIEq3ru6c= -google.golang.org/appengine v1.6.7/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= -google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= -google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= -google.golang.org/genproto v0.0.0-20190418145605-e7d98fc518a7/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= -google.golang.org/genproto v0.0.0-20190425155659-357c62f0e4bb/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= -google.golang.org/genproto v0.0.0-20190502173448-54afdca5d873/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= -google.golang.org/genproto v0.0.0-20190801165951-fa694d86fc64/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= -google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= -google.golang.org/genproto v0.0.0-20190911173649-1774047e7e51/go.mod h1:IbNlFCBrqXvoKpeg0TB2l7cyZUmoaFKYIwrEpbDKLA8= -google.golang.org/genproto v0.0.0-20191108220845-16a3f7862a1a/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= -google.golang.org/genproto v0.0.0-20191115194625-c23dd37a84c9/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= -google.golang.org/genproto v0.0.0-20191216164720-4f79533eabd1/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= -google.golang.org/genproto v0.0.0-20191230161307-f3c370f40bfb/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= -google.golang.org/genproto v0.0.0-20200115191322-ca5a22157cba/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= -google.golang.org/genproto v0.0.0-20200122232147-0452cf42e150/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= -google.golang.org/genproto v0.0.0-20200204135345-fa8e72b47b90/go.mod h1:GmwEX6Z4W5gMy59cAlVYjN9JhxgbQH6Gn+gFDQe2lzA= -google.golang.org/genproto v0.0.0-20200212174721-66ed5ce911ce/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= -google.golang.org/genproto v0.0.0-20200224152610-e50cd9704f63/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= -google.golang.org/genproto v0.0.0-20200228133532-8c2c7df3a383/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= -google.golang.org/genproto v0.0.0-20200305110556-506484158171/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= -google.golang.org/genproto v0.0.0-20200312145019-da6875a35672/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= -google.golang.org/genproto v0.0.0-20200331122359-1ee6d9798940/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= -google.golang.org/genproto v0.0.0-20200430143042-b979b6f78d84/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= -google.golang.org/genproto v0.0.0-20200511104702-f5ebc3bea380/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= -google.golang.org/genproto v0.0.0-20200513103714-09dca8ec2884/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= -google.golang.org/genproto v0.0.0-20200515170657-fc4c6c6a6587/go.mod h1:YsZOwe1myG/8QRHRsmBRE1LrgQY60beZKjly0O1fX9U= -google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo= -google.golang.org/genproto v0.0.0-20200618031413-b414f8b61790/go.mod h1:jDfRM7FcilCzHH/e9qn6dsT145K34l5v+OpcnNgKAAA= -google.golang.org/genproto v0.0.0-20200729003335-053ba62fc06f/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= -google.golang.org/genproto v0.0.0-20200804131852-c06518451d9c/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= -google.golang.org/genproto v0.0.0-20200825200019-8632dd797987/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= -google.golang.org/genproto v0.0.0-20200904004341-0bd0a958aa1d/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= -google.golang.org/genproto v0.0.0-20201109203340-2640f1f9cdfb/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= -google.golang.org/genproto v0.0.0-20201201144952-b05cb90ed32e/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= -google.golang.org/genproto v0.0.0-20201210142538-e3217bee35cc/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= -google.golang.org/genproto v0.0.0-20201214200347-8c77b98c765d/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= -google.golang.org/genproto v0.0.0-20210222152913-aa3ee6e6a81c/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= -google.golang.org/genproto v0.0.0-20210303154014-9728d6b83eeb/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= -google.golang.org/genproto v0.0.0-20210310155132-4ce2db91004e/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= -google.golang.org/genproto v0.0.0-20210319143718-93e7006c17a6/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= -google.golang.org/genproto v0.0.0-20210329143202-679c6ae281ee/go.mod h1:9lPAdzaEmUacj36I+k7YKbEc5CXzPIeORRgDAUOu28A= -google.golang.org/genproto v0.0.0-20210402141018-6c239bbf2bb1/go.mod h1:9lPAdzaEmUacj36I+k7YKbEc5CXzPIeORRgDAUOu28A= -google.golang.org/genproto v0.0.0-20210513213006-bf773b8c8384/go.mod h1:P3QM42oQyzQSnHPnZ/vqoCdDmzH28fzWByN9asMeM8A= -google.golang.org/genproto v0.0.0-20210602131652-f16073e35f0c/go.mod h1:UODoCrxHCcBojKKwX1terBiRUaqAsFqJiF615XL43r0= -google.golang.org/genproto v0.0.0-20210604141403-392c879c8b08/go.mod h1:UODoCrxHCcBojKKwX1terBiRUaqAsFqJiF615XL43r0= -google.golang.org/genproto v0.0.0-20210608205507-b6d2f5bf0d7d/go.mod h1:UODoCrxHCcBojKKwX1terBiRUaqAsFqJiF615XL43r0= -google.golang.org/genproto v0.0.0-20210624195500-8bfb893ecb84/go.mod h1:SzzZ/N+nwJDaO1kznhnlzqS8ocJICar6hYhVyhi++24= -google.golang.org/genproto v0.0.0-20210713002101-d411969a0d9a/go.mod h1:AxrInvYm1dci+enl5hChSFPOmmUF1+uAa/UsgNRWd7k= -google.golang.org/genproto v0.0.0-20210716133855-ce7ef5c701ea/go.mod h1:AxrInvYm1dci+enl5hChSFPOmmUF1+uAa/UsgNRWd7k= -google.golang.org/genproto v0.0.0-20210728212813-7823e685a01f/go.mod h1:ob2IJxKrgPT52GcgX759i1sleT07tiKowYBGbczaW48= -google.golang.org/genproto v0.0.0-20210805201207-89edb61ffb67/go.mod h1:ob2IJxKrgPT52GcgX759i1sleT07tiKowYBGbczaW48= -google.golang.org/genproto v0.0.0-20210813162853-db860fec028c/go.mod h1:cFeNkxwySK631ADgubI+/XFU/xp8FD5KIVV4rj8UC5w= -google.golang.org/genproto v0.0.0-20210821163610-241b8fcbd6c8/go.mod h1:eFjDcFEctNawg4eG61bRv87N7iHBWyVhJu7u1kqDUXY= -google.golang.org/genproto v0.0.0-20210828152312-66f60bf46e71/go.mod h1:eFjDcFEctNawg4eG61bRv87N7iHBWyVhJu7u1kqDUXY= -google.golang.org/genproto v0.0.0-20210831024726-fe130286e0e2/go.mod h1:eFjDcFEctNawg4eG61bRv87N7iHBWyVhJu7u1kqDUXY= -google.golang.org/genproto v0.0.0-20210903162649-d08c68adba83/go.mod h1:eFjDcFEctNawg4eG61bRv87N7iHBWyVhJu7u1kqDUXY= -google.golang.org/genproto v0.0.0-20210909211513-a8c4777a87af/go.mod h1:eFjDcFEctNawg4eG61bRv87N7iHBWyVhJu7u1kqDUXY= -google.golang.org/genproto v0.0.0-20210924002016-3dee208752a0/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc= -google.golang.org/genproto v0.0.0-20211118181313-81c1377c94b1/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc= -google.golang.org/genproto v0.0.0-20211206160659-862468c7d6e0/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc= -google.golang.org/genproto v0.0.0-20211208223120-3a66f561d7aa/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc= -google.golang.org/genproto v0.0.0-20211221195035-429b39de9b1c/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc= -google.golang.org/genproto v0.0.0-20220126215142-9970aeb2e350/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc= -google.golang.org/genproto v0.0.0-20220207164111-0872dc986b00/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc= -google.golang.org/genproto v0.0.0-20220218161850-94dd64e39d7c/go.mod h1:kGP+zUP2Ddo0ayMi4YuN7C3WZyJvGLZRh8Z5wnAqvEI= -google.golang.org/genproto v0.0.0-20220222213610-43724f9ea8cf/go.mod h1:kGP+zUP2Ddo0ayMi4YuN7C3WZyJvGLZRh8Z5wnAqvEI= -google.golang.org/genproto v0.0.0-20220304144024-325a89244dc8/go.mod h1:kGP+zUP2Ddo0ayMi4YuN7C3WZyJvGLZRh8Z5wnAqvEI= -google.golang.org/genproto v0.0.0-20220310185008-1973136f34c6/go.mod h1:kGP+zUP2Ddo0ayMi4YuN7C3WZyJvGLZRh8Z5wnAqvEI= -google.golang.org/genproto v0.0.0-20220324131243-acbaeb5b85eb/go.mod h1:hAL49I2IFola2sVEjAn7MEwsja0xp51I0tlGAf9hz4E= -google.golang.org/genproto v0.0.0-20220407144326-9054f6ed7bac/go.mod h1:8w6bsBMX6yCPbAVTeqQHvzxW0EIFigd5lZyahWgyfDo= -google.golang.org/genproto v0.0.0-20220413183235-5e96e2839df9/go.mod h1:8w6bsBMX6yCPbAVTeqQHvzxW0EIFigd5lZyahWgyfDo= -google.golang.org/genproto v0.0.0-20220414192740-2d67ff6cf2b4/go.mod h1:8w6bsBMX6yCPbAVTeqQHvzxW0EIFigd5lZyahWgyfDo= -google.golang.org/genproto v0.0.0-20220421151946-72621c1f0bd3/go.mod h1:8w6bsBMX6yCPbAVTeqQHvzxW0EIFigd5lZyahWgyfDo= -google.golang.org/genproto v0.0.0-20220429170224-98d788798c3e/go.mod h1:8w6bsBMX6yCPbAVTeqQHvzxW0EIFigd5lZyahWgyfDo= -google.golang.org/genproto v0.0.0-20220505152158-f39f71e6c8f3/go.mod h1:RAyBrSAP7Fh3Nc84ghnVLDPuV51xc9agzmm4Ph6i0Q4= -google.golang.org/genproto v0.0.0-20220518221133-4f43b3371335/go.mod h1:RAyBrSAP7Fh3Nc84ghnVLDPuV51xc9agzmm4Ph6i0Q4= -google.golang.org/genproto v0.0.0-20220523171625-347a074981d8/go.mod h1:RAyBrSAP7Fh3Nc84ghnVLDPuV51xc9agzmm4Ph6i0Q4= -google.golang.org/genproto v0.0.0-20220608133413-ed9918b62aac/go.mod h1:KEWEmljWE5zPzLBa/oHl6DaEt9LmfH6WtH1OHIvleBA= -google.golang.org/genproto v0.0.0-20220616135557-88e70c0c3a90 h1:4SPz2GL2CXJt28MTF8V6Ap/9ZiVbQlJeGSd9qtA7DLs= -google.golang.org/genproto v0.0.0-20220616135557-88e70c0c3a90/go.mod h1:KEWEmljWE5zPzLBa/oHl6DaEt9LmfH6WtH1OHIvleBA= -google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= -google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38= -google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM= -google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= -google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY= -google.golang.org/grpc v1.26.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= -google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= -google.golang.org/grpc v1.27.1/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= -google.golang.org/grpc v1.28.0/go.mod h1:rpkK4SK4GF4Ach/+MFLZUBavHOvF2JJB5uozKKal+60= -google.golang.org/grpc v1.29.1/go.mod h1:itym6AZVZYACWQqET3MqgPpjcuV5QH3BxFS3IjizoKk= -google.golang.org/grpc v1.30.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= -google.golang.org/grpc v1.31.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= -google.golang.org/grpc v1.31.1/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= -google.golang.org/grpc v1.33.1/go.mod h1:fr5YgcSWrqhRRxogOsw7RzIpsmvOZ6IcH4kBYTpR3n0= -google.golang.org/grpc v1.33.2/go.mod h1:JMHMWHQWaTccqQQlmk3MJZS+GWXOdAesneDmEnv2fbc= -google.golang.org/grpc v1.34.0/go.mod h1:WotjhfgOW/POjDeRt8vscBtXq+2VjORFy659qA51WJ8= -google.golang.org/grpc v1.35.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU= -google.golang.org/grpc v1.36.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU= -google.golang.org/grpc v1.36.1/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU= -google.golang.org/grpc v1.37.0/go.mod h1:NREThFqKR1f3iQ6oBuvc5LadQuXVGo9rkm5ZGrQdJfM= -google.golang.org/grpc v1.37.1/go.mod h1:NREThFqKR1f3iQ6oBuvc5LadQuXVGo9rkm5ZGrQdJfM= -google.golang.org/grpc v1.38.0/go.mod h1:NREThFqKR1f3iQ6oBuvc5LadQuXVGo9rkm5ZGrQdJfM= -google.golang.org/grpc v1.39.0/go.mod h1:PImNr+rS9TWYb2O4/emRugxiyHZ5JyHW5F+RPnDzfrE= -google.golang.org/grpc v1.39.1/go.mod h1:PImNr+rS9TWYb2O4/emRugxiyHZ5JyHW5F+RPnDzfrE= -google.golang.org/grpc v1.40.0/go.mod h1:ogyxbiOoUXAkP+4+xa6PZSE9DZgIHtSpzjDTB9KAK34= -google.golang.org/grpc v1.40.1/go.mod h1:ogyxbiOoUXAkP+4+xa6PZSE9DZgIHtSpzjDTB9KAK34= -google.golang.org/grpc v1.41.0/go.mod h1:U3l9uK9J0sini8mHphKoXyaqDA/8VyGnDee1zzIUK6k= -google.golang.org/grpc v1.44.0/go.mod h1:k+4IHHFw41K8+bbowsex27ge2rCb65oeWqe4jJ590SU= -google.golang.org/grpc v1.45.0/go.mod h1:lN7owxKUQEqMfSyQikvvk5tf/6zMPsrK+ONuO11+0rQ= -google.golang.org/grpc v1.46.0/go.mod h1:vN9eftEi1UMyUsIF80+uQXhHjbXYbm0uXoFCACuMGWk= -google.golang.org/grpc v1.46.2/go.mod h1:vN9eftEi1UMyUsIF80+uQXhHjbXYbm0uXoFCACuMGWk= -google.golang.org/grpc v1.47.0 h1:9n77onPX5F3qfFCqjy9dhn8PbNQsIKeVU04J9G7umt8= -google.golang.org/grpc v1.47.0/go.mod h1:vN9eftEi1UMyUsIF80+uQXhHjbXYbm0uXoFCACuMGWk= -google.golang.org/grpc/cmd/protoc-gen-go-grpc v1.1.0/go.mod h1:6Kw0yEErY5E/yWrBtf03jp27GLLJujG4z/JK95pnjjw= -google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= -google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= -google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= -google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE= -google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo= -google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= -google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= -google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= -google.golang.org/protobuf v1.24.0/go.mod h1:r/3tXBNzIEhYS9I1OUVjXDlt8tc493IdKGjtUeSXeh4= -google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c= -google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= -google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= -google.golang.org/protobuf v1.27.1/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= -google.golang.org/protobuf v1.28.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= -google.golang.org/protobuf v1.28.1 h1:d0NfwRgPtno5B1Wa6L2DAG+KivqkdutMf1UhdNx175w= -google.golang.org/protobuf v1.28.1/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= -gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw= -gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= -gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= -gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= -gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= -gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= -gopkg.in/go-playground/assert.v1 v1.2.1/go.mod h1:9RXL0bg/zibRAgZUYszZSwO/z8Y/a8bDuhia5mkpMnE= -gopkg.in/go-playground/validator.v9 v9.29.1/go.mod h1:+c9/zcJMFNgbLvly1L1V+PpxWdVbfP1avr/N00E2vyQ= -gopkg.in/ini.v1 v1.62.0 h1:duBzk771uxoUuOlyRLkHsygud9+5lrlGjdFBb4mSKDU= -gopkg.in/ini.v1 v1.62.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= -gopkg.in/square/go-jose.v2 v2.6.0 h1:NGk74WTnPKBNUhNzQX7PYcTLUjoq7mzKk2OKbvwk2iI= -gopkg.in/square/go-jose.v2 v2.6.0/go.mod h1:M9dMgbHiYLoDGQrXy7OpJDJWiKiU//h+vD76mk0e1AI= -gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= -gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= -gopkg.in/yaml.v2 v2.2.3/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= -gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= -gopkg.in/yaml.v2 v2.2.5/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= -gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= -gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= -gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= -gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= -gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= -gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= -gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= -gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= -honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= -honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= -honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= -honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= -honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg= -honnef.co/go/tools v0.0.1-2020.1.3/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= -honnef.co/go/tools v0.0.1-2020.1.4/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= -rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8= -rsc.io/pdf v0.1.1/go.mod h1:n8OzWcQ6Sp37PL01nO98y4iUCRdTGarVfzxY20ICaU4= -rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0= -rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA= -sigs.k8s.io/yaml v1.2.0/go.mod h1:yfXDCHCao9+ENCvLSE62v9VSji2MKu5jeNfTrofGhJc= diff --git a/api/internal/conf/conf.go b/api/internal/conf/conf.go deleted file mode 100644 index 3879eafe80..0000000000 --- a/api/internal/conf/conf.go +++ /dev/null @@ -1,434 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one or more - * contributor license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright ownership. - * The ASF licenses this file to You under the Apache License, Version 2.0 - * (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package conf - -import ( - "encoding/json" - "fmt" - "io/ioutil" - "os" - "path/filepath" - "runtime" - "strings" - - "github.com/gorilla/sessions" - "github.com/spf13/viper" - "github.com/tidwall/gjson" - "golang.org/x/oauth2" - - "github.com/apisix/manager-api/internal/utils" -) - -const ( - EnvPROD = "prod" - EnvBETA = "beta" - EnvDEV = "dev" - EnvLOCAL = "local" - EnvTEST = "test" - - WebDir = "html/" - - DefaultCSP = "default-src 'self'; script-src 'self' 'unsafe-eval' 'unsafe-inline'; style-src 'self' 'unsafe-inline'; img-src 'self' data:" - State = "123456" -) - -var ( - ENV string - Schema gjson.Result - WorkDir = "." - ConfigFile = "" - ServerHost = "0.0.0.0" - ServerPort = 80 - SSLHost = "0.0.0.0" - SSLPort = 443 - SSLCert string - SSLKey string - ETCDConfig *Etcd - ErrorLogLevel = "warn" - ErrorLogPath = "logs/error.log" - AccessLogPath = "logs/access.log" - UserList = make(map[string]User, 2) - AuthConf Authentication - SSLDefaultStatus = 1 //enable ssl by default - ImportSizeLimit = 10 * 1024 * 1024 - AllowList []string - Plugins = map[string]bool{} - SecurityConf Security - CookieStore = sessions.NewCookieStore([]byte("oidc")) - OidcEnabled = false - OidcId string - OidcConfig oauth2.Config - OidcExpireTime int - OidcUserInfoURL string -) - -type MTLS struct { - CaFile string `mapstructure:"ca_file"` - CertFile string `mapstructure:"cert_file"` - KeyFile string `mapstructure:"key_file"` -} - -type Etcd struct { - Endpoints []string - Username string - Password string - MTLS *MTLS - Prefix string -} - -type SSL struct { - Host string `mapstructure:"host"` - Port int `mapstructure:"port"` - Cert string `mapstructure:"cert"` - Key string `mapstructure:"key"` -} - -type Listen struct { - Host string - Port int -} - -type ErrorLog struct { - Level string - FilePath string `mapstructure:"file_path"` -} - -type AccessLog struct { - FilePath string `mapstructure:"file_path"` -} - -type Log struct { - ErrorLog ErrorLog `mapstructure:"error_log"` - AccessLog AccessLog `mapstructure:"access_log"` -} - -type Conf struct { - Etcd Etcd - Listen Listen - SSL SSL - Log Log - AllowList []string `mapstructure:"allow_list"` - MaxCpu int `mapstructure:"max_cpu"` - Security Security -} - -type User struct { - Username string - Password string -} - -type Authentication struct { - Secret string - ExpireTime int `mapstructure:"expire_time"` - Users []User -} - -type Oidc struct { - Enabled bool `mapstructure:"enabled"` - ExpireTime int `mapstructure:"expire_time" yaml:"expire_time"` - ClientID string `mapstructure:"client_id"` - ClientSecret string `mapstructure:"client_secret"` - AuthURL string `mapstructure:"auth_url"` - TokenURL string `mapstructure:"token_url"` - UserInfoURL string `mapstructure:"user_info_url"` - RedirectURL string `mapstructure:"redirect_url"` - Scope string -} - -type Config struct { - Conf Conf - Authentication Authentication - Plugins []string - Oidc Oidc -} - -type Security struct { - AllowCredentials string `mapstructure:"access_control_allow_credentials"` - AllowOrigin string `mapstructure:"access_control_allow_origin"` - AllowMethods string `mapstructure:"access_control-allow_methods"` - AllowHeaders string `mapstructure:"access_control_allow_headers"` - XFrameOptions string `mapstructure:"x_frame_options"` - ContentSecurityPolicy string `mapstructure:"content_security_policy"` -} - -// TODO: we should no longer use init() function after remove all handler's integration tests -// ENV=test is for integration tests only, other ENV should call "InitConf" explicitly -func init() { - if env := os.Getenv("ENV"); env == EnvTEST { - InitConf() - } -} - -func InitConf() { - //go test - if workDir := os.Getenv("APISIX_API_WORKDIR"); workDir != "" { - WorkDir = workDir - } - - setupConfig() - setupEnv() - initSchema() -} - -func setupConfig() { - // setup config file path - if ConfigFile == "" { - ConfigFile = "conf.yaml" - if profile := os.Getenv("APISIX_PROFILE"); profile != "" { - ConfigFile = "conf" + "-" + profile + ".yaml" - } - viper.SetConfigName(ConfigFile) - viper.SetConfigType("yaml") - viper.AddConfigPath(WorkDir + "/conf") - } else { - viper.SetConfigFile(ConfigFile) - } - - // load config - if err := viper.ReadInConfig(); err != nil { - panic(fmt.Sprintf("fail to read configuration, err: %s", err.Error())) - } - - // unmarshal config - config := Config{} - err := viper.Unmarshal(&config) - if err != nil { - panic(fmt.Sprintf("fail to unmarshal configuration: %s, err: %s", ConfigFile, err.Error())) - } - - // listen - if config.Conf.Listen.Port != 0 { - ServerPort = config.Conf.Listen.Port - } - if config.Conf.Listen.Host != "" { - ServerHost = config.Conf.Listen.Host - } - - // SSL - if config.Conf.SSL.Port != 0 { - SSLPort = config.Conf.SSL.Port - } - if config.Conf.SSL.Cert != "" { - SSLCert = config.Conf.SSL.Cert - } - if config.Conf.SSL.Key != "" { - SSLKey = config.Conf.SSL.Key - } - - // ETCD Storage - if len(config.Conf.Etcd.Endpoints) > 0 { - initEtcdConfig(config.Conf.Etcd) - } - - // error log - if config.Conf.Log.ErrorLog.Level != "" { - ErrorLogLevel = config.Conf.Log.ErrorLog.Level - } - if config.Conf.Log.ErrorLog.FilePath != "" { - ErrorLogPath = config.Conf.Log.ErrorLog.FilePath - } - - // access log - if config.Conf.Log.AccessLog.FilePath != "" { - AccessLogPath = config.Conf.Log.AccessLog.FilePath - } - - if !filepath.IsAbs(ErrorLogPath) { - if strings.HasPrefix(ErrorLogPath, "winfile") { - return - } - ErrorLogPath, err = filepath.Abs(filepath.Join(WorkDir, ErrorLogPath)) - if err != nil { - panic(err) - } - if runtime.GOOS == "windows" { - ErrorLogPath = `winfile:///` + ErrorLogPath - } - } - if !filepath.IsAbs(AccessLogPath) { - if strings.HasPrefix(AccessLogPath, "winfile") { - return - } - AccessLogPath, err = filepath.Abs(filepath.Join(WorkDir, AccessLogPath)) - if err != nil { - panic(err) - } - if runtime.GOOS == "windows" { - AccessLogPath = `winfile:///` + AccessLogPath - } - } - - AllowList = config.Conf.AllowList - - // set degree of parallelism - initParallelism(config.Conf.MaxCpu) - - // set authentication - initAuthentication(config.Authentication) - - // set Oidc - initOidc(config.Oidc) - - // set plugin - initPlugins(config.Plugins) - - // security configuration - initSecurity(config.Conf.Security) -} - -func setupEnv() { - ENV = EnvPROD - if env := os.Getenv("ENV"); env != "" { - ENV = env - } -} - -func initAuthentication(conf Authentication) { - AuthConf = conf - if AuthConf.Secret == "secret" { - AuthConf.Secret = utils.GetFlakeUidStr() - } - - userList := conf.Users - // create user list - for _, item := range userList { - UserList[item.Username] = item - } -} - -func initOidc(conf Oidc) { - OidcEnabled = conf.Enabled - OidcExpireTime = conf.ExpireTime - OidcConfig.ClientID = conf.ClientID - OidcConfig.ClientSecret = conf.ClientSecret - OidcConfig.Endpoint = oauth2.Endpoint{AuthURL: conf.AuthURL, TokenURL: conf.TokenURL, AuthStyle: 1} - OidcConfig.Scopes = append(OidcConfig.Scopes, conf.Scope) - OidcConfig.RedirectURL = conf.RedirectURL - OidcUserInfoURL = conf.UserInfoURL -} - -func initPlugins(plugins []string) { - for _, pluginName := range plugins { - Plugins[pluginName] = true - } -} - -func initSchema() { - var ( - apisixSchemaPath = WorkDir + "/conf/schema.json" - customizeSchemaPath = WorkDir + "/conf/customize_schema.json" - apisixSchemaContent []byte - customizeSchemaContent []byte - err error - ) - - if apisixSchemaContent, err = ioutil.ReadFile(apisixSchemaPath); err != nil { - panic(fmt.Errorf("fail to read configuration: %s, error: %s", apisixSchemaPath, err.Error())) - } - - if customizeSchemaContent, err = ioutil.ReadFile(customizeSchemaPath); err != nil { - panic(fmt.Errorf("fail to read configuration: %s, error: %s", customizeSchemaPath, err.Error())) - } - - content, err := mergeSchema(apisixSchemaContent, customizeSchemaContent) - if err != nil { - panic(err) - } - - Schema = gjson.ParseBytes(content) -} - -func mergeSchema(apisixSchema, customizeSchema []byte) ([]byte, error) { - var ( - apisixSchemaMap map[string]map[string]interface{} - customizeSchemaMap map[string]map[string]interface{} - ) - - if err := json.Unmarshal(apisixSchema, &apisixSchemaMap); err != nil { - return nil, err - } - if err := json.Unmarshal(customizeSchema, &customizeSchemaMap); err != nil { - return nil, err - } - - for key := range apisixSchemaMap["main"] { - if _, ok := customizeSchemaMap["main"][key]; ok { - return nil, fmt.Errorf("duplicates key: main.%s between schema.json and customize_schema.json", key) - } - } - - for k, v := range customizeSchemaMap["main"] { - apisixSchemaMap["main"][k] = v - } - - return json.Marshal(apisixSchemaMap) -} - -// initialize etcd config -func initEtcdConfig(conf Etcd) { - var endpoints = []string{"127.0.0.1:2379"} - if len(conf.Endpoints) > 0 { - endpoints = conf.Endpoints - } - - prefix := "/apisix" - if len(conf.Prefix) > 0 { - prefix = conf.Prefix - } - - ETCDConfig = &Etcd{ - Endpoints: endpoints, - Username: conf.Username, - Password: conf.Password, - MTLS: conf.MTLS, - Prefix: prefix, - } -} - -// initialize parallelism settings -func initParallelism(choiceCores int) { - if choiceCores < 1 { - return - } - maxSupportedCores := runtime.NumCPU() - - if choiceCores > maxSupportedCores { - choiceCores = maxSupportedCores - } - runtime.GOMAXPROCS(choiceCores) -} - -// initialize security settings -func initSecurity(conf Security) { - var se Security - // if conf == se, then conf is empty, we should use default value - if conf != se { - SecurityConf = conf - if conf.ContentSecurityPolicy == "" { - SecurityConf.ContentSecurityPolicy = DefaultCSP - } - if conf.XFrameOptions == "" { - SecurityConf.XFrameOptions = "deny" - } - return - } - - SecurityConf = Security{ - XFrameOptions: "deny", - ContentSecurityPolicy: DefaultCSP, - } -} diff --git a/api/internal/conf/conf_test.go b/api/internal/conf/conf_test.go deleted file mode 100644 index 1a1a1c3ef3..0000000000 --- a/api/internal/conf/conf_test.go +++ /dev/null @@ -1,63 +0,0 @@ -package conf - -import ( - "encoding/json" - "testing" - - "github.com/stretchr/testify/assert" -) - -func Test_mergeSchema(t *testing.T) { - type args struct { - apisixSchema []byte - customizeSchema []byte - } - tests := []struct { - name string - args args - wantRes []byte - wantErr bool - wantErrMessage string - }{ - { - name: "should failed when have duplicates key", - args: args{ - apisixSchema: []byte(`{"main":{"a":1,"b":2},"plugins":{"a":1}}`), - customizeSchema: []byte(`{"main":{"b":1}}`), - }, - wantErr: true, - wantErrMessage: "duplicates key: main.b between schema.json and customize_schema.json", - }, - { - name: "should success", - args: args{ - apisixSchema: []byte(`{"main":{"a":1,"b":2},"plugins":{"a":1}}`), - customizeSchema: []byte(`{"main":{"c":3}}`), - }, - wantErr: false, - wantRes: []byte(`{"main":{"a":1,"b":2,"c":3},"plugins":{"a":1}}`), - }, - } - for _, tt := range tests { - - t.Run(tt.name, func(t *testing.T) { - var ( - wantMap map[string]interface{} - gotMap map[string]interface{} - ) - - got, err := mergeSchema(tt.args.apisixSchema, tt.args.customizeSchema) - if tt.wantErr { - assert.Equal(t, tt.wantErrMessage, err.Error()) - return - } - - assert.NoError(t, err) - err = json.Unmarshal(got, &gotMap) - assert.NoError(t, err) - err = json.Unmarshal(tt.wantRes, &wantMap) - assert.NoError(t, err) - assert.Equal(t, wantMap, gotMap) - }) - } -} diff --git a/api/internal/core/entity/entity.go b/api/internal/core/entity/entity.go deleted file mode 100644 index c061f4703f..0000000000 --- a/api/internal/core/entity/entity.go +++ /dev/null @@ -1,315 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one or more - * contributor license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright ownership. - * The ASF licenses this file to You under the Apache License, Version 2.0 - * (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package entity - -import ( - "reflect" - "time" - - "github.com/apisix/manager-api/internal/utils" -) - -type BaseInfo struct { - ID interface{} `json:"id"` - CreateTime int64 `json:"create_time,omitempty"` - UpdateTime int64 `json:"update_time,omitempty"` -} - -func (info *BaseInfo) GetBaseInfo() *BaseInfo { - return info -} - -func (info *BaseInfo) Creating() { - if info.ID == nil { - info.ID = utils.GetFlakeUidStr() - } else { - // convert to string if it's not - if reflect.TypeOf(info.ID).String() != "string" { - info.ID = utils.InterfaceToString(info.ID) - } - } - info.CreateTime = time.Now().Unix() - info.UpdateTime = time.Now().Unix() -} - -func (info *BaseInfo) Updating(storedInfo *BaseInfo) { - info.ID = storedInfo.ID - info.CreateTime = storedInfo.CreateTime - info.UpdateTime = time.Now().Unix() -} - -func (info *BaseInfo) KeyCompat(key string) { - if info.ID == nil && key != "" { - info.ID = key - } -} - -type Status uint8 - -// swagger:model Route -type Route struct { - BaseInfo - URI string `json:"uri,omitempty"` - Uris []string `json:"uris,omitempty"` - Name string `json:"name"` - Desc string `json:"desc,omitempty"` - Priority int `json:"priority,omitempty"` - Methods []string `json:"methods,omitempty"` - Host string `json:"host,omitempty"` - Hosts []string `json:"hosts,omitempty"` - RemoteAddr string `json:"remote_addr,omitempty"` - RemoteAddrs []string `json:"remote_addrs,omitempty"` - Vars []interface{} `json:"vars,omitempty"` - FilterFunc string `json:"filter_func,omitempty"` - Script interface{} `json:"script,omitempty"` - ScriptID interface{} `json:"script_id,omitempty"` // For debug and optimization(cache), currently same as Route's ID - Plugins map[string]interface{} `json:"plugins,omitempty"` - PluginConfigID interface{} `json:"plugin_config_id,omitempty"` - Upstream *UpstreamDef `json:"upstream,omitempty"` - ServiceID interface{} `json:"service_id,omitempty"` - UpstreamID interface{} `json:"upstream_id,omitempty"` - ServiceProtocol string `json:"service_protocol,omitempty"` - Labels map[string]string `json:"labels,omitempty"` - EnableWebsocket bool `json:"enable_websocket,omitempty"` - Status Status `json:"status"` -} - -// --- structures for upstream start --- -type TimeoutValue float32 -type Timeout struct { - Connect TimeoutValue `json:"connect,omitempty"` - Send TimeoutValue `json:"send,omitempty"` - Read TimeoutValue `json:"read,omitempty"` -} - -type Node struct { - Host string `json:"host,omitempty"` - Port int `json:"port,omitempty"` - Weight int `json:"weight"` - Metadata interface{} `json:"metadata,omitempty"` - Priority int `json:"priority,omitempty"` -} - -type K8sInfo struct { - Namespace string `json:"namespace,omitempty"` - DeployName string `json:"deploy_name,omitempty"` - ServiceName string `json:"service_name,omitempty"` - Port int `json:"port,omitempty"` - BackendType string `json:"backend_type,omitempty"` -} - -type Healthy struct { - Interval int `json:"interval,omitempty"` - HttpStatuses []int `json:"http_statuses,omitempty"` - Successes int `json:"successes,omitempty"` -} - -type UnHealthy struct { - Interval int `json:"interval,omitempty"` - HTTPStatuses []int `json:"http_statuses,omitempty"` - TCPFailures int `json:"tcp_failures,omitempty"` - Timeouts int `json:"timeouts,omitempty"` - HTTPFailures int `json:"http_failures,omitempty"` -} - -type Active struct { - Type string `json:"type,omitempty"` - Timeout TimeoutValue `json:"timeout,omitempty"` - Concurrency int `json:"concurrency,omitempty"` - Host string `json:"host,omitempty"` - Port int `json:"port,omitempty"` - HTTPPath string `json:"http_path,omitempty"` - HTTPSVerifyCertificate bool `json:"https_verify_certificate,omitempty"` - Healthy Healthy `json:"healthy,omitempty"` - UnHealthy UnHealthy `json:"unhealthy,omitempty"` - ReqHeaders []string `json:"req_headers,omitempty"` -} - -type Passive struct { - Type string `json:"type,omitempty"` - Healthy Healthy `json:"healthy,omitempty"` - UnHealthy UnHealthy `json:"unhealthy,omitempty"` -} - -type HealthChecker struct { - Active Active `json:"active,omitempty"` - Passive Passive `json:"passive,omitempty"` -} - -type UpstreamTLS struct { - ClientCert string `json:"client_cert,omitempty"` - ClientKey string `json:"client_key,omitempty"` -} - -type UpstreamKeepalivePool struct { - IdleTimeout *TimeoutValue `json:"idle_timeout,omitempty"` - Requests int `json:"requests,omitempty"` - Size int `json:"size"` -} - -type UpstreamDef struct { - Nodes interface{} `json:"nodes,omitempty"` - Retries *int `json:"retries,omitempty"` - Timeout *Timeout `json:"timeout,omitempty"` - Type string `json:"type,omitempty"` - Checks interface{} `json:"checks,omitempty"` - HashOn string `json:"hash_on,omitempty"` - Key string `json:"key,omitempty"` - Scheme string `json:"scheme,omitempty"` - DiscoveryType string `json:"discovery_type,omitempty"` - DiscoveryArgs map[string]interface{} `json:"discovery_args,omitempty"` - PassHost string `json:"pass_host,omitempty"` - UpstreamHost string `json:"upstream_host,omitempty"` - Name string `json:"name,omitempty"` - Desc string `json:"desc,omitempty"` - ServiceName string `json:"service_name,omitempty"` - Labels map[string]string `json:"labels,omitempty"` - TLS *UpstreamTLS `json:"tls,omitempty"` - KeepalivePool *UpstreamKeepalivePool `json:"keepalive_pool,omitempty"` - RetryTimeout TimeoutValue `json:"retry_timeout,omitempty"` -} - -// swagger:model Upstream -type Upstream struct { - BaseInfo - UpstreamDef -} - -type UpstreamNameResponse struct { - ID interface{} `json:"id"` - Name string `json:"name"` -} - -func (upstream *Upstream) Parse2NameResponse() (*UpstreamNameResponse, error) { - nameResp := &UpstreamNameResponse{ - ID: upstream.ID, - Name: upstream.Name, - } - return nameResp, nil -} - -// --- structures for upstream end --- - -// swagger:model Consumer -type Consumer struct { - Username string `json:"username"` - Desc string `json:"desc,omitempty"` - Plugins map[string]interface{} `json:"plugins,omitempty"` - Labels map[string]string `json:"labels,omitempty"` - CreateTime int64 `json:"create_time,omitempty"` - UpdateTime int64 `json:"update_time,omitempty"` -} - -type SSLClient struct { - CA string `json:"ca,omitempty"` - Depth int `json:"depth,omitempty"` -} - -// swagger:model SSL -type SSL struct { - BaseInfo - Cert string `json:"cert,omitempty"` - Key string `json:"key,omitempty"` - Sni string `json:"sni,omitempty"` - Snis []string `json:"snis,omitempty"` - Certs []string `json:"certs,omitempty"` - Keys []string `json:"keys,omitempty"` - ExpTime int64 `json:"exptime,omitempty"` - Status int `json:"status"` - ValidityStart int64 `json:"validity_start,omitempty"` - ValidityEnd int64 `json:"validity_end,omitempty"` - Labels map[string]string `json:"labels,omitempty"` - Client *SSLClient `json:"client,omitempty"` -} - -// swagger:model Service -type Service struct { - BaseInfo - Name string `json:"name,omitempty"` - Desc string `json:"desc,omitempty"` - Upstream *UpstreamDef `json:"upstream,omitempty"` - UpstreamID interface{} `json:"upstream_id,omitempty"` - Plugins map[string]interface{} `json:"plugins,omitempty"` - Script string `json:"script,omitempty"` - Labels map[string]string `json:"labels,omitempty"` - EnableWebsocket bool `json:"enable_websocket,omitempty"` - Hosts []string `json:"hosts,omitempty"` -} - -type Script struct { - ID string `json:"id"` - Script interface{} `json:"script,omitempty"` -} - -type RequestValidation struct { - Type string `json:"type,omitempty"` - Required []string `json:"required,omitempty"` - Properties interface{} `json:"properties,omitempty"` -} - -// swagger:model GlobalPlugins -type GlobalPlugins struct { - BaseInfo - Plugins map[string]interface{} `json:"plugins"` -} - -type ServerInfo struct { - BaseInfo - LastReportTime int64 `json:"last_report_time,omitempty"` - UpTime int64 `json:"up_time,omitempty"` - BootTime int64 `json:"boot_time,omitempty"` - EtcdVersion string `json:"etcd_version,omitempty"` - Hostname string `json:"hostname,omitempty"` - Version string `json:"version,omitempty"` -} - -// swagger:model GlobalPlugins -type PluginConfig struct { - BaseInfo - Desc string `json:"desc,omitempty"` - Plugins map[string]interface{} `json:"plugins"` - Labels map[string]string `json:"labels,omitempty"` -} - -// swagger:model Proto -type Proto struct { - BaseInfo - Desc string `json:"desc,omitempty"` - Content string `json:"content"` -} - -// swagger:model StreamRoute -type StreamRoute struct { - BaseInfo - Desc string `json:"desc,omitempty"` - RemoteAddr string `json:"remote_addr,omitempty"` - ServerAddr string `json:"server_addr,omitempty"` - ServerPort int `json:"server_port,omitempty"` - SNI string `json:"sni,omitempty"` - Upstream *UpstreamDef `json:"upstream,omitempty"` - UpstreamID interface{} `json:"upstream_id,omitempty"` - Plugins map[string]interface{} `json:"plugins,omitempty"` -} - -// swagger:model SystemConfig -type SystemConfig struct { - ConfigName string `json:"config_name"` - Desc string `json:"desc,omitempty"` - Payload map[string]interface{} `json:"payload,omitempty"` - CreateTime int64 `json:"create_time,omitempty"` - UpdateTime int64 `json:"update_time,omitempty"` -} diff --git a/api/internal/core/entity/format.go b/api/internal/core/entity/format.go deleted file mode 100644 index d7df2b13c9..0000000000 --- a/api/internal/core/entity/format.go +++ /dev/null @@ -1,113 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one or more - * contributor license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright ownership. - * The ASF licenses this file to You under the Apache License, Version 2.0 - * (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package entity - -import ( - "errors" - "net" - "strconv" - "strings" - - "github.com/apisix/manager-api/internal/log" -) - -func mapKV2Node(key string, val float64) (*Node, error) { - host, port, err := net.SplitHostPort(key) - - // ipv6 address - if strings.Count(host, ":") >= 2 { - host = "[" + host + "]" - } - - if err != nil { - if strings.Contains(err.Error(), "missing port in address") { - // according to APISIX upstream nodes policy, port is optional - host = key - port = "0" - } else { - return nil, errors.New("invalid upstream node") - } - } - - portInt, err := strconv.Atoi(port) - if err != nil { - log.Errorf("parse port to int fail: %s", err) - return nil, err - } - - node := &Node{ - Host: host, - Port: portInt, - Weight: int(val), - } - - return node, nil -} - -func NodesFormat(obj interface{}) interface{} { - nodes := make([]*Node, 0) - switch objType := obj.(type) { - case map[string]float64: - log.Infof("nodes type: %v", objType) - value := obj.(map[string]float64) - for key, val := range value { - node, err := mapKV2Node(key, val) - if err != nil { - return obj - } - nodes = append(nodes, node) - } - return nodes - case map[string]interface{}: - log.Infof("nodes type: %v", objType) - value := obj.(map[string]interface{}) - for key, val := range value { - node, err := mapKV2Node(key, val.(float64)) - if err != nil { - return obj - } - nodes = append(nodes, node) - } - return nodes - case []*Node: - log.Infof("nodes type: %v", objType) - return obj - case []interface{}: - log.Infof("nodes type []interface{}: %v", objType) - list := obj.([]interface{}) - for _, v := range list { - val := v.(map[string]interface{}) - node := &Node{ - Host: val["host"].(string), - Port: int(val["port"].(float64)), - Weight: int(val["weight"].(float64)), - } - if _, ok := val["priority"]; ok { - node.Priority = int(val["priority"].(float64)) - } - - if _, ok := val["metadata"]; ok { - node.Metadata = val["metadata"].(map[string]interface{}) - } - - nodes = append(nodes, node) - } - return nodes - } - - return obj -} diff --git a/api/internal/core/entity/format_test.go b/api/internal/core/entity/format_test.go deleted file mode 100644 index ef12542040..0000000000 --- a/api/internal/core/entity/format_test.go +++ /dev/null @@ -1,424 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one or more - * contributor license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright ownership. - * The ASF licenses this file to You under the Apache License, Version 2.0 - * (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package entity - -import ( - "encoding/json" - "testing" - - "github.com/stretchr/testify/assert" -) - -func TestNodesFormat(t *testing.T) { - // route data saved in ETCD - routeStr := `{ - "uris": ["/*"], - "upstream": { - "type": "roundrobin", - "nodes": [{ - "host": "127.0.0.1", - "port": 80, - "weight": 0, - "priority":10 - }] - } - }` - - // bind struct - var route Route - err := json.Unmarshal([]byte(routeStr), &route) - assert.Nil(t, err) - - // nodes format - nodes := NodesFormat(route.Upstream.Nodes) - - // json encode for client - res, err := json.Marshal(nodes) - assert.Nil(t, err) - jsonStr := string(res) - assert.Contains(t, jsonStr, `"weight":0`) - assert.Contains(t, jsonStr, `"port":80`) - assert.Contains(t, jsonStr, `"host":"127.0.0.1"`) - assert.Contains(t, jsonStr, `"priority":10`) -} - -func TestNodesFormat_ipv6(t *testing.T) { - // route data saved in ETCD - routeStr := `{ - "uris": ["/*"], - "upstream": { - "type": "roundrobin", - "nodes": [{ - "host": "::1", - "port": 80, - "weight": 0, - "priority":10 - }] - } - }` - - // bind struct - var route Route - err := json.Unmarshal([]byte(routeStr), &route) - assert.Nil(t, err) - - // nodes format - nodes := NodesFormat(route.Upstream.Nodes) - - // json encode for client - res, err := json.Marshal(nodes) - assert.Nil(t, err) - jsonStr := string(res) - assert.Contains(t, jsonStr, `"weight":0`) - assert.Contains(t, jsonStr, `"port":80`) - assert.Contains(t, jsonStr, `"host":"::1"`) - assert.Contains(t, jsonStr, `"priority":10`) -} - -func TestNodesFormat_struct(t *testing.T) { - // route data saved in ETCD - var route Route - route.Uris = []string{"/*"} - route.Upstream = &UpstreamDef{} - route.Upstream.Type = "roundrobin" - var nodes = []*Node{{Host: "127.0.0.1", Port: 80, Weight: 0}} - route.Upstream.Nodes = nodes - - // nodes format - formattedNodes := NodesFormat(route.Upstream.Nodes) - - // json encode for client - res, err := json.Marshal(formattedNodes) - assert.Nil(t, err) - jsonStr := string(res) - assert.Contains(t, jsonStr, `"weight":0`) - assert.Contains(t, jsonStr, `"port":80`) - assert.Contains(t, jsonStr, `"host":"127.0.0.1"`) -} - -func TestNodesFormat_struct_ipv6(t *testing.T) { - // route data saved in ETCD - var route Route - route.Uris = []string{"/*"} - route.Upstream = &UpstreamDef{} - route.Upstream.Type = "roundrobin" - var nodes = []*Node{{Host: "::1", Port: 80, Weight: 0}} - route.Upstream.Nodes = nodes - - // nodes format - formattedNodes := NodesFormat(route.Upstream.Nodes) - - // json encode for client - res, err := json.Marshal(formattedNodes) - assert.Nil(t, err) - jsonStr := string(res) - assert.Contains(t, jsonStr, `"weight":0`) - assert.Contains(t, jsonStr, `"port":80`) - assert.Contains(t, jsonStr, `"host":"::1"`) -} - -func TestNodesFormat_Map(t *testing.T) { - // route data saved in ETCD - routeStr := `{ - "uris": ["/*"], - "upstream": { - "type": "roundrobin", - "nodes": {"127.0.0.1:8080": 0} - } - }` - - // bind struct - var route Route - err := json.Unmarshal([]byte(routeStr), &route) - assert.Nil(t, err) - - // nodes format - nodes := NodesFormat(route.Upstream.Nodes) - - // json encode for client - res, err := json.Marshal(nodes) - assert.Nil(t, err) - jsonStr := string(res) - assert.Contains(t, jsonStr, `"weight":0`) - assert.Contains(t, jsonStr, `"port":8080`) - assert.Contains(t, jsonStr, `"host":"127.0.0.1"`) -} - -func TestNodesFormat_Map_ipv6(t *testing.T) { - // route data saved in ETCD - routeStr := `{ - "uris": ["/*"], - "upstream": { - "type": "roundrobin", - "nodes": {"[::1]:8080": 0} - } - }` - - // bind struct - var route Route - err := json.Unmarshal([]byte(routeStr), &route) - assert.Nil(t, err) - - // nodes format - nodes := NodesFormat(route.Upstream.Nodes) - - // json encode for client - res, err := json.Marshal(nodes) - assert.Nil(t, err) - jsonStr := string(res) - assert.Contains(t, jsonStr, `"weight":0`) - assert.Contains(t, jsonStr, `"port":8080`) - assert.Contains(t, jsonStr, `"host":"[::1]"`) -} - -func TestNodesFormat_empty_struct(t *testing.T) { - // route data saved in ETCD - routeStr := `{ - "uris": ["/*"], - "upstream": { - "type": "roundrobin", - "nodes": [] - } - }` - - // bind struct - var route Route - err := json.Unmarshal([]byte(routeStr), &route) - assert.Nil(t, err) - - // nodes format - nodes := NodesFormat(route.Upstream.Nodes) - - // json encode for client - res, err := json.Marshal(nodes) - assert.Nil(t, err) - jsonStr := string(res) - assert.Contains(t, jsonStr, `[]`) -} - -func TestNodesFormat_empty_map(t *testing.T) { - // route data saved in ETCD - routeStr := `{ - "uris": ["/*"], - "upstream": { - "type": "roundrobin", - "nodes": {} - } - }` - - // bind struct - var route Route - err := json.Unmarshal([]byte(routeStr), &route) - assert.Nil(t, err) - - // nodes format - nodes := NodesFormat(route.Upstream.Nodes) - - // json encode for client - res, err := json.Marshal(nodes) - assert.Nil(t, err) - jsonStr := string(res) - assert.Contains(t, jsonStr, `[]`) -} - -func TestNodesFormat_no_nodes(t *testing.T) { - // route data saved in ETCD - routeStr := `{ - "uris": ["/*"], - "upstream": { - "type": "roundrobin", - "service_name": "USER-SERVICE", - "discovery_type": "eureka" - } - }` - - // bind struct - var route Route - err := json.Unmarshal([]byte(routeStr), &route) - assert.Nil(t, err) - - // nodes format - nodes := NodesFormat(route.Upstream.Nodes) - - // json encode for client - res, err := json.Marshal(nodes) - assert.Nil(t, err) - jsonStr := string(res) - assert.Contains(t, jsonStr, `null`) -} - -func TestNodesFormat_nodes_without_port(t *testing.T) { - nodes := map[string]float64{"127.0.0.1": 0} - // nodes format - formattedNodes := NodesFormat(nodes) - - // json encode for client - res, err := json.Marshal(formattedNodes) - assert.Nil(t, err) - assert.Equal(t, res, []byte(`[{"host":"127.0.0.1","weight":0}]`)) -} - -func Test_Idle_Timeout_nil_and_zero(t *testing.T) { - ukp0 := UpstreamKeepalivePool{} - // Unmarshal from zero value - err := json.Unmarshal([]byte(`{"idle_timeout":0}`), &ukp0) - assert.Nil(t, err) - assert.Equal(t, *ukp0.IdleTimeout, TimeoutValue(0)) - - // Marshal with zero value - marshaled, err := json.Marshal(ukp0) - assert.Nil(t, err) - assert.Contains(t, string(marshaled), `"idle_timeout":0`) - - ukpNil := UpstreamKeepalivePool{} - - // Unmarshal from nil value - err = json.Unmarshal([]byte(`{}`), &ukpNil) - assert.Nil(t, err) - assert.Nil(t, ukpNil.IdleTimeout) - - // Marshal with nil value - marshaledNil, err := json.Marshal(ukpNil) - assert.Nil(t, err) - assert.Equal(t, string(marshaledNil), `{"size":0}`) -} - -func TestUpstream_nil_and_zero_retries(t *testing.T) { - ud0 := UpstreamDef{} - // Unmarshal from zero value - err := json.Unmarshal([]byte(`{"retries":0}`), &ud0) - assert.Nil(t, err) - assert.Equal(t, *ud0.Retries, 0) - - // Marshal with zero value - marshaled, err := json.Marshal(ud0) - assert.Nil(t, err) - assert.Contains(t, string(marshaled), `"retries":0`) - - udNull := UpstreamDef{} - - // Unmarshal from null value - err = json.Unmarshal([]byte(`{}`), &udNull) - assert.Nil(t, err) - assert.Nil(t, udNull.Retries) - - // Marshal to null value - marshaledNull, err := json.Marshal(udNull) - assert.Nil(t, err) - assert.Equal(t, string(marshaledNull), `{}`) -} - -func TestMapKV2Node(t *testing.T) { - testCases := []struct { - name string - key string - value float64 - wantErr bool - errMessage string - wantRes *Node - }{ - { - name: "invalid upstream node", - key: "127.0.0.1:0:0", - wantErr: true, - errMessage: "invalid upstream node", - }, - { - name: "when address contains port convert should succeed", - key: "127.0.0.1:8080", - value: 100, - wantErr: false, - wantRes: &Node{ - Host: "127.0.0.1", - Port: 8080, - Weight: 100, - }, - }, - { - name: "when address without port convert should succeed", - key: "127.0.0.1", - wantErr: false, - wantRes: &Node{ - Host: "127.0.0.1", - Port: 0, - Weight: 0, - }, - }, - { - name: "address with ipv6", - key: "[::1]:443", - value: 100, - wantErr: false, - wantRes: &Node{ - Host: "[::1]", - Port: 443, - Weight: 100, - }, - }, - } - - for _, tc := range testCases { - t.Run(tc.name, func(t *testing.T) { - got, err := mapKV2Node(tc.key, tc.value) - if tc.wantErr { - assert.NotNil(t, err) - assert.Contains(t, err.Error(), tc.errMessage) - return - } - - assert.Nil(t, err) - assert.Equal(t, tc.wantRes, got) - }) - } -} - -func TestNodesFormatWithMetadata(t *testing.T) { - // route data saved in ETCD - routeStr := `{ - "uris": ["/*"], - "upstream": { - "type": "roundrobin", - "nodes": [{ - "host": "127.0.0.1", - "port": 80, - "weight": 0, - "priority":10, - "metadata": { - "name": "test" - } - }] - } - }` - - // bind struct - var route Route - err := json.Unmarshal([]byte(routeStr), &route) - assert.Nil(t, err) - - // nodes format - nodes := NodesFormat(route.Upstream.Nodes) - - // json encode for client - res, err := json.Marshal(nodes) - assert.Nil(t, err) - jsonStr := string(res) - assert.Contains(t, jsonStr, `"weight":0`) - assert.Contains(t, jsonStr, `"port":80`) - assert.Contains(t, jsonStr, `"host":"127.0.0.1"`) - assert.Contains(t, jsonStr, `"priority":10`) - assert.Contains(t, jsonStr, `"metadata":{"name":"test"}`) -} diff --git a/api/internal/core/entity/interface.go b/api/internal/core/entity/interface.go deleted file mode 100644 index ea34fc6c08..0000000000 --- a/api/internal/core/entity/interface.go +++ /dev/null @@ -1,45 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one or more - * contributor license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright ownership. - * The ASF licenses this file to You under the Apache License, Version 2.0 - * (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package entity - -type GetBaseInfo interface { - GetBaseInfo() *BaseInfo -} - -type GetPlugins interface { - GetPlugins() map[string]interface{} -} - -func (r *Route) GetPlugins() map[string]interface{} { - return r.Plugins -} - -func (s *Service) GetPlugins() map[string]interface{} { - return s.Plugins -} - -func (c *Consumer) GetPlugins() map[string]interface{} { - return c.Plugins -} - -func (g *GlobalPlugins) GetPlugins() map[string]interface{} { - return g.Plugins -} - -func (p *PluginConfig) GetPlugins() map[string]interface{} { - return p.Plugins -} diff --git a/api/internal/core/migrate/conflict.go b/api/internal/core/migrate/conflict.go deleted file mode 100644 index 6a0723ef37..0000000000 --- a/api/internal/core/migrate/conflict.go +++ /dev/null @@ -1,48 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one or more - * contributor license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright ownership. - * The ASF licenses this file to You under the Apache License, Version 2.0 - * (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package migrate - -import ( - "context" - - "github.com/apisix/manager-api/internal/core/store" - "github.com/apisix/manager-api/internal/log" -) - -func isConflicted(ctx context.Context, new *DataSet) (bool, *DataSet) { - isConflict := false - conflictedData := newDataSet() - store.RangeStore(func(key store.HubKey, s *store.GenericStore) bool { - new.rangeData(key, func(i int, obj interface{}) bool { - // Only check key of store conflict for now. - // TODO: Maybe check name of some entiries. - _, err := s.CreateCheck(obj) - if err != nil { - isConflict = true - err = conflictedData.Add(obj) - if err != nil { - log.Errorf("Add obj to conflict list failed:%s", err) - return true - } - } - return true - }) - return true - }) - return isConflict, conflictedData -} diff --git a/api/internal/core/migrate/dataset.go b/api/internal/core/migrate/dataset.go deleted file mode 100644 index d09edd8965..0000000000 --- a/api/internal/core/migrate/dataset.go +++ /dev/null @@ -1,127 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one or more - * contributor license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright ownership. - * The ASF licenses this file to You under the Apache License, Version 2.0 - * (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package migrate - -import ( - "errors" - - "github.com/apisix/manager-api/internal/core/entity" - "github.com/apisix/manager-api/internal/core/store" -) - -type DataSet struct { - Consumers []*entity.Consumer - Routes []*entity.Route - Services []*entity.Service - SSLs []*entity.SSL - Upstreams []*entity.Upstream - Scripts []*entity.Script - GlobalPlugins []*entity.GlobalPlugins - PluginConfigs []*entity.PluginConfig -} - -func newDataSet() *DataSet { - return &DataSet{ - Consumers: make([]*entity.Consumer, 0), - Routes: make([]*entity.Route, 0), - Services: make([]*entity.Service, 0), - SSLs: make([]*entity.SSL, 0), - Upstreams: make([]*entity.Upstream, 0), - Scripts: make([]*entity.Script, 0), - GlobalPlugins: make([]*entity.GlobalPlugins, 0), - PluginConfigs: make([]*entity.PluginConfig, 0), - } -} - -func (a *DataSet) rangeData(key store.HubKey, f func(int, interface{}) bool) { - switch key { - case store.HubKeyConsumer: - for i, v := range a.Consumers { - if !f(i, v) { - break - } - } - case store.HubKeyRoute: - for i, v := range a.Routes { - if !f(i, v) { - break - } - } - case store.HubKeyService: - for i, v := range a.Services { - if !f(i, v) { - break - } - } - case store.HubKeySsl: - for i, v := range a.SSLs { - if !f(i, v) { - break - } - } - case store.HubKeyUpstream: - for i, v := range a.Upstreams { - if !f(i, v) { - break - } - } - case store.HubKeyScript: - for i, v := range a.Scripts { - if !f(i, v) { - break - } - } - case store.HubKeyGlobalRule: - for i, v := range a.GlobalPlugins { - if !f(i, v) { - break - } - } - case store.HubKeyPluginConfig: - for i, v := range a.PluginConfigs { - if !f(i, v) { - break - } - } - } -} - -func (a *DataSet) Add(obj interface{}) error { - var err error = nil - switch obj := obj.(type) { - case *entity.Consumer: - a.Consumers = append(a.Consumers, obj) - case *entity.Route: - a.Routes = append(a.Routes, obj) - case *entity.Service: - a.Services = append(a.Services, obj) - case *entity.SSL: - a.SSLs = append(a.SSLs, obj) - case *entity.Upstream: - a.Upstreams = append(a.Upstreams, obj) - case *entity.Script: - a.Scripts = append(a.Scripts, obj) - case *entity.GlobalPlugins: - a.GlobalPlugins = append(a.GlobalPlugins, obj) - case *entity.PluginConfig: - a.PluginConfigs = append(a.PluginConfigs, obj) - default: - err = errors.New("Unknown type of obj") - } - return err -} diff --git a/api/internal/core/migrate/migrate.go b/api/internal/core/migrate/migrate.go deleted file mode 100644 index ea5a80ca39..0000000000 --- a/api/internal/core/migrate/migrate.go +++ /dev/null @@ -1,99 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one or more - * contributor license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright ownership. - * The ASF licenses this file to You under the Apache License, Version 2.0 - * (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package migrate - -import ( - "context" - "encoding/json" - "errors" - - "github.com/apisix/manager-api/internal/core/store" - "github.com/apisix/manager-api/internal/log" -) - -var ( - ErrConflict = errors.New("conflict") -) - -func Export(ctx context.Context) ([]byte, error) { - exportData := newDataSet() - store.RangeStore(func(key store.HubKey, s *store.GenericStore) bool { - s.Range(ctx, func(_ string, obj interface{}) bool { - err := exportData.Add(obj) - if err != nil { - log.Errorf("Add obj to export list failed:%s", err) - return true - } - return true - }) - return true - }) - - data, err := json.Marshal(exportData) - if err != nil { - return nil, err - } - - return data, nil -} - -type ConflictMode int - -const ( - ModeReturn ConflictMode = iota - ModeOverwrite - ModeSkip -) - -func Import(ctx context.Context, data []byte, mode ConflictMode) (*DataSet, error) { - importData := newDataSet() - err := json.Unmarshal(data, &importData) - if err != nil { - return nil, err - } - conflict, conflictData := isConflicted(ctx, importData) - if conflict && mode == ModeReturn { - return conflictData, ErrConflict - } - store.RangeStore(func(key store.HubKey, s *store.GenericStore) bool { - importData.rangeData(key, func(i int, obj interface{}) bool { - _, e := s.CreateCheck(obj) - if e != nil { - switch mode { - case ModeSkip: - return true - case ModeOverwrite: - _, e := s.Update(ctx, obj, true) - if e != nil { - err = e - return false - } - } - } else { - _, e := s.Create(ctx, obj) - if err != nil { - err = e - return false - } - } - return true - }) - return true - }) - return nil, err -} diff --git a/api/internal/core/server/http.go b/api/internal/core/server/http.go deleted file mode 100644 index ea0c56c405..0000000000 --- a/api/internal/core/server/http.go +++ /dev/null @@ -1,71 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one or more - * contributor license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright ownership. - * The ASF licenses this file to You under the Apache License, Version 2.0 - * (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package server - -import ( - "crypto/tls" - "net" - "net/http" - "strconv" - "time" - - "github.com/shiningrush/droplet" - - "github.com/apisix/manager-api/internal" - "github.com/apisix/manager-api/internal/conf" - "github.com/apisix/manager-api/internal/handler" -) - -func (s *server) setupAPI() { - // orchestrator - droplet.Option.Orchestrator = func(mws []droplet.Middleware) []droplet.Middleware { - var newMws []droplet.Middleware - // default middleware order: resp_reshape, auto_input, traffic_log - // We should put err_transform at second to catch all error - newMws = append(newMws, mws[0], &handler.ErrorTransformMiddleware{}) - newMws = append(newMws, mws[1:]...) - return newMws - } - - // routes - r := internal.SetUpRouter() - - // HTTP - addr := net.JoinHostPort(conf.ServerHost, strconv.Itoa(conf.ServerPort)) - s.server = &http.Server{ - Addr: addr, - Handler: r, - ReadTimeout: time.Duration(1000) * time.Millisecond, - WriteTimeout: time.Duration(5000) * time.Millisecond, - } - - // HTTPS - if conf.SSLCert != "" && conf.SSLKey != "" { - addrSSL := net.JoinHostPort(conf.SSLHost, strconv.Itoa(conf.SSLPort)) - s.serverSSL = &http.Server{ - Addr: addrSSL, - Handler: r, - ReadTimeout: time.Duration(1000) * time.Millisecond, - WriteTimeout: time.Duration(5000) * time.Millisecond, - TLSConfig: &tls.Config{ - // Causes servers to use Go's default ciphersuite preferences, - // which are tuned to avoid attacks. Does nothing on clients. - PreferServerCipherSuites: true, - }, - } - } -} diff --git a/api/internal/core/server/server.go b/api/internal/core/server/server.go deleted file mode 100644 index ea3ddc119a..0000000000 --- a/api/internal/core/server/server.go +++ /dev/null @@ -1,122 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one or more - * contributor license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright ownership. - * The ASF licenses this file to You under the Apache License, Version 2.0 - * (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package server - -import ( - "context" - "fmt" - "net/http" - "os" - "time" - - "github.com/spf13/viper" - - "github.com/apisix/manager-api/internal/conf" - "github.com/apisix/manager-api/internal/log" - "github.com/apisix/manager-api/internal/utils" -) - -// server is the manager of Manager API, which is responsible for managing the life cycle of Manager API, including initialization, start, stop and so on -type server struct { - server *http.Server - serverSSL *http.Server - options *Options -} - -type Options struct{} - -// NewServer Create a server manager -func NewServer(options *Options) (*server, error) { - return &server{options: options}, nil -} - -func (s *server) Start(errSig chan error) { - // initialize server - err := s.init() - if err != nil { - errSig <- err - return - } - - // print server info to stdout - s.printInfo() - - // start HTTP server - log.Infof("The Manager API is listening on %s", s.server.Addr) - go func() { - err := s.server.ListenAndServe() - if err != nil && err != http.ErrServerClosed { - log.Errorf("listen and serv fail: %s", err) - errSig <- err - } - }() - - // start HTTPs server - if conf.SSLCert != "" && conf.SSLKey != "" { - go func() { - err := s.serverSSL.ListenAndServeTLS(conf.SSLCert, conf.SSLKey) - if err != nil && err != http.ErrServerClosed { - log.Errorf("listen and serve for HTTPS failed: %s", err) - errSig <- err - } - }() - } -} - -func (s *server) Stop() { - utils.CloseAll() - - s.shutdownServer(s.server) - s.shutdownServer(s.serverSSL) -} - -func (s *server) init() error { - log.Info("Initialize Manager API store") - err := s.setupStore() - if err != nil { - return err - } - - log.Info("Initialize Manager API server") - s.setupAPI() - - return nil -} - -func (s *server) shutdownServer(server *http.Server) { - if server != nil { - ctx, cancel := context.WithTimeout(context.TODO(), 5*time.Second) - defer cancel() - - if err := server.Shutdown(ctx); err != nil { - log.Errorf("Shutting down server error: %s", err) - } - } -} - -func (s *server) printInfo() { - fmt.Fprint(os.Stdout, "The manager-api is running successfully!\n\n") - utils.PrintVersion() - fmt.Fprintf(os.Stdout, "%-8s: %s\n", "Config File", viper.ConfigFileUsed()) - fmt.Fprintf(os.Stdout, "%-8s: %s:%d\n", "Listen", conf.ServerHost, conf.ServerPort) - if conf.SSLCert != "" && conf.SSLKey != "" { - fmt.Fprintf(os.Stdout, "%-8s: %s:%d\n", "HTTPS Listen", conf.SSLHost, conf.SSLPort) - } - fmt.Fprintf(os.Stdout, "%-8s: %s\n", "Loglevel", conf.ErrorLogLevel) - fmt.Fprintf(os.Stdout, "%-8s: %s\n", "ErrorLogFile", conf.ErrorLogPath) - fmt.Fprintf(os.Stdout, "%-8s: %s\n\n", "AccessLogFile", conf.AccessLogPath) -} diff --git a/api/internal/core/server/store.go b/api/internal/core/server/store.go deleted file mode 100644 index 3c13949045..0000000000 --- a/api/internal/core/server/store.go +++ /dev/null @@ -1,36 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one or more - * contributor license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright ownership. - * The ASF licenses this file to You under the Apache License, Version 2.0 - * (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package server - -import ( - "github.com/apisix/manager-api/internal/conf" - "github.com/apisix/manager-api/internal/core/storage" - "github.com/apisix/manager-api/internal/core/store" - "github.com/apisix/manager-api/internal/log" -) - -func (s *server) setupStore() error { - if err := storage.InitETCDClient(conf.ETCDConfig); err != nil { - log.Errorf("init etcd client fail: %v", err) - return err - } - if err := store.InitStores(); err != nil { - log.Errorf("init stores fail: %v", err) - return err - } - return nil -} diff --git a/api/internal/core/storage/etcd.go b/api/internal/core/storage/etcd.go deleted file mode 100644 index ff90983673..0000000000 --- a/api/internal/core/storage/etcd.go +++ /dev/null @@ -1,239 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one or more - * contributor license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright ownership. - * The ASF licenses this file to You under the Apache License, Version 2.0 - * (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package storage - -import ( - "context" - "fmt" - "time" - - "go.etcd.io/etcd/client/pkg/v3/transport" - clientv3 "go.etcd.io/etcd/client/v3" - - "github.com/apisix/manager-api/internal/conf" - "github.com/apisix/manager-api/internal/log" - "github.com/apisix/manager-api/internal/utils" - "github.com/apisix/manager-api/internal/utils/runtime" -) - -const ( - // SkippedValueEtcdInitDir indicates the init_dir - // etcd event will be skipped. - SkippedValueEtcdInitDir = "init_dir" - - // SkippedValueEtcdEmptyObject indicates the data with an - // empty JSON value {}, which may be set by APISIX, - // should be also skipped. - // - // Important: at present, {} is considered as invalid, - // but may be changed in the future. - SkippedValueEtcdEmptyObject = "{}" -) - -var ( - etcdClient *clientv3.Client -) - -type EtcdV3Storage struct { - closing bool - client *clientv3.Client -} - -func InitETCDClient(etcdConf *conf.Etcd) error { - config := clientv3.Config{ - Endpoints: etcdConf.Endpoints, - DialTimeout: 5 * time.Second, - Username: etcdConf.Username, - Password: etcdConf.Password, - } - // mTLS - if etcdConf.MTLS != nil && etcdConf.MTLS.CaFile != "" && - etcdConf.MTLS.CertFile != "" && etcdConf.MTLS.KeyFile != "" { - tlsInfo := transport.TLSInfo{ - CertFile: etcdConf.MTLS.CertFile, - KeyFile: etcdConf.MTLS.KeyFile, - TrustedCAFile: etcdConf.MTLS.CaFile, - } - tlsConfig, err := tlsInfo.ClientConfig() - if err != nil { - return err - } - config.TLS = tlsConfig - } - - cli, err := clientv3.New(config) - if err != nil { - log.Errorf("init etcd failed: %s", err) - return fmt.Errorf("init etcd failed: %s", err) - } - - etcdClient = cli - utils.AppendToClosers(Close) - return nil -} - -func GenEtcdStorage() *EtcdV3Storage { - return &EtcdV3Storage{ - client: etcdClient, - } -} - -func Close() error { - if err := etcdClient.Close(); err != nil { - log.Errorf("etcd client close failed: %s", err) - return err - } - return nil -} - -func (s *EtcdV3Storage) Get(ctx context.Context, key string) (string, error) { - resp, err := s.client.Get(ctx, key) - if err != nil { - log.Errorf("etcd get failed: %s", err) - return "", fmt.Errorf("etcd get failed: %s", err) - } - if resp.Count == 0 { - log.Warnf("key: %s is not found", key) - return "", fmt.Errorf("key: %s is not found", key) - } - - return string(resp.Kvs[0].Value), nil -} - -func (s *EtcdV3Storage) List(ctx context.Context, key string) ([]Keypair, error) { - resp, err := s.client.Get(ctx, key, clientv3.WithPrefix()) - if err != nil { - log.Errorf("etcd get failed: %s", err) - return nil, fmt.Errorf("etcd get failed: %s", err) - } - var ret []Keypair - for i := range resp.Kvs { - key := string(resp.Kvs[i].Key) - value := string(resp.Kvs[i].Value) - - // Skip the data if its value is init_dir or {} - // during fetching-all phase. - // - // For more complex cases, an explicit function to determine if - // skippable would be better. - if value == SkippedValueEtcdInitDir || value == SkippedValueEtcdEmptyObject { - continue - } - - data := Keypair{ - Key: key, - Value: value, - } - ret = append(ret, data) - } - - return ret, nil -} - -func (s *EtcdV3Storage) Create(ctx context.Context, key, val string) error { - _, err := s.client.Put(ctx, key, val) - if err != nil { - log.Errorf("etcd put failed: %s", err) - return fmt.Errorf("etcd put failed: %s", err) - } - return nil -} - -func (s *EtcdV3Storage) Update(ctx context.Context, key, val string) error { - _, err := s.client.Put(ctx, key, val) - if err != nil { - log.Errorf("etcd put failed: %s", err) - return fmt.Errorf("etcd put failed: %s", err) - } - return nil -} - -func (s *EtcdV3Storage) BatchDelete(ctx context.Context, keys []string) error { - for i := range keys { - resp, err := s.client.Delete(ctx, keys[i]) - if err != nil { - log.Errorf("delete etcd key[%s] failed: %s", keys[i], err) - return fmt.Errorf("delete etcd key[%s] failed: %s", keys[i], err) - } - if resp.Deleted == 0 { - log.Warnf("key: %s is not found", keys[i]) - return fmt.Errorf("key: %s is not found", keys[i]) - } - } - return nil -} - -func (s *EtcdV3Storage) Watch(ctx context.Context, key string) <-chan WatchResponse { - eventChan := s.client.Watch(ctx, key, clientv3.WithPrefix()) - ch := make(chan WatchResponse, 1) - go func() { - defer runtime.HandlePanic() - for event := range eventChan { - if event.Err() != nil { - log.Errorf("etcd watch error: key: %s err: %v", key, event.Err()) - close(ch) - return - } - - output := WatchResponse{ - Canceled: event.Canceled, - } - - for i := range event.Events { - key := string(event.Events[i].Kv.Key) - value := string(event.Events[i].Kv.Value) - - // Skip the data if its value is init_dir or {} - // during watching phase. - // - // For more complex cases, an explicit function to determine if - // skippable would be better. - if value == SkippedValueEtcdInitDir || value == SkippedValueEtcdEmptyObject { - continue - } - - e := Event{ - Keypair: Keypair{ - Key: key, - Value: value, - }, - } - switch event.Events[i].Type { - case clientv3.EventTypePut: - e.Type = EventTypePut - case clientv3.EventTypeDelete: - e.Type = EventTypeDelete - } - output.Events = append(output.Events, e) - } - if output.Canceled { - log.Error("channel canceled") - output.Error = fmt.Errorf("channel canceled") - } - ch <- output - } - - close(ch) - }() - - return ch -} - -func (s *EtcdV3Storage) GetClient() *clientv3.Client { - return s.client -} diff --git a/api/internal/core/storage/storage.go b/api/internal/core/storage/storage.go deleted file mode 100644 index eec0c60c6e..0000000000 --- a/api/internal/core/storage/storage.go +++ /dev/null @@ -1,51 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one or more - * contributor license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright ownership. - * The ASF licenses this file to You under the Apache License, Version 2.0 - * (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package storage - -import "context" - -type Interface interface { - Get(ctx context.Context, key string) (string, error) - List(ctx context.Context, key string) ([]Keypair, error) - Create(ctx context.Context, key, val string) error - Update(ctx context.Context, key, val string) error - BatchDelete(ctx context.Context, keys []string) error - Watch(ctx context.Context, key string) <-chan WatchResponse -} - -type WatchResponse struct { - Events []Event - Error error - Canceled bool -} - -type Keypair struct { - Key string - Value string -} - -type Event struct { - Keypair - Type EventType -} - -type EventType string - -var ( - EventTypePut EventType = "put" - EventTypeDelete EventType = "delete" -) diff --git a/api/internal/core/storage/storage_mock.go b/api/internal/core/storage/storage_mock.go deleted file mode 100644 index f85d8b76b0..0000000000 --- a/api/internal/core/storage/storage_mock.go +++ /dev/null @@ -1,130 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one or more - * contributor license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright ownership. - * The ASF licenses this file to You under the Apache License, Version 2.0 - * (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package storage - -import ( - context "context" - - mock "github.com/stretchr/testify/mock" -) - -// MockInterface is an autogenerated mock type for the Interface type -type MockInterface struct { - mock.Mock -} - -// BatchDelete provides a mock function with given fields: ctx, keys -func (_m *MockInterface) BatchDelete(ctx context.Context, keys []string) error { - ret := _m.Called(ctx, keys) - - var r0 error - if rf, ok := ret.Get(0).(func(context.Context, []string) error); ok { - r0 = rf(ctx, keys) - } else { - r0 = ret.Error(0) - } - - return r0 -} - -// Create provides a mock function with given fields: ctx, key, val -func (_m *MockInterface) Create(ctx context.Context, key string, val string) error { - ret := _m.Called(ctx, key, val) - - var r0 error - if rf, ok := ret.Get(0).(func(context.Context, string, string) error); ok { - r0 = rf(ctx, key, val) - } else { - r0 = ret.Error(0) - } - - return r0 -} - -// Get provides a mock function with given fields: ctx, key -func (_m *MockInterface) Get(ctx context.Context, key string) (string, error) { - ret := _m.Called(ctx, key) - - var r0 string - if rf, ok := ret.Get(0).(func(context.Context, string) string); ok { - r0 = rf(ctx, key) - } else { - r0 = ret.Get(0).(string) - } - - var r1 error - if rf, ok := ret.Get(1).(func(context.Context, string) error); ok { - r1 = rf(ctx, key) - } else { - r1 = ret.Error(1) - } - - return r0, r1 -} - -// List provides a mock function with given fields: ctx, key -func (_m *MockInterface) List(ctx context.Context, key string) ([]Keypair, error) { - ret := _m.Called(ctx, key) - - var r0 []Keypair - if rf, ok := ret.Get(0).(func(context.Context, string) []Keypair); ok { - r0 = rf(ctx, key) - } else { - if ret.Get(0) != nil { - r0 = ret.Get(0).([]Keypair) - } - } - - var r1 error - if rf, ok := ret.Get(1).(func(context.Context, string) error); ok { - r1 = rf(ctx, key) - } else { - r1 = ret.Error(1) - } - - return r0, r1 -} - -// Update provides a mock function with given fields: ctx, key, val -func (_m *MockInterface) Update(ctx context.Context, key string, val string) error { - ret := _m.Called(ctx, key, val) - - var r0 error - if rf, ok := ret.Get(0).(func(context.Context, string, string) error); ok { - r0 = rf(ctx, key, val) - } else { - r0 = ret.Error(0) - } - - return r0 -} - -// Watch provides a mock function with given fields: ctx, key -func (_m *MockInterface) Watch(ctx context.Context, key string) <-chan WatchResponse { - ret := _m.Called(ctx, key) - - var r0 <-chan WatchResponse - if rf, ok := ret.Get(0).(func(context.Context, string) <-chan WatchResponse); ok { - r0 = rf(ctx, key) - } else { - if ret.Get(0) != nil { - r0 = ret.Get(0).(<-chan WatchResponse) - } - } - - return r0 -} diff --git a/api/internal/core/store/store.go b/api/internal/core/store/store.go deleted file mode 100644 index b75f406dda..0000000000 --- a/api/internal/core/store/store.go +++ /dev/null @@ -1,423 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one or more - * contributor license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright ownership. - * The ASF licenses this file to You under the Apache License, Version 2.0 - * (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package store - -import ( - "context" - "encoding/json" - "fmt" - "os" - "reflect" - "sort" - "sync" - "time" - - "github.com/shiningrush/droplet/data" - - "github.com/apisix/manager-api/internal/core/entity" - "github.com/apisix/manager-api/internal/core/storage" - "github.com/apisix/manager-api/internal/log" - "github.com/apisix/manager-api/internal/utils" - "github.com/apisix/manager-api/internal/utils/runtime" -) - -var ( - storeNeedReInit = make([]*GenericStore, 0) -) - -type Pagination struct { - PageSize int `json:"page_size" form:"page_size" auto_read:"page_size"` - PageNumber int `json:"page" form:"page" auto_read:"page"` -} - -type Interface interface { - Type() HubKey - Get(ctx context.Context, key string) (interface{}, error) - List(ctx context.Context, input ListInput) (*ListOutput, error) - Create(ctx context.Context, obj interface{}) (interface{}, error) - Update(ctx context.Context, obj interface{}, createIfNotExist bool) (interface{}, error) - BatchDelete(ctx context.Context, keys []string) error -} - -type GenericStore struct { - Stg storage.Interface - initLock sync.Mutex - - cache sync.Map - opt GenericStoreOption - - cancel context.CancelFunc - closing bool -} - -type GenericStoreOption struct { - BasePath string - ObjType reflect.Type - KeyFunc func(obj interface{}) string - StockCheck func(obj interface{}, stockObj interface{}) error - Validator Validator - HubKey HubKey -} - -func NewGenericStore(opt GenericStoreOption) (*GenericStore, error) { - if opt.BasePath == "" { - log.Error("base path empty") - return nil, fmt.Errorf("base path can not be empty") - } - if opt.ObjType == nil { - log.Errorf("object type is nil") - return nil, fmt.Errorf("object type can not be nil") - } - if opt.KeyFunc == nil { - log.Error("key func is nil") - return nil, fmt.Errorf("key func can not be nil") - } - - if opt.ObjType.Kind() == reflect.Ptr { - opt.ObjType = opt.ObjType.Elem() - } - if opt.ObjType.Kind() != reflect.Struct { - log.Error("obj type is invalid") - return nil, fmt.Errorf("obj type is invalid") - } - s := &GenericStore{ - opt: opt, - } - s.Stg = storage.GenEtcdStorage() - - return s, nil -} - -func ReInit() error { - for _, store := range storeNeedReInit { - if err := store.Init(); err != nil { - return err - } - } - return nil -} - -func (s *GenericStore) Init() error { - s.initLock.Lock() - defer s.initLock.Unlock() - return s.listAndWatch() -} - -func (s *GenericStore) Type() HubKey { - return s.opt.HubKey -} - -func (s *GenericStore) Get(_ context.Context, key string) (interface{}, error) { - ret, ok := s.cache.Load(key) - if !ok { - log.Warnf("data not found by key: %s", key) - return nil, data.ErrNotFound - } - return ret, nil -} - -type ListInput struct { - Predicate func(obj interface{}) bool - Format func(obj interface{}) interface{} - PageSize int - // start from 1 - PageNumber int - Less func(i, j interface{}) bool -} - -type ListOutput struct { - Rows []interface{} `json:"rows"` - TotalSize int `json:"total_size"` -} - -// NewListOutput returns JSON marshalling safe struct pointer for empty slice -func NewListOutput() *ListOutput { - return &ListOutput{Rows: make([]interface{}, 0)} -} - -var defLessFunc = func(i, j interface{}) bool { - iBase := i.(entity.GetBaseInfo).GetBaseInfo() - jBase := j.(entity.GetBaseInfo).GetBaseInfo() - if iBase.CreateTime != jBase.CreateTime { - return iBase.CreateTime < jBase.CreateTime - } - if iBase.UpdateTime != jBase.UpdateTime { - return iBase.UpdateTime < jBase.UpdateTime - } - iID := utils.InterfaceToString(iBase.ID) - jID := utils.InterfaceToString(jBase.ID) - return iID < jID -} - -func (s *GenericStore) List(_ context.Context, input ListInput) (*ListOutput, error) { - var ret []interface{} - s.cache.Range(func(key, value interface{}) bool { - if input.Predicate != nil && !input.Predicate(value) { - return true - } - if input.Format != nil { - value = input.Format(value) - } - ret = append(ret, value) - return true - }) - - //should return an empty array not a null for client - if ret == nil { - ret = []interface{}{} - } - - output := &ListOutput{ - Rows: ret, - TotalSize: len(ret), - } - if input.Less == nil { - input.Less = defLessFunc - } - - sort.Slice(output.Rows, func(i, j int) bool { - return input.Less(output.Rows[i], output.Rows[j]) - }) - - if input.PageSize > 0 && input.PageNumber > 0 { - skipCount := (input.PageNumber - 1) * input.PageSize - if skipCount > output.TotalSize { - output.Rows = []interface{}{} - return output, nil - } - - endIdx := skipCount + input.PageSize - if endIdx >= output.TotalSize { - output.Rows = ret[skipCount:] - return output, nil - } - output.Rows = ret[skipCount:endIdx] - } - - return output, nil -} - -func (s *GenericStore) Range(_ context.Context, f func(key string, obj interface{}) bool) { - s.cache.Range(func(key, value interface{}) bool { - return f(key.(string), value) - }) -} - -func (s *GenericStore) ingestValidate(obj interface{}) (err error) { - if s.opt.Validator != nil { - if err := s.opt.Validator.Validate(obj); err != nil { - log.Errorf("data validate failed: %s, %v", err, obj) - return err - } - } - - if s.opt.StockCheck != nil { - s.cache.Range(func(key, value interface{}) bool { - if err = s.opt.StockCheck(obj, value); err != nil { - return false - } - return true - }) - } - return err -} - -func (s *GenericStore) CreateCheck(obj interface{}) ([]byte, error) { - - if setter, ok := obj.(entity.GetBaseInfo); ok { - info := setter.GetBaseInfo() - info.Creating() - } - - if err := s.ingestValidate(obj); err != nil { - return nil, err - } - - key := s.opt.KeyFunc(obj) - if key == "" { - return nil, fmt.Errorf("key is required") - } - _, ok := s.cache.Load(key) - if ok { - log.Warnf("key: %s is conflicted", key) - return nil, fmt.Errorf("key: %s is conflicted", key) - } - - bytes, err := json.Marshal(obj) - if err != nil { - log.Errorf("json marshal failed: %s", err) - return nil, fmt.Errorf("json marshal failed: %s", err) - } - - return bytes, nil -} - -func (s *GenericStore) Create(ctx context.Context, obj interface{}) (interface{}, error) { - if setter, ok := obj.(entity.GetBaseInfo); ok { - info := setter.GetBaseInfo() - info.Creating() - } - - bytes, err := s.CreateCheck(obj) - if err != nil { - return nil, err - } - - if err := s.Stg.Create(ctx, s.GetObjStorageKey(obj), string(bytes)); err != nil { - return nil, err - } - - return obj, nil -} - -func (s *GenericStore) Update(ctx context.Context, obj interface{}, createIfNotExist bool) (interface{}, error) { - if err := s.ingestValidate(obj); err != nil { - return nil, err - } - - key := s.opt.KeyFunc(obj) - if key == "" { - return nil, fmt.Errorf("key is required") - } - storedObj, ok := s.cache.Load(key) - if !ok { - if createIfNotExist { - return s.Create(ctx, obj) - } - log.Warnf("key: %s is not found", key) - return nil, fmt.Errorf("key: %s is not found", key) - } - - if setter, ok := obj.(entity.GetBaseInfo); ok { - storedGetter := storedObj.(entity.GetBaseInfo) - storedInfo := storedGetter.GetBaseInfo() - info := setter.GetBaseInfo() - info.Updating(storedInfo) - } - - bs, err := json.Marshal(obj) - if err != nil { - log.Errorf("json marshal failed: %s", err) - return nil, fmt.Errorf("json marshal failed: %s", err) - } - if err := s.Stg.Update(ctx, s.GetObjStorageKey(obj), string(bs)); err != nil { - return nil, err - } - - return obj, nil -} - -func (s *GenericStore) BatchDelete(ctx context.Context, keys []string) error { - var storageKeys []string - for i := range keys { - storageKeys = append(storageKeys, s.GetStorageKey(keys[i])) - } - - return s.Stg.BatchDelete(ctx, storageKeys) -} - -func (s *GenericStore) listAndWatch() error { - lc, lcancel := context.WithTimeout(context.TODO(), 5*time.Second) - defer lcancel() - ret, err := s.Stg.List(lc, s.opt.BasePath) - if err != nil { - return err - } - for i := range ret { - key := ret[i].Key[len(s.opt.BasePath)+1:] - objPtr, err := s.StringToObjPtr(ret[i].Value, key) - if err != nil { - _, _ = fmt.Fprintf(os.Stderr, "Error occurred while initializing logical store: %s, err: %v", s.opt.BasePath, err) - return err - } - - s.cache.Store(s.opt.KeyFunc(objPtr), objPtr) - } - - // start watch - s.cancel = s.watch() - - return nil -} - -func (s *GenericStore) watch() context.CancelFunc { - c, cancel := context.WithCancel(context.TODO()) - ch := s.Stg.Watch(c, s.opt.BasePath) - go func() { - defer func() { - if !s.closing { - log.Errorf("etcd watch exception closed, restarting: resource: %s", s.Type()) - storeNeedReInit = append(storeNeedReInit, s) - } - }() - defer runtime.HandlePanic() - for event := range ch { - if event.Canceled { - log.Warnf("etcd watch failed: %s", event.Error) - return - } - - for i := range event.Events { - switch event.Events[i].Type { - case storage.EventTypePut: - key := event.Events[i].Key[len(s.opt.BasePath)+1:] - objPtr, err := s.StringToObjPtr(event.Events[i].Value, key) - if err != nil { - log.Warnf("value convert to obj failed: %s", err) - continue - } - s.cache.Store(key, objPtr) - case storage.EventTypeDelete: - s.cache.Delete(event.Events[i].Key[len(s.opt.BasePath)+1:]) - } - } - } - }() - return cancel -} - -func (s *GenericStore) Close() error { - s.closing = true - s.cancel() - return nil -} - -func (s *GenericStore) StringToObjPtr(str, key string) (interface{}, error) { - objPtr := reflect.New(s.opt.ObjType) - ret := objPtr.Interface() - err := json.Unmarshal([]byte(str), ret) - if err != nil { - log.Errorf("json unmarshal failed: %s", err) - return nil, fmt.Errorf("json unmarshal failed\n\tRelated Key:\t\t%s\n\tError Description:\t%s", key, err) - } - - if setter, ok := ret.(entity.GetBaseInfo); ok { - info := setter.GetBaseInfo() - info.KeyCompat(key) - } - - return ret, nil -} - -func (s *GenericStore) GetObjStorageKey(obj interface{}) string { - return s.GetStorageKey(s.opt.KeyFunc(obj)) -} - -func (s *GenericStore) GetStorageKey(key string) string { - return fmt.Sprintf("%s/%s", s.opt.BasePath, key) -} diff --git a/api/internal/core/store/store_mock.go b/api/internal/core/store/store_mock.go deleted file mode 100644 index 10fc5b3032..0000000000 --- a/api/internal/core/store/store_mock.go +++ /dev/null @@ -1,80 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one or more - * contributor license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright ownership. - * The ASF licenses this file to You under the Apache License, Version 2.0 - * (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package store - -import ( - "context" - "sort" - - "github.com/stretchr/testify/mock" -) - -type MockInterface struct { - mock.Mock - HubKey HubKey -} - -func (m *MockInterface) Type() HubKey { - return m.HubKey -} - -func (m *MockInterface) Get(_ context.Context, key string) (interface{}, error) { - ret := m.Mock.Called(key) - return ret.Get(0), ret.Error(1) -} - -func (m *MockInterface) List(_ context.Context, input ListInput) (*ListOutput, error) { - ret := m.Called(input) - - var ( - r0 *ListOutput - r1 error - ) - - if rf, ok := ret.Get(0).(func(ListInput) *ListOutput); ok { - r0 = rf(input) - } else { - r0 = ret.Get(0).(*ListOutput) - } - - if input.Less == nil { - input.Less = defLessFunc - } - - sort.Slice(r0.Rows, func(i, j int) bool { - return input.Less(r0.Rows[i], r0.Rows[j]) - }) - - r1 = ret.Error(1) - - return r0, r1 -} - -func (m *MockInterface) Create(ctx context.Context, obj interface{}) (interface{}, error) { - ret := m.Mock.Called(ctx, obj) - return ret.Get(0), ret.Error(1) -} - -func (m *MockInterface) Update(ctx context.Context, obj interface{}, createOnFail bool) (interface{}, error) { - ret := m.Mock.Called(ctx, obj, createOnFail) - return ret.Get(0), ret.Error(1) -} - -func (m *MockInterface) BatchDelete(ctx context.Context, keys []string) error { - ret := m.Mock.Called(ctx, keys) - return ret.Error(0) -} diff --git a/api/internal/core/store/store_test.go b/api/internal/core/store/store_test.go deleted file mode 100644 index 1cb8f8dd1c..0000000000 --- a/api/internal/core/store/store_test.go +++ /dev/null @@ -1,812 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one or more - * contributor license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright ownership. - * The ASF licenses this file to You under the Apache License, Version 2.0 - * (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package store - -import ( - "context" - "encoding/json" - "fmt" - "reflect" - "strings" - "testing" - "time" - - "github.com/shiningrush/droplet/data" - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/mock" - - "github.com/apisix/manager-api/internal/core/entity" - "github.com/apisix/manager-api/internal/core/storage" - "github.com/apisix/manager-api/internal/utils" -) - -func TestNewGenericStore(t *testing.T) { - dfFunc := func(obj interface{}) string { return "" } - tests := []struct { - giveOpt GenericStoreOption - giveCache map[string]interface{} - wantStore *GenericStore - wantErr error - }{ - { - giveOpt: GenericStoreOption{ - BasePath: "test", - ObjType: reflect.TypeOf(GenericStoreOption{}), - KeyFunc: dfFunc, - }, - wantStore: &GenericStore{ - Stg: storage.GenEtcdStorage(), - opt: GenericStoreOption{ - BasePath: "test", - ObjType: reflect.TypeOf(GenericStoreOption{}), - KeyFunc: dfFunc, - }, - }, - }, - { - giveOpt: GenericStoreOption{ - BasePath: "", - ObjType: reflect.TypeOf(GenericStoreOption{}), - KeyFunc: dfFunc, - }, - wantErr: fmt.Errorf("base path can not be empty"), - }, - { - giveOpt: GenericStoreOption{ - BasePath: "test", - ObjType: reflect.TypeOf(""), - KeyFunc: dfFunc, - }, - wantErr: fmt.Errorf("obj type is invalid"), - }, - { - giveOpt: GenericStoreOption{ - BasePath: "test", - ObjType: nil, - KeyFunc: dfFunc, - }, - wantErr: fmt.Errorf("object type can not be nil"), - }, - { - giveOpt: GenericStoreOption{ - BasePath: "test", - ObjType: reflect.TypeOf(GenericStoreOption{}), - KeyFunc: nil, - }, - wantErr: fmt.Errorf("key func can not be nil"), - }, - } - for _, tc := range tests { - s, err := NewGenericStore(tc.giveOpt) - assert.Equal(t, tc.wantErr, err) - if err != nil { - continue - } - assert.Equal(t, tc.wantStore.Stg, s.Stg) - assert.Equal(t, tc.wantStore.cache, s.cache) - assert.Equal(t, tc.wantStore.opt.BasePath, s.opt.BasePath) - assert.Equal(t, tc.wantStore.opt.ObjType, s.opt.ObjType) - assert.Equal(t, reflect.TypeOf(tc.wantStore.opt.KeyFunc), reflect.TypeOf(s.opt.KeyFunc)) - } -} - -type TestStruct struct { - entity.BaseInfo - Field1 string - Field2 string -} - -func TestGenericStore_Init(t *testing.T) { - tests := []struct { - caseDesc string - giveStore *GenericStore - giveListErr error - giveListRet []storage.Keypair - giveWatchCh chan storage.WatchResponse - giveResp storage.WatchResponse - wantErr error - wantCache map[string]interface{} - wantListCalled bool - wantWatchCalled bool - }{ - { - caseDesc: "sanity", - giveStore: &GenericStore{ - opt: GenericStoreOption{ - BasePath: "test", - ObjType: reflect.TypeOf(TestStruct{}), - KeyFunc: func(obj interface{}) string { - return obj.(*TestStruct).Field1 - }, - }, - }, - giveListRet: []storage.Keypair{ - { - Key: "test/demo1-f1", - Value: `{"Field1":"demo1-f1", "Field2":"demo1-f2"}`, - }, - { - Key: "test/demo2-f1", - Value: `{"Field1":"demo2-f1", "Field2":"demo2-f2"}`, - }, - }, - giveWatchCh: make(chan storage.WatchResponse), - giveResp: storage.WatchResponse{ - Events: []storage.Event{ - { - Keypair: storage.Keypair{ - Key: "test/demo3-f1", - Value: `{"Field1":"demo3-f1", "Field2":"demo3-f2"}`, - }, - Type: storage.EventTypePut, - }, - { - Type: storage.EventTypeDelete, - Keypair: storage.Keypair{ - Key: "test/demo1-f1", - }, - }, - }, - }, - wantCache: map[string]interface{}{ - "demo2-f1": &TestStruct{ - BaseInfo: entity.BaseInfo{ID: "demo2-f1"}, - Field1: "demo2-f1", - Field2: "demo2-f2", - }, - "demo3-f1": &TestStruct{ - BaseInfo: entity.BaseInfo{ID: "demo3-f1"}, - Field1: "demo3-f1", - Field2: "demo3-f2", - }, - }, - wantListCalled: true, - wantWatchCalled: true, - }, - { - caseDesc: "list error", - giveStore: &GenericStore{}, - giveListErr: fmt.Errorf("list error"), - wantErr: fmt.Errorf("list error"), - wantListCalled: true, - }, - { - caseDesc: "json error", - giveStore: &GenericStore{ - opt: GenericStoreOption{ - BasePath: "test", - ObjType: reflect.TypeOf(TestStruct{}), - KeyFunc: func(obj interface{}) string { - return obj.(*TestStruct).Field1 - }, - }, - }, - giveListRet: []storage.Keypair{ - { - Key: "test/demo1-f1", - Value: `{"Field1","demo1-f1", "Field2":"demo1-f2"}`, - }, - { - Key: "test/demo2-f1", - Value: `{"Field1":"demo2-f1", "Field2":"demo2-f2"}`, - }, - }, - wantErr: fmt.Errorf("json unmarshal failed\n\tRelated Key:\t\tdemo1-f1\n\tError Description:\t" + - "invalid character ',' after object key"), - wantListCalled: true, - }, - } - - for _, tc := range tests { - listCalled, watchCalled := false, false - mStorage := &storage.MockInterface{} - mStorage.On("List", mock.Anything, mock.Anything).Run(func(args mock.Arguments) { - listCalled = true - assert.Equal(t, tc.giveStore.opt.BasePath, args[1], tc.caseDesc) - }).Return(tc.giveListRet, tc.giveListErr) - mStorage.On("Watch", mock.Anything, mock.Anything).Run(func(args mock.Arguments) { - watchCalled = true - assert.Equal(t, tc.giveStore.opt.BasePath, args[1], tc.caseDesc) - }).Return((<-chan storage.WatchResponse)(tc.giveWatchCh)) - - tc.giveStore.Stg = mStorage - err := tc.giveStore.Init() - assert.Equal(t, tc.wantListCalled, listCalled, tc.caseDesc) - assert.Equal(t, tc.wantWatchCalled, watchCalled, tc.caseDesc) - if err != nil { - assert.Equal(t, tc.wantErr.Error(), err.Error(), tc.caseDesc) - continue - } - tc.giveWatchCh <- tc.giveResp - time.Sleep(1 * time.Second) - close(tc.giveWatchCh) - tc.giveStore.cache.Range(func(key, value interface{}) bool { - assert.Equal(t, tc.wantCache[key.(string)], value) - return true - }) - } -} - -func TestGenericStore_Get(t *testing.T) { - tests := []struct { - caseDesc string - giveId string - giveStore *GenericStore - giveCache map[string]interface{} - wantRet interface{} - wantErr error - }{ - { - caseDesc: "sanity", - giveId: "test1", - giveCache: map[string]interface{}{ - "test2": TestStruct{ - Field1: "test2-f1", - Field2: "test2-f2", - }, - "test1": TestStruct{ - Field1: "test1-f1", - Field2: "test1-f2", - }, - }, - giveStore: &GenericStore{}, - wantRet: TestStruct{ - Field1: "test1-f1", - Field2: "test1-f2", - }, - }, - { - caseDesc: "not found", - giveId: "not", - giveCache: map[string]interface{}{ - "test2": TestStruct{ - Field1: "test2-f1", - Field2: "test2-f2", - }, - "test1": TestStruct{ - Field1: "test1-f1", - Field2: "test1-f2", - }, - }, - giveStore: &GenericStore{}, - wantErr: data.ErrNotFound, - }, - } - - for _, tc := range tests { - for k, v := range tc.giveCache { - tc.giveStore.cache.Store(k, v) - } - ret, err := tc.giveStore.Get(context.Background(), tc.giveId) - assert.Equal(t, tc.wantRet, ret, tc.caseDesc) - assert.Equal(t, tc.wantErr, err, tc.caseDesc) - } -} - -func TestGenericStore_List(t *testing.T) { - tests := []struct { - caseDesc string - giveInput ListInput - giveStore *GenericStore - giveCache map[string]interface{} - wantRet *ListOutput - wantErr error - }{ - { - caseDesc: "sanity", - giveInput: ListInput{ - Predicate: func(obj interface{}) bool { - for _, v := range strings.Split("test1-f2,test3-f2", ",") { - if v == obj.(*TestStruct).Field2 { - return true - } - } - return false - }, - }, - giveCache: map[string]interface{}{ - "test1": &TestStruct{ - Field1: "test1-f1", - Field2: "test1-f2", - }, - "test2": &TestStruct{ - Field1: "test2-f1", - Field2: "test2-f2", - }, - "test3": &TestStruct{ - Field1: "test3-f1", - Field2: "test3-f2", - }, - "test4": &TestStruct{ - Field1: "test4-f1", - Field2: "test4-f2", - }, - }, - giveStore: &GenericStore{}, - wantRet: &ListOutput{ - Rows: []interface{}{ - &TestStruct{ - Field1: "test1-f1", - Field2: "test1-f2", - }, - &TestStruct{ - Field1: "test3-f1", - Field2: "test3-f2", - }, - }, - TotalSize: 2, - }, - }, - { - caseDesc: "sanity-page", - giveInput: ListInput{ - Predicate: func(obj interface{}) bool { - for _, v := range strings.Split("test1-f2,test3-f2", ",") { - if v == obj.(*TestStruct).Field2 { - return true - } - } - return false - }, - PageSize: 1, - PageNumber: 2, - }, - giveCache: map[string]interface{}{ - "test1": &TestStruct{ - Field1: "test1-f1", - Field2: "test1-f2", - }, - "test2": &TestStruct{ - Field1: "test2-f1", - Field2: "test2-f2", - }, - "test3": &TestStruct{ - BaseInfo: entity.BaseInfo{ - CreateTime: 100, - }, - Field1: "test3-f1", - Field2: "test3-f2", - }, - "test4": &TestStruct{ - Field1: "test4-f1", - Field2: "test4-f2", - }, - }, - giveStore: &GenericStore{}, - wantRet: &ListOutput{ - Rows: []interface{}{ - &TestStruct{ - BaseInfo: entity.BaseInfo{ - CreateTime: 100, - }, - Field1: "test3-f1", - Field2: "test3-f2", - }, - }, - TotalSize: 2, - }, - }, - { - caseDesc: "page overflow", - giveInput: ListInput{ - Predicate: func(obj interface{}) bool { - for _, v := range strings.Split("test1-f2,test3-f2", ",") { - if v == obj.(*TestStruct).Field2 { - return true - } - } - return false - }, - PageSize: 1, - PageNumber: 33, - }, - giveCache: map[string]interface{}{ - "test1": &TestStruct{ - Field1: "test1-f1", - Field2: "test1-f2", - }, - "test2": &TestStruct{ - Field1: "test2-f1", - Field2: "test2-f2", - }, - "test3": &TestStruct{ - Field1: "test3-f1", - Field2: "test3-f2", - }, - "test4": &TestStruct{ - Field1: "test4-f1", - Field2: "test4-f2", - }, - }, - giveStore: &GenericStore{}, - wantRet: &ListOutput{ - Rows: []interface{}{}, - TotalSize: 2, - }, - }, - } - - for _, tc := range tests { - for k, v := range tc.giveCache { - tc.giveStore.cache.Store(k, v) - } - - ret, err := tc.giveStore.List(context.Background(), tc.giveInput) - assert.Equal(t, tc.wantRet.TotalSize, ret.TotalSize, tc.caseDesc) - assert.ElementsMatch(t, tc.wantRet.Rows, ret.Rows, tc.caseDesc) - assert.Equal(t, tc.wantErr, err, tc.caseDesc) - } -} - -func TestGenericStore_ingestValidate(t *testing.T) { - tests := []struct { - giveStore *GenericStore - giveCache map[string]interface{} - giveObj interface{} - giveStockCheck func(obj interface{}, stockObj interface{}) error - giveValidateErr error - wantErr error - }{ - { - giveStore: &GenericStore{}, - giveCache: map[string]interface{}{ - "test1-f1": &TestStruct{Field1: "test1-f1", Field2: "test1-f2"}, - "test2-f1": &TestStruct{Field1: "test2-f1", Field2: "test2-f2"}, - }, - giveObj: &TestStruct{ - Field1: "test3-f1", - Field2: "test2-f2", - }, - giveStockCheck: func(obj interface{}, stockObj interface{}) error { - if obj.(*TestStruct).Field2 == stockObj.(*TestStruct).Field2 { - return fmt.Errorf("field2: %s is conflicted", obj.(*TestStruct).Field2) - } - return nil - }, - wantErr: fmt.Errorf("field2: test2-f2 is conflicted"), - }, - { - giveStore: &GenericStore{}, - giveObj: &TestStruct{}, - giveValidateErr: fmt.Errorf("validate failed"), - wantErr: fmt.Errorf("validate failed"), - }, - } - - for _, tc := range tests { - for k, v := range tc.giveCache { - tc.giveStore.cache.Store(k, v) - } - - validateCalled := false - mValidator := &MockValidator{} - mValidator.On("Validate", mock.Anything).Run(func(args mock.Arguments) { - validateCalled = true - assert.Equal(t, tc.giveObj, args.Get(0)) - }).Return(tc.giveValidateErr) - - tc.giveStore.opt.Validator = mValidator - tc.giveStore.opt.StockCheck = tc.giveStockCheck - err := tc.giveStore.ingestValidate(tc.giveObj) - assert.True(t, validateCalled) - assert.Equal(t, tc.wantErr, err) - } -} - -func TestGenericStore_Create(t *testing.T) { - tests := []struct { - caseDesc string - giveStore *GenericStore - giveCache map[string]interface{} - giveObj *TestStruct - giveErr error - giveValidateErr error - wantKey string - wantErr error - }{ - { - caseDesc: "sanity", - giveObj: &TestStruct{ - Field1: "test1", - Field2: "test2", - }, - giveStore: &GenericStore{ - opt: GenericStoreOption{ - BasePath: "test/path", - KeyFunc: func(obj interface{}) string { - return obj.(*TestStruct).Field1 - }, - }, - }, - wantKey: "test/path/test1", - }, - { - caseDesc: "create failed", - giveObj: &TestStruct{ - Field1: "test1", - Field2: "test2", - }, - giveStore: &GenericStore{ - opt: GenericStoreOption{ - BasePath: "test/path", - KeyFunc: func(obj interface{}) string { - return obj.(*TestStruct).Field1 - }, - }, - }, - giveErr: fmt.Errorf("create failed"), - wantKey: "test/path/test1", - wantErr: fmt.Errorf("create failed"), - }, - { - caseDesc: "conflicted", - giveObj: &TestStruct{ - Field1: "test1", - Field2: "test2", - }, - giveCache: map[string]interface{}{ - "test1": struct{}{}, - }, - giveStore: &GenericStore{ - opt: GenericStoreOption{ - BasePath: "test/path", - KeyFunc: func(obj interface{}) string { - return obj.(*TestStruct).Field1 - }, - }, - }, - wantErr: fmt.Errorf("key: test1 is conflicted"), - }, - { - caseDesc: "validate failed", - giveObj: &TestStruct{ - Field1: "test1", - Field2: "test2", - }, - giveCache: map[string]interface{}{ - "test1": struct{}{}, - }, - giveStore: &GenericStore{ - opt: GenericStoreOption{}, - }, - giveValidateErr: fmt.Errorf("validate failed"), - wantErr: fmt.Errorf("validate failed"), - }, - } - - for _, tc := range tests { - for k, v := range tc.giveCache { - tc.giveStore.cache.Store(k, v) - } - - createCalled, validateCalled := false, false - mStorage := &storage.MockInterface{} - mStorage.On("Create", mock.Anything, mock.Anything, mock.Anything).Run(func(args mock.Arguments) { - createCalled = true - assert.Equal(t, tc.wantKey, args[1], tc.caseDesc) - input := TestStruct{} - err := json.Unmarshal([]byte(args[2].(string)), &input) - id := utils.InterfaceToString(input.ID) - assert.Nil(t, err) - assert.Equal(t, tc.giveObj.Field1, input.Field1, tc.caseDesc) - assert.Equal(t, tc.giveObj.Field2, input.Field2, tc.caseDesc) - assert.NotEqual(t, 0, len(id), tc.caseDesc) - assert.NotEqual(t, 0, input.CreateTime, tc.caseDesc) - assert.NotEqual(t, 0, input.UpdateTime, tc.caseDesc) - }).Return(tc.giveErr) - - mValidator := &MockValidator{} - mValidator.On("Validate", mock.Anything).Run(func(args mock.Arguments) { - validateCalled = true - assert.Equal(t, tc.giveObj, args.Get(0), tc.caseDesc) - }).Return(tc.giveValidateErr) - - tc.giveStore.Stg = mStorage - tc.giveStore.opt.Validator = mValidator - ret, err := tc.giveStore.Create(context.TODO(), tc.giveObj) - assert.True(t, validateCalled, tc.caseDesc) - if err != nil { - assert.Equal(t, tc.wantErr, err, tc.caseDesc) - continue - } - retTs, ok := ret.(*TestStruct) - assert.True(t, ok) - // The returned value (retTs) should be the same as the input (tc.giveObj) - assert.Equal(t, tc.giveObj.Field1, retTs.Field1, tc.caseDesc) - assert.Equal(t, tc.giveObj.Field2, retTs.Field2, tc.caseDesc) - assert.True(t, createCalled, tc.caseDesc) - } -} - -func TestGenericStore_Update(t *testing.T) { - tests := []struct { - caseDesc string - giveStore *GenericStore - giveCache map[string]interface{} - giveObj *TestStruct - giveErr error - giveValidateErr error - wantKey string - wantErr error - }{ - { - caseDesc: "sanity", - giveObj: &TestStruct{ - Field1: "test1", - Field2: "test2", - }, - giveCache: map[string]interface{}{ - "test1": &TestStruct{}, - }, - giveStore: &GenericStore{ - opt: GenericStoreOption{ - BasePath: "test/path", - KeyFunc: func(obj interface{}) string { - return obj.(*TestStruct).Field1 - }, - }, - }, - wantKey: "test/path/test1", - }, - { - caseDesc: "create failed", - giveObj: &TestStruct{ - Field1: "test1", - Field2: "test2", - }, - giveCache: map[string]interface{}{ - "test1": &TestStruct{}, - }, - giveStore: &GenericStore{ - opt: GenericStoreOption{ - BasePath: "test/path", - KeyFunc: func(obj interface{}) string { - return obj.(*TestStruct).Field1 - }, - }, - }, - giveErr: fmt.Errorf("create failed"), - wantKey: "test/path/test1", - wantErr: fmt.Errorf("create failed"), - }, - { - caseDesc: "not found", - giveObj: &TestStruct{ - Field1: "test1", - Field2: "test2", - }, - giveCache: map[string]interface{}{ - "test2": &TestStruct{}, - }, - giveStore: &GenericStore{ - opt: GenericStoreOption{ - BasePath: "test/path", - KeyFunc: func(obj interface{}) string { - return obj.(*TestStruct).Field1 - }, - }, - }, - wantErr: fmt.Errorf("key: test1 is not found"), - }, - } - - for _, tc := range tests { - for k, v := range tc.giveCache { - tc.giveStore.cache.Store(k, v) - } - - createCalled, validateCalled := false, false - mStorage := &storage.MockInterface{} - mStorage.On("Update", mock.Anything, mock.Anything, mock.Anything).Run(func(args mock.Arguments) { - createCalled = true - assert.Equal(t, tc.wantKey, args[1], tc.caseDesc) - input := TestStruct{} - err := json.Unmarshal([]byte(args[2].(string)), &input) - assert.Nil(t, err) - assert.Equal(t, tc.giveObj.Field1, input.Field1, tc.caseDesc) - assert.Equal(t, tc.giveObj.Field2, input.Field2, tc.caseDesc) - assert.NotEqual(t, 0, input.UpdateTime, tc.caseDesc) - }).Return(tc.giveErr) - - mValidator := &MockValidator{} - mValidator.On("Validate", mock.Anything).Run(func(args mock.Arguments) { - validateCalled = true - assert.Equal(t, tc.giveObj, args.Get(0), tc.caseDesc) - }).Return(tc.giveValidateErr) - - tc.giveStore.Stg = mStorage - tc.giveStore.opt.Validator = mValidator - - ret, err := tc.giveStore.Update(context.TODO(), tc.giveObj, false) - assert.True(t, validateCalled, tc.caseDesc) - if err != nil { - assert.Equal(t, tc.wantErr, err, tc.caseDesc) - continue - } - retTs, ok := ret.(*TestStruct) - assert.True(t, ok) - // The returned value (retTs) should be the same as the input (tc.giveObj) - assert.Equal(t, tc.giveObj.Field1, retTs.Field1, tc.caseDesc) - assert.Equal(t, tc.giveObj.Field2, retTs.Field2, tc.caseDesc) - assert.True(t, createCalled, tc.caseDesc) - } -} - -func TestGenericStore_Delete(t *testing.T) { - tests := []struct { - caseDesc string - giveStore *GenericStore - giveKeys []string - giveErr error - wantKey []string - wantErr error - }{ - { - caseDesc: "sanity", - giveKeys: []string{"test1", "test2"}, - giveStore: &GenericStore{ - opt: GenericStoreOption{ - BasePath: "test/path", - }, - }, - wantKey: []string{"test/path/test1", "test/path/test2"}, - }, - { - caseDesc: "delete failed", - giveKeys: []string{"test1", "test2"}, - giveStore: &GenericStore{ - opt: GenericStoreOption{ - BasePath: "test/path", - }, - }, - wantKey: []string{"test/path/test1", "test/path/test2"}, - giveErr: fmt.Errorf("delete failed"), - wantErr: fmt.Errorf("delete failed"), - }, - } - - for _, tc := range tests { - createCalled := false - mStorage := &storage.MockInterface{} - mStorage.On("BatchDelete", mock.Anything, mock.Anything).Run(func(args mock.Arguments) { - createCalled = true - assert.Equal(t, tc.wantKey, args[1], tc.caseDesc) - }).Return(tc.giveErr) - - tc.giveStore.Stg = mStorage - err := tc.giveStore.BatchDelete(context.TODO(), tc.giveKeys) - assert.True(t, createCalled, tc.caseDesc) - assert.Equal(t, tc.wantErr, err, tc.caseDesc) - } -} - -func TestGenericStore_StringToObjPtr(t *testing.T) { - s, err := NewGenericStore(GenericStoreOption{ - BasePath: "test", - ObjType: reflect.TypeOf(entity.SSL{}), - KeyFunc: func(obj interface{}) string { - r := obj.(*entity.Route) - return utils.InterfaceToString(r.ID) - }, - }) - assert.Nil(t, err) - id := "1" - sslStr := `{"key":"test_key", "cert":"test_cert"}` - sslInterface, err := s.StringToObjPtr(sslStr, id) - ssl := sslInterface.(*entity.SSL) - assert.Equal(t, id, ssl.ID) -} diff --git a/api/internal/core/store/storehub.go b/api/internal/core/store/storehub.go deleted file mode 100644 index 6660e320ae..0000000000 --- a/api/internal/core/store/storehub.go +++ /dev/null @@ -1,248 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one or more - * contributor license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright ownership. - * The ASF licenses this file to You under the Apache License, Version 2.0 - * (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package store - -import ( - "fmt" - "reflect" - - "github.com/apisix/manager-api/internal/conf" - "github.com/apisix/manager-api/internal/core/entity" - "github.com/apisix/manager-api/internal/log" - "github.com/apisix/manager-api/internal/utils" -) - -type HubKey string - -const ( - HubKeyConsumer HubKey = "consumer" - HubKeyRoute HubKey = "route" - HubKeyService HubKey = "service" - HubKeySsl HubKey = "ssl" - HubKeyUpstream HubKey = "upstream" - HubKeyScript HubKey = "script" - HubKeyGlobalRule HubKey = "global_rule" - HubKeyServerInfo HubKey = "server_info" - HubKeyPluginConfig HubKey = "plugin_config" - HubKeyProto HubKey = "proto" - HubKeyStreamRoute HubKey = "stream_route" - HubKeySystemConfig HubKey = "system_config" -) - -var ( - storeHub = map[HubKey]*GenericStore{} -) - -func InitStore(key HubKey, opt GenericStoreOption) error { - hubsNeedCheck := map[HubKey]bool{ - HubKeyConsumer: true, - HubKeyRoute: true, - HubKeySsl: true, - HubKeyService: true, - HubKeyUpstream: true, - HubKeyGlobalRule: true, - HubKeyStreamRoute: true, - HubKeySystemConfig: true, - } - - if _, ok := hubsNeedCheck[key]; ok { - validator, err := NewAPISIXJsonSchemaValidator("main." + string(key)) - if err != nil { - return err - } - opt.Validator = validator - } - opt.HubKey = key - s, err := NewGenericStore(opt) - if err != nil { - log.Errorf("NewGenericStore error: %s", err) - return err - } - if err := s.Init(); err != nil { - log.Errorf("GenericStore init error: %s", err) - return err - } - - utils.AppendToClosers(s.Close) - storeHub[key] = s - return nil -} - -func GetStore(key HubKey) *GenericStore { - if s, ok := storeHub[key]; ok { - return s - } - panic(fmt.Sprintf("no store with key: %s", key)) -} - -func RangeStore(f func(key HubKey, store *GenericStore) bool) { - for k, s := range storeHub { - if k != "" && s != nil { - if !f(k, s) { - break - } - } - } -} - -func InitStores() error { - err := InitStore(HubKeyConsumer, GenericStoreOption{ - BasePath: conf.ETCDConfig.Prefix + "/consumers", - ObjType: reflect.TypeOf(entity.Consumer{}), - KeyFunc: func(obj interface{}) string { - r := obj.(*entity.Consumer) - return r.Username - }, - }) - if err != nil { - return err - } - - err = InitStore(HubKeyRoute, GenericStoreOption{ - BasePath: conf.ETCDConfig.Prefix + "/routes", - ObjType: reflect.TypeOf(entity.Route{}), - KeyFunc: func(obj interface{}) string { - r := obj.(*entity.Route) - return utils.InterfaceToString(r.ID) - }, - }) - if err != nil { - return err - } - - err = InitStore(HubKeyService, GenericStoreOption{ - BasePath: conf.ETCDConfig.Prefix + "/services", - ObjType: reflect.TypeOf(entity.Service{}), - KeyFunc: func(obj interface{}) string { - r := obj.(*entity.Service) - return utils.InterfaceToString(r.ID) - }, - }) - if err != nil { - return err - } - - err = InitStore(HubKeySsl, GenericStoreOption{ - BasePath: conf.ETCDConfig.Prefix + "/ssls", - ObjType: reflect.TypeOf(entity.SSL{}), - KeyFunc: func(obj interface{}) string { - r := obj.(*entity.SSL) - return utils.InterfaceToString(r.ID) - }, - }) - if err != nil { - return err - } - - err = InitStore(HubKeyUpstream, GenericStoreOption{ - BasePath: conf.ETCDConfig.Prefix + "/upstreams", - ObjType: reflect.TypeOf(entity.Upstream{}), - KeyFunc: func(obj interface{}) string { - r := obj.(*entity.Upstream) - return utils.InterfaceToString(r.ID) - }, - }) - if err != nil { - return err - } - - err = InitStore(HubKeyScript, GenericStoreOption{ - BasePath: conf.ETCDConfig.Prefix + "/scripts", - ObjType: reflect.TypeOf(entity.Script{}), - KeyFunc: func(obj interface{}) string { - r := obj.(*entity.Script) - return r.ID - }, - }) - if err != nil { - return err - } - - err = InitStore(HubKeyGlobalRule, GenericStoreOption{ - BasePath: conf.ETCDConfig.Prefix + "/global_rules", - ObjType: reflect.TypeOf(entity.GlobalPlugins{}), - KeyFunc: func(obj interface{}) string { - r := obj.(*entity.GlobalPlugins) - return utils.InterfaceToString(r.ID) - }, - }) - if err != nil { - return err - } - - err = InitStore(HubKeyServerInfo, GenericStoreOption{ - BasePath: conf.ETCDConfig.Prefix + "/data_plane/server_info", - ObjType: reflect.TypeOf(entity.ServerInfo{}), - KeyFunc: func(obj interface{}) string { - r := obj.(*entity.ServerInfo) - return utils.InterfaceToString(r.ID) - }, - }) - if err != nil { - return err - } - - err = InitStore(HubKeyPluginConfig, GenericStoreOption{ - BasePath: conf.ETCDConfig.Prefix + "/plugin_configs", - ObjType: reflect.TypeOf(entity.PluginConfig{}), - KeyFunc: func(obj interface{}) string { - r := obj.(*entity.PluginConfig) - return utils.InterfaceToString(r.ID) - }, - }) - if err != nil { - return err - } - - err = InitStore(HubKeyProto, GenericStoreOption{ - BasePath: conf.ETCDConfig.Prefix + "/protos", - ObjType: reflect.TypeOf(entity.Proto{}), - KeyFunc: func(obj interface{}) string { - r := obj.(*entity.Proto) - return utils.InterfaceToString(r.ID) - }, - }) - if err != nil { - return err - } - - err = InitStore(HubKeyStreamRoute, GenericStoreOption{ - BasePath: conf.ETCDConfig.Prefix + "/stream_routes", - ObjType: reflect.TypeOf(entity.StreamRoute{}), - KeyFunc: func(obj interface{}) string { - r := obj.(*entity.StreamRoute) - return utils.InterfaceToString(r.ID) - }, - }) - if err != nil { - return err - } - - err = InitStore(HubKeySystemConfig, GenericStoreOption{ - BasePath: conf.ETCDConfig.Prefix + "/system_config", - ObjType: reflect.TypeOf(entity.SystemConfig{}), - KeyFunc: func(obj interface{}) string { - r := obj.(*entity.SystemConfig) - return r.ConfigName - }, - }) - if err != nil { - return err - } - - return nil -} diff --git a/api/internal/core/store/test_case.json b/api/internal/core/store/test_case.json deleted file mode 100644 index 616dd5514f..0000000000 --- a/api/internal/core/store/test_case.json +++ /dev/null @@ -1,19 +0,0 @@ -{ - "$schema": "http://json-schema.org/draft-04/schema#", - "type": "object", - "properties": { - "name": { - "type": "string", - "minLength": 10 - }, - "email": { - "type": "string", - "maxLength": 10 - }, - "age": { - "type": "integer", - "minimum": 0 - } - }, - "additionalProperties": false -} diff --git a/api/internal/core/store/validate.go b/api/internal/core/store/validate.go deleted file mode 100644 index 94d7fe71c3..0000000000 --- a/api/internal/core/store/validate.go +++ /dev/null @@ -1,362 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one or more - * contributor license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright ownership. - * The ASF licenses this file to You under the Apache License, Version 2.0 - * (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package store - -import ( - "encoding/json" - "errors" - "fmt" - "io/ioutil" - - "github.com/xeipuuv/gojsonschema" - "go.uber.org/zap/buffer" - - "github.com/apisix/manager-api/internal/conf" - "github.com/apisix/manager-api/internal/core/entity" - "github.com/apisix/manager-api/internal/log" -) - -type Validator interface { - Validate(obj interface{}) error -} -type JsonSchemaValidator struct { - schema *gojsonschema.Schema -} - -func NewJsonSchemaValidator(jsonPath string) (Validator, error) { - bs, err := ioutil.ReadFile(jsonPath) - if err != nil { - return nil, fmt.Errorf("get abs path failed: %s", err) - } - s, err := gojsonschema.NewSchema(gojsonschema.NewStringLoader(string(bs))) - if err != nil { - return nil, fmt.Errorf("new schema failed: %s", err) - } - return &JsonSchemaValidator{ - schema: s, - }, nil -} - -func (v *JsonSchemaValidator) Validate(obj interface{}) error { - ret, err := v.schema.Validate(gojsonschema.NewGoLoader(obj)) - if err != nil { - return fmt.Errorf("validate failed: %s", err) - } - - if !ret.Valid() { - errString := buffer.Buffer{} - for i, vErr := range ret.Errors() { - if i != 0 { - errString.AppendString("\n") - } - errString.AppendString(vErr.String()) - } - return errors.New(errString.String()) - } - return nil -} - -type APISIXJsonSchemaValidator struct { - schema *gojsonschema.Schema - schemaDef string -} - -func NewAPISIXJsonSchemaValidator(jsonPath string) (Validator, error) { - schemaDef := conf.Schema.Get(jsonPath).String() - if schemaDef == "" { - log.Errorf("schema validate failed: schema not found, path: %s", jsonPath) - return nil, fmt.Errorf("schema validate failed: schema not found, path: %s", jsonPath) - } - - s, err := gojsonschema.NewSchema(gojsonschema.NewStringLoader(schemaDef)) - if err != nil { - log.Errorf("new schema failed: %s", err) - return nil, fmt.Errorf("new schema failed: %s", err) - } - return &APISIXJsonSchemaValidator{ - schema: s, - schemaDef: schemaDef, - }, nil -} - -func getPlugins(reqBody interface{}) (map[string]interface{}, string) { - switch bodyType := reqBody.(type) { - case *entity.Route: - log.Infof("type of reqBody: %#v", bodyType) - route := reqBody.(*entity.Route) - return route.Plugins, "schema" - case *entity.Service: - log.Infof("type of reqBody: %#v", bodyType) - service := reqBody.(*entity.Service) - return service.Plugins, "schema" - case *entity.Consumer: - log.Infof("type of reqBody: %#v", bodyType) - consumer := reqBody.(*entity.Consumer) - return consumer.Plugins, "consumer_schema" - } - return nil, "" -} - -func cHashKeySchemaCheck(upstream *entity.UpstreamDef) error { - if upstream.HashOn == "consumer" { - return nil - } - if upstream.HashOn != "vars" && - upstream.HashOn != "header" && - upstream.HashOn != "cookie" { - return fmt.Errorf("invalid hash_on type: %s", upstream.HashOn) - } - - var schemaDef string - if upstream.HashOn == "vars" { - schemaDef = conf.Schema.Get("main.upstream_hash_vars_schema").String() - if schemaDef == "" { - return fmt.Errorf("schema validate failed: schema not found, path: main.upstream_hash_vars_schema") - } - } - - if upstream.HashOn == "header" || upstream.HashOn == "cookie" { - schemaDef = conf.Schema.Get("main.upstream_hash_header_schema").String() - if schemaDef == "" { - return fmt.Errorf("schema validate failed: schema not found, path: main.upstream_hash_header_schema") - } - } - - s, err := gojsonschema.NewSchema(gojsonschema.NewStringLoader(schemaDef)) - if err != nil { - return fmt.Errorf("schema validate failed: %s", err) - } - - ret, err := s.Validate(gojsonschema.NewGoLoader(upstream.Key)) - if err != nil { - return fmt.Errorf("schema validate failed: %s", err) - } - - if !ret.Valid() { - errString := buffer.Buffer{} - for i, vErr := range ret.Errors() { - if i != 0 { - errString.AppendString("\n") - } - errString.AppendString(vErr.String()) - } - return fmt.Errorf("schema validate failed: %s", errString.String()) - } - - return nil -} - -func checkUpstream(upstream *entity.UpstreamDef) error { - if upstream == nil { - return nil - } - - if upstream.PassHost == "node" && upstream.Nodes != nil { - nodes, ok := entity.NodesFormat(upstream.Nodes).([]*entity.Node) - if !ok { - return fmt.Errorf("upstrams nodes not support value %v when `pass_host` is `node`", nodes) - } else if len(nodes) != 1 { - return fmt.Errorf("only support single node for `node` mode currentlywhen `pass_host` is `node`") - } - } - - if upstream.PassHost == "rewrite" && upstream.UpstreamHost == "" { - return fmt.Errorf("`upstream_host` can't be empty when `pass_host` is `rewrite`") - } - - if upstream.Type != "chash" { - return nil - } - - //to confirm - if upstream.HashOn == "" { - upstream.HashOn = "vars" - } - - if upstream.HashOn != "consumer" && upstream.Key == "" { - return fmt.Errorf("missing key") - } - - if err := cHashKeySchemaCheck(upstream); err != nil { - return err - } - - return nil -} - -func checkRemoteAddr(remoteAddrs []string) error { - for _, remoteAddr := range remoteAddrs { - if remoteAddr == "" { - return fmt.Errorf("schema validate failed: invalid field remote_addrs") - } - } - return nil -} - -func checkConf(reqBody interface{}) error { - switch bodyType := reqBody.(type) { - case *entity.Route: - route := reqBody.(*entity.Route) - log.Infof("type of reqBody: %#v", bodyType) - if err := checkUpstream(route.Upstream); err != nil { - return err - } - // todo: this is a temporary method, we'll drop it later - if err := checkRemoteAddr(route.RemoteAddrs); err != nil { - return err - } - case *entity.Service: - service := reqBody.(*entity.Service) - if err := checkUpstream(service.Upstream); err != nil { - return err - } - case *entity.Upstream: - upstream := reqBody.(*entity.Upstream) - if err := checkUpstream(&upstream.UpstreamDef); err != nil { - return err - } - } - return nil -} - -func (v *APISIXJsonSchemaValidator) Validate(obj interface{}) error { - ret, err := v.schema.Validate(gojsonschema.NewGoLoader(obj)) - if err != nil { - log.Errorf("schema validate failed: %s, s: %v, obj: %v", err, v.schema, obj) - return fmt.Errorf("schema validate failed: %s", err) - } - - if !ret.Valid() { - errString := buffer.Buffer{} - for i, vErr := range ret.Errors() { - if i != 0 { - errString.AppendString("\n") - } - errString.AppendString(vErr.String()) - } - log.Errorf("schema validate failed:s: %v, obj: %#v", v.schemaDef, obj) - return fmt.Errorf("schema validate failed: %s", errString.String()) - } - - //custom check - if err := checkConf(obj); err != nil { - return err - } - - plugins, schemaType := getPlugins(obj) - for pluginName, pluginConf := range plugins { - schemaValue := conf.Schema.Get("plugins." + pluginName + "." + schemaType).Value() - if schemaValue == nil && schemaType == "consumer_schema" { - schemaValue = conf.Schema.Get("plugins." + pluginName + ".schema").Value() - } - - if schemaValue == nil { - log.Errorf("schema validate failed: schema not found, %s, %s", "plugins."+pluginName, schemaType) - return fmt.Errorf("schema validate failed: schema not found, path: %s", "plugins."+pluginName) - } - schemaMap := schemaValue.(map[string]interface{}) - schemaByte, err := json.Marshal(schemaMap) - if err != nil { - log.Warnf("schema validate failed: schema json encode failed, path: %s, %w", "plugins."+pluginName, err) - return fmt.Errorf("schema validate failed: schema json encode failed, path: %s, %w", "plugins."+pluginName, err) - } - - s, err := gojsonschema.NewSchema(gojsonschema.NewBytesLoader(schemaByte)) - if err != nil { - log.Errorf("init schema validate failed: %s", err) - return fmt.Errorf("schema validate failed: %s", err) - } - - // check property disable, if is bool, remove from json schema checking - conf := pluginConf.(map[string]interface{}) - var exchange bool - disable, ok := conf["disable"] - if ok { - if fmt.Sprintf("%T", disable) == "bool" { - delete(conf, "disable") - exchange = true - } - } - - // check schema - ret, err := s.Validate(gojsonschema.NewGoLoader(conf)) - if err != nil { - log.Errorf("schema validate failed: %s", err) - return fmt.Errorf("schema validate failed: %s", err) - } - - // put the value back to the property disable - if exchange { - conf["disable"] = disable - } - - if !ret.Valid() { - errString := buffer.Buffer{} - for i, vErr := range ret.Errors() { - if i != 0 { - errString.AppendString("\n") - } - errString.AppendString(vErr.String()) - } - return fmt.Errorf("schema validate failed: %s", errString.String()) - } - } - - return nil -} - -type APISIXSchemaValidator struct { - schema *gojsonschema.Schema -} - -func NewAPISIXSchemaValidator(jsonPath string) (Validator, error) { - schemaDef := conf.Schema.Get(jsonPath).String() - if schemaDef == "" { - log.Warnf("schema validate failed: schema not found, path: %s", jsonPath) - return nil, fmt.Errorf("schema validate failed: schema not found, path: %s", jsonPath) - } - - s, err := gojsonschema.NewSchema(gojsonschema.NewStringLoader(schemaDef)) - if err != nil { - log.Warnf("new schema failed: %w", err) - return nil, fmt.Errorf("new schema failed: %w", err) - } - return &APISIXSchemaValidator{ - schema: s, - }, nil -} - -func (v *APISIXSchemaValidator) Validate(obj interface{}) error { - ret, err := v.schema.Validate(gojsonschema.NewBytesLoader(obj.([]byte))) - if err != nil { - log.Warnf("schema validate failed: %w", err) - return fmt.Errorf("schema validate failed: %w", err) - } - - if !ret.Valid() { - errString := buffer.Buffer{} - for i, vErr := range ret.Errors() { - if i != 0 { - errString.AppendString("\n") - } - errString.AppendString(vErr.String()) - } - return fmt.Errorf("schema validate failed: %s", errString.String()) - } - - return nil -} diff --git a/api/internal/core/store/validate_mock.go b/api/internal/core/store/validate_mock.go deleted file mode 100644 index ad6922cfbb..0000000000 --- a/api/internal/core/store/validate_mock.go +++ /dev/null @@ -1,35 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one or more - * contributor license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright ownership. - * The ASF licenses this file to You under the Apache License, Version 2.0 - * (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package store - -import mock "github.com/stretchr/testify/mock" - -// MockValidator is an autogenerated mock type for the Validator type -type MockValidator struct { - mock.Mock -} - -// Validate provides a mock function with given fields: obj -func (_m *MockValidator) Validate(obj interface{}) error { - ret := _m.Called(obj) - - if rf, ok := ret.Get(0).(func(interface{}) error); ok { - return rf(obj) - } - - return ret.Error(0) -} diff --git a/api/internal/core/store/validate_test.go b/api/internal/core/store/validate_test.go deleted file mode 100644 index fc7b43c27c..0000000000 --- a/api/internal/core/store/validate_test.go +++ /dev/null @@ -1,530 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one or more - * contributor license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright ownership. - * The ASF licenses this file to You under the Apache License, Version 2.0 - * (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package store - -import ( - "encoding/json" - "fmt" - "testing" - - "github.com/stretchr/testify/assert" - - "github.com/apisix/manager-api/internal/core/entity" -) - -type TestObj struct { - Name string `json:"name"` - Email string `json:"email"` - Age int `json:"age"` -} - -func TestJsonSchemaValidator_Validate(t *testing.T) { - tests := []struct { - givePath string - giveObj interface{} - wantNewErr error - wantValidateErr []error - }{ - { - givePath: "./test_case.json", - giveObj: TestObj{ - Name: "lessName", - Email: "too long name greater than 10", - Age: 12, - }, - wantValidateErr: []error{ - fmt.Errorf("name: String length must be greater than or equal to 10\nemail: String length must be less than or equal to 10"), - fmt.Errorf("email: String length must be less than or equal to 10\nname: String length must be greater than or equal to 10"), - }, - }, - } - - for _, tc := range tests { - v, err := NewJsonSchemaValidator(tc.givePath) - if err != nil { - assert.Equal(t, tc.wantNewErr, err) - continue - } - err = v.Validate(tc.giveObj) - ret := false - for _, wantErr := range tc.wantValidateErr { - if wantErr.Error() == err.Error() { - ret = true - } - } - assert.True(t, ret) - } -} - -func TestAPISIXJsonSchemaValidator_Validate(t *testing.T) { - validator, err := NewAPISIXJsonSchemaValidator("main.consumer") - assert.Nil(t, err) - - consumer := &entity.Consumer{} - reqBody := `{ - "username": "jack", - "plugins": { - "limit-count": { - "count": 2, - "time_window": 60, - "rejected_code": 503, - "key": "remote_addr", - "policy": "local" - } - }, - "desc": "test description" - }` - err = json.Unmarshal([]byte(reqBody), consumer) - assert.Nil(t, err) - err = validator.Validate(consumer) - assert.Nil(t, err) - - //check nil obj - err = validator.Validate(nil) - assert.NotNil(t, err) - assert.EqualError(t, err, "schema validate failed: (root): Invalid type. Expected: object, given: null") - - //plugin schema fail - consumer3 := &entity.Consumer{} - reqBody = `{ - "username": "jack", - "plugins": { - "limit-count": { - "time_window": 60, - "rejected_code": 503, - "key": "remote_addr", - "policy": "local" - } - }, - "desc": "test description" - }` - err = json.Unmarshal([]byte(reqBody), consumer3) - assert.Nil(t, err) - err = validator.Validate(consumer3) - assert.NotNil(t, err) - assert.EqualError(t, err, "schema validate failed: (root): count is required") -} - -func TestAPISIXJsonSchemaValidator_checkUpstream(t *testing.T) { - validator, err := NewAPISIXJsonSchemaValidator("main.route") - assert.Nil(t, err) - - // type:chash, hash_on: consumer, missing key, ok - route := &entity.Route{} - reqBody := `{ - "id": "1", - "name": "route1", - "methods": ["GET"], - "upstream": { - "nodes": { - "127.0.0.1:8080": 1 - }, - "type": "chash", - "hash_on":"consumer" - }, - "desc": "new route", - "uri": "/index.html" - }` - err = json.Unmarshal([]byte(reqBody), route) - assert.Nil(t, err) - err = validator.Validate(route) - assert.Nil(t, err) - - // type:chash, hash_on: default(vars), missing key - route2 := &entity.Route{} - reqBody = `{ - "id": "1", - "name": "route1", - "methods": ["GET"], - "upstream": { - "nodes": { - "127.0.0.1:8080": 1 - }, - "type": "chash" - }, - "desc": "new route", - "uri": "/index.html" - }` - err = json.Unmarshal([]byte(reqBody), route2) - assert.Nil(t, err) - err = validator.Validate(route2) - assert.NotNil(t, err) - assert.EqualError(t, err, "missing key") - - //type:chash, hash_on: header, missing key - route3 := &entity.Route{} - reqBody = `{ - "id": "1", - "name": "route1", - "methods": ["GET"], - "upstream": { - "nodes": { - "127.0.0.1:8080": 1 - }, - "type": "chash", - "hash_on":"header" - }, - "desc": "new route", - "uri": "/index.html" - }` - err = json.Unmarshal([]byte(reqBody), route3) - assert.Nil(t, err) - err = validator.Validate(route3) - assert.NotNil(t, err) - assert.EqualError(t, err, "missing key") - - //type:chash, hash_on: cookie, missing key - route4 := &entity.Route{} - reqBody = `{ - "id": "1", - "name": "route1", - "methods": ["GET"], - "upstream": { - "nodes": { - "127.0.0.1:8080": 1 - }, - "type": "chash", - "hash_on":"cookie" - }, - "desc": "new route", - "uri": "/index.html" - }` - err = json.Unmarshal([]byte(reqBody), route4) - assert.Nil(t, err) - err = validator.Validate(route4) - assert.NotNil(t, err) - assert.EqualError(t, err, "missing key") - - //type:chash, hash_on: vars, wrong key - route5 := &entity.Route{} - reqBody = `{ - "id": "1", - "name": "route1", - "methods": ["GET"], - "upstream": { - "nodes": { - "127.0.0.1:8080": 1 - }, - "type": "chash", - "hash_on":"vars", - "key": "not_support" - }, - "desc": "new route", - "uri": "/index.html" - }` - err = json.Unmarshal([]byte(reqBody), route5) - assert.Nil(t, err) - err = validator.Validate(route5) - assert.NotNil(t, err) - assert.EqualError(t, err, "schema validate failed: (root): Does not match pattern '^((uri|server_name|server_addr|request_uri|remote_port|remote_addr|query_string|host|hostname)|arg_[0-9a-zA-z_-]+)$'") -} - -func TestAPISIXJsonSchemaValidator_Plugin(t *testing.T) { - validator, err := NewAPISIXJsonSchemaValidator("main.route") - assert.Nil(t, err) - - // validate plugin's schema which has no `properties` or empty `properties` - route := &entity.Route{} - reqBody := `{ - "id": "1", - "name": "route1", - "uri": "/hello", - "plugins": { - "prometheus": { - "disable": false - }, - "key-auth": { - "disable": true - } - } - }` - err = json.Unmarshal([]byte(reqBody), route) - assert.Nil(t, err) - err = validator.Validate(route) - assert.Nil(t, err) - - // validate plugin's schema which use `oneOf` - reqBody = `{ - "id": "1", - "uri": "/hello", - "plugins": { - "ip-restriction": { - "blacklist": [ - "127.0.0.0/24" - ], - "disable": true - } - } - }` - err = json.Unmarshal([]byte(reqBody), route) - assert.Nil(t, err) - err = validator.Validate(route) - assert.Nil(t, err) - - // validate plugin's schema with invalid type for `disable` - reqBody = `{ - "id": "1", - "uri": "/hello", - "plugins": { - "ip-restriction": { - "blacklist": [ - "127.0.0.0/24" - ], - "_meta": { - "disable": 1 - } - } - } - }` - err = json.Unmarshal([]byte(reqBody), route) - assert.Nil(t, err) - err = validator.Validate(route) - assert.Equal(t, fmt.Errorf("schema validate failed: _meta.disable: Invalid type. Expected: boolean, given: integer"), err) -} - -func TestAPISIXJsonSchemaValidator_Route_checkRemoteAddr(t *testing.T) { - tests := []struct { - caseDesc string - giveContent string - wantNewErr error - wantValidateErr error - }{ - { - caseDesc: "correct remote_addr", - giveContent: `{ - "id": "1", - "name": "route1", - "uri": "/*", - "upstream": { - "nodes": [{ - "host": "127.0.0.1", - "port": 8080, - "weight": 1 - }], - "type": "roundrobin" - }, - "remote_addr": "127.0.0.1" - }`, - }, - { - caseDesc: "correct remote_addr (CIDR)", - giveContent: `{ - "id": "1", - "name": "route1", - "uri": "/*", - "upstream": { - "nodes": [{ - "host": "127.0.0.1", - "port": 8080, - "weight": 1 - }], - "type": "roundrobin" - }, - "remote_addr": "192.168.1.0/24" - }`, - }, - { - caseDesc: "invalid remote_addr", - giveContent: `{ - "id": "1", - "name": "route1", - "uri": "/*", - "upstream": { - "nodes": [{ - "host": "127.0.0.1", - "port": 8080, - "weight": 1 - }], - "type": "roundrobin" - }, - "remote_addr": "127.0.0." - }`, - wantValidateErr: fmt.Errorf("schema validate failed: remote_addr: Must validate at least one schema (anyOf)\nremote_addr: Does not match format 'ipv4'"), - }, - { - caseDesc: "correct remote_addrs", - giveContent: `{ - "id": "1", - "name": "route1", - "uri": "/*", - "upstream": { - "nodes": [{ - "host": "127.0.0.1", - "port": 8080, - "weight": 1 - }], - "type": "roundrobin" - }, - "remote_addrs": ["127.0.0.1", "192.0.0.0/8", "::1", "fe80::1/64"] - }`, - }, - { - caseDesc: "invalid remote_addrs", - giveContent: `{ - "id": "1", - "name": "route1", - "uri": "/*", - "upstream": { - "nodes": [{ - "host": "127.0.0.1", - "port": 8080, - "weight": 1 - }], - "type": "roundrobin" - }, - "remote_addrs": ["127.0.0.", "192.0.0.0/128", "::1"] - }`, - wantValidateErr: fmt.Errorf("schema validate failed: remote_addrs.0: Must validate at least one schema (anyOf)\nremote_addrs.0: Does not match format 'ipv4'\nremote_addrs.1: Must validate at least one schema (anyOf)\nremote_addrs.1: Does not match format 'ipv4'"), - }, - { - caseDesc: "invalid remote_addrs (an empty string item)", - giveContent: `{ - "id": "1", - "name": "route1", - "uri": "/*", - "upstream": { - "nodes": [{ - "host": "127.0.0.1", - "port": 8080, - "weight": 1 - }], - "type": "roundrobin" - }, - "remote_addrs": [""] - }`, - wantValidateErr: fmt.Errorf("schema validate failed: remote_addrs.0: Must validate at least one schema (anyOf)\nremote_addrs.0: Does not match format 'ipv4'"), - }, - } - - // todo: add a test case for "remote_addr": "" - - for _, tc := range tests { - validator, err := NewAPISIXJsonSchemaValidator("main.route") - if err != nil { - assert.Equal(t, tc.wantNewErr, err, tc.caseDesc) - continue - } - route := &entity.Route{} - err = json.Unmarshal([]byte(tc.giveContent), route) - assert.Nil(t, err, tc.caseDesc) - - err = validator.Validate(route) - if tc.wantValidateErr == nil { - assert.Equal(t, nil, err, tc.caseDesc) - continue - } - - assert.Equal(t, tc.wantValidateErr, err, tc.caseDesc) - } -} - -func TestAPISIXSchemaValidator_SystemConfig(t *testing.T) { - tests := []struct { - name string - givePath string - giveObj interface{} - wantNewErr bool - wantValidateErr bool - wantErrMessage string - }{ - { - name: "new json schema validator failed", - givePath: "main.xxx", - wantNewErr: true, - wantErrMessage: "schema validate failed: schema not found, path: main.xxx", - }, - { - name: "invalid configName (configName is empty)", - givePath: "main.system_config", - giveObj: &entity.SystemConfig{ - Payload: map[string]interface{}{"a": 1}, - }, - wantValidateErr: true, - wantErrMessage: "schema validate failed: config_name: String length must be greater than or equal to 1\nconfig_name: Does not match pattern '^[a-zA-Z0-9_]+$'", - }, - { - name: "invalid configName (configName do not match regex)", - givePath: "main.system_config", - giveObj: &entity.SystemConfig{ - ConfigName: "1@2", - Payload: map[string]interface{}{"a": 1}, - }, - wantValidateErr: true, - wantErrMessage: "schema validate failed: config_name: Does not match pattern '^[a-zA-Z0-9_]+$'", - }, - { - name: "invalid payload", - givePath: "main.system_config", - giveObj: &entity.SystemConfig{ - ConfigName: "cc", - }, - wantValidateErr: true, - wantErrMessage: "schema validate failed: (root): payload is required", - }, - { - name: "validate should succeed", - givePath: "main.system_config", - giveObj: &entity.SystemConfig{ - ConfigName: "aaa", - Payload: map[string]interface{}{"a": 1}, - }, - }, - } - - for _, tc := range tests { - validator, err := NewAPISIXSchemaValidator(tc.givePath) - if tc.wantNewErr { - assert.Error(t, err) - assert.Equal(t, tc.wantErrMessage, err.Error()) - continue - } - - assert.NoError(t, err) - assert.NotNil(t, validator) - - req, err := json.Marshal(tc.giveObj) - assert.NoError(t, err) - err = validator.Validate(req) - if tc.wantValidateErr { - assert.Error(t, err) - assert.Equal(t, tc.wantErrMessage, err.Error()) - continue - } - assert.NoError(t, err) - } -} - -func TestAPISIXSchemaValidator_Validate(t *testing.T) { - validator, err := NewAPISIXSchemaValidator("main.consumer") - assert.Nil(t, err) - - // normal config, should pass - reqBody := `{ - "username": "jack", - "plugins": { - "limit-count": { - "count": 2, - "time_window": 60, - "rejected_code": 503, - "key": "remote_addr" - } - }, - "desc": "test description" - }` - err = validator.Validate([]byte(reqBody)) - assert.Nil(t, err) -} diff --git a/api/internal/filter/authentication.go b/api/internal/filter/authentication.go deleted file mode 100644 index fed117835f..0000000000 --- a/api/internal/filter/authentication.go +++ /dev/null @@ -1,91 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one or more - * contributor license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright ownership. - * The ASF licenses this file to You under the Apache License, Version 2.0 - * (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package filter - -import ( - "net/http" - "strings" - - "github.com/gin-gonic/gin" - "github.com/golang-jwt/jwt" - - "github.com/apisix/manager-api/internal/conf" - "github.com/apisix/manager-api/internal/log" -) - -func Authentication() gin.HandlerFunc { - return func(c *gin.Context) { - if c.Request.URL.Path == "/apisix/admin/user/login" || - c.Request.URL.Path == "/apisix/admin/tool/version" || - !strings.HasPrefix(c.Request.URL.Path, "/apisix") { - c.Next() - return - } - - cookie, _ := conf.CookieStore.Get(c.Request, "oidc") - errResp := gin.H{ - "code": 010013, - "message": "request unauthorized", - } - - if cookie.IsNew { - tokenStr := c.GetHeader("Authorization") - // verify token - token, err := jwt.ParseWithClaims(tokenStr, &jwt.StandardClaims{}, func(token *jwt.Token) (interface{}, error) { - return []byte(conf.AuthConf.Secret), nil - }) - - if err != nil || token == nil || !token.Valid { - log.Warnf("token validate failed: %s", err) - c.AbortWithStatusJSON(http.StatusUnauthorized, errResp) - return - } - - claims, ok := token.Claims.(*jwt.StandardClaims) - if !ok { - log.Warnf("token validate failed: %s, %v", err, token.Valid) - c.AbortWithStatusJSON(http.StatusUnauthorized, errResp) - return - } - - if err := token.Claims.Valid(); err != nil { - log.Warnf("token claims validate failed: %s", err) - c.AbortWithStatusJSON(http.StatusUnauthorized, errResp) - return - } - - if claims.Subject == "" { - log.Warn("token claims subject empty") - c.AbortWithStatusJSON(http.StatusUnauthorized, errResp) - return - } - - if _, ok := conf.UserList[claims.Subject]; !ok { - log.Warnf("user not exists by token claims subject %s", claims.Subject) - c.AbortWithStatusJSON(http.StatusUnauthorized, errResp) - return - } - } else { - if cookie.Values["oidc_id"] != conf.OidcId { - c.AbortWithStatusJSON(http.StatusUnauthorized, errResp) - return - } - } - - c.Next() - } -} diff --git a/api/internal/filter/authentication_test.go b/api/internal/filter/authentication_test.go deleted file mode 100644 index ab3da9d743..0000000000 --- a/api/internal/filter/authentication_test.go +++ /dev/null @@ -1,74 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one or more - * contributor license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright ownership. - * The ASF licenses this file to You under the Apache License, Version 2.0 - * (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package filter - -import ( - "net/http" - "testing" - "time" - - "github.com/gin-gonic/gin" - "github.com/golang-jwt/jwt" - "github.com/stretchr/testify/assert" - - "github.com/apisix/manager-api/internal/conf" -) - -func genToken(username string, issueAt, expireAt int64) string { - claims := jwt.StandardClaims{ - Subject: username, - IssuedAt: issueAt, - ExpiresAt: expireAt, - } - token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims) - signedToken, _ := token.SignedString([]byte(conf.AuthConf.Secret)) - - return signedToken -} - -func TestAuthenticationMiddleware_Handle(t *testing.T) { - r := gin.New() - r.Use(Authentication()) - r.GET("/*path", func(c *gin.Context) { - }) - - w := performRequest(r, "GET", "/apisix/admin/user/login", nil) - assert.Equal(t, http.StatusOK, w.Code) - - w = performRequest(r, "GET", "/apisix/admin/routes", nil) - assert.Equal(t, http.StatusUnauthorized, w.Code) - - // test with token expire - expireToken := genToken("admin", time.Now().Unix(), time.Now().Unix()-60*3600) - w = performRequest(r, "GET", "/apisix/admin/routes", map[string]string{"Authorization": expireToken}) - assert.Equal(t, http.StatusUnauthorized, w.Code) - - // test with empty subject - emptySubjectToken := genToken("", time.Now().Unix(), time.Now().Unix()+60*3600) - w = performRequest(r, "GET", "/apisix/admin/routes", map[string]string{"Authorization": emptySubjectToken}) - assert.Equal(t, http.StatusUnauthorized, w.Code) - - // test token with nonexistent username - nonexistentUserToken := genToken("user1", time.Now().Unix(), time.Now().Unix()+60*3600) - w = performRequest(r, "GET", "/apisix/admin/routes", map[string]string{"Authorization": nonexistentUserToken}) - assert.Equal(t, http.StatusUnauthorized, w.Code) - - // test auth success - validToken := genToken("admin", time.Now().Unix(), time.Now().Unix()+60*3600) - w = performRequest(r, "GET", "/apisix/admin/routes", map[string]string{"Authorization": validToken}) - assert.Equal(t, http.StatusOK, w.Code) -} diff --git a/api/internal/filter/cors.go b/api/internal/filter/cors.go deleted file mode 100644 index 28ca331625..0000000000 --- a/api/internal/filter/cors.go +++ /dev/null @@ -1,56 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one or more - * contributor license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright ownership. - * The ASF licenses this file to You under the Apache License, Version 2.0 - * (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package filter - -import ( - "github.com/gin-gonic/gin" - - "github.com/apisix/manager-api/internal/conf" -) - -func CORS() gin.HandlerFunc { - return func(c *gin.Context) { - if conf.SecurityConf.AllowOrigin != "" { - c.Writer.Header().Set("Access-Control-Allow-Origin", conf.SecurityConf.AllowOrigin) - } - - if conf.SecurityConf.AllowHeaders != "" { - c.Writer.Header().Set("Access-Control-Allow-Headers", conf.SecurityConf.AllowHeaders) - } - - if conf.SecurityConf.AllowMethods != "" { - c.Writer.Header().Set("Access-Control-Allow-Methods", conf.SecurityConf.AllowMethods) - } - - if conf.SecurityConf.AllowCredentials != "" { - c.Writer.Header().Set("Access-Control-Allow-Credentials", conf.SecurityConf.AllowCredentials) - } - - if conf.SecurityConf.XFrameOptions != "" { - c.Writer.Header().Set("X-Frame-Options", conf.SecurityConf.XFrameOptions) - } - - if conf.SecurityConf.ContentSecurityPolicy != "" { - c.Writer.Header().Set("Content-Security-Policy", conf.SecurityConf.ContentSecurityPolicy) - } - if c.Request.Method == "OPTIONS" { - c.AbortWithStatus(204) - return - } - c.Next() - } -} diff --git a/api/internal/filter/invalid_request.go b/api/internal/filter/invalid_request.go deleted file mode 100644 index 442fccd4a4..0000000000 --- a/api/internal/filter/invalid_request.go +++ /dev/null @@ -1,38 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one or more - * contributor license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright ownership. - * The ASF licenses this file to You under the Apache License, Version 2.0 - * (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package filter - -import ( - "net/url" - "strings" - - "github.com/gin-gonic/gin" -) - -// InvalidRequest provides a filtering mechanism for illegitimate requests -func InvalidRequest() gin.HandlerFunc { - return func(c *gin.Context) { - if !checkURL(c.Request.URL) { - c.AbortWithStatus(403) - } - c.Next() - } -} - -func checkURL(url *url.URL) bool { - return !strings.Contains(url.Path, "..") -} diff --git a/api/internal/filter/invalid_request_test.go b/api/internal/filter/invalid_request_test.go deleted file mode 100644 index 986a79102a..0000000000 --- a/api/internal/filter/invalid_request_test.go +++ /dev/null @@ -1,30 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one or more - * contributor license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright ownership. - * The ASF licenses this file to You under the Apache License, Version 2.0 - * (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package filter - -import ( - "net/url" - "testing" - - "github.com/stretchr/testify/assert" -) - -func TestURLCheck_double_dot(t *testing.T) { - assert.Equal(t, false, checkURL(&url.URL{Path: "../../../etc/hosts"})) - assert.Equal(t, false, checkURL(&url.URL{Path: "/../../../etc/hosts"})) - assert.Equal(t, true, checkURL(&url.URL{Path: "/etc/hosts"})) -} diff --git a/api/internal/filter/ip_filter.go b/api/internal/filter/ip_filter.go deleted file mode 100644 index 2d07dea552..0000000000 --- a/api/internal/filter/ip_filter.go +++ /dev/null @@ -1,109 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one or more - * contributor license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright ownership. - * The ASF licenses this file to You under the Apache License, Version 2.0 - * (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package filter - -import ( - "net" - "net/http" - "strings" - - "github.com/gin-gonic/gin" - - "github.com/apisix/manager-api/internal/conf" - "github.com/apisix/manager-api/internal/log" - "github.com/apisix/manager-api/internal/utils/consts" -) - -type subnet struct { - ipStr string - ipNet *net.IPNet - allowed bool -} - -func generateIPSet(ipList []string) (map[string]bool, []*subnet) { - var ips = map[string]bool{} - var subnets []*subnet - for _, ipStr := range ipList { - if ip, net, err := net.ParseCIDR(ipStr); err == nil { - if n, total := net.Mask.Size(); n == total { - ips[ip.String()] = true - continue - } - - subnets = append(subnets, &subnet{ - ipStr: ipStr, - ipNet: net, - allowed: true, - }) - continue - } - if ip := net.ParseIP(ipStr); ip != nil { - ips[ip.String()] = true - } - } - - return ips, subnets -} - -func checkIP(ipStr string, ips map[string]bool, subnets []*subnet) bool { - allowed, ok := ips[ipStr] - if ok { - return allowed - } - - ip := net.ParseIP(ipStr) - if ip == nil { - return false - } - - for _, subnet := range subnets { - if subnet.ipNet.Contains(ip) { - return subnet.allowed - } - } - - return false -} - -func IPFilter() gin.HandlerFunc { - ips, subnets := generateIPSet(conf.AllowList) - return func(c *gin.Context) { - var ipStr string - if ip, _, err := net.SplitHostPort(strings.TrimSpace(c.Request.RemoteAddr)); err == nil { - ipStr = ip - } - - if len(conf.AllowList) < 1 { - c.Next() - return - } - - if ipStr == "" { - log.Warn("forbidden by empty IP") - c.AbortWithStatusJSON(http.StatusForbidden, consts.ErrIPNotAllow) - return - } - - res := checkIP(ipStr, ips, subnets) - if !res { - log.Warnf("forbidden by IP: %s, allowed list: %v", ipStr, conf.AllowList) - c.AbortWithStatusJSON(http.StatusForbidden, consts.ErrIPNotAllow) - } - - c.Next() - } -} diff --git a/api/internal/filter/ip_filter_test.go b/api/internal/filter/ip_filter_test.go deleted file mode 100644 index e0c975f7e2..0000000000 --- a/api/internal/filter/ip_filter_test.go +++ /dev/null @@ -1,77 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one or more - * contributor license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright ownership. - * The ASF licenses this file to You under the Apache License, Version 2.0 - * (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package filter - -import ( - "net/http/httptest" - "testing" - - "github.com/gin-gonic/gin" - "github.com/stretchr/testify/assert" - - "github.com/apisix/manager-api/internal/conf" -) - -func TestIPFilter_Handle(t *testing.T) { - // empty allowed ip list --> should normal - conf.AllowList = []string{} - r := gin.New() - r.Use(IPFilter()) - - r.GET("/", func(c *gin.Context) { - }) - - w := performRequest(r, "GET", "/", nil) - assert.Equal(t, 200, w.Code) - - // should forbidden - conf.AllowList = []string{"10.0.0.0/8", "10.0.0.1"} - r = gin.New() - r.Use(IPFilter()) - r.GET("/fbd", func(c *gin.Context) { - }) - - w = performRequest(r, "GET", "/fbd", nil) - assert.Equal(t, 403, w.Code) - - // should allowed - conf.AllowList = []string{"10.0.0.0/8", "0.0.0.0/0"} - r = gin.New() - r.Use(IPFilter()) - r.GET("/test", func(c *gin.Context) { - }) - w = performRequest(r, "GET", "/test", nil) - assert.Equal(t, 200, w.Code) - - // should forbidden - conf.AllowList = []string{"127.0.0.1"} - r = gin.New() - r.Use(IPFilter()) - r.GET("/test", func(c *gin.Context) {}) - - req := httptest.NewRequest("GET", "/test", nil) - req.Header.Set("X-Forwarded-For", "127.0.0.1") - w = httptest.NewRecorder() - r.ServeHTTP(w, req) - assert.Equal(t, 403, w.Code) - - req = httptest.NewRequest("GET", "/test", nil) - req.Header.Set("X-Real-Ip", "127.0.0.1") - w = httptest.NewRecorder() - r.ServeHTTP(w, req) - assert.Equal(t, 403, w.Code) -} diff --git a/api/internal/filter/logging.go b/api/internal/filter/logging.go deleted file mode 100644 index 0a01f6a42f..0000000000 --- a/api/internal/filter/logging.go +++ /dev/null @@ -1,68 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one or more - * contributor license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright ownership. - * The ASF licenses this file to You under the Apache License, Version 2.0 - * (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package filter - -import ( - "bytes" - "time" - - "github.com/gin-gonic/gin" - "go.uber.org/zap" -) - -func RequestLogHandler(logger *zap.SugaredLogger) gin.HandlerFunc { - return func(c *gin.Context) { - start, host, remoteIP, path, method := time.Now(), c.Request.Host, c.ClientIP(), c.Request.URL.Path, c.Request.Method - query := c.Request.URL.RawQuery - requestId := c.Writer.Header().Get("X-Request-Id") - - blw := &bodyLogWriter{body: bytes.NewBuffer(nil), ResponseWriter: c.Writer} - c.Writer = blw - c.Next() - latency := time.Since(start) / 1000000 - statusCode := c.Writer.Status() - //respBody := blw.body.String() - - var errs []string - for _, err := range c.Errors { - errs = append(errs, err.Error()) - } - - logger.Desugar().Info(path, - //zap.String("path", path), - zap.Int("status", statusCode), - zap.String("host", host), - zap.String("query", query), - zap.String("requestId", requestId), - zap.Duration("latency", latency), - zap.String("remoteIP", remoteIP), - zap.String("method", method), - //zap.String("respBody", respBody), - zap.Strings("errs", errs), - ) - } -} - -type bodyLogWriter struct { - gin.ResponseWriter - body *bytes.Buffer -} - -func (w bodyLogWriter) Write(b []byte) (int, error) { - w.body.Write(b) - return w.ResponseWriter.Write(b) -} diff --git a/api/internal/filter/logging_test.go b/api/internal/filter/logging_test.go deleted file mode 100644 index 5a02bafd7f..0000000000 --- a/api/internal/filter/logging_test.go +++ /dev/null @@ -1,49 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one or more - * contributor license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright ownership. - * The ASF licenses this file to You under the Apache License, Version 2.0 - * (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package filter - -import ( - "net/http" - "net/http/httptest" - "testing" - - "github.com/gin-gonic/gin" - "github.com/stretchr/testify/assert" - - "github.com/apisix/manager-api/internal/log" -) - -func performRequest(r http.Handler, method, path string, headers map[string]string) *httptest.ResponseRecorder { - req := httptest.NewRequest(method, path, nil) - for key, val := range headers { - req.Header.Add(key, val) - } - w := httptest.NewRecorder() - r.ServeHTTP(w, req) - return w -} - -func TestRequestLogHandler(t *testing.T) { - r := gin.New() - logger := log.GetLogger(log.AccessLog) - r.Use(RequestLogHandler(logger)) - r.GET("/", func(c *gin.Context) { - }) - - w := performRequest(r, "GET", "/", nil) - assert.Equal(t, 200, w.Code) -} diff --git a/api/internal/filter/oidc.go b/api/internal/filter/oidc.go deleted file mode 100644 index 29a7336ec9..0000000000 --- a/api/internal/filter/oidc.go +++ /dev/null @@ -1,100 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one or more - * contributor license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright ownership. - * The ASF licenses this file to You under the Apache License, Version 2.0 - * (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package filter - -import ( - "net/http" - - "github.com/coreos/go-oidc/v3/oidc" - "github.com/gin-gonic/gin" - "golang.org/x/oauth2" - - "github.com/apisix/manager-api/internal/conf" - "github.com/apisix/manager-api/internal/log" -) - -type Token struct { - AccessToken string -} - -func (token *Token) Token() (*oauth2.Token, error) { - oauth2Token := &oauth2.Token{AccessToken: token.AccessToken} - return oauth2Token, nil -} - -func Oidc() gin.HandlerFunc { - return func(c *gin.Context) { - if c.Request.URL.Path == "/apisix/admin/oidc/login" { - url := conf.OidcConfig.AuthCodeURL(conf.State) - c.Redirect(302, url) - c.Abort() - return - } - - if c.Request.URL.Path == "/apisix/admin/oidc/callback" { - state := c.Query("state") - if state != conf.State { - log.Warn("the state does not match") - c.AbortWithStatus(http.StatusForbidden) - return - } - - // in exchange for token - oauth2Token, err := conf.OidcConfig.Exchange(c, c.Query("code")) - if err != nil { - log.Warnf("exchange code for token failed: %s", err) - c.AbortWithStatus(http.StatusForbidden) - return - } - - // in exchange for user's information - token := &Token{oauth2Token.AccessToken} - providerConfig := oidc.ProviderConfig{UserInfoURL: conf.OidcUserInfoURL} - provider := providerConfig.NewProvider(c) - userInfo, err := provider.UserInfo(c, token) - if err != nil { - log.Warnf("exchange access_token for user's information failed: %s", err) - c.AbortWithStatus(http.StatusForbidden) - return - } - - // set the cookie - conf.CookieStore.MaxAge(conf.OidcExpireTime) - cookie, _ := conf.CookieStore.Get(c.Request, "oidc") - cookie.Values["oidc_id"] = userInfo.Subject - conf.OidcId = userInfo.Subject - cookie.Save(c.Request, c.Writer) - c.AbortWithStatus(http.StatusOK) - return - } - - if c.Request.URL.Path == "/apisix/admin/oidc/logout" { - cookie, _ := conf.CookieStore.Get(c.Request, "oidc") - if cookie.IsNew { - c.AbortWithStatus(http.StatusForbidden) - return - } - - cookie.Options.MaxAge = -1 - cookie.Save(c.Request, c.Writer) - c.AbortWithStatus(http.StatusOK) - return - } - - c.Next() - } -} diff --git a/api/internal/filter/recover.go b/api/internal/filter/recover.go deleted file mode 100644 index a8da66c4a7..0000000000 --- a/api/internal/filter/recover.go +++ /dev/null @@ -1,121 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one or more - * contributor license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright ownership. - * The ASF licenses this file to You under the Apache License, Version 2.0 - * (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package filter - -import ( - "bytes" - "fmt" - "io/ioutil" - "net/http" - "runtime" - "time" - - "github.com/gin-gonic/gin" - - "github.com/apisix/manager-api/internal/log" -) - -var ( - dunno = []byte("???") - centerDot = []byte("ยท") - dot = []byte(".") - slash = []byte("/") -) - -func RecoverHandler() gin.HandlerFunc { - return func(c *gin.Context) { - defer func() { - if err := recover(); err != nil { - fmt.Println("err๏ผ›", err) - //uuid := c.Writer.Header().Get("X-Request-Id") - stack := stack(3) - fmt.Printf("[Recovery] %s panic recovered:\n\n%s\n%s", timeFormat(time.Now()), err, stack) - - //log.With(zap.String("uuid", uuid)) - log.Errorf("[Recovery] %s panic recovered:\n\n%s\n%s", timeFormat(time.Now()), err, stack) - c.AbortWithStatus(http.StatusInternalServerError) - } - }() - c.Next() - } -} - -func WrapGo(f func(...interface{}), args ...interface{}) { - defer func() { - if err := recover(); err != nil { - stack := stack(3) - log.Errorf("[Recovery] %s panic recovered:\n\n%s\n%s", timeFormat(time.Now()), err, stack) - } - }() - f(args...) -} - -func stack(skip int) []byte { - buf := new(bytes.Buffer) // the returned data - // loaded file. - var lines [][]byte - var lastFile string - for i := skip; ; i++ { - pc, file, line, ok := runtime.Caller(i) - if !ok { - break - } - // Print this much at least. If we can't find the source, it won't show. - fmt.Fprintf(buf, "%s:%d (0x%x)\n", file, line, pc) - if file != lastFile { - data, err := ioutil.ReadFile(file) - if err != nil { - continue - } - lines = bytes.Split(data, []byte{'\n'}) - lastFile = file - } - fmt.Fprintf(buf, "\t%s: %s\n", function(pc), source(lines, line)) - } - return buf.Bytes() -} - -// source returns a space-trimmed slice of the n'th line. -func source(lines [][]byte, n int) []byte { - n-- // in stack trace, lines are 1-indexed but our array is 0-indexed - if n < 0 || n >= len(lines) { - return dunno - } - return bytes.TrimSpace(lines[n]) -} - -// function returns, if possible, the name of the function containing the PC. -func function(pc uintptr) []byte { - fn := runtime.FuncForPC(pc) - if fn == nil { - return dunno - } - name := []byte(fn.Name()) - if lastslash := bytes.LastIndex(name, slash); lastslash >= 0 { - name = name[lastslash+1:] - } - if period := bytes.Index(name, dot); period >= 0 { - name = name[period+1:] - } - name = bytes.Replace(name, centerDot, dot, -1) - return name -} - -func timeFormat(t time.Time) string { - var timeString = t.Format("2006/01/02 - 15:04:05") - return timeString -} diff --git a/api/internal/filter/request_id.go b/api/internal/filter/request_id.go deleted file mode 100644 index aced90c730..0000000000 --- a/api/internal/filter/request_id.go +++ /dev/null @@ -1,43 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one or more - * contributor license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright ownership. - * The ASF licenses this file to You under the Apache License, Version 2.0 - * (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package filter - -import ( - "github.com/gin-gonic/gin" - uuid "github.com/satori/go.uuid" -) - -func RequestId() gin.HandlerFunc { - return func(c *gin.Context) { - // Check for incoming header, use it if exists - requestId := c.Request.Header.Get("X-Request-Id") - - // Create request id with UUID4 - if requestId == "" { - u4 := uuid.NewV4() - requestId = u4.String() - } - - // Expose it for use in the application - c.Set("X-Request-Id", requestId) - c.Request.Header.Set("X-Request-Id", requestId) - - // Set X-Request-Id header - c.Writer.Header().Set("X-Request-Id", requestId) - c.Next() - } -} diff --git a/api/internal/filter/schema.go b/api/internal/filter/schema.go deleted file mode 100644 index 4c37f26d95..0000000000 --- a/api/internal/filter/schema.go +++ /dev/null @@ -1,242 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one or more - * contributor license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright ownership. - * The ASF licenses this file to You under the Apache License, Version 2.0 - * (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package filter - -import ( - "bytes" - "crypto/tls" - "crypto/x509" - "encoding/json" - "encoding/pem" - "errors" - "fmt" - "io/ioutil" - "net/http" - "strings" - - "github.com/gin-gonic/gin" - - "github.com/apisix/manager-api/internal/core/entity" - "github.com/apisix/manager-api/internal/core/store" - "github.com/apisix/manager-api/internal/log" - "github.com/apisix/manager-api/internal/utils/consts" -) - -var resources = map[string]string{ - "routes": "route", - "upstreams": "upstream", - "services": "service", - "consumers": "consumer", - "ssl": "ssl", - "global_rules": "global_rule", - "proto": "proto", -} - -const ( - StatusDisable entity.Status = iota - StatusEnable -) - -func parseCert(crt, key string) ([]string, error) { - if crt == "" || key == "" { - return nil, errors.New("empty certificate or private key") - } - - certDERBlock, _ := pem.Decode([]byte(crt)) - if certDERBlock == nil { - return nil, errors.New("Certificate resolution failed") - } - // match - _, err := tls.X509KeyPair([]byte(crt), []byte(key)) - if err != nil { - return nil, errors.New("key and cert don't match") - } - - x509Cert, err := x509.ParseCertificate(certDERBlock.Bytes) - - if err != nil { - return nil, errors.New("Certificate resolution failed") - } - - //domain - var snis []string - if x509Cert.DNSNames != nil && len(x509Cert.DNSNames) > 0 { - snis = x509Cert.DNSNames - } else if x509Cert.IPAddresses != nil && len(x509Cert.IPAddresses) > 0 { - for _, ip := range x509Cert.IPAddresses { - snis = append(snis, ip.String()) - } - } else { - if x509Cert.Subject.Names != nil && len(x509Cert.Subject.Names) > 0 { - var attributeTypeNames = map[string]string{ - "2.5.4.6": "C", - "2.5.4.10": "O", - "2.5.4.11": "OU", - "2.5.4.3": "CN", - "2.5.4.5": "SERIALNUMBER", - "2.5.4.7": "L", - "2.5.4.8": "ST", - "2.5.4.9": "STREET", - "2.5.4.17": "POSTALCODE", - } - for _, tv := range x509Cert.Subject.Names { - oidString := tv.Type.String() - typeName, ok := attributeTypeNames[oidString] - if ok && typeName == "CN" { - valueString := fmt.Sprint(tv.Value) - snis = append(snis, valueString) - } - } - } - } - - return snis, nil -} - -func handleSpecialField(resource string, reqBody []byte) ([]byte, error) { - var bodyMap map[string]interface{} - err := json.Unmarshal(reqBody, &bodyMap) - if err != nil { - return reqBody, fmt.Errorf("read request body failed: %s", err) - } - if _, ok := bodyMap["create_time"]; ok { - return reqBody, errors.New("we don't accept create_time from client") - } - if _, ok := bodyMap["update_time"]; ok { - return reqBody, errors.New("we don't accept update_time from client") - } - - // remove script, because it's a map, and need to be parsed into lua code - if resource == "routes" { - var route map[string]interface{} - err := json.Unmarshal(reqBody, &route) - if err != nil { - return nil, fmt.Errorf("read request body failed: %s", err) - } - if _, ok := route["script"]; ok { - delete(route, "script") - reqBody, err = json.Marshal(route) - if err != nil { - return nil, fmt.Errorf("read request body failed: %s", err) - } - } - } - - // SSL does not need to pass sni, we need to parse the SSL to get sni - if resource == "ssl" { - var ssl map[string]interface{} - err := json.Unmarshal(reqBody, &ssl) - if err != nil { - return nil, fmt.Errorf("read request body failed: %s", err) - } - ssl["snis"], err = parseCert(ssl["cert"].(string), ssl["key"].(string)) - if err != nil { - return nil, fmt.Errorf("SSL parse failed: %s", err) - } - reqBody, err = json.Marshal(ssl) - if err != nil { - return nil, fmt.Errorf("read request body failed: %s", err) - } - } - - return reqBody, nil -} - -func handleDefaultValue(resource string, reqBody []byte) ([]byte, error) { - // go jsonschema lib doesn't support setting default values, so we need to set for some fields necessary - if resource == "routes" { - var route map[string]interface{} - err := json.Unmarshal(reqBody, &route) - if err != nil { - return reqBody, fmt.Errorf("read request body failed: %s", err) - } - if _, ok := route["status"]; !ok { - route["status"] = StatusEnable - reqBody, err = json.Marshal(route) - if err != nil { - return nil, fmt.Errorf("read request body failed: %s", err) - } - } - } - return reqBody, nil -} - -func SchemaCheck() gin.HandlerFunc { - return func(c *gin.Context) { - pathPrefix := "/apisix/admin/" - resource := strings.TrimPrefix(c.Request.URL.Path, pathPrefix) - idx := strings.LastIndex(resource, "/") - if idx > 1 { - resource = resource[:idx] - } - method := strings.ToUpper(c.Request.Method) - - if method != "PUT" && method != "POST" { - c.Next() - return - } - schemaKey, ok := resources[resource] - if !ok { - c.Next() - return - } - - reqBody, err := c.GetRawData() - if err != nil { - log.Errorf("read request body failed: %s", err) - c.AbortWithStatusJSON(http.StatusBadRequest, consts.ErrInvalidRequest) - return - } - - // set default value - reqBody, err = handleDefaultValue(resource, reqBody) - if err != nil { - errMsg := err.Error() - c.AbortWithStatusJSON(http.StatusBadRequest, consts.InvalidParam(errMsg)) - log.Error(errMsg) - return - } - - // other filter need it - c.Request.Body = ioutil.NopCloser(bytes.NewBuffer(reqBody)) - - validator, err := store.NewAPISIXSchemaValidator("main." + schemaKey) - if err != nil { - errMsg := err.Error() - c.AbortWithStatusJSON(http.StatusBadRequest, consts.InvalidParam(errMsg)) - log.Error(errMsg) - return - } - - reqBody, err = handleSpecialField(resource, reqBody) - if err != nil { - errMsg := err.Error() - c.AbortWithStatusJSON(http.StatusBadRequest, consts.InvalidParam(errMsg)) - log.Error(errMsg) - return - } - - if err := validator.Validate(reqBody); err != nil { - errMsg := err.Error() - c.AbortWithStatusJSON(http.StatusBadRequest, consts.InvalidParam(errMsg)) - log.Warn(errMsg) - return - } - - c.Next() - } -} diff --git a/api/internal/handler/authentication/authentication.go b/api/internal/handler/authentication/authentication.go deleted file mode 100644 index 28efa5cba3..0000000000 --- a/api/internal/handler/authentication/authentication.go +++ /dev/null @@ -1,108 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one or more - * contributor license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright ownership. - * The ASF licenses this file to You under the Apache License, Version 2.0 - * (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package authentication - -import ( - "reflect" - "time" - - "github.com/gin-gonic/gin" - "github.com/golang-jwt/jwt" - "github.com/shiningrush/droplet" - "github.com/shiningrush/droplet/wrapper" - wgin "github.com/shiningrush/droplet/wrapper/gin" - - "github.com/apisix/manager-api/internal/conf" - "github.com/apisix/manager-api/internal/handler" - "github.com/apisix/manager-api/internal/utils/consts" -) - -type Handler struct { -} - -func NewHandler() (handler.RouteRegister, error) { - return &Handler{}, nil -} - -func (h *Handler) ApplyRoute(r *gin.Engine) { - r.POST("/apisix/admin/user/login", wgin.Wraps(h.userLogin, - wrapper.InputType(reflect.TypeOf(LoginInput{})))) -} - -type UserSession struct { - Token string `json:"token"` -} - -// swagger:model LoginInput -type LoginInput struct { - // user name - Username string `json:"username" validate:"required"` - // password - Password string `json:"password" validate:"required"` -} - -// swagger:operation POST /apisix/admin/user/login userLogin -// -// user login. -// -// --- -// produces: -// - application/json -// parameters: -// - name: username -// in: body -// description: user name -// required: true -// type: string -// - name: password -// in: body -// description: password -// required: true -// type: string -// responses: -// '0': -// description: login success -// schema: -// "$ref": "#/definitions/ApiError" -// default: -// description: unexpected error -// schema: -// "$ref": "#/definitions/ApiError" -func (h *Handler) userLogin(c droplet.Context) (interface{}, error) { - input := c.Input().(*LoginInput) - username := input.Username - password := input.Password - - user := conf.UserList[username] - if username != user.Username || password != user.Password { - return nil, consts.ErrUsernamePassword - } - - // create JWT for session - claims := jwt.StandardClaims{ - Subject: username, - IssuedAt: time.Now().Unix(), - ExpiresAt: time.Now().Add(time.Second * time.Duration(conf.AuthConf.ExpireTime)).Unix(), - } - token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims) - signedToken, _ := token.SignedString([]byte(conf.AuthConf.Secret)) - - // output token - return &UserSession{ - Token: signedToken, - }, nil -} diff --git a/api/internal/handler/authentication/authentication_test.go b/api/internal/handler/authentication/authentication_test.go deleted file mode 100644 index e1a7e500e0..0000000000 --- a/api/internal/handler/authentication/authentication_test.go +++ /dev/null @@ -1,70 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one or more - * contributor license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright ownership. - * The ASF licenses this file to You under the Apache License, Version 2.0 - * (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package authentication - -import ( - "encoding/json" - "testing" - - "github.com/shiningrush/droplet" - "github.com/stretchr/testify/assert" -) - -func TestAuthentication(t *testing.T) { - // init - handler := &Handler{} - assert.NotNil(t, handler) - - //login - input := &LoginInput{} - ctx := droplet.NewContext() - reqBody := `{ - "username": "admin", - "password": "admin" - }` - err := json.Unmarshal([]byte(reqBody), input) - assert.Nil(t, err) - ctx.SetInput(input) - _, err = handler.userLogin(ctx) - assert.Nil(t, err) - - //username error - input2 := &LoginInput{} - reqBody = `{ - "username": "sdfasdf", - "password": "admin" - }` - err = json.Unmarshal([]byte(reqBody), input2) - assert.Nil(t, err) - ctx.SetInput(input2) - _, err = handler.userLogin(ctx) - assert.EqualError(t, err, "username or password error") - - //password error - input3 := &LoginInput{} - reqBody = `{ - "username": "admin", - "password": "admin9384938" - }` - err = json.Unmarshal([]byte(reqBody), input3) - assert.Nil(t, err) - ctx.SetInput(input3) - _, err = handler.userLogin(ctx) - assert.EqualError(t, err, "username or password error") - -} diff --git a/api/internal/handler/consumer/consumer.go b/api/internal/handler/consumer/consumer.go deleted file mode 100644 index 0b169bbcbc..0000000000 --- a/api/internal/handler/consumer/consumer.go +++ /dev/null @@ -1,192 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one or more - * contributor license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright ownership. - * The ASF licenses this file to You under the Apache License, Version 2.0 - * (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package consumer - -import ( - "reflect" - "strings" - "time" - - "github.com/gin-gonic/gin" - "github.com/shiningrush/droplet" - "github.com/shiningrush/droplet/wrapper" - wgin "github.com/shiningrush/droplet/wrapper/gin" - - "github.com/apisix/manager-api/internal/core/entity" - "github.com/apisix/manager-api/internal/core/store" - "github.com/apisix/manager-api/internal/handler" -) - -type Handler struct { - consumerStore store.Interface -} - -func NewHandler() (handler.RouteRegister, error) { - return &Handler{ - consumerStore: store.GetStore(store.HubKeyConsumer), - }, nil -} - -func (h *Handler) ApplyRoute(r *gin.Engine) { - r.GET("/apisix/admin/consumers/:username", wgin.Wraps(h.Get, - wrapper.InputType(reflect.TypeOf(GetInput{})))) - r.GET("/apisix/admin/consumers", wgin.Wraps(h.List, - wrapper.InputType(reflect.TypeOf(ListInput{})))) - r.PUT("/apisix/admin/consumers/:username", wgin.Wraps(h.Set, - wrapper.InputType(reflect.TypeOf(SetInput{})))) - r.PUT("/apisix/admin/consumers", wgin.Wraps(h.Set, - wrapper.InputType(reflect.TypeOf(SetInput{})))) - r.DELETE("/apisix/admin/consumers/:usernames", wgin.Wraps(h.BatchDelete, - wrapper.InputType(reflect.TypeOf(BatchDeleteInput{})))) -} - -type GetInput struct { - Username string `auto_read:"username,path" validate:"required"` -} - -func (h *Handler) Get(c droplet.Context) (interface{}, error) { - input := c.Input().(*GetInput) - - r, err := h.consumerStore.Get(c.Context(), input.Username) - if err != nil { - return handler.SpecCodeResponse(err), err - } - return r, nil -} - -type ListInput struct { - Username string `auto_read:"username,query"` - store.Pagination -} - -// swagger:operation GET /apisix/admin/consumers getConsumerList -// -// Return the consumer list according to the specified page number and page size, and can search consumers by username. -// -// --- -// produces: -// - application/json -// parameters: -// - name: page -// in: query -// description: page number -// required: false -// type: integer -// - name: page_size -// in: query -// description: page size -// required: false -// type: integer -// - name: username -// in: query -// description: username of consumer -// required: false -// type: string -// responses: -// '0': -// description: list response -// schema: -// type: array -// items: -// "$ref": "#/definitions/consumer" -// default: -// description: unexpected error -// schema: -// "$ref": "#/definitions/ApiError" -func (h *Handler) List(c droplet.Context) (interface{}, error) { - input := c.Input().(*ListInput) - - ret, err := h.consumerStore.List(c.Context(), store.ListInput{ - Predicate: func(obj interface{}) bool { - if input.Username != "" { - return strings.Contains(obj.(*entity.Consumer).Username, input.Username) - } - return true - }, - Less: func(i, j interface{}) bool { - iBase := i.(*entity.Consumer) - jBase := j.(*entity.Consumer) - if iBase.CreateTime != jBase.CreateTime { - return iBase.CreateTime < jBase.CreateTime - } - if iBase.UpdateTime != jBase.UpdateTime { - return iBase.UpdateTime < jBase.UpdateTime - } - return iBase.Username < jBase.Username - }, - PageSize: input.PageSize, - PageNumber: input.PageNumber, - }) - if err != nil { - return nil, err - } - - return ret, nil -} - -type SetInput struct { - entity.Consumer - Username string `auto_read:"username,path"` -} - -func (h *Handler) Set(c droplet.Context) (interface{}, error) { - input := c.Input().(*SetInput) - if input.Username != "" { - input.Consumer.Username = input.Username - } - ensurePluginsDefValue(input.Plugins) - - // Because the ID of consumer has been removed, - // `BaseInfo` is no longer embedded in consumer's struct, - // So we need to maintain create_time and update_time separately for consumer - savedConsumer, _ := h.consumerStore.Get(c.Context(), input.Consumer.Username) - input.Consumer.CreateTime = time.Now().Unix() - input.Consumer.UpdateTime = time.Now().Unix() - if savedConsumer != nil { - input.Consumer.CreateTime = savedConsumer.(*entity.Consumer).CreateTime - } - - ret, err := h.consumerStore.Update(c.Context(), &input.Consumer, true) - if err != nil { - return handler.SpecCodeResponse(err), err - } - - return ret, nil -} - -func ensurePluginsDefValue(plugins map[string]interface{}) { - if plugins["jwt-auth"] != nil { - jwtAuth, ok := plugins["jwt-auth"].(map[string]interface{}) - if ok && jwtAuth["exp"] == nil { - jwtAuth["exp"] = 86400 - } - } -} - -type BatchDeleteInput struct { - UserNames string `auto_read:"usernames,path"` -} - -func (h *Handler) BatchDelete(c droplet.Context) (interface{}, error) { - input := c.Input().(*BatchDeleteInput) - - if err := h.consumerStore.BatchDelete(c.Context(), strings.Split(input.UserNames, ",")); err != nil { - return handler.SpecCodeResponse(err), err - } - - return nil, nil -} diff --git a/api/internal/handler/consumer/consumer_test.go b/api/internal/handler/consumer/consumer_test.go deleted file mode 100644 index 9a76bc98b3..0000000000 --- a/api/internal/handler/consumer/consumer_test.go +++ /dev/null @@ -1,461 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one or more - * contributor license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright ownership. - * The ASF licenses this file to You under the Apache License, Version 2.0 - * (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package consumer - -import ( - "context" - "fmt" - "net/http" - "testing" - - "github.com/shiningrush/droplet" - "github.com/shiningrush/droplet/data" - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/mock" - - "github.com/apisix/manager-api/internal/core/entity" - "github.com/apisix/manager-api/internal/core/store" -) - -func TestHandler_Get(t *testing.T) { - tests := []struct { - caseDesc string - giveInput *GetInput - giveRet interface{} - giveErr error - wantErr error - wantGetKey string - wantRet interface{} - }{ - { - caseDesc: "normal", - giveInput: &GetInput{Username: "test"}, - wantGetKey: "test", - giveRet: "hello", - wantRet: "hello", - }, - { - caseDesc: "store get failed", - giveInput: &GetInput{Username: "failed key"}, - wantGetKey: "failed key", - giveErr: fmt.Errorf("get failed"), - wantErr: fmt.Errorf("get failed"), - wantRet: &data.SpecCodeResponse{ - StatusCode: http.StatusInternalServerError, - }, - }, - } - - for _, tc := range tests { - t.Run(tc.caseDesc, func(t *testing.T) { - getCalled := false - mStore := &store.MockInterface{} - mStore.On("Get", mock.Anything).Run(func(args mock.Arguments) { - getCalled = true - assert.Equal(t, tc.wantGetKey, args.Get(0)) - }).Return(tc.giveRet, tc.giveErr) - - h := Handler{consumerStore: mStore} - ctx := droplet.NewContext() - ctx.SetInput(tc.giveInput) - ret, err := h.Get(ctx) - assert.True(t, getCalled) - assert.Equal(t, tc.wantRet, ret) - assert.Equal(t, tc.wantErr, err) - }) - } -} - -func TestHandler_List(t *testing.T) { - tests := []struct { - caseDesc string - giveInput *ListInput - giveData []*entity.Consumer - giveErr error - wantErr error - wantInput store.ListInput - wantRet interface{} - }{ - { - caseDesc: "list all condition", - giveInput: &ListInput{ - Username: "testUser", - Pagination: store.Pagination{ - PageSize: 10, - PageNumber: 10, - }, - }, - wantInput: store.ListInput{ - PageSize: 10, - PageNumber: 10, - }, - giveData: []*entity.Consumer{ - {Username: "user1"}, - {Username: "testUser"}, - {Username: "iam-testUser"}, - {Username: "testUser-is-me"}, - }, - wantRet: &store.ListOutput{ - Rows: []interface{}{ - &entity.Consumer{Username: "iam-testUser"}, - &entity.Consumer{Username: "testUser"}, - &entity.Consumer{Username: "testUser-is-me"}, - }, - TotalSize: 3, - }, - }, - { - caseDesc: "store list failed", - giveInput: &ListInput{ - Username: "testUser", - Pagination: store.Pagination{ - PageSize: 10, - PageNumber: 10, - }, - }, - wantInput: store.ListInput{ - PageSize: 10, - PageNumber: 10, - }, - giveData: []*entity.Consumer{}, - giveErr: fmt.Errorf("list failed"), - wantErr: fmt.Errorf("list failed"), - }, - } - - for _, tc := range tests { - t.Run(tc.caseDesc, func(t *testing.T) { - getCalled := false - mStore := &store.MockInterface{} - mStore.On("List", mock.Anything).Run(func(args mock.Arguments) { - getCalled = true - input := args.Get(0).(store.ListInput) - assert.Equal(t, tc.wantInput.PageSize, input.PageSize) - assert.Equal(t, tc.wantInput.PageNumber, input.PageNumber) - }).Return(func(input store.ListInput) *store.ListOutput { - var returnData []interface{} - for _, c := range tc.giveData { - if input.Predicate(c) { - returnData = append(returnData, c) - } - } - return &store.ListOutput{ - Rows: returnData, - TotalSize: len(returnData), - } - }, tc.giveErr) - - h := Handler{consumerStore: mStore} - ctx := droplet.NewContext() - ctx.SetInput(tc.giveInput) - ret, err := h.List(ctx) - assert.True(t, getCalled) - assert.Equal(t, tc.wantRet, ret) - assert.Equal(t, tc.wantErr, err) - }) - } -} - -func TestHandler_Create(t *testing.T) { - tests := []struct { - caseDesc string - giveInput *SetInput - giveCtx context.Context - giveErr error - giveRet interface{} - wantErr error - wantInput *SetInput - wantRet interface{} - wantCalled bool - }{ - { - caseDesc: "normal", - giveInput: &SetInput{ - Consumer: entity.Consumer{ - Username: "name", - Plugins: map[string]interface{}{ - "jwt-auth": map[string]interface{}{}, - }, - }, - }, - giveCtx: context.WithValue(context.Background(), "test", "value"), - giveRet: &entity.Consumer{ - Username: "name", - Plugins: map[string]interface{}{ - "jwt-auth": map[string]interface{}{ - "exp": 86400, - }, - }, - }, - wantInput: &SetInput{ - Consumer: entity.Consumer{ - Username: "name", - Plugins: map[string]interface{}{ - "jwt-auth": map[string]interface{}{ - "exp": 86400, - }, - }, - }, - }, - wantRet: &entity.Consumer{ - Username: "name", - Plugins: map[string]interface{}{ - "jwt-auth": map[string]interface{}{ - "exp": 86400, - }, - }, - }, - wantCalled: true, - }, - { - caseDesc: "store create failed", - giveInput: &SetInput{ - Consumer: entity.Consumer{ - Username: "name", - Plugins: map[string]interface{}{ - "jwt-auth": map[string]interface{}{ - "exp": 5000, - }, - }, - }, - }, - giveRet: &data.SpecCodeResponse{ - StatusCode: http.StatusInternalServerError, - }, - giveErr: fmt.Errorf("create failed"), - wantInput: &SetInput{ - Consumer: entity.Consumer{ - Username: "name", - Plugins: map[string]interface{}{ - "jwt-auth": map[string]interface{}{ - "exp": 5000, - }, - }, - }, - }, - wantErr: fmt.Errorf("create failed"), - wantRet: &data.SpecCodeResponse{ - StatusCode: http.StatusInternalServerError, - }, - wantCalled: true, - }, - } - - for _, tc := range tests { - t.Run(tc.caseDesc, func(t *testing.T) { - methodCalled := true - mStore := &store.MockInterface{} - mStore.On("Update", mock.Anything, mock.Anything, mock.Anything).Run(func(args mock.Arguments) { - methodCalled = true - assert.Equal(t, tc.giveCtx, args.Get(0)) - assert.True(t, args.Bool(2)) - }).Return(tc.giveRet, tc.giveErr) - - mStore.On("Get", mock.Anything).Run(func(args mock.Arguments) { - }).Return(nil, nil) - - h := Handler{consumerStore: mStore} - ctx := droplet.NewContext() - ctx.SetInput(tc.giveInput) - ctx.SetContext(tc.giveCtx) - ret, err := h.Set(ctx) - assert.Equal(t, tc.wantCalled, methodCalled) - assert.Equal(t, tc.wantRet, ret) - assert.Equal(t, tc.wantErr, err) - }) - } -} - -func TestHandler_Update(t *testing.T) { - tests := []struct { - caseDesc string - giveInput *SetInput - giveCtx context.Context - giveRet interface{} - giveErr error - wantErr error - wantInput *entity.Consumer - wantRet interface{} - wantCalled bool - getRet interface{} - }{ - { - caseDesc: "normal", - giveInput: &SetInput{ - Username: "name", - Consumer: entity.Consumer{ - Plugins: map[string]interface{}{ - "jwt-auth": map[string]interface{}{ - "exp": 500, - }, - }, - }, - }, - giveCtx: context.WithValue(context.Background(), "test", "value"), - giveRet: &entity.Consumer{ - Username: "name", - Plugins: map[string]interface{}{ - "jwt-auth": map[string]interface{}{ - "exp": 500, - }, - }, - CreateTime: 1618648423, - }, - wantInput: &entity.Consumer{ - Username: "name", - Plugins: map[string]interface{}{ - "jwt-auth": map[string]interface{}{ - "exp": 500, - }, - }, - }, - wantRet: &entity.Consumer{ - Username: "name", - Plugins: map[string]interface{}{ - "jwt-auth": map[string]interface{}{ - "exp": 500, - }, - }, - CreateTime: 1618648423, - }, - wantCalled: true, - getRet: &entity.Consumer{ - Username: "name", - CreateTime: 1618648423, - UpdateTime: 1618648423, - }, - }, - { - caseDesc: "store update failed", - giveInput: &SetInput{ - Username: "name", - Consumer: entity.Consumer{ - Plugins: map[string]interface{}{ - "jwt-auth": map[string]interface{}{}, - }, - }, - }, - giveRet: &data.SpecCodeResponse{ - StatusCode: http.StatusInternalServerError, - }, - giveErr: fmt.Errorf("create failed"), - wantInput: &entity.Consumer{ - Username: "name", - Plugins: map[string]interface{}{ - "jwt-auth": map[string]interface{}{ - "exp": 86400, - }, - }, - }, - wantErr: fmt.Errorf("create failed"), - wantRet: &data.SpecCodeResponse{ - StatusCode: http.StatusInternalServerError, - }, - wantCalled: true, - }, - } - - for _, tc := range tests { - t.Run(tc.caseDesc, func(t *testing.T) { - methodCalled := true - mStore := &store.MockInterface{} - mStore.On("Update", mock.Anything, mock.Anything, mock.Anything).Run(func(args mock.Arguments) { - methodCalled = true - assert.Equal(t, tc.giveCtx, args.Get(0)) - assert.True(t, args.Bool(2)) - }).Return(tc.giveRet, tc.giveErr) - - mStore.On("Get", mock.Anything).Run(func(args mock.Arguments) { - }).Return(tc.getRet, nil) - - h := Handler{consumerStore: mStore} - ctx := droplet.NewContext() - ctx.SetInput(tc.giveInput) - ctx.SetContext(tc.giveCtx) - ret, err := h.Set(ctx) - assert.Equal(t, tc.wantCalled, methodCalled) - assert.Equal(t, tc.wantRet, ret) - assert.Equal(t, tc.wantErr, err) - if err == nil { - assert.Equal(t, tc.getRet.(*entity.Consumer).CreateTime, ret.(*entity.Consumer).CreateTime) - assert.NotEqual(t, tc.getRet.(*entity.Consumer).UpdateTime, ret.(*entity.Consumer).UpdateTime) - } - }) - } -} - -func TestHandler_BatchDelete(t *testing.T) { - tests := []struct { - caseDesc string - giveInput *BatchDeleteInput - giveCtx context.Context - giveErr error - wantErr error - wantInput []string - wantRet interface{} - }{ - { - caseDesc: "normal", - giveInput: &BatchDeleteInput{ - UserNames: "user1,user2", - }, - giveCtx: context.WithValue(context.Background(), "test", "value"), - wantInput: []string{ - "user1", - "user2", - }, - }, - { - caseDesc: "store delete failed", - giveInput: &BatchDeleteInput{ - UserNames: "user1,user2", - }, - giveCtx: context.WithValue(context.Background(), "test", "value"), - giveErr: fmt.Errorf("delete failed"), - wantInput: []string{ - "user1", - "user2", - }, - wantErr: fmt.Errorf("delete failed"), - wantRet: &data.SpecCodeResponse{ - StatusCode: http.StatusInternalServerError, - }, - }, - } - - for _, tc := range tests { - t.Run(tc.caseDesc, func(t *testing.T) { - methodCalled := true - mStore := &store.MockInterface{} - mStore.On("BatchDelete", mock.Anything, mock.Anything, mock.Anything).Run(func(args mock.Arguments) { - methodCalled = true - assert.Equal(t, tc.giveCtx, args.Get(0)) - assert.Equal(t, tc.wantInput, args.Get(1)) - }).Return(tc.giveErr) - - h := Handler{consumerStore: mStore} - ctx := droplet.NewContext() - ctx.SetInput(tc.giveInput) - ctx.SetContext(tc.giveCtx) - ret, err := h.BatchDelete(ctx) - assert.True(t, methodCalled) - assert.Equal(t, tc.wantErr, err) - assert.Equal(t, tc.wantRet, ret) - }) - } -} diff --git a/api/internal/handler/data_loader/loader/loader.go b/api/internal/handler/data_loader/loader/loader.go deleted file mode 100644 index a1bd8ffabe..0000000000 --- a/api/internal/handler/data_loader/loader/loader.go +++ /dev/null @@ -1,44 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one or more - * contributor license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright ownership. - * The ASF licenses this file to You under the Apache License, Version 2.0 - * (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package loader - -import "github.com/apisix/manager-api/internal/core/entity" - -// DataSets are intermediate structures used to handle -// import and export data with APISIX entities. -// On import, raw data will be parsed as DataSets -// On export, DataSets will be encoded to raw data -type DataSets struct { - Routes []entity.Route - Upstreams []entity.Upstream - Services []entity.Service - Consumers []entity.Consumer - SSLs []entity.SSL - StreamRoutes []entity.StreamRoute - GlobalPlugins []entity.GlobalPlugins - PluginConfigs []entity.PluginConfig - Protos []entity.Proto -} - -// Loader provide data loader abstraction -type Loader interface { - // Import accepts data and converts it into entity data sets - Import(input interface{}) (*DataSets, error) - - // Export accepts entity data sets and converts it into a specific format - Export(data DataSets) (interface{}, error) -} diff --git a/api/internal/handler/data_loader/loader/openapi3/export.go b/api/internal/handler/data_loader/loader/openapi3/export.go deleted file mode 100644 index 9d56dd3a35..0000000000 --- a/api/internal/handler/data_loader/loader/openapi3/export.go +++ /dev/null @@ -1,24 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one or more - * contributor license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright ownership. - * The ASF licenses this file to You under the Apache License, Version 2.0 - * (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package openapi3 - -import "github.com/apisix/manager-api/internal/handler/data_loader/loader" - -func (Loader) Export(data loader.DataSets) (interface{}, error) { - //TODO implement me - panic("implement me") -} diff --git a/api/internal/handler/data_loader/loader/openapi3/import.go b/api/internal/handler/data_loader/loader/openapi3/import.go deleted file mode 100644 index e1bcf9dbc1..0000000000 --- a/api/internal/handler/data_loader/loader/openapi3/import.go +++ /dev/null @@ -1,130 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one or more - * contributor license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright ownership. - * The ASF licenses this file to You under the Apache License, Version 2.0 - * (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package openapi3 - -import ( - "fmt" - "reflect" - "strings" - "time" - - "github.com/getkin/kin-openapi/openapi3" - "github.com/pkg/errors" - - "github.com/apisix/manager-api/internal/core/entity" - "github.com/apisix/manager-api/internal/handler/data_loader/loader" - "github.com/apisix/manager-api/internal/utils/consts" -) - -func (o Loader) Import(input interface{}) (*loader.DataSets, error) { - if input == nil { - panic("input is nil") - } - - d, ok := input.([]byte) - if !ok { - panic(fmt.Sprintf("input format error: expected []byte but it is %s", reflect.TypeOf(input).Kind().String())) - } - - // load OAS3 document - swagger, err := openapi3.NewSwaggerLoader().LoadSwaggerFromData(d) - if err != nil { - return nil, err - } - - // no paths in OAS3 document - if len(swagger.Paths) <= 0 { - return nil, errors.Wrap(errors.New("OpenAPI documentation does not contain any paths"), consts.ErrImportFile.Error()) - } - - if o.TaskName == "" { - o.TaskName = "openapi_" + time.Now().Format("20060102150405") - } - - data, err := o.convertToEntities(swagger) - if err != nil { - return nil, err - } - - return data, nil -} - -func (o Loader) convertToEntities(s *openapi3.Swagger) (*loader.DataSets, error) { - var ( - // temporarily save the parsed data - data = &loader.DataSets{} - // global upstream ID - globalUpstreamID = o.TaskName - // global uri prefix - globalPath = "" - ) - - // create upstream when servers field not empty - if len(s.Servers) > 0 { - upstream := entity.Upstream{ - BaseInfo: entity.BaseInfo{ID: globalUpstreamID}, - UpstreamDef: entity.UpstreamDef{ - Name: globalUpstreamID, - Type: "roundrobin", - Nodes: map[string]float64{ - "0.0.0.0": 1, - }, - }, - } - data.Upstreams = append(data.Upstreams, upstream) - } - - // each one will correspond to a route - for uri, v := range s.Paths { - // replace parameter in uri to wildcard - realUri := regURIVar.ReplaceAllString(uri, "*") - // generate route Name - routeName := o.TaskName + "_" + strings.TrimPrefix(uri, "/") - - // decide whether to merge multi-method routes based on configuration - if o.MergeMethod { - // create a single route for each path, merge all methods - route := generateBaseRoute(routeName, v.Summary) - route.Uris = []string{globalPath + realUri} - route.UpstreamID = globalUpstreamID - for method := range v.Operations() { - route.Methods = append(route.Methods, strings.ToUpper(method)) - } - data.Routes = append(data.Routes, route) - } else { - // create routes for each method of each path - for method, operation := range v.Operations() { - subRouteID := routeName + "_" + method - route := generateBaseRoute(subRouteID, operation.Summary) - route.Uris = []string{globalPath + realUri} - route.Methods = []string{strings.ToUpper(method)} - route.UpstreamID = globalUpstreamID - data.Routes = append(data.Routes, route) - } - } - } - return data, nil -} - -// Generate a base route for customize -func generateBaseRoute(name string, desc string) entity.Route { - return entity.Route{ - Name: name, - Desc: desc, - Plugins: make(map[string]interface{}), - } -} diff --git a/api/internal/handler/data_loader/loader/openapi3/import_test.go b/api/internal/handler/data_loader/loader/openapi3/import_test.go deleted file mode 100644 index bc6ff809c0..0000000000 --- a/api/internal/handler/data_loader/loader/openapi3/import_test.go +++ /dev/null @@ -1,119 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one or more - * contributor license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright ownership. - * The ASF licenses this file to You under the Apache License, Version 2.0 - * (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package openapi3 - -import ( - "io/ioutil" - "testing" - - "github.com/stretchr/testify/assert" - - "github.com/apisix/manager-api/internal/core/entity" -) - -var ( - TestAPI101 = "../../../../../test/testdata/import/Postman-API101.yaml" -) - -// Test API 101 on no MergeMethod mode -func TestParseAPI101NoMerge(t *testing.T) { - fileContent, err := ioutil.ReadFile(TestAPI101) - assert.NoError(t, err) - - l := &Loader{MergeMethod: false, TaskName: "test"} - data, err := l.Import(fileContent) - assert.NoError(t, err) - - assert.Len(t, data.Routes, 5) - assert.Len(t, data.Upstreams, 1) - - // Upstream - assert.Equal(t, "test", data.Upstreams[0].Name) - assert.Equal(t, "roundrobin", data.Upstreams[0].Type) - - // Route - assert.Equal(t, data.Upstreams[0].ID, data.Routes[0].UpstreamID) - for _, route := range data.Routes { - switch route.Name { - case "test_customers_GET": - assert.Contains(t, route.Uris, "/customers") - assert.Contains(t, route.Methods, "GET") - assert.Equal(t, "Get all customers", route.Desc) - assert.Equal(t, entity.Status(0), route.Status) - case "test_customer_GET": - assert.Contains(t, route.Uris, "/customer") - assert.Contains(t, route.Methods, "GET") - assert.Equal(t, "Get one customer", route.Desc) - assert.Equal(t, entity.Status(0), route.Status) - case "test_customer_POST": - assert.Contains(t, route.Uris, "/customer") - assert.Contains(t, route.Methods, "POST") - assert.Equal(t, "Add new customer", route.Desc) - assert.Equal(t, entity.Status(0), route.Status) - case "test_customer/{customer_id}_PUT": - assert.Contains(t, route.Uris, "/customer/*") - assert.Contains(t, route.Methods, "PUT") - assert.Equal(t, "Update customer", route.Desc) - assert.Equal(t, entity.Status(0), route.Status) - case "test_customer/{customer_id}_DELETE": - assert.Contains(t, route.Uris, "/customer/*") - assert.Contains(t, route.Methods, "DELETE") - assert.Equal(t, "Remove customer", route.Desc) - assert.Equal(t, entity.Status(0), route.Status) - default: - t.Fatal("bad route name exist") - } - } -} - -// Test API 101 on MergeMethod mode -func TestParseAPI101Merge(t *testing.T) { - fileContent, err := ioutil.ReadFile(TestAPI101) - assert.NoError(t, err) - - l := &Loader{MergeMethod: true, TaskName: "test"} - data, err := l.Import(fileContent) - assert.NoError(t, err) - - assert.Len(t, data.Routes, 3) - assert.Len(t, data.Upstreams, 1) - - // Upstream - assert.Equal(t, "test", data.Upstreams[0].Name) - assert.Equal(t, "roundrobin", data.Upstreams[0].Type) - - // Route - assert.Equal(t, data.Upstreams[0].ID, data.Routes[0].UpstreamID) - for _, route := range data.Routes { - switch route.Name { - case "test_customer": - assert.Contains(t, route.Uris, "/customer") - assert.Contains(t, route.Methods, "GET", "GET") - assert.Equal(t, entity.Status(0), route.Status) - case "test_customers": - assert.Contains(t, route.Uris, "/customers") - assert.Contains(t, route.Methods, "GET") - assert.Equal(t, entity.Status(0), route.Status) - case "test_customer/{customer_id}": - assert.Contains(t, route.Uris, "/customer/*") - assert.Contains(t, route.Methods, "PUT", "DELETE") - assert.Equal(t, entity.Status(0), route.Status) - default: - t.Fatal("bad route name exist") - } - } -} diff --git a/api/internal/handler/data_loader/loader/openapi3/openapi3.go b/api/internal/handler/data_loader/loader/openapi3/openapi3.go deleted file mode 100644 index f480ff22d3..0000000000 --- a/api/internal/handler/data_loader/loader/openapi3/openapi3.go +++ /dev/null @@ -1,41 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one or more - * contributor license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright ownership. - * The ASF licenses this file to You under the Apache License, Version 2.0 - * (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package openapi3 - -import ( - "regexp" - - "github.com/getkin/kin-openapi/openapi3" -) - -type OpenAPISpecFileType string - -type Loader struct { - // MergeMethod indicates whether to merge routes when multiple HTTP methods are on the same path - MergeMethod bool - // TaskName indicates the name of current import/export task - TaskName string -} - -type PathValue struct { - Method string - Value *openapi3.Operation -} - -var ( - regURIVar = regexp.MustCompile(`{.*?}`) -) diff --git a/api/internal/handler/data_loader/route_export.go b/api/internal/handler/data_loader/route_export.go deleted file mode 100644 index a61d5d1ed5..0000000000 --- a/api/internal/handler/data_loader/route_export.go +++ /dev/null @@ -1,511 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one or more - * contributor license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright ownership. - * The ASF licenses this file to You under the Apache License, Version 2.0 - * (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package data_loader - -import ( - "encoding/json" - "fmt" - "net/http" - "reflect" - "strconv" - "strings" - - "github.com/getkin/kin-openapi/openapi3" - "github.com/gin-gonic/gin" - "github.com/shiningrush/droplet" - "github.com/shiningrush/droplet/data" - "github.com/shiningrush/droplet/wrapper" - wgin "github.com/shiningrush/droplet/wrapper/gin" - - "github.com/apisix/manager-api/internal/core/entity" - "github.com/apisix/manager-api/internal/core/store" - "github.com/apisix/manager-api/internal/handler" - "github.com/apisix/manager-api/internal/log" - "github.com/apisix/manager-api/internal/utils" - "github.com/apisix/manager-api/internal/utils/consts" -) - -type Handler struct { - routeStore store.Interface - upstreamStore store.Interface - serviceStore store.Interface - consumerStore store.Interface -} - -func NewHandler() (handler.RouteRegister, error) { - return &Handler{ - routeStore: store.GetStore(store.HubKeyRoute), - upstreamStore: store.GetStore(store.HubKeyUpstream), - serviceStore: store.GetStore(store.HubKeyService), - consumerStore: store.GetStore(store.HubKeyConsumer), - }, nil -} - -func (h *Handler) ApplyRoute(r *gin.Engine) { - r.GET("/apisix/admin/export/routes/:ids", wgin.Wraps(h.ExportRoutes, - wrapper.InputType(reflect.TypeOf(ExportInput{})))) - r.GET("/apisix/admin/export/routes", wgin.Wraps(h.ExportAllRoutes)) -} - -type ExportInput struct { - IDs string `auto_read:"ids,path"` -} - -//ExportRoutes Export data by passing route ID, such as "R1" or multiple route parameters, such as "R1,R2" -func (h *Handler) ExportRoutes(c droplet.Context) (interface{}, error) { - input := c.Input().(*ExportInput) - - if input.IDs == "" { - return nil, consts.ErrParameterID - } - - ids := strings.Split(input.IDs, ",") - routes := []*entity.Route{} - - for _, id := range ids { - route, err := h.routeStore.Get(c.Context(), id) - if err != nil { - if err == data.ErrNotFound { - return nil, fmt.Errorf(consts.IDNotFound, "upstream", id) - } - return nil, err - } - routes = append(routes, route.(*entity.Route)) - } - - swagger, err := h.RouteToOpenAPI3(c, routes) - if err != nil { - return nil, err - } - return swagger, nil -} - -type AuthType string - -const ( - BasicAuth AuthType = "basic-auth" - KeyAuth AuthType = "key-auth" - JWTAuth AuthType = "jwt-auth" -) - -var ( - openApi = "3.0.0" - title = "RoutesExport" - service interface{} - err error - routeMethods []string - _allHTTPMethods = []string{http.MethodGet, http.MethodPost, http.MethodPut, http.MethodDelete, http.MethodPatch, http.MethodHead, http.MethodConnect, http.MethodTrace, http.MethodOptions} -) - -//ExportAllRoutes All routes can be directly exported without passing parameters -func (h *Handler) ExportAllRoutes(c droplet.Context) (interface{}, error) { - routelist, err := h.routeStore.List(c.Context(), store.ListInput{}) - - if err != nil { - return nil, err - } - - if len(routelist.Rows) < 1 { - return nil, consts.ErrRouteData - } - - routes := []*entity.Route{} - - for _, route := range routelist.Rows { - routes = append(routes, route.(*entity.Route)) - } - - swagger, err := h.RouteToOpenAPI3(c, routes) - if err != nil { - return nil, err - } - return swagger, nil -} - -//RouteToOpenAPI3 Pass in route list parameter: []*entity.Route, convert route data to openapi3 and export processing function -func (h *Handler) RouteToOpenAPI3(c droplet.Context, routes []*entity.Route) (*openapi3.Swagger, error) { - paths := openapi3.Paths{} - paramsRefs := []*openapi3.ParameterRef{} - requestBody := &openapi3.RequestBody{} - components := &openapi3.Components{} - secSchemas := openapi3.SecuritySchemes{} - _pathNumber := GetPathNumber() - - for _, route := range routes { - extensions := make(map[string]interface{}) - servicePlugins := make(map[string]interface{}) - plugins := make(map[string]interface{}) - serviceLabels := make(map[string]string) - - pathItem := &openapi3.PathItem{} - path := openapi3.Operation{} - path.Summary = route.Desc - path.OperationID = route.Name - - if route.ServiceID != nil { - serviceID := utils.InterfaceToString(route.ServiceID) - service, err = h.serviceStore.Get(c.Context(), serviceID) - if err != nil { - if err == data.ErrNotFound { - return nil, fmt.Errorf(consts.IDNotFound, "service", route.ServiceID) - } - return nil, err - } - - _service := service.(*entity.Service) - servicePlugins = _service.Plugins - serviceLabels = _service.Labels - } - - //Parse upstream - _upstream, err := h.ParseRouteUpstream(c, route) - - if err != nil { - log.Errorf("ParseRouteUpstream err: ", err) - return nil, err - } else if _upstream != nil { - extensions["x-apisix-upstream"] = _upstream - } - - if route.Host != "" { - extensions["x-apisix-host"] = route.Host - } - - if route.Hosts != nil { - extensions["x-apisix-hosts"] = route.Hosts - } - - //Parse Labels - labels, err := ParseLabels(route, serviceLabels) - if err != nil { - log.Errorf("parseLabels err: ", err) - return nil, err - } - - if labels != nil { - extensions["x-apisix-labels"] = labels - } - - if route.RemoteAddr != "" { - extensions["x-apisix-remote_addr"] = route.RemoteAddr - } - - if route.RemoteAddrs != nil { - extensions["x-apisix-remote_addrs"] = route.RemoteAddrs - } - - if route.FilterFunc != "" { - extensions["x-apisix-filter_func"] = route.FilterFunc - } - - if route.Script != nil { - extensions["x-apisix-script"] = route.Script - } - - if route.ServiceProtocol != "" { - extensions["x-apisix-service_protocol"] = route.ServiceProtocol - } - - if route.Vars != nil { - extensions["x-apisix-vars"] = route.Vars - } - - if route.ID != nil { - extensions["x-apisix-id"] = route.ID - } - - // Parse Route URIs - paths, paramsRefs = ParseRouteUris(route, paths, paramsRefs, pathItem, _pathNumber()) - - //Parse Route Plugins - path, secSchemas, paramsRefs, plugins, err = ParseRoutePlugins(route, paramsRefs, plugins, path, servicePlugins, secSchemas, requestBody) - - if err != nil { - log.Errorf("parseRoutePlugins err: ", err) - return nil, err - } - - if len(plugins) > 0 { - extensions["x-apisix-plugins"] = plugins - } - - extensions["x-apisix-priority"] = route.Priority - extensions["x-apisix-status"] = route.Status - extensions["x-apisix-enable_websocket"] = route.EnableWebsocket - path.Extensions = extensions - path.Parameters = paramsRefs - path.RequestBody = &openapi3.RequestBodyRef{Value: requestBody} - path.Responses = openapi3.NewResponses() - - if route.Methods != nil && len(route.Methods) > 0 { - routeMethods = route.Methods - } else { - routeMethods = _allHTTPMethods - } - - for i := range routeMethods { - switch strings.ToUpper(routeMethods[i]) { - case http.MethodGet: - pathItem.Get = ParsePathItem(path, http.MethodGet) - case http.MethodPost: - pathItem.Post = ParsePathItem(path, http.MethodPost) - case http.MethodPut: - pathItem.Put = ParsePathItem(path, http.MethodPut) - case http.MethodDelete: - pathItem.Delete = ParsePathItem(path, http.MethodDelete) - case http.MethodPatch: - pathItem.Patch = ParsePathItem(path, http.MethodPatch) - case http.MethodHead: - pathItem.Head = ParsePathItem(path, http.MethodHead) - case http.MethodConnect: - pathItem.Connect = ParsePathItem(path, http.MethodConnect) - case http.MethodTrace: - pathItem.Trace = ParsePathItem(path, http.MethodTrace) - case http.MethodOptions: - pathItem.Options = ParsePathItem(path, http.MethodOptions) - } - } - } - - components.SecuritySchemes = secSchemas - swagger := openapi3.Swagger{ - OpenAPI: openApi, - Info: &openapi3.Info{Title: title, Version: openApi}, - Paths: paths, - Components: *components, - } - return &swagger, nil -} - -//ParseLabels When service and route have labels at the same time, use route's label. -//When route has no label, service sometimes uses service's label. This function is used to process this logic -func ParseLabels(route *entity.Route, serviceLabels map[string]string) (map[string]string, error) { - if route.Labels != nil { - return route.Labels, nil - } else if route.ServiceID != nil { - return serviceLabels, nil - } - return nil, nil -} - -//ParsePathItem Convert data in route to openapi3 -func ParsePathItem(path openapi3.Operation, routeMethod string) *openapi3.Operation { - _path := &openapi3.Operation{ - ExtensionProps: path.ExtensionProps, - Tags: path.Tags, - Summary: path.Summary, - Description: path.Description, - OperationID: path.OperationID + routeMethod, - Parameters: path.Parameters, - RequestBody: path.RequestBody, - Responses: path.Responses, - Callbacks: path.Callbacks, - Deprecated: path.Deprecated, - Security: path.Security, - Servers: path.Servers, - ExternalDocs: path.ExternalDocs, - } - return _path -} - -// ParseRoutePlugins Merge service with plugin in route -func ParseRoutePlugins(route *entity.Route, paramsRefs []*openapi3.ParameterRef, plugins map[string]interface{}, path openapi3.Operation, servicePlugins map[string]interface{}, secSchemas openapi3.SecuritySchemes, requestBody *openapi3.RequestBody) (openapi3.Operation, openapi3.SecuritySchemes, []*openapi3.ParameterRef, map[string]interface{}, error) { - if route.Plugins != nil { - param := &openapi3.Parameter{} - secReq := &openapi3.SecurityRequirements{} - - // analysis plugins - for key, value := range route.Plugins { - // analysis request-validation plugin - if key == "request-validation" { - if valueMap, ok := value.(map[string]interface{}); ok { - if hsVal, ok := valueMap["header_schema"]; ok { - param.In = "header" - requestValidation := &entity.RequestValidation{} - reqBytes, _ := json.Marshal(&hsVal) - err := json.Unmarshal(reqBytes, requestValidation) - if err != nil { - log.Errorf("json marshal failed: %s", err) - } - for key1, value1 := range requestValidation.Properties.(map[string]interface{}) { - for _, arr := range requestValidation.Required { - if arr == key1 { - param.Required = true - } - } - param.Name = key1 - typeStr := value1.(map[string]interface{}) - schema := &openapi3.Schema{Type: typeStr["type"].(string)} - param.Schema = &openapi3.SchemaRef{Value: schema} - paramsRefs = append(paramsRefs, &openapi3.ParameterRef{Value: param}) - } - } - - if bsVal, ok := valueMap["body_schema"]; ok { - m := map[string]*openapi3.MediaType{} - reqBytes, _ := json.Marshal(&bsVal) - schema := &openapi3.Schema{} - err := json.Unmarshal(reqBytes, schema) - if err != nil { - log.Errorf("json marshal failed: %s", err) - } - // In the swagger format conversion, there are many cases of content type data format - // Such as (application/json, application/xml, text/xml) and more. - // There are many matching methods, such as equal, inclusive and so on. - // Therefore, the current processing method is to use "*/*" to match all - m["*/*"] = &openapi3.MediaType{Schema: &openapi3.SchemaRef{Value: schema}} - requestBody.Content = m - } - } - continue - } - // analysis security plugins - securityEnv := &openapi3.SecurityRequirement{} - switch key { - case string(KeyAuth): - secSchemas["api_key"] = &openapi3.SecuritySchemeRef{Value: openapi3.NewCSRFSecurityScheme()} - securityEnv.Authenticate("api_key", " ") - secReq.With(*securityEnv) - continue - case string(BasicAuth): - secSchemas["basicAuth"] = &openapi3.SecuritySchemeRef{Value: &openapi3.SecurityScheme{ - Type: "basicAuth", - Name: "basicAuth", - In: "header", - }} - securityEnv.Authenticate("basicAuth", " ") - secReq.With(*securityEnv) - continue - case string(JWTAuth): - secSchemas["bearerAuth"] = &openapi3.SecuritySchemeRef{Value: openapi3.NewJWTSecurityScheme()} - securityEnv.Authenticate("bearerAuth", " ") - secReq.With(*securityEnv) - continue - } - plugins[key] = value - } - path.Security = secReq - - if route.ServiceID != nil && servicePlugins != nil { - _servicePlugins, err := json.Marshal(servicePlugins) - if err != nil { - log.Errorf("MapToJson err: ", err) - return path, nil, nil, nil, err - } - _plugins, err := json.Marshal(plugins) - if err != nil { - log.Errorf("MapToJson err: ", err) - return path, nil, nil, nil, err - } - bytePlugins, err := utils.MergeJson(_servicePlugins, _plugins) - if err != nil { - log.Errorf("Plugins MergeJson err: ", err) - return path, nil, nil, nil, err - } - err = json.Unmarshal(bytePlugins, &plugins) - if err != nil { - log.Errorf("JsonToMapDemo err: ", err) - return path, nil, nil, nil, err - } - } - } else if route.Plugins == nil && route.ServiceID != nil { - plugins = servicePlugins - } - return path, secSchemas, paramsRefs, plugins, nil -} - -// ParseRouteUris The URI and URIs of route are converted to paths URI in openapi3 -func ParseRouteUris(route *entity.Route, paths openapi3.Paths, paramsRefs []*openapi3.ParameterRef, pathItem *openapi3.PathItem, _pathNumber int) (openapi3.Paths, []*openapi3.ParameterRef) { - routeURIs := []string{} - if route.URI != "" { - routeURIs = append(routeURIs, route.URI) - } - - if route.Uris != nil { - routeURIs = route.Uris - } - - for _, uri := range routeURIs { - if strings.Contains(uri, "*") { - if _, ok := paths[strings.Split(uri, "*")[0]+"{params}"]; !ok { - paths[strings.Split(uri, "*")[0]+"{params}"] = pathItem - } else { - paths[strings.Split(uri, "*")[0]+"{params}"+"-APISIX-REPEAT-URI-"+strconv.Itoa(_pathNumber)] = pathItem - } - // add params introduce - paramsRefs = append(paramsRefs, &openapi3.ParameterRef{ - Value: &openapi3.Parameter{ - In: "path", - Name: "params", - Required: true, - Description: "params in path", - Schema: &openapi3.SchemaRef{Value: &openapi3.Schema{Type: "string"}}}}) - } else { - if _, ok := paths[uri]; !ok { - paths[uri] = pathItem - } else { - paths[uri+"-APISIX-REPEAT-URI-"+strconv.Itoa(_pathNumber)] = pathItem - } - } - } - return paths, paramsRefs -} - -// ParseRouteUpstream Processing the upstream in service and route -func (h *Handler) ParseRouteUpstream(c droplet.Context, route *entity.Route) (interface{}, error) { - // The upstream data of route has the highest priority. - // If there is one, it will be used directly. - // If there is no route, the upstream data of service will be used. - // If there is no route, the upstream data of service will not be used normally. - if route.Upstream != nil { - return route.Upstream, nil - } else if route.UpstreamID != nil && route.Upstream == nil { - upstreamID := utils.InterfaceToString(route.UpstreamID) - upstream, err := h.upstreamStore.Get(c.Context(), upstreamID) - if err != nil { - if err == data.ErrNotFound { - return nil, fmt.Errorf(consts.IDNotFound, "upstream", route.UpstreamID) - } - return nil, err - } - return upstream, nil - } else if route.UpstreamID == nil && route.Upstream == nil && route.ServiceID != nil { - _service := service.(*entity.Service) - if _service.Upstream != nil { - return _service.Upstream, nil - } else if _service.Upstream == nil && _service.UpstreamID != nil { - upstreamID := utils.InterfaceToString(_service.UpstreamID) - upstream, err := h.upstreamStore.Get(c.Context(), upstreamID) - if err != nil { - if err == data.ErrNotFound { - return nil, fmt.Errorf(consts.IDNotFound, "upstream", _service.UpstreamID) - } - return nil, err - } - return upstream, nil - } - } - return nil, nil -} - -func GetPathNumber() func() int { - i := 0 - return func() int { - i++ - return i - } -} diff --git a/api/internal/handler/data_loader/route_export_test.go b/api/internal/handler/data_loader/route_export_test.go deleted file mode 100644 index ad397f25e9..0000000000 --- a/api/internal/handler/data_loader/route_export_test.go +++ /dev/null @@ -1,2405 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one or more - * contributor license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright ownership. - * The ASF licenses this file to You under the Apache License, Version 2.0 - * (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package data_loader - -import ( - "encoding/json" - "errors" - "strings" - "testing" - - "github.com/apisix/manager-api/internal/core/entity" - "github.com/apisix/manager-api/internal/core/store" - "github.com/shiningrush/droplet" - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/mock" -) - -// 1.Export data as the route of URIs Hosts -func TestExportRoutes1(t *testing.T) { - input := &ExportInput{IDs: "1"} - //*entity.Route - r1 := `{ - "name": "aaaa", - "labels": { - "build":"16", - "env":"production", - "version":"v2" - }, - "plugins": { - "limit-count": { - "count": 2, - "time_window": 60, - "rejected_code": 503, - "key": "remote_addr" - } - }, - "status": 1, - "uris": ["/hello_"], - "hosts": ["foo.com", "*.bar.com"], - "methods": ["GET", "POST"], - "upstream": { - "nodes": { - "172.16.238.20:1980": 1 - }, - "type": "roundrobin" - } - }` - - exportR1 := `{ - "components": {}, - "info": { - "title": "RoutesExport", - "version": "3.0.0" - }, - "openapi": "3.0.0", - "paths": { - "/hello_": { - "get": { - "operationId": "aaaaGET", - "requestBody": {}, - "responses": { - "default": { - "description": "" - } - }, - "security": [], - "x-apisix-enable_websocket": false, - "x-apisix-hosts": ["foo.com", "*.bar.com"], - "x-apisix-labels": { - "build": "16", - "env": "production", - "version": "v2" - }, - "x-apisix-plugins": { - "limit-count": { - "count": 2, - "key": "remote_addr", - "rejected_code": 503, - "time_window": 60 - } - }, - "x-apisix-priority": 0, - "x-apisix-status": 1, - "x-apisix-upstream": { - "nodes": { - "172.16.238.20:1980": 1 - }, - "type": "roundrobin" - } - }, - "post": { - "operationId": "aaaaPOST", - "requestBody": {}, - "responses": { - "default": { - "description": "" - } - }, - "security": [], - "x-apisix-enable_websocket": false, - "x-apisix-hosts": ["foo.com", "*.bar.com"], - "x-apisix-labels": { - "build": "16", - "env": "production", - "version": "v2" - }, - "x-apisix-plugins": { - "limit-count": { - "count": 2, - "key": "remote_addr", - "rejected_code": 503, - "time_window": 60 - } - }, - "x-apisix-priority": 0, - "x-apisix-status": 1, - "x-apisix-upstream": { - "nodes": { - "172.16.238.20:1980": 1 - }, - "type": "roundrobin" - } - } - } - } - }` - var route *entity.Route - err := json.Unmarshal([]byte(r1), &route) - mStore := &store.MockInterface{} - mStore.On("Get", mock.Anything).Run(func(args mock.Arguments) { - }).Return(route, nil) - - h := Handler{routeStore: mStore} - ctx := droplet.NewContext() - ctx.SetInput(input) - - ret, err := h.ExportRoutes(ctx) - assert.Nil(t, err) - ret1, err := json.Marshal(ret) - assert.Nil(t, err) - assert.Equal(t, replaceStr(exportR1), string(ret1)) - assert.NotNil(t, ret1) -} - -// 2.Export data as the route of URI host -func TestExportRoutes2(t *testing.T) { - input := &ExportInput{IDs: "1"} - //*entity.Route - r2 := `{ - "name": "aaaa2", - "labels": { - "build":"16", - "env":"production", - "version":"v2" - }, - "plugins": { - "limit-count": { - "count": 2, - "time_window": 60, - "rejected_code": 503, - "key": "remote_addr" - } - }, - "status": 1, - "uri": "/hello2", - "host": "*.bar.com", - "methods": ["GET", "POST"], - "upstream": { - "nodes": { - "172.16.238.20:1980": 1 - }, - "type": "roundrobin" - } - }` - - exportR2 := `{ - "components": {}, - "info": { - "title": "RoutesExport", - "version": "3.0.0" - }, - "openapi": "3.0.0", - "paths": { - "/hello2": { - "get": { - "operationId": "aaaa2GET", - "requestBody": {}, - "responses": { - "default": { - "description": "" - } - }, - "security": [], - "x-apisix-enable_websocket": false, - "x-apisix-host": "*.bar.com", - "x-apisix-labels": { - "build": "16", - "env": "production", - "version": "v2" - }, - "x-apisix-plugins": { - "limit-count": { - "count": 2, - "key": "remote_addr", - "rejected_code": 503, - "time_window": 60 - } - }, - "x-apisix-priority": 0, - "x-apisix-status": 1, - "x-apisix-upstream": { - "nodes": { - "172.16.238.20:1980": 1 - }, - "type": "roundrobin" - } - }, - "post": { - "operationId": "aaaa2POST", - "requestBody": {}, - "responses": { - "default": { - "description": "" - } - }, - "security": [], - "x-apisix-enable_websocket": false, - "x-apisix-host": "*.bar.com", - "x-apisix-labels": { - "build": "16", - "env": "production", - "version": "v2" - }, - "x-apisix-plugins": { - "limit-count": { - "count": 2, - "key": "remote_addr", - "rejected_code": 503, - "time_window": 60 - } - }, - "x-apisix-priority": 0, - "x-apisix-status": 1, - "x-apisix-upstream": { - "nodes": { - "172.16.238.20:1980": 1 - }, - "type": "roundrobin" - } - } - } - } - }` - var route *entity.Route - err := json.Unmarshal([]byte(r2), &route) - mStore := &store.MockInterface{} - mStore.On("Get", mock.Anything).Run(func(args mock.Arguments) { - }).Return(route, nil) - - h := Handler{routeStore: mStore} - ctx := droplet.NewContext() - ctx.SetInput(input) - - ret, err := h.ExportRoutes(ctx) - assert.Nil(t, err) - ret1, err := json.Marshal(ret) - assert.Nil(t, err) - assert.Equal(t, replaceStr(exportR2), string(ret1)) - assert.NotNil(t, ret1) -} - -// 3.Create a service that contains complete data and use the service_id create route -func TestExportRoutesCreateByServiceId(t *testing.T) { - input := &ExportInput{IDs: "1"} - //*entity.Route - r := `{ - "methods": ["GET"], - "uri": "/hello", - "service_id": "s1" - }` - - s := `{ - "id": "s1", - "name": "testservice", - "desc": "testservice_desc", - "labels": { - "build":"16", - "env":"production", - "version":"v2" - }, - "enable_websocket":true, - "plugins": { - "limit-count": { - "count": 100, - "time_window": 60, - "rejected_code": 503, - "key": "remote_addr" - } - }, - "upstream": { - "type": "roundrobin", - "nodes": [{ - "host": "172.16.238.20", - "port": 1980, - "weight": 1 - }] - } - }` - - exportR := `{ - "components": {}, - "info": { - "title": "RoutesExport", - "version": "3.0.0" - }, - "openapi": "3.0.0", - "paths": { - "/hello": { - "get": { - "operationId": "GET", - "requestBody": {}, - "responses": { - "default": { - "description": "" - } - }, - "x-apisix-enable_websocket": false, - "x-apisix-labels": { - "build": "16", - "env": "production", - "version": "v2" - }, - "x-apisix-plugins": { - "limit-count": { - "count": 100, - "key": "remote_addr", - "rejected_code": 503, - "time_window": 60 - } - }, - "x-apisix-priority": 0, - "x-apisix-status": 0, - "x-apisix-upstream": { - "nodes": [{ - "host": "172.16.238.20", - "port": 1980, - "weight": 1 - }], - "type": "roundrobin" - } - } - } - } - }` - var route *entity.Route - var service *entity.Service - err := json.Unmarshal([]byte(r), &route) - err = json.Unmarshal([]byte(s), &service) - mStore := &store.MockInterface{} - mStore.On("Get", mock.Anything).Run(func(args mock.Arguments) { - }).Return(route, nil) - mStoreService := &store.MockInterface{} - mStoreService.On("Get", mock.Anything).Run(func(args mock.Arguments) { - }).Return(service, nil) - - h := Handler{routeStore: mStore, serviceStore: mStoreService} - ctx := droplet.NewContext() - ctx.SetInput(input) - - ret, err := h.ExportRoutes(ctx) - assert.Nil(t, err) - _ret, err := json.Marshal(ret) - assert.Nil(t, err) - assert.Equal(t, replaceStr(exportR), string(_ret)) - assert.NotNil(t, _ret) -} - -// 4.Create a service containing plugin and a route containing plugin to test the fusion of exported data -func TestExportRoutesCreateByServiceId2(t *testing.T) { - input := &ExportInput{IDs: "1"} - //*entity.Route - r := `{ - "methods": ["GET"], - "uri": "/hello", - "service_id": "s1", - "enable_websocket":false, - "plugins": { - "prometheus": { - "disable": false - } - }, - "upstream": { - "type": "roundrobin", - "nodes": [{ - "host": "172.16.238.20", - "port": 1980, - "weight": 1 - }] - } - }` - - s := `{ - "id": "s1", - "name": "testservice", - "desc": "testservice_desc", - "labels": { - "build":"16", - "env":"production", - "version":"v2" - }, - "enable_websocket":true, - "plugins": { - "limit-count": { - "count": 100, - "time_window": 60, - "rejected_code": 503, - "key": "remote_addr" - } - }, - "upstream": { - "type": "roundrobin", - "nodes": [{ - "host": "172.16.238.20", - "port": 1980, - "weight": 1 - }] - } - }` - - exportR := `{ - "components": {}, - "info": { - "title": "RoutesExport", - "version": "3.0.0" - }, - "openapi": "3.0.0", - "paths": { - "/hello": { - "get": { - "operationId": "GET", - "requestBody": {}, - "responses": { - "default": { - "description": "" - } - }, - "security": [], - "x-apisix-enable_websocket": false, - "x-apisix-labels": { - "build": "16", - "env": "production", - "version": "v2" - }, - "x-apisix-plugins": { - "limit-count": { - "count": 100, - "key": "remote_addr", - "rejected_code": 503, - "time_window": 60 - }, - "prometheus": { - "disable": false - } - }, - "x-apisix-priority": 0, - "x-apisix-status": 0, - "x-apisix-upstream": { - "nodes": [{ - "host": "172.16.238.20", - "port": 1980, - "weight": 1 - }], - "type": "roundrobin" - } - } - } - } - }` - var route *entity.Route - var service *entity.Service - err := json.Unmarshal([]byte(r), &route) - err = json.Unmarshal([]byte(s), &service) - mStore := &store.MockInterface{} - mStore.On("Get", mock.Anything).Run(func(args mock.Arguments) { - }).Return(route, nil) - mStoreService := &store.MockInterface{} - mStoreService.On("Get", mock.Anything).Run(func(args mock.Arguments) { - }).Return(service, nil) - - h := Handler{routeStore: mStore, serviceStore: mStoreService} - ctx := droplet.NewContext() - ctx.SetInput(input) - - ret, err := h.ExportRoutes(ctx) - assert.Nil(t, err) - _ret, err := json.Marshal(ret) - assert.Nil(t, err) - assert.Equal(t, replaceStr(exportR), string(_ret)) - assert.NotNil(t, _ret) -} - -// 5.Create a service according to the upstream ID and a route according to the service ID -func TestExportRoutesCreateByServiceId3(t *testing.T) { - input := &ExportInput{IDs: "1"} - us := `{ - "id": "u1", - "nodes": [ - { - "host": "172.16.238.20", - "port": 1980, - "weight": 1 - } - ], - "type": "roundrobin" - }` - - s := `{ - "id": "s1", - "name": "testservice", - "desc": "testservice_desc", - "labels": { - "build":"16", - "env":"production", - "version":"v2" - }, - "enable_websocket":true, - "plugins": { - "limit-count": { - "count": 100, - "time_window": 60, - "rejected_code": 503, - "key": "remote_addr" - } - }, - "upstream_id": "u1" - }` - - r := `{ - "methods": ["GET"], - "uri": "/hello", - "service_id": "s1", - "enable_websocket":false, - "plugins": { - "prometheus": { - "disable": false - } - }, - "upstream": { - "type": "roundrobin", - "nodes": [{ - "host": "172.16.238.20", - "port": 1981, - "weight": 1 - }] - } - }` - - exportR := `{ - "components": {}, - "info": { - "title": "RoutesExport", - "version": "3.0.0" - }, - "openapi": "3.0.0", - "paths": { - "/hello": { - "get": { - "operationId": "GET", - "requestBody": {}, - "responses": { - "default": { - "description": "" - } - }, - "security": [], - "x-apisix-enable_websocket": false, - "x-apisix-labels": { - "build": "16", - "env": "production", - "version": "v2" - }, - "x-apisix-plugins": { - "limit-count": { - "count": 100, - "key": "remote_addr", - "rejected_code": 503, - "time_window": 60 - }, - "prometheus": { - "disable": false - } - }, - "x-apisix-priority": 0, - "x-apisix-status": 0, - "x-apisix-upstream": { - "nodes": [{ - "host": "172.16.238.20", - "port": 1981, - "weight": 1 - }], - "type": "roundrobin" - } - } - } - } - }` - var route *entity.Route - var service *entity.Service - var upstream *entity.Upstream - err := json.Unmarshal([]byte(r), &route) - err = json.Unmarshal([]byte(s), &service) - err = json.Unmarshal([]byte(us), &upstream) - - mStore := &store.MockInterface{} - mStore.On("Get", mock.Anything).Run(func(args mock.Arguments) { - }).Return(route, nil) - - mStoreService := &store.MockInterface{} - mStoreService.On("Get", mock.Anything).Run(func(args mock.Arguments) { - }).Return(service, nil) - - mStoreUpstream := &store.MockInterface{} - mStoreUpstream.On("Get", mock.Anything).Run(func(args mock.Arguments) { - }).Return(upstream, nil) - - h := Handler{routeStore: mStore, serviceStore: mStoreService, upstreamStore: mStoreUpstream} - ctx := droplet.NewContext() - ctx.SetInput(input) - - ret, err := h.ExportRoutes(ctx) - assert.Nil(t, err) - _ret, err := json.Marshal(ret) - assert.Nil(t, err) - assert.Equal(t, replaceStr(exportR), string(_ret)) - assert.NotNil(t, _ret) -} - -// 6.Create and export route according to upstream ID -func TestExportRoutesCreateByUpstreamId(t *testing.T) { - input := &ExportInput{IDs: "1"} - us := `{ - "id": "u1", - "nodes": [ - { - "host": "172.16.238.20", - "port": 1980, - "weight": 1 - } - ], - "type": "roundrobin" - }` - - r := `{ - "methods": ["GET"], - "uri": "/hello", - "enable_websocket":false, - "plugins": { - "prometheus": { - "disable": false - } - }, - "upstream_id": "u1" - }` - - exportR := `{ - "components": {}, - "info": { - "title": "RoutesExport", - "version": "3.0.0" - }, - "openapi": "3.0.0", - "paths": { - "/hello": { - "get": { - "operationId": "GET", - "requestBody": {}, - "responses": { - "default": { - "description": "" - } - }, - "security": [], - "x-apisix-enable_websocket": false, - "x-apisix-plugins": { - "prometheus": { - "disable": false - } - }, - "x-apisix-priority": 0, - "x-apisix-status": 0, - "x-apisix-upstream": { - "id": "u1", - "nodes": [{ - "host": "172.16.238.20", - "port": 1980, - "weight": 1 - }], - "type": "roundrobin" - } - } - } - } - }` - var route *entity.Route - var upstream *entity.Upstream - err := json.Unmarshal([]byte(r), &route) - err = json.Unmarshal([]byte(us), &upstream) - - mStore := &store.MockInterface{} - mStore.On("Get", mock.Anything).Run(func(args mock.Arguments) { - }).Return(route, nil) - - mStoreUpstream := &store.MockInterface{} - mStoreUpstream.On("Get", mock.Anything).Run(func(args mock.Arguments) { - }).Return(upstream, nil) - - h := Handler{routeStore: mStore, upstreamStore: mStoreUpstream} - ctx := droplet.NewContext() - ctx.SetInput(input) - - ret, err := h.ExportRoutes(ctx) - assert.Nil(t, err) - _ret, err := json.Marshal(ret) - assert.Nil(t, err) - assert.Equal(t, replaceStr(exportR), string(_ret)) - assert.NotNil(t, _ret) -} - -// 7.Create route according to upstream ID and service ID -func TestExportRoutesCreateByUpstreamIdandServiceId(t *testing.T) { - input := &ExportInput{IDs: "1"} - us := `{ - "id": "u1", - "nodes": [ - { - "host": "172.16.238.20", - "port": 1980, - "weight": 1 - } - ], - "type": "roundrobin" - }` - - s := `{ - "id": "s1", - "name": "testservice", - "desc": "testservice_desc", - "labels": { - "build":"16", - "env":"production", - "version":"v2" - }, - "enable_websocket":true, - "plugins": { - "limit-count": { - "count": 100, - "time_window": 60, - "rejected_code": 503, - "key": "remote_addr" - } - }, - "upstream_id": "u1" - }` - - r := `{ - "name": "route_all", - "desc": "ๆ‰€ๆœ‰", - "status": 1, - "methods": ["GET"], - "priority": 0, - "service_id": "s1", - "labels": { - "test": "1", - "API_VERSION": "v1" - }, - "vars": [ - ["arg_name", "==", "test"] - ], - "uri": "/hello", - "enable_websocket":false, - "plugins": { - "prometheus": { - "disable": false - } - }, - "upstream_id": "u1" - }` - - exportR := `{ - "components": {}, - "info": { - "title": "RoutesExport", - "version": "3.0.0" - }, - "openapi": "3.0.0", - "paths": { - "/hello": { - "get": { - "operationId": "route_allGET", - "requestBody": {}, - "responses": { - "default": { - "description": "" - } - }, - "security": [], - "summary": "ๆ‰€ๆœ‰", - "x-apisix-enable_websocket": false, - "x-apisix-labels": { - "API_VERSION": "v1", - "test": "1" - }, - "x-apisix-plugins": { - "limit-count": { - "count": 100, - "key": "remote_addr", - "rejected_code": 503, - "time_window": 60 - }, - "prometheus": { - "disable": false - } - }, - "x-apisix-priority": 0, - "x-apisix-status": 1, - "x-apisix-upstream": { - "id": "u1", - "nodes": [{ - "host": "172.16.238.20", - "port": 1980, - "weight": 1 - }], - "type": "roundrobin" - }, - "x-apisix-vars": [ - ["arg_name", "==", "test"] - ] - } - } - } - }` - var route *entity.Route - var service *entity.Service - var upstream *entity.Upstream - err := json.Unmarshal([]byte(r), &route) - err = json.Unmarshal([]byte(s), &service) - err = json.Unmarshal([]byte(us), &upstream) - - mStore := &store.MockInterface{} - mStore.On("Get", mock.Anything).Run(func(args mock.Arguments) { - }).Return(route, nil) - - mStoreUpstream := &store.MockInterface{} - mStoreUpstream.On("Get", mock.Anything).Run(func(args mock.Arguments) { - }).Return(upstream, nil) - - mStoreService := &store.MockInterface{} - mStoreService.On("Get", mock.Anything).Run(func(args mock.Arguments) { - }).Return(service, nil) - - h := Handler{routeStore: mStore, upstreamStore: mStoreUpstream, serviceStore: mStoreService} - ctx := droplet.NewContext() - ctx.SetInput(input) - - ret, err := h.ExportRoutes(ctx) - assert.Nil(t, err) - _ret, err := json.Marshal(ret) - assert.Nil(t, err) - assert.Equal(t, replaceStr(exportR), string(_ret)) - assert.NotNil(t, _ret) -} - -// 8.Creating route using service ID does not contain upstream data -func TestExportRoutesCreateByServiceIdNoUpstream(t *testing.T) { - input := &ExportInput{IDs: "1"} - us := `{ - "id": "u1", - "nodes": [ - { - "host": "172.16.238.20", - "port": 1980, - "weight": 1 - } - ], - "type": "roundrobin" - }` - - s := `{ - "name": "testservice", - "desc": "testservice_desc", - "enable_websocket":true, - "upstream_id": "6" - }` - - r := `{ - "name": "route_all", - "desc": "ๆ‰€ๆœ‰", - "status": 1, - "methods": ["GET"], - "priority": 0, - "service_id": "s5", - "labels": { - "test": "1", - "API_VERSION": "v1" - }, - "vars": [ - ["arg_name", "==", "test"] - ], - "uri": "/hello", - "enable_websocket":false, - "plugins": { - "prometheus": { - "disable": false - } - } - }` - - exportR := `{ - "components": {}, - "info": { - "title": "RoutesExport", - "version": "3.0.0" - }, - "openapi": "3.0.0", - "paths": { - "/hello": { - "get": { - "operationId": "route_allGET", - "requestBody": {}, - "responses": { - "default": { - "description": "" - } - }, - "security": [], - "summary": "ๆ‰€ๆœ‰", - "x-apisix-enable_websocket": false, - "x-apisix-labels": { - "API_VERSION": "v1", - "test": "1" - }, - "x-apisix-plugins": { - "prometheus": { - "disable": false - } - }, - "x-apisix-priority": 0, - "x-apisix-status": 1, - "x-apisix-upstream": { - "id": "u1", - "nodes": [{ - "host": "172.16.238.20", - "port": 1980, - "weight": 1 - }], - "type": "roundrobin" - }, - "x-apisix-vars": [ - ["arg_name", "==", "test"] - ] - } - } - } - }` - var route *entity.Route - var service *entity.Service - var upstream *entity.Upstream - err := json.Unmarshal([]byte(r), &route) - err = json.Unmarshal([]byte(s), &service) - err = json.Unmarshal([]byte(us), &upstream) - - mStore := &store.MockInterface{} - mStore.On("Get", mock.Anything).Run(func(args mock.Arguments) { - }).Return(route, nil) - - mStoreUpstream := &store.MockInterface{} - mStoreUpstream.On("Get", mock.Anything).Run(func(args mock.Arguments) { - }).Return(upstream, nil) - - mStoreService := &store.MockInterface{} - mStoreService.On("Get", mock.Anything).Run(func(args mock.Arguments) { - }).Return(service, nil) - - h := Handler{routeStore: mStore, upstreamStore: mStoreUpstream, serviceStore: mStoreService} - ctx := droplet.NewContext() - ctx.SetInput(input) - - ret, err := h.ExportRoutes(ctx) - assert.Nil(t, err) - _ret, err := json.Marshal(ret) - assert.Nil(t, err) - assert.Equal(t, replaceStr(exportR), string(_ret)) - assert.NotNil(t, _ret) -} - -// 9.Create a service with label data and a route with label data, and export the route. -// Label is the original data of the route -func TestExportRoutesCreateByLabel(t *testing.T) { - input := &ExportInput{IDs: "1"} - s := `{ - "name": "testservice", - "desc": "testservice_desc", - "enable_websocket":true, - "labels": { - "build": "10" - } - }` - - r := `{ - "name": "route_all", - "desc": "ๆ‰€ๆœ‰", - "status": 1, - "methods": ["GET"], - "service_id": "s1", - "labels": { - "test": "1", - "API_VERSION": "v1" - }, - "uri": "/hello", - "enable_websocket":false, - "upstream": { - "nodes": { - "172.16.238.20:1980": 1 - }, - "type": "roundrobin" - } - }` - - exportR := `{ - "components": {}, - "info": { - "title": "RoutesExport", - "version": "3.0.0" - }, - "openapi": "3.0.0", - "paths": { - "/hello": { - "get": { - "operationId": "route_allGET", - "requestBody": {}, - "responses": { - "default": { - "description": "" - } - }, - "summary": "ๆ‰€ๆœ‰", - "x-apisix-enable_websocket": false, - "x-apisix-labels": { - "API_VERSION": "v1", - "test": "1" - }, - "x-apisix-priority": 0, - "x-apisix-status": 1, - "x-apisix-upstream": { - "nodes": { - "172.16.238.20:1980": 1 - }, - "type": "roundrobin" - } - } - } - } - }` - var route *entity.Route - var service *entity.Service - err := json.Unmarshal([]byte(r), &route) - err = json.Unmarshal([]byte(s), &service) - - mStore := &store.MockInterface{} - mStore.On("Get", mock.Anything).Run(func(args mock.Arguments) { - }).Return(route, nil) - - mStoreService := &store.MockInterface{} - mStoreService.On("Get", mock.Anything).Run(func(args mock.Arguments) { - }).Return(service, nil) - - h := Handler{routeStore: mStore, serviceStore: mStoreService} - ctx := droplet.NewContext() - ctx.SetInput(input) - - ret, err := h.ExportRoutes(ctx) - assert.Nil(t, err) - _ret, err := json.Marshal(ret) - assert.Nil(t, err) - assert.Equal(t, replaceStr(exportR), string(_ret)) - assert.NotNil(t, _ret) -} - -// 10.Create a service with label data and a route without label data, and export the route. -// Label is the data of the service -func TestExportRoutesCreateByLabel2(t *testing.T) { - input := &ExportInput{IDs: "1"} - s := `{ - "name": "testservice", - "desc": "testservice_desc", - "enable_websocket":true, - "labels": { - "build": "16", - "env": "production", - "version": "v2" - } - }` - - r := `{ - "name": "route_all", - "desc": "ๆ‰€ๆœ‰", - "status": 1, - "methods": ["GET"], - "service_id": "s2", - "vars": [ - ["arg_name", "==", "test"] - ], - "uri": "/hello" - }` - - exportR := `{ - "components": {}, - "info": { - "title": "RoutesExport", - "version": "3.0.0" - }, - "openapi": "3.0.0", - "paths": { - "/hello": { - "get": { - "operationId": "route_allGET", - "requestBody": {}, - "responses": { - "default": { - "description": "" - } - }, - "summary": "ๆ‰€ๆœ‰", - "x-apisix-enable_websocket": false, - "x-apisix-labels": { - "build": "16", - "env": "production", - "version": "v2" - }, - "x-apisix-priority": 0, - "x-apisix-status": 1, - "x-apisix-vars": [ - ["arg_name", "==", "test"] - ] - } - } - } - }` - var route *entity.Route - var service *entity.Service - err := json.Unmarshal([]byte(r), &route) - err = json.Unmarshal([]byte(s), &service) - - mStore := &store.MockInterface{} - mStore.On("Get", mock.Anything).Run(func(args mock.Arguments) { - }).Return(route, nil) - - mStoreService := &store.MockInterface{} - mStoreService.On("Get", mock.Anything).Run(func(args mock.Arguments) { - }).Return(service, nil) - - h := Handler{routeStore: mStore, serviceStore: mStoreService} - ctx := droplet.NewContext() - ctx.SetInput(input) - - ret, err := h.ExportRoutes(ctx) - assert.Nil(t, err) - _ret, err := json.Marshal(ret) - assert.Nil(t, err) - assert.Equal(t, replaceStr(exportR), string(_ret)) - assert.NotNil(t, _ret) -} - -// 11.Test export route request_ validation data correctness -func TestExportRoutesCreateByRequestValidation(t *testing.T) { - input := &ExportInput{IDs: "1"} - r := `{ - "uris": ["/test-test"], - "name": "route_all", - "desc": "ๆ‰€ๆœ‰", - "methods": ["GET"], - "hosts": ["test.com"], - "plugins": { - "request-validation": { - "body_schema": { - "properties": { - "boolean_payload": { - "type": "boolean" - }, - "required_payload": { - "type": "string" - } - }, - "required": ["required_payload"], - "type": "object" - }, - "disable": false, - "header_schema": { - "properties": { - "test": { - "enum": "test-enum", - "type": "string" - } - }, - "type": "string" - } - } - }, - "status": 1 - }` - - exportR := `{ - "components": {}, - "info": { - "title": "RoutesExport", - "version": "3.0.0" - }, - "openapi": "3.0.0", - "paths": { - "/test-test": { - "get": { - "operationId": "route_allGET", - "parameters": [{ - "in": "header", - "name": "test", - "schema": { - "type": "string" - } - }], - "requestBody": { - "content": { - "*/*": { - "schema": { - "properties": { - "boolean_payload": { - "type": "boolean" - }, - "required_payload": { - "type": "string" - } - }, - "required": ["required_payload"], - "type": "object" - } - } - } - }, - "responses": { - "default": { - "description": "" - } - }, - "security": [], - "summary": "ๆ‰€ๆœ‰", - "x-apisix-enable_websocket": false, - "x-apisix-hosts": ["test.com"], - "x-apisix-priority": 0, - "x-apisix-status": 1 - } - } - } - }` - var route *entity.Route - err := json.Unmarshal([]byte(r), &route) - - mStore := &store.MockInterface{} - mStore.On("Get", mock.Anything).Run(func(args mock.Arguments) { - }).Return(route, nil) - - h := Handler{routeStore: mStore} - ctx := droplet.NewContext() - ctx.SetInput(input) - - ret, err := h.ExportRoutes(ctx) - assert.Nil(t, err) - _ret, err := json.Marshal(ret) - assert.Nil(t, err) - assert.Equal(t, replaceStr(exportR), string(_ret)) - assert.NotNil(t, _ret) -} - -// 12.Export route create by jwt-auth plugin -func TestExportRoutesCreateByJWTAuth(t *testing.T) { - input := &ExportInput{IDs: "1"} - r := `{ - "uri": "/hello", - "methods": ["Get"], - "plugins": { - "jwt-auth": {} - }, - "upstream": { - "type": "roundrobin", - "nodes": [{ - "host": "172.16.238.20", - "port": 1980, - "weight": 1 - }] - } - }` - - c := `{ - "username": "jack", - "plugins": { - "jwt-auth": { - "key": "user-key", - "secret": "my-secret-key", - "algorithm": "HS256" - } - }, - "desc": "test description" - }` - - exportR := `{ - "components": { - "securitySchemes": { - "bearerAuth": { - "bearerFormat": "JWT", - "scheme": "bearer", - "type": "http" - } - } - }, - "info": { - "title": "RoutesExport", - "version": "3.0.0" - }, - "openapi": "3.0.0", - "paths": { - "/hello": { - "get": { - "operationId": "GET", - "requestBody": {}, - "responses": { - "default": { - "description": "" - } - }, - "security": [{ - "bearerAuth": [""] - }], - "x-apisix-enable_websocket": false, - "x-apisix-priority": 0, - "x-apisix-status": 0, - "x-apisix-upstream": { - "nodes": [{ - "host": "172.16.238.20", - "port": 1980, - "weight": 1 - }], - "type": "roundrobin" - } - } - } - } - }` - - var route *entity.Route - var consumer *entity.Consumer - err := json.Unmarshal([]byte(r), &route) - err = json.Unmarshal([]byte(c), &consumer) - - mStore := &store.MockInterface{} - mStore.On("Get", mock.Anything).Run(func(args mock.Arguments) { - }).Return(route, nil) - - mStoreConsumer := &store.MockInterface{} - mStoreConsumer.On("Get", mock.Anything).Run(func(args mock.Arguments) { - }).Return(consumer, nil) - - h := Handler{routeStore: mStore, consumerStore: mStoreConsumer} - ctx := droplet.NewContext() - ctx.SetInput(input) - - ret, err := h.ExportRoutes(ctx) - assert.Nil(t, err) - _ret, err := json.Marshal(ret) - assert.Nil(t, err) - assert.Equal(t, replaceStr(exportR), strings.Replace(string(_ret), " ", "", -1)) - assert.NotNil(t, _ret) -} - -// 13.Export route create by apikey-auth plugin basic-auth plugin -func TestExportRoutesCreateByKeyAuthAndBasicAuth(t *testing.T) { - input := &ExportInput{IDs: "1"} - r := `{ - "uri": "/hello", - "methods": ["Get"], - "plugins": { - "key-auth": {}, - "basic-auth": {} - }, - "upstream": { - "type": "roundrobin", - "nodes": [{ - "host": "172.16.238.20", - "port": 1980, - "weight": 1 - }] - } - }` - - c := `{ - "username": "jack", - "plugins": { - "key-auth": { - "key": "auth-one" - }, - "basic-auth": { - "username": "jack", - "password": "123456" - } - }, - "desc": "test description" - }` - - exportR := `{ - "components": { - "securitySchemes": { - "api_key": { - "in": "header", - "name": "X-XSRF-TOKEN", - "type": "apiKey" - }, - "basicAuth": { - "in": "header", - "name": "basicAuth", - "type": "basicAuth" - } - } - }, - "info": { - "title": "RoutesExport", - "version": "3.0.0" - }, - "openapi": "3.0.0", - "paths": { - "/hello": { - "get": {` - - var route *entity.Route - var consumer *entity.Consumer - err := json.Unmarshal([]byte(r), &route) - err = json.Unmarshal([]byte(c), &consumer) - - mStore := &store.MockInterface{} - mStore.On("Get", mock.Anything).Run(func(args mock.Arguments) { - }).Return(route, nil) - - mStoreConsumer := &store.MockInterface{} - mStoreConsumer.On("Get", mock.Anything).Run(func(args mock.Arguments) { - }).Return(consumer, nil) - - h := Handler{routeStore: mStore, consumerStore: mStoreConsumer} - ctx := droplet.NewContext() - ctx.SetInput(input) - - ret, err := h.ExportRoutes(ctx) - assert.Nil(t, err) - _ret, err := json.Marshal(ret) - assert.Nil(t, err) - assert.NotNil(t, _ret) - assert.Contains(t, strings.Replace(string(_ret), " ", "", -1), replaceStr(exportR)) -} - -// 14.Export all routes -func TestExportRoutesAll(t *testing.T) { - input := &store.ListInput{} - //*entity.Route - r1 := `{ - "name": "aaaa", - "status": 1, - "uri": "/hello_", - "host": "*.bar.com", - "methods": [ "POST"], - "upstream": { - "nodes": { - "172.16.238.20:1980": 1 - }, - "type": "roundrobin" - } - }` - - r2 := `{ - "name": "aaaa2", - "status": 1, - "uris": ["/hello_2"], - "hosts": ["foo.com", "*.bar.com"], - "methods": ["GET"], - "upstream": { - "nodes": { - "172.16.238.20:1980": 1 - }, - "type": "roundrobin" - } - }` - - exportR1 := `{ - "components": {}, - "info": { - "title": "RoutesExport", - "version": "3.0.0" - }, - "openapi": "3.0.0", - "paths": { - "/hello_": { - "post": { - "operationId": "aaaaPOST", - "requestBody": {}, - "responses": { - "default": { - "description": "" - } - }, - "x-apisix-enable_websocket": false, - "x-apisix-host": "*.bar.com", - "x-apisix-priority": 0, - "x-apisix-status": 1, - "x-apisix-upstream": { - "nodes": { - "172.16.238.20:1980": 1 - }, - "type": "roundrobin" - } - } - }, - "/hello_2": { - "get": { - "operationId": "aaaa2GET", - "requestBody": {}, - "responses": { - "default": { - "description": "" - } - }, - "x-apisix-enable_websocket": false, - "x-apisix-hosts": ["foo.com", "*.bar.com"], - "x-apisix-priority": 0, - "x-apisix-status": 1, - "x-apisix-upstream": { - "nodes": { - "172.16.238.20:1980": 1 - }, - "type": "roundrobin" - } - } - } - } - }` - var route *entity.Route - var route2 *entity.Route - var routes []*entity.Route - err := json.Unmarshal([]byte(r1), &route) - err = json.Unmarshal([]byte(r2), &route2) - mStore := &store.MockInterface{} - getCalled := false - - routes = append(routes, route) - routes = append(routes, route2) - - mStore.On("List", mock.Anything).Run(func(args mock.Arguments) { - getCalled = true - }).Return(func(input store.ListInput) *store.ListOutput { - var returnData []interface{} - for _, c := range routes { - returnData = append(returnData, c) - } - return &store.ListOutput{ - Rows: returnData, - TotalSize: len(returnData), - } - }, nil) - - h := Handler{routeStore: mStore} - ctx := droplet.NewContext() - ctx.SetInput(input) - - ret, err := h.ExportAllRoutes(ctx) - assert.Nil(t, err) - ret1, err := json.Marshal(ret) - assert.Nil(t, err) - assert.Equal(t, replaceStr(exportR1), strings.Replace(string(ret1), " ", "", -1)) - assert.NotNil(t, ret1) - assert.True(t, getCalled) -} - -//15.Create service according to upstream1 ID -// Create route according to upstream2 ID and service ID -func TestExportRoutesCreateByUpstreamIDAndServiceID2(t *testing.T) { - input := &ExportInput{IDs: "1"} - us := `{ - "id": "u1", - "nodes": [ - { - "host": "172.16.238.20", - "port": 1980, - "weight": 1 - } - ], - "type": "roundrobin" - }` - - us2 := `{ - "id": "u2", - "nodes": [ - { - "host": "172.16.238.20", - "port": 1981, - "weight": 1 - } - ], - "type": "roundrobin" - }` - - s := `{ - "id": "s1", - "name": "testservice", - "desc": "testservice_desc", - "enable_websocket":true, - "upstream_id": "u2" - }` - - r := `{ - "name": "route_all", - "desc": "ๆ‰€ๆœ‰", - "status": 1, - "methods": ["GET"], - "priority": 0, - "service_id": "s1", - "labels": { - "test": "1", - "API_VERSION": "v1" - }, - "vars": [ - ["arg_name", "==", "test"] - ], - "uri": "/hello", - "enable_websocket":false, - "plugins": { - "prometheus": { - "disable": false - } - }, - "upstream_id": "u1" - }` - - exportR := `{ - "components": {}, - "info": { - "title": "RoutesExport", - "version": "3.0.0" - }, - "openapi": "3.0.0", - "paths": { - "/hello": { - "get": { - "operationId": "route_allGET", - "requestBody": {}, - "responses": { - "default": { - "description": "" - } - }, - "security": [], - "summary": "ๆ‰€ๆœ‰", - "x-apisix-enable_websocket": false, - "x-apisix-labels": { - "API_VERSION": "v1", - "test": "1" - }, - "x-apisix-plugins": { - "prometheus": { - "disable": false - } - }, - "x-apisix-priority": 0, - "x-apisix-status": 1, - "x-apisix-upstream": { - "id": "u1", - "nodes": [{ - "host": "172.16.238.20", - "port": 1980, - "weight": 1 - }], - "type": "roundrobin" - }, - "x-apisix-vars": [ - ["arg_name", "==", "test"] - ] - } - } - } - }` - var route *entity.Route - var service *entity.Service - var upstream *entity.Upstream - var upstream2 *entity.Upstream - var upstreams []*entity.Upstream - - err := json.Unmarshal([]byte(r), &route) - err = json.Unmarshal([]byte(s), &service) - err = json.Unmarshal([]byte(us), &upstream) - err = json.Unmarshal([]byte(us2), &upstream2) - - mStore := &store.MockInterface{} - mStore.On("Get", mock.Anything).Run(func(args mock.Arguments) { - }).Return(route, nil) - - upstreams = append(upstreams, upstream) - upstreams = append(upstreams, upstream2) - getCalled := true - - mStoreUpstream := &store.MockInterface{} - mStoreUpstream.On("Get", mock.Anything).Run(func(args mock.Arguments) { - }).Return(upstream, nil) - - mStoreUpstream.On("List", mock.Anything).Run(func(args mock.Arguments) { - getCalled = true - }).Return(func(input store.ListInput) *store.ListOutput { - var returnData []interface{} - for _, c := range upstreams { - returnData = append(returnData, c) - } - return &store.ListOutput{ - Rows: returnData, - TotalSize: len(returnData), - } - }, nil) - - mStoreService := &store.MockInterface{} - mStoreService.On("Get", mock.Anything).Run(func(args mock.Arguments) { - }).Return(service, nil) - - h := Handler{routeStore: mStore, upstreamStore: mStoreUpstream, serviceStore: mStoreService} - ctx := droplet.NewContext() - ctx.SetInput(input) - - ret, err := h.ExportRoutes(ctx) - assert.Nil(t, err) - _ret, err := json.Marshal(ret) - assert.Nil(t, err) - assert.Equal(t, replaceStr(exportR), string(_ret)) - assert.NotNil(t, _ret) - assert.True(t, getCalled) -} - -// 16.Add suffix when testing the same URI export "APISIX-REPEAT-URI-" + Millisecond time stamp: "APISIX-REPEAT-URI-1257894000000" -func TestExportRoutesSameURI(t *testing.T) { - input := &store.ListInput{} - //*entity.Route - r1 := `{ - "uris": ["/test-test"], - "name": "route_all", - "desc": "ๆ‰€ๆœ‰", - "methods": ["GET"], - "hosts": ["test.com"], - "status": 1, - "upstream": { - "nodes": { - "172.16.238.20:1980": 1 - }, - "type": "roundrobin" - } -}` - - r2 := `{ - "uris": ["/test-test"], - "name": "route_all", - "desc": "ๆ‰€ๆœ‰1", - "methods": ["GET"], - "hosts": ["test.com"], - "status": 1, - "upstream": { - "nodes": { - "172.16.238.20:1980": 1 - }, - "type": "roundrobin" - } -}` - - r3 := `{ - "uris": ["/test-test"], - "name": "route_all", - "desc": "ๆ‰€ๆœ‰2", - "methods": ["GET"], - "hosts": ["test.com"], - "status": 1, - "upstream": { - "nodes": { - "172.16.238.20:1981": 1 - }, - "type": "roundrobin" - } -}` - - exportR1 := `{ - "components": {}, - "info": { - "title": "RoutesExport", - "version": "3.0.0" - }, - "openapi": "3.0.0", - "paths": { - "/test-test": { - "get": { - "operationId": "route_allGET", - "requestBody": {}, - "responses": { - "default": { - "description": "" - } - }, - "summary": "ๆ‰€ๆœ‰", - "x-apisix-enable_websocket": false, - "x-apisix-hosts": ["test.com"], - "x-apisix-priority": 0, - "x-apisix-status": 1, - "x-apisix-upstream": { - "nodes": { - "172.16.238.20:1980": 1 - }, - "type": "roundrobin" - } - } - }, - "/test-test-APISIX-REPEAT-URI-2": { - "get": { - "operationId": "route_allGET", - "requestBody": {}, - "responses": { - "default": { - "description": "" - } - }, - "summary": "ๆ‰€ๆœ‰1", - "x-apisix-enable_websocket": false, - "x-apisix-hosts": ["test.com"], - "x-apisix-priority": 0, - "x-apisix-status": 1, - "x-apisix-upstream": { - "nodes": { - "172.16.238.20:1980": 1 - }, - "type": "roundrobin" - } - } - }, - "/test-test-APISIX-REPEAT-URI-3": { - "get": { - "operationId": "route_allGET", - "requestBody": {}, - "responses": { - "default": { - "description": "" - } - }, - "summary": "ๆ‰€ๆœ‰2", - "x-apisix-enable_websocket": false, - "x-apisix-hosts": ["test.com"], - "x-apisix-priority": 0, - "x-apisix-status": 1, - "x-apisix-upstream": { - "nodes": { - "172.16.238.20:1981": 1 - }, - "type": "roundrobin" - } - } - } - } - }` - var route *entity.Route - var route2 *entity.Route - var route3 *entity.Route - var routes []*entity.Route - err := json.Unmarshal([]byte(r1), &route) - err = json.Unmarshal([]byte(r2), &route2) - err = json.Unmarshal([]byte(r3), &route3) - mStore := &store.MockInterface{} - getCalled := false - - routes = append(routes, route) - routes = append(routes, route2) - routes = append(routes, route3) - - mStore.On("List", mock.Anything).Run(func(args mock.Arguments) { - getCalled = true - }).Return(func(input store.ListInput) *store.ListOutput { - var returnData []interface{} - for _, c := range routes { - returnData = append(returnData, c) - } - return &store.ListOutput{ - Rows: returnData, - TotalSize: len(returnData), - } - }, nil) - - h := Handler{routeStore: mStore} - ctx := droplet.NewContext() - ctx.SetInput(input) - - ret, err := h.ExportAllRoutes(ctx) - assert.Nil(t, err) - ret1, err := json.Marshal(ret) - assert.Nil(t, err) - assert.Equal(t, replaceStr(exportR1), string(ret1)) - assert.NotNil(t, ret1) - assert.True(t, getCalled) -} - -func TestExportRoutesParameterEmpty(t *testing.T) { - // Error test when pass parameter is null - h := Handler{} - - exportInput := &ExportInput{} - exportInput.IDs = "" - - ctx := droplet.NewContext() - ctx.SetInput(exportInput) - - _, err1 := h.ExportRoutes(ctx) - assert.Equal(t, errors.New("Parameter IDs cannot be empty"), err1) -} - -func TestExportAllRoutesDataEmpty(t *testing.T) { - // When there is no route data in the database, export route - input := &store.ListInput{} - mStore := &store.MockInterface{} - getCalled := false - - mStore.On("List", mock.Anything).Run(func(args mock.Arguments) { - getCalled = true - }).Return(func(input store.ListInput) *store.ListOutput { - var returnData []interface{} - return &store.ListOutput{ - Rows: returnData, - TotalSize: 0, - } - }, nil) - - h := Handler{routeStore: mStore} - ctx := droplet.NewContext() - ctx.SetInput(input) - - _, err := h.ExportAllRoutes(ctx) - assert.Equal(t, errors.New("Route data is empty, cannot be exported"), err) - assert.True(t, getCalled) -} - -func TestExportRoutesMethodsFeildEmpty(t *testing.T) { - input := &ExportInput{IDs: "1"} - //*entity.Route - r1 := `{ - "uris": ["/test-test"], - "name": "route", - "methods": [], - "hosts": ["test.com"], - "status": 1, - "upstream": { - "nodes": { - "172.16.238.20:1980": 1 - }, - "type": "roundrobin" - } -}` - - exportR1 := `{ - "components": {}, - "info": { - "title": "RoutesExport", - "version": "3.0.0" - }, - "openapi": "3.0.0", - "paths": { - "/test-test": { - "connect": { - "operationId": "routeCONNECT", - "requestBody": {}, - "responses": { - "default": { - "description": "" - } - }, - "x-apisix-enable_websocket": false, - "x-apisix-hosts": ["test.com"], - "x-apisix-priority": 0, - "x-apisix-status": 1, - "x-apisix-upstream": { - "nodes": { - "172.16.238.20:1980": 1 - }, - "type": "roundrobin" - } - }, - "delete": { - "operationId": "routeDELETE", - "requestBody": {}, - "responses": { - "default": { - "description": "" - } - }, - "x-apisix-enable_websocket": false, - "x-apisix-hosts": ["test.com"], - "x-apisix-priority": 0, - "x-apisix-status": 1, - "x-apisix-upstream": { - "nodes": { - "172.16.238.20:1980": 1 - }, - "type": "roundrobin" - } - }, - "get": { - "operationId": "routeGET", - "requestBody": {}, - "responses": { - "default": { - "description": "" - } - }, - "x-apisix-enable_websocket": false, - "x-apisix-hosts": ["test.com"], - "x-apisix-priority": 0, - "x-apisix-status": 1, - "x-apisix-upstream": { - "nodes": { - "172.16.238.20:1980": 1 - }, - "type": "roundrobin" - } - }, - "head": { - "operationId": "routeHEAD", - "requestBody": {}, - "responses": { - "default": { - "description": "" - } - }, - "x-apisix-enable_websocket": false, - "x-apisix-hosts": ["test.com"], - "x-apisix-priority": 0, - "x-apisix-status": 1, - "x-apisix-upstream": { - "nodes": { - "172.16.238.20:1980": 1 - }, - "type": "roundrobin" - } - }, - "options": { - "operationId": "routeOPTIONS", - "requestBody": {}, - "responses": { - "default": { - "description": "" - } - }, - "x-apisix-enable_websocket": false, - "x-apisix-hosts": ["test.com"], - "x-apisix-priority": 0, - "x-apisix-status": 1, - "x-apisix-upstream": { - "nodes": { - "172.16.238.20:1980": 1 - }, - "type": "roundrobin" - } - }, - "patch": { - "operationId": "routePATCH", - "requestBody": {}, - "responses": { - "default": { - "description": "" - } - }, - "x-apisix-enable_websocket": false, - "x-apisix-hosts": ["test.com"], - "x-apisix-priority": 0, - "x-apisix-status": 1, - "x-apisix-upstream": { - "nodes": { - "172.16.238.20:1980": 1 - }, - "type": "roundrobin" - } - }, - "post": { - "operationId": "routePOST", - "requestBody": {}, - "responses": { - "default": { - "description": "" - } - }, - "x-apisix-enable_websocket": false, - "x-apisix-hosts": ["test.com"], - "x-apisix-priority": 0, - "x-apisix-status": 1, - "x-apisix-upstream": { - "nodes": { - "172.16.238.20:1980": 1 - }, - "type": "roundrobin" - } - }, - "put": { - "operationId": "routePUT", - "requestBody": {}, - "responses": { - "default": { - "description": "" - } - }, - "x-apisix-enable_websocket": false, - "x-apisix-hosts": ["test.com"], - "x-apisix-priority": 0, - "x-apisix-status": 1, - "x-apisix-upstream": { - "nodes": { - "172.16.238.20:1980": 1 - }, - "type": "roundrobin" - } - }, - "trace": { - "operationId": "routeTRACE", - "requestBody": {}, - "responses": { - "default": { - "description": "" - } - }, - "x-apisix-enable_websocket": false, - "x-apisix-hosts": ["test.com"], - "x-apisix-priority": 0, - "x-apisix-status": 1, - "x-apisix-upstream": { - "nodes": { - "172.16.238.20:1980": 1 - }, - "type": "roundrobin" - } - } - } - } - }` - var route *entity.Route - err := json.Unmarshal([]byte(r1), &route) - - mStore := &store.MockInterface{} - mStore.On("Get", mock.Anything).Run(func(args mock.Arguments) { - }).Return(route, nil) - h := Handler{routeStore: mStore} - ctx := droplet.NewContext() - ctx.SetInput(input) - - ret, err := h.ExportRoutes(ctx) - assert.Nil(t, err) - ret1, err := json.Marshal(ret) - assert.Nil(t, err) - assert.Equal(t, replaceStr(exportR1), strings.Replace(string(ret1), " ", "", -1)) - assert.NotNil(t, ret1) -} - -func TestExportRoutesMethodsFeildNil(t *testing.T) { - input := &ExportInput{IDs: "1"} - //*entity.Route - r1 := `{ - "uris": ["/test-test"], - "name": "route", - "hosts": ["test.com"], - "status": 1, - "upstream": { - "nodes": { - "172.16.238.20:1980": 1 - }, - "type": "roundrobin" - } -}` - - exportR1 := `{ - "components": {}, - "info": { - "title": "RoutesExport", - "version": "3.0.0" - }, - "openapi": "3.0.0", - "paths": { - "/test-test": { - "connect": { - "operationId": "routeCONNECT", - "requestBody": {}, - "responses": { - "default": { - "description": "" - } - }, - "x-apisix-enable_websocket": false, - "x-apisix-hosts": ["test.com"], - "x-apisix-priority": 0, - "x-apisix-status": 1, - "x-apisix-upstream": { - "nodes": { - "172.16.238.20:1980": 1 - }, - "type": "roundrobin" - } - }, - "delete": { - "operationId": "routeDELETE", - "requestBody": {}, - "responses": { - "default": { - "description": "" - } - }, - "x-apisix-enable_websocket": false, - "x-apisix-hosts": ["test.com"], - "x-apisix-priority": 0, - "x-apisix-status": 1, - "x-apisix-upstream": { - "nodes": { - "172.16.238.20:1980": 1 - }, - "type": "roundrobin" - } - }, - "get": { - "operationId": "routeGET", - "requestBody": {}, - "responses": { - "default": { - "description": "" - } - }, - "x-apisix-enable_websocket": false, - "x-apisix-hosts": ["test.com"], - "x-apisix-priority": 0, - "x-apisix-status": 1, - "x-apisix-upstream": { - "nodes": { - "172.16.238.20:1980": 1 - }, - "type": "roundrobin" - } - }, - "head": { - "operationId": "routeHEAD", - "requestBody": {}, - "responses": { - "default": { - "description": "" - } - }, - "x-apisix-enable_websocket": false, - "x-apisix-hosts": ["test.com"], - "x-apisix-priority": 0, - "x-apisix-status": 1, - "x-apisix-upstream": { - "nodes": { - "172.16.238.20:1980": 1 - }, - "type": "roundrobin" - } - }, - "options": { - "operationId": "routeOPTIONS", - "requestBody": {}, - "responses": { - "default": { - "description": "" - } - }, - "x-apisix-enable_websocket": false, - "x-apisix-hosts": ["test.com"], - "x-apisix-priority": 0, - "x-apisix-status": 1, - "x-apisix-upstream": { - "nodes": { - "172.16.238.20:1980": 1 - }, - "type": "roundrobin" - } - }, - "patch": { - "operationId": "routePATCH", - "requestBody": {}, - "responses": { - "default": { - "description": "" - } - }, - "x-apisix-enable_websocket": false, - "x-apisix-hosts": ["test.com"], - "x-apisix-priority": 0, - "x-apisix-status": 1, - "x-apisix-upstream": { - "nodes": { - "172.16.238.20:1980": 1 - }, - "type": "roundrobin" - } - }, - "post": { - "operationId": "routePOST", - "requestBody": {}, - "responses": { - "default": { - "description": "" - } - }, - "x-apisix-enable_websocket": false, - "x-apisix-hosts": ["test.com"], - "x-apisix-priority": 0, - "x-apisix-status": 1, - "x-apisix-upstream": { - "nodes": { - "172.16.238.20:1980": 1 - }, - "type": "roundrobin" - } - }, - "put": { - "operationId": "routePUT", - "requestBody": {}, - "responses": { - "default": { - "description": "" - } - }, - "x-apisix-enable_websocket": false, - "x-apisix-hosts": ["test.com"], - "x-apisix-priority": 0, - "x-apisix-status": 1, - "x-apisix-upstream": { - "nodes": { - "172.16.238.20:1980": 1 - }, - "type": "roundrobin" - } - }, - "trace": { - "operationId": "routeTRACE", - "requestBody": {}, - "responses": { - "default": { - "description": "" - } - }, - "x-apisix-enable_websocket": false, - "x-apisix-hosts": ["test.com"], - "x-apisix-priority": 0, - "x-apisix-status": 1, - "x-apisix-upstream": { - "nodes": { - "172.16.238.20:1980": 1 - }, - "type": "roundrobin" - } - } - } - } - }` - var route *entity.Route - err := json.Unmarshal([]byte(r1), &route) - - mStore := &store.MockInterface{} - mStore.On("Get", mock.Anything).Run(func(args mock.Arguments) { - }).Return(route, nil) - h := Handler{routeStore: mStore} - ctx := droplet.NewContext() - ctx.SetInput(input) - - ret, err := h.ExportRoutes(ctx) - assert.Nil(t, err) - ret1, err := json.Marshal(ret) - assert.Nil(t, err) - assert.Equal(t, replaceStr(exportR1), strings.Replace(string(ret1), " ", "", -1)) - assert.NotNil(t, ret1) -} - -func replaceStr(str string) string { - str = strings.Replace(str, "\n", "", -1) - str = strings.Replace(str, "\t", "", -1) - str = strings.Replace(str, " ", "", -1) - return str -} diff --git a/api/internal/handler/data_loader/route_import.go b/api/internal/handler/data_loader/route_import.go deleted file mode 100644 index e94dec02de..0000000000 --- a/api/internal/handler/data_loader/route_import.go +++ /dev/null @@ -1,312 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one or more - * contributor license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright ownership. - * The ASF licenses this file to You under the Apache License, Version 2.0 - * (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package data_loader - -import ( - "bytes" - "context" - "fmt" - "path" - "reflect" - - "github.com/gin-gonic/gin" - "github.com/juliangruber/go-intersect" - "github.com/pkg/errors" - "github.com/shiningrush/droplet" - "github.com/shiningrush/droplet/wrapper" - wgin "github.com/shiningrush/droplet/wrapper/gin" - - "github.com/apisix/manager-api/internal/conf" - "github.com/apisix/manager-api/internal/core/entity" - "github.com/apisix/manager-api/internal/core/store" - "github.com/apisix/manager-api/internal/handler" - loader "github.com/apisix/manager-api/internal/handler/data_loader/loader" - "github.com/apisix/manager-api/internal/handler/data_loader/loader/openapi3" -) - -type ImportHandler struct { - routeStore store.Interface - upstreamStore store.Interface - serviceStore store.Interface - consumerStore store.Interface - sslStore store.Interface - streamRouteStore store.Interface - globalPluginStore store.Interface - pluginConfigStore store.Interface - protoStore store.Interface -} - -func NewImportHandler() (handler.RouteRegister, error) { - return &ImportHandler{ - routeStore: store.GetStore(store.HubKeyRoute), - upstreamStore: store.GetStore(store.HubKeyUpstream), - serviceStore: store.GetStore(store.HubKeyService), - consumerStore: store.GetStore(store.HubKeyConsumer), - sslStore: store.GetStore(store.HubKeySsl), - streamRouteStore: store.GetStore(store.HubKeyStreamRoute), - globalPluginStore: store.GetStore(store.HubKeyGlobalRule), - pluginConfigStore: store.GetStore(store.HubKeyPluginConfig), - protoStore: store.GetStore(store.HubKeyProto), - }, nil -} - -func (h *ImportHandler) ApplyRoute(r *gin.Engine) { - r.POST("/apisix/admin/import/routes", wgin.Wraps(h.Import, - wrapper.InputType(reflect.TypeOf(ImportInput{})))) -} - -type ImportResult struct { - Total int `json:"total"` - Failed int `json:"failed"` - Errors []string `json:"errors"` -} - -type LoaderType string - -type ImportInput struct { - Type string `auto_read:"type"` - TaskName string `auto_read:"task_name"` - FileName string `auto_read:"_file"` - FileContent []byte `auto_read:"file"` - - MergeMethod string `auto_read:"merge_method"` -} - -const ( - LoaderTypeOpenAPI3 LoaderType = "openapi3" -) - -func (h *ImportHandler) Import(c droplet.Context) (interface{}, error) { - input := c.Input().(*ImportInput) - - // input file content check - suffix := path.Ext(input.FileName) - if suffix != ".json" && suffix != ".yaml" && suffix != ".yml" { - return nil, errors.Errorf("required file type is .yaml, .yml or .json but got: %s", suffix) - } - contentLen := bytes.Count(input.FileContent, nil) - 1 - if contentLen <= 0 { - return nil, errors.New("uploaded file is empty") - } - if contentLen > conf.ImportSizeLimit { - return nil, errors.Errorf("uploaded file size exceeds the limit, limit is %d", conf.ImportSizeLimit) - } - - var l loader.Loader - switch LoaderType(input.Type) { - case LoaderTypeOpenAPI3: - l = &openapi3.Loader{ - MergeMethod: input.MergeMethod == "true", - TaskName: input.TaskName, - } - break - default: - return nil, fmt.Errorf("unsupported data loader type: %s", input.Type) - } - - dataSets, err := l.Import(input.FileContent) - if err != nil { - return nil, err - } - - // Pre-checking for route duplication - preCheckErrs := h.preCheck(c.Context(), dataSets) - if _, ok := preCheckErrs[store.HubKeyRoute]; ok && len(preCheckErrs[store.HubKeyRoute]) > 0 { - return h.convertToImportResult(dataSets, preCheckErrs), nil - } - - // Create APISIX resources - createErrs := h.createEntities(c.Context(), dataSets) - return h.convertToImportResult(dataSets, createErrs), nil -} - -// Pre-check imported data for duplicates -// The main problem facing duplication is routing, so here -// we mainly check the duplication of routes, based on -// domain name and uri. -func (h *ImportHandler) preCheck(ctx context.Context, data *loader.DataSets) map[store.HubKey][]string { - errs := make(map[store.HubKey][]string) - for _, route := range data.Routes { - errs[store.HubKeyRoute] = make([]string, 0) - o, err := h.routeStore.List(ctx, store.ListInput{ - // The check logic here is that if when a duplicate HOST or URI - // has been found, the HTTP method is checked for overlap, and - // if there is overlap it is determined to be a duplicate route - // and the import is rejected. - Predicate: func(obj interface{}) bool { - r := obj.(*entity.Route) - - // Check URI and host duplication - isURIDuplicated := r.URI != "" && route.URI != "" && r.URI == route.URI - isURIsDuplicated := len(r.Uris) > 0 && len(route.Uris) > 0 && - len(intersect.Hash(r.Uris, route.Uris)) > 0 - isMethodDuplicated := len(intersect.Hash(r.Methods, route.Methods)) > 0 - - // First check for duplicate URIs - if isURIDuplicated || isURIsDuplicated { - // Then check if the host field exists, and if it does, check for duplicates - if r.Host != "" && route.Host != "" { - return r.Host == route.Host && isMethodDuplicated - } else if len(r.Hosts) > 0 && len(route.Hosts) > 0 { - return len(intersect.Hash(r.Hosts, route.Hosts)) > 0 && isMethodDuplicated - } - // If the host field does not exist, only the presence or absence - // of HTTP method duplication is returned by default. - return isMethodDuplicated - } - return false - }, - PageSize: 0, - PageNumber: 0, - }) - if err != nil { - // When a special storage layer error occurs, return directly. - return map[store.HubKey][]string{ - store.HubKeyRoute: {err.Error()}, - } - } - - // Duplicate routes found - if o.TotalSize > 0 { - for _, row := range o.Rows { - r, ok := row.(*entity.Route) - if ok { - errs[store.HubKeyRoute] = append(errs[store.HubKeyRoute], - errors.Errorf("%s is duplicated with route %s", - route.Uris[0], - r.Name). - Error()) - } - } - } - } - - return errs -} - -// Create parsed resources -func (h *ImportHandler) createEntities(ctx context.Context, data *loader.DataSets) map[store.HubKey][]string { - errs := make(map[store.HubKey][]string) - - for _, route := range data.Routes { - _, err := h.routeStore.Create(ctx, &route) - if err != nil { - errs[store.HubKeyRoute] = append(errs[store.HubKeyRoute], err.Error()) - } - } - for _, upstream := range data.Upstreams { - _, err := h.upstreamStore.Create(ctx, &upstream) - if err != nil { - errs[store.HubKeyUpstream] = append(errs[store.HubKeyUpstream], err.Error()) - } - } - for _, service := range data.Services { - _, err := h.serviceStore.Create(ctx, &service) - if err != nil { - errs[store.HubKeyService] = append(errs[store.HubKeyService], err.Error()) - } - } - for _, consumer := range data.Consumers { - _, err := h.consumerStore.Create(ctx, &consumer) - if err != nil { - errs[store.HubKeyConsumer] = append(errs[store.HubKeyConsumer], err.Error()) - } - } - for _, ssl := range data.SSLs { - _, err := h.sslStore.Create(ctx, &ssl) - if err != nil { - errs[store.HubKeySsl] = append(errs[store.HubKeySsl], err.Error()) - } - } - for _, route := range data.StreamRoutes { - _, err := h.streamRouteStore.Create(ctx, &route) - if err != nil { - errs[store.HubKeyStreamRoute] = append(errs[store.HubKeyStreamRoute], err.Error()) - } - } - for _, plugin := range data.GlobalPlugins { - _, err := h.globalPluginStore.Create(ctx, &plugin) - if err != nil { - errs[store.HubKeyGlobalRule] = append(errs[store.HubKeyGlobalRule], err.Error()) - } - } - for _, config := range data.PluginConfigs { - _, err := h.pluginConfigStore.Create(ctx, &config) - if err != nil { - errs[store.HubKeyPluginConfig] = append(errs[store.HubKeyPluginConfig], err.Error()) - } - } - for _, proto := range data.Protos { - _, err := h.protoStore.Create(ctx, &proto) - if err != nil { - errs[store.HubKeyProto] = append(errs[store.HubKeyProto], err.Error()) - } - } - - return errs -} - -// Convert import errors to response result -func (ImportHandler) convertToImportResult(data *loader.DataSets, errs map[store.HubKey][]string) map[store.HubKey]ImportResult { - return map[store.HubKey]ImportResult{ - store.HubKeyRoute: { - Total: len(data.Routes), - Failed: len(errs[store.HubKeyRoute]), - Errors: errs[store.HubKeyRoute], - }, - store.HubKeyUpstream: { - Total: len(data.Upstreams), - Failed: len(errs[store.HubKeyUpstream]), - Errors: errs[store.HubKeyUpstream], - }, - store.HubKeyService: { - Total: len(data.Services), - Failed: len(errs[store.HubKeyService]), - Errors: errs[store.HubKeyService], - }, - store.HubKeyConsumer: { - Total: len(data.Consumers), - Failed: len(errs[store.HubKeyConsumer]), - Errors: errs[store.HubKeyConsumer], - }, - store.HubKeySsl: { - Total: len(data.SSLs), - Failed: len(errs[store.HubKeySsl]), - Errors: errs[store.HubKeySsl], - }, - store.HubKeyStreamRoute: { - Total: len(data.StreamRoutes), - Failed: len(errs[store.HubKeyStreamRoute]), - Errors: errs[store.HubKeyStreamRoute], - }, - store.HubKeyGlobalRule: { - Total: len(data.GlobalPlugins), - Failed: len(errs[store.HubKeyGlobalRule]), - Errors: errs[store.HubKeyGlobalRule], - }, - store.HubKeyPluginConfig: { - Total: len(data.PluginConfigs), - Failed: len(errs[store.HubKeyPluginConfig]), - Errors: errs[store.HubKeyPluginConfig], - }, - store.HubKeyProto: { - Total: len(data.Protos), - Failed: len(errs[store.HubKeyProto]), - Errors: errs[store.HubKeyProto], - }, - } -} diff --git a/api/internal/handler/data_loader/route_import_test.go b/api/internal/handler/data_loader/route_import_test.go deleted file mode 100644 index 548ef1cb24..0000000000 --- a/api/internal/handler/data_loader/route_import_test.go +++ /dev/null @@ -1,65 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one or more - * contributor license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright ownership. - * The ASF licenses this file to You under the Apache License, Version 2.0 - * (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package data_loader - -import ( - "testing" - - "github.com/shiningrush/droplet" - "github.com/stretchr/testify/assert" -) - -func TestImport_invalid_loader(t *testing.T) { - input := &ImportInput{} - input.Type = "test" - input.FileName = "file1.yaml" - input.FileContent = []byte("hello") - - h := ImportHandler{} - ctx := droplet.NewContext() - ctx.SetInput(input) - - _, err := h.Import(ctx) - assert.EqualError(t, err, "unsupported data loader type: test") -} - -func TestImport_openapi3_invalid_file_type(t *testing.T) { - input := &ImportInput{} - input.FileName = "file1.txt" - input.FileContent = []byte("hello") - - h := ImportHandler{} - ctx := droplet.NewContext() - ctx.SetInput(input) - - _, err := h.Import(ctx) - assert.EqualError(t, err, "required file type is .yaml, .yml or .json but got: .txt") -} - -func TestImport_openapi3_invalid_content(t *testing.T) { - input := &ImportInput{} - input.Type = "openapi3" - input.FileName = "file1.json" - input.FileContent = []byte(`{"test": "a"}`) - - h := ImportHandler{} - ctx := droplet.NewContext() - ctx.SetInput(input) - - _, err := h.Import(ctx) - assert.EqualError(t, err, "empty or invalid imported file: OpenAPI documentation does not contain any paths") -} diff --git a/api/internal/handler/global_rule/global_rule.go b/api/internal/handler/global_rule/global_rule.go deleted file mode 100644 index cc83e939cc..0000000000 --- a/api/internal/handler/global_rule/global_rule.go +++ /dev/null @@ -1,202 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one or more - * contributor license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright ownership. - * The ASF licenses this file to You under the Apache License, Version 2.0 - * (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package global_rule - -import ( - "encoding/json" - "net/http" - "reflect" - - "github.com/gin-gonic/gin" - "github.com/shiningrush/droplet" - "github.com/shiningrush/droplet/data" - "github.com/shiningrush/droplet/wrapper" - wgin "github.com/shiningrush/droplet/wrapper/gin" - - "github.com/apisix/manager-api/internal/core/entity" - "github.com/apisix/manager-api/internal/core/store" - "github.com/apisix/manager-api/internal/handler" - "github.com/apisix/manager-api/internal/utils" -) - -type Handler struct { - globalRuleStore store.Interface -} - -func NewHandler() (handler.RouteRegister, error) { - return &Handler{ - globalRuleStore: store.GetStore(store.HubKeyGlobalRule), - }, nil -} - -func (h *Handler) ApplyRoute(r *gin.Engine) { - // global plugins - r.GET("/apisix/admin/global_rules/:id", wgin.Wraps(h.Get, - wrapper.InputType(reflect.TypeOf(GetInput{})))) - r.GET("/apisix/admin/global_rules", wgin.Wraps(h.List, - wrapper.InputType(reflect.TypeOf(ListInput{})))) - r.PUT("/apisix/admin/global_rules/:id", wgin.Wraps(h.Set, - wrapper.InputType(reflect.TypeOf(SetInput{})))) - r.PUT("/apisix/admin/global_rules", wgin.Wraps(h.Set, - wrapper.InputType(reflect.TypeOf(SetInput{})))) - - r.PATCH("/apisix/admin/global_rules/:id", wgin.Wraps(h.Patch, - wrapper.InputType(reflect.TypeOf(PatchInput{})))) - r.PATCH("/apisix/admin/global_rules/:id/*path", wgin.Wraps(h.Patch, - wrapper.InputType(reflect.TypeOf(PatchInput{})))) - - r.DELETE("/apisix/admin/global_rules/:id", wgin.Wraps(h.BatchDelete, - wrapper.InputType(reflect.TypeOf(BatchDeleteInput{})))) -} - -type GetInput struct { - ID string `auto_read:"id,path" validate:"required"` -} - -func (h *Handler) Get(c droplet.Context) (interface{}, error) { - input := c.Input().(*GetInput) - - r, err := h.globalRuleStore.Get(c.Context(), input.ID) - if err != nil { - return handler.SpecCodeResponse(err), err - } - return r, nil -} - -type ListInput struct { - store.Pagination -} - -// swagger:operation GET /apisix/admin/global_rules getGlobalRuleList -// -// Return the global rule list according to the specified page number and page size. -// -// --- -// produces: -// - application/json -// parameters: -// - name: page -// in: query -// description: page number -// required: false -// type: integer -// - name: page_size -// in: query -// description: page size -// required: false -// type: integer -// responses: -// '0': -// description: list response -// schema: -// type: array -// items: -// "$ref": "#/definitions/GlobalPlugins" -// default: -// description: unexpected error -// schema: -// "$ref": "#/definitions/ApiError" -func (h *Handler) List(c droplet.Context) (interface{}, error) { - input := c.Input().(*ListInput) - - ret, err := h.globalRuleStore.List(c.Context(), store.ListInput{ - PageSize: input.PageSize, - PageNumber: input.PageNumber, - }) - if err != nil { - return nil, err - } - - return ret, nil -} - -type SetInput struct { - entity.GlobalPlugins - ID string `auto_read:"id,path"` -} - -func (h *Handler) Set(c droplet.Context) (interface{}, error) { - input := c.Input().(*SetInput) - - // check if ID in body is equal ID in path - if err := handler.IDCompare(input.ID, input.GlobalPlugins.ID); err != nil { - return &data.SpecCodeResponse{StatusCode: http.StatusBadRequest}, err - } - - // if has id in path, use it - if input.ID != "" { - input.GlobalPlugins.ID = input.ID - } - - ret, err := h.globalRuleStore.Update(c.Context(), &input.GlobalPlugins, true) - if err != nil { - return handler.SpecCodeResponse(err), err - } - - return ret, nil -} - -type PatchInput struct { - ID string `auto_read:"id,path"` - SubPath string `auto_read:"path,path"` - Body []byte `auto_read:"@body"` -} - -func (h *Handler) Patch(c droplet.Context) (interface{}, error) { - input := c.Input().(*PatchInput) - reqBody := input.Body - ID := input.ID - subPath := input.SubPath - - routeStore := store.GetStore(store.HubKeyGlobalRule) - stored, err := routeStore.Get(c.Context(), ID) - if err != nil { - return handler.SpecCodeResponse(err), err - } - - res, err := utils.MergePatch(stored, subPath, reqBody) - if err != nil { - return handler.SpecCodeResponse(err), err - } - - var globalRule entity.GlobalPlugins - err = json.Unmarshal(res, &globalRule) - if err != nil { - return handler.SpecCodeResponse(err), err - } - - ret, err := routeStore.Update(c.Context(), &globalRule, false) - if err != nil { - return handler.SpecCodeResponse(err), err - } - - return ret, nil -} - -type BatchDeleteInput struct { - ID string `auto_read:"id,path"` -} - -func (h *Handler) BatchDelete(c droplet.Context) (interface{}, error) { - input := c.Input().(*BatchDeleteInput) - - if err := h.globalRuleStore.BatchDelete(c.Context(), []string{input.ID}); err != nil { - return handler.SpecCodeResponse(err), err - } - - return nil, nil -} diff --git a/api/internal/handler/global_rule/global_rule_test.go b/api/internal/handler/global_rule/global_rule_test.go deleted file mode 100644 index 6e13e9fbd8..0000000000 --- a/api/internal/handler/global_rule/global_rule_test.go +++ /dev/null @@ -1,354 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one or more - * contributor license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright ownership. - * The ASF licenses this file to You under the Apache License, Version 2.0 - * (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package global_rule - -import ( - "context" - "fmt" - "net/http" - "net/http/httptest" - "testing" - - "github.com/gin-gonic/gin" - "github.com/shiningrush/droplet" - "github.com/shiningrush/droplet/data" - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/mock" - - "github.com/apisix/manager-api/internal/core/entity" - "github.com/apisix/manager-api/internal/core/store" -) - -func performRequest(r http.Handler, method, path string) *httptest.ResponseRecorder { - req := httptest.NewRequest(method, path, nil) - w := httptest.NewRecorder() - r.ServeHTTP(w, req) - return w -} - -func TestHandler_ApplyRoute(t *testing.T) { - mStore := &store.MockInterface{} - giveRet := `{ - "id": "test", - "plugins": { - "jwt-auth": {} - } - }` - mStore.On("Get", mock.Anything).Run(func(args mock.Arguments) { - assert.Equal(t, "test", args.Get(0)) - }).Return(giveRet, nil) - - h := Handler{globalRuleStore: mStore} - r := gin.New() - h.ApplyRoute(r) - - w := performRequest(r, "GET", "/apisix/admin/global_rules/test") - assert.Equal(t, 200, w.Code) -} - -func TestHandler_Get(t *testing.T) { - tests := []struct { - caseDesc string - giveInput *GetInput - giveRet interface{} - giveErr error - wantErr error - wantGetKey string - wantRet interface{} - }{ - { - caseDesc: "normal", - giveInput: &GetInput{ID: "test"}, - wantGetKey: "test", - giveRet: `{ - "id": "test", - "plugins": { - "jwt-auth": {} - } - }`, - wantRet: `{ - "id": "test", - "plugins": { - "jwt-auth": {} - } - }`, - }, - { - caseDesc: "store get failed", - giveInput: &GetInput{ID: "non-existent-key"}, - wantGetKey: "non-existent-key", - giveErr: fmt.Errorf("data not found"), - wantErr: fmt.Errorf("data not found"), - wantRet: &data.SpecCodeResponse{ - StatusCode: http.StatusNotFound, - }, - }, - } - - for _, tc := range tests { - t.Run(tc.caseDesc, func(t *testing.T) { - getCalled := false - mStore := &store.MockInterface{} - mStore.On("Get", mock.Anything).Run(func(args mock.Arguments) { - getCalled = true - assert.Equal(t, tc.wantGetKey, args.Get(0)) - }).Return(tc.giveRet, tc.giveErr) - - h := Handler{globalRuleStore: mStore} - ctx := droplet.NewContext() - ctx.SetInput(tc.giveInput) - ret, err := h.Get(ctx) - assert.True(t, getCalled) - assert.Equal(t, tc.wantRet, ret) - assert.Equal(t, tc.wantErr, err) - }) - } -} - -func TestHandler_List(t *testing.T) { - tests := []struct { - caseDesc string - giveInput *ListInput - giveData []*entity.GlobalPlugins - giveErr error - wantErr error - wantInput store.ListInput - wantRet interface{} - }{ - { - caseDesc: "list all condition", - giveInput: &ListInput{ - Pagination: store.Pagination{ - PageSize: 10, - PageNumber: 1, - }, - }, - wantInput: store.ListInput{ - PageSize: 10, - PageNumber: 1, - }, - giveData: []*entity.GlobalPlugins{ - {BaseInfo: entity.BaseInfo{ID: "global-rules-1"}}, - {BaseInfo: entity.BaseInfo{ID: "global-rules-2"}}, - {BaseInfo: entity.BaseInfo{ID: "global-rules-3"}}, - }, - wantRet: &store.ListOutput{ - Rows: []interface{}{ - &entity.GlobalPlugins{BaseInfo: entity.BaseInfo{ID: "global-rules-1"}}, - &entity.GlobalPlugins{BaseInfo: entity.BaseInfo{ID: "global-rules-2"}}, - &entity.GlobalPlugins{BaseInfo: entity.BaseInfo{ID: "global-rules-3"}}, - }, - TotalSize: 3, - }, - }, - { - caseDesc: "store list failed", - giveInput: &ListInput{ - Pagination: store.Pagination{ - PageSize: 10, - PageNumber: 10, - }, - }, - wantInput: store.ListInput{ - PageSize: 10, - PageNumber: 10, - }, - giveData: []*entity.GlobalPlugins{}, - giveErr: fmt.Errorf("list failed"), - wantErr: fmt.Errorf("list failed"), - }, - } - - for _, tc := range tests { - t.Run(tc.caseDesc, func(t *testing.T) { - getCalled := false - mStore := &store.MockInterface{} - mStore.On("List", mock.Anything).Run(func(args mock.Arguments) { - getCalled = true - input := args.Get(0).(store.ListInput) - assert.Equal(t, tc.wantInput.PageSize, input.PageSize) - assert.Equal(t, tc.wantInput.PageNumber, input.PageNumber) - }).Return(func(input store.ListInput) *store.ListOutput { - var returnData []interface{} - for _, c := range tc.giveData { - if input.Predicate == nil || input.Predicate(c) { - returnData = append(returnData, c) - } - } - return &store.ListOutput{ - Rows: returnData, - TotalSize: len(returnData), - } - }, tc.giveErr) - - h := Handler{globalRuleStore: mStore} - ctx := droplet.NewContext() - ctx.SetInput(tc.giveInput) - ret, err := h.List(ctx) - assert.True(t, getCalled) - assert.Equal(t, tc.wantRet, ret) - assert.Equal(t, tc.wantErr, err) - }) - } -} - -func TestHandler_Set(t *testing.T) { - tests := []struct { - caseDesc string - giveInput *SetInput - giveCtx context.Context - giveRet interface{} - giveErr error - wantErr error - wantInput *entity.GlobalPlugins - wantRet interface{} - wantCalled bool - }{ - { - caseDesc: "normal", - giveInput: &SetInput{ - ID: "name", - GlobalPlugins: entity.GlobalPlugins{ - Plugins: map[string]interface{}{ - "jwt-auth": map[string]interface{}{}, - }, - }, - }, - giveCtx: context.WithValue(context.Background(), "test", "value"), - giveRet: &entity.GlobalPlugins{ - BaseInfo: entity.BaseInfo{ID: "name"}, - Plugins: map[string]interface{}{ - "jwt-auth": map[string]interface{}{}, - }, - }, - wantInput: &entity.GlobalPlugins{ - BaseInfo: entity.BaseInfo{ID: "name"}, - Plugins: map[string]interface{}{ - "jwt-auth": map[string]interface{}{}, - }, - }, - wantRet: &entity.GlobalPlugins{ - BaseInfo: entity.BaseInfo{ID: "name"}, - Plugins: map[string]interface{}{ - "jwt-auth": map[string]interface{}{}, - }, - }, - wantCalled: true, - }, - { - caseDesc: "store create failed", - giveInput: &SetInput{ - ID: "name", - GlobalPlugins: entity.GlobalPlugins{}, - }, - giveErr: fmt.Errorf("create failed"), - giveRet: &data.SpecCodeResponse{ - StatusCode: http.StatusInternalServerError, - }, - wantInput: &entity.GlobalPlugins{ - BaseInfo: entity.BaseInfo{ID: "name"}, - Plugins: map[string]interface{}(nil), - }, - wantErr: fmt.Errorf("create failed"), - wantRet: &data.SpecCodeResponse{ - StatusCode: http.StatusInternalServerError, - }, - wantCalled: true, - }, - } - - for _, tc := range tests { - t.Run(tc.caseDesc, func(t *testing.T) { - methodCalled := true - mStore := &store.MockInterface{} - mStore.On("Update", mock.Anything, mock.Anything, mock.Anything).Run(func(args mock.Arguments) { - methodCalled = true - assert.Equal(t, tc.giveCtx, args.Get(0)) - assert.Equal(t, tc.wantInput, args.Get(1)) - assert.True(t, args.Bool(2)) - }).Return(tc.giveRet, tc.giveErr) - - h := Handler{globalRuleStore: mStore} - ctx := droplet.NewContext() - ctx.SetInput(tc.giveInput) - ctx.SetContext(tc.giveCtx) - ret, err := h.Set(ctx) - assert.Equal(t, tc.wantCalled, methodCalled) - assert.Equal(t, tc.wantRet, ret) - assert.Equal(t, tc.wantErr, err) - }) - } -} - -func TestHandler_BatchDelete(t *testing.T) { - tests := []struct { - caseDesc string - giveInput *BatchDeleteInput - giveCtx context.Context - giveErr error - wantErr error - wantInput []string - wantRet interface{} - }{ - { - caseDesc: "normal", - giveInput: &BatchDeleteInput{ - ID: "user1", - }, - giveCtx: context.WithValue(context.Background(), "test", "value"), - wantInput: []string{ - "user1", - }, - }, - { - caseDesc: "store delete failed", - giveInput: &BatchDeleteInput{ - ID: "user2", - }, - giveCtx: context.WithValue(context.Background(), "test", "value"), - giveErr: fmt.Errorf("delete failed"), - wantInput: []string{ - "user2", - }, - wantErr: fmt.Errorf("delete failed"), - wantRet: &data.SpecCodeResponse{ - StatusCode: http.StatusInternalServerError, - }, - }, - } - - for _, tc := range tests { - t.Run(tc.caseDesc, func(t *testing.T) { - methodCalled := true - mStore := &store.MockInterface{} - mStore.On("BatchDelete", mock.Anything, mock.Anything, mock.Anything).Run(func(args mock.Arguments) { - methodCalled = true - assert.Equal(t, tc.giveCtx, args.Get(0)) - assert.Equal(t, tc.wantInput, args.Get(1)) - }).Return(tc.giveErr) - - h := Handler{globalRuleStore: mStore} - ctx := droplet.NewContext() - ctx.SetInput(tc.giveInput) - ctx.SetContext(tc.giveCtx) - ret, err := h.BatchDelete(ctx) - assert.True(t, methodCalled) - assert.Equal(t, tc.wantErr, err) - assert.Equal(t, tc.wantRet, ret) - }) - } -} diff --git a/api/internal/handler/handler.go b/api/internal/handler/handler.go deleted file mode 100644 index af5c9dc220..0000000000 --- a/api/internal/handler/handler.go +++ /dev/null @@ -1,149 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one or more - * contributor license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright ownership. - * The ASF licenses this file to You under the Apache License, Version 2.0 - * (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -// API doc of Manager API. -// -// Manager API directly operates ETCD and provides data management for Apache APISIX, provides APIs for Front-end or other clients. -// -// Terms Of Service: -// Schemes: http, https -// Host: 127.0.0.1 -// License: Apache License 2.0 http://www.apache.org/licenses/LICENSE-2.0 -// -// Consumes: -// - application/json -// - application/xml -// -// Produces: -// - application/json -// - application/xml -// -// swagger:meta -package handler - -import ( - "context" - "fmt" - "net/http" - "strings" - - "github.com/gin-gonic/gin" - "github.com/shiningrush/droplet" - "github.com/shiningrush/droplet/data" - "github.com/shiningrush/droplet/middleware" - - "github.com/apisix/manager-api/internal/core/entity" - "github.com/apisix/manager-api/internal/core/store" - "github.com/apisix/manager-api/internal/utils" -) - -type RegisterFactory func() (RouteRegister, error) - -type RouteRegister interface { - ApplyRoute(r *gin.Engine) -} - -func SpecCodeResponse(err error) *data.SpecCodeResponse { - errMsg := err.Error() - if strings.Contains(errMsg, "required") || - strings.Contains(errMsg, "conflicted") || - strings.Contains(errMsg, "invalid") || - strings.Contains(errMsg, "missing") || - strings.Contains(errMsg, "schema validate failed") { - return &data.SpecCodeResponse{StatusCode: http.StatusBadRequest} - } - - if strings.Contains(errMsg, "not found") { - return &data.SpecCodeResponse{StatusCode: http.StatusNotFound} - } - - return &data.SpecCodeResponse{StatusCode: http.StatusInternalServerError} -} - -type ErrorTransformMiddleware struct { - middleware.BaseMiddleware -} - -func (mw *ErrorTransformMiddleware) Handle(ctx droplet.Context) error { - if err := mw.BaseMiddleware.Handle(ctx); err != nil { - bErr, ok := err.(*data.BaseError) - if !ok { - return err - } - switch bErr.Code { - case data.ErrCodeValidate, data.ErrCodeFormat: - ctx.SetOutput(&data.SpecCodeResponse{StatusCode: http.StatusBadRequest}) - case data.ErrCodeInternal: - ctx.SetOutput(&data.SpecCodeResponse{StatusCode: http.StatusInternalServerError}) - } - return err - } - return nil -} - -func IDCompare(idOnPath string, idOnBody interface{}) error { - idOnBodyStr, ok := idOnBody.(string) - if !ok { - idOnBodyStr = utils.InterfaceToString(idOnBody) - } - - // check if id on path is == to id on body ONLY if both ids are valid - if idOnPath != "" && idOnBodyStr != "" && idOnBodyStr != idOnPath { - return fmt.Errorf("ID on path (%s) doesn't match ID on body (%s)", idOnPath, idOnBodyStr) - } - - return nil -} - -func NameExistCheck(ctx context.Context, stg store.Interface, resource, name string, excludeID interface{}) (interface{}, error) { - ret, err := stg.List(ctx, store.ListInput{ - Predicate: func(obj interface{}) bool { - var objName string - var objID interface{} - switch resource { - case "route": - objID = obj.(*entity.Route).ID - objName = obj.(*entity.Route).Name - case "service": - objID = obj.(*entity.Service).ID - objName = obj.(*entity.Service).Name - case "upstream": - objID = obj.(*entity.Upstream).ID - objName = obj.(*entity.Upstream).Name - default: - panic("bad resource") - } - - if excludeID != nil && objID == excludeID { - return false - } - if objName == name { - return true - } - - return false - }, - }) - if err != nil { - return &data.SpecCodeResponse{StatusCode: http.StatusInternalServerError}, err - } - if ret.TotalSize > 0 { - return &data.SpecCodeResponse{StatusCode: http.StatusBadRequest}, - fmt.Errorf("%s name exists", resource) - } - - return nil, nil -} diff --git a/api/internal/handler/handler_test.go b/api/internal/handler/handler_test.go deleted file mode 100644 index 4e5b53339d..0000000000 --- a/api/internal/handler/handler_test.go +++ /dev/null @@ -1,145 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one or more - * contributor license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright ownership. - * The ASF licenses this file to You under the Apache License, Version 2.0 - * (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package handler - -import ( - "errors" - "net/http" - "testing" - - "github.com/shiningrush/droplet" - "github.com/shiningrush/droplet/data" - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/mock" - - "github.com/apisix/manager-api/internal/core/store" -) - -func TestSpecCodeResponse(t *testing.T) { - err := errors.New("schema validate failed: remote_addr: Must validate at least one schema (anyOf)") - resp := SpecCodeResponse(err) - assert.Equal(t, &data.SpecCodeResponse{StatusCode: http.StatusBadRequest}, resp) - - err = errors.New("data not found") - resp = SpecCodeResponse(err) - assert.Equal(t, &data.SpecCodeResponse{StatusCode: http.StatusNotFound}, resp) - - err = errors.New("system error") - resp = SpecCodeResponse(err) - assert.Equal(t, &data.SpecCodeResponse{StatusCode: http.StatusInternalServerError}, resp) -} - -func TestIDCompare(t *testing.T) { - // init - cases := []struct { - idOnPath, desc string - idOnBody interface{} - wantError error - }{ - { - desc: "ID on body is int, and it could be considered the same as ID on path", - idOnPath: "1", - idOnBody: 1, - }, - { - desc: "ID on body is int, and it is different from ID on path", - idOnPath: "1", - idOnBody: 2, - wantError: errors.New("ID on path (1) doesn't match ID on body (2)"), - }, - { - desc: "ID on body is same as ID on path", - idOnPath: "1", - idOnBody: "1", - }, - { - desc: "ID on body is different from ID on path", - idOnPath: "a", - idOnBody: "b", - wantError: errors.New("ID on path (a) doesn't match ID on body (b)"), - }, - { - desc: "No ID on body", - idOnPath: "1", - }, - { - desc: "No ID on path", - idOnBody: 1, - }, - } - for _, c := range cases { - t.Run(c.desc, func(t *testing.T) { - err := IDCompare(c.idOnPath, c.idOnBody) - assert.Equal(t, c.wantError, err) - }) - } -} - -func TestNameExistCheck(t *testing.T) { - tests := []struct { - resource string - name string - id interface{} - mockErr error - mockRet []interface{} - wantErr error - wantRet interface{} - caseDesc string - }{ - { - caseDesc: "normal, name not exists", - resource: "service", - name: "test", - }, - { - caseDesc: "get list error", - resource: "route", - name: "test", - mockErr: errors.New("test error"), - wantErr: errors.New("test error"), - wantRet: &data.SpecCodeResponse{StatusCode: http.StatusInternalServerError}, - }, - { - caseDesc: "name exists", - resource: "upstream", - name: "test", - mockRet: []interface{}{"test"}, - wantErr: errors.New("upstream name exists"), - wantRet: &data.SpecCodeResponse{StatusCode: http.StatusBadRequest}, - }, - } - for _, tc := range tests { - t.Run(tc.caseDesc, func(t *testing.T) { - mStore := &store.MockInterface{} - mStore.On("List", mock.Anything, mock.Anything).Run(func(args mock.Arguments) { - }).Return(func(input store.ListInput) *store.ListOutput { - return &store.ListOutput{ - Rows: tc.mockRet, - TotalSize: len(tc.mockRet), - } - }, tc.mockErr) - - ctx := droplet.NewContext() - res, err := NameExistCheck(ctx.Context(), mStore, tc.resource, tc.name, tc.id) - - if res != nil { - assert.Equal(t, tc.wantRet.(*data.SpecCodeResponse).StatusCode, res.(*data.SpecCodeResponse).StatusCode) - } - assert.Equal(t, tc.wantErr, err) - }) - } -} diff --git a/api/internal/handler/healthz/healthz.go b/api/internal/handler/healthz/healthz.go deleted file mode 100644 index c35c8601d8..0000000000 --- a/api/internal/handler/healthz/healthz.go +++ /dev/null @@ -1,40 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one or more - * contributor license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright ownership. - * The ASF licenses this file to You under the Apache License, Version 2.0 - * (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package healthz - -import ( - "github.com/gin-gonic/gin" - "github.com/shiningrush/droplet" - wgin "github.com/shiningrush/droplet/wrapper/gin" - - "github.com/apisix/manager-api/internal/handler" -) - -type Handler struct { -} - -func NewHandler() (handler.RouteRegister, error) { - return &Handler{}, nil -} - -func (h *Handler) ApplyRoute(r *gin.Engine) { - r.GET("/ping", wgin.Wraps(h.healthZHandler)) -} - -func (h *Handler) healthZHandler(c droplet.Context) (interface{}, error) { - return "pong", nil -} diff --git a/api/internal/handler/label/label.go b/api/internal/handler/label/label.go deleted file mode 100644 index fc767256c7..0000000000 --- a/api/internal/handler/label/label.go +++ /dev/null @@ -1,270 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one or more - * contributor license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright ownership. - * The ASF licenses this file to You under the Apache License, Version 2.0 - * (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package label - -import ( - "encoding/json" - "fmt" - "net/http" - "reflect" - "sort" - "strconv" - "strings" - - "github.com/gin-gonic/gin" - "github.com/shiningrush/droplet" - "github.com/shiningrush/droplet/data" - "github.com/shiningrush/droplet/wrapper" - wgin "github.com/shiningrush/droplet/wrapper/gin" - - "github.com/apisix/manager-api/internal/core/entity" - "github.com/apisix/manager-api/internal/core/store" - "github.com/apisix/manager-api/internal/handler" - "github.com/apisix/manager-api/internal/utils" -) - -type Handler struct { - routeStore store.Interface - serviceStore store.Interface - upstreamStore store.Interface - sslStore store.Interface - consumerStore store.Interface - pluginConfigStore store.Interface -} - -var _ json.Marshaler = Pair{} - -type Pair struct { - Key string - Val string -} - -func (p Pair) MarshalJSON() ([]byte, error) { - res := fmt.Sprintf("{%s:%s}", strconv.Quote(p.Key), strconv.Quote(p.Val)) - return []byte(res), nil -} - -func NewHandler() (handler.RouteRegister, error) { - return &Handler{ - routeStore: store.GetStore(store.HubKeyRoute), - serviceStore: store.GetStore(store.HubKeyService), - upstreamStore: store.GetStore(store.HubKeyUpstream), - sslStore: store.GetStore(store.HubKeySsl), - consumerStore: store.GetStore(store.HubKeyConsumer), - pluginConfigStore: store.GetStore(store.HubKeyPluginConfig), - }, nil -} - -func (h *Handler) ApplyRoute(r *gin.Engine) { - r.GET("/apisix/admin/labels/:type", wgin.Wraps(h.List, - wrapper.InputType(reflect.TypeOf(ListInput{})))) -} - -type ListInput struct { - store.Pagination - Type string `auto_read:"type,path" validate:"required"` - Label string `auto_read:"label,query"` -} - -func subsetOf(reqLabels map[string]struct{}, labels map[string]string) map[string]string { - if len(reqLabels) == 0 { - return labels - } - - var res = make(map[string]string) - for k, v := range labels { - if _, exist := reqLabels[k]; exist { - res[k] = v - } - - if _, exist := reqLabels[k+":"+v]; exist { - res[k] = v - } - } - - return res -} - -// swagger:operation GET /api/labels getLabelsList -// -// Return the labels list among `route,ssl,consumer,upstream,service` -// according to the specified page number and page size, and can search labels by label. -// -// --- -// produces: -// - application/json -// parameters: -// - name: page -// in: query -// description: page number -// required: false -// type: integer -// - name: page_size -// in: query -// description: page size -// required: false -// type: integer -// - name: label -// in: query -// description: label filter of labels -// required: false -// type: string -// responses: -// '0': -// description: list response -// schema: -// type: array -// items: -// "$ref": "#/definitions/service" -// default: -// description: unexpected error -// schema: -// "$ref": "#/definitions/ApiError" -func (h *Handler) List(c droplet.Context) (interface{}, error) { - input := c.Input().(*ListInput) - - typ := input.Type - reqLabels, err := utils.GenLabelMap(input.Label) - if err != nil { - return &data.SpecCodeResponse{StatusCode: http.StatusBadRequest}, - fmt.Errorf("%s: \"%s\"", err.Error(), input.Label) - } - - var items []interface{} - switch typ { - case "route": - items = append(items, h.routeStore) - case "service": - items = append(items, h.serviceStore) - case "consumer": - items = append(items, h.consumerStore) - case "ssl": - items = append(items, h.sslStore) - case "upstream": - items = append(items, h.upstreamStore) - case "plugin_config": - items = append(items, h.pluginConfigStore) - case "all": - items = append(items, h.routeStore, h.serviceStore, h.upstreamStore, - h.sslStore, h.consumerStore, h.pluginConfigStore) - } - - predicate := func(obj interface{}) bool { - var ls map[string]string - - switch obj := obj.(type) { - case *entity.Route: - ls = obj.Labels - case *entity.Consumer: - ls = obj.Labels - case *entity.SSL: - ls = obj.Labels - case *entity.Service: - ls = obj.Labels - case *entity.Upstream: - ls = obj.Labels - case *entity.PluginConfig: - ls = obj.Labels - default: - return false - } - - return utils.LabelContains(ls, reqLabels) - } - - format := func(obj interface{}) interface{} { - val := reflect.ValueOf(obj).Elem() - l := val.FieldByName("Labels") - if l.IsNil() { - return nil - } - - ls := l.Interface().(map[string]string) - return subsetOf(reqLabels, ls) - } - - var totalRet = store.NewListOutput() - var existMap = make(map[string]struct{}) - for _, item := range items { - ret, err := item.(store.Interface).List(c.Context(), - store.ListInput{ - Predicate: predicate, - Format: format, - // Sort it later. - PageSize: 0, - PageNumber: 0, - Less: func(i, j interface{}) bool { - return true - }, - }, - ) - - if err != nil { - return nil, err - } - - for _, r := range ret.Rows { - if r == nil { - continue - } - - for k, v := range r.(map[string]string) { - key := fmt.Sprintf("%s:%s", k, v) - if _, exist := existMap[key]; exist { - continue - } - - existMap[key] = struct{}{} - p := Pair{Key: k, Val: v} - totalRet.Rows = append(totalRet.Rows, p) - } - } - } - totalRet.TotalSize = len(totalRet.Rows) - - sort.Slice(totalRet.Rows, func(i, j int) bool { - p1 := totalRet.Rows[i].(Pair) - p2 := totalRet.Rows[j].(Pair) - - if strings.Compare(p1.Key, p2.Key) == 0 { - return strings.Compare(p1.Val, p2.Val) < 0 - } - - return strings.Compare(p1.Key, p2.Key) < 0 - }) - - /* There are more than one store items, - So we need sort after getting all of labels. - */ - if input.PageSize > 0 && input.PageNumber > 0 { - skipCount := (input.PageNumber - 1) * input.PageSize - if skipCount > totalRet.TotalSize { - totalRet.Rows = []interface{}{} - return totalRet, nil - } - - endIdx := skipCount + input.PageSize - if endIdx >= totalRet.TotalSize { - totalRet.Rows = totalRet.Rows[skipCount:] - return totalRet, nil - } - - totalRet.Rows = totalRet.Rows[skipCount:endIdx] - } - - return totalRet, nil -} diff --git a/api/internal/handler/label/label_test.go b/api/internal/handler/label/label_test.go deleted file mode 100644 index d227b95211..0000000000 --- a/api/internal/handler/label/label_test.go +++ /dev/null @@ -1,377 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one or more - * contributor license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright ownership. - * The ASF licenses this file to You under the Apache License, Version 2.0 - * (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package label - -import ( - "encoding/json" - "math/rand" - "testing" - - "github.com/shiningrush/droplet" - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/mock" - - "github.com/apisix/manager-api/internal/core/entity" - "github.com/apisix/manager-api/internal/core/store" -) - -type testCase struct { - giveInput *ListInput - giveData []interface{} - wantRet interface{} -} - -func TestPair_MarshalJSON(t *testing.T) { - type tempStruct struct { - Val string `json:"test_key"` - } - - temp := tempStruct{Val: "test_val"} - expect, err := json.Marshal(temp) - assert.Nil(t, err) - - p := Pair{Key: "test_key", Val: `test_val`} - content, err := json.Marshal(p) - assert.Nil(t, err, nil) - assert.Equal(t, expect, content) - - mp := make(map[string]string) - err = json.Unmarshal(content, &mp) - assert.Nil(t, err) - assert.Equal(t, mp["test_key"], "test_val") - - // Because the quote in json key is not allowed. - // So we only test the quote in json value. - temp = tempStruct{Val: "test_val\""} - expect, err = json.Marshal(temp) - assert.Nil(t, err) - - p = Pair{Key: "test_key", Val: `test_val"`} - content, err = json.Marshal(p) - assert.Nil(t, err, nil) - assert.Equal(t, expect, content) - - mp = make(map[string]string) - err = json.Unmarshal(content, &mp) - assert.Nil(t, err) - assert.Equal(t, mp["test_key"], "test_val\"") -} - -func genMockStore(t *testing.T, giveData []interface{}) *store.MockInterface { - mStore := &store.MockInterface{} - mStore.On("List", mock.Anything).Run(func(args mock.Arguments) { - input := args.Get(0).(store.ListInput) - assert.Equal(t, 0, input.PageSize) - assert.Equal(t, 0, input.PageNumber) - }).Return(func(input store.ListInput) *store.ListOutput { - var returnData []interface{} - for _, c := range giveData { - if input.Predicate(c) { - returnData = append(returnData, input.Format(c)) - } - } - return &store.ListOutput{ - Rows: returnData, - TotalSize: len(returnData), - } - }, nil) - - return mStore -} - -func newCase(giveData []interface{}, ret []interface{}) *testCase { - t := testCase{} - t.giveInput = &ListInput{ - Pagination: store.Pagination{ - PageSize: 10, - PageNumber: 1, - }, - } - - t.giveData = giveData - t.wantRet = &store.ListOutput{ - Rows: ret, - TotalSize: len(ret), - } - - return &t -} - -func genRoute(labels map[string]string) *entity.Route { - r := entity.Route{ - BaseInfo: entity.BaseInfo{ - ID: rand.Int(), - CreateTime: rand.Int63(), - }, - Host: "test.com", - URI: "/test/route", - Labels: labels, - } - - return &r -} - -func genService(labels map[string]string) *entity.Service { - r := entity.Service{ - BaseInfo: entity.BaseInfo{ - ID: rand.Int(), - CreateTime: rand.Int63(), - }, - EnableWebsocket: true, - Labels: labels, - } - - return &r -} - -func genSSL(labels map[string]string) *entity.SSL { - r := entity.SSL{ - BaseInfo: entity.BaseInfo{ - ID: rand.Int(), - CreateTime: rand.Int63(), - }, - Labels: labels, - } - - return &r -} - -func genUpstream(labels map[string]string) *entity.Upstream { - r := entity.Upstream{ - BaseInfo: entity.BaseInfo{ - ID: rand.Int(), - CreateTime: rand.Int63(), - }, - UpstreamDef: entity.UpstreamDef{ - Labels: labels, - }, - } - - return &r -} - -func genConsumer(labels map[string]string) *entity.Consumer { - r := entity.Consumer{ - Username: "test", - Labels: labels, - } - - return &r -} - -func genPluginConfig(labels map[string]string) *entity.PluginConfig { - r := entity.PluginConfig{ - BaseInfo: entity.BaseInfo{ - ID: rand.Int(), - CreateTime: rand.Int63(), - }, - Labels: labels, - } - - return &r -} - -func TestLabel(t *testing.T) { - m1 := map[string]string{ - "label1": "value1", - "label2": "value2", - } - - m2 := map[string]string{ - "label1": "value2", - } - - // TODO: Test SSL after the ssl config bug fixed - types := []string{"route", "service", "upstream", "consumer", "plugin_config"} - - var giveData []interface{} - for _, typ := range types { - switch typ { - case "route": - giveData = []interface{}{ - genRoute(m1), - genRoute(m2), - } - case "service": - giveData = []interface{}{ - genService(m1), - genService(m2), - } - case "ssl": - giveData = []interface{}{ - genSSL(m1), - genSSL(m2), - } - case "upstream": - giveData = []interface{}{ - genUpstream(m1), - genUpstream(m2), - } - case "consumer": - giveData = []interface{}{ - genConsumer(m1), - genConsumer(m2), - } - case "plugin_config": - giveData = []interface{}{ - genPluginConfig(m1), - genPluginConfig(m2), - } - } - - var testCases []*testCase - - expect := []interface{}{ - Pair{"label1", "value1"}, - Pair{"label1", "value2"}, - Pair{"label2", "value2"}, - } - tc := newCase(giveData, expect) - tc.giveInput.Type = typ - testCases = append(testCases, tc) - - expect = []interface{}{ - Pair{"label1", "value1"}, - Pair{"label1", "value2"}, - } - tc = newCase(giveData, expect) - tc.giveInput.Type = typ - tc.giveInput.Label = "label1" - testCases = append(testCases, tc) - - expect = []interface{}{ - Pair{"label1", "value2"}, - } - tc = newCase(giveData, expect) - tc.giveInput.Type = typ - tc.giveInput.Label = "label1:value2" - testCases = append(testCases, tc) - - expect = []interface{}{ - Pair{"label1", "value1"}, - Pair{"label1", "value2"}, - } - tc = newCase(giveData, expect) - tc.giveInput.Type = typ - tc.giveInput.Label = "label1:value1,label1:value2" - testCases = append(testCases, tc) - - handler := Handler{} - for _, tc := range testCases { - switch typ { - case "route": - handler.routeStore = genMockStore(t, tc.giveData) - case "service": - handler.serviceStore = genMockStore(t, tc.giveData) - case "ssl": - handler.sslStore = genMockStore(t, tc.giveData) - case "upstream": - handler.upstreamStore = genMockStore(t, tc.giveData) - case "consumer": - handler.consumerStore = genMockStore(t, tc.giveData) - case "plugin_config": - handler.pluginConfigStore = genMockStore(t, tc.giveData) - } - - ctx := droplet.NewContext() - ctx.SetInput(tc.giveInput) - ret, err := handler.List(ctx) - assert.Nil(t, err) - assert.Equal(t, tc.wantRet, ret) - } - } - - // test all - m3 := map[string]string{ - "label3": "value3", - } - - m4 := map[string]string{ - "label4": "value4", - } - - m5 := map[string]string{ - "label4": "value4", - "label5": "value5", - } - - handler := Handler{ - routeStore: genMockStore(t, []interface{}{genRoute(m1)}), - sslStore: genMockStore(t, []interface{}{genSSL(m2)}), - upstreamStore: genMockStore(t, []interface{}{genUpstream(m3)}), - consumerStore: genMockStore(t, []interface{}{genConsumer(m4)}), - serviceStore: genMockStore(t, []interface{}{genService(m5)}), - pluginConfigStore: genMockStore(t, []interface{}{genPluginConfig(m5)}), - } - - var testCases []*testCase - - expect := []interface{}{ - Pair{"label1", "value1"}, - Pair{"label1", "value2"}, - Pair{"label2", "value2"}, - Pair{"label3", "value3"}, - Pair{"label4", "value4"}, - Pair{"label5", "value5"}, - } - tc := newCase(nil, expect) - tc.giveInput.Type = "all" - testCases = append(testCases, tc) - - expect = []interface{}{ - Pair{"label1", "value1"}, - Pair{"label1", "value2"}, - } - tc = newCase(nil, expect) - tc.giveInput.Type = "all" - tc.giveInput.Label = "label1" - testCases = append(testCases, tc) - - expect = []interface{}{ - Pair{"label1", "value2"}, - } - tc = newCase(nil, expect) - tc.giveInput.Type = "all" - tc.giveInput.Label = "label1:value2" - testCases = append(testCases, tc) - - expect = []interface{}{ - Pair{"label1", "value1"}, - Pair{"label1", "value2"}, - Pair{"label5", "value5"}, - } - tc = newCase(nil, expect) - tc.giveInput.Type = "all" - tc.giveInput.Label = "label1,label5:value5" - - expect = []interface{}{ - Pair{"label1", "value1"}, - Pair{"label1", "value2"}, - } - tc = newCase(nil, expect) - tc.giveInput.Type = "all" - tc.giveInput.Label = "label1=value1,label1=value2" - - for _, tc := range testCases { - ctx := droplet.NewContext() - ctx.SetInput(tc.giveInput) - ret, err := handler.List(ctx) - assert.Nil(t, err) - assert.Equal(t, tc.wantRet, ret) - } -} diff --git a/api/internal/handler/migrate/migrate.go b/api/internal/handler/migrate/migrate.go deleted file mode 100644 index 28c4fe6f6f..0000000000 --- a/api/internal/handler/migrate/migrate.go +++ /dev/null @@ -1,133 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one or more - * contributor license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright ownership. - * The ASF licenses this file to You under the Apache License, Version 2.0 - * (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package migrate - -import ( - "encoding/binary" - "hash/crc32" - "io/ioutil" - "net/http" - - "github.com/gin-gonic/gin" - "github.com/shiningrush/droplet/data" - - "github.com/apisix/manager-api/internal/core/migrate" - "github.com/apisix/manager-api/internal/handler" - "github.com/apisix/manager-api/internal/log" - "github.com/apisix/manager-api/internal/utils/consts" -) - -const ( - exportFileName = "apisix-config.bak" - checksumLength = 4 // 4 bytes (uint32) -) - -type Handler struct{} - -func NewHandler() (handler.RouteRegister, error) { - return &Handler{}, nil -} - -func (h *Handler) ApplyRoute(r *gin.Engine) { - r.GET("/apisix/admin/migrate/export", h.ExportConfig) - r.POST("/apisix/admin/migrate/import", h.ImportConfig) -} - -type ExportInput struct{} - -func (h *Handler) ExportConfig(c *gin.Context) { - data, err := migrate.Export(c) - if err != nil { - log.Errorf("Export: %s", err) - c.JSON(http.StatusInternalServerError, err) - return - } - // To check file integrity - // Add 4 byte(uint32) checksum at the end of file. - checksumUint32 := crc32.ChecksumIEEE(data) - checksum := make([]byte, checksumLength) - binary.BigEndian.PutUint32(checksum, checksumUint32) - fileBytes := append(data, checksum...) - - c.Writer.WriteHeader(http.StatusOK) - c.Header("Content-Disposition", "attachment; filename="+exportFileName) - c.Header("Content-Type", "application/octet-stream") - c.Header("Content-Transfer-Encoding", "binary") - _, err = c.Writer.Write([]byte(fileBytes)) - if err != nil { - log.Errorf("Write: %s", err) - } -} - -type ImportOutput struct { - ConflictItems *migrate.DataSet -} - -var modeMap = map[string]migrate.ConflictMode{ - "return": migrate.ModeReturn, - "overwrite": migrate.ModeOverwrite, - "skip": migrate.ModeSkip, -} - -func (h *Handler) ImportConfig(c *gin.Context) { - paraMode := c.PostForm("mode") - mode := migrate.ModeReturn - if m, ok := modeMap[paraMode]; ok { - mode = m - } - file, _, err := c.Request.FormFile("file") - if err != nil { - c.JSON(http.StatusInternalServerError, err) - return - } - content, err := ioutil.ReadAll(file) - if err != nil { - c.JSON(http.StatusInternalServerError, err) - return - } - // checksum uint32,4 bytes - importData := content[:len(content)-4] - checksum := binary.BigEndian.Uint32(content[len(content)-4:]) - if checksum != crc32.ChecksumIEEE(importData) { - c.JSON(http.StatusOK, &data.BaseError{ - Code: consts.ErrBadRequest, - Message: "Checksum check failure,maybe file broken", - }) - return - } - conflictData, err := migrate.Import(c, importData, mode) - if err != nil { - if err == migrate.ErrConflict { - c.JSON(http.StatusOK, &data.BaseError{ - Code: consts.ErrBadRequest, - Message: "Config conflict", - Data: ImportOutput{ConflictItems: conflictData}, - }) - } else { - log.Errorf("Import failed: %s", err) - c.JSON(http.StatusOK, &data.BaseError{ - Code: consts.ErrBadRequest, - Message: err.Error(), - Data: ImportOutput{ConflictItems: conflictData}, - }) - } - return - } - c.JSON(http.StatusOK, &data.Response{ - Data: ImportOutput{ConflictItems: conflictData}, - }) -} diff --git a/api/internal/handler/plugin_config/plugin_config.go b/api/internal/handler/plugin_config/plugin_config.go deleted file mode 100644 index ec09a7f495..0000000000 --- a/api/internal/handler/plugin_config/plugin_config.go +++ /dev/null @@ -1,267 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one or more - * contributor license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright ownership. - * The ASF licenses this file to You under the Apache License, Version 2.0 - * (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package plugin_config - -import ( - "encoding/json" - "fmt" - "net/http" - "reflect" - "strings" - - "github.com/gin-gonic/gin" - "github.com/shiningrush/droplet" - "github.com/shiningrush/droplet/data" - "github.com/shiningrush/droplet/wrapper" - wgin "github.com/shiningrush/droplet/wrapper/gin" - - "github.com/apisix/manager-api/internal/core/entity" - "github.com/apisix/manager-api/internal/core/store" - "github.com/apisix/manager-api/internal/handler" - "github.com/apisix/manager-api/internal/log" - "github.com/apisix/manager-api/internal/utils" -) - -type Handler struct { - pluginConfigStore store.Interface - routeStore store.Interface -} - -func NewHandler() (handler.RouteRegister, error) { - return &Handler{ - pluginConfigStore: store.GetStore(store.HubKeyPluginConfig), - routeStore: store.GetStore(store.HubKeyRoute), - }, nil -} - -func (h *Handler) ApplyRoute(r *gin.Engine) { - r.GET("/apisix/admin/plugin_configs/:id", wgin.Wraps(h.Get, - wrapper.InputType(reflect.TypeOf(GetInput{})))) - r.GET("/apisix/admin/plugin_configs", wgin.Wraps(h.List, - wrapper.InputType(reflect.TypeOf(ListInput{})))) - r.POST("/apisix/admin/plugin_configs", wgin.Wraps(h.Create, - wrapper.InputType(reflect.TypeOf(entity.PluginConfig{})))) - r.PUT("/apisix/admin/plugin_configs", wgin.Wraps(h.Update, - wrapper.InputType(reflect.TypeOf(UpdateInput{})))) - r.PUT("/apisix/admin/plugin_configs/:id", wgin.Wraps(h.Update, - wrapper.InputType(reflect.TypeOf(UpdateInput{})))) - r.PATCH("/apisix/admin/plugin_configs/:id", wgin.Wraps(h.Patch, - wrapper.InputType(reflect.TypeOf(PatchInput{})))) - r.PATCH("/apisix/admin/plugin_configs/:id/*path", wgin.Wraps(h.Patch, - wrapper.InputType(reflect.TypeOf(PatchInput{})))) - r.DELETE("/apisix/admin/plugin_configs/:ids", wgin.Wraps(h.BatchDelete, - wrapper.InputType(reflect.TypeOf(BatchDelete{})))) -} - -type GetInput struct { - ID string `auto_read:"id,path" validate:"required"` -} - -func (h *Handler) Get(c droplet.Context) (interface{}, error) { - input := c.Input().(*GetInput) - - pluginConfig, err := h.pluginConfigStore.Get(c.Context(), input.ID) - if err != nil { - return handler.SpecCodeResponse(err), err - } - - return pluginConfig, nil -} - -type ListInput struct { - Search string `auto_read:"search,query"` - Label string `auto_read:"label,query"` - store.Pagination -} - -// swagger:operation GET /apisix/admin/plugin_configs getPluginConfigList -// -// Return the plugin_config list according to the specified page number and page size, and support search. -// -// --- -// produces: -// - application/json -// parameters: -// - name: page -// in: query -// description: page number -// required: false -// type: integer -// - name: page_size -// in: query -// description: page size -// required: false -// type: integer -// - name: search -// in: query -// description: search keyword -// required: false -// type: string -// responses: -// '0': -// description: list response -// schema: -// type: array -// items: -// "$ref": "#/definitions/pluginConfig" -// default: -// description: unexpected error -// schema: -// "$ref": "#/definitions/ApiError" -func (h *Handler) List(c droplet.Context) (interface{}, error) { - input := c.Input().(*ListInput) - labelMap, err := utils.GenLabelMap(input.Label) - if err != nil { - return &data.SpecCodeResponse{StatusCode: http.StatusBadRequest}, - fmt.Errorf("%s: \"%s\"", err.Error(), input.Label) - } - - ret, err := h.pluginConfigStore.List(c.Context(), store.ListInput{ - Predicate: func(obj interface{}) bool { - if input.Search != "" { - return strings.Contains(obj.(*entity.PluginConfig).Desc, input.Search) - } - - if input.Label != "" && !utils.LabelContains(obj.(*entity.PluginConfig).Labels, labelMap) { - return false - } - - return true - }, - PageSize: input.PageSize, - PageNumber: input.PageNumber, - }) - if err != nil { - return nil, err - } - - return ret, nil -} - -func (h *Handler) Create(c droplet.Context) (interface{}, error) { - input := c.Input().(*entity.PluginConfig) - - ret, err := h.pluginConfigStore.Create(c.Context(), input) - if err != nil { - return handler.SpecCodeResponse(err), err - } - - return ret, nil -} - -type UpdateInput struct { - ID string `auto_read:"id,path"` - entity.PluginConfig -} - -func (h *Handler) Update(c droplet.Context) (interface{}, error) { - input := c.Input().(*UpdateInput) - - // check if ID in body is equal ID in path - if err := handler.IDCompare(input.ID, input.PluginConfig.ID); err != nil { - return &data.SpecCodeResponse{StatusCode: http.StatusBadRequest}, err - } - - if input.ID != "" { - input.PluginConfig.ID = input.ID - } - - ret, err := h.pluginConfigStore.Update(c.Context(), &input.PluginConfig, true) - if err != nil { - return handler.SpecCodeResponse(err), err - } - - return ret, nil -} - -type BatchDelete struct { - IDs string `auto_read:"ids,path"` -} - -func (h *Handler) BatchDelete(c droplet.Context) (interface{}, error) { - input := c.Input().(*BatchDelete) - - IDs := strings.Split(input.IDs, ",") - IDMap := map[string]bool{} - for _, id := range IDs { - IDMap[id] = true - } - ret, err := h.routeStore.List(c.Context(), store.ListInput{ - Predicate: func(obj interface{}) bool { - id := utils.InterfaceToString(obj.(*entity.Route).PluginConfigID) - if _, ok := IDMap[id]; ok { - return true - } - return false - }, - }) - - if err != nil { - return nil, err - } - - if len(ret.Rows) > 0 { - return &data.SpecCodeResponse{StatusCode: http.StatusBadRequest}, - fmt.Errorf("please disconnect the route (ID: %s) with this plugin config first", - ret.Rows[0].(*entity.Route).ID) - } - - if err := h.pluginConfigStore.BatchDelete(c.Context(), strings.Split(input.IDs, ",")); err != nil { - return handler.SpecCodeResponse(err), err - } - - return nil, nil -} - -type PatchInput struct { - ID string `auto_read:"id,path"` - SubPath string `auto_read:"path,path"` - Body []byte `auto_read:"@body"` -} - -func (h *Handler) Patch(c droplet.Context) (interface{}, error) { - input := c.Input().(*PatchInput) - reqBody := input.Body - id := input.ID - subPath := input.SubPath - - stored, err := h.pluginConfigStore.Get(c.Context(), id) - if err != nil { - log.Warnf("get stored data from etcd failed: %s", err) - return handler.SpecCodeResponse(err), err - } - - res, err := utils.MergePatch(stored, subPath, reqBody) - if err != nil { - log.Warnf("merge failed: %s", err) - return handler.SpecCodeResponse(err), err - } - - var pluginConfig entity.PluginConfig - if err := json.Unmarshal(res, &pluginConfig); err != nil { - log.Warnf("unmarshal to pluginConfig failed: %s", err) - return handler.SpecCodeResponse(err), err - } - - ret, err := h.pluginConfigStore.Update(c.Context(), &pluginConfig, false) - if err != nil { - log.Warnf("update failed: %s", err) - return handler.SpecCodeResponse(err), err - } - - return ret, nil -} diff --git a/api/internal/handler/plugin_config/plugin_config_test.go b/api/internal/handler/plugin_config/plugin_config_test.go deleted file mode 100644 index 1dbdbbe651..0000000000 --- a/api/internal/handler/plugin_config/plugin_config_test.go +++ /dev/null @@ -1,715 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one or more - * contributor license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright ownership. - * The ASF licenses this file to You under the Apache License, Version 2.0 - * (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package plugin_config - -import ( - "errors" - "fmt" - "net/http" - "testing" - - "github.com/shiningrush/droplet" - "github.com/shiningrush/droplet/data" - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/mock" - - "github.com/apisix/manager-api/internal/core/entity" - "github.com/apisix/manager-api/internal/core/store" - "github.com/apisix/manager-api/internal/handler" -) - -func TestPluginConfig_Get(t *testing.T) { - tests := []struct { - caseDesc string - giveInput *GetInput - giveRet *entity.PluginConfig - giveErr error - wantErr error - wantGetKey string - wantRet interface{} - }{ - { - caseDesc: "normal", - giveInput: &GetInput{ID: "1"}, - wantGetKey: "1", - giveRet: &entity.PluginConfig{ - BaseInfo: entity.BaseInfo{ - ID: "1", - }, - Plugins: map[string]interface{}{ - "limit-count": map[string]interface{}{ - "count": 2, - "time_window": 60, - "rejected_code": 503, - "key": "remote_addr", - }, - }, - }, - wantRet: &entity.PluginConfig{ - BaseInfo: entity.BaseInfo{ - ID: "1", - }, - Plugins: map[string]interface{}{ - "limit-count": map[string]interface{}{ - "count": 2, - "time_window": 60, - "rejected_code": 503, - "key": "remote_addr", - }, - }, - }, - }, - { - caseDesc: "store get failed", - giveInput: &GetInput{ID: "failed_key"}, - wantGetKey: "failed_key", - giveErr: fmt.Errorf("get failed"), - wantErr: fmt.Errorf("get failed"), - wantRet: &data.SpecCodeResponse{ - StatusCode: http.StatusInternalServerError, - }, - }, - } - - for _, tc := range tests { - t.Run(tc.caseDesc, func(t *testing.T) { - getCalled := true - mStore := &store.MockInterface{} - mStore.On("Get", mock.Anything, mock.Anything).Run(func(args mock.Arguments) { - getCalled = true - assert.Equal(t, tc.wantGetKey, args.Get(0)) - }).Return(tc.giveRet, tc.giveErr) - - h := Handler{pluginConfigStore: mStore} - ctx := droplet.NewContext() - ctx.SetInput(tc.giveInput) - ret, err := h.Get(ctx) - assert.True(t, getCalled) - assert.Equal(t, tc.wantRet, ret) - assert.Equal(t, tc.wantErr, err) - }) - } -} - -func TestPluginConfig_List(t *testing.T) { - tests := []struct { - caseDesc string - giveInput *ListInput - giveData []*entity.PluginConfig - giveErr error - wantErr error - wantInput store.ListInput - wantRet interface{} - }{ - { - caseDesc: "list all plugin config", - giveInput: &ListInput{ - Pagination: store.Pagination{ - PageSize: 10, - PageNumber: 10, - }, - }, - wantInput: store.ListInput{ - PageSize: 10, - PageNumber: 10, - }, - giveData: []*entity.PluginConfig{ - {Desc: "1"}, - {Desc: "s2"}, - {Desc: "test_plugin_config"}, - {Desc: "plugin_config_test"}, - }, - wantRet: &store.ListOutput{ - Rows: []interface{}{ - &entity.PluginConfig{Desc: "1"}, - &entity.PluginConfig{Desc: "s2"}, - &entity.PluginConfig{Desc: "test_plugin_config"}, - &entity.PluginConfig{Desc: "plugin_config_test"}, - }, - TotalSize: 4, - }, - }, - { - caseDesc: "list plugin config with 'plugin_config'", - giveInput: &ListInput{ - Search: "plugin_config", - Pagination: store.Pagination{ - PageSize: 10, - PageNumber: 10, - }, - }, - wantInput: store.ListInput{ - PageSize: 10, - PageNumber: 10, - }, - giveData: []*entity.PluginConfig{ - {BaseInfo: entity.BaseInfo{CreateTime: 1609376661}, Desc: "1"}, - {BaseInfo: entity.BaseInfo{CreateTime: 1609376662}, Desc: "s2"}, - {BaseInfo: entity.BaseInfo{CreateTime: 1609376663}, Desc: "test_plugin_config"}, - {BaseInfo: entity.BaseInfo{CreateTime: 1609376664}, Desc: "plugin_config_test"}, - }, - wantRet: &store.ListOutput{ - Rows: []interface{}{ - &entity.PluginConfig{BaseInfo: entity.BaseInfo{CreateTime: 1609376663}, Desc: "test_plugin_config"}, - &entity.PluginConfig{BaseInfo: entity.BaseInfo{CreateTime: 1609376664}, Desc: "plugin_config_test"}, - }, - TotalSize: 2, - }, - }, - { - caseDesc: "list plugin config with label", - giveInput: &ListInput{ - Label: "extra", - Pagination: store.Pagination{ - PageSize: 10, - PageNumber: 10, - }, - }, - wantInput: store.ListInput{ - PageSize: 10, - PageNumber: 10, - }, - giveData: []*entity.PluginConfig{ - { - Desc: "1", - Labels: map[string]string{ - "version": "v1", - "extra": "t", - }, - }, - {Desc: "s2"}, - {Desc: "test_plugin_config"}, - {Desc: "plugin_config_test"}, - }, - wantRet: &store.ListOutput{ - Rows: []interface{}{ - &entity.PluginConfig{ - Desc: "1", - Labels: map[string]string{ - "version": "v1", - "extra": "t", - }, - }, - }, - TotalSize: 1, - }, - }, - { - caseDesc: "list plugin config with label (k:v)", - giveInput: &ListInput{ - Label: "version:v1", - Pagination: store.Pagination{ - PageSize: 10, - PageNumber: 10, - }, - }, - wantInput: store.ListInput{ - PageSize: 10, - PageNumber: 10, - }, - giveData: []*entity.PluginConfig{ - { - Desc: "1", - Labels: map[string]string{ - "version": "v1", - "build": "16", - }, - }, - {Desc: "s2"}, - {Desc: "test_plugin_config"}, - {Desc: "plugin_config_test"}, - }, - wantRet: &store.ListOutput{ - Rows: []interface{}{ - &entity.PluginConfig{ - Desc: "1", - Labels: map[string]string{ - "version": "v1", - "build": "16", - }, - }, - }, - TotalSize: 1, - }, - }, - } - - for _, tc := range tests { - t.Run(tc.caseDesc, func(t *testing.T) { - getCalled := true - mStore := &store.MockInterface{} - mStore.On("List", mock.Anything, mock.Anything).Run(func(args mock.Arguments) { - getCalled = true - input := args.Get(0).(store.ListInput) - assert.Equal(t, tc.wantInput.PageSize, input.PageSize) - assert.Equal(t, tc.wantInput.PageNumber, input.PageNumber) - }).Return(func(input store.ListInput) *store.ListOutput { - var returnData []interface{} - for _, c := range tc.giveData { - if input.Predicate(c) { - if input.Format == nil { - returnData = append(returnData, c) - continue - } - - returnData = append(returnData, input.Format(c)) - } - } - return &store.ListOutput{ - Rows: returnData, - TotalSize: len(returnData), - } - }, tc.giveErr) - - h := Handler{pluginConfigStore: mStore} - ctx := droplet.NewContext() - ctx.SetInput(tc.giveInput) - ret, err := h.List(ctx) - assert.True(t, getCalled) - assert.Equal(t, tc.wantRet, ret) - assert.Equal(t, tc.wantErr, err) - }) - } -} - -func TestPluginConfig_Create(t *testing.T) { - tests := []struct { - caseDesc string - getCalled bool - giveInput *entity.PluginConfig - giveRet interface{} - giveErr error - wantInput *entity.PluginConfig - wantErr error - wantRet interface{} - }{ - { - caseDesc: "create success", - getCalled: true, - giveInput: &entity.PluginConfig{ - Desc: "test plugin config", - }, - wantInput: &entity.PluginConfig{ - Desc: "test plugin config", - }, - }, - { - caseDesc: "create failed, create return error", - getCalled: true, - giveInput: &entity.PluginConfig{ - Desc: "test plugin config", - }, - giveErr: fmt.Errorf("create failed"), - wantInput: &entity.PluginConfig{ - Desc: "test plugin config", - }, - wantErr: fmt.Errorf("create failed"), - wantRet: handler.SpecCodeResponse(fmt.Errorf("create failed")), - }, - } - - for _, tc := range tests { - t.Run(tc.caseDesc, func(t *testing.T) { - getCalled := false - pluginConfigStore := &store.MockInterface{} - pluginConfigStore.On("Create", mock.Anything, mock.Anything).Run(func(args mock.Arguments) { - getCalled = true - input := args.Get(1).(*entity.PluginConfig) - assert.Equal(t, tc.wantInput, input) - }).Return(tc.giveRet, tc.giveErr) - - h := Handler{pluginConfigStore: pluginConfigStore} - ctx := droplet.NewContext() - ctx.SetInput(tc.giveInput) - ret, err := h.Create(ctx) - assert.Equal(t, tc.getCalled, getCalled) - assert.Equal(t, tc.wantRet, ret) - assert.Equal(t, tc.wantErr, err) - }) - } -} - -func TestPluginConfig_Update(t *testing.T) { - tests := []struct { - caseDesc string - getCalled bool - giveInput *UpdateInput - giveErr error - giveRet interface{} - wantInput *entity.PluginConfig - wantErr error - wantRet interface{} - }{ - { - caseDesc: "create success", - getCalled: true, - giveInput: &UpdateInput{ - ID: "1", - PluginConfig: entity.PluginConfig{ - Desc: "test plugin config", - }, - }, - wantInput: &entity.PluginConfig{ - BaseInfo: entity.BaseInfo{ - ID: "1", - }, - Desc: "test plugin config", - }, - }, - { - caseDesc: "create failed, different id", - giveInput: &UpdateInput{ - ID: "1", - PluginConfig: entity.PluginConfig{ - BaseInfo: entity.BaseInfo{ - ID: "s2", - }, - Desc: "test plugin config", - }, - }, - wantRet: &data.SpecCodeResponse{StatusCode: http.StatusBadRequest}, - wantErr: fmt.Errorf("ID on path (1) doesn't match ID on body (s2)"), - }, - { - caseDesc: "update failed, update return error", - getCalled: true, - giveInput: &UpdateInput{ - ID: "1", - PluginConfig: entity.PluginConfig{ - Desc: "test plugin config", - }, - }, - giveErr: fmt.Errorf("update failed"), - wantInput: &entity.PluginConfig{ - BaseInfo: entity.BaseInfo{ID: "1"}, - Desc: "test plugin config", - }, - wantErr: fmt.Errorf("update failed"), - wantRet: handler.SpecCodeResponse(fmt.Errorf("update failed")), - }, - } - - for _, tc := range tests { - t.Run(tc.caseDesc, func(t *testing.T) { - getCalled := false - pluginConfigStore := &store.MockInterface{} - pluginConfigStore.On("Update", mock.Anything, mock.Anything, mock.Anything).Run(func(args mock.Arguments) { - getCalled = true - input := args.Get(1).(*entity.PluginConfig) - createIfNotExist := args.Get(2).(bool) - assert.Equal(t, tc.wantInput, input) - assert.True(t, createIfNotExist) - }).Return(tc.giveRet, tc.giveErr) - - h := Handler{pluginConfigStore: pluginConfigStore} - ctx := droplet.NewContext() - ctx.SetInput(tc.giveInput) - ret, err := h.Update(ctx) - assert.Equal(t, tc.getCalled, getCalled) - assert.Equal(t, tc.wantRet, ret) - assert.Equal(t, tc.wantErr, err) - }) - } -} - -func TestPluginConfig_Patch(t *testing.T) { - existPluginConfig := &entity.PluginConfig{ - BaseInfo: entity.BaseInfo{ - ID: "1", - CreateTime: 1609340491, - UpdateTime: 1609340491, - }, - Plugins: map[string]interface{}{ - "limit-count": map[string]interface{}{ - "count": 2, - "time_window": 60, - "rejected_code": 503, - "key": "remote_addr", - }, - }, - Labels: map[string]string{ - "version": "v1", - }, - Desc: "desc", - } - - tests := []struct { - caseDesc string - giveInput *PatchInput - giveErr error - giveRet interface{} - wantInput *entity.PluginConfig - wantErr error - wantRet interface{} - pluginConfigInput string - pluginConfigRet *entity.PluginConfig - pluginConfigErr error - called bool - }{ - { - caseDesc: "patch all success", - giveInput: &PatchInput{ - ID: "1", - SubPath: "", - Body: []byte(`{ - "desc":"patched", - "plugins":{ - "limit-count":{ - "count":2, - "time_window":60, - "rejected_code": 504, - "key":"remote_addr" - }, - "key-auth":{ - "key":"auth-one" - } - }, - "labels":{ - "version":"v1", - "build":"16" - } - }`), - }, - wantInput: &entity.PluginConfig{ - BaseInfo: entity.BaseInfo{ - ID: "1", - CreateTime: 1609340491, - UpdateTime: 1609340491, - }, - Desc: "patched", - Plugins: map[string]interface{}{ - "limit-count": map[string]interface{}{ - "count": float64(2), - "time_window": float64(60), - "rejected_code": float64(504), - "key": "remote_addr", - }, - "key-auth": map[string]interface{}{ - "key": "auth-one", - }, - }, - Labels: map[string]string{ - "version": "v1", - "build": "16", - }, - }, - pluginConfigInput: "1", - pluginConfigRet: existPluginConfig, - called: true, - }, - { - caseDesc: "patch part of plugin config success", - giveInput: &PatchInput{ - ID: "1", - SubPath: "", - Body: []byte(`{ - "desc":"patched" - }`), - }, - wantInput: &entity.PluginConfig{ - BaseInfo: entity.BaseInfo{ - ID: "1", - CreateTime: 1609340491, - UpdateTime: 1609340491, - }, - Desc: "patched", - Plugins: map[string]interface{}{ - "limit-count": map[string]interface{}{ - "count": float64(2), - "time_window": float64(60), - "rejected_code": float64(503), - "key": "remote_addr", - }, - }, - Labels: map[string]string{ - "version": "v1", - }, - }, - pluginConfigInput: "1", - pluginConfigRet: existPluginConfig, - called: true, - }, - { - caseDesc: "patch desc success with sub path", - giveInput: &PatchInput{ - ID: "1", - SubPath: "/desc", - Body: []byte(`"desc_patched"`), - }, - wantInput: &entity.PluginConfig{ - BaseInfo: entity.BaseInfo{ - ID: "1", - CreateTime: 1609340491, - UpdateTime: 1609340491, - }, - Desc: "desc_patched", - Plugins: map[string]interface{}{ - "limit-count": map[string]interface{}{ - "count": float64(2), - "time_window": float64(60), - "rejected_code": float64(503), - "key": "remote_addr", - }, - }, - Labels: map[string]string{ - "version": "v1", - }, - }, - pluginConfigInput: "1", - pluginConfigRet: existPluginConfig, - called: true, - }, - { - caseDesc: "patch failed, plugin config store get error", - giveInput: &PatchInput{ - ID: "1", - Body: []byte{}, - }, - pluginConfigInput: "1", - pluginConfigErr: fmt.Errorf("get error"), - wantRet: handler.SpecCodeResponse(fmt.Errorf("get error")), - wantErr: fmt.Errorf("get error"), - called: false, - }, - } - - for _, tc := range tests { - t.Run(tc.caseDesc, func(t *testing.T) { - getCalled := false - pluginConfigStore := &store.MockInterface{} - pluginConfigStore.On("Update", mock.Anything, mock.Anything, mock.Anything).Run(func(args mock.Arguments) { - getCalled = true - input := args.Get(1).(*entity.PluginConfig) - createIfNotExist := args.Get(2).(bool) - assert.Equal(t, tc.wantInput, input) - assert.False(t, createIfNotExist) - }).Return(tc.giveRet, tc.giveErr) - - pluginConfigStore.On("Get", mock.Anything, mock.Anything).Run(func(args mock.Arguments) { - input := args.Get(0).(string) - assert.Equal(t, tc.pluginConfigInput, input) - }).Return(tc.pluginConfigRet, tc.pluginConfigErr) - - h := Handler{pluginConfigStore: pluginConfigStore} - ctx := droplet.NewContext() - ctx.SetInput(tc.giveInput) - ret, err := h.Patch(ctx) - assert.Equal(t, tc.called, getCalled) - assert.Equal(t, tc.wantRet, ret) - assert.Equal(t, tc.wantErr, err) - }) - } -} - -func TestPluginConfigs_Delete(t *testing.T) { - tests := []struct { - caseDesc string - giveInput *BatchDelete - giveErr error - listRet *store.ListOutput - wantInput []string - wantErr error - wantRet interface{} - }{ - { - caseDesc: "delete success", - giveInput: &BatchDelete{ - IDs: "1", - }, - listRet: &store.ListOutput{ - Rows: []interface{}{}, - TotalSize: 0, - }, - wantInput: []string{"1"}, - }, - { - caseDesc: "batch delete success", - giveInput: &BatchDelete{ - IDs: "1,s2", - }, - listRet: &store.ListOutput{ - Rows: []interface{}{}, - TotalSize: 0, - }, - wantInput: []string{"1", "s2"}, - }, - - { - caseDesc: "delete failed - being used by user", - giveInput: &BatchDelete{ - IDs: "001,002", - }, - giveErr: fmt.Errorf("delete failed"), - wantInput: []string{ - "001", - "002", - }, - listRet: &store.ListOutput{ - Rows: []interface{}{ - &entity.Route{BaseInfo: entity.BaseInfo{ID: "a"}}, - &entity.Route{BaseInfo: entity.BaseInfo{ID: "b"}}, - }, - TotalSize: 2, - }, - wantErr: errors.New("please disconnect the route (ID: a) with this plugin config first"), - wantRet: &data.SpecCodeResponse{ - StatusCode: http.StatusBadRequest, - }, - }, - - { - caseDesc: "delete failed", - giveInput: &BatchDelete{ - IDs: "1", - }, - listRet: &store.ListOutput{ - Rows: []interface{}{}, - TotalSize: 0, - }, - giveErr: fmt.Errorf("delete error"), - wantInput: []string{"1"}, - wantRet: handler.SpecCodeResponse(fmt.Errorf("delete error")), - wantErr: fmt.Errorf("delete error"), - }, - } - - for _, tc := range tests { - t.Run(tc.caseDesc, func(t *testing.T) { - getCalled := false - pluginConfigStore := &store.MockInterface{} - pluginConfigStore.On("BatchDelete", mock.Anything, mock.Anything).Run(func(args mock.Arguments) { - getCalled = true - input := args.Get(1).([]string) - assert.Equal(t, tc.wantInput, input) - }).Return(tc.giveErr) - - mockRouteStore := &store.MockInterface{} - mockRouteStore.On("List", mock.Anything).Run(func(args mock.Arguments) { - getCalled = true - }).Return(tc.listRet, nil) - - h := Handler{pluginConfigStore: pluginConfigStore, routeStore: mockRouteStore} - ctx := droplet.NewContext() - ctx.SetInput(tc.giveInput) - ret, err := h.BatchDelete(ctx) - assert.True(t, getCalled) - assert.Equal(t, tc.wantRet, ret) - assert.Equal(t, tc.wantErr, err) - }) - } -} diff --git a/api/internal/handler/proto/proto.go b/api/internal/handler/proto/proto.go deleted file mode 100644 index 6c815e733c..0000000000 --- a/api/internal/handler/proto/proto.go +++ /dev/null @@ -1,260 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one or more - * contributor license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright ownership. - * The ASF licenses this file to You under the Apache License, Version 2.0 - * (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package proto - -import ( - "context" - "encoding/json" - "errors" - "fmt" - "net/http" - "reflect" - "strings" - - "github.com/gin-gonic/gin" - "github.com/shiningrush/droplet" - "github.com/shiningrush/droplet/data" - "github.com/shiningrush/droplet/wrapper" - wgin "github.com/shiningrush/droplet/wrapper/gin" - - "github.com/apisix/manager-api/internal/core/entity" - "github.com/apisix/manager-api/internal/core/store" - "github.com/apisix/manager-api/internal/handler" - "github.com/apisix/manager-api/internal/utils" -) - -type Handler struct { - routeStore store.Interface - serviceStore store.Interface - consumerStore store.Interface - pluginConfigStore store.Interface - globalRuleStore store.Interface - protoStore store.Interface -} - -func NewHandler() (handler.RouteRegister, error) { - return &Handler{ - routeStore: store.GetStore(store.HubKeyRoute), - serviceStore: store.GetStore(store.HubKeyService), - consumerStore: store.GetStore(store.HubKeyConsumer), - pluginConfigStore: store.GetStore(store.HubKeyPluginConfig), - globalRuleStore: store.GetStore(store.HubKeyGlobalRule), - protoStore: store.GetStore(store.HubKeyProto), - }, nil -} - -func (h *Handler) ApplyRoute(r *gin.Engine) { - r.GET("/apisix/admin/proto/:id", wgin.Wraps(h.Get, - wrapper.InputType(reflect.TypeOf(GetInput{})))) - r.GET("/apisix/admin/proto", wgin.Wraps(h.List, - wrapper.InputType(reflect.TypeOf(ListInput{})))) - r.POST("/apisix/admin/proto", wgin.Wraps(h.Create, - wrapper.InputType(reflect.TypeOf(entity.Proto{})))) - r.PUT("/apisix/admin/proto", wgin.Wraps(h.Update, - wrapper.InputType(reflect.TypeOf(UpdateInput{})))) - r.PUT("/apisix/admin/proto/:id", wgin.Wraps(h.Update, - wrapper.InputType(reflect.TypeOf(UpdateInput{})))) - r.PATCH("/apisix/admin/proto/:id", wgin.Wraps(h.Patch, - wrapper.InputType(reflect.TypeOf(PatchInput{})))) - r.PATCH("/apisix/admin/proto/:id/*path", wgin.Wraps(h.Patch, - wrapper.InputType(reflect.TypeOf(PatchInput{})))) - r.DELETE("/apisix/admin/proto/:ids", wgin.Wraps(h.BatchDelete, - wrapper.InputType(reflect.TypeOf(BatchDeleteInput{})))) -} - -var plugins = []string{"grpc-transcode"} - -type GetInput struct { - ID string `auto_read:"id,path" validate:"required"` -} - -func (h *Handler) Get(c droplet.Context) (interface{}, error) { - input := c.Input().(*GetInput) - - r, err := h.protoStore.Get(c.Context(), input.ID) - if err != nil { - return handler.SpecCodeResponse(err), err - } - - return r, nil -} - -type ListInput struct { - Desc string `auto_read:"desc,query"` - store.Pagination -} - -func (h *Handler) List(c droplet.Context) (interface{}, error) { - input := c.Input().(*ListInput) - - ret, err := h.protoStore.List(c.Context(), store.ListInput{ - Predicate: func(obj interface{}) bool { - if input.Desc != "" { - return strings.Contains(obj.(*entity.Proto).Desc, input.Desc) - } - return true - }, - PageSize: input.PageSize, - PageNumber: input.PageNumber, - }) - if err != nil { - return nil, err - } - - return ret, nil -} - -func (h *Handler) Create(c droplet.Context) (interface{}, error) { - input := c.Input().(*entity.Proto) - - // check proto id exist - if input.ID != nil { - protoID := utils.InterfaceToString(input.ID) - ret, err := h.protoStore.Get(c.Context(), protoID) - if err != nil && err != data.ErrNotFound { - return handler.SpecCodeResponse(err), err - } - if ret != nil { - return &data.SpecCodeResponse{StatusCode: http.StatusBadRequest}, errors.New("proto id exists") - } - } - - // create - ret, err := h.protoStore.Create(c.Context(), input) - if err != nil { - return handler.SpecCodeResponse(err), err - } - - return ret, nil -} - -type UpdateInput struct { - ID string `auto_read:"id,path"` - entity.Proto -} - -func (h *Handler) Update(c droplet.Context) (interface{}, error) { - input := c.Input().(*UpdateInput) - - // check if ID in body is equal ID in path - if err := handler.IDCompare(input.ID, input.Proto.ID); err != nil { - return &data.SpecCodeResponse{StatusCode: http.StatusBadRequest}, err - } - - if input.ID != "" { - input.Proto.ID = input.ID - } - - res, err := h.protoStore.Update(c.Context(), &input.Proto, true) - if err != nil { - return handler.SpecCodeResponse(err), err - } - - return res, nil -} - -type PatchInput struct { - ID string `auto_read:"id,path"` - SubPath string `auto_read:"path,path"` - Body []byte `auto_read:"@body"` -} - -func (h *Handler) Patch(c droplet.Context) (interface{}, error) { - input := c.Input().(*PatchInput) - reqBody := input.Body - id := input.ID - subPath := input.SubPath - - stored, err := h.protoStore.Get(c.Context(), id) - if err != nil { - return handler.SpecCodeResponse(err), err - } - - res, err := utils.MergePatch(stored, subPath, reqBody) - - if err != nil { - return handler.SpecCodeResponse(err), err - } - - var proto entity.Proto - if err := json.Unmarshal(res, &proto); err != nil { - return handler.SpecCodeResponse(err), err - } - - ret, err := h.protoStore.Update(c.Context(), &proto, false) - if err != nil { - return handler.SpecCodeResponse(err), err - } - - return ret, nil -} - -type BatchDeleteInput struct { - IDs string `auto_read:"ids,path"` -} - -func (h *Handler) BatchDelete(c droplet.Context) (interface{}, error) { - input := c.Input().(*BatchDeleteInput) - - ids := strings.Split(input.IDs, ",") - checklist := []store.Interface{h.routeStore, h.consumerStore, h.serviceStore, h.pluginConfigStore, h.globalRuleStore} - - for _, id := range ids { - for _, store := range checklist { - if err := h.checkProtoUsed(c.Context(), store, id); err != nil { - return handler.SpecCodeResponse(err), err - } - } - } - - if err := h.protoStore.BatchDelete(c.Context(), ids); err != nil { - return handler.SpecCodeResponse(err), err - } - - return nil, nil -} - -func (h *Handler) checkProtoUsed(ctx context.Context, storeInterface store.Interface, key string) error { - ret, err := storeInterface.List(ctx, store.ListInput{ - Predicate: func(obj interface{}) bool { - record := obj.(entity.GetPlugins) - for _, plugin := range plugins { - if _, ok := record.GetPlugins()[plugin]; ok { - configs := record.GetPlugins()[plugin].(map[string]interface{}) - protoId := utils.InterfaceToString(configs["proto_id"]) - if protoId == key { - return true - } - } - } - return false - }, - Format: func(obj interface{}) interface{} { - return obj.(entity.GetPlugins) - }, - PageSize: 0, - PageNumber: 0, - }) - if err != nil { - return err - } - if ret.TotalSize > 0 { - return fmt.Errorf("proto used check invalid: %s: %s is using this proto", storeInterface.Type(), ret.Rows[0].(entity.GetBaseInfo).GetBaseInfo().ID) - } - return nil - -} diff --git a/api/internal/handler/proto/proto_test.go b/api/internal/handler/proto/proto_test.go deleted file mode 100644 index 942dacdd14..0000000000 --- a/api/internal/handler/proto/proto_test.go +++ /dev/null @@ -1,47 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one or more - * contributor license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright ownership. - * The ASF licenses this file to You under the Apache License, Version 2.0 - * (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package proto - -import ( - "encoding/json" - "testing" - - "github.com/stretchr/testify/assert" - - "github.com/apisix/manager-api/internal/core/entity" -) - -func TestStructUnmarshal(t *testing.T) { - // define and parse data - jsonStr := `{ - "id": 1, - "create_time": 1700000000, - "update_time": 1700000000, - "desc": "desc", - "content": "content" -}` - proto := entity.Proto{} - err := json.Unmarshal([]byte(jsonStr), &proto) - - // asserts - assert.Nil(t, err) - assert.Equal(t, proto.ID, float64(1)) - assert.Equal(t, proto.CreateTime, int64(1700000000)) - assert.Equal(t, proto.UpdateTime, int64(1700000000)) - assert.Equal(t, proto.Desc, "desc") - assert.Equal(t, proto.Content, "content") -} diff --git a/api/internal/handler/route/route.go b/api/internal/handler/route/route.go deleted file mode 100644 index d100c85b40..0000000000 --- a/api/internal/handler/route/route.go +++ /dev/null @@ -1,614 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one or more - * contributor license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright ownership. - * The ASF licenses this file to You under the Apache License, Version 2.0 - * (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package route - -import ( - "encoding/json" - "fmt" - "net/http" - "os" - "path/filepath" - "reflect" - "strconv" - "strings" - - "github.com/gin-gonic/gin" - "github.com/shiningrush/droplet" - "github.com/shiningrush/droplet/data" - "github.com/shiningrush/droplet/wrapper" - wgin "github.com/shiningrush/droplet/wrapper/gin" - lua "github.com/yuin/gopher-lua" - - "github.com/apisix/manager-api/internal/conf" - "github.com/apisix/manager-api/internal/core/entity" - "github.com/apisix/manager-api/internal/core/store" - "github.com/apisix/manager-api/internal/handler" - "github.com/apisix/manager-api/internal/log" - "github.com/apisix/manager-api/internal/utils" - "github.com/apisix/manager-api/internal/utils/consts" -) - -type Handler struct { - routeStore store.Interface - svcStore store.Interface - upstreamStore store.Interface - scriptStore store.Interface -} - -func NewHandler() (handler.RouteRegister, error) { - return &Handler{ - routeStore: store.GetStore(store.HubKeyRoute), - svcStore: store.GetStore(store.HubKeyService), - upstreamStore: store.GetStore(store.HubKeyUpstream), - scriptStore: store.GetStore(store.HubKeyScript), - }, nil -} - -func (h *Handler) ApplyRoute(r *gin.Engine) { - r.GET("/apisix/admin/routes/:id", wgin.Wraps(h.Get, - wrapper.InputType(reflect.TypeOf(GetInput{})))) - r.GET("/apisix/admin/routes", wgin.Wraps(h.List, - wrapper.InputType(reflect.TypeOf(ListInput{})))) - r.POST("/apisix/admin/routes", wgin.Wraps(h.Create, - wrapper.InputType(reflect.TypeOf(entity.Route{})))) - r.PUT("/apisix/admin/routes", wgin.Wraps(h.Update, - wrapper.InputType(reflect.TypeOf(UpdateInput{})))) - r.PUT("/apisix/admin/routes/:id", wgin.Wraps(h.Update, - wrapper.InputType(reflect.TypeOf(UpdateInput{})))) - - r.DELETE("/apisix/admin/routes/:ids", wgin.Wraps(h.BatchDelete, - wrapper.InputType(reflect.TypeOf(BatchDelete{})))) - - r.PATCH("/apisix/admin/routes/:id", wgin.Wraps(h.Patch, - wrapper.InputType(reflect.TypeOf(PatchInput{})))) - r.PATCH("/apisix/admin/routes/:id/*path", wgin.Wraps(h.Patch, - wrapper.InputType(reflect.TypeOf(PatchInput{})))) - - r.GET("/apisix/admin/notexist/routes", wgin.Wraps(h.Exist, - wrapper.InputType(reflect.TypeOf(ExistCheckInput{})))) -} - -type PatchInput struct { - ID string `auto_read:"id,path"` - SubPath string `auto_read:"path,path"` - Body []byte `auto_read:"@body"` -} - -func (h *Handler) Patch(c droplet.Context) (interface{}, error) { - input := c.Input().(*PatchInput) - reqBody := input.Body - ID := input.ID - subPath := input.SubPath - - stored, err := h.routeStore.Get(c.Context(), ID) - if err != nil { - return handler.SpecCodeResponse(err), err - } - - res, err := utils.MergePatch(stored, subPath, reqBody) - if err != nil { - return handler.SpecCodeResponse(err), err - } - - var route entity.Route - err = json.Unmarshal(res, &route) - if err != nil { - return handler.SpecCodeResponse(err), err - } - - ret, err := h.routeStore.Update(c.Context(), &route, false) - if err != nil { - return handler.SpecCodeResponse(err), err - } - - return ret, nil -} - -type GetInput struct { - ID string `auto_read:"id,path" validate:"required"` -} - -// swagger:operation GET /apisix/admin/routes getRouteList -// -// Return the route list according to the specified page number and page size, and can search routes by name and uri. -// -// --- -// produces: -// - application/json -// parameters: -// - name: page -// in: query -// description: page number -// required: false -// type: integer -// - name: page_size -// in: query -// description: page size -// required: false -// type: integer -// - name: name -// in: query -// description: name of route -// required: false -// type: string -// - name: uri -// in: query -// description: uri of route -// required: false -// type: string -// - name: label -// in: query -// description: label of route -// required: false -// type: string -// responses: -// '0': -// description: list response -// schema: -// type: array -// items: -// "$ref": "#/definitions/route" -// default: -// description: unexpected error -// schema: -// "$ref": "#/definitions/ApiError" -func (h *Handler) Get(c droplet.Context) (interface{}, error) { - input := c.Input().(*GetInput) - - r, err := h.routeStore.Get(c.Context(), input.ID) - if err != nil { - return &data.SpecCodeResponse{StatusCode: http.StatusNotFound}, err - } - - //format respond - route := r.(*entity.Route) - script, _ := h.scriptStore.Get(c.Context(), input.ID) - if script != nil { - route.Script = script.(*entity.Script).Script - } - - //format - if route.Upstream != nil && route.Upstream.Nodes != nil { - route.Upstream.Nodes = entity.NodesFormat(route.Upstream.Nodes) - } - - return route, nil -} - -type ListInput struct { - Name string `auto_read:"name,query"` - URI string `auto_read:"uri,query"` - Label string `auto_read:"label,query"` - Status string `auto_read:"status,query"` - Host string `auto_read:"host,query"` - ID string `auto_read:"id,query"` - Desc string `auto_read:"desc,query"` - store.Pagination -} - -func uriContains(obj *entity.Route, uri string) bool { - if strings.Contains(obj.URI, uri) { - return true - } - for _, str := range obj.Uris { - result := strings.Contains(str, uri) - if result { - return true - } - } - return false -} - -func (h *Handler) List(c droplet.Context) (interface{}, error) { - input := c.Input().(*ListInput) - labelMap, err := utils.GenLabelMap(input.Label) - if err != nil { - return &data.SpecCodeResponse{StatusCode: http.StatusBadRequest}, - fmt.Errorf("%s: \"%s\"", err.Error(), input.Label) - } - - ret, err := h.routeStore.List(c.Context(), store.ListInput{ - Predicate: func(obj interface{}) bool { - if input.Name != "" && !strings.Contains(obj.(*entity.Route).Name, input.Name) { - return false - } - - if input.URI != "" && !uriContains(obj.(*entity.Route), input.URI) { - return false - } - - if input.Label != "" && !utils.LabelContains(obj.(*entity.Route).Labels, labelMap) { - return false - } - - if input.Status != "" && strconv.Itoa(int(obj.(*entity.Route).Status)) != input.Status { - return false - } - - if input.Host != "" && !strings.Contains(obj.(*entity.Route).Host, input.Host) { - return false - } - - if input.Desc != "" && !strings.Contains(obj.(*entity.Route).Desc, input.Desc) { - return false - } - - if obj != nil && obj.(*entity.Route) != nil && obj.(*entity.Route).ID != nil && input.ID != "" { - if !strings.Contains(utils.InterfaceToString(obj.(*entity.Route).ID), input.ID) { - return false // IDs do not match, so object should not be included in the filtered result - } - } - - return true - }, - Format: func(obj interface{}) interface{} { - route := obj.(*entity.Route) - if route.Upstream != nil && route.Upstream.Nodes != nil { - route.Upstream.Nodes = entity.NodesFormat(route.Upstream.Nodes) - } - return route - }, - PageSize: input.PageSize, - PageNumber: input.PageNumber, - }) - - if err != nil { - return nil, err - } - - //format respond - var route *entity.Route - for i, item := range ret.Rows { - route = item.(*entity.Route) - id := utils.InterfaceToString(route.ID) - script, _ := h.scriptStore.Get(c.Context(), id) - if script != nil { - route.Script = script.(*entity.Script).Script - } - ret.Rows[i] = route - } - - return ret, nil -} - -func generateLuaCode(script map[string]interface{}) (string, error) { - scriptString, err := json.Marshal(script) - if err != nil { - return "", err - } - workDir, err := filepath.Abs(conf.WorkDir) - if err != nil { - return "", err - } - libDir := filepath.Join(workDir, "dag-to-lua/") - if err := os.Chdir(libDir); err != nil { - log.Errorf("Chdir to libDir failed: %s", err) - return "", err - } - - defer func() { - if err := os.Chdir(workDir); err != nil { - log.Errorf("Chdir to workDir failed: %s", err) - } - }() - - L := lua.NewState() - defer L.Close() - - if err := L.DoString(` - local dag_to_lua = require 'dag-to-lua' - local conf = [==[` + string(scriptString) + `]==] - code = dag_to_lua.generate(conf) - `); err != nil { - return "", err - } - - code := L.GetGlobal("code") - - return code.String(), nil -} - -func (h *Handler) Create(c droplet.Context) (interface{}, error) { - input := c.Input().(*entity.Route) - //check depend - if input.ServiceID != nil { - serviceID := utils.InterfaceToString(input.ServiceID) - _, err := h.svcStore.Get(c.Context(), serviceID) - if err != nil { - if err == data.ErrNotFound { - return &data.SpecCodeResponse{StatusCode: http.StatusBadRequest}, - fmt.Errorf(consts.IDNotFound, "service", input.ServiceID) - } - return &data.SpecCodeResponse{StatusCode: http.StatusBadRequest}, err - } - } - if input.UpstreamID != nil { - upstreamID := utils.InterfaceToString(input.UpstreamID) - _, err := h.upstreamStore.Get(c.Context(), upstreamID) - if err != nil { - if err == data.ErrNotFound { - return &data.SpecCodeResponse{StatusCode: http.StatusBadRequest}, - fmt.Errorf("upstream id: %s not found", input.UpstreamID) - } - return &data.SpecCodeResponse{StatusCode: http.StatusBadRequest}, err - } - } - - // If route's script_id is set, it must be equals to the route's id. - if input.ScriptID != nil && (utils.InterfaceToString(input.ID) != utils.InterfaceToString(input.ScriptID)) { - return &data.SpecCodeResponse{StatusCode: http.StatusBadRequest}, - fmt.Errorf("script_id must be the same as id") - } - - if input.Script != nil { - if utils.InterfaceToString(input.ID) == "" { - input.ID = utils.GetFlakeUidStr() - } - script := &entity.Script{} - script.ID = utils.InterfaceToString(input.ID) - script.Script = input.Script - - var err error - // Explicitly to lua if input script is of the map type, otherwise - // it will always represent a piece of lua code of the string type. - if scriptConf, ok := input.Script.(map[string]interface{}); ok { - // For lua code of map type, syntax validation is done by - // the generateLuaCode function - input.Script, err = generateLuaCode(scriptConf) - if err != nil { - return &data.SpecCodeResponse{StatusCode: http.StatusBadRequest}, err - } - } else { - // For lua code of string type, use utility func to syntax validation - err = utils.ValidateLuaCode(input.Script.(string)) - if err != nil { - return &data.SpecCodeResponse{StatusCode: http.StatusBadRequest}, err - } - } - - //save original conf - if _, err = h.scriptStore.Create(c.Context(), script); err != nil { - return nil, err - } - - // After saving the Script entity, always set route's script_id - // the same as route's id. - input.ScriptID = input.ID - } else { - // If script is unset, script_id must be unset neither. - if input.ScriptID != nil { - return &data.SpecCodeResponse{StatusCode: http.StatusBadRequest}, - fmt.Errorf("script_id cannot be set if script is unset") - } - } - - // check name existed - ret, err := handler.NameExistCheck(c.Context(), h.routeStore, "route", input.Name, nil) - if err != nil { - return ret, err - } - - // create - res, err := h.routeStore.Create(c.Context(), input) - if err != nil { - return handler.SpecCodeResponse(err), err - } - - return res, nil -} - -type UpdateInput struct { - ID string `auto_read:"id,path"` - entity.Route -} - -func (h *Handler) Update(c droplet.Context) (interface{}, error) { - input := c.Input().(*UpdateInput) - - // check if ID in body is equal ID in path - if err := handler.IDCompare(input.ID, input.Route.ID); err != nil { - return &data.SpecCodeResponse{StatusCode: http.StatusBadRequest}, err - } - - // if has id in path, use it - if input.ID != "" { - input.Route.ID = input.ID - } - - //check depend - if input.ServiceID != nil { - serviceID := utils.InterfaceToString(input.ServiceID) - _, err := h.svcStore.Get(c.Context(), serviceID) - if err != nil { - if err == data.ErrNotFound { - return &data.SpecCodeResponse{StatusCode: http.StatusBadRequest}, - fmt.Errorf(consts.IDNotFound, "service", input.ServiceID) - } - return &data.SpecCodeResponse{StatusCode: http.StatusBadRequest}, err - } - } - if input.UpstreamID != nil { - upstreamID := utils.InterfaceToString(input.UpstreamID) - _, err := h.upstreamStore.Get(c.Context(), upstreamID) - if err != nil { - if err == data.ErrNotFound { - return &data.SpecCodeResponse{StatusCode: http.StatusBadRequest}, - fmt.Errorf("upstream id: %s not found", input.UpstreamID) - } - return &data.SpecCodeResponse{StatusCode: http.StatusBadRequest}, err - } - } - - // If route's script_id is set, it must be equals to the route's id. - if input.Route.ScriptID != nil && (utils.InterfaceToString(input.ID) != utils.InterfaceToString(input.Route.ScriptID)) { - return &data.SpecCodeResponse{StatusCode: http.StatusBadRequest}, - fmt.Errorf("script_id must be the same as id") - } - - if input.Script != nil { - script := &entity.Script{} - script.ID = input.ID - script.Script = input.Script - - var err error - // Explicitly to lua if input script is of the map type, otherwise - // it will always represent a piece of lua code of the string type. - if scriptConf, ok := input.Script.(map[string]interface{}); ok { - // For lua code of map type, syntax validation is done by - // the generateLuaCode function - input.Route.Script, err = generateLuaCode(scriptConf) - if err != nil { - return &data.SpecCodeResponse{StatusCode: http.StatusBadRequest}, err - } - } else { - // For lua code of string type, use utility func to syntax validation - err = utils.ValidateLuaCode(input.Script.(string)) - if err != nil { - return &data.SpecCodeResponse{StatusCode: http.StatusBadRequest}, err - } - } - - //save original conf - if _, err = h.scriptStore.Update(c.Context(), script, true); err != nil { - //if not exists, create - if err.Error() == fmt.Sprintf("key: %s is not found", script.ID) { - if _, err := h.scriptStore.Create(c.Context(), script); err != nil { - return handler.SpecCodeResponse(err), err - } - } else { - return handler.SpecCodeResponse(err), err - } - } - - // After updating the Script entity, always set route's script_id - // the same as route's id. - input.Route.ScriptID = input.ID - } else { - // If script is unset, script_id must be unset neither. - if input.Route.ScriptID != nil { - return &data.SpecCodeResponse{StatusCode: http.StatusBadRequest}, - fmt.Errorf("script_id cannot be set if script is unset") - } - //remove exists script - id := utils.InterfaceToString(input.Route.ID) - script, _ := h.scriptStore.Get(c.Context(), id) - if script != nil { - if err := h.scriptStore.BatchDelete(c.Context(), strings.Split(id, ",")); err != nil { - log.Warnf("delete script %s failed", input.Route.ID) - } - } - } - - // check name existed - ret, err := handler.NameExistCheck(c.Context(), h.routeStore, "route", input.Name, input.ID) - if err != nil { - return ret, err - } - - // create - res, err := h.routeStore.Update(c.Context(), &input.Route, true) - if err != nil { - return handler.SpecCodeResponse(err), err - } - - return res, nil -} - -type BatchDelete struct { - IDs string `auto_read:"ids,path"` -} - -func (h *Handler) BatchDelete(c droplet.Context) (interface{}, error) { - input := c.Input().(*BatchDelete) - - //delete route - if err := h.routeStore.BatchDelete(c.Context(), strings.Split(input.IDs, ",")); err != nil { - return handler.SpecCodeResponse(err), err - } - - //delete stored script - if err := h.scriptStore.BatchDelete(c.Context(), strings.Split(input.IDs, ",")); err != nil { - //try again - log.Warn("try to delete script %s again", input.IDs) - if err := h.scriptStore.BatchDelete(c.Context(), strings.Split(input.IDs, ",")); err != nil { - return nil, nil - } - } - - return nil, nil -} - -type ExistCheckInput struct { - Name string `auto_read:"name,query"` - Exclude string `auto_read:"exclude,query"` -} - -// swagger:operation GET /apisix/admin/notexist/routes checkRouteExist -// -// Return result of route exists checking by name and exclude id. -// -// --- -// produces: -// - application/json -// parameters: -// - name: name -// in: query -// description: name of route -// required: false -// type: string -// - name: exclude -// in: query -// description: id of route that exclude checking -// required: false -// type: string -// responses: -// '0': -// description: route not exists -// schema: -// "$ref": "#/definitions/ApiError" -// default: -// description: unexpected error -// schema: -// "$ref": "#/definitions/ApiError" -func (h *Handler) Exist(c droplet.Context) (interface{}, error) { - input := c.Input().(*ExistCheckInput) - name := input.Name - exclude := input.Exclude - - ret, err := h.routeStore.List(c.Context(), store.ListInput{ - Predicate: func(obj interface{}) bool { - r := obj.(*entity.Route) - if r.Name == name && r.ID != exclude { - return true - } - - return false - }, - PageSize: 0, - PageNumber: 0, - }) - if err != nil { - return nil, err - } - - if ret.TotalSize > 0 { - return &data.SpecCodeResponse{StatusCode: http.StatusBadRequest}, - consts.InvalidParam("Route name is reduplicate") - } - - return nil, nil -} diff --git a/api/internal/handler/route/route_test.go b/api/internal/handler/route/route_test.go deleted file mode 100644 index aab0731acd..0000000000 --- a/api/internal/handler/route/route_test.go +++ /dev/null @@ -1,1667 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one or more - * contributor license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright ownership. - * The ASF licenses this file to You under the Apache License, Version 2.0 - * (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package route - -import ( - "encoding/json" - "errors" - "fmt" - "net/http" - "testing" - - "github.com/shiningrush/droplet" - "github.com/shiningrush/droplet/data" - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/mock" - - "github.com/apisix/manager-api/internal/core/entity" - "github.com/apisix/manager-api/internal/core/store" - "github.com/apisix/manager-api/internal/handler" - "github.com/apisix/manager-api/internal/utils/consts" -) - -type testCase struct { - caseDesc string - giveInput interface{} - mockInput interface{} - mockRet interface{} - mockErr interface{} - wantRet interface{} - wantErr interface{} - called bool - serviceInput string - scriptRet interface{} - scriptErr error - serviceRet interface{} - serviceErr error - upstreamRet interface{} - upstreamErr error - nameExistRet []interface{} -} - -var DagScript = ` -{ - "rule":{ - "root":"451106f8-560c-43a4-acf2-2a6ed0ea57b8", - "451106f8-560c-43a4-acf2-2a6ed0ea57b8":[ - [ - "code == 403", - "b93d622c-92ef-48b4-b6bb-57e1ce893ee3" - ], - [ - "", - "988ef5c2-c896-4606-a666-3d4cbe24a731" - ] - ] - }, - "conf":{ - "451106f8-560c-43a4-acf2-2a6ed0ea57b8":{ - "name":"uri-blocker", - "conf":{ - "block_rules":[ - "root.exe", - "root.m+" - ], - "rejected_code":403 - } - }, - "988ef5c2-c896-4606-a666-3d4cbe24a731":{ - "name":"kafka-logger", - "conf":{ - "batch_max_size":1000, - "broker_list":{ - - }, - "buffer_duration":60, - "inactive_timeout":5, - "include_req_body":false, - "kafka_topic":"1", - "key":"2", - "max_retry_count":0, - "name":"kafka logger", - "retry_delay":1, - "timeout":3 - } - }, - "b93d622c-92ef-48b4-b6bb-57e1ce893ee3":{ - "name":"fault-injection", - "conf":{ - "abort":{ - "body":"200", - "http_status":300 - }, - "delay":{ - "duration":500 - } - } - } - }, - "chart":{ - "hovered":{ - - }, - "links":{ - "3a110c30-d6f3-40b1-a8ac-b828cfaa2489":{ - "from":{ - "nodeId":"3365eca3-4bc8-4769-bab3-1485dfd6a43c", - "portId":"port3" - }, - "id":"3a110c30-d6f3-40b1-a8ac-b828cfaa2489", - "to":{ - "nodeId":"b93d622c-92ef-48b4-b6bb-57e1ce893ee3", - "portId":"port1" - } - }, - "c1958993-c1ef-44b1-bb32-7fc6f34870c2":{ - "from":{ - "nodeId":"3365eca3-4bc8-4769-bab3-1485dfd6a43c", - "portId":"port2" - }, - "id":"c1958993-c1ef-44b1-bb32-7fc6f34870c2", - "to":{ - "nodeId":"988ef5c2-c896-4606-a666-3d4cbe24a731", - "portId":"port1" - } - }, - "f9c42bf6-c8aa-4e86-8498-8dfbc5c53c23":{ - "from":{ - "nodeId":"451106f8-560c-43a4-acf2-2a6ed0ea57b8", - "portId":"port2" - }, - "id":"f9c42bf6-c8aa-4e86-8498-8dfbc5c53c23", - "to":{ - "nodeId":"3365eca3-4bc8-4769-bab3-1485dfd6a43c", - "portId":"port1" - } - } - }, - "nodes":{ - "3365eca3-4bc8-4769-bab3-1485dfd6a43c":{ - "id":"3365eca3-4bc8-4769-bab3-1485dfd6a43c", - "orientation":0, - "ports":{ - "port1":{ - "id":"port1", - "position":{ - "x":107, - "y":0 - }, - "type":"input" - }, - "port2":{ - "id":"port2", - "position":{ - "x":92, - "y":96 - }, - "properties":{ - "value":"no" - }, - "type":"output" - }, - "port3":{ - "id":"port3", - "position":{ - "x":122, - "y":96 - }, - "properties":{ - "value":"yes" - }, - "type":"output" - } - }, - "position":{ - "x":750.2627969928922, - "y":301.0370335799397 - }, - "properties":{ - "customData":{ - "name":"code == 403", - "type":1 - } - }, - "size":{ - "height":96, - "width":214 - }, - "type":"ๅˆคๆ–ญๆกไปถ" - }, - "451106f8-560c-43a4-acf2-2a6ed0ea57b8":{ - "id":"451106f8-560c-43a4-acf2-2a6ed0ea57b8", - "orientation":0, - "ports":{ - "port1":{ - "id":"port1", - "position":{ - "x":100, - "y":0 - }, - "properties":{ - "custom":"property" - }, - "type":"input" - }, - "port2":{ - "id":"port2", - "position":{ - "x":100, - "y":96 - }, - "properties":{ - "custom":"property" - }, - "type":"output" - } - }, - "position":{ - "x":741.5684544145346, - "y":126.75879247285502 - }, - "properties":{ - "customData":{ - "data":{ - "block_rules":[ - "root.exe", - "root.m+" - ], - "rejected_code":403 - }, - "name":"uri-blocker", - "type":0 - } - }, - "size":{ - "height":96, - "width":201 - }, - "type":"uri-blocker" - }, - "988ef5c2-c896-4606-a666-3d4cbe24a731":{ - "id":"988ef5c2-c896-4606-a666-3d4cbe24a731", - "orientation":0, - "ports":{ - "port1":{ - "id":"port1", - "position":{ - "x":106, - "y":0 - }, - "properties":{ - "custom":"property" - }, - "type":"input" - }, - "port2":{ - "id":"port2", - "position":{ - "x":106, - "y":96 - }, - "properties":{ - "custom":"property" - }, - "type":"output" - } - }, - "position":{ - "x":607.9687500000001, - "y":471.17788461538447 - }, - "properties":{ - "customData":{ - "data":{ - "batch_max_size":1000, - "broker_list":{ - - }, - "buffer_duration":60, - "inactive_timeout":5, - "include_req_body":false, - "kafka_topic":"1", - "key":"2", - "max_retry_count":0, - "name":"kafka logger", - "retry_delay":1, - "timeout":3 - }, - "name":"kafka-logger", - "type":0 - } - }, - "size":{ - "height":96, - "width":212 - }, - "type":"kafka-logger" - }, - "b93d622c-92ef-48b4-b6bb-57e1ce893ee3":{ - "id":"b93d622c-92ef-48b4-b6bb-57e1ce893ee3", - "orientation":0, - "ports":{ - "port1":{ - "id":"port1", - "position":{ - "x":110, - "y":0 - }, - "properties":{ - "custom":"property" - }, - "type":"input" - }, - "port2":{ - "id":"port2", - "position":{ - "x":110, - "y":96 - }, - "properties":{ - "custom":"property" - }, - "type":"output" - } - }, - "position":{ - "x":988.9074986362261, - "y":478.62041800736495 - }, - "properties":{ - "customData":{ - "data":{ - "abort":{ - "body":"200", - "http_status":300 - }, - "delay":{ - "duration":500 - } - }, - "name":"fault-injection", - "type":0 - } - }, - "size":{ - "height":96, - "width":219 - }, - "type":"fault-injection" - } - }, - "offset":{ - "x":-376.83, - "y":87.98 - }, - "scale":0.832, - "selected":{ - "id":"b93d622c-92ef-48b4-b6bb-57e1ce893ee3", - "type":"node" - } - } -}` - -func TestRoute_Get(t *testing.T) { - tests := []testCase{ - { - caseDesc: "route: get success", - giveInput: &GetInput{ID: "r1"}, - mockInput: "r1", - mockRet: &entity.Route{ - BaseInfo: entity.BaseInfo{ - ID: "r1", - }, - URI: "/test", - }, - mockErr: nil, - wantRet: &entity.Route{ - BaseInfo: entity.BaseInfo{ - ID: "r1", - }, - Script: "script", - URI: "/test", - }, - wantErr: nil, - called: true, - scriptRet: &entity.Script{ID: "r1", Script: "script"}, - scriptErr: nil, - }, - { - caseDesc: "route: store get failed", - giveInput: &GetInput{ID: "failed_key"}, - mockInput: "failed_key", - mockRet: nil, - mockErr: fmt.Errorf("get failed"), - wantRet: &data.SpecCodeResponse{ - StatusCode: http.StatusNotFound, - }, - wantErr: fmt.Errorf("get failed"), - called: true, - }, - { - caseDesc: "script: store get failed", - giveInput: &GetInput{ID: "failed_key"}, - mockInput: "failed_key", - mockRet: nil, - mockErr: fmt.Errorf("get failed"), - wantRet: &data.SpecCodeResponse{ - StatusCode: http.StatusNotFound, - }, - wantErr: fmt.Errorf("get failed"), - scriptErr: errors.New("get failed"), - called: true, - }, - } - - for _, tc := range tests { - getCalled := false - mStore := &store.MockInterface{} - - mStore.On("Get", mock.Anything, mock.Anything).Run(func(args mock.Arguments) { - getCalled = true - assert.Equal(t, tc.mockInput, args.Get(0)) - }).Return(tc.mockRet, tc.mockErr) - - sStore := &store.MockInterface{} - sStore.On("Get", mock.Anything, mock.Anything).Return(tc.scriptRet, tc.scriptErr) - - h := Handler{routeStore: mStore, scriptStore: sStore} - ctx := droplet.NewContext() - ctx.SetInput(tc.giveInput) - ret, err := h.Get(ctx) - assert.True(t, tc.called, getCalled) - assert.Equal(t, tc.wantRet, ret) - assert.Equal(t, tc.wantErr, err) - } -} - -func TestRoute_List(t *testing.T) { - mockData := []*entity.Route{ - { - BaseInfo: entity.BaseInfo{CreateTime: 1609742634}, - Name: "r1", - URI: "/test_r1", - Labels: map[string]string{ - "version": "v1", - "build": "16", - }, - Upstream: &entity.UpstreamDef{ - Nodes: []interface{}{ - map[string]interface{}{ - "host": "39.97.63.215", - "port": float64(80), - "weight": float64(1), - "priority": float64(10), - }, - }, - }, - }, - { - BaseInfo: entity.BaseInfo{CreateTime: 1609742635}, - Name: "r2", - URI: "/test_r2", - Labels: map[string]string{ - "version": "v1", - "build": "16", - }, - }, - { - BaseInfo: entity.BaseInfo{CreateTime: 1609742636}, - Name: "route_test", - URI: "/test_route_test", - Labels: map[string]string{ - "version": "v2", - "build": "17", - }, - }, - { - BaseInfo: entity.BaseInfo{CreateTime: 1609742636}, - Name: "test_route", - URI: "/test_test_route", - Labels: map[string]string{ - "version": "v2", - "build": "17", - "extra": "test", - }, - }, - } - - tests := []testCase{ - { - caseDesc: "list all route", - giveInput: &ListInput{ - Pagination: store.Pagination{ - PageSize: 10, - PageNumber: 10, - }, - }, - mockInput: store.ListInput{ - PageSize: 10, - PageNumber: 10, - }, - wantRet: &store.ListOutput{ - Rows: []interface{}{ - mockData[0], - mockData[1], - mockData[2], - mockData[3], - }, - TotalSize: 4, - }, - scriptRet: &entity.Script{ID: "r1", Script: "script"}, - called: true, - }, - { - caseDesc: "list routes with name", - giveInput: &ListInput{ - Name: "route", - Pagination: store.Pagination{ - PageSize: 10, - PageNumber: 10, - }, - }, - mockInput: store.ListInput{ - PageSize: 10, - PageNumber: 10, - }, - wantRet: &store.ListOutput{ - Rows: []interface{}{ - mockData[2], - mockData[3], - }, - TotalSize: 2, - }, - scriptRet: &entity.Script{ID: "r1", Script: "script"}, - called: true, - }, - { - caseDesc: "list routes with uri", - giveInput: &ListInput{ - URI: "test_r2", - Pagination: store.Pagination{ - PageSize: 10, - PageNumber: 10, - }, - }, - mockInput: store.ListInput{ - PageSize: 10, - PageNumber: 10, - }, - wantRet: &store.ListOutput{ - Rows: []interface{}{ - mockData[1], - }, - TotalSize: 1, - }, - scriptRet: &entity.Script{ID: "r1", Script: "script"}, - called: true, - }, - { - caseDesc: "list routes with label", - giveInput: &ListInput{ - Label: "version:v1", - Pagination: store.Pagination{ - PageSize: 10, - PageNumber: 10, - }, - }, - mockInput: store.ListInput{ - PageSize: 10, - PageNumber: 10, - }, - wantRet: &store.ListOutput{ - Rows: []interface{}{ - mockData[0], - mockData[1], - }, - TotalSize: 2, - }, - scriptRet: &entity.Script{ID: "s1", Script: "script"}, - called: true, - }, - { - caseDesc: "list routes with label", - giveInput: &ListInput{ - Label: "extra", - Pagination: store.Pagination{ - PageSize: 10, - PageNumber: 10, - }, - }, - mockInput: store.ListInput{ - PageSize: 10, - PageNumber: 10, - }, - wantRet: &store.ListOutput{ - Rows: []interface{}{ - mockData[3], - }, - TotalSize: 1, - }, - scriptRet: &entity.Script{ID: "s1", Script: "script"}, - called: true, - }, - { - caseDesc: "list routes and test format", - giveInput: &ListInput{ - Name: "r1", - Pagination: store.Pagination{ - PageSize: 10, - PageNumber: 10, - }, - }, - mockInput: store.ListInput{ - PageSize: 10, - PageNumber: 10, - }, - wantRet: &store.ListOutput{ - Rows: []interface{}{ - &entity.Route{ - BaseInfo: entity.BaseInfo{CreateTime: 1609742634}, - Name: "r1", - URI: "/test_r1", - Labels: map[string]string{ - "version": "v1", - "build": "16", - }, - Script: "script", - Upstream: &entity.UpstreamDef{ - Nodes: []*entity.Node{ - { - Host: "39.97.63.215", - Port: 80, - Weight: 1, - Priority: 10, - }, - }, - }, - }, - }, - - TotalSize: 1, - }, - called: true, - }, - } - - for _, tc := range tests { - t.Run(tc.caseDesc, func(t *testing.T) { - getCalled := false - mStore := &store.MockInterface{} - mStore.On("List", mock.Anything).Run(func(args mock.Arguments) { - getCalled = true - input := args.Get(0).(store.ListInput) - mockInput := tc.mockInput.(store.ListInput) - assert.Equal(t, mockInput.PageSize, input.PageSize) - assert.Equal(t, mockInput.PageNumber, input.PageNumber) - }).Return(func(input store.ListInput) *store.ListOutput { - var returnData []interface{} - for _, c := range mockData { - if input.Predicate(c) { - if input.Format == nil { - returnData = append(returnData, c) - continue - } - - returnData = append(returnData, input.Format(c)) - } - } - - return &store.ListOutput{ - Rows: returnData, - TotalSize: len(returnData), - } - }, tc.mockErr) - - sStore := &store.MockInterface{} - sStore.On("Get", mock.Anything, mock.Anything).Return(tc.scriptRet, tc.scriptErr) - - h := Handler{routeStore: mStore, scriptStore: sStore} - ctx := droplet.NewContext() - ctx.SetInput(tc.giveInput) - - ret, err := h.List(ctx) - assert.Equal(t, tc.called, getCalled) - assert.Equal(t, tc.wantRet, ret) - assert.Equal(t, tc.wantErr, err) - }) - } -} - -func TestRoute_Create(t *testing.T) { - scriptMap := make(map[string]interface{}) - - err := json.Unmarshal([]byte(DagScript), &scriptMap) - assert.Nil(t, err) - - luaCode, err := generateLuaCode(scriptMap) - assert.Nil(t, err) - - tests := []testCase{ - { - caseDesc: "create route success", - giveInput: &entity.Route{ - BaseInfo: entity.BaseInfo{ - ID: "s1", - CreateTime: 1609746531, - }, - Name: "s1", - Desc: "test_route", - UpstreamID: "u1", - ServiceID: "s1", - Script: "", - Labels: map[string]string{ - "version": "v1", - }, - }, - mockInput: &entity.Route{ - BaseInfo: entity.BaseInfo{ - ID: "s1", - CreateTime: 1609746531, - }, - Name: "s1", - Desc: "test_route", - UpstreamID: "u1", - ServiceID: "s1", - ScriptID: "s1", - Script: "", - Labels: map[string]string{ - "version": "v1", - }, - }, - mockRet: &entity.Route{ - BaseInfo: entity.BaseInfo{ - ID: "s1", - CreateTime: 1609746531, - }, - Name: "s1", - Desc: "test_route", - UpstreamID: "u1", - ServiceID: "s1", - ScriptID: "s1", - Script: "", - Labels: map[string]string{ - "version": "v1", - }, - }, - serviceRet: "service", - serviceErr: nil, - upstreamRet: "upstream", - upstreamErr: nil, - scriptRet: "script", - scriptErr: nil, - wantRet: &entity.Route{ - BaseInfo: entity.BaseInfo{ - ID: "s1", - CreateTime: 1609746531, - }, - Name: "s1", - Desc: "test_route", - UpstreamID: "u1", - ServiceID: "s1", - ScriptID: "s1", - Script: "", - Labels: map[string]string{ - "version": "v1", - }, - }, - wantErr: nil, - called: true, - }, - { - caseDesc: "create route failed, service not found", - giveInput: &entity.Route{ - BaseInfo: entity.BaseInfo{ - ID: "s2", - CreateTime: 1609746531, - }, - Name: "s1", - Desc: "test_route", - UpstreamID: "u1", - ServiceID: "not_found", - Script: "", - Labels: map[string]string{ - "version": "v1", - }, - }, - wantRet: &data.SpecCodeResponse{StatusCode: http.StatusBadRequest}, - wantErr: fmt.Errorf("service id: not_found not found"), - serviceRet: nil, - serviceErr: data.ErrNotFound, - }, - { - caseDesc: "create route failed, service store get error", - giveInput: &entity.Route{ - BaseInfo: entity.BaseInfo{ - ID: "r1", - CreateTime: 1609746531, - }, - Name: "r1", - Desc: "test route", - UpstreamID: "r1", - // mock store will return err if service is s3 - ServiceID: "error", - Script: "", - Labels: map[string]string{ - "version": "v1", - }, - }, - wantRet: &data.SpecCodeResponse{StatusCode: http.StatusBadRequest}, - wantErr: errors.New("service error"), - serviceRet: nil, - serviceErr: errors.New("service error"), - called: false, - }, - { - caseDesc: "create route failed, upstream not found", - giveInput: &entity.Route{ - BaseInfo: entity.BaseInfo{ - ID: "s2", - CreateTime: 1609746531, - }, - Name: "s1", - Desc: "test_route", - UpstreamID: "not_found", - ServiceID: "s2", - Script: "", - Labels: map[string]string{ - "version": "v1", - }, - }, - wantRet: &data.SpecCodeResponse{StatusCode: http.StatusBadRequest}, - wantErr: fmt.Errorf("upstream id: not_found not found"), - upstreamErr: data.ErrNotFound, - called: false, - }, - { - caseDesc: "create route failed, upstream store get error", - giveInput: &entity.Route{ - BaseInfo: entity.BaseInfo{ - ID: "s2", - CreateTime: 1609746531, - }, - Name: "s1", - Desc: "test_route", - UpstreamID: "error", - ServiceID: "s2", - Script: "", - Labels: map[string]string{ - "version": "v1", - }, - }, - wantRet: &data.SpecCodeResponse{StatusCode: http.StatusBadRequest}, - wantErr: errors.New("upstream error"), - upstreamErr: errors.New("upstream error"), - called: false, - }, - { - caseDesc: "create route failed, script create error", - giveInput: &entity.Route{ - BaseInfo: entity.BaseInfo{ - ID: "s2", - CreateTime: 1609746531, - }, - Name: "s1", - Desc: "test_route", - UpstreamID: "u1", - ServiceID: "s2", - Script: "", - Labels: map[string]string{ - "version": "v1", - }, - }, - wantRet: &data.SpecCodeResponse{StatusCode: http.StatusBadRequest}, - wantErr: fmt.Errorf("upstream error"), - serviceErr: fmt.Errorf("upstream error"), - called: false, - }, - { - caseDesc: "create route success with script", - giveInput: &entity.Route{ - BaseInfo: entity.BaseInfo{ - ID: "s1", - CreateTime: 1609746531, - }, - Name: "s1", - Desc: "test_route", - UpstreamID: "u1", - ServiceID: "s1", - Script: scriptMap, - Labels: map[string]string{ - "version": "v1", - }, - }, - mockInput: &entity.Route{ - BaseInfo: entity.BaseInfo{ - ID: "s1", - CreateTime: 1609746531, - }, - Name: "s1", - Desc: "test_route", - UpstreamID: "u1", - ServiceID: "s1", - Script: luaCode, - ScriptID: "s1", - Labels: map[string]string{ - "version": "v1", - }, - }, - mockRet: &entity.Route{ - BaseInfo: entity.BaseInfo{ - ID: "s1", - CreateTime: 1609746531, - }, - Name: "s1", - Desc: "test_route", - UpstreamID: "u1", - ServiceID: "s1", - Script: luaCode, - ScriptID: "s1", - Labels: map[string]string{ - "version": "v1", - }, - }, - serviceRet: "service", - serviceErr: nil, - upstreamRet: "upstream", - upstreamErr: nil, - scriptRet: "script", - scriptErr: nil, - wantRet: &entity.Route{ - BaseInfo: entity.BaseInfo{ - ID: "s1", - CreateTime: 1609746531, - }, - Name: "s1", - Desc: "test_route", - UpstreamID: "u1", - ServiceID: "s1", - Script: luaCode, - ScriptID: "s1", - Labels: map[string]string{ - "version": "v1", - }, - }, - wantErr: nil, - called: true, - }, - } - - for _, tc := range tests { - t.Run(tc.caseDesc, func(t *testing.T) { - getCalled := false - - mStore := &store.MockInterface{} - mStore.On("Create", mock.Anything, mock.Anything).Run(func(args mock.Arguments) { - getCalled = true - route := args.Get(1).(*entity.Route) - assert.Equal(t, tc.mockInput, route) - }).Return(tc.mockRet, tc.mockErr) - - mStore.On("List", mock.Anything, mock.Anything).Run(func(args mock.Arguments) { - }).Return(func(input store.ListInput) *store.ListOutput { - return &store.ListOutput{ - Rows: tc.nameExistRet, - TotalSize: len(tc.nameExistRet), - } - }, nil) - - svcStore := &store.MockInterface{} - svcStore.On("Get", mock.Anything, mock.Anything).Return(tc.serviceRet, tc.serviceErr) - - uStore := &store.MockInterface{} - uStore.On("Get", mock.Anything, mock.Anything).Return(tc.upstreamRet, tc.upstreamErr) - - scriptStore := &store.MockInterface{} - scriptStore.On("Create", mock.Anything, mock.Anything).Return(tc.scriptRet, tc.serviceErr) - - h := Handler{routeStore: mStore, svcStore: svcStore, upstreamStore: uStore, scriptStore: scriptStore} - - ctx := droplet.NewContext() - ctx.SetInput(tc.giveInput) - ret, err := h.Create(ctx) - assert.Equal(t, tc.called, getCalled) - assert.Equal(t, tc.wantRet, ret) - assert.Equal(t, tc.wantErr, err) - }) - } -} - -func TestRoute_Update(t *testing.T) { - luaScript := "local _M = {} \n function _M.access(api_ctx) \n ngx.log(ngx.WARN,\"hit access phase\") \n end \nreturn _M" - scriptMap := make(map[string]interface{}) - err := json.Unmarshal([]byte(DagScript), &scriptMap) - assert.Nil(t, err) - - luaCode, err := generateLuaCode(scriptMap) - assert.Nil(t, err) - tests := []testCase{ - { - caseDesc: "update success with script", - giveInput: &UpdateInput{ - ID: "r1", - Route: entity.Route{ - Name: "r1", - Desc: "updated route", - UpstreamID: "u2", - Script: luaScript, - ServiceID: "s1", - Labels: map[string]string{ - "version": "v2", - }, - }, - }, - mockInput: &entity.Route{ - BaseInfo: entity.BaseInfo{ - ID: "r1", - }, - Name: "r1", - Desc: "updated route", - UpstreamID: "u2", - Script: luaScript, - ScriptID: "r1", - ServiceID: "s1", - Labels: map[string]string{ - "version": "v2", - }, - }, - mockRet: &entity.Route{ - BaseInfo: entity.BaseInfo{ - ID: "r1", - }, - Name: "r1", - Desc: "updated route", - UpstreamID: "u2", - Script: luaScript, - ScriptID: "r1", - ServiceID: "s1", - Labels: map[string]string{ - "version": "v2", - }, - }, - wantRet: &entity.Route{ - BaseInfo: entity.BaseInfo{ - ID: "r1", - }, - Name: "r1", - Desc: "updated route", - UpstreamID: "u2", - Script: luaScript, - ScriptID: "r1", - ServiceID: "s1", - Labels: map[string]string{ - "version": "v2", - }, - }, - mockErr: nil, - serviceInput: "s2", - called: true, - }, - { - caseDesc: "update success with script map", - giveInput: &UpdateInput{ - ID: "r1", - Route: entity.Route{ - Name: "r1", - Desc: "updated route", - UpstreamID: "u2", - Script: scriptMap, - ServiceID: "s1", - Labels: map[string]string{ - "version": "v2", - }, - }, - }, - mockInput: &entity.Route{ - BaseInfo: entity.BaseInfo{ - ID: "r1", - }, - Name: "r1", - Desc: "updated route", - UpstreamID: "u2", - Script: luaCode, - ScriptID: "r1", - ServiceID: "s1", - Labels: map[string]string{ - "version": "v2", - }, - }, - mockRet: &entity.Route{ - BaseInfo: entity.BaseInfo{ - ID: "r1", - }, - Name: "r1", - Desc: "updated route", - UpstreamID: "u2", - Script: luaCode, - ScriptID: "r1", - ServiceID: "s1", - Labels: map[string]string{ - "version": "v2", - }, - }, - mockErr: nil, - wantRet: &entity.Route{ - BaseInfo: entity.BaseInfo{ - ID: "r1", - }, - Name: "r1", - Desc: "updated route", - UpstreamID: "u2", - Script: luaCode, - ScriptID: "r1", - ServiceID: "s1", - Labels: map[string]string{ - "version": "v2", - }, - }, - serviceInput: "s2", - called: true, - }, - { - caseDesc: "update failed, different id", - giveInput: &UpdateInput{ - ID: "r1", - Route: entity.Route{ - BaseInfo: entity.BaseInfo{ - ID: "r2", - }, - Name: "test_route", - UpstreamID: "u1", - Desc: "test service", - }, - }, - wantRet: &data.SpecCodeResponse{StatusCode: http.StatusBadRequest}, - wantErr: fmt.Errorf("ID on path (r1) doesn't match ID on body (r2)"), - called: false, - }, - { - caseDesc: "update failed, service not found", - giveInput: &UpdateInput{ - ID: "r1", - Route: entity.Route{ - BaseInfo: entity.BaseInfo{ - ID: "r1", - }, - Name: "test route", - ServiceID: "not_found", - UpstreamID: "u1", - }, - }, - wantRet: &data.SpecCodeResponse{StatusCode: http.StatusBadRequest}, - wantErr: fmt.Errorf("service id: not_found not found"), - serviceErr: data.ErrNotFound, - called: false, - }, - { - caseDesc: "update failed, upstream not found", - giveInput: &UpdateInput{ - ID: "r1", - Route: entity.Route{ - Name: "test_route", - UpstreamID: "not_found", - ServiceID: "s1", - Desc: "test route", - }, - }, - wantRet: &data.SpecCodeResponse{StatusCode: http.StatusBadRequest}, - wantErr: fmt.Errorf("upstream id: not_found not found"), - serviceRet: "service", - upstreamErr: data.ErrNotFound, - called: false, - }, - { - caseDesc: "update failed, route return error", - giveInput: &UpdateInput{ - ID: "r1", - Route: entity.Route{ - Name: "r1", - Desc: "test route", - }, - }, - mockInput: &entity.Route{ - BaseInfo: entity.BaseInfo{ - ID: "r1", - }, - Name: "r1", - Desc: "test route", - }, - mockErr: fmt.Errorf("route update error"), - wantErr: fmt.Errorf("route update error"), - wantRet: &data.SpecCodeResponse{StatusCode: http.StatusInternalServerError}, - serviceRet: "service", - upstreamRet: "upstream", - serviceInput: "s1", - called: true, - }, - } - - for _, tc := range tests { - t.Run(tc.caseDesc, func(t *testing.T) { - getCalled := false - routeStore := &store.MockInterface{} - - routeStore.On("Update", mock.Anything, mock.Anything, mock.Anything).Run(func(args mock.Arguments) { - getCalled = true - input := args.Get(1).(*entity.Route) - createIfNotExist := args.Get(2).(bool) - assert.Equal(t, tc.mockInput, input) - assert.True(t, createIfNotExist) - }).Return(tc.mockRet, tc.mockErr) - - routeStore.On("List", mock.Anything, mock.Anything).Run(func(args mock.Arguments) { - }).Return(func(input store.ListInput) *store.ListOutput { - return &store.ListOutput{ - Rows: tc.nameExistRet, - TotalSize: len(tc.nameExistRet), - } - }, nil) - - serviceStore := &store.MockInterface{} - serviceStore.On("Get", mock.Anything, mock.Anything).Return(tc.serviceRet, tc.serviceErr) - - upstreamStore := &store.MockInterface{} - upstreamStore.On("Get", mock.Anything, mock.Anything).Return(tc.upstreamRet, tc.upstreamErr) - - scriptStore := &store.MockInterface{} - scriptStore.On("Get", mock.Anything, mock.Anything).Return(tc.scriptRet, tc.scriptErr) - scriptStore.On("Update", mock.Anything, mock.Anything, mock.Anything).Return(luaScript, nil) - - h := Handler{svcStore: serviceStore, upstreamStore: upstreamStore, scriptStore: scriptStore, - routeStore: routeStore} - - ctx := droplet.NewContext() - ctx.SetInput(tc.giveInput) - ret, err := h.Update(ctx) - assert.Equal(t, tc.called, getCalled) - assert.Equal(t, tc.wantRet, ret) - assert.Equal(t, tc.wantErr, err) - }) - } -} - -func TestRoute_Patch(t *testing.T) { - existRoute := &entity.Route{ - BaseInfo: entity.BaseInfo{ - ID: "r1", - CreateTime: 1609340491, - UpdateTime: 1609340491, - }, - Name: "exist_service", - UpstreamID: "u1", - Upstream: &entity.UpstreamDef{ - Key: "key", - }, - EnableWebsocket: false, - Labels: map[string]string{ - "version": "v1", - }, - Plugins: map[string]interface{}{ - "limit-count": map[string]interface{}{ - "count": 2, - "time_window": 60, - "rejected_code": 503, - "key": "remote_addr", - }, - }, - Status: 1, - } - - tests := []testCase{ - { - caseDesc: "patch success", - giveInput: &PatchInput{ - ID: "r1", - SubPath: "", - Body: []byte("{\"status\":0}"), - }, - mockInput: &entity.Route{ - BaseInfo: entity.BaseInfo{ - ID: "r1", - CreateTime: 1609340491, - UpdateTime: 1609340491, - }, - Name: "exist_service", - UpstreamID: "u1", - Upstream: &entity.UpstreamDef{ - Key: "key", - }, - EnableWebsocket: false, - Labels: map[string]string{ - "version": "v1", - }, - Plugins: map[string]interface{}{ - "limit-count": map[string]interface{}{ - "count": float64(2), - "time_window": float64(60), - "rejected_code": float64(503), - "key": "remote_addr", - }, - }, - Status: 0, - }, - mockRet: &entity.Route{ - BaseInfo: entity.BaseInfo{ - ID: "r1", - CreateTime: 1609340491, - UpdateTime: 1609340491, - }, - Name: "exist_service", - UpstreamID: "u1", - Upstream: &entity.UpstreamDef{ - Key: "key", - }, - EnableWebsocket: false, - Labels: map[string]string{ - "version": "v1", - }, - Plugins: map[string]interface{}{ - "limit-count": map[string]interface{}{ - "count": float64(2), - "time_window": float64(60), - "rejected_code": float64(503), - "key": "remote_addr", - }, - }, - Status: 0, - }, - wantRet: &entity.Route{ - BaseInfo: entity.BaseInfo{ - ID: "r1", - CreateTime: 1609340491, - UpdateTime: 1609340491, - }, - Name: "exist_service", - UpstreamID: "u1", - Upstream: &entity.UpstreamDef{ - Key: "key", - }, - EnableWebsocket: false, - Labels: map[string]string{ - "version": "v1", - }, - Plugins: map[string]interface{}{ - "limit-count": map[string]interface{}{ - "count": float64(2), - "time_window": float64(60), - "rejected_code": float64(503), - "key": "remote_addr", - }, - }, - Status: 0, - }, - called: true, - }, - { - caseDesc: "patch success by path", - giveInput: &PatchInput{ - ID: "r1", - SubPath: "/status", - Body: []byte("0"), - }, - mockInput: &entity.Route{ - BaseInfo: entity.BaseInfo{ - ID: "r1", - CreateTime: 1609340491, - UpdateTime: 1609340491, - }, - Name: "exist_service", - UpstreamID: "u1", - Upstream: &entity.UpstreamDef{ - Key: "key", - }, - EnableWebsocket: false, - Labels: map[string]string{ - "version": "v1", - }, - Plugins: map[string]interface{}{ - "limit-count": map[string]interface{}{ - "count": float64(2), - "time_window": float64(60), - "rejected_code": float64(503), - "key": "remote_addr", - }, - }, - Status: 0, - }, - mockRet: &entity.Route{ - BaseInfo: entity.BaseInfo{ - ID: "r1", - CreateTime: 1609340491, - UpdateTime: 1609340491, - }, - Name: "exist_service", - UpstreamID: "u1", - Upstream: &entity.UpstreamDef{ - Key: "key", - }, - EnableWebsocket: false, - Labels: map[string]string{ - "version": "v1", - }, - Plugins: map[string]interface{}{ - "limit-count": map[string]interface{}{ - "count": float64(2), - "time_window": float64(60), - "rejected_code": float64(503), - "key": "remote_addr", - }, - }, - Status: 0, - }, - wantRet: &entity.Route{ - BaseInfo: entity.BaseInfo{ - ID: "r1", - CreateTime: 1609340491, - UpdateTime: 1609340491, - }, - Name: "exist_service", - UpstreamID: "u1", - Upstream: &entity.UpstreamDef{ - Key: "key", - }, - EnableWebsocket: false, - Labels: map[string]string{ - "version": "v1", - }, - Plugins: map[string]interface{}{ - "limit-count": map[string]interface{}{ - "count": float64(2), - "time_window": float64(60), - "rejected_code": float64(503), - "key": "remote_addr", - }, - }, - Status: 0, - }, - called: true, - }, - { - caseDesc: "patch failed, path error", - giveInput: &PatchInput{ - ID: "r1", - SubPath: "error", - Body: []byte("0"), - }, - wantRet: handler.SpecCodeResponse( - errors.New("add operation does not apply: doc is missing path: \"error\": missing value")), - wantErr: errors.New("add operation does not apply: doc is missing path: \"error\": missing value"), - }, - } - for _, tc := range tests { - t.Run(tc.caseDesc, func(t *testing.T) { - getCalled := false - - routeStore := &store.MockInterface{} - routeStore.On("Get", mock.Anything, mock.Anything).Return(existRoute, nil) - routeStore.On("Update", mock.Anything, mock.Anything, mock.Anything).Run(func(args mock.Arguments) { - getCalled = true - input := args.Get(1).(*entity.Route) - createIfNotExist := args.Get(2).(bool) - assert.Equal(t, tc.mockInput, input) - assert.False(t, createIfNotExist) - }).Return(tc.mockRet, tc.mockErr) - h := Handler{routeStore: routeStore} - ctx := droplet.NewContext() - ctx.SetInput(tc.giveInput) - ret, err := h.Patch(ctx) - assert.Equal(t, tc.called, getCalled) - assert.Equal(t, tc.wantRet, ret) - if tc.wantErr != nil && err != nil { - assert.Error(t, tc.wantErr.(error), err.Error()) - } else { - assert.Equal(t, tc.wantErr, err) - } - }) - } -} - -func TestRoute_Delete(t *testing.T) { - tests := []testCase{ - { - caseDesc: "delete success", - giveInput: &BatchDelete{ - IDs: "r1", - }, - mockInput: []string{"r1"}, - called: true, - }, - { - caseDesc: "batch delete success", - giveInput: &BatchDelete{ - IDs: "r1,r2", - }, - mockInput: []string{"r1", "r2"}, - called: true, - }, - { - caseDesc: "delete failed, route delete error", - giveInput: &BatchDelete{ - IDs: "r1", - }, - mockInput: []string{"r1"}, - mockErr: fmt.Errorf("delete error"), - wantRet: handler.SpecCodeResponse(fmt.Errorf("delete error")), - wantErr: fmt.Errorf("delete error"), - called: true, - }, - { - caseDesc: "delete failed, script delete error", - giveInput: &BatchDelete{ - IDs: "r1", - }, - mockInput: []string{"r1"}, - scriptErr: fmt.Errorf("delete error"), - wantRet: nil, - wantErr: nil, - called: true, - }, - } - - for _, tc := range tests { - t.Run(tc.caseDesc, func(t *testing.T) { - getCalled := false - routeStore := &store.MockInterface{} - routeStore.On("BatchDelete", mock.Anything, mock.Anything).Run(func(args mock.Arguments) { - getCalled = true - input := args.Get(1).([]string) - assert.Equal(t, tc.mockInput, input) - }).Return(tc.mockErr) - - scriptStore := &store.MockInterface{} - scriptStore.On("BatchDelete", mock.Anything, mock.Anything).Return(tc.serviceRet, tc.scriptErr) - - h := Handler{routeStore: routeStore, scriptStore: scriptStore} - ctx := droplet.NewContext() - ctx.SetInput(tc.giveInput) - ret, err := h.BatchDelete(ctx) - assert.True(t, getCalled) - assert.Equal(t, tc.wantRet, ret) - if tc.wantErr != nil && err != nil { - assert.Error(t, tc.wantErr.(error), err.Error()) - } else { - assert.Equal(t, tc.wantErr, err) - } - }) - } -} - -func TestRoute_Exist(t *testing.T) { - mockData := []*entity.Route{ - { - BaseInfo: entity.BaseInfo{ID: "001", CreateTime: 1609742634}, - Name: "r1", - URI: "/test_r1", - Labels: map[string]string{ - "version": "v1", - "build": "16", - }, - Upstream: &entity.UpstreamDef{ - Nodes: []interface{}{ - map[string]interface{}{ - "host": "39.97.63.215", - "port": float64(80), - "weight": float64(1), - }, - }, - }, - }, - { - BaseInfo: entity.BaseInfo{ID: "002", CreateTime: 1609742635}, - Name: "r2", - URI: "/test_r2", - Labels: map[string]string{ - "version": "v1", - "build": "16", - }, - }, - { - BaseInfo: entity.BaseInfo{ID: "003", CreateTime: 1609742636}, - Name: "route_test", - URI: "/test_route_test", - Labels: map[string]string{ - "version": "v2", - "build": "17", - }, - }, - } - - tests := []testCase{ - { - caseDesc: "check route exist, excluded", - giveInput: &ExistCheckInput{ - Name: "r1", - Exclude: "001", - }, - wantRet: nil, - called: true, - }, - { - caseDesc: "check route exist, not excluded", - giveInput: &ExistCheckInput{ - Name: "r1", - Exclude: "002", - }, - wantRet: &data.SpecCodeResponse{StatusCode: http.StatusBadRequest}, - wantErr: consts.InvalidParam("Route name is reduplicate"), - called: true, - }, - { - caseDesc: "check route exist, not existed", - giveInput: &ExistCheckInput{ - Name: "r3", - Exclude: "001", - }, - wantRet: nil, - wantErr: nil, - called: true, - }, - } - - for _, tc := range tests { - t.Run(tc.caseDesc, func(t *testing.T) { - getCalled := false - routeStore := &store.MockInterface{} - routeStore.On("List", mock.Anything, mock.Anything).Run(func(args mock.Arguments) { - getCalled = true - }).Return(func(input store.ListInput) *store.ListOutput { - var res []interface{} - for _, c := range mockData { - if input.Predicate(c) { - if input.Format != nil { - res = append(res, input.Format(c)) - } else { - res = append(res, c) - } - } - } - - return &store.ListOutput{ - Rows: res, - TotalSize: len(res), - } - }, nil) - - h := Handler{routeStore: routeStore} - ctx := droplet.NewContext() - ctx.SetInput(tc.giveInput) - ret, err := h.Exist(ctx) - assert.True(t, getCalled) - assert.Equal(t, tc.wantRet, ret) - assert.Equal(t, tc.wantErr, err) - }) - } -} diff --git a/api/internal/handler/route_online_debug/route_online_debug.go b/api/internal/handler/route_online_debug/route_online_debug.go deleted file mode 100644 index c65b69b665..0000000000 --- a/api/internal/handler/route_online_debug/route_online_debug.go +++ /dev/null @@ -1,172 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one or more - * contributor license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright ownership. - * The ASF licenses this file to You under the Apache License, Version 2.0 - * (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package route_online_debug - -import ( - "bytes" - "compress/gzip" - "encoding/json" - "fmt" - "io" - "io/ioutil" - "net/http" - "reflect" - "strings" - "time" - - "github.com/gin-gonic/gin" - "github.com/shiningrush/droplet" - "github.com/shiningrush/droplet/data" - "github.com/shiningrush/droplet/wrapper" - wgin "github.com/shiningrush/droplet/wrapper/gin" - - "github.com/apisix/manager-api/internal/handler" -) - -type Handler struct { -} - -func NewHandler() (handler.RouteRegister, error) { - return &Handler{}, nil -} - -type ProtocolSupport interface { - RequestForwarding(c droplet.Context) (interface{}, error) -} - -func (h *Handler) ApplyRoute(r *gin.Engine) { - r.POST("/apisix/admin/debug-request-forwarding", wgin.Wraps(h.DebugRequestForwarding, - wrapper.InputType(reflect.TypeOf(DebugOnlineInput{})))) -} - -type DebugOnlineInput struct { - URL string `auto_read:"online_debug_url,header"` - RequestProtocol string `auto_read:"online_debug_request_protocol,header"` - Method string `auto_read:"online_debug_method,header"` - HeaderParams string `auto_read:"online_debug_header_params,header"` - ContentType string `auto_read:"Content-Type,header"` - Body []byte `auto_read:"@body"` -} - -type Result struct { - Code int `json:"code,omitempty"` - Header interface{} `json:"header,omitempty"` - Message string `json:"message,omitempty"` - Data interface{} `json:"data,omitempty"` -} - -func (h *Handler) DebugRequestForwarding(c droplet.Context) (interface{}, error) { - //TODO: other Protocols, e.g: grpc, websocket - input := c.Input().(*DebugOnlineInput) - protocol := input.RequestProtocol - if protocol == "" { - protocol = "http" - } - - protocolMap := make(map[string]ProtocolSupport) - protocolMap["http"] = &HTTPProtocolSupport{} - protocolMap["https"] = &HTTPProtocolSupport{} - - if v, ok := protocolMap[protocol]; ok { - ret, err := v.RequestForwarding(c) - return ret, err - } - - return &data.SpecCodeResponse{StatusCode: http.StatusBadRequest}, fmt.Errorf("protocol unsupported %s", protocol) -} - -type HTTPProtocolSupport struct { -} - -func (h *HTTPProtocolSupport) RequestForwarding(c droplet.Context) (interface{}, error) { - input := c.Input().(*DebugOnlineInput) - url := input.URL - method := input.Method - body := input.Body - contentType := input.ContentType - - transport := http.DefaultTransport.(*http.Transport).Clone() - transport.DisableCompression = true - - client := &http.Client{ - Transport: transport, - Timeout: 5 * time.Second, - } - - var tempMap map[string][]string - err := json.Unmarshal([]byte(input.HeaderParams), &tempMap) - - if err != nil { - return &data.SpecCodeResponse{StatusCode: http.StatusBadRequest}, fmt.Errorf("can not get header") - } - - req, err := http.NewRequest(strings.ToUpper(method), url, bytes.NewReader(body)) - if err != nil { - return &data.SpecCodeResponse{StatusCode: http.StatusBadRequest}, err - } - - req.Header.Add("Content-Type", contentType) - for k, v := range tempMap { - for _, v1 := range v { - if !strings.EqualFold(k, "Content-Type") { - req.Header.Add(k, v1) - } else { - req.Header.Set(k, v1) - } - } - } - - resp, err := client.Do(req) - if err != nil { - return &data.SpecCodeResponse{StatusCode: http.StatusInternalServerError}, err - } - - defer resp.Body.Close() - - // handle gzip content encoding - var reader io.ReadCloser - switch resp.Header.Get("Content-Encoding") { - case "gzip": - reader, err = gzip.NewReader(resp.Body) - if err != nil { - return &data.SpecCodeResponse{StatusCode: http.StatusInternalServerError}, err - } - defer reader.Close() - default: - reader = resp.Body - } - - _body, err := ioutil.ReadAll(reader) - if err != nil { - return &data.SpecCodeResponse{StatusCode: http.StatusInternalServerError}, err - } - returnData := make(map[string]interface{}) - result := &Result{} - err = json.Unmarshal(_body, &returnData) - if err != nil { - result.Code = resp.StatusCode - result.Header = resp.Header - result.Message = resp.Status - result.Data = string(_body) - } else { - result.Code = resp.StatusCode - result.Header = resp.Header - result.Message = resp.Status - result.Data = returnData - } - return result, nil -} diff --git a/api/internal/handler/schema/plugin.go b/api/internal/handler/schema/plugin.go deleted file mode 100644 index 4585f56d0c..0000000000 --- a/api/internal/handler/schema/plugin.go +++ /dev/null @@ -1,79 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one or more - * contributor license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright ownership. - * The ASF licenses this file to You under the Apache License, Version 2.0 - * (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package schema - -import ( - "reflect" - - "github.com/gin-gonic/gin" - "github.com/shiningrush/droplet" - "github.com/shiningrush/droplet/wrapper" - wgin "github.com/shiningrush/droplet/wrapper/gin" - - "github.com/apisix/manager-api/internal/conf" - "github.com/apisix/manager-api/internal/handler" -) - -type Handler struct { -} - -func NewHandler() (handler.RouteRegister, error) { - return &Handler{}, nil -} - -func (h *Handler) ApplyRoute(r *gin.Engine) { - r.GET("/apisix/admin/plugins", wgin.Wraps(h.Plugins, - wrapper.InputType(reflect.TypeOf(ListInput{})))) -} - -type ListInput struct { - All bool `auto_read:"all,query"` -} - -func (h *Handler) Plugins(c droplet.Context) (interface{}, error) { - input := c.Input().(*ListInput) - - plugins := conf.Schema.Get("plugins") - if input.All { - var res []map[string]interface{} - list := plugins.Value().(map[string]interface{}) - for name, schemaConfig := range list { - if enable, ok := conf.Plugins[name]; !ok || !enable { - continue - } - plugin := schemaConfig.(map[string]interface{}) - plugin["name"] = name - if _, ok := plugin["type"]; !ok { - plugin["type"] = "other" - } - res = append(res, plugin) - } - return res, nil - } - - var ret []string - list := plugins.Map() - for pluginName := range list { - if enable, ok := conf.Plugins[pluginName]; !ok || !enable { - continue - } - - ret = append(ret, pluginName) - } - - return ret, nil -} diff --git a/api/internal/handler/schema/plugin_test.go b/api/internal/handler/schema/plugin_test.go deleted file mode 100644 index 251a45c35a..0000000000 --- a/api/internal/handler/schema/plugin_test.go +++ /dev/null @@ -1,66 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one or more - * contributor license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright ownership. - * The ASF licenses this file to You under the Apache License, Version 2.0 - * (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package schema - -import ( - "encoding/json" - "testing" - - "github.com/shiningrush/droplet" - "github.com/stretchr/testify/assert" -) - -func TestPlugin(t *testing.T) { - // init - handler := &Handler{} - assert.NotNil(t, handler) - - // plugin list(old api, return name only) - listInput := &ListInput{} - ctx := droplet.NewContext() - ctx.SetInput(listInput) - list, err := handler.Plugins(ctx) - assert.Nil(t, err) - assert.Contains(t, list.([]string), "limit-count") - - // plugin list(return all fields of plugin) - listInput = &ListInput{ - All: true, - } - ctx = droplet.NewContext() - ctx.SetInput(listInput) - list, err = handler.Plugins(ctx) - assert.Nil(t, err) - plugins := list.([]map[string]interface{}) - var authPlugins []string - var basicAuthConsumerSchema string - for _, plugin := range plugins { - if plugin["type"] == "auth" { - authPlugins = append(authPlugins, plugin["name"].(string)) - } - if plugin["name"] == "basic-auth" { - consumerSchemaByte, err := json.Marshal(plugin["consumer_schema"]) - basicAuthConsumerSchema = string(consumerSchemaByte) - assert.Nil(t, err) - } - } - // plugin type - assert.ElementsMatch(t, []string{"basic-auth", "jwt-auth", "hmac-auth", "key-auth", "wolf-rbac", "ldap-auth"}, authPlugins) - // consumer schema - assert.Equal(t, `{"properties":{"password":{"type":"string"},"username":{"type":"string"}},"required":["password","username"],"title":"work with consumer object","type":"object"}`, basicAuthConsumerSchema) -} diff --git a/api/internal/handler/schema/schema.go b/api/internal/handler/schema/schema.go deleted file mode 100644 index 7341376e56..0000000000 --- a/api/internal/handler/schema/schema.go +++ /dev/null @@ -1,89 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one or more - * contributor license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright ownership. - * The ASF licenses this file to You under the Apache License, Version 2.0 - * (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package schema - -import ( - "fmt" - "net/http" - "reflect" - - "github.com/gin-gonic/gin" - "github.com/shiningrush/droplet" - "github.com/shiningrush/droplet/data" - "github.com/shiningrush/droplet/wrapper" - wgin "github.com/shiningrush/droplet/wrapper/gin" - - "github.com/apisix/manager-api/internal/conf" - "github.com/apisix/manager-api/internal/handler" -) - -type SchemaHandler struct { -} - -func NewSchemaHandler() (handler.RouteRegister, error) { - return &SchemaHandler{}, nil -} - -func (h *SchemaHandler) ApplyRoute(r *gin.Engine) { - r.GET("/apisix/admin/schema/plugins/:name", wgin.Wraps(h.PluginSchema, - wrapper.InputType(reflect.TypeOf(PluginSchemaInput{})))) - - r.GET("/apisix/admin/schemas/:resource", wgin.Wraps(h.Schema, - wrapper.InputType(reflect.TypeOf(SchemaInput{})))) -} - -type SchemaInput struct { - Resource string `auto_read:"resource,path" validate:"required"` -} - -func (h *SchemaHandler) Schema(c droplet.Context) (interface{}, error) { - input := c.Input().(*SchemaInput) - - ret := conf.Schema.Get("main." + input.Resource).Value() - - if ret == nil { - return &data.SpecCodeResponse{StatusCode: http.StatusNotFound}, - fmt.Errorf("schema of %s not found", input.Resource) - } - - return ret, nil -} - -type PluginSchemaInput struct { - Name string `auto_read:"name,path" validate:"required"` - SchemaType string `auto_read:"schema_type,query"` -} - -func (h *SchemaHandler) PluginSchema(c droplet.Context) (interface{}, error) { - input := c.Input().(*PluginSchemaInput) - - var ret interface{} - if input.SchemaType == "consumer" { - ret = conf.Schema.Get("plugins." + input.Name + ".consumer_schema").Value() - } - - if ret == nil { - ret = conf.Schema.Get("plugins." + input.Name + ".schema").Value() - } - - if ret == nil { - return &data.SpecCodeResponse{StatusCode: http.StatusNotFound}, - fmt.Errorf("schema of plugins %s not found", input.Name) - } - - return ret, nil -} diff --git a/api/internal/handler/schema/schema_test.go b/api/internal/handler/schema/schema_test.go deleted file mode 100644 index cd564dc655..0000000000 --- a/api/internal/handler/schema/schema_test.go +++ /dev/null @@ -1,118 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one or more - * contributor license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright ownership. - * The ASF licenses this file to You under the Apache License, Version 2.0 - * (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package schema - -import ( - "encoding/json" - "net/http" - "testing" - - "github.com/shiningrush/droplet" - "github.com/shiningrush/droplet/data" - "github.com/stretchr/testify/assert" -) - -func TestSchema(t *testing.T) { - // init - ctx := droplet.NewContext() - handler := &SchemaHandler{} - assert.NotNil(t, handler) - - input := &SchemaInput{} - - // not exists return nil - reqBody := `{ - "resource": "not-exists" - }` - err := json.Unmarshal([]byte(reqBody), input) - assert.Nil(t, err) - ctx.SetInput(input) - ret, _ := handler.Schema(ctx) - assert.Equal(t, http.StatusNotFound, ret.(*data.SpecCodeResponse).StatusCode) - - // route - reqBody = `{ - "resource": "route" - }` - err = json.Unmarshal([]byte(reqBody), input) - assert.Nil(t, err) - ctx.SetInput(input) - val, _ := handler.Schema(ctx) - assert.NotNil(t, val) - - // ----------- plugin schema ---------- - - // limit-count - pluginInput := &PluginSchemaInput{ - Name: "limit-count", - } - ctx.SetInput(pluginInput) - val, _ = handler.PluginSchema(ctx) - assert.NotNil(t, val) - - // not exists - reqBody = `{ - "name": "not-exists" - }` - err = json.Unmarshal([]byte(reqBody), pluginInput) - assert.Nil(t, err) - ctx.SetInput(pluginInput) - res, _ := handler.PluginSchema(ctx) - assert.Equal(t, http.StatusNotFound, res.(*data.SpecCodeResponse).StatusCode) - - /* - get plugin schema with schema_type: consumer - plugin has consumer_schema - return plugin`s consumer_schema - */ - reqBody = `{ - "name": "jwt-auth", - "schema_type": "consumer" - }` - json.Unmarshal([]byte(reqBody), pluginInput) - ctx.SetInput(pluginInput) - val, _ = handler.PluginSchema(ctx) - assert.NotNil(t, val) - - /* - get plugin schema with schema_type: consumer - plugin does not have consumer_schema - return plugin`s schema - */ - reqBody = `{ - "name": "limit-count", - "schema_type": "consumer" - }` - json.Unmarshal([]byte(reqBody), pluginInput) - ctx.SetInput(pluginInput) - val, _ = handler.PluginSchema(ctx) - assert.NotNil(t, val) - - /* - get plugin schema with wrong schema_type: type, - return plugin`s schema - */ - reqBody = `{ - "name": "jwt-auth", - "schema_type": "type" - }` - json.Unmarshal([]byte(reqBody), pluginInput) - ctx.SetInput(pluginInput) - val, _ = handler.PluginSchema(ctx) - assert.NotNil(t, val) -} diff --git a/api/internal/handler/server_info/server_info.go b/api/internal/handler/server_info/server_info.go deleted file mode 100644 index ee16a7bac4..0000000000 --- a/api/internal/handler/server_info/server_info.go +++ /dev/null @@ -1,89 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one or more - * contributor license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright ownership. - * The ASF licenses this file to You under the Apache License, Version 2.0 - * (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package server_info - -import ( - "reflect" - "strings" - - "github.com/gin-gonic/gin" - "github.com/shiningrush/droplet" - "github.com/shiningrush/droplet/wrapper" - wgin "github.com/shiningrush/droplet/wrapper/gin" - - "github.com/apisix/manager-api/internal/core/entity" - "github.com/apisix/manager-api/internal/core/store" - "github.com/apisix/manager-api/internal/handler" -) - -type Handler struct { - serverInfoStore store.Interface -} - -func NewHandler() (handler.RouteRegister, error) { - return &Handler{ - serverInfoStore: store.GetStore(store.HubKeyServerInfo), - }, nil -} - -func (h *Handler) ApplyRoute(r *gin.Engine) { - r.GET("/apisix/admin/server_info/:id", wgin.Wraps(h.Get, - wrapper.InputType(reflect.TypeOf(GetInput{})))) - r.GET("/apisix/admin/server_info", wgin.Wraps(h.List, - wrapper.InputType(reflect.TypeOf(ListInput{})))) -} - -type GetInput struct { - ID string `auto_read:"id,path" validate:"required"` -} - -func (h *Handler) Get(c droplet.Context) (interface{}, error) { - input := c.Input().(*GetInput) - - r, err := h.serverInfoStore.Get(c.Context(), input.ID) - if err != nil { - return handler.SpecCodeResponse(err), err - } - - return r, nil -} - -type ListInput struct { - store.Pagination - Hostname string `auto_read:"hostname,query"` -} - -func (h *Handler) List(c droplet.Context) (interface{}, error) { - input := c.Input().(*ListInput) - - ret, err := h.serverInfoStore.List(c.Context(), store.ListInput{ - Predicate: func(obj interface{}) bool { - if input.Hostname != "" { - return strings.Contains(obj.(*entity.ServerInfo).Hostname, input.Hostname) - } - return true - }, - PageSize: input.PageSize, - PageNumber: input.PageNumber, - }) - - if err != nil { - return nil, err - } - - return ret, nil -} diff --git a/api/internal/handler/server_info/server_info_test.go b/api/internal/handler/server_info/server_info_test.go deleted file mode 100644 index c8ebcfe261..0000000000 --- a/api/internal/handler/server_info/server_info_test.go +++ /dev/null @@ -1,222 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one or more - * contributor license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright ownership. - * The ASF licenses this file to You under the Apache License, Version 2.0 - * (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package server_info - -import ( - "errors" - "testing" - - "github.com/shiningrush/droplet" - "github.com/shiningrush/droplet/data" - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/mock" - - "github.com/apisix/manager-api/internal/core/entity" - "github.com/apisix/manager-api/internal/core/store" -) - -func TestHandler_Get(t *testing.T) { - var ( - tests = []struct { - caseDesc string - giveInput *GetInput - giveErr error - giveRet interface{} - wantErr error - wantGetKey string - wantRet interface{} - }{ - { - caseDesc: "get server_info", - giveInput: &GetInput{ID: "server_1"}, - giveRet: &entity.ServerInfo{ - BaseInfo: entity.BaseInfo{ID: "server_1"}, - UpTime: 10, - LastReportTime: 1608195454, - BootTime: 1608195454, - Hostname: "gentoo", - Version: "v3", - }, - wantGetKey: "server_1", - wantRet: &entity.ServerInfo{ - BaseInfo: entity.BaseInfo{ID: "server_1"}, - UpTime: 10, - LastReportTime: 1608195454, - BootTime: 1608195454, - Hostname: "gentoo", - Version: "v3", - }, - }, - { - caseDesc: "get server_info not exist", - giveInput: &GetInput{ID: "server_3"}, - giveRet: &data.SpecCodeResponse{Response: data.Response{Code: 0}, StatusCode: 404}, - giveErr: errors.New("not found"), - wantGetKey: "server_3", - wantRet: &data.SpecCodeResponse{Response: data.Response{Code: 0}, StatusCode: 404}, - wantErr: errors.New("not found"), - }, - } - ) - - for _, tc := range tests { - t.Run(tc.caseDesc, func(t *testing.T) { - getCalled := false - mStore := &store.MockInterface{} - mStore.On("Get", mock.Anything).Run(func(args mock.Arguments) { - getCalled = true - assert.Equal(t, tc.wantGetKey, args.Get(0)) - }).Return(tc.giveRet, tc.giveErr) - - h := Handler{serverInfoStore: mStore} - ctx := droplet.NewContext() - ctx.SetInput(tc.giveInput) - ret, err := h.Get(ctx) - assert.True(t, getCalled) - assert.Equal(t, tc.wantErr, err) - assert.Equal(t, tc.wantRet, ret) - }) - } -} - -func TestHandler_List(t *testing.T) { - var ( - tests = []struct { - caseDesc string - giveInput *ListInput - giveData []interface{} - giveErr error - wantErr error - wantGetKey *ListInput - wantRet interface{} - }{ - { - caseDesc: "list server_info", - giveInput: &ListInput{Hostname: ""}, - giveData: []interface{}{ - &entity.ServerInfo{ - BaseInfo: entity.BaseInfo{ID: "server_1"}, - UpTime: 10, - LastReportTime: 1608195454, - BootTime: 1608195454, - Hostname: "gentoo", - Version: "v3", - }, - &entity.ServerInfo{ - BaseInfo: entity.BaseInfo{ID: "server_2"}, - UpTime: 10, - LastReportTime: 1608195454, - BootTime: 1608195454, - Hostname: "ubuntu", - Version: "v2", - }, - }, - wantRet: &store.ListOutput{ - Rows: []interface{}{ - &entity.ServerInfo{ - BaseInfo: entity.BaseInfo{ID: "server_1"}, - UpTime: 10, - LastReportTime: 1608195454, - BootTime: 1608195454, - Hostname: "gentoo", - Version: "v3", - }, - &entity.ServerInfo{ - BaseInfo: entity.BaseInfo{ID: "server_2"}, - UpTime: 10, - LastReportTime: 1608195454, - BootTime: 1608195454, - Hostname: "ubuntu", - Version: "v2", - }, - }, - TotalSize: 2, - }, - }, - { - caseDesc: "list server_info with hostname", - giveInput: &ListInput{Hostname: "ubuntu"}, - giveData: []interface{}{ - &entity.ServerInfo{ - BaseInfo: entity.BaseInfo{ID: "server_1"}, - UpTime: 10, - LastReportTime: 1608195454, - BootTime: 1608195454, - Hostname: "gentoo", - Version: "v3", - }, - &entity.ServerInfo{ - BaseInfo: entity.BaseInfo{ID: "server_2"}, - UpTime: 10, - LastReportTime: 1608195454, - BootTime: 1608195454, - Hostname: "ubuntu", - Version: "v2", - }, - }, - wantRet: &store.ListOutput{ - Rows: []interface{}{ - &entity.ServerInfo{ - BaseInfo: entity.BaseInfo{ID: "server_2"}, - UpTime: 10, - LastReportTime: 1608195454, - BootTime: 1608195454, - Hostname: "ubuntu", - Version: "v2", - }, - }, - TotalSize: 1, - }, - }, - } - ) - - for _, tc := range tests { - t.Run(tc.caseDesc, func(t *testing.T) { - getCalled := false - mStore := &store.MockInterface{} - mStore.On("List", mock.Anything).Run(func(args mock.Arguments) { - getCalled = true - }).Return(func(input store.ListInput) *store.ListOutput { - var res []interface{} - for _, c := range tc.giveData { - if input.Predicate(c) { - if input.Format != nil { - res = append(res, input.Format(c)) - } else { - res = append(res, c) - } - } - } - - return &store.ListOutput{ - Rows: res, - TotalSize: len(res), - } - }, tc.giveErr) - - h := Handler{serverInfoStore: mStore} - ctx := droplet.NewContext() - ctx.SetInput(tc.giveInput) - ret, err := h.List(ctx) - assert.True(t, getCalled) - assert.Equal(t, tc.wantErr, err) - assert.Equal(t, tc.wantRet, ret) - }) - } -} diff --git a/api/internal/handler/service/service.go b/api/internal/handler/service/service.go deleted file mode 100644 index ab94bfd804..0000000000 --- a/api/internal/handler/service/service.go +++ /dev/null @@ -1,315 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one or more - * contributor license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright ownership. - * The ASF licenses this file to You under the Apache License, Version 2.0 - * (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package service - -import ( - "encoding/json" - "fmt" - "net/http" - "reflect" - "strings" - - "github.com/gin-gonic/gin" - "github.com/shiningrush/droplet" - "github.com/shiningrush/droplet/data" - "github.com/shiningrush/droplet/wrapper" - wgin "github.com/shiningrush/droplet/wrapper/gin" - - "github.com/apisix/manager-api/internal/core/entity" - "github.com/apisix/manager-api/internal/core/store" - "github.com/apisix/manager-api/internal/handler" - "github.com/apisix/manager-api/internal/utils" -) - -type Handler struct { - serviceStore store.Interface - upstreamStore store.Interface - routeStore store.Interface -} - -func NewHandler() (handler.RouteRegister, error) { - return &Handler{ - serviceStore: store.GetStore(store.HubKeyService), - upstreamStore: store.GetStore(store.HubKeyUpstream), - routeStore: store.GetStore(store.HubKeyRoute), - }, nil -} - -func (h *Handler) ApplyRoute(r *gin.Engine) { - r.GET("/apisix/admin/services/:id", wgin.Wraps(h.Get, - wrapper.InputType(reflect.TypeOf(GetInput{})))) - r.GET("/apisix/admin/services", wgin.Wraps(h.List, - wrapper.InputType(reflect.TypeOf(ListInput{})))) - r.POST("/apisix/admin/services", wgin.Wraps(h.Create, - wrapper.InputType(reflect.TypeOf(entity.Service{})))) - r.PUT("/apisix/admin/services", wgin.Wraps(h.Update, - wrapper.InputType(reflect.TypeOf(UpdateInput{})))) - r.PUT("/apisix/admin/services/:id", wgin.Wraps(h.Update, - wrapper.InputType(reflect.TypeOf(UpdateInput{})))) - r.PATCH("/apisix/admin/services/:id", wgin.Wraps(h.Patch, - wrapper.InputType(reflect.TypeOf(PatchInput{})))) - r.PATCH("/apisix/admin/services/:id/*path", wgin.Wraps(h.Patch, - wrapper.InputType(reflect.TypeOf(PatchInput{})))) - r.DELETE("/apisix/admin/services/:ids", wgin.Wraps(h.BatchDelete, - wrapper.InputType(reflect.TypeOf(BatchDelete{})))) -} - -type GetInput struct { - ID string `auto_read:"id,path" validate:"required"` -} - -func (h *Handler) Get(c droplet.Context) (interface{}, error) { - input := c.Input().(*GetInput) - - r, err := h.serviceStore.Get(c.Context(), input.ID) - if err != nil { - return handler.SpecCodeResponse(err), err - } - - service := r.(*entity.Service) - if service.Upstream != nil && service.Upstream.Nodes != nil { - service.Upstream.Nodes = entity.NodesFormat(service.Upstream.Nodes) - } - - return r, nil -} - -type ListInput struct { - Name string `auto_read:"name,query"` - ID string `auto_read:"id,query"` - Desc string `auto_read:"desc,query"` - store.Pagination -} - -// swagger:operation GET /apisix/admin/services getServiceList -// -// Return the service list according to the specified page number and page size, and can search services by name. -// -// --- -// produces: -// - application/json -// parameters: -// - name: page -// in: query -// description: page number -// required: false -// type: integer -// - name: page_size -// in: query -// description: page size -// required: false -// type: integer -// - name: name -// in: query -// description: name of service -// required: false -// type: string -// responses: -// '0': -// description: list response -// schema: -// type: array -// items: -// "$ref": "#/definitions/service" -// default: -// description: unexpected error -// schema: -// "$ref": "#/definitions/ApiError" -func (h *Handler) List(c droplet.Context) (interface{}, error) { - input := c.Input().(*ListInput) - - ret, err := h.serviceStore.List(c.Context(), store.ListInput{ - Predicate: func(obj interface{}) bool { - if input.Name != "" { - return strings.Contains(obj.(*entity.Service).Name, input.Name) - } - - if input.Desc != "" { - return strings.Contains(obj.(*entity.Service).Desc, input.Desc) - } - - if input.ID != "" { - return strings.Contains(utils.InterfaceToString(obj.(*entity.Service).ID), input.ID) - } - return true - }, - Format: func(obj interface{}) interface{} { - service := obj.(*entity.Service) - if service.Upstream != nil && service.Upstream.Nodes != nil { - service.Upstream.Nodes = entity.NodesFormat(service.Upstream.Nodes) - } - return service - }, - PageSize: input.PageSize, - PageNumber: input.PageNumber, - }) - if err != nil { - return nil, err - } - - return ret, nil -} - -func (h *Handler) Create(c droplet.Context) (interface{}, error) { - input := c.Input().(*entity.Service) - - if input.UpstreamID != nil { - upstreamID := utils.InterfaceToString(input.UpstreamID) - _, err := h.upstreamStore.Get(c.Context(), upstreamID) - if err != nil { - if err == data.ErrNotFound { - return &data.SpecCodeResponse{StatusCode: http.StatusBadRequest}, - fmt.Errorf("upstream id: %s not found", input.UpstreamID) - } - return &data.SpecCodeResponse{StatusCode: http.StatusBadRequest}, err - } - } - - // check name existed - ret, err := handler.NameExistCheck(c.Context(), h.serviceStore, "service", input.Name, nil) - if err != nil { - return ret, err - } - - // create - res, err := h.serviceStore.Create(c.Context(), input) - if err != nil { - return handler.SpecCodeResponse(err), err - } - - return res, nil -} - -type UpdateInput struct { - ID string `auto_read:"id,path"` - entity.Service -} - -func (h *Handler) Update(c droplet.Context) (interface{}, error) { - input := c.Input().(*UpdateInput) - - // check if ID in body is equal ID in path - if err := handler.IDCompare(input.ID, input.Service.ID); err != nil { - return &data.SpecCodeResponse{StatusCode: http.StatusBadRequest}, err - } - - if input.ID != "" { - input.Service.ID = input.ID - } - - if input.UpstreamID != nil { - upstreamID := utils.InterfaceToString(input.UpstreamID) - _, err := h.upstreamStore.Get(c.Context(), upstreamID) - if err != nil { - if err == data.ErrNotFound { - return &data.SpecCodeResponse{StatusCode: http.StatusBadRequest}, - fmt.Errorf("upstream id: %s not found", input.UpstreamID) - } - return &data.SpecCodeResponse{StatusCode: http.StatusBadRequest}, err - } - } - - // check name existed - ret, err := handler.NameExistCheck(c.Context(), h.serviceStore, "service", input.Name, input.ID) - if err != nil { - return ret, err - } - - // update or create(if not exists) - res, err := h.serviceStore.Update(c.Context(), &input.Service, true) - if err != nil { - return handler.SpecCodeResponse(err), err - } - - return res, nil -} - -type BatchDelete struct { - IDs string `auto_read:"ids,path"` -} - -func (h *Handler) BatchDelete(c droplet.Context) (interface{}, error) { - input := c.Input().(*BatchDelete) - ids := strings.Split(input.IDs, ",") - mp := make(map[string]struct{}) - for _, id := range ids { - mp[id] = struct{}{} - } - - ret, err := h.routeStore.List(c.Context(), store.ListInput{ - Predicate: func(obj interface{}) bool { - route := obj.(*entity.Route) - if _, exist := mp[utils.InterfaceToString(route.ServiceID)]; exist { - return true - } - - return false - }, - PageSize: 0, - PageNumber: 0, - }) - if err != nil { - return handler.SpecCodeResponse(err), err - } - - if ret.TotalSize > 0 { - return &data.SpecCodeResponse{StatusCode: http.StatusBadRequest}, - fmt.Errorf("route: %s is using this service", ret.Rows[0].(*entity.Route).Name) - } - - if err := h.serviceStore.BatchDelete(c.Context(), ids); err != nil { - return handler.SpecCodeResponse(err), err - } - - return nil, nil -} - -type PatchInput struct { - ID string `auto_read:"id,path"` - SubPath string `auto_read:"path,path"` - Body []byte `auto_read:"@body"` -} - -func (h *Handler) Patch(c droplet.Context) (interface{}, error) { - input := c.Input().(*PatchInput) - reqBody := input.Body - ID := input.ID - subPath := input.SubPath - - stored, err := h.serviceStore.Get(c.Context(), ID) - if err != nil { - return handler.SpecCodeResponse(err), err - } - - res, err := utils.MergePatch(stored, subPath, reqBody) - if err != nil { - return handler.SpecCodeResponse(err), err - } - - var service entity.Service - err = json.Unmarshal(res, &service) - if err != nil { - return handler.SpecCodeResponse(err), err - } - - ret, err := h.serviceStore.Update(c.Context(), &service, false) - if err != nil { - return handler.SpecCodeResponse(err), err - } - - return ret, nil -} diff --git a/api/internal/handler/service/service_test.go b/api/internal/handler/service/service_test.go deleted file mode 100644 index 3132843da8..0000000000 --- a/api/internal/handler/service/service_test.go +++ /dev/null @@ -1,941 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one or more - * contributor license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright ownership. - * The ASF licenses this file to You under the Apache License, Version 2.0 - * (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package service - -import ( - "errors" - "fmt" - "net/http" - "testing" - - "github.com/shiningrush/droplet" - "github.com/shiningrush/droplet/data" - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/mock" - - "github.com/apisix/manager-api/internal/core/entity" - "github.com/apisix/manager-api/internal/core/store" - "github.com/apisix/manager-api/internal/handler" -) - -func TestService_Get(t *testing.T) { - tests := []struct { - caseDesc string - giveInput *GetInput - giveRet *entity.Service - giveErr error - wantErr error - wantGetKey string - wantRet interface{} - }{ - { - caseDesc: "normal", - giveInput: &GetInput{ID: "s1"}, - wantGetKey: "s1", - giveRet: &entity.Service{ - BaseInfo: entity.BaseInfo{ - ID: "s1", - }, - Plugins: map[string]interface{}{ - "limit-count": map[string]interface{}{ - "count": 2, - "time_window": 60, - "rejected_code": 503, - "key": "remote_addr", - }, - }, - }, - wantRet: &entity.Service{ - BaseInfo: entity.BaseInfo{ - ID: "s1", - }, - Plugins: map[string]interface{}{ - "limit-count": map[string]interface{}{ - "count": 2, - "time_window": 60, - "rejected_code": 503, - "key": "remote_addr", - }, - }, - }, - }, - { - caseDesc: "store get failed", - giveInput: &GetInput{ID: "failed_key"}, - wantGetKey: "failed_key", - giveErr: fmt.Errorf("get failed"), - wantErr: fmt.Errorf("get failed"), - wantRet: &data.SpecCodeResponse{ - StatusCode: http.StatusInternalServerError, - }, - }, - } - - for _, tc := range tests { - t.Run(tc.caseDesc, func(t *testing.T) { - getCalled := true - mStore := &store.MockInterface{} - mStore.On("Get", mock.Anything, mock.Anything).Run(func(args mock.Arguments) { - getCalled = true - assert.Equal(t, tc.wantGetKey, args.Get(0)) - }).Return(tc.giveRet, tc.giveErr) - - h := Handler{serviceStore: mStore} - ctx := droplet.NewContext() - ctx.SetInput(tc.giveInput) - ret, err := h.Get(ctx) - assert.True(t, getCalled) - assert.Equal(t, tc.wantRet, ret) - assert.Equal(t, tc.wantErr, err) - }) - } -} - -func TestService_List(t *testing.T) { - tests := []struct { - caseDesc string - giveInput *ListInput - giveData []*entity.Service - giveErr error - wantErr error - wantInput store.ListInput - wantRet interface{} - }{ - { - caseDesc: "list all service", - giveInput: &ListInput{ - Pagination: store.Pagination{ - PageSize: 10, - PageNumber: 10, - }, - }, - wantInput: store.ListInput{ - PageSize: 10, - PageNumber: 10, - }, - giveData: []*entity.Service{ - {Name: "s1"}, - {Name: "s2"}, - {Name: "test_service"}, - {Name: "service_test"}, - }, - wantRet: &store.ListOutput{ - Rows: []interface{}{ - &entity.Service{Name: "s1"}, - &entity.Service{Name: "s2"}, - &entity.Service{Name: "test_service"}, - &entity.Service{Name: "service_test"}, - }, - TotalSize: 4, - }, - }, - { - caseDesc: "list service with 'service'", - giveInput: &ListInput{ - Name: "service", - Pagination: store.Pagination{ - PageSize: 10, - PageNumber: 10, - }, - }, - wantInput: store.ListInput{ - PageSize: 10, - PageNumber: 10, - }, - giveData: []*entity.Service{ - {BaseInfo: entity.BaseInfo{CreateTime: 1609376661}, Name: "s1"}, - {BaseInfo: entity.BaseInfo{CreateTime: 1609376662}, Name: "s2"}, - {BaseInfo: entity.BaseInfo{CreateTime: 1609376663}, Name: "test_service"}, - {BaseInfo: entity.BaseInfo{CreateTime: 1609376664}, Name: "service_test"}, - }, - wantRet: &store.ListOutput{ - Rows: []interface{}{ - &entity.Service{BaseInfo: entity.BaseInfo{CreateTime: 1609376663}, Name: "test_service"}, - &entity.Service{BaseInfo: entity.BaseInfo{CreateTime: 1609376664}, Name: "service_test"}, - }, - TotalSize: 2, - }, - }, - { - caseDesc: "list service with key s1", - giveInput: &ListInput{ - Name: "s1", - Pagination: store.Pagination{ - PageSize: 10, - PageNumber: 10, - }, - }, - wantInput: store.ListInput{ - PageSize: 10, - PageNumber: 10, - }, - giveData: []*entity.Service{ - {Name: "s1"}, - {Name: "s2"}, - {Name: "test_service"}, - {Name: "service_test"}, - }, - wantRet: &store.ListOutput{ - Rows: []interface{}{ - &entity.Service{Name: "s1"}, - }, - TotalSize: 1, - }, - }, - { - caseDesc: "list service and format", - giveInput: &ListInput{ - Pagination: store.Pagination{ - PageSize: 10, - PageNumber: 10, - }, - }, - wantInput: store.ListInput{ - PageSize: 10, - PageNumber: 10, - }, - giveData: []*entity.Service{ - { - Name: "s1", - Upstream: &entity.UpstreamDef{ - Nodes: []interface{}{ - map[string]interface{}{ - "host": "39.97.63.215", - "port": float64(80), - "weight": float64(1), - "priority": float64(10), - }, - }, - }, - }, - {Name: "s2"}, - {Name: "test_service"}, - {Name: "service_test"}, - }, - wantRet: &store.ListOutput{ - Rows: []interface{}{ - &entity.Service{Name: "s1", Upstream: &entity.UpstreamDef{ - Nodes: []*entity.Node{ - { - Host: "39.97.63.215", - Port: 80, - Weight: 1, - Priority: 10, - }, - }, - }}, - &entity.Service{Name: "s2"}, - &entity.Service{Name: "test_service"}, - &entity.Service{Name: "service_test"}, - }, - TotalSize: 4, - }, - }, - } - - for _, tc := range tests { - t.Run(tc.caseDesc, func(t *testing.T) { - getCalled := true - mStore := &store.MockInterface{} - mStore.On("List", mock.Anything, mock.Anything).Run(func(args mock.Arguments) { - getCalled = true - input := args.Get(0).(store.ListInput) - assert.Equal(t, tc.wantInput.PageSize, input.PageSize) - assert.Equal(t, tc.wantInput.PageNumber, input.PageNumber) - }).Return(func(input store.ListInput) *store.ListOutput { - var returnData []interface{} - for _, c := range tc.giveData { - if input.Predicate(c) { - if input.Format == nil { - returnData = append(returnData, c) - continue - } - - returnData = append(returnData, input.Format(c)) - } - } - return &store.ListOutput{ - Rows: returnData, - TotalSize: len(returnData), - } - }, tc.giveErr) - - h := Handler{serviceStore: mStore} - ctx := droplet.NewContext() - ctx.SetInput(tc.giveInput) - ret, err := h.List(ctx) - assert.True(t, getCalled) - assert.Equal(t, tc.wantRet, ret) - assert.Equal(t, tc.wantErr, err) - }) - } -} - -func TestService_Create(t *testing.T) { - tests := []struct { - caseDesc string - getCalled bool - giveInput *entity.Service - giveRet interface{} - giveErr error - wantInput *entity.Service - wantErr error - wantRet interface{} - upstreamInput string - upstreamRet interface{} - upstreamErr interface{} - nameExistRet []interface{} - }{ - { - caseDesc: "create success", - getCalled: true, - giveInput: &entity.Service{ - Name: "s1", - UpstreamID: "u1", - Desc: "test service", - }, - wantInput: &entity.Service{ - Name: "s1", - UpstreamID: "u1", - Desc: "test service", - }, - upstreamInput: "u1", - upstreamRet: entity.Upstream{ - BaseInfo: entity.BaseInfo{ - ID: "u1", - }, - }, - }, - { - caseDesc: "create failed, upstream not found", - getCalled: false, - giveInput: &entity.Service{ - Name: "s1", - UpstreamID: "u1", - Desc: "test service", - }, - wantInput: &entity.Service{ - Name: "s1", - UpstreamID: "u1", - Desc: "test service", - }, - wantErr: fmt.Errorf("upstream id: u1 not found"), - wantRet: &data.SpecCodeResponse{StatusCode: http.StatusBadRequest}, - upstreamInput: "u1", - upstreamErr: data.ErrNotFound, - }, - { - caseDesc: "create failed, upstream return error", - getCalled: false, - giveInput: &entity.Service{ - Name: "s1", - UpstreamID: "u1", - Desc: "test service", - }, - wantInput: &entity.Service{ - Name: "s1", - UpstreamID: "u1", - Desc: "test service", - }, - wantErr: fmt.Errorf("unknown error"), - wantRet: &data.SpecCodeResponse{StatusCode: http.StatusBadRequest}, - upstreamInput: "u1", - upstreamErr: fmt.Errorf("unknown error"), - }, - { - caseDesc: "create failed, create return error", - getCalled: true, - giveInput: &entity.Service{ - Name: "s1", - UpstreamID: "u1", - Desc: "test service", - }, - giveErr: fmt.Errorf("create failed"), - wantInput: &entity.Service{ - Name: "s1", - UpstreamID: "u1", - Desc: "test service", - }, - upstreamInput: "u1", - upstreamRet: entity.Upstream{ - BaseInfo: entity.BaseInfo{ - ID: "u1", - }, - }, - wantErr: fmt.Errorf("create failed"), - wantRet: handler.SpecCodeResponse(fmt.Errorf("create failed")), - }, - } - - for _, tc := range tests { - t.Run(tc.caseDesc, func(t *testing.T) { - getCalled := false - serviceStore := &store.MockInterface{} - serviceStore.On("Create", mock.Anything, mock.Anything).Run(func(args mock.Arguments) { - getCalled = true - input := args.Get(1).(*entity.Service) - assert.Equal(t, tc.wantInput, input) - }).Return(tc.giveRet, tc.giveErr) - - upstreamStore := &store.MockInterface{} - upstreamStore.On("Get", mock.Anything, mock.Anything).Run(func(args mock.Arguments) { - id := args.Get(0).(string) - assert.Equal(t, tc.upstreamInput, id) - }).Return(tc.upstreamRet, tc.upstreamErr) - - serviceStore.On("List", mock.Anything, mock.Anything).Run(func(args mock.Arguments) { - }).Return(func(input store.ListInput) *store.ListOutput { - return &store.ListOutput{ - Rows: tc.nameExistRet, - TotalSize: len(tc.nameExistRet), - } - }, nil) - - h := Handler{serviceStore: serviceStore, upstreamStore: upstreamStore} - ctx := droplet.NewContext() - ctx.SetInput(tc.giveInput) - ret, err := h.Create(ctx) - assert.Equal(t, tc.getCalled, getCalled) - assert.Equal(t, tc.wantRet, ret) - assert.Equal(t, tc.wantErr, err) - }) - } -} - -func TestService_Update(t *testing.T) { - tests := []struct { - caseDesc string - getCalled bool - giveInput *UpdateInput - giveErr error - giveRet interface{} - wantInput *entity.Service - wantErr error - wantRet interface{} - upstreamInput string - upstreamRet interface{} - upstreamErr interface{} - nameExistRet []interface{} - }{ - { - caseDesc: "create success", - getCalled: true, - giveInput: &UpdateInput{ - ID: "s1", - Service: entity.Service{ - Name: "s1", - UpstreamID: "u1", - Desc: "test service", - }, - }, - wantInput: &entity.Service{ - BaseInfo: entity.BaseInfo{ - ID: "s1", - }, - Name: "s1", - UpstreamID: "u1", - Desc: "test service", - }, - upstreamInput: "u1", - upstreamRet: entity.Upstream{ - BaseInfo: entity.BaseInfo{ - ID: "u1", - }, - }, - }, - { - caseDesc: "create failed, different id", - giveInput: &UpdateInput{ - ID: "s1", - Service: entity.Service{ - BaseInfo: entity.BaseInfo{ - ID: "s2", - }, - Name: "s1", - UpstreamID: "u1", - Desc: "test service", - }, - }, - wantRet: &data.SpecCodeResponse{StatusCode: http.StatusBadRequest}, - wantErr: fmt.Errorf("ID on path (s1) doesn't match ID on body (s2)"), - }, - { - caseDesc: "update failed, upstream not found", - giveInput: &UpdateInput{ - ID: "s1", - Service: entity.Service{ - Name: "s1", - UpstreamID: "u1", - Desc: "test service", - }, - }, - wantInput: &entity.Service{ - Name: "s1", - UpstreamID: "u1", - Desc: "test service", - }, - wantErr: fmt.Errorf("upstream id: u1 not found"), - wantRet: &data.SpecCodeResponse{StatusCode: http.StatusBadRequest}, - upstreamInput: "u1", - upstreamErr: data.ErrNotFound, - }, - { - caseDesc: "update failed, upstream return error", - giveInput: &UpdateInput{ - ID: "s1", - Service: entity.Service{ - Name: "s1", - UpstreamID: "u1", - Desc: "test service", - }, - }, - wantInput: &entity.Service{ - Name: "s1", - UpstreamID: "u1", - Desc: "test service", - }, - wantErr: fmt.Errorf("unknown error"), - wantRet: &data.SpecCodeResponse{StatusCode: http.StatusBadRequest}, - upstreamInput: "u1", - upstreamErr: fmt.Errorf("unknown error"), - }, - { - caseDesc: "update failed, update return error", - getCalled: true, - giveInput: &UpdateInput{ - ID: "s1", - Service: entity.Service{ - Name: "s1", - UpstreamID: "u1", - Desc: "test service", - }, - }, - giveErr: fmt.Errorf("update failed"), - upstreamInput: "u1", - upstreamRet: entity.Upstream{ - BaseInfo: entity.BaseInfo{ - ID: "u1", - }, - }, - wantInput: &entity.Service{ - BaseInfo: entity.BaseInfo{ID: "s1"}, - Name: "s1", - UpstreamID: "u1", - Desc: "test service", - }, - wantErr: fmt.Errorf("update failed"), - wantRet: handler.SpecCodeResponse(fmt.Errorf("update failed")), - }, - } - - for _, tc := range tests { - t.Run(tc.caseDesc, func(t *testing.T) { - getCalled := false - serviceStore := &store.MockInterface{} - serviceStore.On("Update", mock.Anything, mock.Anything, mock.Anything).Run(func(args mock.Arguments) { - getCalled = true - input := args.Get(1).(*entity.Service) - createIfNotExist := args.Get(2).(bool) - assert.Equal(t, tc.wantInput, input) - assert.True(t, createIfNotExist) - }).Return(tc.giveRet, tc.giveErr) - - upstreamStore := &store.MockInterface{} - upstreamStore.On("Get", mock.Anything, mock.Anything).Run(func(args mock.Arguments) { - id := args.Get(0).(string) - assert.Equal(t, tc.upstreamInput, id) - }).Return(tc.upstreamRet, tc.upstreamErr) - - serviceStore.On("List", mock.Anything, mock.Anything).Run(func(args mock.Arguments) { - }).Return(func(input store.ListInput) *store.ListOutput { - return &store.ListOutput{ - Rows: tc.nameExistRet, - TotalSize: len(tc.nameExistRet), - } - }, nil) - - h := Handler{serviceStore: serviceStore, upstreamStore: upstreamStore} - ctx := droplet.NewContext() - ctx.SetInput(tc.giveInput) - ret, err := h.Update(ctx) - assert.Equal(t, tc.getCalled, getCalled) - assert.Equal(t, tc.wantRet, ret) - assert.Equal(t, tc.wantErr, err) - }) - } -} - -func TestService_Patch(t *testing.T) { - existService := &entity.Service{ - BaseInfo: entity.BaseInfo{ - ID: "s1", - CreateTime: 1609340491, - UpdateTime: 1609340491, - }, - Name: "exist_service", - UpstreamID: "u1", - EnableWebsocket: false, - Labels: map[string]string{ - "version": "v1", - }, - Plugins: map[string]interface{}{ - "limit-count": map[string]interface{}{ - "count": 2, - "time_window": 60, - "rejected_code": 503, - "key": "remote_addr", - }, - }, - } - - tests := []struct { - caseDesc string - giveInput *PatchInput - giveErr error - giveRet interface{} - wantInput *entity.Service - wantErr error - wantRet interface{} - serviceInput string - serviceRet *entity.Service - serviceErr error - called bool - }{ - { - caseDesc: "patch all success", - giveInput: &PatchInput{ - ID: "s1", - SubPath: "", - Body: []byte(`{ - "name":"patched", - "upstream_id":"u2", - "enable_websocket":true, - "labels":{ - "version":"v1", - "build":"16" - }, - "plugins":{ - "limit-count":{ - "count":2, - "time_window":60, - "rejected_code": 504, - "key":"remote_addr" - }, - "key-auth":{ - "key":"auth-one" - } - } - }`), - }, - wantInput: &entity.Service{ - BaseInfo: entity.BaseInfo{ - ID: "s1", - CreateTime: 1609340491, - UpdateTime: 1609340491, - }, - Name: "patched", - UpstreamID: "u2", - EnableWebsocket: true, - Labels: map[string]string{ - "version": "v1", - "build": "16", - }, - Plugins: map[string]interface{}{ - "limit-count": map[string]interface{}{ - "count": float64(2), - "time_window": float64(60), - "rejected_code": float64(504), - "key": "remote_addr", - }, - "key-auth": map[string]interface{}{ - "key": "auth-one", - }, - }, - }, - serviceInput: "s1", - serviceRet: existService, - called: true, - }, - { - caseDesc: "patch part of service success", - giveInput: &PatchInput{ - ID: "s1", - SubPath: "", - Body: []byte(`{ - "name":"patched", - "upstream_id":"u2", - "enable_websocket":true, - "labels":{ - "version":"v1", - "build":"16" - } - }`), - }, - wantInput: &entity.Service{ - BaseInfo: entity.BaseInfo{ - ID: "s1", - CreateTime: 1609340491, - UpdateTime: 1609340491, - }, - Name: "patched", - UpstreamID: "u2", - EnableWebsocket: true, - Labels: map[string]string{ - "version": "v1", - "build": "16", - }, - Plugins: map[string]interface{}{ - "limit-count": map[string]interface{}{ - "count": float64(2), - "time_window": float64(60), - "rejected_code": float64(503), - "key": "remote_addr", - }, - }, - }, - serviceInput: "s1", - serviceRet: existService, - called: true, - }, - { - caseDesc: "patch name success with sub path", - giveInput: &PatchInput{ - ID: "s1", - SubPath: "/upstream_id", - Body: []byte(`{"upstream_id":"u3"}`), - }, - wantInput: &entity.Service{ - BaseInfo: entity.BaseInfo{ - ID: "s1", - CreateTime: 1609340491, - UpdateTime: 1609340491, - }, - Name: "exist_service", - UpstreamID: map[string]interface{}{ - "upstream_id": "u3", - }, - EnableWebsocket: false, - Labels: map[string]string{ - "version": "v1", - }, - Plugins: map[string]interface{}{ - "limit-count": map[string]interface{}{ - "count": float64(2), - "time_window": float64(60), - "rejected_code": float64(503), - "key": "remote_addr", - }, - }, - }, - serviceInput: "s1", - serviceRet: existService, - called: true, - }, - { - caseDesc: "patch labels success", - giveInput: &PatchInput{ - ID: "s1", - SubPath: "/labels", - Body: []byte(`{"version": "v3"}`), - }, - wantInput: &entity.Service{ - BaseInfo: entity.BaseInfo{ - ID: "s1", - CreateTime: 1609340491, - UpdateTime: 1609340491, - }, - Name: "exist_service", - EnableWebsocket: false, - Labels: map[string]string{ - "version": "v3", - }, - UpstreamID: "u1", - Plugins: map[string]interface{}{ - "limit-count": map[string]interface{}{ - "count": float64(2), - "time_window": float64(60), - "rejected_code": float64(503), - "key": "remote_addr", - }, - }, - }, - serviceInput: "s1", - serviceRet: existService, - called: true, - }, - { - caseDesc: "patch failed, service store get error", - giveInput: &PatchInput{ - ID: "s1", - Body: []byte{}, - }, - serviceInput: "s1", - serviceErr: fmt.Errorf("get error"), - wantRet: handler.SpecCodeResponse(fmt.Errorf("get error")), - wantErr: fmt.Errorf("get error"), - called: false, - }, - } - - for _, tc := range tests { - t.Run(tc.caseDesc, func(t *testing.T) { - getCalled := false - serviceStore := &store.MockInterface{} - serviceStore.On("Update", mock.Anything, mock.Anything, mock.Anything).Run(func(args mock.Arguments) { - getCalled = true - input := args.Get(1).(*entity.Service) - createIfNotExist := args.Get(2).(bool) - assert.Equal(t, tc.wantInput, input) - assert.False(t, createIfNotExist) - }).Return(tc.giveRet, tc.giveErr) - - serviceStore.On("Get", mock.Anything, mock.Anything).Run(func(args mock.Arguments) { - input := args.Get(0).(string) - assert.Equal(t, tc.serviceInput, input) - }).Return(tc.serviceRet, tc.serviceErr) - - h := Handler{serviceStore: serviceStore} - ctx := droplet.NewContext() - ctx.SetInput(tc.giveInput) - ret, err := h.Patch(ctx) - assert.Equal(t, tc.called, getCalled) - assert.Equal(t, tc.wantRet, ret) - assert.Equal(t, tc.wantErr, err) - }) - } -} - -func TestServices_Delete(t *testing.T) { - tests := []struct { - caseDesc string - giveInput *BatchDelete - giveErr error - wantInput []string - wantErr error - wantRet interface{} - routeMockData []*entity.Route - routeMockErr error - getCalled bool - }{ - { - caseDesc: "delete success", - giveInput: &BatchDelete{ - IDs: "s1", - }, - wantInput: []string{"s1"}, - getCalled: true, - }, - { - caseDesc: "batch delete success", - giveInput: &BatchDelete{ - IDs: "s1,s2", - }, - wantInput: []string{"s1", "s2"}, - getCalled: true, - }, - { - caseDesc: "delete failed", - giveInput: &BatchDelete{ - IDs: "s1", - }, - giveErr: fmt.Errorf("delete error"), - wantInput: []string{"s1"}, - wantRet: handler.SpecCodeResponse(fmt.Errorf("delete error")), - wantErr: fmt.Errorf("delete error"), - getCalled: true, - }, - { - caseDesc: "delete failed, route is using", - giveInput: &BatchDelete{ - IDs: "s1", - }, - wantInput: []string{"s1"}, - routeMockData: []*entity.Route{ - &entity.Route{ - BaseInfo: entity.BaseInfo{ - ID: "r1", - CreateTime: 1609746531, - }, - Name: "route1", - Desc: "test_route", - UpstreamID: "u1", - ServiceID: "s1", - Labels: map[string]string{ - "version": "v1", - }, - }, - }, - routeMockErr: nil, - getCalled: false, - wantRet: &data.SpecCodeResponse{StatusCode: 400}, - wantErr: errors.New("route: route1 is using this service"), - }, - { - caseDesc: "delete failed, route list error", - giveInput: &BatchDelete{ - IDs: "s1", - }, - wantInput: []string{"s1"}, - routeMockData: nil, - routeMockErr: errors.New("route list error"), - wantRet: handler.SpecCodeResponse(errors.New("route list error")), - wantErr: errors.New("route list error"), - getCalled: false, - }, - } - - for _, tc := range tests { - t.Run(tc.caseDesc, func(t *testing.T) { - getCalled := false - serviceStore := &store.MockInterface{} - serviceStore.On("BatchDelete", mock.Anything, mock.Anything).Run(func(args mock.Arguments) { - getCalled = true - input := args.Get(1).([]string) - assert.Equal(t, tc.wantInput, input) - }).Return(tc.giveErr) - - routeStore := &store.MockInterface{} - routeStore.On("List", mock.Anything).Return(func(input store.ListInput) *store.ListOutput { - var returnData []interface{} - for _, c := range tc.routeMockData { - if input.Predicate(c) { - if input.Format == nil { - returnData = append(returnData, c) - continue - } - - returnData = append(returnData, input.Format(c)) - } - } - - return &store.ListOutput{ - Rows: returnData, - TotalSize: len(returnData), - } - }, tc.routeMockErr) - - h := Handler{serviceStore: serviceStore, routeStore: routeStore} - ctx := droplet.NewContext() - ctx.SetInput(tc.giveInput) - ret, err := h.BatchDelete(ctx) - assert.Equal(t, tc.getCalled, getCalled) - assert.Equal(t, tc.wantRet, ret) - assert.Equal(t, tc.wantErr, err) - }) - } -} diff --git a/api/internal/handler/ssl/ssl.go b/api/internal/handler/ssl/ssl.go deleted file mode 100644 index 3d512bf8a1..0000000000 --- a/api/internal/handler/ssl/ssl.go +++ /dev/null @@ -1,536 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one or more - * contributor license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright ownership. - * The ASF licenses this file to You under the Apache License, Version 2.0 - * (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package ssl - -import ( - "crypto/tls" - "crypto/x509" - "encoding/json" - "encoding/pem" - "fmt" - "net/http" - "reflect" - "strings" - - "github.com/gin-gonic/gin" - "github.com/shiningrush/droplet" - "github.com/shiningrush/droplet/data" - "github.com/shiningrush/droplet/wrapper" - wgin "github.com/shiningrush/droplet/wrapper/gin" - - "github.com/apisix/manager-api/internal/conf" - "github.com/apisix/manager-api/internal/core/entity" - "github.com/apisix/manager-api/internal/core/store" - "github.com/apisix/manager-api/internal/handler" - "github.com/apisix/manager-api/internal/utils" - "github.com/apisix/manager-api/internal/utils/consts" -) - -type Handler struct { - sslStore store.Interface -} - -func NewHandler() (handler.RouteRegister, error) { - return &Handler{ - sslStore: store.GetStore(store.HubKeySsl), - }, nil -} - -func checkSniExists(rows []interface{}, sni string) bool { - for _, item := range rows { - ssl := item.(*entity.SSL) - - if ssl.Sni == sni { - return true - } - - if inArray(sni, ssl.Snis) { - return true - } - - // Wildcard Domain - firstDot := strings.Index(sni, ".") - if firstDot > 0 && sni[0:1] != "*" { - wildcardDomain := "*" + sni[firstDot:] - if ssl.Sni == wildcardDomain { - return true - } - - if inArray(wildcardDomain, ssl.Snis) { - return true - } - } - } - - return false -} - -func (h *Handler) ApplyRoute(r *gin.Engine) { - r.GET("/apisix/admin/ssl/:id", wgin.Wraps(h.Get, - wrapper.InputType(reflect.TypeOf(GetInput{})))) - r.GET("/apisix/admin/ssl", wgin.Wraps(h.List, - wrapper.InputType(reflect.TypeOf(ListInput{})))) - r.POST("/apisix/admin/ssl", wgin.Wraps(h.Create, - wrapper.InputType(reflect.TypeOf(entity.SSL{})))) - r.PUT("/apisix/admin/ssl", wgin.Wraps(h.Update, - wrapper.InputType(reflect.TypeOf(UpdateInput{})))) - r.PUT("/apisix/admin/ssl/:id", wgin.Wraps(h.Update, - wrapper.InputType(reflect.TypeOf(UpdateInput{})))) - r.DELETE("/apisix/admin/ssl/:ids", wgin.Wraps(h.BatchDelete, - wrapper.InputType(reflect.TypeOf(BatchDelete{})))) - r.POST("/apisix/admin/check_ssl_cert", wgin.Wraps(h.Validate, - wrapper.InputType(reflect.TypeOf(entity.SSL{})))) - - r.PATCH("/apisix/admin/ssl/:id", wgin.Wraps(h.Patch, - wrapper.InputType(reflect.TypeOf(PatchInput{})))) - r.PATCH("/apisix/admin/ssl/:id/*path", wgin.Wraps(h.Patch, - wrapper.InputType(reflect.TypeOf(PatchInput{})))) - - r.POST("/apisix/admin/check_ssl_exists", wgin.Wraps(h.Exist, - wrapper.InputType(reflect.TypeOf(ExistCheckInput{})))) -} - -type GetInput struct { - ID string `auto_read:"id,path" validate:"required"` -} - -func (h *Handler) Get(c droplet.Context) (interface{}, error) { - input := c.Input().(*GetInput) - - ret, err := h.sslStore.Get(c.Context(), input.ID) - if err != nil { - return handler.SpecCodeResponse(err), err - } - - //format respond - ssl := &entity.SSL{} - err = utils.ObjectClone(ret, ssl) - if err != nil { - return handler.SpecCodeResponse(err), err - } - ssl.Key = "" - ssl.Keys = nil - - return ssl, nil -} - -type ListInput struct { - SNI string `auto_read:"sni,query"` - store.Pagination -} - -// swagger:operation GET /apisix/admin/ssl getSSLList -// -// Return the SSL list according to the specified page number and page size, and can SSLs search by sni. -// -// --- -// produces: -// - application/json -// parameters: -// - name: page -// in: query -// description: page number -// required: false -// type: integer -// - name: page_size -// in: query -// description: page size -// required: false -// type: integer -// - name: sni -// in: query -// description: sni of SSL -// required: false -// type: string -// responses: -// '0': -// description: list response -// schema: -// type: array -// items: -// "$ref": "#/definitions/ssl" -// default: -// description: unexpected error -// schema: -// "$ref": "#/definitions/ApiError" -func (h *Handler) List(c droplet.Context) (interface{}, error) { - input := c.Input().(*ListInput) - - ret, err := h.sslStore.List(c.Context(), store.ListInput{ - Predicate: func(obj interface{}) bool { - if input.SNI != "" { - if strings.Contains(obj.(*entity.SSL).Sni, input.SNI) { - return true - } - for _, str := range obj.(*entity.SSL).Snis { - result := strings.Contains(str, input.SNI) - if result { - return true - } - } - return false - } - return true - }, - PageSize: input.PageSize, - PageNumber: input.PageNumber, - }) - if err != nil { - return nil, err - } - - //format respond - var list []interface{} - for _, item := range ret.Rows { - ssl := &entity.SSL{} - _ = utils.ObjectClone(item, ssl) - x509_validity, _ := x509CertValidity(ssl.Cert) - if x509_validity != nil { - ssl.ValidityStart = x509_validity.NotBefore - ssl.ValidityEnd = x509_validity.NotAfter - } - ssl.Key = "" - ssl.Keys = nil - list = append(list, ssl) - } - if list == nil { - list = []interface{}{} - } - ret.Rows = list - - return ret, nil -} - -func (h *Handler) Create(c droplet.Context) (interface{}, error) { - input := c.Input().(*entity.SSL) - ssl, err := ParseCert(input.Cert, input.Key) - if err != nil { - return &data.SpecCodeResponse{StatusCode: http.StatusBadRequest}, err - } - - ssl.ID = input.ID - ssl.Labels = input.Labels - //set default value for SSL status, if not set, it will be 0 which means disable. - ssl.Status = conf.SSLDefaultStatus - ret, err := h.sslStore.Create(c.Context(), ssl) - if err != nil { - return handler.SpecCodeResponse(err), err - } - - ssl = ret.(*entity.SSL) - ssl.Key = "" - ssl.Keys = nil - - return ssl, nil -} - -type UpdateInput struct { - ID string `auto_read:"id,path"` - entity.SSL -} - -func (h *Handler) Update(c droplet.Context) (interface{}, error) { - input := c.Input().(*UpdateInput) - - // check if ID in body is equal ID in path - if err := handler.IDCompare(input.ID, input.SSL.ID); err != nil { - return &data.SpecCodeResponse{StatusCode: http.StatusBadRequest}, err - } - - ssl, err := ParseCert(input.Cert, input.Key) - if err != nil { - return &data.SpecCodeResponse{StatusCode: http.StatusBadRequest}, err - } - - if input.ID != "" { - ssl.ID = input.ID - } - - if input.Labels != nil { - ssl.Labels = input.Labels - } - - //set default value for SSL status, if not set, it will be 0 which means disable. - ssl.Status = conf.SSLDefaultStatus - ret, err := h.sslStore.Update(c.Context(), ssl, true) - if err != nil { - return handler.SpecCodeResponse(err), err - } - - ssl = ret.(*entity.SSL) - ssl.Key = "" - ssl.Keys = nil - - return ssl, nil -} - -type PatchInput struct { - ID string `auto_read:"id,path"` - SubPath string `auto_read:"path,path"` - Body []byte `auto_read:"@body"` -} - -func (h *Handler) Patch(c droplet.Context) (interface{}, error) { - input := c.Input().(*PatchInput) - reqBody := input.Body - id := input.ID - subPath := input.SubPath - - stored, err := h.sslStore.Get(c.Context(), id) - if err != nil { - return handler.SpecCodeResponse(err), err - } - - res, err := utils.MergePatch(stored, subPath, reqBody) - if err != nil { - return handler.SpecCodeResponse(err), err - } - - var ssl entity.SSL - err = json.Unmarshal(res, &ssl) - if err != nil { - return handler.SpecCodeResponse(err), err - } - - ret, err := h.sslStore.Update(c.Context(), &ssl, false) - if err != nil { - return handler.SpecCodeResponse(err), err - } - - _ssl := ret.(*entity.SSL) - _ssl.Key = "" - _ssl.Keys = nil - - return _ssl, nil -} - -type BatchDelete struct { - Ids string `auto_read:"ids,path"` -} - -func (h *Handler) BatchDelete(c droplet.Context) (interface{}, error) { - input := c.Input().(*BatchDelete) - - if err := h.sslStore.BatchDelete(c.Context(), strings.Split(input.Ids, ",")); err != nil { - return handler.SpecCodeResponse(err), err - } - - return nil, nil -} - -// validity allows unmarshaling the certificate validity date range -type validity struct { - NotBefore, NotAfter int64 -} - -func x509CertValidity(crt string) (*validity, error) { - if crt == "" { - return nil, consts.ErrSSLCertificate - } - - certDERBlock, _ := pem.Decode([]byte(crt)) - if certDERBlock == nil { - return nil, consts.ErrSSLCertificateResolution - } - - x509Cert, err := x509.ParseCertificate(certDERBlock.Bytes) - - if err != nil { - return nil, consts.ErrSSLCertificateResolution - } - - val := validity{} - - val.NotBefore = x509Cert.NotBefore.Unix() - val.NotAfter = x509Cert.NotAfter.Unix() - - return &val, nil -} - -func ParseCert(crt, key string) (*entity.SSL, error) { - if crt == "" || key == "" { - return nil, consts.ErrSSLCertificate - } - - certDERBlock, _ := pem.Decode([]byte(crt)) - if certDERBlock == nil { - return nil, consts.ErrSSLCertificateResolution - } - // match - _, err := tls.X509KeyPair([]byte(crt), []byte(key)) - if err != nil { - return nil, consts.ErrSSLKeyAndCert - } - - x509Cert, err := x509.ParseCertificate(certDERBlock.Bytes) - - if err != nil { - return nil, consts.ErrSSLCertificateResolution - } - - ssl := entity.SSL{} - //domain - snis := []string{} - if x509Cert.DNSNames != nil && len(x509Cert.DNSNames) > 0 { - snis = x509Cert.DNSNames - } else if x509Cert.IPAddresses != nil && len(x509Cert.IPAddresses) > 0 { - for _, ip := range x509Cert.IPAddresses { - snis = append(snis, ip.String()) - } - } else { - if x509Cert.Subject.Names != nil && len(x509Cert.Subject.Names) > 1 { - var attributeTypeNames = map[string]string{ - "2.5.4.6": "C", - "2.5.4.10": "O", - "2.5.4.11": "OU", - "2.5.4.3": "CN", - "2.5.4.5": "SERIALNUMBER", - "2.5.4.7": "L", - "2.5.4.8": "ST", - "2.5.4.9": "STREET", - "2.5.4.17": "POSTALCODE", - } - for _, tv := range x509Cert.Subject.Names { - oidString := tv.Type.String() - typeName, ok := attributeTypeNames[oidString] - if ok && typeName == "CN" { - valueString := fmt.Sprint(tv.Value) - snis = append(snis, valueString) - } - } - } - } - - ssl.Snis = snis - ssl.Key = key - ssl.Cert = crt - - return &ssl, nil -} - -// swagger:operation POST /apisix/admin/check_ssl_cert checkSSL -// -// verify SSL cert and key. -// -// --- -// produces: -// - application/json -// parameters: -// - name: cert -// in: body -// description: cert of SSL -// required: true -// type: string -// - name: key -// in: body -// description: key of SSL -// required: true -// type: string -// responses: -// '0': -// description: SSL verify passed -// schema: -// "$ref": "#/definitions/ApiError" -// default: -// description: unexpected error -// schema: -// "$ref": "#/definitions/ApiError" -func (h *Handler) Validate(c droplet.Context) (interface{}, error) { - input := c.Input().(*entity.SSL) - ssl, err := ParseCert(input.Cert, input.Key) - if err != nil { - return nil, err - } - - x509_validity, _ := x509CertValidity(input.Cert) - if x509_validity != nil { - ssl.ValidityStart = x509_validity.NotBefore - ssl.ValidityEnd = x509_validity.NotAfter - } - - return ssl, nil -} - -type ExistInput struct { - Name string `auto_read:"name,query"` -} - -func inArray(key string, array []string) bool { - for _, item := range array { - if key == item { - return true - } - } - - return false -} - -type ExistCheckInput struct { - Hosts []string -} - -// swagger:operation POST /apisix/admin/check_ssl_exists checkSSLExist -// -// Check whether the SSL exists. -// -// --- -// produces: -// - application/json -// parameters: -// - name: hosts -// in: body -// description: hosts of Route -// required: true -// type: array -// items: -// type: string -// responses: -// '0': -// description: SSL exists -// schema: -// "$ref": "#/definitions/ApiError" -// default: -// description: unexpected error -// schema: -// "$ref": "#/definitions/ApiError" -func (h *Handler) Exist(c droplet.Context) (interface{}, error) { - input := c.Input().(*ExistCheckInput) - if len(input.Hosts) == 0 { - return &data.SpecCodeResponse{StatusCode: http.StatusBadRequest}, - consts.InvalidParam("hosts could not be empty") - } - - ret, err := h.sslStore.List(c.Context(), store.ListInput{ - Predicate: nil, - PageSize: 0, - PageNumber: 0, - }) - - if err != nil { - return &data.SpecCodeResponse{StatusCode: http.StatusInternalServerError}, err - } - - for _, host := range input.Hosts { - exist := checkSniExists(ret.Rows, host) - if !exist { - return &data.SpecCodeResponse{StatusCode: http.StatusNotFound}, - consts.InvalidParam("SSL cert not exists for sni๏ผš" + host) - } - } - - return nil, nil -} diff --git a/api/internal/handler/ssl/ssl_test.go b/api/internal/handler/ssl/ssl_test.go deleted file mode 100644 index 28dab2c120..0000000000 --- a/api/internal/handler/ssl/ssl_test.go +++ /dev/null @@ -1,855 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one or more - * contributor license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright ownership. - * The ASF licenses this file to You under the Apache License, Version 2.0 - * (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package ssl - -import ( - "encoding/json" - "errors" - "fmt" - "io/ioutil" - "net/http" - "testing" - - "github.com/shiningrush/droplet" - "github.com/shiningrush/droplet/data" - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/mock" - - "github.com/apisix/manager-api/internal/core/entity" - "github.com/apisix/manager-api/internal/core/store" - "github.com/apisix/manager-api/internal/handler" - "github.com/apisix/manager-api/internal/utils/consts" -) - -func getTestKeyCert(t *testing.T) (string, string) { - testCert, err := ioutil.ReadFile("../../../test/certs/test2.crt") - assert.Nil(t, err) - testKey, err := ioutil.ReadFile("../../../test/certs/test2.key") - assert.Nil(t, err) - return string(testCert), string(testKey) -} - -func TestSSL_Get(t *testing.T) { - _cert, _key := getTestKeyCert(t) - tests := []struct { - caseDesc string - giveInput *GetInput - giveRet *entity.SSL - giveErr error - wantErr error - wantGetKey string - wantRet interface{} - }{ - { - caseDesc: "normal", - giveInput: &GetInput{ID: "ssl1"}, - wantGetKey: "ssl1", - giveRet: &entity.SSL{ - BaseInfo: entity.BaseInfo{ - ID: "ssl1", - }, - Key: _key, - Cert: _cert, - Labels: map[string]string{ - "build": "16", - "env": "production", - "version": "v2", - }, - }, - wantRet: &entity.SSL{ - BaseInfo: entity.BaseInfo{ - ID: "ssl1", - }, - Key: "", - Cert: _cert, - Labels: map[string]string{ - "build": "16", - "env": "production", - "version": "v2", - }, - }, - }, - { - caseDesc: "store get failed", - giveInput: &GetInput{ID: "failed_key"}, - wantGetKey: "failed_key", - giveErr: fmt.Errorf("get failed"), - wantErr: fmt.Errorf("get failed"), - wantRet: &data.SpecCodeResponse{ - StatusCode: http.StatusInternalServerError, - }, - }, - } - - for _, tc := range tests { - t.Run(tc.caseDesc, func(t *testing.T) { - getCalled := true - mStore := &store.MockInterface{} - mStore.On("Get", mock.Anything, mock.Anything).Run(func(args mock.Arguments) { - getCalled = true - assert.Equal(t, tc.wantGetKey, args.Get(0)) - }).Return(tc.giveRet, tc.giveErr) - - h := Handler{sslStore: mStore} - ctx := droplet.NewContext() - ctx.SetInput(tc.giveInput) - ret, err := h.Get(ctx) - assert.True(t, getCalled) - assert.Equal(t, tc.wantRet, ret) - assert.Equal(t, tc.wantErr, err) - }) - } -} - -func TestSSLs_List(t *testing.T) { - mockData := []*entity.SSL{ - { - BaseInfo: entity.BaseInfo{ - ID: "ssl1", - CreateTime: 1609340491, - UpdateTime: 1609340491, - }, - Status: 0, - Labels: map[string]string{ - "build": "16", - "env": "production", - "version": "v2", - }, - }, - { - BaseInfo: entity.BaseInfo{ - ID: "ssl2", - CreateTime: 1609340492, - UpdateTime: 1609340492, - }, - Sni: "route", - Status: 1, - Labels: map[string]string{ - "build": "17", - "env": "production", - "version": "v2", - }, - }, - { - BaseInfo: entity.BaseInfo{ - ID: "ssl3", - CreateTime: 1609340493, - UpdateTime: 1609340493, - }, - Status: 0, - Labels: map[string]string{ - "build": "18", - "env": "production", - "version": "v2", - }, - }, - } - - tests := []struct { - caseDesc string - giveInput *ListInput - giveData []*entity.SSL - giveErr error - wantErr error - wantInput store.ListInput - wantRet interface{} - }{ - { - caseDesc: "list all ssl", - giveInput: &ListInput{ - Pagination: store.Pagination{ - PageSize: 10, - PageNumber: 10, - }, - }, - wantInput: store.ListInput{ - PageSize: 10, - PageNumber: 10, - }, - wantRet: &store.ListOutput{ - Rows: []interface{}{ - mockData[0], - mockData[1], - mockData[2], - }, - TotalSize: 3, - }, - }, - { - caseDesc: "list ssl with 'SNI'", - giveInput: &ListInput{ - SNI: "route", - Pagination: store.Pagination{ - PageSize: 10, - PageNumber: 10, - }, - }, - wantInput: store.ListInput{ - PageSize: 10, - PageNumber: 10, - }, - wantRet: &store.ListOutput{ - Rows: []interface{}{ - mockData[1], - }, - TotalSize: 1, - }, - }, - } - - for _, tc := range tests { - t.Run(tc.caseDesc, func(t *testing.T) { - getCalled := true - mStore := &store.MockInterface{} - mStore.On("List", mock.Anything, mock.Anything).Run(func(args mock.Arguments) { - getCalled = true - input := args.Get(0).(store.ListInput) - assert.Equal(t, tc.wantInput.PageSize, input.PageSize) - assert.Equal(t, tc.wantInput.PageNumber, input.PageNumber) - }).Return(func(input store.ListInput) *store.ListOutput { - var returnData []interface{} - for _, c := range mockData { - if input.Predicate(c) { - if input.Format == nil { - returnData = append(returnData, c) - continue - } - - returnData = append(returnData, input.Format(c)) - } - } - return &store.ListOutput{ - Rows: returnData, - TotalSize: len(returnData), - } - }, tc.giveErr) - - h := Handler{sslStore: mStore} - ctx := droplet.NewContext() - ctx.SetInput(tc.giveInput) - ret, err := h.List(ctx) - assert.True(t, getCalled) - assert.Equal(t, tc.wantRet, ret) - assert.Equal(t, tc.wantErr, err) - }) - } -} - -func TestSSL_Create(t *testing.T) { - _cert, _key := getTestKeyCert(t) - tests := []struct { - caseDesc string - getCalled bool - giveInput *entity.SSL - giveRet interface{} - giveErr error - wantInput *entity.SSL - wantErr error - wantRet interface{} - }{ - { - caseDesc: "create success", - getCalled: true, - giveInput: &entity.SSL{ - BaseInfo: entity.BaseInfo{ - ID: "ssl1", - }, - Key: _key, - Cert: _cert, - Labels: map[string]string{ - "build": "16", - "env": "production", - "version": "v2", - }, - }, - giveRet: &entity.SSL{ - BaseInfo: entity.BaseInfo{ - ID: "ssl1", - }, - Key: _key, - Cert: _cert, - Labels: map[string]string{ - "build": "16", - "env": "production", - "version": "v2", - }, - Snis: []string{"test2.com", "*.test2.com"}, - Status: 1, - }, - wantInput: &entity.SSL{ - BaseInfo: entity.BaseInfo{ - ID: "ssl1", - }, - Key: _key, - Cert: _cert, - Labels: map[string]string{ - "build": "16", - "env": "production", - "version": "v2", - }, - Snis: []string{"test2.com", "*.test2.com"}, - Status: 1, - }, - wantRet: &entity.SSL{ - BaseInfo: entity.BaseInfo{ - ID: "ssl1", - }, - Key: "", - Cert: _cert, - Labels: map[string]string{ - "build": "16", - "env": "production", - "version": "v2", - }, - Snis: []string{"test2.com", "*.test2.com"}, - Status: 1, - }, - wantErr: nil, - }, - { - caseDesc: "create failed, create return error", - getCalled: true, - giveInput: &entity.SSL{ - Key: _key, - Cert: _cert, - Labels: map[string]string{ - "build": "16", - "env": "production", - "version": "v2", - }, - }, - giveErr: fmt.Errorf("create failed"), - wantInput: &entity.SSL{ - Key: _key, - Cert: _cert, - Labels: map[string]string{ - "build": "16", - "env": "production", - "version": "v2", - }, - Snis: []string{"test2.com", "*.test2.com"}, - Status: 1, - }, - wantErr: fmt.Errorf("create failed"), - wantRet: handler.SpecCodeResponse(fmt.Errorf("create failed")), - }, - } - - for _, tc := range tests { - t.Run(tc.caseDesc, func(t *testing.T) { - getCalled := false - - sslStore := &store.MockInterface{} - sslStore.On("Create", mock.Anything, mock.Anything).Run(func(args mock.Arguments) { - getCalled = true - input := args.Get(1).(*entity.SSL) - assert.Equal(t, tc.wantInput, input) - }).Return(tc.giveRet, tc.giveErr) - - h := Handler{sslStore: sslStore} - - ctx := droplet.NewContext() - ctx.SetInput(tc.giveInput) - ret, err := h.Create(ctx) - assert.True(t, getCalled) - assert.Equal(t, tc.wantRet, ret) - assert.Equal(t, tc.wantErr, err) - }) - } -} - -func TestSSL_Update(t *testing.T) { - _cert, _key := getTestKeyCert(t) - tests := []struct { - caseDesc string - getCalled bool - giveInput *UpdateInput - giveErr error - giveRet interface{} - wantInput *entity.SSL - wantErr error - wantRet interface{} - }{ - { - caseDesc: "create success", - getCalled: true, - giveInput: &UpdateInput{ - ID: "ssl1", - SSL: entity.SSL{ - Key: _key, - Cert: _cert, - Labels: map[string]string{ - "build": "16", - "env": "production", - "version": "v2", - }, - }, - }, - giveRet: &entity.SSL{ - BaseInfo: entity.BaseInfo{ - ID: "ssl1", - }, - Key: _key, - Cert: _cert, - Labels: map[string]string{ - "build": "16", - "env": "production", - "version": "v2", - }, - Snis: []string{"test2.com", "*.test2.com"}, - Status: 1, - }, - wantInput: &entity.SSL{ - BaseInfo: entity.BaseInfo{ - ID: "ssl1", - }, - Key: _key, - Cert: _cert, - Labels: map[string]string{ - "build": "16", - "env": "production", - "version": "v2", - }, - Snis: []string{"test2.com", "*.test2.com"}, - Status: 1, - }, - wantRet: &entity.SSL{ - BaseInfo: entity.BaseInfo{ - ID: "ssl1", - }, - Key: "", - Cert: _cert, - Labels: map[string]string{ - "build": "16", - "env": "production", - "version": "v2", - }, - Snis: []string{"test2.com", "*.test2.com"}, - Status: 1, - }, - }, - { - caseDesc: "create failed, different id", - giveInput: &UpdateInput{ - ID: "ssl1", - SSL: entity.SSL{ - BaseInfo: entity.BaseInfo{ - ID: "ssl2", - }, - Key: _key, - Cert: _cert, - Labels: map[string]string{ - "build": "16", - "env": "production", - "version": "v2", - }, - }, - }, - wantRet: &data.SpecCodeResponse{StatusCode: http.StatusBadRequest}, - wantErr: fmt.Errorf("ID on path (ssl1) doesn't match ID on body (ssl2)"), - }, - } - - for _, tc := range tests { - t.Run(tc.caseDesc, func(t *testing.T) { - getCalled := false - sslStore := &store.MockInterface{} - sslStore.On("Update", mock.Anything, mock.Anything, mock.Anything).Run(func(args mock.Arguments) { - getCalled = true - input := args.Get(1).(*entity.SSL) - createIfNotExist := args.Get(2).(bool) - assert.Equal(t, tc.wantInput, input) - assert.True(t, createIfNotExist) - }).Return(tc.giveRet, tc.giveErr) - - h := Handler{sslStore: sslStore} - ctx := droplet.NewContext() - ctx.SetInput(tc.giveInput) - ret, err := h.Update(ctx) - assert.Equal(t, tc.getCalled, getCalled) - assert.Equal(t, tc.wantRet, ret) - assert.Equal(t, tc.wantErr, err) - }) - } -} - -func TestSSL_Patch(t *testing.T) { - _cert, _key := getTestKeyCert(t) - existSSL := &entity.SSL{ - BaseInfo: entity.BaseInfo{ - ID: "ssl1", - CreateTime: 1609340491, - UpdateTime: 1609340491, - }, - Status: 0, - Key: _key, - Cert: _cert, - Labels: map[string]string{ - "build": "16", - "env": "production", - "version": "v2", - }, - } - - patchSSL := &entity.SSL{ - BaseInfo: entity.BaseInfo{ - ID: "ssl1", - CreateTime: 1609340491, - UpdateTime: 1609340491, - }, - Status: 1, - Key: _key, - Cert: _cert, - Labels: map[string]string{ - "build": "16", - "env": "production", - "version": "v2", - }, - } - patchSSLBytes, err := json.Marshal(patchSSL) - assert.Nil(t, err) - - tests := []struct { - caseDesc string - getCalled bool - giveInput *PatchInput - giveErr error - giveRet interface{} - wantInput *entity.SSL - wantErr error - wantRet interface{} - }{ - { - caseDesc: "patch success", - giveRet: &entity.SSL{ - BaseInfo: entity.BaseInfo{ - ID: "ssl1", - }, - Key: _key, - Cert: _cert, - Labels: map[string]string{ - "build": "16", - "env": "production", - "version": "v2", - }, - Snis: []string{"test2.com", "*.test2.com"}, - Status: 1, - }, - giveInput: &PatchInput{ - ID: "ssl1", - SubPath: "", - Body: patchSSLBytes, - }, - wantInput: &entity.SSL{ - BaseInfo: entity.BaseInfo{ - ID: "ssl1", - CreateTime: 1609340491, - UpdateTime: 1609340491, - }, - Status: 1, - Key: _key, - Cert: _cert, - Labels: map[string]string{ - "build": "16", - "env": "production", - "version": "v2", - }, - }, - wantRet: &entity.SSL{ - BaseInfo: entity.BaseInfo{ - ID: "ssl1", - }, - Key: "", - Cert: _cert, - Labels: map[string]string{ - "build": "16", - "env": "production", - "version": "v2", - }, - Snis: []string{"test2.com", "*.test2.com"}, - Status: 1, - }, - getCalled: true, - }, - { - caseDesc: "patch success by path", - giveInput: &PatchInput{ - ID: "ssl1", - SubPath: "/status", - Body: []byte("1"), - }, - giveRet: &entity.SSL{ - BaseInfo: entity.BaseInfo{ - ID: "ssl1", - }, - Key: _key, - Cert: _cert, - Labels: map[string]string{ - "build": "16", - "env": "production", - "version": "v2", - }, - Snis: []string{"test2.com", "*.test2.com"}, - Status: 1, - }, - wantInput: &entity.SSL{ - BaseInfo: entity.BaseInfo{ - ID: "ssl1", - CreateTime: 1609340491, - UpdateTime: 1609340491, - }, - Status: 1, - Key: _key, - Cert: _cert, - Labels: map[string]string{ - "build": "16", - "env": "production", - "version": "v2", - }, - }, - wantRet: &entity.SSL{ - BaseInfo: entity.BaseInfo{ - ID: "ssl1", - }, - Key: "", - Cert: _cert, - Labels: map[string]string{ - "build": "16", - "env": "production", - "version": "v2", - }, - Snis: []string{"test2.com", "*.test2.com"}, - Status: 1, - }, - getCalled: true, - }, - { - caseDesc: "patch failed, path error", - giveInput: &PatchInput{ - ID: "ssl", - SubPath: "error", - Body: []byte("0"), - }, - wantRet: handler.SpecCodeResponse( - errors.New("add operation does not apply: doc is missing path: \"error\": missing value")), - wantErr: errors.New("add operation does not apply: doc is missing path: \"error\": missing value"), - }, - } - for _, tc := range tests { - t.Run(tc.caseDesc, func(t *testing.T) { - getCalled := false - - sslStore := &store.MockInterface{} - sslStore.On("Get", mock.Anything, mock.Anything).Return(existSSL, nil) - sslStore.On("Update", mock.Anything, mock.Anything, mock.Anything).Run(func(args mock.Arguments) { - getCalled = true - input := args.Get(1).(*entity.SSL) - createIfNotExist := args.Get(2).(bool) - assert.Equal(t, tc.wantInput, input) - assert.False(t, createIfNotExist) - }).Return(tc.giveRet, tc.giveErr) - h := Handler{sslStore: sslStore} - ctx := droplet.NewContext() - ctx.SetInput(tc.giveInput) - ret, err := h.Patch(ctx) - assert.Equal(t, tc.getCalled, getCalled) - assert.Equal(t, tc.wantRet, ret) - if tc.wantErr != nil && err != nil { - assert.Error(t, tc.wantErr.(error), err.Error()) - } else { - assert.Equal(t, tc.wantErr, err) - } - }) - } -} - -func TestSSLs_Delete(t *testing.T) { - tests := []struct { - caseDesc string - giveInput *BatchDelete - giveErr error - wantInput []string - wantErr error - wantRet interface{} - }{ - { - caseDesc: "delete success", - giveInput: &BatchDelete{ - Ids: "ssl1", - }, - wantInput: []string{"ssl1"}, - }, - { - caseDesc: "batch delete success", - giveInput: &BatchDelete{ - Ids: "ssl1,ssl2", - }, - wantInput: []string{"ssl1", "ssl2"}, - }, - { - caseDesc: "delete failed", - giveInput: &BatchDelete{ - Ids: "ssl1", - }, - giveErr: fmt.Errorf("delete error"), - wantInput: []string{"ssl1"}, - wantRet: handler.SpecCodeResponse(fmt.Errorf("delete error")), - wantErr: fmt.Errorf("delete error"), - }, - } - - for _, tc := range tests { - t.Run(tc.caseDesc, func(t *testing.T) { - getCalled := false - sslStore := &store.MockInterface{} - sslStore.On("BatchDelete", mock.Anything, mock.Anything).Run(func(args mock.Arguments) { - getCalled = true - input := args.Get(1).([]string) - assert.Equal(t, tc.wantInput, input) - }).Return(tc.giveErr) - - h := Handler{sslStore: sslStore} - ctx := droplet.NewContext() - ctx.SetInput(tc.giveInput) - ret, err := h.BatchDelete(ctx) - assert.True(t, getCalled) - assert.Equal(t, tc.wantRet, ret) - assert.Equal(t, tc.wantErr, err) - }) - } -} - -func TestSSL_Exist(t *testing.T) { - mockData := []*entity.SSL{ - { - BaseInfo: entity.BaseInfo{ - ID: "ssl1", - CreateTime: 1609340491, - UpdateTime: 1609340491, - }, - Sni: "route", - Status: 0, - Labels: map[string]string{ - "build": "16", - "env": "production", - "version": "v2", - }, - }, - { - BaseInfo: entity.BaseInfo{ - ID: "ssl2", - CreateTime: 1609340492, - UpdateTime: 1609340492, - }, - Sni: "www.route.com", - Status: 1, - Labels: map[string]string{ - "build": "17", - "env": "production", - "version": "v2", - }, - }, - { - BaseInfo: entity.BaseInfo{ - ID: "ssl3", - CreateTime: 1609340493, - UpdateTime: 1609340493, - }, - Snis: []string{"test.com", "ssl_test.com"}, - Status: 0, - Labels: map[string]string{ - "build": "18", - "env": "production", - "version": "v2", - }, - }, - } - - tests := []struct { - caseDesc string - giveInput *ExistCheckInput - giveErr error - getCalled bool - wantInput []string - wantErr error - wantRet interface{} - }{ - { - caseDesc: "check SSL cert not exists for sni", - giveInput: &ExistCheckInput{ - Hosts: []string{"www.route2.com"}, - }, - wantRet: &data.SpecCodeResponse{StatusCode: http.StatusNotFound}, - wantErr: consts.InvalidParam("SSL cert not exists for sni๏ผšwww.route2.com"), - getCalled: true, - }, - { - caseDesc: "check SSL cert exists for sni", - giveInput: &ExistCheckInput{ - Hosts: []string{"www.route.com"}, - }, - wantRet: nil, - getCalled: true, - }, - { - caseDesc: "check SSL cert not exists for snis", - giveInput: &ExistCheckInput{ - Hosts: []string{"test1.com", "ssl_test2.com"}, - }, - wantRet: &data.SpecCodeResponse{StatusCode: http.StatusNotFound}, - wantErr: consts.InvalidParam("SSL cert not exists for sni๏ผštest1.com"), - getCalled: true, - }, - { - caseDesc: "check SSL cert exists for snis", - giveInput: &ExistCheckInput{ - Hosts: []string{"ssl_test.com"}, - }, - wantRet: nil, - getCalled: true, - }, - } - - for _, tc := range tests { - t.Run(tc.caseDesc, func(t *testing.T) { - getCalled := false - sslStore := &store.MockInterface{} - sslStore.On("List", mock.Anything, mock.Anything).Run(func(args mock.Arguments) { - getCalled = true - }).Return(func(input store.ListInput) *store.ListOutput { - var res []interface{} - for _, c := range mockData { - res = append(res, c) - } - - return &store.ListOutput{ - Rows: res, - TotalSize: len(res), - } - }, nil) - - h := Handler{sslStore: sslStore} - ctx := droplet.NewContext() - ctx.SetInput(tc.giveInput) - ret, err := h.Exist(ctx) - assert.True(t, getCalled) - assert.Equal(t, tc.wantRet, ret) - assert.Equal(t, tc.wantErr, err) - }) - } -} diff --git a/api/internal/handler/stream_route/stream_route.go b/api/internal/handler/stream_route/stream_route.go deleted file mode 100644 index 66253fc747..0000000000 --- a/api/internal/handler/stream_route/stream_route.go +++ /dev/null @@ -1,186 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one or more - * contributor license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright ownership. - * The ASF licenses this file to You under the Apache License, Version 2.0 - * (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package stream_route - -import ( - "fmt" - "net/http" - "reflect" - "strings" - - "github.com/gin-gonic/gin" - "github.com/shiningrush/droplet" - "github.com/shiningrush/droplet/data" - "github.com/shiningrush/droplet/wrapper" - wgin "github.com/shiningrush/droplet/wrapper/gin" - - "github.com/apisix/manager-api/internal/core/entity" - "github.com/apisix/manager-api/internal/core/store" - "github.com/apisix/manager-api/internal/handler" - "github.com/apisix/manager-api/internal/utils" -) - -type Handler struct { - streamRouteStore store.Interface - upstreamStore store.Interface -} - -func NewHandler() (handler.RouteRegister, error) { - return &Handler{ - streamRouteStore: store.GetStore(store.HubKeyStreamRoute), - upstreamStore: store.GetStore(store.HubKeyUpstream), - }, nil -} - -func (h *Handler) ApplyRoute(r *gin.Engine) { - r.GET("/apisix/admin/stream_routes/:id", wgin.Wraps(h.Get, - wrapper.InputType(reflect.TypeOf(GetInput{})))) - r.GET("/apisix/admin/stream_routes", wgin.Wraps(h.List, - wrapper.InputType(reflect.TypeOf(ListInput{})))) - r.POST("/apisix/admin/stream_routes", wgin.Wraps(h.Create, - wrapper.InputType(reflect.TypeOf(entity.StreamRoute{})))) - r.PUT("/apisix/admin/stream_routes", wgin.Wraps(h.Update, - wrapper.InputType(reflect.TypeOf(UpdateInput{})))) - r.PUT("/apisix/admin/stream_routes/:id", wgin.Wraps(h.Update, - wrapper.InputType(reflect.TypeOf(UpdateInput{})))) - r.DELETE("/apisix/admin/stream_routes/:ids", wgin.Wraps(h.BatchDelete, - wrapper.InputType(reflect.TypeOf(BatchDelete{})))) -} - -type GetInput struct { - ID string `auto_read:"id,path" validate:"required"` -} - -func (h *Handler) Get(c droplet.Context) (interface{}, error) { - input := c.Input().(*GetInput) - streamRoute, err := h.streamRouteStore.Get(c.Context(), input.ID) - if err != nil { - return handler.SpecCodeResponse(err), err - } - return streamRoute, nil -} - -type ListInput struct { - RemoteAddr string `auto_read:"remote_addr,query"` - ServerAddr string `auto_read:"server_addr,query"` - ServerPort int `auto_read:"server_port,query"` - SNI string `auto_read:"sni,query"` - store.Pagination -} - -func (h *Handler) List(c droplet.Context) (interface{}, error) { - input := c.Input().(*ListInput) - ret, err := h.streamRouteStore.List(c.Context(), store.ListInput{ - Predicate: func(obj interface{}) bool { - if input.RemoteAddr != "" && !strings.Contains(obj.(*entity.StreamRoute).RemoteAddr, input.RemoteAddr) { - return false - } - - if input.ServerAddr != "" && !strings.Contains(obj.(*entity.StreamRoute).ServerAddr, input.ServerAddr) { - return false - } - - if input.ServerPort != 0 && obj.(*entity.StreamRoute).ServerPort != input.ServerPort { - return false - } - - if input.SNI != "" && !strings.Contains(obj.(*entity.StreamRoute).SNI, input.SNI) { - return false - } - - return true - }, - PageSize: input.PageSize, - PageNumber: input.PageNumber, - }) - - if err != nil { - return nil, err - } - return ret, nil -} - -func (h *Handler) Create(c droplet.Context) (interface{}, error) { - streamRoute := c.Input().(*entity.StreamRoute) - if streamRoute.UpstreamID != nil { - upstreamID := utils.InterfaceToString(streamRoute.UpstreamID) - _, err := h.upstreamStore.Get(c.Context(), upstreamID) - if err != nil { - if err == data.ErrNotFound { - return &data.SpecCodeResponse{StatusCode: http.StatusBadRequest}, - fmt.Errorf("upstream id: %s not found", streamRoute.UpstreamID) - } - return handler.SpecCodeResponse(err), err - } - } - create, err := h.streamRouteStore.Create(c.Context(), streamRoute) - if err != nil { - return handler.SpecCodeResponse(err), err - } - return create, nil -} - -type UpdateInput struct { - ID string `auto_read:"id,path"` - entity.StreamRoute -} - -func (h *Handler) Update(c droplet.Context) (interface{}, error) { - input := c.Input().(*UpdateInput) - - // check if ID in body is equal ID in path - if err := handler.IDCompare(input.ID, input.StreamRoute.ID); err != nil { - return &data.SpecCodeResponse{StatusCode: http.StatusBadRequest}, err - } - - // if has id in path, use it - if input.ID != "" { - input.StreamRoute.ID = input.ID - } - - if input.UpstreamID != nil { - upstreamID := utils.InterfaceToString(input.UpstreamID) - _, err := h.upstreamStore.Get(c.Context(), upstreamID) - if err != nil { - if err == data.ErrNotFound { - return &data.SpecCodeResponse{StatusCode: http.StatusBadRequest}, - fmt.Errorf("upstream id: %s not found", input.UpstreamID) - } - return &data.SpecCodeResponse{StatusCode: http.StatusBadRequest}, err - } - } - res, err := h.streamRouteStore.Update(c.Context(), &input.StreamRoute, true) - if err != nil { - return handler.SpecCodeResponse(err), err - } - - return res, nil -} - -type BatchDelete struct { - IDs string `auto_read:"ids,path"` -} - -func (h *Handler) BatchDelete(c droplet.Context) (interface{}, error) { - input := c.Input().(*BatchDelete) - - if err := h.streamRouteStore.BatchDelete(c.Context(), strings.Split(input.IDs, ",")); err != nil { - return handler.SpecCodeResponse(err), err - } - - return nil, nil -} diff --git a/api/internal/handler/stream_route/stream_route_test.go b/api/internal/handler/stream_route/stream_route_test.go deleted file mode 100644 index 3dc3fc781f..0000000000 --- a/api/internal/handler/stream_route/stream_route_test.go +++ /dev/null @@ -1,188 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one or more - * contributor license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright ownership. - * The ASF licenses this file to You under the Apache License, Version 2.0 - * (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package stream_route - -import ( - "encoding/json" - "testing" - - "github.com/shiningrush/droplet" - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/mock" - - "github.com/apisix/manager-api/internal/core/entity" - "github.com/apisix/manager-api/internal/core/store" -) - -func TestStructUnmarshal(t *testing.T) { - // define and parse data - jsonStr := `{ - "id": 1, - "create_time": 1700000000, - "update_time": 1700000000, - "desc": "desc", - "remote_addr": "1.1.1.1", - "server_addr": "2.2.2.2", - "server_port": 9080, - "sni": "example.com", - "upstream": { - "nodes": [ - { - "host": "10.10.10.10", - "port": 8080, - "weight": 1 - } - ], - "type": "roundrobin", - "scheme": "http", - "pass_host": "pass" - }, - "upstream_id": 1 -}` - streamRoute := entity.StreamRoute{} - err := json.Unmarshal([]byte(jsonStr), &streamRoute) - - // asserts - assert.Nil(t, err) - assert.Equal(t, streamRoute.ID, float64(1)) - assert.Equal(t, streamRoute.CreateTime, int64(1700000000)) - assert.Equal(t, streamRoute.UpdateTime, int64(1700000000)) - assert.Equal(t, streamRoute.Desc, "desc") - assert.Equal(t, streamRoute.RemoteAddr, "1.1.1.1") - assert.Equal(t, streamRoute.ServerAddr, "2.2.2.2") - assert.Equal(t, streamRoute.ServerPort, 9080) - assert.Equal(t, streamRoute.SNI, "example.com") - assert.Equal(t, streamRoute.UpstreamID, float64(1)) - assert.NotNil(t, streamRoute.Upstream) -} - -func TestStreamRouteConditionList(t *testing.T) { - giveData := []*entity.StreamRoute{ - {BaseInfo: entity.BaseInfo{CreateTime: 1609376663}, RemoteAddr: "127.0.0.1", ServerAddr: "127.0.0.1", ServerPort: 9090, Upstream: nil, UpstreamID: "u1"}, - {BaseInfo: entity.BaseInfo{CreateTime: 1609376664}, RemoteAddr: "127.0.0.2", ServerAddr: "127.0.0.1", ServerPort: 9091, Upstream: nil, UpstreamID: "u1"}, - {BaseInfo: entity.BaseInfo{CreateTime: 1609376665}, RemoteAddr: "127.0.0.3", ServerAddr: "127.0.0.1", ServerPort: 9092, Upstream: nil, UpstreamID: "u1"}, - {BaseInfo: entity.BaseInfo{CreateTime: 1609376666}, RemoteAddr: "127.0.0.4", ServerAddr: "127.0.0.1", ServerPort: 9093, Upstream: nil, UpstreamID: "u1"}, - } - tests := []struct { - desc string - giveInput *ListInput - giveErr error - wantErr error - wantRet interface{} - }{ - { - desc: "list all stream route", - giveInput: &ListInput{ - Pagination: store.Pagination{ - PageSize: 10, - PageNumber: 10, - }, - }, - wantRet: &store.ListOutput{ - Rows: []interface{}{ - giveData[0], giveData[1], giveData[2], giveData[3], - }, - TotalSize: 4, - }, - }, - { - desc: "list stream route with remote_addr", - giveInput: &ListInput{ - RemoteAddr: "127.0.0.1", - Pagination: store.Pagination{ - PageSize: 10, - PageNumber: 10, - }, - }, - wantRet: &store.ListOutput{ - Rows: []interface{}{ - &entity.StreamRoute{BaseInfo: entity.BaseInfo{CreateTime: 1609376663}, RemoteAddr: "127.0.0.1", ServerAddr: "127.0.0.1", ServerPort: 9090, Upstream: nil, UpstreamID: "u1"}, - }, - TotalSize: 1, - }, - }, - { - desc: "list stream route with server_addr", - giveInput: &ListInput{ - ServerAddr: "127.0.0.1", - Pagination: store.Pagination{ - PageSize: 10, - PageNumber: 10, - }, - }, - wantRet: &store.ListOutput{ - Rows: []interface{}{ - &entity.StreamRoute{BaseInfo: entity.BaseInfo{CreateTime: 1609376663}, RemoteAddr: "127.0.0.1", ServerAddr: "127.0.0.1", ServerPort: 9090, Upstream: nil, UpstreamID: "u1"}, - &entity.StreamRoute{BaseInfo: entity.BaseInfo{CreateTime: 1609376664}, RemoteAddr: "127.0.0.2", ServerAddr: "127.0.0.1", ServerPort: 9091, Upstream: nil, UpstreamID: "u1"}, - &entity.StreamRoute{BaseInfo: entity.BaseInfo{CreateTime: 1609376665}, RemoteAddr: "127.0.0.3", ServerAddr: "127.0.0.1", ServerPort: 9092, Upstream: nil, UpstreamID: "u1"}, - &entity.StreamRoute{BaseInfo: entity.BaseInfo{CreateTime: 1609376666}, RemoteAddr: "127.0.0.4", ServerAddr: "127.0.0.1", ServerPort: 9093, Upstream: nil, UpstreamID: "u1"}, - }, - TotalSize: 4, - }, - }, - { - desc: "list stream route with server_port", - giveInput: &ListInput{ - ServerPort: 9092, - Pagination: store.Pagination{ - PageSize: 10, - PageNumber: 10, - }, - }, - wantRet: &store.ListOutput{ - Rows: []interface{}{ - &entity.StreamRoute{BaseInfo: entity.BaseInfo{CreateTime: 1609376665}, RemoteAddr: "127.0.0.3", ServerAddr: "127.0.0.1", ServerPort: 9092, Upstream: nil, UpstreamID: "u1"}, - }, - TotalSize: 1, - }, - }, - } - - for _, tc := range tests { - t.Run(tc.desc, func(t *testing.T) { - getCalled := true - mStore := &store.MockInterface{} - mStore.On("List", mock.Anything, mock.Anything).Run(func(args mock.Arguments) { - getCalled = true - }).Return(func(input store.ListInput) *store.ListOutput { - var returnData []interface{} - for _, c := range giveData { - if input.Predicate(c) { - if input.Format == nil { - returnData = append(returnData, c) - continue - } - - returnData = append(returnData, input.Format(c)) - } - } - return &store.ListOutput{ - Rows: returnData, - TotalSize: len(returnData), - } - }, tc.giveErr) - - h := Handler{streamRouteStore: mStore} - ctx := droplet.NewContext() - ctx.SetInput(tc.giveInput) - ret, err := h.List(ctx) - assert.True(t, getCalled) - assert.Equal(t, tc.wantRet, ret) - assert.Equal(t, tc.wantErr, err) - }) - } -} diff --git a/api/internal/handler/system_config/system_config.go b/api/internal/handler/system_config/system_config.go deleted file mode 100644 index 154c029da9..0000000000 --- a/api/internal/handler/system_config/system_config.go +++ /dev/null @@ -1,109 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one or more - * contributor license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright ownership. - * The ASF licenses this file to You under the Apache License, Version 2.0 - * (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package system_config - -import ( - "reflect" - "time" - - "github.com/gin-gonic/gin" - "github.com/shiningrush/droplet" - "github.com/shiningrush/droplet/wrapper" - wgin "github.com/shiningrush/droplet/wrapper/gin" - - "github.com/apisix/manager-api/internal/core/entity" - "github.com/apisix/manager-api/internal/core/store" - "github.com/apisix/manager-api/internal/handler" -) - -type Handler struct { - systemConfig store.Interface -} - -func NewHandler() (handler.RouteRegister, error) { - return &Handler{ - systemConfig: store.GetStore(store.HubKeySystemConfig), - }, nil -} - -func (h *Handler) ApplyRoute(r *gin.Engine) { - r.GET("/apisix/admin/system_config/:config_name", wgin.Wraps(h.Get, - wrapper.InputType(reflect.TypeOf(GetInput{})))) - r.POST("/apisix/admin/system_config", wgin.Wraps(h.Post, - wrapper.InputType(reflect.TypeOf(entity.SystemConfig{})))) - r.PUT("/apisix/admin/system_config", wgin.Wraps(h.Put, - wrapper.InputType(reflect.TypeOf(entity.SystemConfig{})))) - r.DELETE("/apisix/admin/system_config/:config_name", wgin.Wraps(h.Delete, - wrapper.InputType(reflect.TypeOf(DeleteInput{})))) -} - -type GetInput struct { - ConfigName string `auto_read:"config_name,path" validate:"required"` -} - -func (h *Handler) Get(c droplet.Context) (interface{}, error) { - input := c.Input().(*GetInput) - r, err := h.systemConfig.Get(c.Context(), input.ConfigName) - - if err != nil { - return handler.SpecCodeResponse(err), err - } - - return r, nil -} - -func (h *Handler) Post(c droplet.Context) (interface{}, error) { - input := c.Input().(*entity.SystemConfig) - input.CreateTime = time.Now().Unix() - input.UpdateTime = time.Now().Unix() - - // create - res, err := h.systemConfig.Create(c.Context(), input) - if err != nil { - return handler.SpecCodeResponse(err), err - } - - return res, nil -} - -func (h *Handler) Put(c droplet.Context) (interface{}, error) { - input := c.Input().(*entity.SystemConfig) - input.UpdateTime = time.Now().Unix() - - // update - res, err := h.systemConfig.Update(c.Context(), input, false) - if err != nil { - return handler.SpecCodeResponse(err), err - } - - return res, nil -} - -type DeleteInput struct { - ConfigName string `auto_read:"config_name,path" validate:"required"` -} - -func (h *Handler) Delete(c droplet.Context) (interface{}, error) { - input := c.Input().(*DeleteInput) - err := h.systemConfig.BatchDelete(c.Context(), []string{input.ConfigName}) - - if err != nil { - return handler.SpecCodeResponse(err), err - } - - return nil, nil -} diff --git a/api/internal/handler/system_config/system_config_test.go b/api/internal/handler/system_config/system_config_test.go deleted file mode 100644 index ca5d6acc5b..0000000000 --- a/api/internal/handler/system_config/system_config_test.go +++ /dev/null @@ -1,258 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one or more - * contributor license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright ownership. - * The ASF licenses this file to You under the Apache License, Version 2.0 - * (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package system_config - -import ( - "errors" - "testing" - - "github.com/shiningrush/droplet" - "github.com/shiningrush/droplet/data" - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/mock" - - "github.com/apisix/manager-api/internal/core/entity" - "github.com/apisix/manager-api/internal/core/store" -) - -func TestSystem_Get(t *testing.T) { - t.Parallel() - type testCase struct { - caseDesc string - giveInput *GetInput - wantErr error - wantRet interface{} - mockStore store.Interface - mockFunc func(tc *testCase) - } - - cases := []*testCase{ - { - caseDesc: "system config not found", - giveInput: &GetInput{ConfigName: "grafana"}, - wantErr: data.ErrNotFound, - mockFunc: func(tc *testCase) { - mockStore := &store.MockInterface{} - mockStore.On("Get", mock.Anything, mock.Anything).Return(nil, tc.wantErr) - tc.mockStore = mockStore - }, - }, - { - caseDesc: "get system config success", - giveInput: &GetInput{ConfigName: "grafana"}, - wantErr: nil, - wantRet: entity.SystemConfig{ - ConfigName: "grafana", - Payload: map[string]interface{}{ - "url": "http://127.0.0.1:3000", - }, - }, - mockFunc: func(tc *testCase) { - mockStore := &store.MockInterface{} - mockStore.On("Get", mock.Anything, mock.Anything).Return(tc.wantRet, nil) - tc.mockStore = mockStore - }, - }, - } - - for _, tc := range cases { - t.Run(tc.caseDesc, func(t *testing.T) { - tc.mockFunc(tc) - h := Handler{tc.mockStore} - ctx := droplet.NewContext() - ctx.SetInput(tc.giveInput) - ret, err := h.Get(ctx) - assert.Equal(t, err, tc.wantErr) - if err == nil { - assert.Equal(t, ret, tc.wantRet) - } - }) - } -} - -func TestSystem_Post(t *testing.T) { - t.Parallel() - type testCase struct { - caseDesc string - giveInput *entity.SystemConfig - wantErr error - wantRet interface{} - mockStore store.Interface - mockFunc func(tc *testCase) - } - - systemConfig := entity.SystemConfig{ - ConfigName: "grafana", - Payload: map[string]interface{}{ - "url": "http://127.0.0.1:3000", - }, - } - - cases := []*testCase{ - { - caseDesc: "create system config error", - giveInput: &systemConfig, - wantErr: errors.New("mock error"), - mockFunc: func(tc *testCase) { - mockStore := &store.MockInterface{} - mockStore.On("Create", mock.Anything, mock.Anything).Return(nil, tc.wantErr) - tc.mockStore = mockStore - }, - }, - { - caseDesc: "create system config success", - giveInput: &systemConfig, - wantErr: nil, - wantRet: entity.SystemConfig{ - ConfigName: "grafana", - Payload: map[string]interface{}{ - "url": "http://127.0.0.1:3000", - }, - }, - mockFunc: func(tc *testCase) { - mockStore := &store.MockInterface{} - mockStore.On("Create", mock.Anything, mock.Anything).Return(tc.wantRet, nil) - tc.mockStore = mockStore - }, - }, - } - - for _, tc := range cases { - t.Run(tc.caseDesc, func(t *testing.T) { - tc.mockFunc(tc) - h := Handler{tc.mockStore} - ctx := droplet.NewContext() - ctx.SetInput(tc.giveInput) - ret, err := h.Post(ctx) - assert.Equal(t, err, tc.wantErr) - if err == nil { - assert.Equal(t, ret, tc.wantRet) - } - }) - } -} - -func TestSystem_Put(t *testing.T) { - t.Parallel() - type testCase struct { - caseDesc string - giveInput *entity.SystemConfig - wantErr error - wantRet interface{} - mockStore store.Interface - mockFunc func(tc *testCase) - } - - systemConfig := entity.SystemConfig{ - ConfigName: "grafana", - Payload: map[string]interface{}{ - "url": "http://127.0.0.1:3000", - }, - } - - cases := []*testCase{ - { - caseDesc: "update system config error", - giveInput: &systemConfig, - wantErr: errors.New("mock error"), - mockFunc: func(tc *testCase) { - mockStore := &store.MockInterface{} - mockStore.On("Update", mock.Anything, mock.Anything, mock.Anything).Return(nil, tc.wantErr) - tc.mockStore = mockStore - }, - }, - { - caseDesc: "update system config success", - giveInput: &systemConfig, - wantErr: nil, - wantRet: entity.SystemConfig{ - ConfigName: "grafana", - Payload: map[string]interface{}{ - "url": "http://127.0.0.1:3000", - }, - }, - mockFunc: func(tc *testCase) { - mockStore := &store.MockInterface{} - mockStore.On("Update", mock.Anything, mock.Anything, mock.Anything).Return(tc.wantRet, nil) - tc.mockStore = mockStore - }, - }, - } - - for _, tc := range cases { - t.Run(tc.caseDesc, func(t *testing.T) { - tc.mockFunc(tc) - h := Handler{tc.mockStore} - ctx := droplet.NewContext() - ctx.SetInput(tc.giveInput) - ret, err := h.Put(ctx) - assert.Equal(t, err, tc.wantErr) - if err == nil { - assert.Equal(t, ret, tc.wantRet) - } - }) - } -} - -func TestSystem_Delete(t *testing.T) { - t.Parallel() - type testCase struct { - caseDesc string - giveInput *DeleteInput - wantErr error - wantRet interface{} - mockStore store.Interface - mockFunc func(tc *testCase) - } - - cases := []*testCase{ - { - caseDesc: "delete system config error", - giveInput: &DeleteInput{ConfigName: "grafana"}, - wantErr: errors.New("mock error"), - mockFunc: func(tc *testCase) { - mockStore := &store.MockInterface{} - mockStore.On("BatchDelete", mock.Anything, mock.Anything).Return(tc.wantErr) - tc.mockStore = mockStore - }, - }, - { - caseDesc: "delete system config success", - giveInput: &DeleteInput{ConfigName: "grafana"}, - wantErr: nil, - mockFunc: func(tc *testCase) { - mockStore := &store.MockInterface{} - mockStore.On("BatchDelete", mock.Anything, mock.Anything).Return(tc.wantRet) - tc.mockStore = mockStore - }, - }, - } - - for _, tc := range cases { - t.Run(tc.caseDesc, func(t *testing.T) { - tc.mockFunc(tc) - h := Handler{tc.mockStore} - ctx := droplet.NewContext() - ctx.SetInput(tc.giveInput) - ret, err := h.Delete(ctx) - assert.Equal(t, err, tc.wantErr) - if err == nil { - assert.Equal(t, ret, tc.wantRet) - } - }) - } -} diff --git a/api/internal/handler/tool/tool.go b/api/internal/handler/tool/tool.go deleted file mode 100644 index c3f9e996f1..0000000000 --- a/api/internal/handler/tool/tool.go +++ /dev/null @@ -1,50 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one or more - * contributor license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright ownership. - * The ASF licenses this file to You under the Apache License, Version 2.0 - * (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package tool - -import ( - "github.com/gin-gonic/gin" - "github.com/shiningrush/droplet" - wgin "github.com/shiningrush/droplet/wrapper/gin" - - "github.com/apisix/manager-api/internal/handler" - "github.com/apisix/manager-api/internal/utils" -) - -type Handler struct { -} - -type InfoOutput struct { - Hash string `json:"commit_hash"` - Version string `json:"version"` -} - -func NewHandler() (handler.RouteRegister, error) { - return &Handler{}, nil -} - -func (h *Handler) ApplyRoute(r *gin.Engine) { - r.GET("/apisix/admin/tool/version", wgin.Wraps(h.Version)) -} - -func (h *Handler) Version(_ droplet.Context) (interface{}, error) { - hash, version := utils.GetHashAndVersion() - return &InfoOutput{ - Hash: hash, - Version: version, - }, nil -} diff --git a/api/internal/handler/tool/tool_test.go b/api/internal/handler/tool/tool_test.go deleted file mode 100644 index 304249fd36..0000000000 --- a/api/internal/handler/tool/tool_test.go +++ /dev/null @@ -1,40 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one or more - * contributor license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright ownership. - * The ASF licenses this file to You under the Apache License, Version 2.0 - * (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package tool - -import ( - "testing" - - "github.com/shiningrush/droplet" - "github.com/stretchr/testify/assert" - - "github.com/apisix/manager-api/internal/utils" -) - -func TestTool_Version(t *testing.T) { - h := Handler{} - ctx := droplet.NewContext() - - hash, version := utils.GetHashAndVersion() - - ret, err := h.Version(ctx) - assert.Nil(t, err) - assert.Equal(t, &InfoOutput{ - Hash: hash, - Version: version, - }, ret) -} diff --git a/api/internal/handler/upstream/upstream.go b/api/internal/handler/upstream/upstream.go deleted file mode 100644 index 55c08a50bd..0000000000 --- a/api/internal/handler/upstream/upstream.go +++ /dev/null @@ -1,398 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one or more - * contributor license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright ownership. - * The ASF licenses this file to You under the Apache License, Version 2.0 - * (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package upstream - -import ( - "encoding/json" - "fmt" - "net/http" - "reflect" - "strings" - - "github.com/gin-gonic/gin" - "github.com/shiningrush/droplet" - "github.com/shiningrush/droplet/data" - "github.com/shiningrush/droplet/wrapper" - wgin "github.com/shiningrush/droplet/wrapper/gin" - - "github.com/apisix/manager-api/internal/core/entity" - "github.com/apisix/manager-api/internal/core/store" - "github.com/apisix/manager-api/internal/handler" - "github.com/apisix/manager-api/internal/utils" - "github.com/apisix/manager-api/internal/utils/consts" -) - -type Handler struct { - upstreamStore store.Interface - routeStore store.Interface - serviceStore store.Interface - streamRouteStore store.Interface -} - -func NewHandler() (handler.RouteRegister, error) { - return &Handler{ - upstreamStore: store.GetStore(store.HubKeyUpstream), - routeStore: store.GetStore(store.HubKeyRoute), - serviceStore: store.GetStore(store.HubKeyService), - streamRouteStore: store.GetStore(store.HubKeyStreamRoute), - }, nil -} - -func (h *Handler) ApplyRoute(r *gin.Engine) { - r.GET("/apisix/admin/upstreams/:id", wgin.Wraps(h.Get, - wrapper.InputType(reflect.TypeOf(GetInput{})))) - r.GET("/apisix/admin/upstreams", wgin.Wraps(h.List, - wrapper.InputType(reflect.TypeOf(ListInput{})))) - r.POST("/apisix/admin/upstreams", wgin.Wraps(h.Create, - wrapper.InputType(reflect.TypeOf(entity.Upstream{})))) - r.PUT("/apisix/admin/upstreams", wgin.Wraps(h.Update, - wrapper.InputType(reflect.TypeOf(UpdateInput{})))) - r.PUT("/apisix/admin/upstreams/:id", wgin.Wraps(h.Update, - wrapper.InputType(reflect.TypeOf(UpdateInput{})))) - r.PATCH("/apisix/admin/upstreams/:id", wgin.Wraps(h.Patch, - wrapper.InputType(reflect.TypeOf(PatchInput{})))) - r.PATCH("/apisix/admin/upstreams/:id/*path", wgin.Wraps(h.Patch, - wrapper.InputType(reflect.TypeOf(PatchInput{})))) - r.DELETE("/apisix/admin/upstreams/:ids", wgin.Wraps(h.BatchDelete, - wrapper.InputType(reflect.TypeOf(BatchDelete{})))) - - r.GET("/apisix/admin/notexist/upstreams", wgin.Wraps(h.Exist, - wrapper.InputType(reflect.TypeOf(ExistCheckInput{})))) - - r.GET("/apisix/admin/names/upstreams", wgin.Wraps(h.listUpstreamNames)) -} - -type GetInput struct { - ID string `auto_read:"id,path" validate:"required"` -} - -func (h *Handler) Get(c droplet.Context) (interface{}, error) { - input := c.Input().(*GetInput) - - r, err := h.upstreamStore.Get(c.Context(), input.ID) - if err != nil { - return handler.SpecCodeResponse(err), err - } - - upstream := r.(*entity.Upstream) - upstream.Nodes = entity.NodesFormat(upstream.Nodes) - - return r, nil -} - -type ListInput struct { - Name string `auto_read:"name,query"` - ID string `auto_read:"id,query"` - Desc string `auto_read:"desc,query"` - store.Pagination -} - -// swagger:operation GET /apisix/admin/upstreams getUpstreamList -// -// Return the upstream list according to the specified page number and page size, and can search upstreams by name. -// -// --- -// produces: -// - application/json -// parameters: -// - name: page -// in: query -// description: page number -// required: false -// type: integer -// - name: page_size -// in: query -// description: page size -// required: false -// type: integer -// - name: name -// in: query -// description: name of upstream -// required: false -// type: string -// responses: -// '0': -// description: list response -// schema: -// type: array -// items: -// "$ref": "#/definitions/upstream" -// default: -// description: unexpected error -// schema: -// "$ref": "#/definitions/ApiError" -func (h *Handler) List(c droplet.Context) (interface{}, error) { - input := c.Input().(*ListInput) - - ret, err := h.upstreamStore.List(c.Context(), store.ListInput{ - Predicate: func(obj interface{}) bool { - if input.Name != "" { - return strings.Contains(obj.(*entity.Upstream).Name, input.Name) - } - - if input.Desc != "" { - return strings.Contains(obj.(*entity.Upstream).Desc, input.Desc) - } - if input.ID != "" { - return strings.Contains(utils.InterfaceToString(obj.(*entity.Upstream).ID), input.ID) - } - return true - }, - Format: func(obj interface{}) interface{} { - upstream := obj.(*entity.Upstream) - upstream.Nodes = entity.NodesFormat(upstream.Nodes) - return upstream - }, - PageSize: input.PageSize, - PageNumber: input.PageNumber, - }) - if err != nil { - return nil, err - } - - return ret, nil -} - -func (h *Handler) Create(c droplet.Context) (interface{}, error) { - input := c.Input().(*entity.Upstream) - - // check name existed - ret, err := handler.NameExistCheck(c.Context(), h.upstreamStore, "upstream", input.Name, nil) - if err != nil { - return ret, err - } - - // create - res, err := h.upstreamStore.Create(c.Context(), input) - if err != nil { - return handler.SpecCodeResponse(err), err - } - - return res, nil -} - -type UpdateInput struct { - ID string `auto_read:"id,path"` - entity.Upstream -} - -func (h *Handler) Update(c droplet.Context) (interface{}, error) { - input := c.Input().(*UpdateInput) - - // check if ID in body is equal ID in path - if err := handler.IDCompare(input.ID, input.Upstream.ID); err != nil { - return &data.SpecCodeResponse{StatusCode: http.StatusBadRequest}, err - } - - if input.ID != "" { - input.Upstream.ID = input.ID - } - - // check name existed - ret, err := handler.NameExistCheck(c.Context(), h.upstreamStore, "upstream", input.Name, input.ID) - if err != nil { - return ret, err - } - - res, err := h.upstreamStore.Update(c.Context(), &input.Upstream, true) - if err != nil { - return handler.SpecCodeResponse(err), err - } - - return res, nil -} - -type BatchDelete struct { - IDs string `auto_read:"ids,path"` -} - -func (h *Handler) BatchDelete(c droplet.Context) (interface{}, error) { - input := c.Input().(*BatchDelete) - - ids := strings.Split(input.IDs, ",") - mp := make(map[string]struct{}) - for _, id := range ids { - mp[id] = struct{}{} - } - - ret, err := h.routeStore.List(c.Context(), store.ListInput{ - Predicate: func(obj interface{}) bool { - route := obj.(*entity.Route) - if _, exist := mp[utils.InterfaceToString(route.UpstreamID)]; exist { - return true - } - - return false - }, - PageSize: 0, - PageNumber: 0, - }) - if err != nil { - return handler.SpecCodeResponse(err), err - } - - if ret.TotalSize > 0 { - return &data.SpecCodeResponse{StatusCode: http.StatusBadRequest}, - fmt.Errorf("route: %s is using this upstream", ret.Rows[0].(*entity.Route).Name) - } - - ret, err = h.serviceStore.List(c.Context(), store.ListInput{ - Predicate: func(obj interface{}) bool { - service := obj.(*entity.Service) - if _, exist := mp[utils.InterfaceToString(service.UpstreamID)]; exist { - return true - } - - return false - }, - PageSize: 0, - PageNumber: 0, - }) - if err != nil { - return handler.SpecCodeResponse(err), err - } - - if ret.TotalSize > 0 { - return &data.SpecCodeResponse{StatusCode: http.StatusBadRequest}, - fmt.Errorf("service: %s is using this upstream", ret.Rows[0].(*entity.Service).Name) - } - - ret, err = h.streamRouteStore.List(c.Context(), store.ListInput{ - Predicate: func(obj interface{}) bool { - streamRoute := obj.(*entity.StreamRoute) - if _, exist := mp[utils.InterfaceToString(streamRoute.UpstreamID)]; exist { - return true - } - - return false - }, - PageSize: 0, - PageNumber: 0, - }) - if err != nil { - return handler.SpecCodeResponse(err), err - } - - if ret.TotalSize > 0 { - return &data.SpecCodeResponse{StatusCode: http.StatusBadRequest}, - fmt.Errorf("stream route: %s is using this upstream", ret.Rows[0].(*entity.StreamRoute).ID) - } - - if err = h.upstreamStore.BatchDelete(c.Context(), ids); err != nil { - return handler.SpecCodeResponse(err), err - } - - return nil, nil -} - -type PatchInput struct { - ID string `auto_read:"id,path"` - SubPath string `auto_read:"path,path"` - Body []byte `auto_read:"@body"` -} - -func (h *Handler) Patch(c droplet.Context) (interface{}, error) { - input := c.Input().(*PatchInput) - reqBody := input.Body - id := input.ID - subPath := input.SubPath - - stored, err := h.upstreamStore.Get(c.Context(), id) - if err != nil { - return handler.SpecCodeResponse(err), err - } - - res, err := utils.MergePatch(stored, subPath, reqBody) - - if err != nil { - return handler.SpecCodeResponse(err), err - } - - var upstream entity.Upstream - if err := json.Unmarshal(res, &upstream); err != nil { - return handler.SpecCodeResponse(err), err - } - - ret, err := h.upstreamStore.Update(c.Context(), &upstream, false) - if err != nil { - return handler.SpecCodeResponse(err), err - } - - return ret, nil -} - -type ExistInput struct { - Name string `auto_read:"name,query"` -} - -type ExistCheckInput struct { - Name string `auto_read:"name,query"` - Exclude string `auto_read:"exclude,query"` -} - -func (h *Handler) Exist(c droplet.Context) (interface{}, error) { - input := c.Input().(*ExistCheckInput) - name := input.Name - exclude := input.Exclude - - ret, err := h.upstreamStore.List(c.Context(), store.ListInput{ - Predicate: func(obj interface{}) bool { - r := obj.(*entity.Upstream) - if r.Name == name && r.ID != exclude { - return true - } - return false - }, - PageSize: 0, - PageNumber: 0, - }) - - if err != nil { - return nil, err - } - - if ret.TotalSize > 0 { - return &data.SpecCodeResponse{StatusCode: http.StatusBadRequest}, - consts.InvalidParam("Upstream name is reduplicate") - } - return nil, nil -} - -func (h *Handler) listUpstreamNames(c droplet.Context) (interface{}, error) { - ret, err := h.upstreamStore.List(c.Context(), store.ListInput{ - Predicate: nil, - PageSize: 0, - PageNumber: 0, - }) - - if err != nil { - return nil, err - } - - rows := make([]interface{}, ret.TotalSize) - for i := range ret.Rows { - row := ret.Rows[i].(*entity.Upstream) - rows[i], _ = row.Parse2NameResponse() - } - - output := &store.ListOutput{ - Rows: rows, - TotalSize: ret.TotalSize, - } - - return output, nil -} diff --git a/api/internal/handler/upstream/upstream_test.go b/api/internal/handler/upstream/upstream_test.go deleted file mode 100644 index 8b4941e203..0000000000 --- a/api/internal/handler/upstream/upstream_test.go +++ /dev/null @@ -1,1962 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one or more - * contributor license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright ownership. - * The ASF licenses this file to You under the Apache License, Version 2.0 - * (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package upstream - -import ( - "encoding/json" - "errors" - "fmt" - "net/http" - "testing" - - "github.com/shiningrush/droplet" - "github.com/shiningrush/droplet/data" - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/mock" - - "github.com/apisix/manager-api/internal/core/entity" - "github.com/apisix/manager-api/internal/core/store" - "github.com/apisix/manager-api/internal/handler" - "github.com/apisix/manager-api/internal/utils/consts" -) - -func TestUpstream_Get(t *testing.T) { - tests := []struct { - caseDesc string - giveInput *GetInput - giveRet *entity.Upstream - giveErr error - wantErr error - wantGetKey string - wantRet interface{} - }{ - { - caseDesc: "upstream: get success", - giveInput: &GetInput{ID: "u1"}, - wantGetKey: "u1", - giveRet: &entity.Upstream{ - BaseInfo: entity.BaseInfo{ - ID: "u1", - }, - UpstreamDef: entity.UpstreamDef{ - Name: "upstream1", - Timeout: &entity.Timeout{ - Connect: 15, - Send: 15, - Read: 15, - }, - Checks: map[string]interface{}{ - "active": map[string]interface{}{ - "timeout": float64(5), - "http_path": "/status", - "host": "foo.com", - "healthy": map[string]interface{}{ - "interval": 2, - "successes": 1, - }, - "unhealthy": map[string]interface{}{ - "interval": 1, - "http_failures": 2, - }, - "req_headers": []interface{}{"User-Agent: curl/7.29.0"}, - }, - "passive": map[string]interface{}{ - "healthy": map[string]interface{}{ - "http_statuses": []interface{}{float64(200), float64(201)}, - "successes": float64(3), - }, - "unhealthy": map[string]interface{}{ - "http_statuses": []interface{}{float64(500)}, - "http_failures": float64(3), - "tcp_failures": float64(3), - }, - }, - }, - Key: "server_addr", - Nodes: []map[string]interface{}{ - { - "host": "39.97.63.215", - "port": float64(80), - "weight": float64(1), - }, - }, - }, - }, - wantRet: &entity.Upstream{ - BaseInfo: entity.BaseInfo{ - ID: "u1", - }, - UpstreamDef: entity.UpstreamDef{ - Name: "upstream1", - Timeout: &entity.Timeout{ - Connect: 15, - Send: 15, - Read: 15, - }, - Checks: map[string]interface{}{ - "active": map[string]interface{}{ - "timeout": float64(5), - "http_path": "/status", - "host": "foo.com", - "healthy": map[string]interface{}{ - "interval": 2, - "successes": 1, - }, - "unhealthy": map[string]interface{}{ - "interval": 1, - "http_failures": 2, - }, - "req_headers": []interface{}{"User-Agent: curl/7.29.0"}, - }, - "passive": map[string]interface{}{ - "healthy": map[string]interface{}{ - "http_statuses": []interface{}{float64(200), float64(201)}, - "successes": float64(3), - }, - "unhealthy": map[string]interface{}{ - "http_statuses": []interface{}{float64(500)}, - "http_failures": float64(3), - "tcp_failures": float64(3), - }, - }, - }, - Key: "server_addr", - Nodes: []map[string]interface{}{ - { - "host": "39.97.63.215", - "port": float64(80), - "weight": float64(1), - }, - }, - }, - }, - }, - { - caseDesc: "store get failed", - giveInput: &GetInput{ID: "failed_key"}, - wantGetKey: "failed_key", - giveErr: fmt.Errorf("get failed"), - wantErr: fmt.Errorf("get failed"), - wantRet: &data.SpecCodeResponse{ - StatusCode: http.StatusInternalServerError, - }, - }, - } - - for _, tc := range tests { - t.Run(tc.caseDesc, func(t *testing.T) { - getCalled := true - upstreamStore := &store.MockInterface{} - upstreamStore.On("Get", mock.Anything, mock.Anything).Run(func(args mock.Arguments) { - getCalled = true - assert.Equal(t, tc.wantGetKey, args.Get(0)) - }).Return(tc.giveRet, tc.giveErr) - - h := Handler{upstreamStore: upstreamStore} - ctx := droplet.NewContext() - ctx.SetInput(tc.giveInput) - ret, err := h.Get(ctx) - assert.True(t, getCalled) - assert.Equal(t, tc.wantRet, ret) - assert.Equal(t, tc.wantErr, err) - }) - } -} - -func TestUpstreams_List(t *testing.T) { - mockData := []*entity.Upstream{ - { - BaseInfo: entity.BaseInfo{ - ID: "u1", - CreateTime: 1609340491, - UpdateTime: 1609340491, - }, - UpstreamDef: entity.UpstreamDef{ - Name: "upstream1", - Key: "server_addr", - Nodes: []map[string]interface{}{ - { - "host": "39.97.63.215", - "port": float64(80), - "weight": float64(1), - }, - }, - }, - }, - { - BaseInfo: entity.BaseInfo{ - ID: "u2", - CreateTime: 1609340491, - UpdateTime: 1609340491, - }, - UpstreamDef: entity.UpstreamDef{ - Name: "upstream2", - Key: "server_addr2", - - Nodes: entity.Node{ - Host: "39.97.63.215", - Port: 80, - Weight: 0, - }, - }, - }, - { - BaseInfo: entity.BaseInfo{ - ID: "u3", - CreateTime: 1609340491, - UpdateTime: 1609340491, - }, - UpstreamDef: entity.UpstreamDef{ - Name: "upstream3", - Key: "server_addr3", - Nodes: []entity.Node{ - { - Host: "39.97.63.215", - Port: 80, - Weight: 0, - }, - }, - }, - }, - } - - tests := []struct { - caseDesc string - giveInput *ListInput - giveData []*entity.Upstream - giveErr error - wantErr error - wantInput store.ListInput - wantRet interface{} - }{ - { - caseDesc: "list all upstream", - giveInput: &ListInput{ - Pagination: store.Pagination{ - PageSize: 10, - PageNumber: 10, - }, - }, - wantInput: store.ListInput{ - PageSize: 10, - PageNumber: 10, - }, - wantRet: &store.ListOutput{ - Rows: []interface{}{ - mockData[0], - mockData[1], - mockData[2], - }, - TotalSize: 3, - }, - }, - { - caseDesc: "list upstream with 'upstream1'", - giveInput: &ListInput{ - Name: "upstream1", - Pagination: store.Pagination{ - PageSize: 10, - PageNumber: 10, - }, - }, - wantInput: store.ListInput{ - PageSize: 10, - PageNumber: 10, - }, - wantRet: &store.ListOutput{ - Rows: []interface{}{ - mockData[0], - }, - TotalSize: 1, - }, - }, - } - - for _, tc := range tests { - t.Run(tc.caseDesc, func(t *testing.T) { - getCalled := true - upstreamStore := &store.MockInterface{} - upstreamStore.On("List", mock.Anything, mock.Anything).Run(func(args mock.Arguments) { - getCalled = true - input := args.Get(0).(store.ListInput) - assert.Equal(t, tc.wantInput.PageSize, input.PageSize) - assert.Equal(t, tc.wantInput.PageNumber, input.PageNumber) - }).Return(func(input store.ListInput) *store.ListOutput { - var returnData []interface{} - for _, c := range mockData { - if input.Predicate(c) { - if input.Format == nil { - returnData = append(returnData, c) - continue - } - returnData = append(returnData, input.Format(c)) - } - } - return &store.ListOutput{ - Rows: returnData, - TotalSize: len(returnData), - } - }, tc.giveErr) - - h := Handler{upstreamStore: upstreamStore} - ctx := droplet.NewContext() - ctx.SetInput(tc.giveInput) - ret, err := h.List(ctx) - assert.True(t, getCalled) - assert.Equal(t, tc.wantRet, ret) - assert.Equal(t, tc.wantErr, err) - }) - } -} - -func TestUpstream_Create(t *testing.T) { - tests := []struct { - caseDesc string - getCalled bool - giveInput *entity.Upstream - giveRet interface{} - giveErr error - wantInput *entity.Upstream - wantErr error - wantRet interface{} - nameExistRet []interface{} - }{ - { - caseDesc: "create success", - getCalled: true, - giveInput: &entity.Upstream{ - BaseInfo: entity.BaseInfo{ - ID: "u1", - }, - UpstreamDef: entity.UpstreamDef{ - Name: "upstream1", - Timeout: &entity.Timeout{ - Connect: 15, - Send: 15, - Read: 15, - }, - Checks: map[string]interface{}{ - "active": map[string]interface{}{ - "timeout": float64(5), - "http_path": "/status", - "host": "foo.com", - "healthy": map[string]interface{}{ - "interval": 2, - "successes": 1, - }, - "unhealthy": map[string]interface{}{ - "interval": 1, - "http_failures": 2, - }, - "req_headers": []interface{}{"User-Agent: curl/7.29.0"}, - }, - "passive": map[string]interface{}{ - "healthy": map[string]interface{}{ - "http_statuses": []interface{}{float64(200), float64(201)}, - "successes": float64(3), - }, - "unhealthy": map[string]interface{}{ - "http_statuses": []interface{}{float64(500)}, - "http_failures": float64(3), - "tcp_failures": float64(3), - }, - }, - }, - Key: "server_addr", - Nodes: []map[string]interface{}{ - { - "host": "39.97.63.215", - "port": float64(80), - "weight": float64(1), - }, - }, - }, - }, - giveRet: &entity.Upstream{ - BaseInfo: entity.BaseInfo{ - ID: "u1", - }, - UpstreamDef: entity.UpstreamDef{ - Name: "upstream1", - Timeout: &entity.Timeout{ - Connect: 15, - Send: 15, - Read: 15, - }, - Checks: map[string]interface{}{ - "active": map[string]interface{}{ - "timeout": float64(5), - "http_path": "/status", - "host": "foo.com", - "healthy": map[string]interface{}{ - "interval": 2, - "successes": 1, - }, - "unhealthy": map[string]interface{}{ - "interval": 1, - "http_failures": 2, - }, - "req_headers": []interface{}{"User-Agent: curl/7.29.0"}, - }, - "passive": map[string]interface{}{ - "healthy": map[string]interface{}{ - "http_statuses": []interface{}{float64(200), float64(201)}, - "successes": float64(3), - }, - "unhealthy": map[string]interface{}{ - "http_statuses": []interface{}{float64(500)}, - "http_failures": float64(3), - "tcp_failures": float64(3), - }, - }, - }, - Key: "server_addr", - Nodes: []map[string]interface{}{ - { - "host": "39.97.63.215", - "port": float64(80), - "weight": float64(1), - }, - }, - }, - }, - wantInput: &entity.Upstream{ - BaseInfo: entity.BaseInfo{ - ID: "u1", - }, - UpstreamDef: entity.UpstreamDef{ - Name: "upstream1", - Timeout: &entity.Timeout{ - Connect: 15, - Send: 15, - Read: 15, - }, - Checks: map[string]interface{}{ - "active": map[string]interface{}{ - "timeout": float64(5), - "http_path": "/status", - "host": "foo.com", - "healthy": map[string]interface{}{ - "interval": 2, - "successes": 1, - }, - "unhealthy": map[string]interface{}{ - "interval": 1, - "http_failures": 2, - }, - "req_headers": []interface{}{"User-Agent: curl/7.29.0"}, - }, - "passive": map[string]interface{}{ - "healthy": map[string]interface{}{ - "http_statuses": []interface{}{float64(200), float64(201)}, - "successes": float64(3), - }, - "unhealthy": map[string]interface{}{ - "http_statuses": []interface{}{float64(500)}, - "http_failures": float64(3), - "tcp_failures": float64(3), - }, - }, - }, - Key: "server_addr", - Nodes: []map[string]interface{}{ - { - "host": "39.97.63.215", - "port": float64(80), - "weight": float64(1), - }, - }, - }, - }, - wantRet: &entity.Upstream{ - BaseInfo: entity.BaseInfo{ - ID: "u1", - }, - UpstreamDef: entity.UpstreamDef{ - Name: "upstream1", - Timeout: &entity.Timeout{ - Connect: 15, - Send: 15, - Read: 15, - }, - Checks: map[string]interface{}{ - "active": map[string]interface{}{ - "timeout": float64(5), - "http_path": "/status", - "host": "foo.com", - "healthy": map[string]interface{}{ - "interval": 2, - "successes": 1, - }, - "unhealthy": map[string]interface{}{ - "interval": 1, - "http_failures": 2, - }, - "req_headers": []interface{}{"User-Agent: curl/7.29.0"}, - }, - "passive": map[string]interface{}{ - "healthy": map[string]interface{}{ - "http_statuses": []interface{}{float64(200), float64(201)}, - "successes": float64(3), - }, - "unhealthy": map[string]interface{}{ - "http_statuses": []interface{}{float64(500)}, - "http_failures": float64(3), - "tcp_failures": float64(3), - }, - }, - }, - Key: "server_addr", - Nodes: []map[string]interface{}{ - { - "host": "39.97.63.215", - "port": float64(80), - "weight": float64(1), - }, - }, - }, - }, - wantErr: nil, - }, - { - caseDesc: "when nodes address without port and pass host is node, create should succeed", - getCalled: true, - giveInput: &entity.Upstream{ - BaseInfo: entity.BaseInfo{ - ID: "u1", - }, - UpstreamDef: entity.UpstreamDef{ - Name: "upstream1", - Timeout: &entity.Timeout{ - Connect: 15, - Send: 15, - Read: 15, - }, - Key: "server_addr", - Nodes: map[string]float64{"127.0.0.1": 100}, - PassHost: "node", - }, - }, - giveRet: &entity.Upstream{ - BaseInfo: entity.BaseInfo{ - ID: "u1", - }, - UpstreamDef: entity.UpstreamDef{ - Name: "upstream1", - Timeout: &entity.Timeout{ - Connect: 15, - Send: 15, - Read: 15, - }, - Key: "server_addr", - Nodes: map[string]float64{"127.0.0.1": 100}, - PassHost: "node", - }, - }, - wantInput: &entity.Upstream{ - BaseInfo: entity.BaseInfo{ - ID: "u1", - }, - UpstreamDef: entity.UpstreamDef{ - Name: "upstream1", - Timeout: &entity.Timeout{ - Connect: 15, - Send: 15, - Read: 15, - }, - Key: "server_addr", - Nodes: map[string]float64{"127.0.0.1": 100}, - PassHost: "node", - }, - }, - wantRet: &entity.Upstream{ - BaseInfo: entity.BaseInfo{ - ID: "u1", - }, - UpstreamDef: entity.UpstreamDef{ - Name: "upstream1", - Timeout: &entity.Timeout{ - Connect: 15, - Send: 15, - Read: 15, - }, - Key: "server_addr", - Nodes: map[string]float64{"127.0.0.1": 100}, - PassHost: "node", - }, - }, - wantErr: nil, - }, - { - caseDesc: "create failed, create return error", - getCalled: true, - giveInput: &entity.Upstream{ - BaseInfo: entity.BaseInfo{ - ID: "u1", - }, - UpstreamDef: entity.UpstreamDef{ - Name: "upstream1", - Timeout: &entity.Timeout{ - Connect: 15, - Send: 15, - Read: 15, - }, - Checks: map[string]interface{}{ - "active": map[string]interface{}{ - "timeout": float64(5), - "http_path": "/status", - "host": "foo.com", - "healthy": map[string]interface{}{ - "interval": 2, - "successes": 1, - }, - "unhealthy": map[string]interface{}{ - "interval": 1, - "http_failures": 2, - }, - "req_headers": []interface{}{"User-Agent: curl/7.29.0"}, - }, - "passive": map[string]interface{}{ - "healthy": map[string]interface{}{ - "http_statuses": []interface{}{float64(200), float64(201)}, - "successes": float64(3), - }, - "unhealthy": map[string]interface{}{ - "http_statuses": []interface{}{float64(500)}, - "http_failures": float64(3), - "tcp_failures": float64(3), - }, - }, - }, - Key: "server_addr", - Nodes: []map[string]interface{}{ - { - "host": "39.97.63.215", - "port": float64(80), - "weight": float64(1), - }, - }, - }, - }, - giveErr: fmt.Errorf("create failed"), - wantInput: &entity.Upstream{ - BaseInfo: entity.BaseInfo{ - ID: "u1", - }, - UpstreamDef: entity.UpstreamDef{ - Name: "upstream1", - Timeout: &entity.Timeout{ - Connect: 15, - Send: 15, - Read: 15, - }, - Checks: map[string]interface{}{ - "active": map[string]interface{}{ - "timeout": float64(5), - "http_path": "/status", - "host": "foo.com", - "healthy": map[string]interface{}{ - "interval": 2, - "successes": 1, - }, - "unhealthy": map[string]interface{}{ - "interval": 1, - "http_failures": 2, - }, - "req_headers": []interface{}{"User-Agent: curl/7.29.0"}, - }, - "passive": map[string]interface{}{ - "healthy": map[string]interface{}{ - "http_statuses": []interface{}{float64(200), float64(201)}, - "successes": float64(3), - }, - "unhealthy": map[string]interface{}{ - "http_statuses": []interface{}{float64(500)}, - "http_failures": float64(3), - "tcp_failures": float64(3), - }, - }, - }, - Key: "server_addr", - Nodes: []map[string]interface{}{ - { - "host": "39.97.63.215", - "port": float64(80), - "weight": float64(1), - }, - }, - }, - }, - wantErr: fmt.Errorf("create failed"), - wantRet: handler.SpecCodeResponse(fmt.Errorf("create failed")), - }, - } - - for _, tc := range tests { - t.Run(tc.caseDesc, func(t *testing.T) { - getCalled := false - - upstreamStore := &store.MockInterface{} - upstreamStore.On("Create", mock.Anything, mock.Anything).Run(func(args mock.Arguments) { - getCalled = true - input := args.Get(1).(*entity.Upstream) - assert.Equal(t, tc.wantInput, input) - }).Return(tc.giveRet, tc.giveErr) - - upstreamStore.On("List", mock.Anything, mock.Anything).Run(func(args mock.Arguments) { - }).Return(func(input store.ListInput) *store.ListOutput { - return &store.ListOutput{ - Rows: tc.nameExistRet, - TotalSize: len(tc.nameExistRet), - } - }, nil) - - h := Handler{upstreamStore: upstreamStore} - - ctx := droplet.NewContext() - ctx.SetInput(tc.giveInput) - ret, err := h.Create(ctx) - assert.True(t, getCalled) - assert.Equal(t, tc.wantRet, ret) - assert.Equal(t, tc.wantErr, err) - }) - } -} - -func TestUpstream_Update(t *testing.T) { - tests := []struct { - caseDesc string - getCalled bool - giveInput *UpdateInput - giveErr error - giveRet interface{} - wantInput *entity.Upstream - wantErr error - wantRet interface{} - nameExistRet []interface{} - }{ - { - caseDesc: "update success", - getCalled: true, - giveInput: &UpdateInput{ - ID: "u1", - Upstream: entity.Upstream{ - UpstreamDef: entity.UpstreamDef{ - Name: "upstream1", - Timeout: &entity.Timeout{ - Connect: 15, - Send: 15, - Read: 15, - }, - Checks: map[string]interface{}{ - "active": map[string]interface{}{ - "timeout": float64(5), - "http_path": "/status", - "host": "foo.com", - "healthy": map[string]interface{}{ - "interval": 2, - "successes": 1, - }, - "unhealthy": map[string]interface{}{ - "interval": 1, - "http_failures": 2, - }, - "req_headers": []interface{}{"User-Agent: curl/7.29.0"}, - }, - "passive": map[string]interface{}{ - "healthy": map[string]interface{}{ - "http_statuses": []interface{}{float64(200), float64(201)}, - "successes": float64(3), - }, - "unhealthy": map[string]interface{}{ - "http_statuses": []interface{}{float64(500)}, - "http_failures": 3, - "tcp_failures": 3, - }, - }, - }, - Key: "server_addr", - Nodes: []map[string]interface{}{ - { - "host": "39.97.63.215", - "port": float64(80), - "weight": float64(1), - }, - }, - }, - }, - }, - giveRet: &entity.Upstream{ - BaseInfo: entity.BaseInfo{ - ID: "u1", - }, - UpstreamDef: entity.UpstreamDef{ - Name: "upstream1", - Timeout: &entity.Timeout{ - Connect: 15, - Send: 15, - Read: 15, - }, - Checks: map[string]interface{}{ - "active": map[string]interface{}{ - "timeout": float64(5), - "http_path": "/status", - "host": "foo.com", - "healthy": map[string]interface{}{ - "interval": 2, - "successes": 1, - }, - "unhealthy": map[string]interface{}{ - "interval": 1, - "http_failures": 2, - }, - "req_headers": []interface{}{"User-Agent: curl/7.29.0"}, - }, - "passive": map[string]interface{}{ - "healthy": map[string]interface{}{ - "http_statuses": []interface{}{float64(200), float64(201)}, - "successes": float64(3), - }, - "unhealthy": map[string]interface{}{ - "http_statuses": []interface{}{float64(500)}, - "http_failures": 3, - "tcp_failures": 3, - }, - }, - }, - Key: "server_addr", - Nodes: []map[string]interface{}{ - { - "host": "39.97.63.215", - "port": float64(80), - "weight": float64(1), - }, - }, - }, - }, - wantInput: &entity.Upstream{ - BaseInfo: entity.BaseInfo{ - ID: "u1", - }, - UpstreamDef: entity.UpstreamDef{ - Name: "upstream1", - Timeout: &entity.Timeout{ - Connect: 15, - Send: 15, - Read: 15, - }, - Checks: map[string]interface{}{ - "active": map[string]interface{}{ - "timeout": float64(5), - "http_path": "/status", - "host": "foo.com", - "healthy": map[string]interface{}{ - "interval": 2, - "successes": 1, - }, - "unhealthy": map[string]interface{}{ - "interval": 1, - "http_failures": 2, - }, - "req_headers": []interface{}{"User-Agent: curl/7.29.0"}, - }, - "passive": map[string]interface{}{ - "healthy": map[string]interface{}{ - "http_statuses": []interface{}{float64(200), float64(201)}, - "successes": float64(3), - }, - "unhealthy": map[string]interface{}{ - "http_statuses": []interface{}{float64(500)}, - "http_failures": 3, - "tcp_failures": 3, - }, - }, - }, - Key: "server_addr", - Nodes: []map[string]interface{}{ - { - "host": "39.97.63.215", - "port": float64(80), - "weight": float64(1), - }, - }, - }, - }, - wantRet: &entity.Upstream{ - BaseInfo: entity.BaseInfo{ - ID: "u1", - }, - UpstreamDef: entity.UpstreamDef{ - Name: "upstream1", - Timeout: &entity.Timeout{ - Connect: 15, - Send: 15, - Read: 15, - }, - Checks: map[string]interface{}{ - "active": map[string]interface{}{ - "timeout": float64(5), - "http_path": "/status", - "host": "foo.com", - "healthy": map[string]interface{}{ - "interval": 2, - "successes": 1, - }, - "unhealthy": map[string]interface{}{ - "interval": 1, - "http_failures": 2, - }, - "req_headers": []interface{}{"User-Agent: curl/7.29.0"}, - }, - "passive": map[string]interface{}{ - "healthy": map[string]interface{}{ - "http_statuses": []interface{}{float64(200), float64(201)}, - "successes": float64(3), - }, - "unhealthy": map[string]interface{}{ - "http_statuses": []interface{}{float64(500)}, - "http_failures": 3, - "tcp_failures": 3, - }, - }, - }, - Key: "server_addr", - Nodes: []map[string]interface{}{ - { - "host": "39.97.63.215", - "port": float64(80), - "weight": float64(1), - }, - }, - }, - }, - }, - { - caseDesc: "update failed, different id", - giveInput: &UpdateInput{ - ID: "u1", - Upstream: entity.Upstream{ - BaseInfo: entity.BaseInfo{ - ID: "u2", - }, - UpstreamDef: entity.UpstreamDef{ - Name: "upstream1", - Timeout: &entity.Timeout{ - Connect: 15, - Send: 15, - Read: 15, - }, - Checks: map[string]interface{}{ - "active": map[string]interface{}{ - "timeout": float64(5), - "http_path": "/status", - "host": "foo.com", - "healthy": map[string]interface{}{ - "interval": 2, - "successes": 1, - }, - "unhealthy": map[string]interface{}{ - "interval": 1, - "http_failures": 2, - }, - "req_headers": []interface{}{"User-Agent: curl/7.29.0"}, - }, - "passive": map[string]interface{}{ - "healthy": map[string]interface{}{ - "http_statuses": []interface{}{float64(200), float64(201)}, - "successes": float64(3), - }, - "unhealthy": map[string]interface{}{ - "http_statuses": []interface{}{float64(500)}, - "http_failures": 3, - "tcp_failures": 3, - }, - }, - }, - Key: "server_addr", - Nodes: []map[string]interface{}{ - { - "host": "39.97.63.215", - "port": float64(80), - "weight": float64(1), - }, - }, - }, - }, - }, - wantRet: &data.SpecCodeResponse{StatusCode: http.StatusBadRequest}, - wantErr: fmt.Errorf("ID on path (u1) doesn't match ID on body (u2)"), - }, - } - - for _, tc := range tests { - t.Run(tc.caseDesc, func(t *testing.T) { - getCalled := false - upstreamStore := &store.MockInterface{} - upstreamStore.On("Update", mock.Anything, mock.Anything, mock.Anything).Run(func(args mock.Arguments) { - getCalled = true - input := args.Get(1).(*entity.Upstream) - createIfNotExist := args.Get(2).(bool) - assert.Equal(t, tc.wantInput, input) - assert.True(t, createIfNotExist) - }).Return(tc.giveRet, tc.giveErr) - - upstreamStore.On("List", mock.Anything, mock.Anything).Run(func(args mock.Arguments) { - }).Return(func(input store.ListInput) *store.ListOutput { - return &store.ListOutput{ - Rows: tc.nameExistRet, - TotalSize: len(tc.nameExistRet), - } - }, nil) - - h := Handler{upstreamStore: upstreamStore} - ctx := droplet.NewContext() - ctx.SetInput(tc.giveInput) - ret, err := h.Update(ctx) - assert.Equal(t, tc.getCalled, getCalled) - assert.Equal(t, tc.wantRet, ret) - assert.Equal(t, tc.wantErr, err) - }) - } -} - -func TestUpstream_Patch(t *testing.T) { - existUpstream := &entity.Upstream{ - BaseInfo: entity.BaseInfo{ - ID: "u1", - }, - UpstreamDef: entity.UpstreamDef{ - Name: "upstream1", - Timeout: &entity.Timeout{ - Connect: 15, - Send: 15, - Read: 15, - }, - Checks: map[string]interface{}{ - "active": map[string]interface{}{ - "timeout": float64(5), - "http_path": "/status", - "host": "foo.com", - "healthy": map[string]interface{}{ - "interval": float64(2), - "successes": float64(1), - }, - "unhealthy": map[string]interface{}{ - "interval": float64(1), - "http_failures": float64(2), - }, - "req_headers": []interface{}{"User-Agent: curl/7.29.0"}, - }, - "passive": map[string]interface{}{ - "healthy": map[string]interface{}{ - "http_statuses": []interface{}{float64(200), float64(201)}, - "successes": float64(3), - }, - "unhealthy": map[string]interface{}{ - "http_statuses": []interface{}{float64(500)}, - "http_failures": 3, - "tcp_failures": 3, - }, - }, - }, - Key: "server_addr", - Nodes: []interface{}{ - map[string]interface{}{ - "host": "39.97.63.215", - "port": float64(80), - "weight": float64(1), - }, - }, - }, - } - - patchUpstream := &entity.Upstream{ - BaseInfo: entity.BaseInfo{ - ID: "u1", - }, - UpstreamDef: entity.UpstreamDef{ - Name: "upstream2", - Timeout: &entity.Timeout{ - Connect: 20, - Send: 20, - Read: 20, - }, - Checks: map[string]interface{}{ - "active": map[string]interface{}{ - "timeout": float64(5), - "http_path": "/status", - "host": "foo.com", - "healthy": map[string]interface{}{ - "interval": float64(2), - "successes": float64(1), - }, - "unhealthy": map[string]interface{}{ - "interval": float64(1), - "http_failures": float64(2), - }, - "req_headers": []interface{}{"User-Agent: curl/7.29.0"}, - }, - "passive": map[string]interface{}{ - "healthy": map[string]interface{}{ - "http_statuses": []interface{}{float64(200), float64(201)}, - "successes": float64(3), - }, - "unhealthy": map[string]interface{}{ - "http_statuses": []interface{}{float64(500)}, - "http_failures": 3, - "tcp_failures": 3, - }, - }, - }, - Key: "server_addr2", - Nodes: []interface{}{ - map[string]interface{}{ - "host": "39.97.63.215", - "port": float64(80), - "weight": float64(1), - }, - }, - }, - } - patchUpstreamBytes, err := json.Marshal(patchUpstream) - assert.Nil(t, err) - - tests := []struct { - caseDesc string - getCalled bool - giveInput *PatchInput - giveErr error - giveRet interface{} - wantInput *entity.Upstream - wantErr error - wantRet interface{} - }{ - { - caseDesc: "patch success", - giveRet: &entity.Upstream{ - BaseInfo: entity.BaseInfo{ - ID: "u1", - }, - UpstreamDef: entity.UpstreamDef{ - Name: "upstream2", - Timeout: &entity.Timeout{ - Connect: 20, - Send: 20, - Read: 20, - }, - Checks: map[string]interface{}{ - "active": map[string]interface{}{ - "timeout": float64(5), - "http_path": "/status", - "host": "foo.com", - "healthy": map[string]interface{}{ - "interval": float64(2), - "successes": float64(1), - }, - "unhealthy": map[string]interface{}{ - "interval": float64(1), - "http_failures": float64(2), - }, - "req_headers": []interface{}{"User-Agent: curl/7.29.0"}, - }, - "passive": map[string]interface{}{ - "healthy": map[string]interface{}{ - "http_statuses": []interface{}{float64(200), float64(201)}, - "successes": float64(3), - }, - "unhealthy": map[string]interface{}{ - "http_statuses": []interface{}{float64(500)}, - "http_failures": float64(3), - "tcp_failures": float64(3), - }, - }, - }, - Key: "server_addr2", - Nodes: []interface{}{ - map[string]interface{}{ - "host": "39.97.63.215", - "port": float64(80), - "weight": float64(1), - }, - }, - }, - }, - giveInput: &PatchInput{ - ID: "u1", - SubPath: "", - Body: patchUpstreamBytes, - }, - wantInput: &entity.Upstream{ - BaseInfo: entity.BaseInfo{ - ID: "u1", - }, - UpstreamDef: entity.UpstreamDef{ - Name: "upstream2", - Timeout: &entity.Timeout{ - Connect: 20, - Send: 20, - Read: 20, - }, - Checks: map[string]interface{}{ - "active": map[string]interface{}{ - "timeout": float64(5), - "http_path": "/status", - "host": "foo.com", - "healthy": map[string]interface{}{ - "interval": float64(2), - "successes": float64(1), - }, - "unhealthy": map[string]interface{}{ - "interval": float64(1), - "http_failures": float64(2), - }, - "req_headers": []interface{}{"User-Agent: curl/7.29.0"}, - }, - "passive": map[string]interface{}{ - "healthy": map[string]interface{}{ - "http_statuses": []interface{}{float64(200), float64(201)}, - "successes": float64(3), - }, - "unhealthy": map[string]interface{}{ - "http_statuses": []interface{}{float64(500)}, - "http_failures": float64(3), - "tcp_failures": float64(3), - }, - }, - }, - Key: "server_addr2", - Nodes: []interface{}{ - map[string]interface{}{ - "host": "39.97.63.215", - "port": float64(80), - "weight": float64(1), - }, - }, - }, - }, - wantRet: &entity.Upstream{ - BaseInfo: entity.BaseInfo{ - ID: "u1", - }, - UpstreamDef: entity.UpstreamDef{ - Name: "upstream2", - Timeout: &entity.Timeout{ - Connect: 20, - Send: 20, - Read: 20, - }, - Checks: map[string]interface{}{ - "active": map[string]interface{}{ - "timeout": float64(5), - "http_path": "/status", - "host": "foo.com", - "healthy": map[string]interface{}{ - "interval": float64(2), - "successes": float64(1), - }, - "unhealthy": map[string]interface{}{ - "interval": float64(1), - "http_failures": float64(2), - }, - "req_headers": []interface{}{"User-Agent: curl/7.29.0"}, - }, - "passive": map[string]interface{}{ - "healthy": map[string]interface{}{ - "http_statuses": []interface{}{float64(200), float64(201)}, - "successes": float64(3), - }, - "unhealthy": map[string]interface{}{ - "http_statuses": []interface{}{float64(500)}, - "http_failures": float64(3), - "tcp_failures": float64(3), - }, - }, - }, - Key: "server_addr2", - Nodes: []interface{}{ - map[string]interface{}{ - "host": "39.97.63.215", - "port": float64(80), - "weight": float64(1), - }, - }, - }, - }, - getCalled: true, - }, - { - caseDesc: "patch success by path", - giveInput: &PatchInput{ - ID: "u1", - SubPath: "/nodes", - Body: []byte(`[{"host": "172.16.238.20","port": 1981,"weight": 1}]`), - }, - giveRet: &entity.Upstream{ - BaseInfo: entity.BaseInfo{ - ID: "u1", - }, - UpstreamDef: entity.UpstreamDef{ - Name: "upstream1", - Timeout: &entity.Timeout{ - Connect: 20, - Send: 20, - Read: 20, - }, - Checks: map[string]interface{}{ - "active": map[string]interface{}{ - "timeout": float64(5), - "http_path": "/status", - "host": "foo.com", - "healthy": map[string]interface{}{ - "interval": 2, - "successes": 1, - }, - "unhealthy": map[string]interface{}{ - "interval": 1, - "http_failures": 2, - }, - "req_headers": []interface{}{"User-Agent: curl/7.29.0"}, - }, - "passive": map[string]interface{}{ - "healthy": map[string]interface{}{ - "http_statuses": []interface{}{float64(200), float64(201)}, - "successes": float64(3), - }, - "unhealthy": map[string]interface{}{ - "http_statuses": []interface{}{float64(500)}, - "http_failures": float64(3), - "tcp_failures": float64(3), - }, - }, - }, - Key: "server_addr_patch", - Nodes: []interface{}{ - map[string]interface{}{ - "host": "172.16.238.20", - "port": float64(1981), - "weight": float64(1), - }, - }, - }, - }, - wantInput: &entity.Upstream{ - BaseInfo: entity.BaseInfo{ - ID: "u1", - }, - UpstreamDef: entity.UpstreamDef{ - Name: "upstream1", - Timeout: &entity.Timeout{ - Connect: 15, - Send: 15, - Read: 15, - }, - Checks: map[string]interface{}{ - "active": map[string]interface{}{ - "timeout": float64(5), - "http_path": "/status", - "host": "foo.com", - "healthy": map[string]interface{}{ - "interval": float64(2), - "successes": float64(1), - }, - "unhealthy": map[string]interface{}{ - "interval": float64(1), - "http_failures": float64(2), - }, - "req_headers": []interface{}{"User-Agent: curl/7.29.0"}, - }, - "passive": map[string]interface{}{ - "healthy": map[string]interface{}{ - "http_statuses": []interface{}{float64(200), float64(201)}, - "successes": float64(3), - }, - "unhealthy": map[string]interface{}{ - "http_statuses": []interface{}{float64(500)}, - "http_failures": float64(3), - "tcp_failures": float64(3), - }, - }, - }, - Key: "server_addr", - Nodes: []interface{}{ - map[string]interface{}{ - "host": "172.16.238.20", - "port": float64(1981), - "weight": float64(1), - }, - }, - }, - }, - wantRet: &entity.Upstream{ - BaseInfo: entity.BaseInfo{ - ID: "u1", - }, - UpstreamDef: entity.UpstreamDef{ - Name: "upstream1", - Timeout: &entity.Timeout{ - Connect: 20, - Send: 20, - Read: 20, - }, - Checks: map[string]interface{}{ - "active": map[string]interface{}{ - "timeout": float64(5), - "http_path": "/status", - "host": "foo.com", - "healthy": map[string]interface{}{ - "interval": 2, - "successes": 1, - }, - "unhealthy": map[string]interface{}{ - "interval": 1, - "http_failures": 2, - }, - "req_headers": []interface{}{"User-Agent: curl/7.29.0"}, - }, - "passive": map[string]interface{}{ - "healthy": map[string]interface{}{ - "http_statuses": []interface{}{float64(200), float64(201)}, - "successes": float64(3), - }, - "unhealthy": map[string]interface{}{ - "http_statuses": []interface{}{float64(500)}, - "http_failures": float64(3), - "tcp_failures": float64(3), - }, - }, - }, - Key: "server_addr_patch", - Nodes: []interface{}{ - map[string]interface{}{ - "host": "172.16.238.20", - "port": float64(1981), - "weight": float64(1), - }, - }, - }, - }, - getCalled: true, - }, - { - caseDesc: "patch failed, path error", - giveInput: &PatchInput{ - ID: "u1", - SubPath: "error", - Body: []byte("0"), - }, - wantRet: handler.SpecCodeResponse( - errors.New("add operation does not apply: doc is missing path: \"error\": missing value")), - wantErr: errors.New("add operation does not apply: doc is missing path: \"error\": missing value"), - }, - } - for _, tc := range tests { - t.Run(tc.caseDesc, func(t *testing.T) { - getCalled := false - - upstreamStore := &store.MockInterface{} - upstreamStore.On("Get", mock.Anything, mock.Anything).Return(existUpstream, nil) - upstreamStore.On("Update", mock.Anything, mock.Anything, mock.Anything).Run(func(args mock.Arguments) { - getCalled = true - input := args.Get(1).(*entity.Upstream) - createIfNotExist := args.Get(2).(bool) - assert.Equal(t, tc.wantInput, input) - assert.False(t, createIfNotExist) - }).Return(tc.giveRet, tc.giveErr) - - h := Handler{upstreamStore: upstreamStore} - ctx := droplet.NewContext() - ctx.SetInput(tc.giveInput) - ret, err := h.Patch(ctx) - assert.Equal(t, tc.getCalled, getCalled) - assert.Equal(t, tc.wantRet, ret) - if tc.wantErr != nil && err != nil { - assert.Error(t, tc.wantErr.(error), err.Error()) - } else { - assert.Equal(t, tc.wantErr, err) - } - }) - } -} - -func TestUpstreams_Delete(t *testing.T) { - tests := []struct { - caseDesc string - giveInput *BatchDelete - giveErr error - wantInput []string - wantErr error - wantRet interface{} - routeMockData []*entity.Route - routeMockErr error - serviceMockData []*entity.Service - serviceMockErr error - streamRouteMockData []*entity.Service - streamRouteMockErr error - getCalled bool - }{ - { - caseDesc: "delete success", - giveInput: &BatchDelete{ - IDs: "u1", - }, - wantInput: []string{"u1"}, - getCalled: true, - }, - { - caseDesc: "batch delete success", - giveInput: &BatchDelete{ - IDs: "u1,u2", - }, - wantInput: []string{"u1", "u2"}, - getCalled: true, - }, - { - caseDesc: "delete failed", - giveInput: &BatchDelete{ - IDs: "u1", - }, - giveErr: fmt.Errorf("delete error"), - wantInput: []string{"u1"}, - wantRet: handler.SpecCodeResponse(fmt.Errorf("delete error")), - wantErr: fmt.Errorf("delete error"), - getCalled: true, - }, - { - caseDesc: "delete failed, route is using", - giveInput: &BatchDelete{ - IDs: "u1", - }, - wantInput: []string{"s1"}, - routeMockData: []*entity.Route{ - &entity.Route{ - BaseInfo: entity.BaseInfo{ - ID: "r1", - CreateTime: 1609746531, - }, - Name: "route1", - Desc: "test_route", - UpstreamID: "u1", - ServiceID: "s1", - Labels: map[string]string{ - "version": "v1", - }, - }, - }, - routeMockErr: nil, - getCalled: false, - wantRet: &data.SpecCodeResponse{StatusCode: 400}, - wantErr: errors.New("route: route1 is using this upstream"), - }, - { - caseDesc: "delete failed, route list error", - giveInput: &BatchDelete{ - IDs: "u1", - }, - wantInput: []string{"u1"}, - routeMockData: nil, - routeMockErr: errors.New("route list error"), - wantRet: handler.SpecCodeResponse(errors.New("route list error")), - wantErr: errors.New("route list error"), - getCalled: false, - }, - { - caseDesc: "delete failed, service is using", - giveInput: &BatchDelete{ - IDs: "u1", - }, - wantInput: []string{"s1"}, - serviceMockData: []*entity.Service{ - &entity.Service{ - BaseInfo: entity.BaseInfo{ - ID: "s1", - CreateTime: 1609746531, - }, - Name: "service1", - UpstreamID: "u1", - }, - }, - serviceMockErr: nil, - getCalled: false, - wantRet: &data.SpecCodeResponse{StatusCode: 400}, - wantErr: errors.New("service: service1 is using this upstream"), - }, - { - caseDesc: "delete failed, service list error", - giveInput: &BatchDelete{ - IDs: "u1", - }, - wantInput: []string{"u1"}, - serviceMockErr: errors.New("service list error"), - wantRet: handler.SpecCodeResponse(errors.New("service list error")), - wantErr: errors.New("service list error"), - getCalled: false, - }, - } - - for _, tc := range tests { - t.Run(tc.caseDesc, func(t *testing.T) { - getCalled := false - upstreamStore := &store.MockInterface{} - upstreamStore.On("BatchDelete", mock.Anything, mock.Anything).Run(func(args mock.Arguments) { - getCalled = true - input := args.Get(1).([]string) - assert.Equal(t, tc.wantInput, input) - }).Return(tc.giveErr) - - routeStore := &store.MockInterface{} - routeStore.On("List", mock.Anything).Return(func(input store.ListInput) *store.ListOutput { - var returnData []interface{} - for _, c := range tc.routeMockData { - if input.Predicate(c) { - if input.Format == nil { - returnData = append(returnData, c) - continue - } - - returnData = append(returnData, input.Format(c)) - } - } - - return &store.ListOutput{ - Rows: returnData, - TotalSize: len(returnData), - } - }, tc.routeMockErr) - - serviceStore := &store.MockInterface{} - serviceStore.On("List", mock.Anything).Return(func(input store.ListInput) *store.ListOutput { - var returnData []interface{} - for _, c := range tc.serviceMockData { - if input.Predicate(c) { - if input.Format == nil { - returnData = append(returnData, c) - continue - } - - returnData = append(returnData, input.Format(c)) - } - } - - return &store.ListOutput{ - Rows: returnData, - TotalSize: len(returnData), - } - }, tc.serviceMockErr) - - streamRouteStore := &store.MockInterface{} - streamRouteStore.On("List", mock.Anything).Return(func(input store.ListInput) *store.ListOutput { - var returnData []interface{} - for _, c := range tc.streamRouteMockData { - if input.Predicate(c) { - if input.Format == nil { - returnData = append(returnData, c) - continue - } - - returnData = append(returnData, input.Format(c)) - } - } - - return &store.ListOutput{ - Rows: returnData, - TotalSize: len(returnData), - } - }, tc.streamRouteMockErr) - - h := Handler{upstreamStore: upstreamStore, routeStore: routeStore, serviceStore: serviceStore, streamRouteStore: streamRouteStore} - ctx := droplet.NewContext() - ctx.SetInput(tc.giveInput) - ret, err := h.BatchDelete(ctx) - assert.Equal(t, tc.getCalled, getCalled) - assert.Equal(t, tc.wantRet, ret) - assert.Equal(t, tc.wantErr, err) - }) - } -} - -func TestUpstream_Exist(t *testing.T) { - mockData := []*entity.Upstream{ - { - BaseInfo: entity.BaseInfo{ID: "001", CreateTime: 1609742634}, - UpstreamDef: entity.UpstreamDef{ - Name: "upstream1", - Key: "server_addr", - Nodes: []interface{}{ - map[string]interface{}{ - "host": "39.97.63.215", - "port": float64(80), - "weight": float64(1), - }, - }, - }, - }, - { - BaseInfo: entity.BaseInfo{ID: "002", CreateTime: 1609742635}, - UpstreamDef: entity.UpstreamDef{ - Name: "upstream2", - Key: "server_addr2", - Nodes: []interface{}{ - map[string]interface{}{ - "host": "39.97.63.216", - "port": float64(80), - "weight": float64(1), - }, - }, - }, - }, - { - BaseInfo: entity.BaseInfo{ID: "003", CreateTime: 1609742636}, - UpstreamDef: entity.UpstreamDef{ - Name: "upstream3", - Key: "server_addr3", - Nodes: []interface{}{ - map[string]interface{}{ - "host": "39.97.63.217", - "port": float64(80), - "weight": float64(1), - }, - }, - }, - }, - } - - tests := []struct { - caseDesc string - giveInput *ExistCheckInput - giveErr error - getCalled bool - wantInput []string - wantErr error - wantRet interface{} - }{ - { - caseDesc: "check upstream exist, excluded", - giveInput: &ExistCheckInput{ - Name: "upstream1", - Exclude: "001", - }, - wantRet: nil, - getCalled: true, - }, - { - caseDesc: "check upstream exist, not excluded", - giveInput: &ExistCheckInput{ - Name: "upstream1", - Exclude: "002", - }, - wantRet: &data.SpecCodeResponse{StatusCode: http.StatusBadRequest}, - wantErr: consts.InvalidParam("Upstream name is reduplicate"), - getCalled: true, - }, - { - caseDesc: "check upstream exist, not existed", - giveInput: &ExistCheckInput{ - Name: "upstream_test", - Exclude: "001", - }, - wantRet: nil, - wantErr: nil, - getCalled: true, - }, - } - - for _, tc := range tests { - t.Run(tc.caseDesc, func(t *testing.T) { - getCalled := false - upstreamStore := &store.MockInterface{} - upstreamStore.On("List", mock.Anything, mock.Anything).Run(func(args mock.Arguments) { - getCalled = true - }).Return(func(input store.ListInput) *store.ListOutput { - var res []interface{} - for _, c := range mockData { - if input.Predicate(c) { - if input.Format != nil { - res = append(res, input.Format(c)) - } else { - res = append(res, c) - } - } - } - return &store.ListOutput{ - Rows: res, - TotalSize: len(res), - } - }, nil) - - h := Handler{upstreamStore: upstreamStore} - ctx := droplet.NewContext() - ctx.SetInput(tc.giveInput) - ret, err := h.Exist(ctx) - assert.True(t, getCalled) - assert.Equal(t, tc.wantRet, ret) - assert.Equal(t, tc.wantErr, err) - }) - } -} - -func TestUpstream_ListUpstreamNames(t *testing.T) { - mockData := []*entity.Upstream{ - { - BaseInfo: entity.BaseInfo{ID: "001", CreateTime: 1609742634}, - UpstreamDef: entity.UpstreamDef{ - Name: "upstream1", - Key: "server_addr", - Nodes: []interface{}{ - map[string]interface{}{ - "host": "39.97.63.215", - "port": float64(80), - "weight": float64(1), - }, - }, - }, - }, - { - BaseInfo: entity.BaseInfo{ID: "002", CreateTime: 1609742635}, - UpstreamDef: entity.UpstreamDef{ - Name: "upstream2", - Key: "server_addr2", - Nodes: []interface{}{ - map[string]interface{}{ - "host": "39.97.63.216", - "port": float64(80), - "weight": float64(1), - }, - }, - }, - }, - { - BaseInfo: entity.BaseInfo{ID: "003", CreateTime: 1609742636}, - UpstreamDef: entity.UpstreamDef{ - Name: "upstream3", - Key: "server_addr3", - Nodes: []interface{}{ - map[string]interface{}{ - "host": "39.97.63.217", - "port": float64(80), - "weight": float64(1), - }, - }, - }, - }, - } - - tests := []struct { - caseDesc string - giveData []*entity.Upstream - giveErr error - wantErr error - wantInput store.ListInput - wantRet *store.ListOutput - getCalled bool - }{ - { - caseDesc: "get upstream list names", - wantRet: &store.ListOutput{ - Rows: []interface{}{ - &entity.UpstreamNameResponse{ - ID: "001", - Name: "upstream1", - }, - &entity.UpstreamNameResponse{ - ID: "002", - Name: "upstream2", - }, - &entity.UpstreamNameResponse{ - ID: "003", - Name: "upstream3", - }, - }, - TotalSize: 3, - }, - getCalled: true, - }, - } - - for _, tc := range tests { - t.Run(tc.caseDesc, func(t *testing.T) { - getCalled := false - upstreamStore := &store.MockInterface{} - upstreamStore.On("List", mock.Anything, mock.Anything).Run(func(args mock.Arguments) { - getCalled = true - }).Return(func(input store.ListInput) *store.ListOutput { - var res []interface{} - for _, c := range mockData { - res = append(res, c) - } - return &store.ListOutput{ - Rows: res, - TotalSize: len(res), - } - }, nil) - - h := Handler{upstreamStore: upstreamStore} - ctx := droplet.NewContext() - ret, err := h.listUpstreamNames(ctx) - assert.True(t, getCalled) - assert.Equal(t, tc.wantRet, ret) - assert.Equal(t, tc.wantErr, err) - }) - } - - mocknilData := []*entity.Upstream{} - - tests1 := []struct { - caseDesc string - giveData []*entity.Upstream - giveErr error - wantErr error - wantInput store.ListInput - wantRet *store.ListOutput - getCalled bool - }{ - { - caseDesc: "get upstream list names nil", - wantRet: &store.ListOutput{ - Rows: []interface{}{}, - TotalSize: 0, - }, - getCalled: true, - }, - } - - for _, tc := range tests1 { - t.Run(tc.caseDesc, func(t *testing.T) { - getCalled := false - upstreamStore := &store.MockInterface{} - upstreamStore.On("List", mock.Anything, mock.Anything).Run(func(args mock.Arguments) { - getCalled = true - }).Return(func(input store.ListInput) *store.ListOutput { - var res []interface{} - for _, c := range mocknilData { - res = append(res, c) - } - return &store.ListOutput{ - Rows: res, - TotalSize: len(res), - } - }, nil) - - h := Handler{upstreamStore: upstreamStore} - ctx := droplet.NewContext() - ret, err := h.listUpstreamNames(ctx) - assert.True(t, getCalled) - assert.Equal(t, tc.wantRet, ret) - assert.Equal(t, tc.wantErr, err) - }) - } -} diff --git a/api/internal/log/log.go b/api/internal/log/log.go deleted file mode 100644 index 43bb6095b3..0000000000 --- a/api/internal/log/log.go +++ /dev/null @@ -1,115 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one or more - * contributor license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright ownership. - * The ASF licenses this file to You under the Apache License, Version 2.0 - * (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package log - -var ( - DefLogger Interface = emptyLog{} -) - -type Type int8 - -const ( - AccessLog Type = iota - 1 - ErrorLog -) - -type emptyLog struct { -} - -type Interface interface { - Debug(msg string, fields ...interface{}) - Debugf(msg string, args ...interface{}) - Info(msg string, fields ...interface{}) - Infof(msg string, args ...interface{}) - Warn(msg string, fields ...interface{}) - Warnf(msg string, args ...interface{}) - Error(msg string, fields ...interface{}) - Errorf(msg string, args ...interface{}) - Fatal(msg string, fields ...interface{}) - Fatalf(msg string, args ...interface{}) -} - -func (e emptyLog) Debug(msg string, fields ...interface{}) { - getZapFields(logger, fields).Debug(msg) -} - -func (e emptyLog) Debugf(msg string, args ...interface{}) { - logger.Debugf(msg, args...) -} - -func (e emptyLog) Info(msg string, fields ...interface{}) { - getZapFields(logger, fields).Info(msg) -} - -func (e emptyLog) Infof(msg string, args ...interface{}) { - logger.Infof(msg, args...) -} - -func (e emptyLog) Warn(msg string, fields ...interface{}) { - getZapFields(logger, fields).Warn(msg) -} - -func (e emptyLog) Warnf(msg string, args ...interface{}) { - logger.Warnf(msg, args...) -} - -func (e emptyLog) Error(msg string, fields ...interface{}) { - getZapFields(logger, fields).Error(msg) -} - -func (e emptyLog) Errorf(msg string, args ...interface{}) { - logger.Errorf(msg, args...) -} - -func (e emptyLog) Fatal(msg string, fields ...interface{}) { - getZapFields(logger, fields).Fatal(msg) -} - -func (e emptyLog) Fatalf(msg string, args ...interface{}) { - logger.Fatalf(msg, args...) -} - -func Debug(msg string, fields ...interface{}) { - DefLogger.Debug(msg, fields...) -} -func Debugf(msg string, args ...interface{}) { - DefLogger.Debugf(msg, args...) -} -func Info(msg string, fields ...interface{}) { - DefLogger.Info(msg, fields...) -} -func Infof(msg string, args ...interface{}) { - DefLogger.Infof(msg, args...) -} -func Warn(msg string, fields ...interface{}) { - DefLogger.Warn(msg, fields...) -} -func Warnf(msg string, args ...interface{}) { - DefLogger.Warnf(msg, args...) -} -func Error(msg string, fields ...interface{}) { - DefLogger.Error(msg, fields...) -} -func Errorf(msg string, args ...interface{}) { - DefLogger.Errorf(msg, args...) -} -func Fatal(msg string, fields ...interface{}) { - DefLogger.Fatal(msg, fields...) -} -func Fatalf(msg string, args ...interface{}) { - DefLogger.Fatalf(msg, args...) -} diff --git a/api/internal/log/zap.go b/api/internal/log/zap.go deleted file mode 100644 index 446dde776d..0000000000 --- a/api/internal/log/zap.go +++ /dev/null @@ -1,123 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one or more - * contributor license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright ownership. - * The ASF licenses this file to You under the Apache License, Version 2.0 - * (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package log - -import ( - "net/url" - "os" - - "go.uber.org/zap" - "go.uber.org/zap/zapcore" - - "github.com/apisix/manager-api/internal/conf" -) - -var logger *zap.SugaredLogger - -// TODO: we should no longer use init() function after remove all handler's integration tests -// ENV=test is for integration tests only, other ENV should call "InitLogger" explicitly -func init() { - if env := os.Getenv("ENV"); env == conf.EnvTEST { - InitLogger() - } -} - -func InitLogger() { - logger = GetLogger(ErrorLog) -} - -func GetLogger(logType Type) *zap.SugaredLogger { - _ = zap.RegisterSink("winfile", newWinFileSink) - - skip := 2 - writeSyncer := fileWriter(logType) - encoder := getEncoder(logType) - logLevel := getLogLevel() - if logType == AccessLog { - logLevel = zapcore.InfoLevel - skip = 0 - } - - core := zapcore.NewCore(encoder, writeSyncer, logLevel) - - zapLogger := zap.New(core, zap.AddCaller(), zap.AddCallerSkip(skip)) - - return zapLogger.Sugar() -} - -func getLogLevel() zapcore.LevelEnabler { - level := zapcore.WarnLevel - switch conf.ErrorLogLevel { - case "debug": - level = zapcore.DebugLevel - case "info": - level = zapcore.InfoLevel - case "warn": - level = zapcore.WarnLevel - case "error": - level = zapcore.ErrorLevel - case "panic": - level = zapcore.PanicLevel - case "fatal": - level = zapcore.FatalLevel - } - return level -} - -func getEncoder(logType Type) zapcore.Encoder { - encoderConfig := zap.NewProductionEncoderConfig() - encoderConfig.EncodeTime = zapcore.ISO8601TimeEncoder - encoderConfig.EncodeLevel = zapcore.CapitalLevelEncoder - - if logType == AccessLog { - encoderConfig.LevelKey = zapcore.OmitKey - } - - return zapcore.NewConsoleEncoder(encoderConfig) -} - -func fileWriter(logType Type) zapcore.WriteSyncer { - logPath := conf.ErrorLogPath - if logType == AccessLog { - logPath = conf.AccessLogPath - } - //standard output - if logPath == "/dev/stdout" { - return zapcore.Lock(os.Stdout) - } - if logPath == "/dev/stderr" { - return zapcore.Lock(os.Stderr) - } - - writer, _, err := zap.Open(logPath) - if err != nil { - panic(err) - } - return writer -} - -func getZapFields(logger *zap.SugaredLogger, fields []interface{}) *zap.SugaredLogger { - if len(fields) == 0 { - return logger - } - - return logger.With(fields) -} - -func newWinFileSink(u *url.URL) (zap.Sink, error) { - return os.OpenFile(u.Path[1:], os.O_WRONLY|os.O_APPEND|os.O_CREATE, 0644) -} diff --git a/api/internal/route.go b/api/internal/route.go deleted file mode 100644 index 37015dd5a9..0000000000 --- a/api/internal/route.go +++ /dev/null @@ -1,110 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one or more - * contributor license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright ownership. - * The ASF licenses this file to You under the Apache License, Version 2.0 - * (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package internal - -import ( - "fmt" - "path/filepath" - - // "github.com/gin-contrib/pprof" - "github.com/gin-contrib/gzip" - "github.com/gin-contrib/static" - "github.com/gin-gonic/gin" - - "github.com/apisix/manager-api/internal/conf" - "github.com/apisix/manager-api/internal/filter" - "github.com/apisix/manager-api/internal/handler" - "github.com/apisix/manager-api/internal/handler/authentication" - "github.com/apisix/manager-api/internal/handler/consumer" - "github.com/apisix/manager-api/internal/handler/data_loader" - "github.com/apisix/manager-api/internal/handler/global_rule" - "github.com/apisix/manager-api/internal/handler/healthz" - "github.com/apisix/manager-api/internal/handler/label" - "github.com/apisix/manager-api/internal/handler/migrate" - "github.com/apisix/manager-api/internal/handler/plugin_config" - "github.com/apisix/manager-api/internal/handler/proto" - "github.com/apisix/manager-api/internal/handler/route" - "github.com/apisix/manager-api/internal/handler/schema" - "github.com/apisix/manager-api/internal/handler/server_info" - "github.com/apisix/manager-api/internal/handler/service" - "github.com/apisix/manager-api/internal/handler/ssl" - "github.com/apisix/manager-api/internal/handler/stream_route" - "github.com/apisix/manager-api/internal/handler/system_config" - "github.com/apisix/manager-api/internal/handler/tool" - "github.com/apisix/manager-api/internal/handler/upstream" - "github.com/apisix/manager-api/internal/log" -) - -func SetUpRouter() *gin.Engine { - if conf.ENV == conf.EnvLOCAL || conf.ENV == conf.EnvDEV { - gin.SetMode(gin.DebugMode) - } else { - gin.SetMode(gin.ReleaseMode) - } - r := gin.New() - logger := log.GetLogger(log.AccessLog) - // security - r.Use(filter.RequestLogHandler(logger), filter.IPFilter(), filter.InvalidRequest()) - - // authenticate - if conf.OidcEnabled { - r.Use(filter.Oidc()) - } - r.Use(filter.Authentication()) - - // misc - r.Use(gzip.Gzip(gzip.DefaultCompression), filter.CORS(), filter.RequestId(), filter.SchemaCheck(), filter.RecoverHandler()) - r.Use(static.Serve("/", static.LocalFile(filepath.Join(conf.WorkDir, conf.WebDir), false))) - r.NoRoute(func(c *gin.Context) { - c.File(fmt.Sprintf("%s/index.html", filepath.Join(conf.WorkDir, conf.WebDir))) - }) - - factories := []handler.RegisterFactory{ - route.NewHandler, - ssl.NewHandler, - consumer.NewHandler, - upstream.NewHandler, - service.NewHandler, - schema.NewHandler, - schema.NewSchemaHandler, - healthz.NewHandler, - authentication.NewHandler, - global_rule.NewHandler, - server_info.NewHandler, - label.NewHandler, - data_loader.NewHandler, - data_loader.NewImportHandler, - tool.NewHandler, - plugin_config.NewHandler, - migrate.NewHandler, - proto.NewHandler, - stream_route.NewHandler, - system_config.NewHandler, - } - - for i := range factories { - h, err := factories[i]() - if err != nil { - panic(err) - } - h.ApplyRoute(r) - } - - // pprof.Register(r) - - return r -} diff --git a/api/internal/utils/closer.go b/api/internal/utils/closer.go deleted file mode 100644 index c848a44a49..0000000000 --- a/api/internal/utils/closer.go +++ /dev/null @@ -1,39 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one or more - * contributor license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright ownership. - * The ASF licenses this file to You under the Apache License, Version 2.0 - * (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package utils - -import "log" - -var ( - _closers []Closer -) - -type Closer func() error - -func AppendToClosers(c Closer) { - _closers = append(_closers, c) -} - -func CloseAll() { - closerLen := len(_closers) - for i := range _closers { - if err := _closers[closerLen-1-i](); err != nil { - log.Println(err) - } - } -} diff --git a/api/internal/utils/consts/api_error.go b/api/internal/utils/consts/api_error.go deleted file mode 100644 index 51e2efd5aa..0000000000 --- a/api/internal/utils/consts/api_error.go +++ /dev/null @@ -1,48 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one or more - * contributor license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright ownership. - * The ASF licenses this file to You under the Apache License, Version 2.0 - * (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package consts - -import ( - "github.com/gin-gonic/gin" -) - -type WrapperHandle func(c *gin.Context) (interface{}, error) - -// swagger:model ApiError -type ApiError struct { - Status int `json:"-"` - // response code - Code int `json:"code"` - // response message - Message string `json:"message"` -} - -func (err ApiError) Error() string { - return err.Message -} - -func InvalidParam(message string) *ApiError { - return &ApiError{400, 10000, message} -} - -func SystemError(message string) *ApiError { - return &ApiError{500, 10001, message} -} - -func NotFound(message string) *ApiError { - return &ApiError{404, 10002, message} -} diff --git a/api/internal/utils/consts/api_error_test.go b/api/internal/utils/consts/api_error_test.go deleted file mode 100644 index 3f5cae4e30..0000000000 --- a/api/internal/utils/consts/api_error_test.go +++ /dev/null @@ -1,29 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one or more - * contributor license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright ownership. - * The ASF licenses this file to You under the Apache License, Version 2.0 - * (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package consts - -import ( - "net/http" - "net/http/httptest" -) - -func performRequest(r http.Handler, method, path string) *httptest.ResponseRecorder { - req := httptest.NewRequest(method, path, nil) - w := httptest.NewRecorder() - r.ServeHTTP(w, req) - return w -} diff --git a/api/internal/utils/consts/error.go b/api/internal/utils/consts/error.go deleted file mode 100644 index e1b7f6598d..0000000000 --- a/api/internal/utils/consts/error.go +++ /dev/null @@ -1,59 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one or more - * contributor license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright ownership. - * The ASF licenses this file to You under the Apache License, Version 2.0 - * (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package consts - -import ( - "errors" - - "github.com/shiningrush/droplet/data" -) - -const ( - ErrBadRequest = 20001 - ErrForbidden = 20002 -) - -const ( - // IDNotFound is the string use for can't find the cache by the id - IDNotFound = "%s id: %s not found" -) - -var ( - // ErrorUsernamePassword is the error means username or password is not correct - ErrUsernamePassword = errors.New("username or password error") - // ErrorIDUsername is the error use for the input's id and username is different - ErrIDUsername = errors.New("consumer's id and username must be a same value") - // ErrorParameterID is the error use for parameter ID is empty - ErrParameterID = errors.New("Parameter IDs cannot be empty") - // ErrorRouteData is the error that the route data is empty - ErrRouteData = errors.New("Route data is empty, cannot be exported") - // ErrorImportFile is the error that use for import a empty file - ErrImportFile = errors.New("empty or invalid imported file") - // ErrorImportFile means the certificate is invalid - ErrSSLCertificate = errors.New("invalid certificate") - // ErrorSSLCertificateResolution means the SSL certificate decode failed - ErrSSLCertificateResolution = errors.New("Certificate resolution failed") - // ErrorSSLKeyAndCert means the SSL key and SSL certificate don't match - ErrSSLKeyAndCert = errors.New("key and cert don't match") -) - -var ( - // base error please refer to github.com/shiningrush/droplet/data, such as data.ErrNotFound, data.ErrConflicted - ErrInvalidRequest = data.BaseError{Code: ErrBadRequest, Message: "invalid request"} - ErrSchemaValidateFailed = data.BaseError{Code: ErrBadRequest, Message: "JSONSchema validate failed"} - ErrIPNotAllow = data.BaseError{Code: ErrForbidden, Message: "IP address not allowed"} -) diff --git a/api/internal/utils/json_patch.go b/api/internal/utils/json_patch.go deleted file mode 100644 index d6bcd1d285..0000000000 --- a/api/internal/utils/json_patch.go +++ /dev/null @@ -1,75 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one or more - * contributor license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright ownership. - * The ASF licenses this file to You under the Apache License, Version 2.0 - * (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package utils - -import ( - "encoding/json" - jsonpatch "github.com/evanphx/json-patch/v5" -) - -func MergeJson(doc, patch []byte) ([]byte, error) { - out, err := jsonpatch.MergePatch(doc, patch) - - if err != nil { - return nil, err - } - return out, nil -} - -func PatchJson(doc []byte, path, val string) ([]byte, error) { - patch := []byte(`[ { "op": "replace", "path": "` + path + `", "value": ` + val + `}]`) - obj, err := jsonpatch.DecodePatch(patch) - if err != nil { - return nil, err - } - - out, err := obj.Apply(doc) - - if err != nil { - // try to add if field not exist - patch = []byte(`[ { "op": "add", "path": "` + path + `", "value": ` + val + `}]`) - obj, err = jsonpatch.DecodePatch(patch) - if err != nil { - return nil, err - } - out, err = obj.Apply(doc) - if err != nil { - return nil, err - } - } - - return out, nil -} - -func MergePatch(obj interface{}, subPath string, reqBody []byte) ([]byte, error) { - var res []byte - jsonBytes, err := json.Marshal(obj) - if err != nil { - return res, err - } - - if subPath != "" { - res, err = PatchJson(jsonBytes, subPath, string(reqBody)) - } else { - res, err = MergeJson(jsonBytes, reqBody) - } - - if err != nil { - return res, err - } - return res, nil -} diff --git a/api/internal/utils/json_patch_test.go b/api/internal/utils/json_patch_test.go deleted file mode 100644 index 8dd25404e1..0000000000 --- a/api/internal/utils/json_patch_test.go +++ /dev/null @@ -1,211 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one or more - * contributor license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright ownership. - * The ASF licenses this file to You under the Apache License, Version 2.0 - * (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package utils - -import ( - "bytes" - "encoding/json" - "reflect" - "testing" -) - -func compareJSON(a, b string) bool { - var objA, objB interface{} - json.Unmarshal([]byte(a), &objA) - json.Unmarshal([]byte(b), &objB) - - return reflect.DeepEqual(objA, objB) -} - -func formatJSON(j string) string { - buf := new(bytes.Buffer) - - json.Indent(buf, []byte(j), "", " ") - - return buf.String() -} - -func TestMergeJson(t *testing.T) { - cases := []struct { - doc, patch, result, desc string - }{ - { - desc: "simple merge", - doc: `{ - "id": "1", - "status": 1, - "key": "fake key", - "cert": "fake cert", - "create_time": 1, - "update_time": 2 - }`, - patch: `{ - "id": "1", - "status": 0, - "key": "fake key1", - "cert": "fake cert1" - }`, - result: `{ - "id": "1", - "status": 0, - "key": "fake key1", - "cert": "fake cert1", - "create_time": 1, - "update_time": 2 - }`, - }, - { - desc: `array merge`, - doc: `{ - "uri": "/index.html", - "upstream": { - "type": "roundrobin", - "nodes": [{ - "host": "39.97.63.215", - "port": 80, - "weight" : 1 - }] - } - }`, - patch: `{ - "upstream": { - "nodes": [{ - "host": "39.97.63.216", - "port": 80, - "weight" : 1 - },{ - "host": "39.97.63.217", - "port": 80, - "weight" : 1 - }] - } - }`, - result: `{ - "uri": "/index.html", - "upstream": { - "type": "roundrobin", - "nodes": [{ - "host": "39.97.63.216", - "port": 80, - "weight" : 1 - },{ - "host": "39.97.63.217", - "port": 80, - "weight" : 1 - }] - } - }`, - }, - } - for _, c := range cases { - out, err := MergeJson([]byte(c.doc), []byte(c.patch)) - - if err != nil { - t.Errorf("Unable to merge patch: %s", err) - } - - if !compareJSON(string(out), c.result) { - t.Errorf("Merge failed. Expected:\n%s\n\nActual:\n%s", - formatJSON(c.result), formatJSON(string(out))) - } - } -} - -func TestPatchJson(t *testing.T) { - cases := []struct { - doc, path, value, result, desc string - }{ - { - desc: "patch array", - doc: `{ - "uri": "/index.html", - "upstream": { - "type": "roundrobin", - "nodes": [{ - "host": "39.97.63.215", - "port": 80, - "weight" : 1 - }] - } - }`, - path: `/upstream/nodes`, - value: `[{ - "host": "39.97.63.216", - "port": 80, - "weight" : 1 - },{ - "host": "39.97.63.217", - "port": 80, - "weight" : 1 - }]`, - result: `{ - "uri": "/index.html", - "upstream": { - "type": "roundrobin", - "nodes": [{ - "host": "39.97.63.216", - "port": 80, - "weight" : 1 - },{ - "host": "39.97.63.217", - "port": 80, - "weight" : 1 - }] - } - }`, - }, - { - desc: "patch field that non existent", - doc: `{ - "uri": "/index.html", - "upstream": { - "type": "roundrobin", - "nodes": [{ - "host": "39.97.63.215", - "port": 80, - "weight" : 1 - }] - } - }`, - path: `/upstream/labels`, - value: `{"app": "test"}`, - result: `{ - "uri": "/index.html", - "upstream": { - "type": "roundrobin", - "nodes": [{ - "host": "39.97.63.215", - "port": 80, - "weight" : 1 - }], - "labels": {"app": "test"} - } - }`, - }, - } - for _, c := range cases { - out, err := PatchJson([]byte(c.doc), c.path, c.value) - if err != nil { - t.Errorf("Unable to patch: %s", err) - } - - if !compareJSON(string(out), c.result) { - t.Errorf("Patch failed. Expected:\n%s\n\nActual:\n%s", - formatJSON(c.result), formatJSON(string(out))) - } - } -} diff --git a/api/internal/utils/pid.go b/api/internal/utils/pid.go deleted file mode 100644 index 4827588b81..0000000000 --- a/api/internal/utils/pid.go +++ /dev/null @@ -1,59 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one or more - * contributor license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright ownership. - * The ASF licenses this file to You under the Apache License, Version 2.0 - * (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package utils - -import ( - "fmt" - "io/ioutil" - "os" - "strconv" -) - -// WritePID write pid to the given file path. -func WritePID(filepath string, forceStart bool) error { - if pid, err := ReadPID(filepath); err == nil { - if !forceStart { - return fmt.Errorf("Instance of Manager API maybe running with a pid %d. If not, please run Manager API with '-f' or '--force' flag\n", pid) - } - fmt.Printf("Force starting new instance. Another instance of Manager API maybe running with pid %d\n", pid) - } - pid := os.Getpid() - f, err := os.OpenFile(filepath, os.O_WRONLY|os.O_CREATE|os.O_TRUNC|os.O_CREATE, 0600) - if err != nil { - return err - } - defer f.Close() - - if _, err := f.WriteString(strconv.Itoa(pid)); err != nil { - return err - } - return nil -} - -// ReadPID reads the pid from the given file path. -func ReadPID(filepath string) (int, error) { - data, err := ioutil.ReadFile(filepath) - if err != nil { - return -1, err - } - pid, err := strconv.Atoi(string(data)) - if err != nil { - return -1, fmt.Errorf("invalid pid: %s", err) - } - return pid, nil -} diff --git a/api/internal/utils/runtime/runtime.go b/api/internal/utils/runtime/runtime.go deleted file mode 100644 index 7b6dcd99b4..0000000000 --- a/api/internal/utils/runtime/runtime.go +++ /dev/null @@ -1,59 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one or more - * contributor license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright ownership. - * The ASF licenses this file to You under the Apache License, Version 2.0 - * (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package runtime - -import ( - "net/http" - "runtime" - - "github.com/apisix/manager-api/internal/log" -) - -var ( - ActuallyPanic = true -) - -var PanicHandlers = []func(interface{}){logPanic} - -func HandlePanic(additionalHandlers ...func(interface{})) { - if err := recover(); err != nil { - for _, fn := range PanicHandlers { - fn(err) - } - for _, fn := range additionalHandlers { - fn(err) - } - if ActuallyPanic { - panic(err) - } - } -} - -func logPanic(r interface{}) { - if r == http.ErrAbortHandler { - return - } - - const size = 32 << 10 - stacktrace := make([]byte, size) - stacktrace = stacktrace[:runtime.Stack(stacktrace, false)] - if _, ok := r.(string); ok { - log.Errorf("observed a panic: %s\n%s", r, stacktrace) - } else { - log.Errorf("observed a panic: %#v (%v)\n%s", r, r, stacktrace) - } -} diff --git a/api/internal/utils/runtime/runtime_test.go b/api/internal/utils/runtime/runtime_test.go deleted file mode 100644 index 103e59f0ad..0000000000 --- a/api/internal/utils/runtime/runtime_test.go +++ /dev/null @@ -1,54 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one or more - * contributor license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright ownership. - * The ASF licenses this file to You under the Apache License, Version 2.0 - * (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package runtime - -import ( - "testing" -) - -func TestHandleCrash(t *testing.T) { - defer func() { - if x := recover(); x == nil { - t.Errorf("Expected a panic to recover from") - } - }() - defer HandlePanic() - panic("Test Panic") -} - -func TestCustomHandleCrash(t *testing.T) { - old := PanicHandlers - defer func() { PanicHandlers = old }() - var result interface{} - PanicHandlers = []func(interface{}){ - func(r interface{}) { - result = r - }, - } - func() { - defer func() { - if x := recover(); x == nil { - t.Errorf("Expected a panic to recover from") - } - }() - defer HandlePanic() - panic("test") - }() - if result != "test" { - t.Errorf("did not receive custom handler") - } -} diff --git a/api/internal/utils/utils.go b/api/internal/utils/utils.go deleted file mode 100644 index c7962bed1c..0000000000 --- a/api/internal/utils/utils.go +++ /dev/null @@ -1,224 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one or more - * contributor license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright ownership. - * The ASF licenses this file to You under the Apache License, Version 2.0 - * (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package utils - -import ( - "bytes" - "encoding/json" - "errors" - "fmt" - "net" - "os" - "sort" - "strconv" - "strings" - - "github.com/sony/sonyflake" - "github.com/yuin/gopher-lua/parse" -) - -var _sf *sonyflake.Sonyflake - -func init() { - saltStr, ok := os.LookupEnv("FLAKE_SALT") - var salt uint16 - if ok { - i, err := strconv.ParseUint(saltStr, 10, 16) - if err != nil { - panic(err) - } - salt = uint16(i) - } - ips, err := getLocalIPs() - if err != nil { - panic(err) - } - _sf = sonyflake.NewSonyflake(sonyflake.Settings{ - MachineID: func() (u uint16, e error) { - return sumIPs(ips) + salt, nil - }, - }) - if _sf == nil { - panic("sonyflake init failed") - } -} - -func sumIPs(ips []net.IP) uint16 { - total := 0 - for _, ip := range ips { - for i := range ip { - total += int(ip[i]) - } - } - return uint16(total) -} - -func getLocalIPs() ([]net.IP, error) { - var ips []net.IP - addrs, err := net.InterfaceAddrs() - if err != nil { - return ips, err - } - for _, a := range addrs { - if ipNet, ok := a.(*net.IPNet); ok && !ipNet.IP.IsLoopback() && ipNet.IP.To4() != nil { - ips = append(ips, ipNet.IP) - } - } - return ips, nil -} - -func GetFlakeUid() uint64 { - uid, err := _sf.NextID() - if err != nil { - panic("get sony flake uid failed:" + err.Error()) - } - return uid -} - -func GetFlakeUidStr() string { - return strconv.FormatUint(GetFlakeUid(), 10) -} - -func InterfaceToString(val interface{}) string { - if val == nil { - return "" - } - str := fmt.Sprintf("%v", val) - return str -} - -// Note: json.Marshal and json.Unmarshal may cause the precision loss -func ObjectClone(origin, copy interface{}) error { - byt, err := json.Marshal(origin) - if err != nil { - return err - } - - err = json.Unmarshal(byt, copy) - return err -} - -func GenLabelMap(label string) (map[string]struct{}, error) { - var err = errors.New("malformed label") - mp := make(map[string]struct{}) - - if label == "" { - return mp, nil - } - - labels := strings.Split(label, ",") - for _, l := range labels { - kv := strings.Split(l, ":") - if len(kv) == 2 { - if kv[0] == "" || kv[1] == "" { - return nil, err - } - - // Because the labels may contain the same key, like this: label=version:v1,version:v2 - // we need to combine them as a map's key - mp[l] = struct{}{} - } else if len(kv) == 1 { - if kv[0] == "" { - return nil, err - } - - mp[kv[0]] = struct{}{} - } else { - return nil, err - } - } - - return mp, nil -} - -func LabelContains(labels map[string]string, reqLabels map[string]struct{}) bool { - if len(reqLabels) == 0 { - return true - } - - for k, v := range labels { - // first check the key - if _, exist := reqLabels[k]; exist { - return true - } - - // second check the key:value - if _, exist := reqLabels[k+":"+v]; exist { - return true - } - } - - return false -} - -// ValidateLuaCode validates lua syntax for input code, return nil -// if passed, otherwise a non-nil error will be returned -func ValidateLuaCode(code string) error { - _, err := parse.Parse(strings.NewReader(code), "") - return err -} - -func StringSliceContains(a, b []string) bool { - if (a == nil) != (b == nil) { - return false - } - - for i := range a { - for j := range b { - if a[i] == b[j] { - return true - } - } - } - - return false -} - -// -func StringSliceEqual(a, b []string) bool { - if (a == nil) != (b == nil) { - return false - } - - if len(a) != len(b) { - return false - } - - sort.Strings(a) - sort.Strings(b) - - for i := range a { - if a[i] != b[i] { - return false - } - } - - return true -} - -// value compare -func ValueEqual(a interface{}, b interface{}) bool { - aBytes, err := json.Marshal(a) - if err != nil { - return false - } - bBytes, err := json.Marshal(b) - if err != nil { - return false - } - return bytes.Equal(aBytes, bBytes) -} diff --git a/api/internal/utils/utils_test.go b/api/internal/utils/utils_test.go deleted file mode 100644 index 5cec1e8a5e..0000000000 --- a/api/internal/utils/utils_test.go +++ /dev/null @@ -1,130 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one or more - * contributor license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright ownership. - * The ASF licenses this file to You under the Apache License, Version 2.0 - * (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package utils - -import ( - "errors" - "testing" - - "github.com/stretchr/testify/assert" -) - -func TestGetFlakeUid(t *testing.T) { - id := GetFlakeUid() - assert.NotEqual(t, 0, id) -} - -func TestGetFlakeUidStr(t *testing.T) { - id := GetFlakeUidStr() - assert.NotEqual(t, "", id) - assert.Equal(t, 18, len(id)) -} - -func TestGetLocalIPs(t *testing.T) { - _, err := getLocalIPs() - assert.Equal(t, nil, err) -} - -func TestSumIPs_with_nil(t *testing.T) { - total := sumIPs(nil) - assert.Equal(t, uint16(0), total) -} - -func TestObjectClone(t *testing.T) { - type test struct { - Str string - Num int - } - - origin := &test{Str: "a", Num: 1} - copy := &test{} - err := ObjectClone(origin, copy) - assert.Nil(t, err) - assert.Equal(t, origin, copy) - - // change value of the copy, should not change value of origin - copy.Num = 2 - assert.NotEqual(t, copy.Num, origin.Num) - assert.Equal(t, 1, origin.Num) -} - -func TestGenLabelMap(t *testing.T) { - expectedErr := errors.New("malformed label") - mp, err := GenLabelMap("l1") - assert.Nil(t, err) - assert.Equal(t, mp["l1"], struct{}{}) - - mp, err = GenLabelMap("l1,l2:v2") - assert.Nil(t, err) - assert.Equal(t, mp["l1"], struct{}{}) - assert.Equal(t, mp["l2:v2"], struct{}{}) - - mp, err = GenLabelMap("l1:v1,l1:v2") - assert.Nil(t, err) - assert.Equal(t, mp["l1:v1"], struct{}{}) - assert.Equal(t, mp["l1:v2"], struct{}{}) - - mp, err = GenLabelMap(",") - assert.Equal(t, expectedErr, err) - assert.Nil(t, mp) - - mp, err = GenLabelMap(",l2:,") - assert.Equal(t, expectedErr, err) - assert.Nil(t, mp) -} - -func TestLabelContains(t *testing.T) { - reqMap, _ := GenLabelMap("l1,l2:v2") - mp := map[string]string{ - "l1": "v1", - } - assert.True(t, LabelContains(mp, reqMap)) - - mp = map[string]string{ - "l1": "v1", - "l2": "v3", - } - assert.True(t, LabelContains(mp, reqMap)) - - mp = map[string]string{ - "l2": "v3", - } - assert.False(t, LabelContains(mp, reqMap)) - - reqMap, _ = GenLabelMap("l1:v1,l1:v2") - mp = map[string]string{ - "l1": "v1", - } - assert.True(t, LabelContains(mp, reqMap)) - - reqMap, _ = GenLabelMap("l1:v1,l1:v2") - mp = map[string]string{ - "l1": "v2", - } - assert.True(t, LabelContains(mp, reqMap)) -} - -func TestValidateLuaCode(t *testing.T) { - validLuaCode := "local _M = {} \n function _M.access(api_ctx) \n ngx.log(ngx.WARN,\"hit access phase\") \n end \nreturn _M" - err := ValidateLuaCode(validLuaCode) - assert.Nil(t, err) - - invalidLuaCode := "local _M = {} \n function _M.access(api_ctx) \n ngx.log(ngx.WARN,\"hit access phase\")" - err = ValidateLuaCode(invalidLuaCode) - assert.NotNil(t, err) - assert.Equal(t, " at EOF: syntax error\n", err.Error()) -} diff --git a/api/internal/utils/version.go b/api/internal/utils/version.go deleted file mode 100644 index c061156406..0000000000 --- a/api/internal/utils/version.go +++ /dev/null @@ -1,39 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one or more - * contributor license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright ownership. - * The ASF licenses this file to You under the Apache License, Version 2.0 - * (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package utils - -import ( - "fmt" - "os" -) - -var ( - gitHash string - version string -) - -// GetHashAndVersion get the hash and version -func GetHashAndVersion() (string, string) { - return gitHash, version -} - -// PrintVersion print version and git hash to stdout -func PrintVersion() { - gitHash, version := GetHashAndVersion() - fmt.Fprintf(os.Stdout, "%-8s: %s\n", "Version", version) - fmt.Fprintf(os.Stdout, "%-8s: %s\n", "GitHash", gitHash) -} diff --git a/api/logs/.gitkeep b/api/logs/.gitkeep deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/api/main.go b/api/main.go deleted file mode 100644 index 6fc7013d70..0000000000 --- a/api/main.go +++ /dev/null @@ -1,25 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one or more - * contributor license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright ownership. - * The ASF licenses this file to You under the Apache License, Version 2.0 - * (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package main - -import ( - "github.com/apisix/manager-api/cmd" -) - -func main() { - cmd.Execute() -} diff --git a/api/main_test.go b/api/main_test.go deleted file mode 100644 index f89149005e..0000000000 --- a/api/main_test.go +++ /dev/null @@ -1,56 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one or more - * contributor license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright ownership. - * The ASF licenses this file to You under the Apache License, Version 2.0 - * (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package main - -import ( - "os" - "os/signal" - "strings" - "syscall" - "testing" -) - -func TestMainWrapper(t *testing.T) { - if os.Getenv("ENV") == "test" { - t.Skip("skipping build binary when execute unit test") - } - - var ( - args []string - ) - for _, arg := range os.Args { - switch { - case strings.HasPrefix(arg, "-test"): - default: - args = append(args, arg) - } - } - waitCh := make(chan int, 1) - os.Args = args - go func() { - main() - close(waitCh) - }() - signalCh := make(chan os.Signal, 1) - signal.Notify(signalCh, syscall.SIGINT, syscall.SIGQUIT, syscall.SIGTERM, syscall.SIGHUP) - select { - case <-signalCh: - return - case <-waitCh: - return - } -} diff --git a/api/run.sh b/api/run.sh deleted file mode 100755 index 71eae44bf4..0000000000 --- a/api/run.sh +++ /dev/null @@ -1,24 +0,0 @@ -#!/usr/bin/env bash -# -# Licensed to the Apache Software Foundation (ASF) under one or more -# contributor license agreements. See the NOTICE file distributed with -# this work for additional information regarding copyright ownership. -# The ASF licenses this file to You under the Apache License, Version 2.0 -# (the "License"); you may not use this file except in compliance with -# the License. You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -# - -export ENV=local -pwd=$(pwd) - -cd ./output || exit - -exec ./manager-api diff --git a/api/service/apisix-dashboard.service b/api/service/apisix-dashboard.service deleted file mode 100644 index e56f75deb0..0000000000 --- a/api/service/apisix-dashboard.service +++ /dev/null @@ -1,8 +0,0 @@ -[Unit] -Description=apisix-dashboard -Conflicts=apisix-dashboard.service -After=network-online.target - -[Service] -WorkingDirectory=/usr/local/apisix-dashboard -ExecStart=/usr/local/apisix-dashboard/manager-api -c /usr/local/apisix-dashboard/conf/conf.yaml diff --git a/api/test/certs/apisix.crt b/api/test/certs/apisix.crt deleted file mode 100644 index 503f277979..0000000000 --- a/api/test/certs/apisix.crt +++ /dev/null @@ -1,27 +0,0 @@ ------BEGIN CERTIFICATE----- -MIIEojCCAwqgAwIBAgIJAK253pMhgCkxMA0GCSqGSIb3DQEBCwUAMFYxCzAJBgNV -BAYTAkNOMRIwEAYDVQQIDAlHdWFuZ0RvbmcxDzANBgNVBAcMBlpodUhhaTEPMA0G -A1UECgwGaXJlc3R5MREwDwYDVQQDDAh0ZXN0LmNvbTAgFw0xOTA2MjQyMjE4MDVa -GA8yMTE5MDUzMTIyMTgwNVowVjELMAkGA1UEBhMCQ04xEjAQBgNVBAgMCUd1YW5n -RG9uZzEPMA0GA1UEBwwGWmh1SGFpMQ8wDQYDVQQKDAZpcmVzdHkxETAPBgNVBAMM -CHRlc3QuY29tMIIBojANBgkqhkiG9w0BAQEFAAOCAY8AMIIBigKCAYEAyCM0rqJe -cvgnCfOw4fATotPwk5Ba0gC2YvIrO+gSbQkyxXF5jhZB3W6BkWUWR4oNFLLSqcVb -VDPitz/Mt46Mo8amuS6zTbQetGnBARzPLtmVhJfoeLj0efMiOepOSZflj9Ob4yKR -2bGdEFOdHPjm+4ggXU9jMKeLqdVvxll/JiVFBW5smPtW1Oc/BV5terhscJdOgmRr -abf9xiIis9/qVYfyGn52u9452V0owUuwP7nZ01jt6iMWEGeQU6mwPENgvj1olji2 -WjdG2UwpUVp3jp3l7j1ekQ6mI0F7yI+LeHzfUwiyVt1TmtMWn1ztk6FfLRqwJWR/ -Evm95vnfS3Le4S2ky3XAgn2UnCMyej3wDN6qHR1onpRVeXhrBajbCRDRBMwaNw/1 -/3Uvza8QKK10PzQR6OcQ0xo9psMkd9j9ts/dTuo2fzaqpIfyUbPST4GdqNG9NyIh -/B9g26/0EWcjyO7mYVkaycrtLMaXm1u9jyRmcQQI1cGrGwyXbrieNp63AgMBAAGj -cTBvMB0GA1UdDgQWBBSZtSvV8mBwl0bpkvFtgyiOUUcbszAfBgNVHSMEGDAWgBSZ -tSvV8mBwl0bpkvFtgyiOUUcbszAMBgNVHRMEBTADAQH/MB8GA1UdEQQYMBaCCHRl -c3QuY29tggoqLnRlc3QuY29tMA0GCSqGSIb3DQEBCwUAA4IBgQAHGEul/x7ViVgC -tC8CbXEslYEkj1XVr2Y4hXZXAXKd3W7V3TC8rqWWBbr6L/tsSVFt126V5WyRmOaY -1A5pju8VhnkhYxYfZALQxJN2tZPFVeME9iGJ9BE1wPtpMgITX8Rt9kbNlENfAgOl -PYzrUZN1YUQjX+X8t8/1VkSmyZysr6ngJ46/M8F16gfYXc9zFj846Z9VST0zCKob -rJs3GtHOkS9zGGldqKKCj+Awl0jvTstI4qtS1ED92tcnJh5j/SSXCAB5FgnpKZWy -hme45nBQj86rJ8FhN+/aQ9H9/2Ib6Q4wbpaIvf4lQdLUEcWAeZGW6Rk0JURwEog1 -7/mMgkapDglgeFx9f/XztSTrkHTaX4Obr+nYrZ2V4KOB4llZnK5GeNjDrOOJDk2y -IJFgBOZJWyS93dQfuKEj42hA79MuX64lMSCVQSjX+ipR289GQZqFrIhiJxLyA+Ve -U/OOcSRr39Kuis/JJ+DkgHYa/PWHZhnJQBxcqXXk1bJGw9BNbhM= ------END CERTIFICATE----- diff --git a/api/test/certs/apisix.key b/api/test/certs/apisix.key deleted file mode 100644 index 71050679e4..0000000000 --- a/api/test/certs/apisix.key +++ /dev/null @@ -1,39 +0,0 @@ ------BEGIN RSA PRIVATE KEY----- -MIIG5AIBAAKCAYEAyCM0rqJecvgnCfOw4fATotPwk5Ba0gC2YvIrO+gSbQkyxXF5 -jhZB3W6BkWUWR4oNFLLSqcVbVDPitz/Mt46Mo8amuS6zTbQetGnBARzPLtmVhJfo -eLj0efMiOepOSZflj9Ob4yKR2bGdEFOdHPjm+4ggXU9jMKeLqdVvxll/JiVFBW5s -mPtW1Oc/BV5terhscJdOgmRrabf9xiIis9/qVYfyGn52u9452V0owUuwP7nZ01jt -6iMWEGeQU6mwPENgvj1olji2WjdG2UwpUVp3jp3l7j1ekQ6mI0F7yI+LeHzfUwiy -Vt1TmtMWn1ztk6FfLRqwJWR/Evm95vnfS3Le4S2ky3XAgn2UnCMyej3wDN6qHR1o -npRVeXhrBajbCRDRBMwaNw/1/3Uvza8QKK10PzQR6OcQ0xo9psMkd9j9ts/dTuo2 -fzaqpIfyUbPST4GdqNG9NyIh/B9g26/0EWcjyO7mYVkaycrtLMaXm1u9jyRmcQQI -1cGrGwyXbrieNp63AgMBAAECggGBAJM8g0duoHmIYoAJzbmKe4ew0C5fZtFUQNmu -O2xJITUiLT3ga4LCkRYsdBnY+nkK8PCnViAb10KtIT+bKipoLsNWI9Xcq4Cg4G3t -11XQMgPPgxYXA6m8t+73ldhxrcKqgvI6xVZmWlKDPn+CY/Wqj5PA476B5wEmYbNC -GIcd1FLl3E9Qm4g4b/sVXOHARF6iSvTR+6ol4nfWKlaXSlx2gNkHuG8RVpyDsp9c -z9zUqAdZ3QyFQhKcWWEcL6u9DLBpB/gUjyB3qWhDMe7jcCBZR1ALyRyEjmDwZzv2 -jlv8qlLFfn9R29UI0pbuL1eRAz97scFOFme1s9oSU9a12YHfEd2wJOM9bqiKju8y -DZzePhEYuTZ8qxwiPJGy7XvRYTGHAs8+iDlG4vVpA0qD++1FTpv06cg/fOdnwshE -OJlEC0ozMvnM2rZ2oYejdG3aAnUHmSNa5tkJwXnmj/EMw1TEXf+H6+xknAkw05nh -zsxXrbuFUe7VRfgB5ElMA/V4NsScgQKBwQDmMRtnS32UZjw4A8DsHOKFzugfWzJ8 -Gc+3sTgs+4dNIAvo0sjibQ3xl01h0BB2Pr1KtkgBYB8LJW/FuYdCRS/KlXH7PHgX -84gYWImhNhcNOL3coO8NXvd6+m+a/Z7xghbQtaraui6cDWPiCNd/sdLMZQ/7LopM -RbM32nrgBKMOJpMok1Z6zsPzT83SjkcSxjVzgULNYEp03uf1PWmHuvjO1yELwX9/ -goACViF+jst12RUEiEQIYwr4y637GQBy+9cCgcEA3pN9W5OjSPDVsTcVERig8++O -BFURiUa7nXRHzKp2wT6jlMVcu8Pb2fjclxRyaMGYKZBRuXDlc/RNO3uTytGYNdC2 -IptU5N4M7iZHXj190xtDxRnYQWWo/PR6EcJj3f/tc3Itm1rX0JfuI3JzJQgDb9Z2 -s/9/ub8RRvmQV9LM/utgyOwNdf5dyVoPcTY2739X4ZzXNH+CybfNa+LWpiJIVEs2 -txXbgZrhmlaWzwA525nZ0UlKdfktdcXeqke9eBghAoHARVTHFy6CjV7ZhlmDEtqE -U58FBOS36O7xRDdpXwsHLnCXhbFu9du41mom0W4UdzjgVI9gUqG71+SXrKr7lTc3 -dMHcSbplxXkBJawND/Q1rzLG5JvIRHO1AGJLmRgIdl8jNgtxgV2QSkoyKlNVbM2H -Wy6ZSKM03lIj74+rcKuU3N87dX4jDuwV0sPXjzJxL7NpR/fHwgndgyPcI14y2cGz -zMC44EyQdTw+B/YfMnoZx83xaaMNMqV6GYNnTHi0TO2TAoHBAKmdrh9WkE2qsr59 -IoHHygh7Wzez+Ewr6hfgoEK4+QzlBlX+XV/9rxIaE0jS3Sk1txadk5oFDebimuSk -lQkv1pXUOqh+xSAwk5v88dBAfh2dnnSa8HFN3oz+ZfQYtnBcc4DR1y2X+fVNgr3i -nxruU2gsAIPFRnmvwKPc1YIH9A6kIzqaoNt1f9VM243D6fNzkO4uztWEApBkkJgR -4s/yOjp6ovS9JG1NMXWjXQPcwTq3sQVLnAHxZRJmOvx69UmK4QKBwFYXXjeXiU3d -bcrPfe6qNGjfzK+BkhWznuFUMbuxyZWDYQD5yb6ukUosrj7pmZv3BxKcKCvmONU+ -CHgIXB+hG+R9S2mCcH1qBQoP/RSm+TUzS/Bl2UeuhnFZh2jSZQy3OwryUi6nhF0u -LDzMI/6aO1ggsI23Ri0Y9ZtqVKczTkxzdQKR9xvoNBUufjimRlS80sJCEB3Qm20S -wzarryret/7GFW1/3cz+hTj9/d45i25zArr3Pocfpur5mfz3fJO8jg== ------END RSA PRIVATE KEY----- diff --git a/api/test/certs/mtls_ca.pem b/api/test/certs/mtls_ca.pem deleted file mode 100644 index b8b7f6fb69..0000000000 --- a/api/test/certs/mtls_ca.pem +++ /dev/null @@ -1,25 +0,0 @@ ------BEGIN CERTIFICATE----- -MIIEKjCCAxKgAwIBAgIUFUwVOj73RH1oKB5hkp1MiU86K6owDQYJKoZIhvcNAQEL -BQAwgawxCzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpDYWxpZm9ybmlhMRYwFAYDVQQH -Ew1TYW4gRnJhbmNpc2NvMSowKAYDVQQKEyFIb25lc3QgQWNobWVkJ3MgVXNlZCBD -ZXJ0aWZpY2F0ZXMxKTAnBgNVBAsTIEhhc3RpbHktR2VuZXJhdGVkIFZhbHVlcyBE -aXZpc29uMRkwFwYDVQQDExBBdXRvZ2VuZXJhdGVkIENBMB4XDTIxMDIwNTA4MTkw -MFoXDTI2MDIwNDA4MTkwMFowgawxCzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpDYWxp -Zm9ybmlhMRYwFAYDVQQHEw1TYW4gRnJhbmNpc2NvMSowKAYDVQQKEyFIb25lc3Qg -QWNobWVkJ3MgVXNlZCBDZXJ0aWZpY2F0ZXMxKTAnBgNVBAsTIEhhc3RpbHktR2Vu -ZXJhdGVkIFZhbHVlcyBEaXZpc29uMRkwFwYDVQQDExBBdXRvZ2VuZXJhdGVkIENB -MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAxSDAqeu4jFF7fpKT1gqp -vhC6fGWipNLDcBMMpqCSiKwi1DF0VvDiOUMNLRhsClheLJjtGXGFBJLisHD9HB3g -q+NsyjETueD0i93qgTl3u/9Dc9oWtoy+1vyLBp5eDSIHsh8zbYFubtf3aBiBrxxk -J83vEjG5u6dfpfroEOHPXFN6mdQxWDpoEQoVf5cUr9ZdzO1Kf+aaRKF6p/IPTonm -WqZ587f21H/7Yrq/5s4kcYVbVmprHnvjHruc4utbdWlwAZzDYDeNK4lT+hZ1ciDX -EWnPSYFn5lSojPDjuhI7dmHnQk3vs+SVX+cTerwc253tbgB9EmIwqsvMne8y8dof -mQIDAQABo0IwQDAOBgNVHQ8BAf8EBAMCAQYwDwYDVR0TAQH/BAUwAwEB/zAdBgNV -HQ4EFgQUWjiJWGaoZJtQp7T4WtCNLkCrBPIwDQYJKoZIhvcNAQELBQADggEBADgj -8hbEamDNhvxQ/QK4BEzW+W0xUzL1GgGMR5Ocr1OSx0htTfwWCjvyz8Qor5j301bN -ek/u3z3hbV7GXgFp819M0sZibk8i3IDVtcXTQTq5aImLw73gOzF4xcpL0LZUOgsO -Zl4/fSMNg0oIUWQXohRh4q9QnoWsWLYfyd8/NJyv75HKzvst7pUlxp1NVbEFjz3l -HXXK1vvQvq1S5dmvS3wCxP1mBemgftormLlAFnpk1GOl5QaBfPgyg9N2uD2KHRec -BYinzfn8uCXxs2vuRwfT4MhTgDN8/u3Z62L+85Pwcn93Dksuy6dDfQfBbCCCSuRM -KeNO9h6V0FYMbX1eYWc= ------END CERTIFICATE----- diff --git a/api/test/certs/mtls_client-key.pem b/api/test/certs/mtls_client-key.pem deleted file mode 100644 index 2b0adeb642..0000000000 --- a/api/test/certs/mtls_client-key.pem +++ /dev/null @@ -1,27 +0,0 @@ ------BEGIN RSA PRIVATE KEY----- -MIIEpAIBAAKCAQEAzX9YEg9zmc/44rYy5Xb4sEEeNLb+CT5VkRFej0K/8/N169Rl -9yZyXla8cGVQMNJneDg0bc2IvABptw+zTSfqfArbCPPrG++a9oJXHLLML4Sj0Zu0 -VuUirbgKT6qEQjHhSVAoXbuR6jvv6FQj1/08BkZ672yCet3XeYQZM/Z8c80skRZi -HjQE6HblyGgKNOTMgRFlK+4tSm2zKlP7r27NNg3DvBCq18MbJkZ17Of9Uvf5irh0 -wztWdzCW/Y4gDzOkw6tIDZq1yUljlZhtDA5Re5pmDchxOY+EKnv3ILvBezIO7oga -rQ65/Xagr4JuO2zdASki6ajNXMPbTwaF8rR8MQIDAQABAoIBAQCY0cfL/oOocfoT -lw04igYdBQASkbdPZnS5oiIhBbG8GGSsUVLWvle1Amm2aBF/jSj3RUzwDzZNIT18 -rodXrIR7ZJNJECParZAfHATuSaUA/XHaMiGlsVbdu4ynfBZJJ9Dy9VJfilrTx2j8 -7H2PZTobLJTFsntCJfHU40De3MHmVuxRLU/b99uIgHihjk3iUibVh/lkapWtPgfk -s4z36H00UBMJY+SbjxRhJDP9dFZ7Sg9vcXiOU38Gq1NoPTp/lYlGWvMboakvDERt -bFrCUFseTq1LJ+mzua0dokiFo4Dsyzz5XmOTL//jYDjNMxDhTmeq1NXtSNIu43x/ -Ch1zOGdBAoGBAOK/BSp/UonxhVDuf1GN5M+RW/uOGB/eJEC997xSrR+gStv71ztq -Pz24W+R4a7ubNOZfXyWk03CzjzyWS2qxOdLwDaOhmRzgQndVyg8x2OITdbVYjDnD -QP2nc7NMJU4ezuxuWUo+HDYrfRlKBgTg/IYVpZ7V8gnjXsq6R21z6U9JAoGBAOgC -hRtlgICu0wahIha62CqoSfbOferMWCCj8niA/LZeWBW6/OCJZInM3FfdzMkybNEX -201tsIeeliYVa6IsvgYaFaiMJgvwtvAQdv9ukJ0+VUI3+TFzIHszgSufB/1aQPj4 -ReZoV3iZOApGeudSN4V6f+dB7agqwjQMtZNDtP2pAoGANlKXVUAdsSiozOPmos5A -1C26AMFhLDlXLB+W+4o/KcWISb3DKdvhfNLvSQREozSi7tJIhEdB1M1f8p77QHtn -JA8Y5Wvwt8dOhTKLbyp9EGSjHagyKCCMMHjusjT69wVQg7pIMA5DSgMPPIDMgly4 -gxMqk6wkCZRsgFsyg5lyeukCgYEA1JFCfRhhRQVoKOHHBsZHucWYhr0oFtEESVuM -kyWy5C/KSpaYi+y1pZ+BniuELi66DlTqQ6WlIIyHCvuDMwIFVDff8h392eDA63Ba -ZqtZaggrO1FnSgwuDVLiHSJGwrRHZRSrjm+4/LB87MUoY/orDmtu9mWsJfCPH/so -/XUCRYkCgYAj1Uf5k4iuRUuR910qYIpnBYqdO3UR+njn7F5mjDkoT0UqWofaLjo1 -fzjDuc58rTBJTixuy0hcdYZraK3NIQTswAOV2mmpBrJpK93dAqdHgdBdufojgRYM -coShlDKGd0MINh5GS0OBPnIIZiNkVr/F+s2ecwxqNUbb8MHj+aAJOA== ------END RSA PRIVATE KEY----- diff --git a/api/test/certs/mtls_client.pem b/api/test/certs/mtls_client.pem deleted file mode 100644 index 01fb62296f..0000000000 --- a/api/test/certs/mtls_client.pem +++ /dev/null @@ -1,25 +0,0 @@ ------BEGIN CERTIFICATE----- -MIIEQTCCAymgAwIBAgIUWdSswpGwJA//LV0Ui9PPKfvFuxQwDQYJKoZIhvcNAQEL -BQAwgawxCzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpDYWxpZm9ybmlhMRYwFAYDVQQH -Ew1TYW4gRnJhbmNpc2NvMSowKAYDVQQKEyFIb25lc3QgQWNobWVkJ3MgVXNlZCBD -ZXJ0aWZpY2F0ZXMxKTAnBgNVBAsTIEhhc3RpbHktR2VuZXJhdGVkIFZhbHVlcyBE -aXZpc29uMRkwFwYDVQQDExBBdXRvZ2VuZXJhdGVkIENBMCAXDTIxMDIwNTA4MTkw -MFoYDzIxMjEwMTEyMDgxOTAwWjBVMRUwEwYDVQQHEwx0aGUgaW50ZXJuZXQxFjAU -BgNVBAoTDWF1dG9nZW5lcmF0ZWQxFTATBgNVBAsTDGV0Y2QgY2x1c3RlcjENMAsG -A1UEAxMEZXRjZDCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAM1/WBIP -c5nP+OK2MuV2+LBBHjS2/gk+VZERXo9Cv/PzdevUZfcmcl5WvHBlUDDSZ3g4NG3N -iLwAabcPs00n6nwK2wjz6xvvmvaCVxyyzC+Eo9GbtFblIq24Ck+qhEIx4UlQKF27 -keo77+hUI9f9PAZGeu9sgnrd13mEGTP2fHPNLJEWYh40BOh25choCjTkzIERZSvu -LUptsypT+69uzTYNw7wQqtfDGyZGdezn/VL3+Yq4dMM7Vncwlv2OIA8zpMOrSA2a -tclJY5WYbQwOUXuaZg3IcTmPhCp79yC7wXsyDu6IGq0Ouf12oK+Cbjts3QEpIumo -zVzD208GhfK0fDECAwEAAaOBrjCBqzAOBgNVHQ8BAf8EBAMCBaAwHQYDVR0lBBYw -FAYIKwYBBQUHAwEGCCsGAQUFBwMCMAwGA1UdEwEB/wQCMAAwHQYDVR0OBBYEFAeJ -xZTNvenGwl5pS/wDwUUgTsRkMB8GA1UdIwQYMBaAFFo4iVhmqGSbUKe0+FrQjS5A -qwTyMCwGA1UdEQQlMCOCCWxvY2FsaG9zdIcEfwAAAYcECZFZeIcECZFZrYcECZFZ -4TANBgkqhkiG9w0BAQsFAAOCAQEAuTo5k2Ycg8zg4hU4QlNr5j/GJ9qegABjJ8W6 -9kGqbgjc3PyeKmdGRXpVJeH2AZPcHFWCMWlP+jJrB6HWaSJMOtNhuOh6Y2Hrb2I4 -ad815h/yC+tKHiE/uzaDK3bH3V6IQQTY38ay45O2bCWjt8pMT2LnCddF+rTXCAGX -fzAtHhNpBh615b/CGAZivMdnmxUcswfHghXjs5aVuV2qffyLoyBr+IFlzT+xbKF9 -9AF57B3hE28jqti8aa6HOaUkspohfEJzd9i9Y8GJuH1L6QZ0WIudISnX5FEpPxRr -5amq6pHoFrSeiJKpCX0zAz9Rv0mV6JkFvQL4fwVpfl5oOi6cpw== ------END CERTIFICATE----- diff --git a/api/test/certs/mtls_server-key.pem b/api/test/certs/mtls_server-key.pem deleted file mode 100644 index 5734e4ef4e..0000000000 --- a/api/test/certs/mtls_server-key.pem +++ /dev/null @@ -1,27 +0,0 @@ ------BEGIN RSA PRIVATE KEY----- -MIIEogIBAAKCAQEAw7tr4rado6XKq3ymvQHajaI93M9HyaWtcVWNOiXSBd0i69WQ -QwhugEkUw2yt+qXJzD06IqscFhR1PwzMITpf3ucjfxpO8CTTzgBaq9kW28ePbfR8 -aKp7t8H/CC8PwkFi/L4AjKT/5le4w0m5i3ZZgewTD8+f9pVzXlPXiGQF/o1SWjTG -8zfsYBQ02rK/HYszd6YdscM2NRlC1YWWUAp52v4ihEEeZS9p1o8lrSyXuMOvzRpf -lffu/izrVukwWAQ+YdIr98OOfWvqmabfANHGP4kofpti/JaCAtwFfgcgCY8QRdZm -BIT+r2TRebwMTXITZAqAq1/LMtuY89D9cP5X/QIDAQABAoIBAGQdDBylDVJz7Yrz -MhHAzfndv0ie2Pgh/unWOWtBhwAq0L7RuH0g5exF9RHUF9T5UZNeycqLvMzqX+IE -+LASPJE1pmlPmoqoO5HFipsVaeS2WP2DrNKYSLl/x6N29teEPE5MHNnTV3SI798r -aXUU7slOZ52RtB8a6CyaM8b2aj59QoxrLqDW5q9XU7OXSGAxuhTd9yofuRE3OCI8 -e6+u2FS7FE78+H8DLjAVYjY3yrBVJN6HrmGzfZC0N7dIYNkqy8n2qK3CK1S5RXj6 -3FNTLfKDQo+Sh8SHLZt7LZWJkc1SSRcfDTuiy4D2nSXijOh2tpF6FP8WdeJ/zveP -JQTxokECgYEA88KTomq01RXjt+YI6gBq0pT9lfy5/jVvI20n+Unr77TFDfXGqj5F -HaFQQgHdjPR/My4qYoJVNAp3iTR9wODpkX+QDxSCYANovoMt+z73WUL6nXNt7vqy -TLEWLinx4SO+vMwTnCXCxWfRV5Bs1EXzfQYPXo3gtuZrynyb4rPoGTECgYEAzY93 -skK2pPZGH5gOphjD1MW6nzaTYs335yRz5hQFsFCNP1aqPBi2fo6Gh6GMc7DFgy77 -f4tatCNnPQHU9HOtivo0WJcy6EU8cMvFq3al1dJx3ZnX/hOKfNubasVHCU9HtlNt -//UyLGu0skLRQ2p7Bz2WZcccWx/cpUDqRc2R+I0CgYAhpk+pER/rdn0cCtZaLzqP -3V9wUBYA4LF563ykLi8yxPqa5b3KDJSP9Y/VvNovtiTFFO9m7+UBLRy5RRTDBolX -u4tQeZ1R0cao3gT/9P5CRTvBdojLf7ITYjLUppesY7nV6DogyRmtFJrSgq5zU0C8 -lpSSkfVeakqhBjiiwAEfUQKBgGZ2O8isPlQtubhn1+1s7LgzMxnHX2Hhns8lOWwW -0NsY278VmNdJzjV5H4+ds9+63kjMc2oY8UZXW09qiVasDnX2z37VJvfmAwGKYOZd -xr21HzLBS4uG/AHOiUKIQSdf0DQOlAcAlljT+wbcDWkYO2jZhw0GWZkGYboxiFTw -6fDFAoGAG2CFVN/4jsXMecCRH+zW9SiyC8RlB4Apfh1B9dRkdM5X82FcByS4zo7K -0e9C+7fDyTEBuEks3xqaD5P6wdvGRXOQDmBRC7wzFYHwHnvPVpiXNA+ZsH9r7GQI -id15Aga1zbZoRktRr81+TtV5n2iFXIhvJhKIa62MTu6MSWP2pb4= ------END RSA PRIVATE KEY----- diff --git a/api/test/certs/mtls_server.pem b/api/test/certs/mtls_server.pem deleted file mode 100644 index 7bd91c69dc..0000000000 --- a/api/test/certs/mtls_server.pem +++ /dev/null @@ -1,25 +0,0 @@ ------BEGIN CERTIFICATE----- -MIIEQTCCAymgAwIBAgIUbq/7ubfAd7VqX/+knutmXICXCKswDQYJKoZIhvcNAQEL -BQAwgawxCzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpDYWxpZm9ybmlhMRYwFAYDVQQH -Ew1TYW4gRnJhbmNpc2NvMSowKAYDVQQKEyFIb25lc3QgQWNobWVkJ3MgVXNlZCBD -ZXJ0aWZpY2F0ZXMxKTAnBgNVBAsTIEhhc3RpbHktR2VuZXJhdGVkIFZhbHVlcyBE -aXZpc29uMRkwFwYDVQQDExBBdXRvZ2VuZXJhdGVkIENBMCAXDTIxMDIwNTA4MTkw -MFoYDzIxMjEwMTEyMDgxOTAwWjBVMRUwEwYDVQQHEwx0aGUgaW50ZXJuZXQxFjAU -BgNVBAoTDWF1dG9nZW5lcmF0ZWQxFTATBgNVBAsTDGV0Y2QgY2x1c3RlcjENMAsG -A1UEAxMEZXRjZDCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAMO7a+K2 -naOlyqt8pr0B2o2iPdzPR8mlrXFVjTol0gXdIuvVkEMIboBJFMNsrfqlycw9OiKr -HBYUdT8MzCE6X97nI38aTvAk084AWqvZFtvHj230fGiqe7fB/wgvD8JBYvy+AIyk -/+ZXuMNJuYt2WYHsEw/Pn/aVc15T14hkBf6NUlo0xvM37GAUNNqyvx2LM3emHbHD -NjUZQtWFllAKedr+IoRBHmUvadaPJa0sl7jDr80aX5X37v4s61bpMFgEPmHSK/fD -jn1r6pmm3wDRxj+JKH6bYvyWggLcBX4HIAmPEEXWZgSE/q9k0Xm8DE1yE2QKgKtf -yzLbmPPQ/XD+V/0CAwEAAaOBrjCBqzAOBgNVHQ8BAf8EBAMCBaAwHQYDVR0lBBYw -FAYIKwYBBQUHAwEGCCsGAQUFBwMCMAwGA1UdEwEB/wQCMAAwHQYDVR0OBBYEFHYc -Fgd9rBDEQ9t82v2JKo8JCA7VMB8GA1UdIwQYMBaAFFo4iVhmqGSbUKe0+FrQjS5A -qwTyMCwGA1UdEQQlMCOCCWxvY2FsaG9zdIcEfwAAAYcECZFZeIcECZFZrYcECZFZ -4TANBgkqhkiG9w0BAQsFAAOCAQEATeAhmtQpydjRmIo+3r6fvAHXi6BMZKjMrYQV -hkqakZ2mZfQXZB+AHLthc5ii4zBrB7buyYx8W4lqC7DW3vC8WrEP4fTOe7M+WbhB -cIyhCFufgs9xSiED5wWOxSfTNZBbXcOvvrOwfFF1KZvuJQWtHNWU5V3fz+uHTCZE -67YQgMdw+dfUl7EzdZKGqXD+BC7j0zGrJR9BlYnrMrDKxL1uZ5OZvySLnSCVjO5u -u2PCXE+VWUs+xtnDz8rIq0ETFe8Yt2CqHYJ14QvMl9oYE7Tkj0/xrtyRtRp8r0ZW -ox/FVX9OajzUZaUErwFNuz2Vej4tojlDtulbVinO9awySrhOjQ== ------END CERTIFICATE----- diff --git a/api/test/certs/test2.crt b/api/test/certs/test2.crt deleted file mode 100644 index 922a8f8b68..0000000000 --- a/api/test/certs/test2.crt +++ /dev/null @@ -1,28 +0,0 @@ ------BEGIN CERTIFICATE----- -MIIEsTCCAxmgAwIBAgIUMbgUUCYHkuKDaPy0bzZowlK0JG4wDQYJKoZIhvcNAQEL -BQAwVzELMAkGA1UEBhMCQ04xEjAQBgNVBAgMCUd1YW5nRG9uZzEPMA0GA1UEBwwG -Wmh1SGFpMQ8wDQYDVQQKDAZpcmVzdHkxEjAQBgNVBAMMCXRlc3QyLmNvbTAgFw0y -MDA0MDQyMjE3NTJaGA8yMTIwMDMxMTIyMTc1MlowVzELMAkGA1UEBhMCQ04xEjAQ -BgNVBAgMCUd1YW5nRG9uZzEPMA0GA1UEBwwGWmh1SGFpMQ8wDQYDVQQKDAZpcmVz -dHkxEjAQBgNVBAMMCXRlc3QyLmNvbTCCAaIwDQYJKoZIhvcNAQEBBQADggGPADCC -AYoCggGBAMQGBk35V3zaNVDWzEzVGd+EkZnUOrRpXQg5mmcnoKnrQ5rQQMsQCbMO -gFvLt/9OEZQmbE2HuEKsPzL79Yjdu8rGjSoQdbJZ9ccO32uvln1gn68iK79o7Tvm -TCi+BayyNA+lo9IxrBm1wGBkOU1ZPasGYzgBAbMLTSDps1EYxNR8t4l9PrTTRsh6 -NZyTYoDeVIsKZ9SckpjWVnxHOkF+AzZzIJJSe2pj572TDLYA/Xw9I4X3L+SHzwTl -iGWNXb2tU367LHERHvensQzdle7mQN2kE5GpB7QPWB+t9V4mn30jc/LyDvOaei6L -+pbl5CriGBTjaR80oXhK765K720BQeKUezri15bQlMaUGQRnzr53ZsqA4PEh6WCX -hUT2ibO32+uZFXzVQw8y/JUkPf76pZagi8DoLV+sfSbUtnpbQ8wyV2qqTM2eCuPi -RgUwXQi2WssKKzrqcgKil3vksHZozLtOmyZiNE4qfNxv+UGoIybJtZmB+9spY0Rw -5zBRuULycQIDAQABo3MwcTAdBgNVHQ4EFgQUCmZefzpizPrb3VbiIDhrA48ypB8w -HwYDVR0jBBgwFoAUCmZefzpizPrb3VbiIDhrA48ypB8wDAYDVR0TBAUwAwEB/zAh -BgNVHREEGjAYggl0ZXN0Mi5jb22CCyoudGVzdDIuY29tMA0GCSqGSIb3DQEBCwUA -A4IBgQA0nRTv1zm1ACugJFfYZfxZ0mLJfRUCFMmFfhy+vGiIu6QtnOFVw/tEOyMa -m78lBiqac15n3YWYiHiC5NFffTZ7XVlOjN2i4x2z2IJsHNa8tU80AX0Q/pizGK/d -+dzlcsGBb9MGT18h/B3/EYQFKLjUsr0zvDb1T0YDlRUsN3Bq6CvZmvfe9F7Yh4Z/ -XO5R+rX8w9c9A2jzM5isBw2qp/Ggn5RQodMwApEYkJdu80MuxaY6s3dssS4Ay8wP -VNFEeLcdauJ00ES1OnbnuNiYSiSMOgWBsnR+c8AaSRB/OZLYQQKGGYbq0tspwRjM -MGJRrI/jdKnvJQ8p02abdvA9ZuFChoD3Wg03qQ6bna68ZKPd9peBPpMrDDGDLkGI -NzZ6bLJKILnQkV6b1OHVnPDsKXfXjUTTNK/QLJejTXu9RpMBakYZMzs/SOSDtFlS -A+q25t6+46nvA8msUSBKyOGBX42mJcKvR4OgG44PfDjYfmjn2l+Dz/jNXDclpb+Q -XAzBnfM= ------END CERTIFICATE----- diff --git a/api/test/certs/test2.key b/api/test/certs/test2.key deleted file mode 100644 index c25d4e5bde..0000000000 --- a/api/test/certs/test2.key +++ /dev/null @@ -1,39 +0,0 @@ ------BEGIN RSA PRIVATE KEY----- -MIIG5QIBAAKCAYEAxAYGTflXfNo1UNbMTNUZ34SRmdQ6tGldCDmaZyegqetDmtBA -yxAJsw6AW8u3/04RlCZsTYe4Qqw/Mvv1iN27ysaNKhB1sln1xw7fa6+WfWCfryIr -v2jtO+ZMKL4FrLI0D6Wj0jGsGbXAYGQ5TVk9qwZjOAEBswtNIOmzURjE1Hy3iX0+ -tNNGyHo1nJNigN5Uiwpn1JySmNZWfEc6QX4DNnMgklJ7amPnvZMMtgD9fD0jhfcv -5IfPBOWIZY1dva1TfrsscREe96exDN2V7uZA3aQTkakHtA9YH631XiaffSNz8vIO -85p6Lov6luXkKuIYFONpHzSheErvrkrvbQFB4pR7OuLXltCUxpQZBGfOvndmyoDg -8SHpYJeFRPaJs7fb65kVfNVDDzL8lSQ9/vqllqCLwOgtX6x9JtS2eltDzDJXaqpM -zZ4K4+JGBTBdCLZayworOupyAqKXe+SwdmjMu06bJmI0Tip83G/5QagjJsm1mYH7 -2yljRHDnMFG5QvJxAgMBAAECggGBAIELlkruwvGmlULKpWRPReEn3NJwLNVoJ56q -jUMri1FRWAgq4PzNahU+jrHfwxmHw3rMcK/5kQwTaOefh1y63E35uCThARqQroSE -/gBeb6vKWFVrIXG5GbQ9QBXyQroV9r/2Q4q0uJ+UTzklwbNx9G8KnXbY8s1zuyrX -rvzMWYepMwqIMSfJjuebzH9vZ4F+3BlMmF4XVUrYj8bw/SDwXB0UXXT2Z9j6PC1J -CS0oKbgIZ8JhoF3KKjcHBGwWTIf5+byRxeG+z99PBEBafm1Puw1vLfOjD3DN/fso -8xCEtD9pBPBJ+W97x/U+10oKetmP1VVEr2Ph8+s2VH1zsRF5jo5d0GtvJqOwIQJ7 -z3OHJ7lLODw0KAjB1NRXW4dTTUDm6EUuUMWFkGAV6YTyhNLAT0DyrUFJck9RiY48 -3QN8vSf3n/+3wwg1gzcJ9w3W4DUbvGqu86CaUQ4UegfYJlusY/3YGp5bGNQdxmws -lgIoSRrHp6UJKsP8Yl08MIvT/oNLgQKBwQD75SuDeyE0ukhEp0t6v+22d18hfSef -q3lLWMI1SQR9Kiem9Z1KdRkIVY8ZAHANm6D8wgjOODT4QZtiqJd2BJn3Xf+aLfCd -CW0hPvmGTcp/E4sDZ2u0HbIrUStz7ZcgXpjD2JJAJGEKY2Z7J65gnTqbqoBDrw1q -1+FqtikkHRte1UqxjwnWBpSdoRQFgNPHxPWffhML1xsD9Pk1B1b7JoakYcKsNoQM -oXUKPLxSZEtd0hIydqmhGYTa9QWBPNDlA5UCgcEAxzfGbOrPBAOOYZd3jORXQI6p -H7SddTHMQyG04i+OWUd0HZFkK7/k6r26GFmImNIsQMB26H+5XoKRFKn+sUl14xHY -FwB140j0XSav2XzT38UpJ9CptbgK1eKGQVp41xwRYjHVScE5hJuA3a1TKM0l26rp -hny/KaP+tXuqt9QbxcUN6efubNYyFP+m6nq2/XdX74bJuGpXLq8W0oFdiocO6tmF -4/Hsc4dCVrcwULqXQa0lJ57zZpfIPARqWM2847xtAoHBANVUNbDpg6rTJMc34722 -dAy3NhL3mqooH9aG+hsEls+l9uT4WFipqSScyU8ERuHPbt0BO1Hi2kFx1rYMUBG8 -PeT4b7NUutVUGV8xpUNv+FH87Bta6CUnjTAQUzuf+QCJ/NjIPrwh0yloG2+roIvk -PLF/CZfI1hUpdZfZZChYmkiLXPHZURw4gH6q33j1rOYf0WFc9aZua0vDmZame6zB -6P+oZ6VPmi/UQXoFC/y/QfDYK18fjfOI2DJTlnDoX4XErQKBwGc3M5xMz/MRcJyJ -oIwj5jzxbRibOJV2tpD1jsU9xG/nQHbtVEwCgTVKFXf2M3qSMhFeZn0xZ7ZayZY+ -OVJbcDO0lBPezjVzIAB/Qc7aCOBAQ4F4b+VRtHN6iPqlSESTK0KH9Szgas+UzeCM -o7BZEctNMu7WBSkq6ZXXu+zAfZ8q6HmPDA3hsFMG3dFQwSxzv+C/IhZlKkRqvNVV -50QVk5oEF4WxW0PECY/qG6NH+YQylDSB+zPlYf4Of5cBCWOoxQKBwQCeo37JpEAR -kYtqSjXkC5GpPTz8KR9lCY4SDuC1XoSVCP0Tk23GX6GGyEf4JWE+fb/gPEFx4Riu -7pvxRwq+F3LaAa/FFTNUpY1+8UuiMO7J0B1RkVXkyJjFUF/aQxAnOoZPmzrdZhWy -bpe2Ka+JS/aXSd1WRN1nmo/DarpWFvdLWZFwUt6zMziH40o1gyPHEuXOqVtf2QCe -Q6WC9xnEz4lbb/fR2TF9QRA4FtoRpDe/f3ZGIpWE0RdwyZZ6uA7T1+Q= ------END RSA PRIVATE KEY----- diff --git a/api/test/docker-deploy/docker-compose.yaml b/api/test/docker-deploy/docker-compose.yaml deleted file mode 100644 index a78debd471..0000000000 --- a/api/test/docker-deploy/docker-compose.yaml +++ /dev/null @@ -1,66 +0,0 @@ -# -# Licensed to the Apache Software Foundation (ASF) under one or more -# contributor license agreements. See the NOTICE file distributed with -# this work for additional information regarding copyright ownership. -# The ASF licenses this file to You under the Apache License, Version 2.0 -# (the "License"); you may not use this file except in compliance with -# the License. You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -# -version: "3.6" - -services: - etcd: - image: gcr.io/etcd-development/etcd:v3.4.0 - ports: - - "2379:2379" - expose: - - 2379 - - 2380 - networks: - apisix_dashboard_e2e: - ipv4_address: 172.16.238.10 - environment: - - ETCDCTL_API=3 - command: - - /usr/local/bin/etcd - - --data-dir=/etcd-data - - --name - - node1 - - --initial-advertise-peer-urls - - http://172.16.238.10:2380 - - --listen-peer-urls - - http://0.0.0.0:2380 - - --advertise-client-urls - - http://172.16.238.10:2379 - - --listen-client-urls - - http://0.0.0.0:2379 - - managerapi: - image: dashboard:ci - restart: always - volumes: - - ../../conf/conf.yaml:/usr/local/apisix-dashboard/conf/conf.yaml:ro - depends_on: - - etcd - ports: - - '9000:9000/tcp' - networks: - apisix_dashboard_e2e: - ipv4_address: 172.16.238.40 - -networks: - apisix_dashboard_e2e: - driver: bridge - ipam: - driver: default - config: - - - subnet: 172.16.238.0/24 diff --git a/api/test/docker/Dockerfile b/api/test/docker/Dockerfile deleted file mode 100644 index a079d5ea99..0000000000 --- a/api/test/docker/Dockerfile +++ /dev/null @@ -1,61 +0,0 @@ -# -# Licensed to the Apache Software Foundation (ASF) under one or more -# contributor license agreements. See the NOTICE file distributed with -# this work for additional information regarding copyright ownership. -# The ASF licenses this file to You under the Apache License, Version 2.0 -# (the "License"); you may not use this file except in compliance with -# the License. You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -# - -FROM golang:1.19 AS build-env - -WORKDIR /go/src/github.com/apisix/manager-api - -COPY ./ ./ - -RUN mkdir -p /go/manager-api/conf \ - && go test -c -cover -covermode=atomic -o /go/manager-api/manager-api -coverpkg "./..." ./ \ - && mv /go/src/github.com/apisix/manager-api/entry.sh /go/manager-api/ \ - && mv /go/src/github.com/apisix/manager-api/conf/conf.yaml /go/manager-api/conf/conf.yaml \ - && mv /go/src/github.com/apisix/manager-api/conf/schema.json /go/manager-api/conf/schema.json \ - && mv /go/src/github.com/apisix/manager-api/conf/customize_schema.json /go/manager-api/conf/customize_schema.json \ - && rm -rf /go/src/github.com/apisix/manager-api \ - && rm -rf /etc/localtime \ - && ln -s /usr/share/zoneinfo/Hongkong /etc/localtime \ - && dpkg-reconfigure -f noninteractive tzdata - -RUN wget https://github.com/api7/dag-to-lua/archive/v1.1.tar.gz \ - && tar -zxvf v1.1.tar.gz \ - && mkdir -p /go/manager-api/dag-to-lua \ - && mv -u ./dag-to-lua-1.1/lib/* /go/manager-api/dag-to-lua/ - -FROM alpine:3.11 - -RUN mkdir -p /go/manager-api \ - && apk update \ - && apk add ca-certificates \ - && update-ca-certificates \ - && apk add --no-cache libc6-compat \ - && echo "hosts: files dns" > /etc/nsswitch.conf \ - && rm -rf /var/cache/apk/* - - -WORKDIR /go/manager-api -COPY --from=build-env /go/manager-api/ /go/manager-api/ -COPY --from=build-env /usr/share/zoneinfo/Hongkong /etc/localtime - -RUN mkdir logs - -EXPOSE 9000 - -RUN chmod +x ./entry.sh -ENTRYPOINT ["/go/manager-api/manager-api"] -CMD ["-test.coverprofile=./testdata/integrationcover.out"] diff --git a/api/test/docker/apisix_config2.yaml b/api/test/docker/apisix_config2.yaml deleted file mode 100644 index 3fcfee1985..0000000000 --- a/api/test/docker/apisix_config2.yaml +++ /dev/null @@ -1,40 +0,0 @@ -# -# Licensed to the Apache Software Foundation (ASF) under one or more -# contributor license agreements. See the NOTICE file distributed with -# this work for additional information regarding copyright ownership. -# The ASF licenses this file to You under the Apache License, Version 2.0 -# (the "License"); you may not use this file except in compliance with -# the License. You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -# -# If you want to set the specified configuration value, you can set the new -# in this file. For example if you want to specify the etcd address: -# - -deployment: - admin: - allow_admin: - - 0.0.0.0/0 - etcd: - host: - - "http://etcd:2379" - resync_delay: 0 - -apisix: - id: "apisix-server2" - -nginx_config: - error_log_level: "debug" - worker_processes: "1" - -plugin_attr: - server-info: - report_interval: 60 - report_ttl: 3600 diff --git a/api/test/docker/docker-compose.yaml b/api/test/docker/docker-compose.yaml deleted file mode 100644 index 40da710b84..0000000000 --- a/api/test/docker/docker-compose.yaml +++ /dev/null @@ -1,136 +0,0 @@ -# -# Licensed to the Apache Software Foundation (ASF) under one or more -# contributor license agreements. See the NOTICE file distributed with -# this work for additional information regarding copyright ownership. -# The ASF licenses this file to You under the Apache License, Version 2.0 -# (the "License"); you may not use this file except in compliance with -# the License. You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -# -version: "3.6" - -services: - etcd: - image: bitnami/etcd:3.5 - environment: - - ALLOW_NONE_AUTHENTICATION=yes - - ETCD_ADVERTISE_CLIENT_URLS=http://etcd:2379 - - ETCD_LISTEN_CLIENT_URLS=http://0.0.0.0:2379 - ports: - - "2379:2379" - networks: - apisix_dashboard_e2e: - ipv4_address: 172.16.238.10 - - upstream: - image: johz/upstream:v2.0 - restart: always - volumes: - - ./upstream.conf:/usr/local/openresty/nginx/conf/nginx.conf:ro - ports: - - '80:80/tcp' - - '1980:1980/tcp' - - '1981:1981/tcp' - - '1982:1982/tcp' - - '1983:1983/tcp' - - '1984:1984/tcp' - - '1991:1991/tcp' - - '1992:1992/udp' - networks: - apisix_dashboard_e2e: - ipv4_address: 172.16.238.20 - - upstream_grpc: - image: grpc_server_example - restart: always - ports: - - '50051:50051' - - '50052:50052' - networks: - apisix_dashboard_e2e: - ipv4_address: 172.16.238.21 - - upstream_httpbin: - image: kennethreitz/httpbin - networks: - apisix_dashboard_e2e: - ipv4_address: 172.16.238.22 - - apisix: - hostname: apisix_server1 - image: apache/apisix:3.0.0-debian - restart: always - volumes: - - ./apisix_config.yaml:/usr/local/apisix/conf/config.yaml:ro - - ./apisix_logs:/usr/local/apisix/logs - depends_on: - - etcd - ports: - - '9080:9080/tcp' - - '9090:9090/tcp' - - '9091:9091/tcp' - - '9443:9443/tcp' - - '9180:9180/tcp' - - '10090:10090/tcp' - - '10091:10091/tcp' - - '10092:10092/tcp' - - '10093:10093/tcp' - - '10095:10095/udp' - networks: - apisix_dashboard_e2e: - ipv4_address: 172.16.238.30 - - apisix2: - hostname: apisix_server2 - image: apache/apisix:3.0.0-debian - restart: always - volumes: - - ./apisix_config2.yaml:/usr/local/apisix/conf/config.yaml:ro - depends_on: - - etcd - ports: - - '9081:9080/tcp' - - '9181:9180/tcp' - networks: - apisix_dashboard_e2e: - ipv4_address: 172.16.238.31 - - managerapi: - build: - context: ../../ - dockerfile: test/docker/Dockerfile - restart: always - volumes: - - ../../conf/conf.yaml:/go/manager-api/conf/conf.yaml:ro - - ../testdata:/go/manager-api/testdata - depends_on: - - etcd - ports: - - '9000:9000/tcp' - networks: - apisix_dashboard_e2e: - ipv4_address: 172.16.238.40 - - keycloak: - image: jboss/keycloak:9.0.2 - environment: - - KEYCLOAK_USER=admin - - KEYCLOAK_PASSWORD=admin - - DB_VENDOR=h2 - ports: - - "8080:8080" - -networks: - apisix_dashboard_e2e: - driver: bridge - ipam: - driver: default - config: - - subnet: 172.16.238.0/24 diff --git a/api/test/docker/manager-api-conf.yaml b/api/test/docker/manager-api-conf.yaml deleted file mode 100644 index 0d987cc7dd..0000000000 --- a/api/test/docker/manager-api-conf.yaml +++ /dev/null @@ -1,42 +0,0 @@ -# -# Licensed to the Apache Software Foundation (ASF) under one or more -# contributor license agreements. See the NOTICE file distributed with -# this work for additional information regarding copyright ownership. -# The ASF licenses this file to You under the Apache License, Version 2.0 -# (the "License"); you may not use this file except in compliance with -# the License. You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -# -# TODO use the original conf/config.yaml + sed, replace of this file. - -conf: - listen: - host: 0.0.0.0 # `manager api` listening ip or host name. It's for e2e test, so it is set to 0.0.0.0 - port: 9000 # `manager api` listening port - etcd: - endpoints: # supports defining multiple etcd host addresses for an etcd cluster - - 172.16.238.10:2379 # ips here are defined in docker compose. - - 172.16.238.11:2379 - - 172.16.238.12:2379 - log: - error_log: # yamllint disable rule:comments-indentation - level: warn # supports levels, lower to higher: debug, info, warn, error, panic, fatal - file_path: logs/error.log # supports relative path, absolute path, standard output - # such as: logs/error.log, /tmp/logs/error.log, /dev/stdout, /dev/stderr -authentication: - secret: secret # secret for jwt token generation. - # *NOTE*: Highly recommended to modify this value to protect `manager api`. - # if it's default value, when `manager api` start, it will generate a random string to replace it. - expire_time: 3600 # jwt token expire time, in second - users: # yamllint enable rule:comments-indentation - - username: admin # username and password for login `manager api` - password: admin - - username: user - password: user diff --git a/api/test/docker/setup.sh b/api/test/docker/setup.sh deleted file mode 100755 index 680612259b..0000000000 --- a/api/test/docker/setup.sh +++ /dev/null @@ -1,157 +0,0 @@ -#!/usr/bin/env bash -# -# Licensed to the Apache Software Foundation (ASF) under one or more -# contributor license agreements. See the NOTICE file distributed with -# this work for additional information regarding copyright ownership. -# The ASF licenses this file to You under the Apache License, Version 2.0 -# (the "License"); you may not use this file except in compliance with -# the License. You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -# - -#Executes commands from the intended directory "/api/test/docker". -cd "$(dirname "$0")" - -main() { - #welcome message & check machine configuration - welcome - check - - if [ $# -eq 0 ]; then - help - give_up "\nNo arguments provided." - fi - - UPFLAG="-d" - UP=0 - for arg in "$@"; do - case "$arg" in - up | -u | -U) - UP=1 - ;; - --non-detach) - UPFLAG="" - ;; - down | -d | -D) - down - ;; - build | -b | -B) - build - ;; - help | -h | --help | -H) - help - ;; - *) - echo -e "Invalid Argument.\n" - help - give_up - ;; - esac - done - - if [ $UP -eq 1 ]; then - up "$UPFLAG" - fi - echo "Execution complete." -} - -check() { - set -e - - if ! docker --version &>/dev/null; then - give_up "The script depends on docker. Please proceed after the installation." - fi - - if ! docker-compose --version &>/dev/null; then - give_up "The script depends on docker-compose. Please proceed after the installation." - fi - - if ! sed --version &>/dev/null; then - give_up "The script depends on sed (GNU Stream Editor). Please proceed after the installation." - fi -} - -CONF_PATH=$(pwd | sed -e 's/test\/docker/conf\//g') -YAML_FILE="${CONF_PATH}conf.yaml" -BACKUP_FILE="${CONF_PATH}.backup.yaml" - -up() { - set -e - #creating backup of current config - if [ ! -f "$BACKUP_FILE" ]; then - cp "$YAML_FILE" "$BACKUP_FILE" - - #modifying config - sed -i 's/127.0.0.1:2379/172.16.238.10:2379/' "$YAML_FILE" - sed -i 's@127.0.0.1@0.0.0.0/0@' "$YAML_FILE" - sed -i '/172.16.238.10:2379/a\ - 172.16.238.11:2379' "$YAML_FILE" - sed -i '/172.16.238.10:2379/a\ - 172.16.238.12:2379' "$YAML_FILE" - sed -i 's@# - dubbo-proxy@- dubbo-proxy@' "$YAML_FILE" - - fi - - if [ ! -d "grpc-server-example" ]; then - wget https://github.com/api7/grpc_server_example/archive/refs/tags/20210819.tar.gz - mkdir grpc-server-example - tar -xzvf 20210819.tar.gz -C grpc-server-example - docker build -t grpc_server_example:latest ./grpc-server-example/grpc_server_example-20210819 - fi - - #Spinning up services - if [ -z "$1" ]; then - docker-compose up - else - docker-compose up "$1" - fi - - ../shell/wait_for_services.sh -} - -down() { - #restoring old configuration. - mv "$BACKUP_FILE" "$YAML_FILE" - - docker-compose down -v -} - -build() { - docker-compose build -} - -help() { - echo -e 'Usage: ./setup.sh [OPTIONS]...\n' - echo "Single argument is mandatory." - echo "Supported Command Arguments" - echo -e ' up,\t-u\t spins up all the services in detach mode.' - echo -e ' --non-detach\t pass the flag with "up / -u" to spin up in non-detach mode.' - echo -e ' down,\t-d\t stop containers & delete containers, networks, volumes.' - echo -e ' build,\t-b\t rebuild all the images from dockerfile.' - echo -e ' help,\t-h\t info about how to use this script.' - - echo -e '\nFull documentation at ' -} - -give_up() { - echo -e "$1" - exit 1 -} - -welcome() { - echo "WELCOME TO THE SETUP SCRIPT" - echo "It spins up a fleet of services required for local development." - echo "Capable of making the necessary changes in yaml file & revert back to original state." - echo "Services: " - echo "1. Three etcd nodes." - echo "2. Two apisix nodes." - echo -e "3. Single node of\n\t manager-api \n\t skywalking \n\t upstream-node \n\t upstream-grpc \n\t upstream-echo" - echo -e "=====================================================\n" -} - -main "$@" diff --git a/api/test/docker/upstream.conf b/api/test/docker/upstream.conf deleted file mode 100644 index 97e01c5ed8..0000000000 --- a/api/test/docker/upstream.conf +++ /dev/null @@ -1,113 +0,0 @@ -# -# Licensed to the Apache Software Foundation (ASF) under one or more -# contributor license agreements. See the NOTICE file distributed with -# this work for additional information regarding copyright ownership. -# The ASF licenses this file to You under the Apache License, Version 2.0 -# (the "License"); you may not use this file except in compliance with -# the License. You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -# - -pcre_jit on; - -events { - worker_connections 1024; -} - -http { - include mime.types; - default_type application/octet-stream; - - log_format main '$remote_addr - $remote_user [$time_local] $request "$status" $body_bytes_sent "$http_referer" "$http_user_agent" "$http_x_forwarded_for" "$request_time" "$upstream_response_time" '; - lua_package_path "/usr/local/openresty/nginx/conf/lua/?.lua;;"; - - sendfile on; - keepalive_timeout 65; - - server { - listen 80; - server_name localhost; - - access_log off; - - location / { - default_type text/html; - content_by_lua_block { - ngx.say(ngx.var.uri) - } - } - } - - server { - listen 1980; - listen 1981; - listen 1982; - listen 5044; - - server_tokens off; - - location / { - content_by_lua_block { - require("server").go() - } - - more_clear_headers Date; - } - } - - server { - listen 1983 ssl; - ssl_certificate cert/apisix.crt; - ssl_certificate_key cert/apisix.key; - lua_ssl_trusted_certificate cert/apisix.crt; - - server_tokens off; - - location / { - content_by_lua_block { - require("server").go() - } - - more_clear_headers Date; - } - } -} - -stream { - server { - listen 1991; - - content_by_lua_block { - local sock = ngx.req.socket(true) - while true do - local data = sock:receive(5) - if data then - sock:send("hello " .. data) - break - end - end - } - } - - server { - listen 1992 udp; - - content_by_lua_block { - local sock = ngx.req.socket() - while true do - local data = sock:receive(5) - if data then - sock:send("hello " .. data) - break - end - end - } - } -} diff --git a/api/test/e2e/auth/auth_suite_test.go b/api/test/e2e/auth/auth_suite_test.go deleted file mode 100644 index fff5c3c51c..0000000000 --- a/api/test/e2e/auth/auth_suite_test.go +++ /dev/null @@ -1,36 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one or more - * contributor license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright ownership. - * The ASF licenses this file to You under the Apache License, Version 2.0 - * (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package auth_test - -import ( - "testing" - "time" - - "github.com/apisix/manager-api/test/e2e/base" - . "github.com/onsi/ginkgo/v2" - . "github.com/onsi/gomega" -) - -func TestAuth(t *testing.T) { - RegisterFailHandler(Fail) - RunSpecs(t, "Auth Suite") -} - -var _ = BeforeSuite(func() { - base.CleanAllResource() - time.Sleep(base.SleepTime) -}) diff --git a/api/test/e2e/auth/authentication_test.go b/api/test/e2e/auth/authentication_test.go deleted file mode 100644 index 2db7ef8ccc..0000000000 --- a/api/test/e2e/auth/authentication_test.go +++ /dev/null @@ -1,56 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one or more - * contributor license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright ownership. - * The ASF licenses this file to You under the Apache License, Version 2.0 - * (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package auth_test - -import ( - "net/http" - - . "github.com/onsi/ginkgo/v2" - - "github.com/apisix/manager-api/test/e2e/base" -) - -var _ = Describe("Authentication", func() { - DescribeTable("test auth module", - func(tc base.HttpTestCase) { - base.RunTestCase(tc) - }, - Entry("Access with valid authentication token", base.HttpTestCase{ - Object: base.ManagerApiExpect(), - Method: http.MethodGet, - Path: "/apisix/admin/routes", - Headers: map[string]string{"Authorization": base.GetToken()}, - ExpectStatus: http.StatusOK, - ExpectBody: `"code":0`, - }), - Entry("Access with malformed authentication token", base.HttpTestCase{ - Object: base.ManagerApiExpect(), - Method: http.MethodGet, - Path: "/apisix/admin/routes", - Headers: map[string]string{"Authorization": "Not-A-Valid-Token"}, - ExpectStatus: http.StatusUnauthorized, - ExpectBody: `"message":"request unauthorized"`, - }), - Entry("Access without authentication token", base.HttpTestCase{ - Object: base.ManagerApiExpect(), - Method: http.MethodGet, - Path: "/apisix/admin/routes", - ExpectStatus: http.StatusUnauthorized, - ExpectBody: `"message":"request unauthorized"`, - }), - ) -}) diff --git a/api/test/e2e/balancer/balancer_suite_test.go b/api/test/e2e/balancer/balancer_suite_test.go deleted file mode 100644 index 2706e24f64..0000000000 --- a/api/test/e2e/balancer/balancer_suite_test.go +++ /dev/null @@ -1,35 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one or more - * contributor license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright ownership. - * The ASF licenses this file to You under the Apache License, Version 2.0 - * (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package balancer_test - -import ( - "testing" - "time" - - . "github.com/onsi/ginkgo/v2" - - "github.com/apisix/manager-api/test/e2e/base" -) - -func TestAuth(t *testing.T) { - RunSpecs(t, "Balancer Suite") -} - -var _ = BeforeSuite(func() { - base.CleanAllResource() - time.Sleep(base.SleepTime) -}) diff --git a/api/test/e2e/balancer/balancer_test.go b/api/test/e2e/balancer/balancer_test.go deleted file mode 100644 index 53614d471d..0000000000 --- a/api/test/e2e/balancer/balancer_test.go +++ /dev/null @@ -1,210 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one or more - * contributor license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright ownership. - * The ASF licenses this file to You under the Apache License, Version 2.0 - * (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package balancer_test - -import ( - "net/http" - "time" - - . "github.com/onsi/ginkgo/v2" - "github.com/stretchr/testify/assert" - - "github.com/apisix/manager-api/test/e2e/base" -) - -var _ = Describe("Balancer", func() { - DescribeTable("test create upstream and route", - func(tc base.HttpTestCase) { - base.RunTestCase(tc) - }, - Entry("create upstream (roundrobin with same weight)", base.HttpTestCase{ - Object: base.ManagerApiExpect(), - Method: http.MethodPut, - Path: "/apisix/admin/upstreams/1", - Body: `{ - "nodes": [{ - "host": "` + base.UpstreamIp + `", - "port": 1980, - "weight": 1 - }, - { - "host": "` + base.UpstreamIp + `", - "port": 1981, - "weight": 1 - }, - { - "host": "` + base.UpstreamIp + `", - "port": 1982, - "weight": 1 - }], - "type": "roundrobin" - }`, - Headers: map[string]string{"Authorization": base.GetToken()}, - ExpectStatus: http.StatusOK, - }), - Entry("create route using the upstream just created", base.HttpTestCase{ - Object: base.ManagerApiExpect(), - Method: http.MethodPut, - Path: "/apisix/admin/routes/1", - Body: `{ - "name": "route1", - "uri": "/server_port", - "upstream_id": "1" - }`, - Headers: map[string]string{"Authorization": base.GetToken()}, - ExpectStatus: http.StatusOK, - Sleep: base.SleepTime, - }), - ) - It("verify balancer by access count(same weight)", func() { - time.Sleep(base.SleepTime) - // batch test /server_port api - res := base.BatchTestServerPort(18, nil, "") - assert.Equal(GinkgoT(), 6, res["1980"]) - assert.Equal(GinkgoT(), 6, res["1981"]) - assert.Equal(GinkgoT(), 6, res["1982"]) - }) - - DescribeTable("test update upstream", - func(tc base.HttpTestCase) { - base.RunTestCase(tc) - }, - Entry("update upstream (roundrobin with different weight)", base.HttpTestCase{ - Object: base.ManagerApiExpect(), - Method: http.MethodPut, - Path: "/apisix/admin/upstreams/1", - Body: `{ - "nodes": [{ - "host": "` + base.UpstreamIp + `", - "port": 1980, - "weight": 1 - }, - { - "host": "` + base.UpstreamIp + `", - "port": 1981, - "weight": 2 - }, - { - "host": "` + base.UpstreamIp + `", - "port": 1982, - "weight": 3 - }], - "type": "roundrobin" - }`, - Headers: map[string]string{"Authorization": base.GetToken()}, - ExpectStatus: http.StatusOK, - }), - ) - It("verify balancer by access count(different weight)", func() { - time.Sleep(base.SleepTime) - // batch test /server_port api - res := base.BatchTestServerPort(18, nil, "") - assert.Equal(GinkgoT(), 3, res["1980"]) - assert.Equal(GinkgoT(), 6, res["1981"]) - assert.Equal(GinkgoT(), 9, res["1982"]) - }) - - DescribeTable("update upstream", - func(tc base.HttpTestCase) { - base.RunTestCase(tc) - }, - Entry("update upstream (roundrobin with weight 1 and 0)", base.HttpTestCase{ - Object: base.ManagerApiExpect(), - Method: http.MethodPut, - Path: "/apisix/admin/upstreams/1", - Body: `{ - "nodes": [{ - "host": "` + base.UpstreamIp + `", - "port": 1980, - "weight": 1 - }, - { - "host": "` + base.UpstreamIp + `", - "port": 1981, - "weight": 0 - }], - "type": "roundrobin" - }`, - Headers: map[string]string{"Authorization": base.GetToken()}, - ExpectStatus: http.StatusOK, - }), - ) - It("verify balancer by access count(weight 1 and 0)", func() { - time.Sleep(base.SleepTime) - // batch test /server_port api - res := base.BatchTestServerPort(18, nil, "") - assert.Equal(GinkgoT(), 18, res["1980"]) - }) - - DescribeTable("update upstream", - func(tc base.HttpTestCase) { - base.RunTestCase(tc) - }, - Entry("update upstream (roundrobin with weight only 1)", base.HttpTestCase{ - Object: base.ManagerApiExpect(), - Method: http.MethodPut, - Path: "/apisix/admin/upstreams/1", - Body: `{ - "nodes": [{ - "host": "` + base.UpstreamIp + `", - "port": 1980, - "weight": 1 - }], - "type": "roundrobin" - }`, - Headers: map[string]string{"Authorization": base.GetToken()}, - ExpectStatus: http.StatusOK, - }), - ) - It("verify balancer by access count(weight only 1)", func() { - time.Sleep(base.SleepTime) - // batch test /server_port api - res := base.BatchTestServerPort(18, nil, "") - assert.Equal(GinkgoT(), 18, res["1980"]) - }) - - Context("test balancer delete", func() { - It("delete route", func() { - base.RunTestCase(base.HttpTestCase{ - Object: base.ManagerApiExpect(), - Method: http.MethodDelete, - Path: "/apisix/admin/routes/1", - Headers: map[string]string{"Authorization": base.GetToken()}, - ExpectStatus: http.StatusOK, - }) - }) - It("delete upstream", func() { - base.RunTestCase(base.HttpTestCase{ - Object: base.ManagerApiExpect(), - Method: http.MethodDelete, - Path: "/apisix/admin/upstreams/1", - Headers: map[string]string{"Authorization": base.GetToken()}, - ExpectStatus: http.StatusOK, - }) - }) - It("hit the route just deleted", func() { - base.RunTestCase(base.HttpTestCase{ - Object: base.APISIXExpect(), - Method: http.MethodGet, - Path: "/server_port", - ExpectStatus: http.StatusNotFound, - ExpectBody: "{\"error_msg\":\"404 Route Not Found\"}\n", - Sleep: base.SleepTime, - }) - }) - }) -}) diff --git a/api/test/e2e/base/base.go b/api/test/e2e/base/base.go deleted file mode 100644 index 7fbaf8afd6..0000000000 --- a/api/test/e2e/base/base.go +++ /dev/null @@ -1,351 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one or more - * contributor license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright ownership. - * The ASF licenses this file to You under the Apache License, Version 2.0 - * (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package base - -import ( - "context" - "crypto/tls" - "fmt" - "io/ioutil" - "net" - "net/http" - "os/exec" - "strconv" - "strings" - "time" - - "github.com/gavv/httpexpect/v2" - . "github.com/onsi/ginkgo/v2" - "github.com/stretchr/testify/assert" - "github.com/tidwall/gjson" -) - -var ( - token string - - UpstreamIp = "upstream" - UpstreamGrpcIp = "upstream_grpc" - UpstreamHTTPBinIp = "upstream_httpbin" - APISIXHost = "http://127.0.0.1:9080" - APISIXAdminAPIHost = "http://127.0.0.1:9180" - APISIXInternalUrl = "http://apisix:9080" - APISIXSingleWorkerHost = "http://127.0.0.1:9081" - ManagerAPIHost = "http://127.0.0.1:9000" - PrometheusExporter = "http://127.0.0.1:9091" -) - -func GetToken() string { - if token != "" { - return token - } - - requestBody := `{ - "username": "admin", - "password": "admin" - }` - - url := ManagerAPIHost + "/apisix/admin/user/login" - body, _, err := HttpPost(url, nil, requestBody) - if err != nil { - panic(err) - } - - respond := gjson.ParseBytes(body) - token = respond.Get("data.token").String() - - return token -} - -func getTestingHandle() httpexpect.LoggerReporter { - return GinkgoT() -} - -func ManagerApiExpect() *httpexpect.Expect { - return httpexpect.New(GinkgoT(), ManagerAPIHost) -} - -func APISIXExpect() *httpexpect.Expect { - return httpexpect.New(GinkgoT(), APISIXHost) -} - -func APISIXAdminAPIExpect() *httpexpect.Expect { - return httpexpect.New(GinkgoT(), APISIXAdminAPIHost) -} - -func APISIXStreamProxyExpect(port uint16, sni string) *httpexpect.Expect { - if port == 0 { - port = 10090 - } - - if sni != "" { - addr := net.JoinHostPort(sni, strconv.Itoa(int(port))) - return httpexpect.WithConfig(httpexpect.Config{ - BaseURL: "https://" + addr, - Reporter: httpexpect.NewAssertReporter(GinkgoT()), - Client: &http.Client{ - Transport: &http.Transport{ - TLSClientConfig: &tls.Config{ - // accept any certificate; for testing only! - InsecureSkipVerify: true, - }, - DialContext: func(ctx context.Context, network, addr string) (net.Conn, error) { - addr = net.JoinHostPort("127.0.0.1", strconv.Itoa(int(port))) - dialer := &net.Dialer{} - return dialer.DialContext(ctx, network, addr) - }, - }, - }, - }) - } else { - return httpexpect.New(GinkgoT(), "http://"+net.JoinHostPort("127.0.0.1", strconv.Itoa(int(port)))) - } -} - -func PrometheusExporterExpect() *httpexpect.Expect { - return httpexpect.New(GinkgoT(), PrometheusExporter) -} - -func APISIXHTTPSExpect() *httpexpect.Expect { - e := httpexpect.WithConfig(httpexpect.Config{ - BaseURL: "https://www.test2.com:9443", - Reporter: httpexpect.NewAssertReporter(GinkgoT()), - Client: &http.Client{ - Transport: &http.Transport{ - TLSClientConfig: &tls.Config{ - // accept any certificate; for testing only! - InsecureSkipVerify: true, - }, - DialContext: func(ctx context.Context, network, addr string) (net.Conn, error) { - if addr == "www.test2.com:9443" { - addr = "127.0.0.1:9443" - } - dialer := &net.Dialer{} - return dialer.DialContext(ctx, network, addr) - }, - }, - }, - }) - - return e -} - -var SleepTime = 300 * time.Millisecond - -type HttpTestCase struct { - Desc string - Object *httpexpect.Expect - Method string - Path string - Query string - Body string - Headers map[string]string - Headers_test map[string]interface{} - ExpectStatus int - ExpectCode int - ExpectMessage string - ExpectBody interface{} - UnexpectBody interface{} - ExpectHeaders map[string]string - Sleep time.Duration //ms -} - -func RunTestCase(tc HttpTestCase) { - //init - expectObj := tc.Object - var req *httpexpect.Request - switch tc.Method { - case http.MethodGet: - req = expectObj.GET(tc.Path) - case http.MethodPut: - req = expectObj.PUT(tc.Path) - case http.MethodPost: - req = expectObj.POST(tc.Path) - case http.MethodDelete: - req = expectObj.DELETE(tc.Path) - case http.MethodPatch: - req = expectObj.PATCH(tc.Path) - case http.MethodOptions: - req = expectObj.OPTIONS(tc.Path) - default: - } - - if req == nil { - panic("fail to init request") - } - - if tc.Sleep != 0 { - time.Sleep(tc.Sleep) - } else { - time.Sleep(time.Duration(50) * time.Millisecond) - } - - if tc.Query != "" { - req.WithQueryString(tc.Query) - } - - // set header - setContentType := false - for key, val := range tc.Headers { - req.WithHeader(key, val) - if strings.ToLower(key) == "content-type" { - setContentType = true - } - } - - // set default content-type - if !setContentType { - req.WithHeader("Content-Type", "application/json") - } - - // set body - if tc.Body != "" { - req.WithText(tc.Body) - } - - // respond check - resp := req.Expect() - - // match http status - if tc.ExpectStatus != 0 { - resp.Status(tc.ExpectStatus) - } - - // match headers - if tc.ExpectHeaders != nil { - for key, val := range tc.ExpectHeaders { - resp.Header(key).Equal(val) - } - } - - // match body - if tc.ExpectBody != nil { - //assert.Contains(t, []string{"string", "[]string"}, reflect.TypeOf(tc.ExpectBody).String()) - if body, ok := tc.ExpectBody.(string); ok { - if body == "" { - // "" indicates the body is expected to be empty - resp.Body().Empty() - } else { - resp.Body().Contains(body) - } - } else if bodies, ok := tc.ExpectBody.([]string); ok && len(bodies) != 0 { - for _, b := range bodies { - resp.Body().Contains(b) - } - } - } - - // match UnexpectBody - if tc.UnexpectBody != nil { - //assert.Contains(t, []string{"string", "[]string"}, reflect.TypeOf(tc.UnexpectBody).String()) - if body, ok := tc.UnexpectBody.(string); ok { - // "" indicates the body is expected to be non empty - if body == "" { - resp.Body().NotEmpty() - } else { - resp.Body().NotContains(body) - } - } else if bodies, ok := tc.UnexpectBody.([]string); ok && len(bodies) != 0 { - for _, b := range bodies { - resp.Body().NotContains(b) - } - } - } -} - -func ReadAPISIXErrorLog() string { - cmd := exec.Command("pwd") - pwdByte, err := cmd.CombinedOutput() - pwd := string(pwdByte) - pwd = strings.Replace(pwd, "\n", "", 1) - pwd = pwd[:strings.Index(pwd, "/e2e")] - bytes, err := ioutil.ReadFile(pwd + "/docker/apisix_logs/error.log") - assert.Nil(GinkgoT(), err) - logContent := string(bytes) - - return logContent -} - -func CleanAPISIXErrorLog() { - cmd := exec.Command("pwd") - pwdByte, err := cmd.CombinedOutput() - pwd := string(pwdByte) - pwd = strings.Replace(pwd, "\n", "", 1) - pwd = pwd[:strings.Index(pwd, "/e2e")] - cmdStr := "echo | sudo tee " + pwd + "/docker/apisix_logs/error.log" - cmd = exec.Command("bash", "-c", cmdStr) - _, err = cmd.Output() - if err != nil { - fmt.Println("cmd error:", err.Error()) - } - assert.Nil(GinkgoT(), err) -} - -func GetResourceList(resource string) string { - body, _, err := HttpGet(ManagerAPIHost+"/apisix/admin/"+resource, map[string]string{"Authorization": GetToken()}) - assert.Nil(GinkgoT(), err) - return string(body) -} - -func CleanResource(resource string) { - resources := GetResourceList(resource) - list := gjson.Get(resources, "data.rows").Value().([]interface{}) - for _, item := range list { - resourceObj := item.(map[string]interface{}) - tc := HttpTestCase{ - Desc: "delete " + resource + "/" + resourceObj["id"].(string), - Object: ManagerApiExpect(), - Method: http.MethodDelete, - Path: "/apisix/admin/" + resource + "/" + resourceObj["id"].(string), - Headers: map[string]string{"Authorization": GetToken()}, - } - RunTestCase(tc) - } - time.Sleep(SleepTime) -} - -func CleanAllResource() { - CleanResource("routes") - CleanResource("upstreams") - CleanResource("consumers") - CleanResource("services") - CleanResource("global_rules") - CleanResource("plugin_configs") - CleanResource("proto") - CleanResource("ssl") - CleanResource("stream_routes") -} - -func RestartManagerAPI() { - e := exec.Command("docker", "restart", "docker_managerapi") - e.Run() -} - -var jwtToken string - -func GetJwtToken(userKey string) string { - if jwtToken != "" { - return jwtToken - } - time.Sleep(SleepTime) - - body, status, err := HttpGet(APISIXHost+"/apisix/plugin/jwt/sign?key="+userKey, nil) - assert.Nil(GinkgoT(), err) - assert.Equal(GinkgoT(), http.StatusOK, status) - jwtToken = string(body) - - return jwtToken -} diff --git a/api/test/e2e/base/http.go b/api/test/e2e/base/http.go deleted file mode 100644 index 7e14a68b76..0000000000 --- a/api/test/e2e/base/http.go +++ /dev/null @@ -1,150 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one or more - * contributor license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright ownership. - * The ASF licenses this file to You under the Apache License, Version 2.0 - * (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package base - -import ( - "bytes" - "encoding/json" - "io" - "io/ioutil" - "mime/multipart" - "net/http" - "net/url" - "os" - "path/filepath" - "strings" - - "github.com/stretchr/testify/assert" -) - -type UploadFile struct { - Name string - Filepath string -} - -func HttpGet(url string, headers map[string]string) ([]byte, int, error) { - return httpRequest(http.MethodGet, url, headers, "") -} - -func HttpDelete(url string, headers map[string]string) ([]byte, int, error) { - return httpRequest(http.MethodDelete, url, headers, "") -} - -func HttpPut(url string, headers map[string]string, reqBody string) ([]byte, int, error) { - return httpRequest(http.MethodPut, url, headers, reqBody) -} - -func HttpPost(url string, headers map[string]string, reqBody string) ([]byte, int, error) { - return httpRequest(http.MethodPost, url, headers, reqBody) -} - -func httpRequest(method, url string, headers map[string]string, reqBody string) ([]byte, int, error) { - var requestBody = new(bytes.Buffer) - if reqBody != "" { - requestBody = bytes.NewBuffer([]byte(reqBody)) - } - req, err := http.NewRequest(method, url, requestBody) - - if err != nil { - return nil, 0, err - } - req.Close = true - - // set header - for key, val := range headers { - req.Header.Add(key, val) - } - - client := &http.Client{} - resp, err := client.Do(req) - if err != nil { - return nil, 0, err - } - defer resp.Body.Close() - - body, err := ioutil.ReadAll(resp.Body) - if err != nil { - return nil, 0, err - } - - return body, resp.StatusCode, nil -} - -func BatchTestServerPort(times int, headers map[string]string, queryString string) map[string]int { - t := getTestingHandle() - url := APISIXSingleWorkerHost + "/server_port" - if queryString != "" { - url = url + "?" + queryString - } - - res := map[string]int{} - for i := 0; i < times; i++ { - bodyByte, status, err := HttpGet(url, headers) - assert.Nil(t, err) - assert.Equal(t, 200, status) - - body := string(bodyByte) - if _, ok := res[body]; !ok { - res[body] = 1 - } else { - res[body] += 1 - } - } - - return res -} - -func GetReader(reqParams map[string]string, contentType string, files []UploadFile) (io.Reader, string, error) { - if strings.Index(contentType, "json") > -1 { - bytesData, _ := json.Marshal(reqParams) - return bytes.NewReader(bytesData), contentType, nil - } - if files != nil { - body := &bytes.Buffer{} - writer := multipart.NewWriter(body) - for _, uploadFile := range files { - file, err := os.Open(uploadFile.Filepath) - if err != nil { - return nil, "", err - } - defer file.Close() - part, err := writer.CreateFormFile(uploadFile.Name, filepath.Base(uploadFile.Filepath)) - if err != nil { - return nil, "", err - } - _, err = io.Copy(part, file) - } - for k, v := range reqParams { - if err := writer.WriteField(k, v); err != nil { - return nil, "", err - } - } - if err := writer.Close(); err != nil { - return nil, "", err - } - return body, writer.FormDataContentType(), nil - } - - urlValues := url.Values{} - for key, val := range reqParams { - urlValues.Set(key, val) - } - - reqBody := urlValues.Encode() - - return strings.NewReader(reqBody), contentType, nil -} diff --git a/api/test/e2e/consumer/consumer_suite_test.go b/api/test/e2e/consumer/consumer_suite_test.go deleted file mode 100644 index b4c7e37d1e..0000000000 --- a/api/test/e2e/consumer/consumer_suite_test.go +++ /dev/null @@ -1,37 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one or more - * contributor license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright ownership. - * The ASF licenses this file to You under the Apache License, Version 2.0 - * (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package consumer_test - -import ( - "testing" - "time" - - . "github.com/onsi/ginkgo/v2" - . "github.com/onsi/gomega" - - "github.com/apisix/manager-api/test/e2e/base" -) - -func TestConsumer(t *testing.T) { - RegisterFailHandler(Fail) - RunSpecs(t, "Consumer Suite") -} - -var _ = BeforeSuite(func() { - base.CleanAllResource() - time.Sleep(base.SleepTime) -}) diff --git a/api/test/e2e/consumer/consumer_test.go b/api/test/e2e/consumer/consumer_test.go deleted file mode 100644 index 9a61915a1f..0000000000 --- a/api/test/e2e/consumer/consumer_test.go +++ /dev/null @@ -1,150 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one or more - * contributor license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright ownership. - * The ASF licenses this file to You under the Apache License, Version 2.0 - * (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package consumer_test - -import ( - "net/http" - - . "github.com/onsi/ginkgo/v2" - - "github.com/apisix/manager-api/test/e2e/base" -) - -var _ = Describe("Consumer", func() { - DescribeTable("test consumer curd", - func(tc base.HttpTestCase) { - base.RunTestCase(tc) - }, - Entry("create consumer", base.HttpTestCase{ - Object: base.ManagerApiExpect(), - Method: http.MethodPut, - Path: "/apisix/admin/consumers", - Body: `{ - "username": "consumer_1", - "plugins": { - "limit-count": { - "count": 2, - "time_window": 60, - "rejected_code": 503, - "key": "remote_addr", - "policy": "local" - } - }, - "desc": "test description" - }`, - Headers: map[string]string{"Authorization": base.GetToken()}, - ExpectStatus: http.StatusOK, - ExpectBody: []string{"\"code\":0", "\"username\":\"consumer_1\""}, - }), - Entry("get consumer #1", base.HttpTestCase{ - Object: base.ManagerApiExpect(), - Method: http.MethodGet, - Path: "/apisix/admin/consumers/consumer_1", - Headers: map[string]string{"Authorization": base.GetToken()}, - ExpectStatus: http.StatusOK, - ExpectBody: "\"username\":\"consumer_1\"", - }), - Entry("update consumer", base.HttpTestCase{ - Object: base.ManagerApiExpect(), - Method: http.MethodPut, - Path: "/apisix/admin/consumers/consumer_1", - Body: `{ - "username": "consumer_1", - "plugins": { - "limit-count": { - "count": 2, - "time_window": 60, - "rejected_code": 504, - "key": "remote_addr", - "policy": "local" - } - }, - "desc": "test description" - }`, - Headers: map[string]string{"Authorization": base.GetToken()}, - ExpectStatus: http.StatusOK, - ExpectBody: []string{"\"code\":0", "\"username\":\"consumer_1\"", "\"rejected_code\":504"}, - }), - Entry("get consumer #2", base.HttpTestCase{ - Object: base.ManagerApiExpect(), - Method: http.MethodGet, - Path: "/apisix/admin/consumers/consumer_1", - Headers: map[string]string{"Authorization": base.GetToken()}, - ExpectStatus: http.StatusOK, - ExpectBody: "\"rejected_code\":504", - }), - Entry("delete consumer", base.HttpTestCase{ - Object: base.ManagerApiExpect(), - Method: http.MethodDelete, - Path: "/apisix/admin/consumers/consumer_1", - Headers: map[string]string{"Authorization": base.GetToken()}, - ExpectStatus: http.StatusOK, - ExpectBody: "\"code\":0", - }), - ) - - DescribeTable("test consumer curd exception", - func(tc base.HttpTestCase) { - base.RunTestCase(tc) - }, - Entry("create consumer by POST method", base.HttpTestCase{ - Object: base.ManagerApiExpect(), - Method: http.MethodPost, - Path: "/apisix/admin/consumers", - Body: `{ - "username": "consumer_1", - "plugins": { - "limit-count": { - "count": 2, - "time_window": 60, - "rejected_code": 503, - "key": "remote_addr", - "policy": "local" - } - }, - "desc": "test description" - }`, - Headers: map[string]string{"Authorization": base.GetToken()}, - ExpectStatus: http.StatusNotFound, - ExpectBody: "404 page not found", - }), - Entry("create consumer with not exist plugin", base.HttpTestCase{ - Object: base.ManagerApiExpect(), - Method: http.MethodPut, - Path: "/apisix/admin/consumers", - Body: `{ - "username": "jack", - "plugins": { - "key-authnotexist": { - "key": "auth-one" - } - }, - "desc": "test description" - }`, - Headers: map[string]string{"Authorization": base.GetToken()}, - ExpectStatus: http.StatusBadRequest, - ExpectBody: "schema validate failed: schema not found, path: plugins.key-authnotexist", - }), - Entry("delete consumer (as delete not exist consumer)", base.HttpTestCase{ - Object: base.ManagerApiExpect(), - Method: http.MethodDelete, - Path: "/apisix/admin/consumers/test", - Headers: map[string]string{"Authorization": base.GetToken()}, - ExpectStatus: http.StatusNotFound, - }), - ) -}) diff --git a/api/test/e2e/consumer/consumer_with_labels_test.go b/api/test/e2e/consumer/consumer_with_labels_test.go deleted file mode 100644 index 9b52f8483e..0000000000 --- a/api/test/e2e/consumer/consumer_with_labels_test.go +++ /dev/null @@ -1,103 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one or more - * contributor license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright ownership. - * The ASF licenses this file to You under the Apache License, Version 2.0 - * (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package consumer_test - -import ( - "net/http" - - . "github.com/onsi/ginkgo/v2" - - "github.com/apisix/manager-api/test/e2e/base" -) - -var _ = DescribeTable("Consumer With Labels", - func(tc base.HttpTestCase) { - base.RunTestCase(tc) - }, - Entry("create the consumer", base.HttpTestCase{ - Object: base.ManagerApiExpect(), - Method: http.MethodPut, - Path: "/apisix/admin/consumers", - Body: `{ - "username": "jack", - "labels": { - "build":"16", - "env":"production", - "version":"v2" - }, - "plugins": { - "key-auth": { - "key": "auth-two" - } - }, - "desc": "test description" - }`, - Headers: map[string]string{"Authorization": base.GetToken()}, - ExpectStatus: http.StatusOK, - }), - Entry("verify the consumer", base.HttpTestCase{ - Object: base.ManagerApiExpect(), - Method: http.MethodGet, - Path: "/apisix/admin/consumers/jack", - Headers: map[string]string{"Authorization": base.GetToken()}, - ExpectStatus: http.StatusOK, - ExpectBody: "\"username\":\"jack\",\"desc\":\"test description\",\"plugins\":{\"key-auth\":{\"key\":\"auth-two\"}},\"labels\":{\"build\":\"16\",\"env\":\"production\",\"version\":\"v2\"}", - }), - Entry("create the route", base.HttpTestCase{ - Object: base.ManagerApiExpect(), - Method: http.MethodPut, - Path: "/apisix/admin/routes/r1", - Body: `{ - "name": "route1", - "uri": "/hello", - "plugins": { - "key-auth": {} - }, - "upstream": { - "type": "roundrobin", - "nodes": [{ - "host": "` + base.UpstreamIp + `", - "port": 1980, - "weight": 1 - }] - } - }`, - Headers: map[string]string{"Authorization": base.GetToken()}, - ExpectStatus: http.StatusOK, - }), - Entry("hit route", base.HttpTestCase{ - Object: base.APISIXExpect(), - Method: http.MethodGet, - Path: "/hello", - Headers: map[string]string{"apikey": "auth-two"}, - ExpectStatus: http.StatusOK, - }), - Entry("delete consumer", base.HttpTestCase{ - Object: base.ManagerApiExpect(), - Method: http.MethodDelete, - Path: "/apisix/admin/consumers/jack", - Headers: map[string]string{"Authorization": base.GetToken()}, - ExpectStatus: http.StatusOK, - }), - Entry("delete route", base.HttpTestCase{ - Object: base.ManagerApiExpect(), - Method: http.MethodDelete, - Path: "/apisix/admin/routes/r1", - Headers: map[string]string{"Authorization": base.GetToken()}, - ExpectStatus: http.StatusOK, - }), -) diff --git a/api/test/e2e/consumer/consumer_with_plugin_key_auth_test.go b/api/test/e2e/consumer/consumer_with_plugin_key_auth_test.go deleted file mode 100644 index b53ec63681..0000000000 --- a/api/test/e2e/consumer/consumer_with_plugin_key_auth_test.go +++ /dev/null @@ -1,118 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one or more - * contributor license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright ownership. - * The ASF licenses this file to You under the Apache License, Version 2.0 - * (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package consumer_test - -import ( - "net/http" - - . "github.com/onsi/ginkgo/v2" - - "github.com/apisix/manager-api/test/e2e/base" -) - -var _ = DescribeTable("Consumer With key-auth", - func(tc base.HttpTestCase) { - base.RunTestCase(tc) - }, - Entry("create route", base.HttpTestCase{ - Object: base.ManagerApiExpect(), - Method: http.MethodPut, - Path: "/apisix/admin/routes/r1", - Body: `{ - "name": "route1", - "uri": "/hello", - "plugins": { - "key-auth": {} - }, - "upstream": { - "type": "roundrobin", - "nodes": [{ - "host": "` + base.UpstreamIp + `", - "port": 1980, - "weight": 1 - }] - } - }`, - Headers: map[string]string{"Authorization": base.GetToken()}, - ExpectStatus: http.StatusOK, - }), - Entry("hit route without apikey", base.HttpTestCase{ - Object: base.APISIXExpect(), - Method: http.MethodGet, - Path: "/hello", - ExpectStatus: http.StatusUnauthorized, - ExpectBody: "Missing API key found in request", - Sleep: base.SleepTime * 2, - }), - Entry("create consumer", base.HttpTestCase{ - Object: base.ManagerApiExpect(), - Method: http.MethodPut, - Path: "/apisix/admin/consumers", - Body: `{ - "username": "jack", - "plugins": { - "key-auth": { - "key": "auth-one" - } - }, - "desc": "test description" - }`, - Headers: map[string]string{"Authorization": base.GetToken()}, - ExpectStatus: http.StatusOK, - }), - Entry("hit route with correct apikey", base.HttpTestCase{ - Object: base.APISIXExpect(), - Method: http.MethodGet, - Path: "/hello", - Headers: map[string]string{"apikey": "auth-one"}, - ExpectStatus: http.StatusOK, - ExpectBody: "hello world", - Sleep: base.SleepTime * 2, - }), - Entry("hit route with incorrect apikey", base.HttpTestCase{ - Object: base.APISIXExpect(), - Method: http.MethodGet, - Path: "/hello", - Headers: map[string]string{"apikey": "auth-new"}, - ExpectStatus: http.StatusUnauthorized, - ExpectBody: "Invalid API key in request", - Sleep: base.SleepTime * 2, - }), - Entry("delete consumer", base.HttpTestCase{ - Object: base.ManagerApiExpect(), - Method: http.MethodDelete, - Path: "/apisix/admin/consumers/jack", - Headers: map[string]string{"Authorization": base.GetToken()}, - ExpectStatus: http.StatusOK, - }), - Entry("hit route (consumer deleted)", base.HttpTestCase{ - Object: base.APISIXExpect(), - Method: http.MethodGet, - Path: "/hello", - Headers: map[string]string{"apikey": "auth-one"}, - ExpectStatus: http.StatusUnauthorized, - ExpectBody: "Missing related consumer", - Sleep: base.SleepTime * 2, - }), - Entry("delete route", base.HttpTestCase{ - Object: base.ManagerApiExpect(), - Method: http.MethodDelete, - Path: "/apisix/admin/routes/r1", - Headers: map[string]string{"Authorization": base.GetToken()}, - ExpectStatus: http.StatusOK, - }), -) diff --git a/api/test/e2e/data_loader/data_loader_suite_test.go b/api/test/e2e/data_loader/data_loader_suite_test.go deleted file mode 100644 index 94199093f7..0000000000 --- a/api/test/e2e/data_loader/data_loader_suite_test.go +++ /dev/null @@ -1,38 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one or more - * contributor license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright ownership. - * The ASF licenses this file to You under the Apache License, Version 2.0 - * (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package data_loader_test - -import ( - "testing" - "time" - - . "github.com/onsi/ginkgo/v2" - . "github.com/onsi/gomega" - - "github.com/apisix/manager-api/test/e2e/base" -) - -func TestDataLoader(t *testing.T) { - RegisterFailHandler(Fail) - RunSpecs(t, "Data Loader Suite") -} - -var _ = BeforeSuite(func() { - base.CleanAllResource() - base.RestartManagerAPI() - time.Sleep(base.SleepTime) -}) diff --git a/api/test/e2e/data_loader/openapi3_test.go b/api/test/e2e/data_loader/openapi3_test.go deleted file mode 100644 index 4f28cfee49..0000000000 --- a/api/test/e2e/data_loader/openapi3_test.go +++ /dev/null @@ -1,322 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one or more - * contributor license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright ownership. - * The ASF licenses this file to You under the Apache License, Version 2.0 - * (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package data_loader_test - -import ( - "encoding/json" - "net/http" - "path/filepath" - - . "github.com/onsi/ginkgo/v2" - . "github.com/onsi/gomega" - "github.com/savsgio/gotils/bytes" - "github.com/tidwall/gjson" - - "github.com/apisix/manager-api/test/e2e/base" -) - -var _ = Describe("OpenAPI 3", func() { - DescribeTable("Import cases", - func(f func()) { - f() - }, - Entry("default.yaml", func() { - path, err := filepath.Abs("../../testdata/import/default.yaml") - Expect(err).To(BeNil()) - - req := base.ManagerApiExpect().POST("/apisix/admin/import/routes") - req.WithMultipart().WithForm(map[string]string{ - "type": "openapi3", - "task_name": "test_default_yaml", - "_file": "default.yaml", - }) - req.WithMultipart().WithFile("file", path) - req.WithHeader("Authorization", base.GetToken()) - resp := req.Expect() - resp.Status(http.StatusOK) - r := gjson.ParseBytes([]byte(resp.Body().Raw())) - - Expect(r.Get("code").Uint()).To(Equal(uint64(0))) - - r = r.Get("data") - for s, result := range r.Map() { - if s == "route" { - Expect(result.Get("total").Uint()).To(Equal(uint64(1))) - Expect(result.Get("failed").Uint()).To(Equal(uint64(0))) - } - } - }), - Entry("default.json", func() { - path, err := filepath.Abs("../../testdata/import/default.json") - Expect(err).To(BeNil()) - - req := base.ManagerApiExpect().POST("/apisix/admin/import/routes") - req.WithMultipart().WithForm(map[string]string{ - "type": "openapi3", - "task_name": "test_default_json", - "_file": "default.json", - }) - req.WithMultipart().WithFile("file", path) - req.WithHeader("Authorization", base.GetToken()) - resp := req.Expect() - resp.Status(http.StatusOK) - r := gjson.ParseBytes([]byte(resp.Body().Raw())) - - Expect(r.Get("code").Uint()).To(Equal(uint64(0))) - - r = r.Get("data") - for s, result := range r.Map() { - if s == "route" { - Expect(result.Get("total").Uint()).To(Equal(uint64(1))) - Expect(result.Get("failed").Uint()).To(Equal(uint64(0))) - } - } - }), - Entry("Postman-API101.yaml merge method", func() { - path, err := filepath.Abs("../../testdata/import/Postman-API101.yaml") - Expect(err).To(BeNil()) - - req := base.ManagerApiExpect().POST("/apisix/admin/import/routes") - req.WithMultipart().WithForm(map[string]string{ - "type": "openapi3", - "task_name": "test_postman_api101_yaml_mm", - "_file": "Postman-API101.yaml", - "merge_method": "true", - }) - req.WithMultipart().WithFile("file", path) - req.WithHeader("Authorization", base.GetToken()) - resp := req.Expect() - resp.Status(http.StatusOK) - r := gjson.ParseBytes([]byte(resp.Body().Raw())) - - Expect(r.Get("code").Uint()).To(Equal(uint64(0))) - - r = r.Get("data") - for s, result := range r.Map() { - if s == "route" { - Expect(result.Get("total").Uint()).To(Equal(uint64(3))) - Expect(result.Get("failed").Uint()).To(Equal(uint64(0))) - } - if s == "upstream" { - Expect(result.Get("total").Uint()).To(Equal(uint64(1))) - Expect(result.Get("failed").Uint()).To(Equal(uint64(0))) - } - } - }), - Entry("Postman-API101.yaml non-merge method", func() { - // clean routes - base.CleanResource("routes") - path, err := filepath.Abs("../../testdata/import/Postman-API101.yaml") - Expect(err).To(BeNil()) - - req := base.ManagerApiExpect().POST("/apisix/admin/import/routes") - req.WithMultipart().WithForm(map[string]string{ - "type": "openapi3", - "task_name": "test_postman_api101_yaml_nmm", - "_file": "Postman-API101.yaml", - "merge_method": "false", - }) - req.WithMultipart().WithFile("file", path) - req.WithHeader("Authorization", base.GetToken()) - resp := req.Expect() - resp.Status(http.StatusOK) - r := gjson.ParseBytes([]byte(resp.Body().Raw())) - - Expect(r.Get("code").Uint()).To(Equal(uint64(0))) - - r = r.Get("data") - for s, result := range r.Map() { - if s == "route" { - Expect(result.Get("total").Uint()).To(Equal(uint64(5))) - Expect(result.Get("failed").Uint()).To(Equal(uint64(0))) - } - if s == "upstream" { - Expect(result.Get("total").Uint()).To(Equal(uint64(1))) - Expect(result.Get("failed").Uint()).To(Equal(uint64(0))) - } - } - }), - Entry("Clean resources", func() { - base.CleanResource("routes") - base.CleanResource("upstreams") - base.CleanResource("services") - }), - ) - DescribeTable("Exception cases", - func(f func()) { - f() - }, - Entry("Empty upload file", func() { - req := base.ManagerApiExpect().POST("/apisix/admin/import/routes") - req.WithMultipart().WithForm(map[string]string{ - "type": "openapi3", - "task_name": "empty_upload", - "_file": "default.yaml", - }) - req.WithHeader("Authorization", base.GetToken()) - resp := req.Expect() - resp.Status(http.StatusOK) - r := gjson.ParseBytes([]byte(resp.Body().Raw())) - - Expect(r.Get("code").Uint()).To(Equal(uint64(10000))) - Expect(r.Get("message").String()).To(Equal("uploaded file is empty")) - }), - Entry("Exceed limit upload file", func() { - req := base.ManagerApiExpect().POST("/apisix/admin/import/routes") - req.WithMultipart().WithForm(map[string]string{ - "type": "openapi3", - "task_name": "exceed_limit_upload", - "_file": "default.yaml", - }) - - req.WithMultipart().WithFileBytes("file", "default.yaml", bytes.Rand(make([]byte, 10*1024*1024+1))) - req.WithHeader("Authorization", base.GetToken()) - resp := req.Expect() - resp.Status(http.StatusOK) - r := gjson.ParseBytes([]byte(resp.Body().Raw())) - - Expect(r.Get("code").Uint()).To(Equal(uint64(10000))) - Expect(r.Get("message").String()).To(Equal("uploaded file size exceeds the limit, limit is 10485760")) - }), - Entry("Routes duplicate #1", func() { - path, err := filepath.Abs("../../testdata/import/Postman-API101.yaml") - Expect(err).To(BeNil()) - req := base.ManagerApiExpect().POST("/apisix/admin/import/routes") - req.WithMultipart().WithForm(map[string]string{ - "type": "openapi3", - "task_name": "duplicate", - "_file": "Postman-API101.yaml", - "merge_method": "true", - }) - req.WithMultipart().WithFile("file", path) - req.WithHeader("Authorization", base.GetToken()) - resp := req.Expect() - resp.Status(http.StatusOK) - r := gjson.ParseBytes([]byte(resp.Body().Raw())) - Expect(r.Get("code").Uint()).To(Equal(uint64(0))) - }), - Entry("Route duplicate #2", func() { - path, err := filepath.Abs("../../testdata/import/Postman-API101.yaml") - Expect(err).To(BeNil()) - req := base.ManagerApiExpect().POST("/apisix/admin/import/routes") - req.WithMultipart().WithForm(map[string]string{ - "type": "openapi3", - "task_name": "duplicate", - "_file": "Postman-API101.yaml", - "merge_method": "true", - }) - req.WithMultipart().WithFile("file", path) - req.WithHeader("Authorization", base.GetToken()) - resp := req.Expect() - resp.Status(http.StatusOK) - r := gjson.ParseBytes([]byte(resp.Body().Raw())) - Expect(r.Get("code").Uint()).To(Equal(uint64(0))) - Expect(r.Get("data").Map()["route"].Get("failed").Uint()).To(Equal(uint64(1))) - Expect(r.Get("data").Map()["route"].Get("errors").Array()[0].String()). - To(ContainSubstring("is duplicated with route duplicate_")) - - }), - Entry("Clean resources", func() { - base.CleanResource("routes") - base.CleanResource("upstreams") - base.CleanResource("services") - }), - ) - DescribeTable("Real API cases", - func(f func()) { - f() - }, - Entry("Import httpbin.org YAML", func() { - path, err := filepath.Abs("../../testdata/import/httpbin.yaml") - Expect(err).To(BeNil()) - - req := base.ManagerApiExpect().POST("/apisix/admin/import/routes") - req.WithMultipart().WithForm(map[string]string{ - "type": "openapi3", - "task_name": "httpbin", - "_file": "httpbin.yaml", - }) - req.WithMultipart().WithFile("file", path) - req.WithHeader("Authorization", base.GetToken()) - resp := req.Expect() - resp.Status(http.StatusOK) - r := gjson.ParseBytes([]byte(resp.Body().Raw())) - - Expect(r.Get("code").Uint()).To(Equal(uint64(0))) - - r = r.Get("data") - for s, result := range r.Map() { - if s == "route" { - Expect(result.Get("total").Uint()).To(Equal(uint64(1))) - Expect(result.Get("failed").Uint()).To(Equal(uint64(0))) - } - } - }), - Entry("Modify upstream", func() { - body := make(map[string]interface{}) - body["nodes"] = []map[string]interface{}{ - { - "host": "httpbin.org", - "port": 80, - "weight": 1, - }, - } - body["type"] = "roundrobin" - _body, err := json.Marshal(body) - Expect(err).To(BeNil()) - base.RunTestCase(base.HttpTestCase{ - Object: base.ManagerApiExpect(), - Method: http.MethodPatch, - Path: "/apisix/admin/upstreams/httpbin", - Body: string(_body), - Headers: map[string]string{"Authorization": base.GetToken()}, - ExpectStatus: http.StatusOK, - }) - }), - Entry("Enable route", func() { - // get route id - req := base.ManagerApiExpect().GET("/apisix/admin/routes") - req.WithHeader("Authorization", base.GetToken()) - resp := req.Expect() - resp.Status(http.StatusOK) - r := gjson.ParseBytes([]byte(resp.Body().Raw())) - Expect(r.Get("code").Uint()).To(Equal(uint64(0))) - id := r.Get("data").Get("rows").Array()[0].Get("id").String() - - body := make(map[string]interface{}) - body["status"] = 1 - _body, err := json.Marshal(body) - Expect(err).To(BeNil()) - base.RunTestCase(base.HttpTestCase{ - Object: base.ManagerApiExpect(), - Method: http.MethodPatch, - Path: "/apisix/admin/routes/" + id, - Body: string(_body), - Headers: map[string]string{"Authorization": base.GetToken()}, - ExpectStatus: http.StatusOK, - Sleep: base.SleepTime, - }) - }), - Entry("Request API", func() { - req := base.APISIXExpect().GET("/get") - resp := req.Expect() - resp.Status(http.StatusOK) - r := gjson.ParseBytes([]byte(resp.Body().Raw())) - Expect(r.Get("headers").Get("User-Agent").String()).To(Equal("Go-http-client/1.1")) - }), - ) -}) diff --git a/api/test/e2e/global_rule/global_rule_suite_test.go b/api/test/e2e/global_rule/global_rule_suite_test.go deleted file mode 100644 index 5d0693c430..0000000000 --- a/api/test/e2e/global_rule/global_rule_suite_test.go +++ /dev/null @@ -1,37 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one or more - * contributor license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright ownership. - * The ASF licenses this file to You under the Apache License, Version 2.0 - * (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package global_rule_test - -import ( - "testing" - "time" - - . "github.com/onsi/ginkgo/v2" - . "github.com/onsi/gomega" - - "github.com/apisix/manager-api/test/e2e/base" -) - -func TestGlobalRule(t *testing.T) { - RegisterFailHandler(Fail) - RunSpecs(t, "Global Rule Suite") -} - -var _ = BeforeSuite(func() { - base.CleanAllResource() - time.Sleep(base.SleepTime) -}) diff --git a/api/test/e2e/global_rule/global_rule_test.go b/api/test/e2e/global_rule/global_rule_test.go deleted file mode 100644 index 65d0c2004d..0000000000 --- a/api/test/e2e/global_rule/global_rule_test.go +++ /dev/null @@ -1,200 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one or more - * contributor license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright ownership. - * The ASF licenses this file to You under the Apache License, Version 2.0 - * (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package global_rule_test - -import ( - "net/http" - - . "github.com/onsi/ginkgo/v2" - - "github.com/apisix/manager-api/test/e2e/base" -) - -var _ = Describe("Global Rule", func() { - var _ = DescribeTable("test global rule curd", - func(tc base.HttpTestCase) { - base.RunTestCase(tc) - }, - Entry("create global rule", base.HttpTestCase{ - Object: base.ManagerApiExpect(), - Method: http.MethodPut, - Path: "/apisix/admin/global_rules/1", - Body: `{ - "plugins": { - "response-rewrite": { - "headers": { - "X-VERSION":"1.0" - } - } - } - }`, - Headers: map[string]string{"Authorization": base.GetToken()}, - ExpectStatus: http.StatusOK, - ExpectBody: "\"X-VERSION\":\"1.0\"", - }), - Entry("full update global rule", base.HttpTestCase{ - Object: base.ManagerApiExpect(), - Method: http.MethodPut, - Path: "/apisix/admin/global_rules/1", - Body: `{ - "plugins": { - "response-rewrite": { - "headers": { - "X-TEST":"1.0" - } - } - } - }`, - Headers: map[string]string{"Authorization": base.GetToken()}, - ExpectStatus: http.StatusOK, - ExpectBody: "\"X-TEST\":\"1.0\"", - }), - Entry("partial update global rule", base.HttpTestCase{ - Object: base.ManagerApiExpect(), - Method: http.MethodPatch, - Path: "/apisix/admin/global_rules/1/plugins", - Body: `{ - "response-rewrite": { - "headers": { - "X-VERSION":"1.0" - } - }, - "key-auth": {} - }`, - Headers: map[string]string{"Authorization": base.GetToken()}, - ExpectStatus: http.StatusOK, - ExpectBody: "\"key-auth\":{}", - }), - Entry("delete global rule", base.HttpTestCase{ - Object: base.ManagerApiExpect(), - Method: http.MethodDelete, - Path: "/apisix/admin/global_rules/1", - Headers: map[string]string{"Authorization": base.GetToken()}, - ExpectStatus: http.StatusOK, - }), - ) - - var _ = DescribeTable("test global rule integration", - func(tc base.HttpTestCase) { - base.RunTestCase(tc) - }, - Entry("create route", base.HttpTestCase{ - Object: base.ManagerApiExpect(), - Method: http.MethodPut, - Path: "/apisix/admin/routes/r1", - Body: `{ - "name": "route1", - "uri": "/hello", - "upstream": { - "type": "roundrobin", - "nodes": [{ - "host": "` + base.UpstreamIp + `", - "port": 1981, - "weight": 1 - }] - } - }`, - Headers: map[string]string{"Authorization": base.GetToken()}, - ExpectStatus: http.StatusOK, - }), - Entry("create global rule", base.HttpTestCase{ - Object: base.ManagerApiExpect(), - Method: http.MethodPut, - Path: "/apisix/admin/global_rules/1", - Body: `{ - "plugins": { - "response-rewrite": { - "headers": { - "X-VERSION":"1.0" - } - }, - "uri-blocker": { - "block_rules": ["select.+(from|limit)", "(?:(union(.*?)select))"] - } - } - }`, - Headers: map[string]string{"Authorization": base.GetToken()}, - ExpectStatus: http.StatusOK, - ExpectBody: "\"X-VERSION\":\"1.0\"", - }), - Entry("verify route with header", base.HttpTestCase{ - Object: base.APISIXExpect(), - Method: http.MethodGet, - Path: "/hello", - ExpectStatus: http.StatusOK, - ExpectBody: "hello world", - ExpectHeaders: map[string]string{"X-VERSION": "1.0"}, - }), - Entry("verify route that should be blocked", base.HttpTestCase{ - Object: base.APISIXExpect(), - Method: http.MethodGet, - Path: "/hello", - Query: "name=%3Bselect%20from%20sys", - ExpectStatus: http.StatusForbidden, - ExpectHeaders: map[string]string{"X-VERSION": "1.0"}, - }), - Entry("update route with same plugin response-rewrite", base.HttpTestCase{ - Object: base.ManagerApiExpect(), - Method: http.MethodPut, - Path: "/apisix/admin/routes/r1", - Body: `{ - "name": "route1", - "uri": "/hello", - "plugins": { - "response-rewrite": { - "headers": { - "X-VERSION":"2.0" - } - } - }, - "upstream": { - "type": "roundrobin", - "nodes": [{ - "host": "` + base.UpstreamIp + `", - "port": 1981, - "weight": 1 - }] - } - }`, - Headers: map[string]string{"Authorization": base.GetToken()}, - ExpectStatus: http.StatusOK, - ExpectBody: "\"X-VERSION\":\"2.0\"", - }), - Entry("verify route that header should override", base.HttpTestCase{ - Object: base.APISIXExpect(), - Method: http.MethodGet, - Path: "/hello", - ExpectStatus: http.StatusOK, - ExpectBody: "hello world", - ExpectHeaders: map[string]string{"X-VERSION": "2.0"}, - }), - Entry("delete global rule", base.HttpTestCase{ - Object: base.ManagerApiExpect(), - Method: http.MethodDelete, - Path: "/apisix/admin/global_rules/1", - Headers: map[string]string{"Authorization": base.GetToken()}, - ExpectStatus: http.StatusOK, - }), - Entry("delete route", base.HttpTestCase{ - Object: base.ManagerApiExpect(), - Method: http.MethodDelete, - Path: "/apisix/admin/routes/r1", - Headers: map[string]string{"Authorization": base.GetToken()}, - ExpectStatus: http.StatusOK, - }), - ) -}) diff --git a/api/test/e2e/go.mod b/api/test/e2e/go.mod deleted file mode 100644 index c545cc2b57..0000000000 --- a/api/test/e2e/go.mod +++ /dev/null @@ -1,21 +0,0 @@ -module github.com/apisix/manager-api/test/e2e - -go 1.15 - -require ( - github.com/Nerzal/gocloak/v11 v11.2.0 - github.com/PuerkitoBio/goquery v1.8.0 - github.com/gavv/httpexpect/v2 v2.8.0 - github.com/golang-jwt/jwt/v4 v4.4.3 // indirect - github.com/gorilla/websocket v1.5.0 // indirect - github.com/klauspost/compress v1.15.13 // indirect - github.com/onsi/ginkgo v1.16.5 // indirect - github.com/onsi/ginkgo/v2 v2.6.1 - github.com/onsi/gomega v1.24.2 - github.com/savsgio/gotils v0.0.0-20220530130905-52f3993e8d6d - github.com/sergi/go-diff v1.2.0 // indirect - github.com/stretchr/testify v1.8.1 - github.com/tidwall/gjson v1.14.4 - github.com/tidwall/pretty v1.2.1 // indirect - github.com/valyala/fasthttp v1.43.0 // indirect -) diff --git a/api/test/e2e/go.sum b/api/test/e2e/go.sum deleted file mode 100644 index c524a7f83f..0000000000 --- a/api/test/e2e/go.sum +++ /dev/null @@ -1,301 +0,0 @@ -github.com/Nerzal/gocloak/v11 v11.2.0 h1:i67+hsEhSaolpJi1YKgwqH4dtSd8IdfHiEluxSEMm/U= -github.com/Nerzal/gocloak/v11 v11.2.0/go.mod h1:vz59u7bBDKWoCdeTpY8i4LELtdwrLrIynAgPvO5ogQA= -github.com/PuerkitoBio/goquery v1.8.0 h1:PJTF7AmFCFKk1N6V6jmKfrNH9tV5pNE6lZMkG0gta/U= -github.com/PuerkitoBio/goquery v1.8.0/go.mod h1:ypIiRMtY7COPGk+I/YbZLbxsxn9g5ejnI2HSMtkjZvI= -github.com/ajg/form v1.5.1 h1:t9c7v8JUKu/XxOGBU0yjNpaMloxGEJhUkqFRq0ibGeU= -github.com/ajg/form v1.5.1/go.mod h1:uL1WgH+h2mgNtvBq0339dVnzXdBETtL2LeUXaIv25UY= -github.com/andybalholm/brotli v1.0.2/go.mod h1:loMXtMfwqflxFJPmdbJO0a3KNoPuLBgiu3qAvBg8x/Y= -github.com/andybalholm/brotli v1.0.4 h1:V7DdXeJtZscaqfNuAdSRuRFzuiKlHSC/Zh3zl9qY3JY= -github.com/andybalholm/brotli v1.0.4/go.mod h1:fO7iG3H7G2nSZ7m0zPUDn85XEX2GTukHGRSepvi9Eig= -github.com/andybalholm/cascadia v1.3.1 h1:nhxRkql1kdYCc8Snf7D5/D3spOX+dBgjA6u8x004T2c= -github.com/andybalholm/cascadia v1.3.1/go.mod h1:R4bJ1UQfqADjvDa4P6HZHLh/3OxWWEqc0Sk8XGwHqvA= -github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI= -github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI= -github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU= -github.com/davecgh/go-spew v0.0.0-20161028175848-04cdfd42973b/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= -github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/fasthttp/websocket v1.4.3-rc.6 h1:omHqsl8j+KXpmzRjF8bmzOSYJ8GnS0E3efi1wYT+niY= -github.com/fasthttp/websocket v1.4.3-rc.6/go.mod h1:43W9OM2T8FeXpCWMsBd9Cb7nE2CACNqNvCqQCoty/Lc= -github.com/fatih/structs v1.1.0 h1:Q7juDM0QtcnhCpeyLGQKyg4TOIghuNXrkL32pHAUMxo= -github.com/fatih/structs v1.1.0/go.mod h1:9NiDSp5zOcgEDl+j00MP/WkGVPOlPRLejGD8Ga6PJ7M= -github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= -github.com/fsnotify/fsnotify v1.4.9 h1:hsms1Qyu0jgnwNXIxa+/V/PDsU6CfLf6CNO8H7IWoS4= -github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ= -github.com/gavv/httpexpect/v2 v2.8.0 h1:sIYO3vVjWq06X9LVncVXGvDGtVytedGLoJLp7tR+m5A= -github.com/gavv/httpexpect/v2 v2.8.0/go.mod h1:jIj2f4rLediVaQK7rIH2EcU4W1ovjeSI8D0g85VJe9o= -github.com/go-logr/logr v1.2.3 h1:2DntVwHkVopvECVRSlL5PSo9eG+cAkDCuckLubN+rq0= -github.com/go-logr/logr v1.2.3/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= -github.com/go-resty/resty/v2 v2.7.0 h1:me+K9p3uhSmXtrBZ4k9jcEAfJmuC8IivWHwaLZwPrFY= -github.com/go-resty/resty/v2 v2.7.0/go.mod h1:9PWDzw47qPphMRFfhsyk0NnSgvluHcljSMVIq3w7q0I= -github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0/go.mod h1:fyg7847qk6SyHyPtNmDHnmrv/HOrqktSC+C9fM+CJOE= -github.com/golang-jwt/jwt/v4 v4.4.1 h1:pC5DB52sCeK48Wlb9oPcdhnjkz1TKt1D/P7WKJ0kUcQ= -github.com/golang-jwt/jwt/v4 v4.4.1/go.mod h1:m21LjoU+eqJr34lmDMbreY2eSTRJ1cv77w39/MY0Ch0= -github.com/golang-jwt/jwt/v4 v4.4.3 h1:Hxl6lhQFj4AnOX6MLrsCb/+7tCj7DxP7VA+2rDIq5AU= -github.com/golang-jwt/jwt/v4 v4.4.3/go.mod h1:m21LjoU+eqJr34lmDMbreY2eSTRJ1cv77w39/MY0Ch0= -github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= -github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8= -github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA= -github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs= -github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w= -github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0= -github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= -github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= -github.com/golang/protobuf v1.5.2 h1:ROPKBNFfQgOUMifHyP+KYbvpjbdoFNs+aK7DXlji0Tw= -github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= -github.com/golang/snappy v0.0.3/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= -github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= -github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= -github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/go-cmp v0.5.8/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= -github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38= -github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= -github.com/google/go-querystring v1.1.0 h1:AnCroh3fv4ZBgVIf1Iwtovgjaw/GiKJo8M8yD/fhyJ8= -github.com/google/go-querystring v1.1.0/go.mod h1:Kcdr2DB4koayq7X8pmAG4sNG59So17icRSOU623lUBU= -github.com/google/pprof v0.0.0-20210407192527-94a9f03dee38/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= -github.com/google/uuid v1.2.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= -github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= -github.com/gorilla/websocket v1.4.2 h1:+/TMaTYc4QFitKJxsQ7Yye35DkWvkdLcvGKqM+x0Ufc= -github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= -github.com/gorilla/websocket v1.5.0 h1:PPwGk2jz7EePpoHN/+ClbZu8SPxiqlu12wZP/3sWmnc= -github.com/gorilla/websocket v1.5.0/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= -github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= -github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= -github.com/imkira/go-interpol v1.1.0 h1:KIiKr0VSG2CUW1hl1jpiyuzuJeKUUpC8iM1AIE7N1Vk= -github.com/imkira/go-interpol v1.1.0/go.mod h1:z0h2/2T3XF8kyEPpRgJ3kmNv+C43p+I/CoI+jC3w2iA= -github.com/k0kubun/colorstring v0.0.0-20150214042306-9440f1994b88 h1:uC1QfSlInpQF+M0ao65imhwqKnz3Q2z/d8PWZRMQvDM= -github.com/k0kubun/colorstring v0.0.0-20150214042306-9440f1994b88/go.mod h1:3w7q1U84EfirKl04SVQ/s7nPm1ZPhiXd34z40TNz36k= -github.com/klauspost/compress v1.12.2/go.mod h1:8dP1Hq4DHOhN9w426knH3Rhby4rFm6D8eO+e+Dq5Gzg= -github.com/klauspost/compress v1.15.0 h1:xqfchp4whNFxn5A4XFyyYtitiWI8Hy5EW59jEwcyL6U= -github.com/klauspost/compress v1.15.0/go.mod h1:/3/Vjq9QcHkK5uEr5lBEmyoZ1iFhe47etQ6QUkpK6sk= -github.com/klauspost/compress v1.15.9/go.mod h1:PhcZ0MbTNciWF3rruxRgKxI5NkcHHrHUDtV4Yw2GlzU= -github.com/klauspost/compress v1.15.13 h1:NFn1Wr8cfnenSJSA46lLq4wHCcBzKTSjnBIexDMMOV0= -github.com/klauspost/compress v1.15.13/go.mod h1:QPwzmACJjUTFsnSHH934V6woptycfrDDJnH7hvFVbGM= -github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= -github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= -github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= -github.com/mattn/go-colorable v0.1.2 h1:/bC9yWikZXAL9uJdulbSfyVNIR3n3trXl+v8+1sx8mU= -github.com/mattn/go-colorable v0.1.2/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE= -github.com/mattn/go-isatty v0.0.8 h1:HLtExJ+uU2HOZ+wI0Tt5DtUDrx8yhUqDcp7fYERX4CE= -github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= -github.com/mitchellh/go-wordwrap v1.0.1 h1:TLuKupo69TCn6TQSyGxwI1EblZZEsQ0vMlAFQflz0v0= -github.com/mitchellh/go-wordwrap v1.0.1/go.mod h1:R62XHJLzvMFRBbcrT7m7WgmE1eOyTSsCt+hzestvNj0= -github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A= -github.com/nxadm/tail v1.4.8 h1:nPr65rt6Y5JFSKQO7qToXr7pePgD6Gwiw05lkbyAQTE= -github.com/nxadm/tail v1.4.8/go.mod h1:+ncqLTQzXmGhMZNUePPaPqPvBxHAIsmXswZKocGu+AU= -github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= -github.com/onsi/ginkgo v1.10.1/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= -github.com/onsi/ginkgo v1.12.1/go.mod h1:zj2OWP4+oCPe1qIXoGWkgMRwljMUYCdkwsT2108oapk= -github.com/onsi/ginkgo v1.16.4/go.mod h1:dX+/inL/fNMqNlz0e9LfyB9TswhZpCVdJM/Z6Vvnwo0= -github.com/onsi/ginkgo v1.16.5 h1:8xi0RTUf59SOSfEtZMvwTvXYMzG4gV23XVHOZiXNtnE= -github.com/onsi/ginkgo v1.16.5/go.mod h1:+E8gABHa3K6zRBolWtd+ROzc/U5bkGt0FwiG042wbpU= -github.com/onsi/ginkgo/v2 v2.1.3/go.mod h1:vw5CSIxN1JObi/U8gcbwft7ZxR2dgaR70JSE3/PpL4c= -github.com/onsi/ginkgo/v2 v2.1.4/go.mod h1:um6tUpWM/cxCK3/FK8BXqEiUMUwRgSM4JXG47RKZmLU= -github.com/onsi/ginkgo/v2 v2.1.6/go.mod h1:MEH45j8TBi6u9BMogfbp0stKC5cdGjumZj5Y7AG4VIk= -github.com/onsi/ginkgo/v2 v2.3.0/go.mod h1:Eew0uilEqZmIEZr8JrvYlvOM7Rr6xzTmMV8AyFNU9d0= -github.com/onsi/ginkgo/v2 v2.4.0/go.mod h1:iHkDK1fKGcBoEHT5W7YBq4RFWaQulw+caOMkAt4OrFo= -github.com/onsi/ginkgo/v2 v2.5.0/go.mod h1:Luc4sArBICYCS8THh8v3i3i5CuSZO+RaQRaJoeNwomw= -github.com/onsi/ginkgo/v2 v2.6.1 h1:1xQPCjcqYw/J5LchOcp4/2q/jzJFjiAOc25chhnDw+Q= -github.com/onsi/ginkgo/v2 v2.6.1/go.mod h1:yjiuMwPokqY1XauOgju45q3sJt6VzQ/Fict1LFVcsAo= -github.com/onsi/gomega v1.7.0/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY= -github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY= -github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo= -github.com/onsi/gomega v1.17.0/go.mod h1:HnhC7FXeEQY45zxNK3PPoIUhzk/80Xly9PcubAlGdZY= -github.com/onsi/gomega v1.19.0/go.mod h1:LY+I3pBVzYsTBU1AnDwOSxaYi9WoWiqgwooUqq9yPro= -github.com/onsi/gomega v1.20.1/go.mod h1:DtrZpjmvpn2mPm4YWQa0/ALMDj9v4YxLgojwPeREyVo= -github.com/onsi/gomega v1.21.1/go.mod h1:iYAIXgPSaDHak0LCMA+AWBpIKBr8WZicMxnE8luStNc= -github.com/onsi/gomega v1.22.1/go.mod h1:x6n7VNe4hw0vkyYUM4mjIXx3JbLiPaBPNgB7PRQ1tuM= -github.com/onsi/gomega v1.24.0/go.mod h1:Z/NWtiqwBrwUt4/2loMmHL63EDLnYHmVbuBpDr2vQAg= -github.com/onsi/gomega v1.24.1/go.mod h1:3AOiACssS3/MajrniINInwbfOOtfZvplPzuRSmvt1jM= -github.com/onsi/gomega v1.24.2 h1:J/tulyYK6JwBldPViHJReihxxZ+22FHs0piGjQAvoUE= -github.com/onsi/gomega v1.24.2/go.mod h1:gs3J10IS7Z7r7eXRoNJIrNqU4ToQukCJhFtKrWgHWnk= -github.com/opentracing/opentracing-go v1.2.0 h1:uEJPy/1a5RIPAJ0Ov+OIO8OxWu77jEv+1B0VhjKrZUs= -github.com/opentracing/opentracing-go v1.2.0/go.mod h1:GxEUsuufX4nBwe+T+Wl9TAgYrxe9dPLANfrWvHYVTgc= -github.com/pkg/diff v0.0.0-20200914180035-5b29258ca4f7/go.mod h1:zO8QMzTeZd5cpnIkz/Gn6iK0jDfGicM1nynOkkPIl28= -github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= -github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= -github.com/pmezard/go-difflib v0.0.0-20151028094244-d8ed2627bdf0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= -github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= -github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= -github.com/sanity-io/litter v1.5.5 h1:iE+sBxPBzoK6uaEP5Lt3fHNgpKcHXc/A2HGETy0uJQo= -github.com/sanity-io/litter v1.5.5/go.mod h1:9gzJgR2i4ZpjZHsKvUXIRQVk7P+yM3e+jAF7bU2UI5U= -github.com/savsgio/gotils v0.0.0-20210617111740-97865ed5a873/go.mod h1:dmPawKuiAeG/aFYVs2i+Dyosoo7FNcm+Pi8iK6ZUrX8= -github.com/savsgio/gotils v0.0.0-20220530130905-52f3993e8d6d h1:Q+gqLBOPkFGHyCJxXMRqtUgUbTjI8/Ze8vu8GGyNFwo= -github.com/savsgio/gotils v0.0.0-20220530130905-52f3993e8d6d/go.mod h1:Gy+0tqhJvgGlqnTF8CVGP0AaGRjwBtXs/a5PA0Y3+A4= -github.com/segmentio/ksuid v1.0.4 h1:sBo2BdShXjmcugAMwjugoGUdUV0pcxY5mW4xKRn3v4c= -github.com/segmentio/ksuid v1.0.4/go.mod h1:/XUiZBD3kVx5SmUOl55voK5yeAbBNNIed+2O73XgrPE= -github.com/sergi/go-diff v1.0.0 h1:Kpca3qRNrduNnOQeazBd0ysaKrUJiIuISHxogkT9RPQ= -github.com/sergi/go-diff v1.0.0/go.mod h1:0CfEIISq7TuYL3j771MWULgwwjU+GofnZX9QAmXWZgo= -github.com/sergi/go-diff v1.2.0 h1:XU+rvMAioB0UC3q1MFrIQy4Vo5/4VsRDQQXHsEya6xQ= -github.com/sergi/go-diff v1.2.0/go.mod h1:STckp+ISIX8hZLjrqAeVduY0gWCT9IjLuqbuNXdaHfM= -github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= -github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= -github.com/stretchr/objx v0.5.0 h1:1zr/of2m5FGMsad5YfcqgdqdWrIhu+EBEJRhR1U7z/c= -github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= -github.com/stretchr/testify v0.0.0-20161117074351-18a02ba4a312/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= -github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= -github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= -github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= -github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= -github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= -github.com/stretchr/testify v1.8.1 h1:w7B6lhMri9wdJUVmEZPGGhZzrYTPvgJArz7wNPgYKsk= -github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= -github.com/tailscale/depaware v0.0.0-20210622194025-720c4b409502/go.mod h1:p9lPsd+cx33L3H9nNoecRRxPssFKUwwI50I3pZ0yT+8= -github.com/tidwall/gjson v1.14.4 h1:uo0p8EbA09J7RQaflQ1aBRffTR7xedD2bcIVSYxLnkM= -github.com/tidwall/gjson v1.14.4/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk= -github.com/tidwall/match v1.1.1 h1:+Ho715JplO36QYgwN9PGYNhgZvoUSc9X2c80KVTi+GA= -github.com/tidwall/match v1.1.1/go.mod h1:eRSPERbgtNPcGhD8UCthc6PmLEQXEWd3PRB5JTxsfmM= -github.com/tidwall/pretty v1.2.0 h1:RWIZEg2iJ8/g6fDDYzMpobmaoGh5OLl4AXtGUGPcqCs= -github.com/tidwall/pretty v1.2.0/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU= -github.com/tidwall/pretty v1.2.1 h1:qjsOFOWWQl+N3RsoF5/ssm1pHmJJwhjlSbZ51I6wMl4= -github.com/tidwall/pretty v1.2.1/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU= -github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw= -github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc= -github.com/valyala/fasthttp v1.27.0/go.mod h1:cmWIqlu99AO/RKcp1HWaViTqc57FswJOfYYdPJBl8BA= -github.com/valyala/fasthttp v1.34.0 h1:d3AAQJ2DRcxJYHm7OXNXtXt2as1vMDfxeIcFvhmGGm4= -github.com/valyala/fasthttp v1.34.0/go.mod h1:epZA5N+7pY6ZaEKRmstzOuYJx9HI8DI1oaCGZpdH4h0= -github.com/valyala/fasthttp v1.43.0 h1:Gy4sb32C98fbzVWZlTM1oTMdLWGyvxR03VhM6cBIU4g= -github.com/valyala/fasthttp v1.43.0/go.mod h1:f6VbjjoI3z1NDOZOv17o6RvtRSWxC77seBFc2uWtgiY= -github.com/valyala/tcplisten v1.0.0/go.mod h1:T0xQ8SeCZGxckz9qRXTfG43PvQ/mcWh7FwZEA7Ioqkc= -github.com/xeipuuv/gojsonpointer v0.0.0-20180127040702-4e3ac2762d5f/go.mod h1:N2zxlSyiKSe5eX1tZViRH5QA0qijqEDrYZiPEAiq3wU= -github.com/xeipuuv/gojsonpointer v0.0.0-20190905194746-02993c407bfb h1:zGWFAtiMcyryUHoUjUJX0/lt1H2+i2Ka2n+D3DImSNo= -github.com/xeipuuv/gojsonpointer v0.0.0-20190905194746-02993c407bfb/go.mod h1:N2zxlSyiKSe5eX1tZViRH5QA0qijqEDrYZiPEAiq3wU= -github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415 h1:EzJWgHovont7NscjpAxXsDA8S8BMYve8Y5+7cuRE7R0= -github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415/go.mod h1:GwrjFmJcFw6At/Gs6z4yjiIwzuJ1/+UwLxMQDVQXShQ= -github.com/xeipuuv/gojsonschema v1.2.0 h1:LhYJRs+L4fBtjZUfuSZIKGeVu0QRy8e5Xi7D17UxZ74= -github.com/xeipuuv/gojsonschema v1.2.0/go.mod h1:anYRn/JVcOK2ZgGU+IjEV4nwlhoK5sQluxsYJ78Id3Y= -github.com/yalp/jsonpath v0.0.0-20180802001716-5cc68e5049a0 h1:6fRhSjgLCkTD3JnJxvaJ4Sj+TYblw757bqYgZaOq5ZY= -github.com/yalp/jsonpath v0.0.0-20180802001716-5cc68e5049a0/go.mod h1:/LWChgwKmvncFJFHJ7Gvn9wZArjbV5/FppcK2fKk/tI= -github.com/yudai/gojsondiff v1.0.0 h1:27cbfqXLVEJ1o8I6v3y9lg8Ydm53EKqHXAOMxEGlCOA= -github.com/yudai/gojsondiff v1.0.0/go.mod h1:AY32+k2cwILAkW1fbgxQ5mUmMiZFgLIV+FBNExI05xg= -github.com/yudai/golcs v0.0.0-20170316035057-ecda9a501e82 h1:BHyfKlQyqbsFN5p3IfnEUduWvb9is428/nNb5L3U01M= -github.com/yudai/golcs v0.0.0-20170316035057-ecda9a501e82/go.mod h1:lgjkn3NuSvDfVJdfcVVdX+jpBxNmX4rDAzaS45IcYoM= -github.com/yudai/pp v2.0.1+incompatible h1:Q4//iY4pNF6yPLZIigmvcl7k/bPgrcTPIFIcmawg5bI= -github.com/yudai/pp v2.0.1+incompatible/go.mod h1:PuxR/8QJ7cyCkFp/aUDS+JY727OFEZkTdatxwunjIkc= -github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= -github.com/yuin/goldmark v1.4.1/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= -github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= -golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= -golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= -golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= -golang.org/x/crypto v0.0.0-20210513164829-c07d793c2f9a/go.mod h1:P+XmwS30IXTQdn5tA2iutPOUgjI07+tq3H3K9MVA1s8= -golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= -golang.org/x/crypto v0.0.0-20220214200702-86341886e292/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= -golang.org/x/crypto v0.0.0-20220427172511-eb4f295cb31f/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= -golang.org/x/crypto v0.1.0 h1:MDRAIl0xIo9Io2xV565hzXHw3zVseKrJKodhohM5CjU= -golang.org/x/crypto v0.1.0/go.mod h1:RecgLatLF4+eUMCP1PoPZQb+cVrJcOPbHkTkbkB9sbw= -golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= -golang.org/x/mod v0.4.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= -golang.org/x/mod v0.6.0-dev.0.20220106191415-9b9b3d81d5e3/go.mod h1:3p9vT2HGsQu2K1YbXdKPJLVgG5VJdoTa1poYQBtP1AY= -golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= -golang.org/x/mod v0.6.0/go.mod h1:4mET923SAdbXp2ki8ey+zGs1SLqsuM2Y0uvdZR/fUNI= -golang.org/x/mod v0.7.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= -golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= -golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= -golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20200520004742-59133d7f0dd7/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-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= -golang.org/x/net v0.0.0-20210428140749-89ef3d95e781/go.mod h1:OJAsFXCWl8Ukc7SiCT/9KSuxbyM7479/AVlXFRxuMCk= -golang.org/x/net v0.0.0-20210510120150-4163338589ed/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= -golang.org/x/net v0.0.0-20210916014120-12bc252f5db8/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= -golang.org/x/net v0.0.0-20211015210444-4f30a5c0130f/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= -golang.org/x/net v0.0.0-20211029224645-99673261e6eb/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= -golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= -golang.org/x/net v0.0.0-20220225172249-27dd8689420f/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= -golang.org/x/net v0.0.0-20220425223048-2871e0cb64e4/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= -golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= -golang.org/x/net v0.0.0-20220906165146-f3363e06e74c/go.mod h1:YDH+HFinaLZZlnHAfSS6ZXJJ9M9t4Dl22yv3iI2vPwk= -golang.org/x/net v0.1.0/go.mod h1:Cx3nUiGt4eDBEyega/BKRp+/AlGL8hYe7U9odMt2Cco= -golang.org/x/net v0.2.0/go.mod h1:KqCZLdyyvdV855qA2rE3GC2aiw5xGR5TEjj8smXukLY= -golang.org/x/net v0.3.0/go.mod h1:MBQ8lrhLObU/6UmLb4fmbmk5OcyYmqtbGd/9yIeKjEE= -golang.org/x/net v0.4.0 h1:Q5QPcMlvfxFTAPV0+07Xz/MpK9NTXu2VDUuy0FeMfaU= -golang.org/x/net v0.4.0/go.mod h1:MBQ8lrhLObU/6UmLb4fmbmk5OcyYmqtbGd/9yIeKjEE= -golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190904154756-749cb33beabd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -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-20210112080510-489259a85091/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210514084401-e8d321eab015/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20211019181941-9d821ace8654/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220227234510-4e6760a101f9/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220319134239-a9b59b0215f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220422013727-9388b58f7150/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220728004956-3c1f35247d10/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.2.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.3.0 h1:w8ZOecv6NaNa/zC8944JTU3vz4u6Lagfk4RPQxv92NQ= -golang.org/x/sys v0.3.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= -golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= -golang.org/x/term v0.1.0/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= -golang.org/x/term v0.2.0/go.mod h1:TVmDHMZPmdnySmBfhjOoOdhjzdE1h4u1VwSiw2l1Nuc= -golang.org/x/term v0.3.0/go.mod h1:q750SLmJuPmVoN1blW3UFBPREJfb1KmY3vwxfr+nFDA= -golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= -golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= -golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= -golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= -golang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= -golang.org/x/text v0.5.0 h1:OLmvp0KP+FVG99Ct/qFiL/Fhk4zp4QQnZ7b2U+5piUM= -golang.org/x/text v0.5.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= -golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= -golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20201211185031-d93e913c1a58/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= -golang.org/x/tools v0.0.0-20201224043029-2b0845dc783e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= -golang.org/x/tools v0.1.10/go.mod h1:Uh6Zz+xoGYZom868N8YTex3t7RhtHDBrE8Gzo9bV56E= -golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= -golang.org/x/tools v0.2.0/go.mod h1:y4OqIKeOV/fWJetJ8bXPU1sEVniLMIyDAZWeHdV+NTA= -golang.org/x/tools v0.4.0/go.mod h1:UE5sM2OK9E/d67R0ANs2xJizIymRP5gJU295PvKXxjQ= -golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= -google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= -google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= -google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE= -google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo= -google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= -google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= -google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= -google.golang.org/protobuf v1.28.0 h1:w43yiav+6bVFTBQFZX0r7ipe9JQ1QsbMgHwbBziscLw= -google.golang.org/protobuf v1.28.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= -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/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= -gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys= -gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ= -gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw= -gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= -gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= -gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= -gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= -gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= -gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= -gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= -gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= -gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= -gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= -moul.io/http2curl/v2 v2.3.0 h1:9r3JfDzWPcbIklMOs2TnIFzDYvfAZvjeavG6EzP7jYs= -moul.io/http2curl/v2 v2.3.0/go.mod h1:RW4hyBjTWSYDOxapodpNEtX0g5Eb16sxklBqmd2RHcE= diff --git a/api/test/e2e/healthz/healthz_suite_test.go b/api/test/e2e/healthz/healthz_suite_test.go deleted file mode 100644 index c7027970e8..0000000000 --- a/api/test/e2e/healthz/healthz_suite_test.go +++ /dev/null @@ -1,34 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one or more - * contributor license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright ownership. - * The ASF licenses this file to You under the Apache License, Version 2.0 - * (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package healthz_test - -import ( - "testing" - "time" - - "github.com/apisix/manager-api/test/e2e/base" - . "github.com/onsi/ginkgo/v2" -) - -func TestHealthz(t *testing.T) { - RunSpecs(t, "Health Suite") -} - -var _ = BeforeSuite(func() { - base.CleanAllResource() - time.Sleep(base.SleepTime) -}) diff --git a/api/test/e2e/healthz/healthz_test.go b/api/test/e2e/healthz/healthz_test.go deleted file mode 100644 index c8b578e0f9..0000000000 --- a/api/test/e2e/healthz/healthz_test.go +++ /dev/null @@ -1,38 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one or more - * contributor license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright ownership. - * The ASF licenses this file to You under the Apache License, Version 2.0 - * (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package healthz_test - -import ( - "net/http" - - . "github.com/onsi/ginkgo/v2" - - "github.com/apisix/manager-api/test/e2e/base" -) - -var _ = Describe("Healthy check", func() { - It("ping manager-api", func() { - base.RunTestCase(base.HttpTestCase{ - Object: base.ManagerApiExpect(), - Method: http.MethodGet, - Path: "/ping", - ExpectStatus: http.StatusOK, - ExpectBody: "pong", - Sleep: base.SleepTime, - }) - }) -}) diff --git a/api/test/e2e/id_compatible/id_compatible_suite_test.go b/api/test/e2e/id_compatible/id_compatible_suite_test.go deleted file mode 100644 index 865f357deb..0000000000 --- a/api/test/e2e/id_compatible/id_compatible_suite_test.go +++ /dev/null @@ -1,37 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one or more - * contributor license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright ownership. - * The ASF licenses this file to You under the Apache License, Version 2.0 - * (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package id_compatible_test - -import ( - "testing" - "time" - - . "github.com/onsi/ginkgo/v2" - . "github.com/onsi/gomega" - - "github.com/apisix/manager-api/test/e2e/base" -) - -func TestIdCompatible(t *testing.T) { - RegisterFailHandler(Fail) - RunSpecs(t, "Id Compatible Suite") -} - -var _ = BeforeSuite(func() { - base.CleanAllResource() - time.Sleep(base.SleepTime) -}) diff --git a/api/test/e2e/id_compatible/id_crossing_test.go b/api/test/e2e/id_compatible/id_crossing_test.go deleted file mode 100644 index 1851df94fb..0000000000 --- a/api/test/e2e/id_compatible/id_crossing_test.go +++ /dev/null @@ -1,116 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one or more - * contributor license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright ownership. - * The ASF licenses this file to You under the Apache License, Version 2.0 - * (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package id_compatible_test - -import ( - "net/http" - - . "github.com/onsi/ginkgo/v2" - - "github.com/apisix/manager-api/test/e2e/base" -) - -var _ = DescribeTable("Id Crossing", - func(tc base.HttpTestCase) { - base.RunTestCase(tc) - }, - Entry("create upstream by admin api", base.HttpTestCase{ - Object: base.APISIXAdminAPIExpect(), - Method: http.MethodPut, - Path: "/apisix/admin/upstreams", - Body: `{ - "id": 3, - "nodes": [{ - "host": "` + base.UpstreamIp + `", - "port": 1980, - "weight": 1 - }], - "type": "roundrobin" - }`, - Headers: map[string]string{"X-API-KEY": "edd1c9f034335f136f87ad84b625c8f1"}, - ExpectStatus: http.StatusCreated, - }), - Entry("create route by admin api", base.HttpTestCase{ - Object: base.APISIXAdminAPIExpect(), - Method: http.MethodPut, - Path: "/apisix/admin/routes/3", - Body: `{ - "name": "route3", - "uri": "/hello", - "upstream_id": 3 - }`, - Headers: map[string]string{"X-API-KEY": "edd1c9f034335f136f87ad84b625c8f1"}, - ExpectStatus: http.StatusCreated, - Sleep: base.SleepTime, - }), - Entry("verify that the upstream is available for manager api", base.HttpTestCase{ - Object: base.ManagerApiExpect(), - Method: http.MethodGet, - Path: "/apisix/admin/upstreams/3", - Headers: map[string]string{"Authorization": base.GetToken()}, - ExpectStatus: http.StatusOK, - ExpectBody: `"id":3`, - Sleep: base.SleepTime, - }), - Entry("verify that the route is available for manager api", base.HttpTestCase{ - Object: base.ManagerApiExpect(), - Method: http.MethodGet, - Path: "/apisix/admin/routes/3", - Headers: map[string]string{"Authorization": base.GetToken()}, - ExpectStatus: http.StatusOK, - ExpectBody: `"upstream_id":3`, - Sleep: base.SleepTime, - }), - Entry("hit the route just created", base.HttpTestCase{ - Object: base.APISIXExpect(), - Method: http.MethodGet, - Path: "/hello", - ExpectStatus: http.StatusOK, - ExpectBody: "hello world", - Sleep: base.SleepTime, - }), - Entry("delete the route", base.HttpTestCase{ - Object: base.ManagerApiExpect(), - Method: http.MethodDelete, - Path: "/apisix/admin/routes/3", - Headers: map[string]string{"Authorization": base.GetToken()}, - ExpectStatus: http.StatusOK, - }), - Entry("delete the upstream", base.HttpTestCase{ - Object: base.ManagerApiExpect(), - Method: http.MethodDelete, - Path: "/apisix/admin/upstreams/3", - Headers: map[string]string{"Authorization": base.GetToken()}, - ExpectStatus: http.StatusOK, - Sleep: base.SleepTime, - }), - Entry("make sure the upstream has been deleted", base.HttpTestCase{ - Object: base.ManagerApiExpect(), - Method: http.MethodGet, - Path: "/apisix/admin/upstreams/3", - Headers: map[string]string{"Authorization": base.GetToken()}, - ExpectStatus: http.StatusNotFound, - Sleep: base.SleepTime, - }), - Entry("hit deleted route", base.HttpTestCase{ - Object: base.APISIXExpect(), - Method: http.MethodGet, - Path: "/hello", - ExpectStatus: http.StatusNotFound, - Sleep: base.SleepTime, - }), -) diff --git a/api/test/e2e/id_compatible/id_not_in_body_test.go b/api/test/e2e/id_compatible/id_not_in_body_test.go deleted file mode 100644 index 0569984adb..0000000000 --- a/api/test/e2e/id_compatible/id_not_in_body_test.go +++ /dev/null @@ -1,164 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one or more - * contributor license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright ownership. - * The ASF licenses this file to You under the Apache License, Version 2.0 - * (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package id_compatible_test - -import ( - "io/ioutil" - "net/http" - "time" - - . "github.com/onsi/ginkgo/v2" - . "github.com/onsi/gomega" - "github.com/tidwall/gjson" - - "github.com/apisix/manager-api/test/e2e/base" -) - -var _ = DescribeTable("Id Not In Body", - func(f func()) { - f() - }, - Entry("make sure the route is not created", func() { - base.RunTestCase(base.HttpTestCase{ - Object: base.APISIXExpect(), - Method: http.MethodGet, - Path: "/hello", - ExpectStatus: http.StatusNotFound, - Sleep: base.SleepTime, - }) - }), - Entry("create route that has no ID in request body by admin api", func() { - base.RunTestCase(base.HttpTestCase{ - Object: base.APISIXAdminAPIExpect(), - Method: http.MethodPut, - Path: "/apisix/admin/routes/r1", - Body: `{ - "name": "route1", - "uri": "/hello", - "upstream": { - "type": "roundrobin", - "nodes": { - "` + base.UpstreamIp + `:1980": 1 - } - } - }`, - Headers: map[string]string{"X-API-KEY": "edd1c9f034335f136f87ad84b625c8f1"}, - ExpectStatus: http.StatusCreated, - Sleep: base.SleepTime, - }) - }), - Entry("verify that the route is available for manager api", func() { - base.RunTestCase(base.HttpTestCase{ - Object: base.ManagerApiExpect(), - Method: http.MethodGet, - Path: "/apisix/admin/routes/r1", - Headers: map[string]string{"Authorization": base.GetToken()}, - ExpectStatus: http.StatusOK, - ExpectBody: `"id":"r1"`, - Sleep: base.SleepTime, - }) - }), - Entry("hit the route just created", func() { - base.RunTestCase(base.HttpTestCase{ - Object: base.APISIXExpect(), - Method: http.MethodGet, - Path: "/hello", - ExpectStatus: http.StatusOK, - ExpectBody: "hello world", - Sleep: base.SleepTime, - }) - }), - Entry("delete the route", func() { - base.RunTestCase(base.HttpTestCase{ - Object: base.ManagerApiExpect(), - Method: http.MethodDelete, - Path: "/apisix/admin/routes/r1", - Headers: map[string]string{"Authorization": base.GetToken()}, - ExpectStatus: http.StatusOK, - }) - }), - Entry("hit deleted route", func() { - base.RunTestCase(base.HttpTestCase{ - Object: base.APISIXExpect(), - Method: http.MethodGet, - Path: "/hello", - ExpectStatus: http.StatusNotFound, - Sleep: base.SleepTime, - }) - }), - Entry("create route that has no ID in request body by admin api (POST)", func() { - base.RunTestCase(base.HttpTestCase{ - Object: base.APISIXAdminAPIExpect(), - Method: http.MethodPost, - Path: "/apisix/admin/routes", - Body: `{ - "uri": "/hello", - "upstream": { - "type": "roundrobin", - "nodes": { - "` + base.UpstreamIp + `:1980": 1 - } - } - }`, - Headers: map[string]string{"X-API-KEY": "edd1c9f034335f136f87ad84b625c8f1"}, - ExpectStatus: http.StatusCreated, - Sleep: base.SleepTime, - }) - }), - Entry("verify that the route is available for manager api", func() { - base.RunTestCase(base.HttpTestCase{ - Object: base.ManagerApiExpect(), - Method: http.MethodGet, - Path: "/apisix/admin/routes", - Headers: map[string]string{"Authorization": base.GetToken()}, - ExpectStatus: http.StatusOK, - ExpectBody: `"uri":"/hello"`, - Sleep: base.SleepTime, - }) - }), - Entry("hit the route just created", func() { - base.RunTestCase(base.HttpTestCase{ - Object: base.APISIXExpect(), - Method: http.MethodGet, - Path: "/hello", - ExpectStatus: http.StatusOK, - ExpectBody: "hello world", - Sleep: base.SleepTime, - }) - }), - Entry("clear the route", func() { - time.Sleep(time.Duration(100) * time.Millisecond) - request, _ := http.NewRequest("GET", base.ManagerAPIHost+"/apisix/admin/routes", nil) - request.Header.Add("Authorization", base.GetToken()) - resp, err := http.DefaultClient.Do(request) - Expect(err).To(BeNil()) - defer resp.Body.Close() - respBody, _ := ioutil.ReadAll(resp.Body) - list := gjson.Get(string(respBody), "data.rows").Value().([]interface{}) - for _, item := range list { - route := item.(map[string]interface{}) - base.RunTestCase(base.HttpTestCase{ - Desc: "delete the route", - Object: base.ManagerApiExpect(), - Method: http.MethodDelete, - Path: "/apisix/admin/routes/" + route["id"].(string), - Headers: map[string]string{"Authorization": base.GetToken()}, - ExpectStatus: http.StatusOK, - }) - } - }), -) diff --git a/api/test/e2e/id_compatible/id_using_int_test.go b/api/test/e2e/id_compatible/id_using_int_test.go deleted file mode 100644 index 9ab1f2e210..0000000000 --- a/api/test/e2e/id_compatible/id_using_int_test.go +++ /dev/null @@ -1,144 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one or more - * contributor license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright ownership. - * The ASF licenses this file to You under the Apache License, Version 2.0 - * (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package id_compatible_test - -import ( - "net/http" - - . "github.com/onsi/ginkgo/v2" - - "github.com/apisix/manager-api/test/e2e/base" -) - -var _ = DescribeTable("Id Using Int", - func(tc base.HttpTestCase) { - base.RunTestCase(tc) - }, - Entry("create upstream", base.HttpTestCase{ - Object: base.ManagerApiExpect(), - Method: http.MethodPut, - Path: "/apisix/admin/upstreams", - Body: `{ - "id": 1, - "nodes": [{ - "host": "` + base.UpstreamIp + `", - "port": 1980, - "weight": 1 - }], - "type": "roundrobin" - }`, - Headers: map[string]string{"Authorization": base.GetToken()}, - ExpectStatus: http.StatusOK, - }), - Entry("create route using the upstream just created", base.HttpTestCase{ - Object: base.ManagerApiExpect(), - Method: http.MethodPut, - Path: "/apisix/admin/routes/1", - Body: `{ - "name": "route1", - "uri": "/hello", - "upstream_id": 1 - }`, - Headers: map[string]string{"Authorization": base.GetToken()}, - ExpectStatus: http.StatusOK, - Sleep: base.SleepTime, - }), - Entry("hit the route just created", base.HttpTestCase{ - Object: base.APISIXExpect(), - Method: http.MethodGet, - Path: "/hello", - ExpectStatus: http.StatusOK, - ExpectBody: "hello world", - Sleep: base.SleepTime, - }), - Entry("create service", base.HttpTestCase{ - Object: base.ManagerApiExpect(), - Method: http.MethodPut, - Path: "/apisix/admin/services", - Body: `{ - "id": 1, - "upstream_id": 1 - }`, - Headers: map[string]string{"Authorization": base.GetToken()}, - ExpectStatus: http.StatusOK, - }), - Entry("update route to use the service just created", base.HttpTestCase{ - Object: base.ManagerApiExpect(), - Method: http.MethodPut, - Path: "/apisix/admin/routes/1", - Body: `{ - "name": "route1", - "uri": "/hello", - "service_id": 1 - }`, - Headers: map[string]string{"Authorization": base.GetToken()}, - ExpectStatus: http.StatusOK, - Sleep: base.SleepTime, - }), - Entry("hit the route just updated", base.HttpTestCase{ - Object: base.APISIXExpect(), - Method: http.MethodGet, - Path: "/hello", - ExpectStatus: http.StatusOK, - ExpectBody: "hello world", - Sleep: base.SleepTime, - }), - Entry("delete the route", base.HttpTestCase{ - Object: base.ManagerApiExpect(), - Method: http.MethodDelete, - Path: "/apisix/admin/routes/1", - Headers: map[string]string{"Authorization": base.GetToken()}, - ExpectStatus: http.StatusOK, - }), - Entry("delete the service", base.HttpTestCase{ - Object: base.ManagerApiExpect(), - Method: http.MethodDelete, - Path: "/apisix/admin/services/1", - Headers: map[string]string{"Authorization": base.GetToken()}, - ExpectStatus: http.StatusOK, - Sleep: base.SleepTime, - }), - Entry("make sure the service has been deleted", base.HttpTestCase{ - Object: base.ManagerApiExpect(), - Method: http.MethodGet, - Path: "/apisix/admin/services/1", - Headers: map[string]string{"Authorization": base.GetToken()}, - ExpectStatus: http.StatusNotFound, - }), - Entry("delete the upstream", base.HttpTestCase{ - Object: base.ManagerApiExpect(), - Method: http.MethodDelete, - Path: "/apisix/admin/upstreams/1", - Headers: map[string]string{"Authorization": base.GetToken()}, - ExpectStatus: http.StatusOK, - }), - Entry("make sure the upstream has been deleted", base.HttpTestCase{ - Object: base.ManagerApiExpect(), - Method: http.MethodGet, - Path: "/apisix/admin/upstreams/1", - Headers: map[string]string{"Authorization": base.GetToken()}, - ExpectStatus: http.StatusNotFound, - Sleep: base.SleepTime, - }), - Entry("hit deleted route", base.HttpTestCase{ - Object: base.APISIXExpect(), - Method: http.MethodGet, - Path: "/hello", - ExpectStatus: http.StatusNotFound, - Sleep: base.SleepTime, - }), -) diff --git a/api/test/e2e/id_compatible/id_using_string_test.go b/api/test/e2e/id_compatible/id_using_string_test.go deleted file mode 100644 index 2aaea2344d..0000000000 --- a/api/test/e2e/id_compatible/id_using_string_test.go +++ /dev/null @@ -1,99 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one or more - * contributor license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright ownership. - * The ASF licenses this file to You under the Apache License, Version 2.0 - * (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package id_compatible_test - -import ( - "net/http" - - . "github.com/onsi/ginkgo/v2" - - "github.com/apisix/manager-api/test/e2e/base" -) - -var _ = DescribeTable("Id Using String", - func(tc base.HttpTestCase) { - base.RunTestCase(tc) - }, - Entry("create upstream", base.HttpTestCase{ - Object: base.ManagerApiExpect(), - Method: http.MethodPut, - Path: "/apisix/admin/upstreams", - Body: `{ - "id": "2", - "nodes": [{ - "host": "` + base.UpstreamIp + `", - "port": 1980, - "weight": 1 - }], - "type": "roundrobin" - }`, - Headers: map[string]string{"Authorization": base.GetToken()}, - ExpectStatus: http.StatusOK, - }), - Entry("create route using the upstream just created", base.HttpTestCase{ - Object: base.ManagerApiExpect(), - Method: http.MethodPut, - Path: "/apisix/admin/routes/2", - Body: `{ - "name": "route2", - "uri": "/hello", - "upstream_id": "2" - }`, - Headers: map[string]string{"Authorization": base.GetToken()}, - ExpectStatus: http.StatusOK, - Sleep: base.SleepTime, - }), - Entry("hit the route just created", base.HttpTestCase{ - Object: base.APISIXExpect(), - Method: http.MethodGet, - Path: "/hello", - ExpectStatus: http.StatusOK, - ExpectBody: "hello world", - Sleep: base.SleepTime, - }), - Entry("delete the route", base.HttpTestCase{ - Object: base.ManagerApiExpect(), - Method: http.MethodDelete, - Path: "/apisix/admin/routes/2", - Headers: map[string]string{"Authorization": base.GetToken()}, - ExpectStatus: http.StatusOK, - Sleep: base.SleepTime, - }), - Entry("delete the upstream", base.HttpTestCase{ - Object: base.ManagerApiExpect(), - Method: http.MethodDelete, - Path: "/apisix/admin/upstreams/2", - Headers: map[string]string{"Authorization": base.GetToken()}, - ExpectStatus: http.StatusOK, - Sleep: base.SleepTime, - }), - Entry("make sure the upstream has been deleted", base.HttpTestCase{ - Object: base.ManagerApiExpect(), - Method: http.MethodGet, - Path: "/apisix/admin/upstreams/2", - Headers: map[string]string{"Authorization": base.GetToken()}, - ExpectStatus: http.StatusNotFound, - Sleep: base.SleepTime, - }), - Entry("hit deleted route", base.HttpTestCase{ - Object: base.APISIXExpect(), - Method: http.MethodGet, - Path: "/hello", - ExpectStatus: http.StatusNotFound, - Sleep: base.SleepTime, - }), -) diff --git a/api/test/e2e/label/label_suite_test.go b/api/test/e2e/label/label_suite_test.go deleted file mode 100644 index fc0b24c44b..0000000000 --- a/api/test/e2e/label/label_suite_test.go +++ /dev/null @@ -1,36 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one or more - * contributor license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright ownership. - * The ASF licenses this file to You under the Apache License, Version 2.0 - * (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package label_test - -import ( - "testing" - "time" - - "github.com/apisix/manager-api/test/e2e/base" - . "github.com/onsi/ginkgo/v2" - . "github.com/onsi/gomega" -) - -func TestLabel(t *testing.T) { - RegisterFailHandler(Fail) - RunSpecs(t, "Label Suite") -} - -var _ = BeforeSuite(func() { - base.CleanAllResource() - time.Sleep(base.SleepTime) -}) diff --git a/api/test/e2e/label/label_test.go b/api/test/e2e/label/label_test.go deleted file mode 100644 index b877af3a42..0000000000 --- a/api/test/e2e/label/label_test.go +++ /dev/null @@ -1,354 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one or more - * contributor license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright ownership. - * The ASF licenses this file to You under the Apache License, Version 2.0 - * (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package label_test - -import ( - "net/http" - - . "github.com/onsi/ginkgo/v2" - - "github.com/apisix/manager-api/test/e2e/base" -) - -var _ = DescribeTable("Test label", - func(tc base.HttpTestCase) { - base.RunTestCase(tc) - }, - Entry("config route", base.HttpTestCase{ - Object: base.ManagerApiExpect(), - Path: "/apisix/admin/routes/r1", - Method: http.MethodPut, - Body: `{ - "name": "route1", - "uri": "/hello", - "labels": { - "build":"16", - "env":"production", - "version":"v2" - }, - "upstream": { - "type": "roundrobin", - "nodes": [{ - "host": "` + base.UpstreamIp + `", - "port": 1980, - "weight": 1 - }] - } - }`, - Headers: map[string]string{"Authorization": base.GetToken()}, - ExpectStatus: http.StatusOK, - }), - Entry("create consumer", base.HttpTestCase{ - Object: base.ManagerApiExpect(), - Path: "/apisix/admin/consumers/c1", - Method: http.MethodPut, - Body: `{ - "username": "c1", - "plugins": { - "key-auth": { - "key": "auth-one" - } - }, - "labels": { - "build":"16", - "env":"production", - "version":"v3" - }, - "desc": "test description" - }`, - Headers: map[string]string{"Authorization": base.GetToken()}, - ExpectStatus: http.StatusOK, - }), - Entry("create upstream", base.HttpTestCase{ - Object: base.ManagerApiExpect(), - Method: http.MethodPut, - Path: "/apisix/admin/upstreams/u1", - Body: `{ - "nodes": [{ - "host": "` + base.UpstreamIp + `", - "port": 1980, - "weight": 1 - }], - "labels": { - "build":"17", - "env":"production", - "version":"v2" - }, - "type": "roundrobin" - }`, - Headers: map[string]string{"Authorization": base.GetToken()}, - ExpectStatus: http.StatusOK, - }), - Entry("create service", base.HttpTestCase{ - Object: base.ManagerApiExpect(), - Method: http.MethodPost, - Path: "/apisix/admin/services", - Body: `{ - "id": "s1", - "plugins": { - "limit-count": { - "count": 2, - "time_window": 60, - "rejected_code": 503, - "key": "remote_addr", - "policy": "local" - } - }, - "upstream": { - "type": "roundrobin", - "nodes": [{ - "host": "39.97.63.215", - "port": 80, - "weight": 1 - }] - }, - "labels": { - "build":"16", - "env":"production", - "version":"v2", - "extra": "test" - } - }`, - Headers: map[string]string{"Authorization": base.GetToken()}, - ExpectStatus: http.StatusOK, - }), - Entry("create plugin_config", base.HttpTestCase{ - Object: base.ManagerApiExpect(), - Method: http.MethodPut, - Path: "/apisix/admin/plugin_configs/1", - Body: `{ - "plugins": { - "response-rewrite": { - "headers": { - "X-VERSION":"22.0" - } - } - }, - "labels": { - "version": "v2", - "build": "17", - "extra": "test" - } - }`, - Headers: map[string]string{"Authorization": base.GetToken()}, - ExpectStatus: http.StatusOK, - }), - Entry("get route label", base.HttpTestCase{ - Object: base.ManagerApiExpect(), - Method: http.MethodGet, - Headers: map[string]string{"Authorization": base.GetToken()}, - Path: "/apisix/admin/labels/route", - ExpectStatus: http.StatusOK, - ExpectBody: "{\"build\":\"16\"},{\"env\":\"production\"},{\"version\":\"v2\"}", - Sleep: base.SleepTime, - }), - Entry("get consumer label", base.HttpTestCase{ - Object: base.ManagerApiExpect(), - Method: http.MethodGet, - Headers: map[string]string{"Authorization": base.GetToken()}, - Path: "/apisix/admin/labels/consumer", - ExpectStatus: http.StatusOK, - ExpectBody: "{\"build\":\"16\"},{\"env\":\"production\"},{\"version\":\"v3\"}", - }), - Entry("get upstream label", base.HttpTestCase{ - Object: base.ManagerApiExpect(), - Method: http.MethodGet, - Headers: map[string]string{"Authorization": base.GetToken()}, - Path: "/apisix/admin/labels/upstream", - ExpectStatus: http.StatusOK, - ExpectBody: "{\"build\":\"17\"},{\"env\":\"production\"},{\"version\":\"v2\"}", - }), - Entry("get service label", base.HttpTestCase{ - Object: base.ManagerApiExpect(), - Method: http.MethodGet, - Headers: map[string]string{"Authorization": base.GetToken()}, - Path: "/apisix/admin/labels/service", - ExpectStatus: http.StatusOK, - ExpectBody: "{\"build\":\"16\"},{\"env\":\"production\"},{\"extra\":\"test\"},{\"version\":\"v2\"}", - }), - Entry("get plugin_config label", base.HttpTestCase{ - Object: base.ManagerApiExpect(), - Method: http.MethodGet, - Headers: map[string]string{"Authorization": base.GetToken()}, - Path: "/apisix/admin/labels/plugin_config", - ExpectStatus: http.StatusOK, - ExpectBody: "{\"build\":\"17\"},{\"extra\":\"test\"},{\"version\":\"v2\"}", - }), - Entry("update plugin_config", base.HttpTestCase{ - Object: base.ManagerApiExpect(), - Method: http.MethodPut, - Path: "/apisix/admin/plugin_configs/1", - Body: `{ - "plugins": { - "response-rewrite": { - "headers": { - "X-VERSION":"22.0" - } - } - }, - "labels": { - "version": "v3", - "build": "16", - "extra": "test" - } - }`, - Headers: map[string]string{"Authorization": base.GetToken()}, - ExpectStatus: http.StatusOK, - }), - Entry("get plugin_config label again to verify update", base.HttpTestCase{ - Object: base.ManagerApiExpect(), - Method: http.MethodGet, - Headers: map[string]string{"Authorization": base.GetToken()}, - Path: "/apisix/admin/labels/plugin_config", - ExpectStatus: http.StatusOK, - ExpectBody: "{\"build\":\"16\"},{\"extra\":\"test\"},{\"version\":\"v3\"}", - Sleep: base.SleepTime, - }), - Entry("get all label", base.HttpTestCase{ - Object: base.ManagerApiExpect(), - Method: http.MethodGet, - Headers: map[string]string{"Authorization": base.GetToken()}, - Path: "/apisix/admin/labels/all", - ExpectStatus: http.StatusOK, - ExpectBody: "{\"build\":\"16\"},{\"build\":\"17\"},{\"env\":\"production\"},{\"extra\":\"test\"},{\"version\":\"v2\"},{\"version\":\"v3\"}", - }), - Entry("get label with page", base.HttpTestCase{ - Object: base.ManagerApiExpect(), - Method: http.MethodGet, - Query: "page=1&page_size=1", - Headers: map[string]string{"Authorization": base.GetToken()}, - Path: "/apisix/admin/labels/all", - ExpectStatus: http.StatusOK, - ExpectBody: "{\"build\":\"16\"}", - }), - Entry("get label with page", base.HttpTestCase{ - Object: base.ManagerApiExpect(), - Method: http.MethodGet, - Query: "page=3&page_size=1", - Headers: map[string]string{"Authorization": base.GetToken()}, - Path: "/apisix/admin/labels/all", - ExpectStatus: http.StatusOK, - ExpectBody: "{\"env\":\"production\"}", - }), - Entry("get labels (key = build)", base.HttpTestCase{ - Object: base.ManagerApiExpect(), - Method: http.MethodGet, - Headers: map[string]string{"Authorization": base.GetToken()}, - Query: "label=build", - Path: "/apisix/admin/labels/all", - ExpectStatus: http.StatusOK, - ExpectBody: "{\"build\":\"16\"},{\"build\":\"17\"}", - }), - Entry("get labels with the same key (key = build)", base.HttpTestCase{ - Object: base.ManagerApiExpect(), - Method: http.MethodGet, - Headers: map[string]string{"Authorization": base.GetToken()}, - Query: "label=build:16,build:17", - Path: "/apisix/admin/labels/all", - ExpectStatus: http.StatusOK, - ExpectBody: "{\"build\":\"16\"},{\"build\":\"17\"}", - }), - Entry("get labels (key = build) with page", base.HttpTestCase{ - Object: base.ManagerApiExpect(), - Method: http.MethodGet, - Headers: map[string]string{"Authorization": base.GetToken()}, - Query: "label=build&page=2&page_size=1", - Path: "/apisix/admin/labels/all", - ExpectStatus: http.StatusOK, - ExpectBody: "{\"build\":\"17\"}", - }), - Entry("get labels with same key (key = build) and page", base.HttpTestCase{ - Object: base.ManagerApiExpect(), - Method: http.MethodGet, - Headers: map[string]string{"Authorization": base.GetToken()}, - Query: "label=build:16,build:17&page=1&page_size=2", - Path: "/apisix/admin/labels/all", - ExpectStatus: http.StatusOK, - ExpectBody: "{\"build\":\"16\"},{\"build\":\"17\"}", - }), - Entry("get labels with same key (key = build) and page", base.HttpTestCase{ - Object: base.ManagerApiExpect(), - Method: http.MethodGet, - Headers: map[string]string{"Authorization": base.GetToken()}, - Query: "label=build:16,build:17&page=2&page_size=1", - Path: "/apisix/admin/labels/all", - ExpectStatus: http.StatusOK, - ExpectBody: "{\"build\":\"17\"}", - }), - Entry("get labels (key = build && env = production)", base.HttpTestCase{ - Object: base.ManagerApiExpect(), - Method: http.MethodGet, - Headers: map[string]string{"Authorization": base.GetToken()}, - Query: "label=build,env:production", - Path: "/apisix/admin/labels/all", - ExpectStatus: http.StatusOK, - ExpectBody: "{\"build\":\"16\"},{\"build\":\"17\"},{\"env\":\"production\"}", - }), - Entry("get labels (build=16 | 17 and env = production)", base.HttpTestCase{ - Object: base.ManagerApiExpect(), - Method: http.MethodGet, - Headers: map[string]string{"Authorization": base.GetToken()}, - Query: "label=build:16,build:17,env:production", - Path: "/apisix/admin/labels/all", - ExpectStatus: http.StatusOK, - ExpectBody: "{\"build\":\"16\"},{\"build\":\"17\"},{\"env\":\"production\"}", - }), - Entry("get labels (key = build && env = production) with page", base.HttpTestCase{ - Object: base.ManagerApiExpect(), - Method: http.MethodGet, - Headers: map[string]string{"Authorization": base.GetToken()}, - Query: "label=build,env:production&page=3&page_size=1", - Path: "/apisix/admin/labels/all", - ExpectStatus: http.StatusOK, - ExpectBody: "{\"env\":\"production\"}", - }), - Entry("delete route", base.HttpTestCase{ - Object: base.ManagerApiExpect(), - Method: http.MethodDelete, - Path: "/apisix/admin/routes/r1", - Headers: map[string]string{"Authorization": base.GetToken()}, - ExpectStatus: http.StatusOK, - }), - Entry("delete consumer", base.HttpTestCase{ - Object: base.ManagerApiExpect(), - Method: http.MethodDelete, - Path: "/apisix/admin/consumers/c1", - Headers: map[string]string{"Authorization": base.GetToken()}, - ExpectStatus: http.StatusOK, - }), - Entry("delete service", base.HttpTestCase{ - Object: base.ManagerApiExpect(), - Method: http.MethodDelete, - Path: "/apisix/admin/services/s1", - Headers: map[string]string{"Authorization": base.GetToken()}, - ExpectStatus: http.StatusOK, - }), - Entry("delete upstream", base.HttpTestCase{ - Object: base.ManagerApiExpect(), - Method: http.MethodDelete, - Path: "/apisix/admin/upstreams/u1", - Headers: map[string]string{"Authorization": base.GetToken()}, - ExpectStatus: http.StatusOK, - }), - Entry("delete plugin_config", base.HttpTestCase{ - Object: base.ManagerApiExpect(), - Method: http.MethodDelete, - Path: "/apisix/admin/plugin_configs/1", - Headers: map[string]string{"Authorization": base.GetToken()}, - ExpectStatus: http.StatusOK, - }), -) diff --git a/api/test/e2e/middlewares/gzip_test.go b/api/test/e2e/middlewares/gzip_test.go deleted file mode 100644 index 745265b348..0000000000 --- a/api/test/e2e/middlewares/gzip_test.go +++ /dev/null @@ -1,37 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one or more - * contributor license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright ownership. - * The ASF licenses this file to You under the Apache License, Version 2.0 - * (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package middlewares_test - -import ( - "net/http" - - . "github.com/onsi/ginkgo/v2" - - "github.com/apisix/manager-api/test/e2e/base" -) - -var _ = Describe("Gzip enable", func() { - It("get index.html", func() { - base.RunTestCase(base.HttpTestCase{ - Object: base.ManagerApiExpect(), - Method: http.MethodGet, - Path: "/", - Headers: map[string]string{"Accept-Encoding": "gzip, deflate, br"}, - ExpectHeaders: map[string]string{"Content-Encoding": "gzip"}, - }) - }) -}) diff --git a/api/test/e2e/middlewares/invalid_request_test.go b/api/test/e2e/middlewares/invalid_request_test.go deleted file mode 100644 index 283b122d44..0000000000 --- a/api/test/e2e/middlewares/invalid_request_test.go +++ /dev/null @@ -1,42 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one or more - * contributor license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright ownership. - * The ASF licenses this file to You under the Apache License, Version 2.0 - * (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package middlewares_test - -import ( - "net/http" - - . "github.com/onsi/ginkgo/v2" - - "github.com/apisix/manager-api/test/e2e/base" -) - -var _ = Describe("Invalid Request", func() { - It("double dot in URL path (arbitrary file index)", func() { - base.RunTestCase(base.HttpTestCase{ - Object: base.ManagerApiExpect(), - Method: http.MethodGet, - Path: "/../../../../etc/hosts", - ExpectStatus: http.StatusForbidden, - }) - base.RunTestCase(base.HttpTestCase{ - Object: base.ManagerApiExpect(), - Method: http.MethodGet, - Path: "/.%2e/%2e%2e/../etc/hosts", - ExpectStatus: http.StatusForbidden, - }) - }) -}) diff --git a/api/test/e2e/middlewares/middlewares_suite_test.go b/api/test/e2e/middlewares/middlewares_suite_test.go deleted file mode 100644 index b4b913ad3f..0000000000 --- a/api/test/e2e/middlewares/middlewares_suite_test.go +++ /dev/null @@ -1,36 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one or more - * contributor license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright ownership. - * The ASF licenses this file to You under the Apache License, Version 2.0 - * (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package middlewares_test - -import ( - "testing" - "time" - - "github.com/apisix/manager-api/test/e2e/base" - . "github.com/onsi/ginkgo/v2" - . "github.com/onsi/gomega" -) - -func TestMiddlewares(t *testing.T) { - RegisterFailHandler(Fail) - RunSpecs(t, "Middlewares Suite") -} - -var _ = BeforeSuite(func() { - base.CleanAllResource() - time.Sleep(base.SleepTime) -}) diff --git a/api/test/e2e/migrate/migrate_suite_test.go b/api/test/e2e/migrate/migrate_suite_test.go deleted file mode 100644 index 1e632ec615..0000000000 --- a/api/test/e2e/migrate/migrate_suite_test.go +++ /dev/null @@ -1,38 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one or more - * contributor license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright ownership. - * The ASF licenses this file to You under the Apache License, Version 2.0 - * (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package migrate_test - -import ( - "testing" - "time" - - . "github.com/onsi/ginkgo/v2" - . "github.com/onsi/gomega" - - "github.com/apisix/manager-api/test/e2e/base" -) - -func TestMigrate(t *testing.T) { - RegisterFailHandler(Fail) - RunSpecs(t, "Migrate Suite") -} - -var _ = BeforeSuite(func() { - base.CleanAllResource() - time.Sleep(base.SleepTime) -}) diff --git a/api/test/e2e/migrate/migrate_test.go b/api/test/e2e/migrate/migrate_test.go deleted file mode 100644 index 5908537244..0000000000 --- a/api/test/e2e/migrate/migrate_test.go +++ /dev/null @@ -1,317 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one or more - * contributor license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright ownership. - * The ASF licenses this file to You under the Apache License, Version 2.0 - * (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package migrate_test - -import ( - "bytes" - "encoding/binary" - "encoding/json" - "hash/crc32" - "net/http" - "time" - - . "github.com/onsi/ginkgo/v2" - . "github.com/onsi/gomega" - - "github.com/apisix/manager-api/test/e2e/base" -) - -const ( - checksumLength = 4 // 4 bytes (uint32) -) - -type AllData struct { - Consumers []interface{} - Routes []interface{} - Services []interface{} - SSLs []interface{} - Upstreams []interface{} - Scripts []interface{} - GlobalPlugins []interface{} - PluginConfigs []interface{} -} - -type response struct { - Code int `json:"Code"` - Message string `json:"Message"` - Data struct { - ConflictItems AllData `json:"ConflictItems"` - } `json:"Data"` -} - -var _ = Describe("Migrate", func() { - var exportData []byte - - DescribeTable("Prepare config data", - func(tc base.HttpTestCase) { - base.RunTestCase(tc) - }, - Entry("create test route", base.HttpTestCase{ - Object: base.ManagerApiExpect(), - Method: http.MethodPut, - Path: "/apisix/admin/routes/r1", - Body: `{ - "name": "route", - "uri": "/hello_", - "upstream": { - "nodes": { - "` + base.UpstreamIp + `:1980": 1 - }, - "type": "roundrobin" - } - }`, - Headers: map[string]string{"Authorization": base.GetToken()}, - ExpectStatus: http.StatusOK, - }), - Entry("create test upstream", base.HttpTestCase{ - Object: base.ManagerApiExpect(), - Method: http.MethodPut, - Path: "/apisix/admin/upstreams/u1", - Body: `{ - "name": "upstream", - "nodes": [ - { - "host": "` + base.UpstreamIp + `", - "port": 1980, - "weight": 1 - } - ], - "type": "roundrobin" - }`, - Headers: map[string]string{"Authorization": base.GetToken()}, - ExpectStatus: http.StatusOK, - }), - Entry("create test service", base.HttpTestCase{ - Object: base.ManagerApiExpect(), - Method: http.MethodPut, - Path: "/apisix/admin/services/s1", - Body: `{ - "name": "service", - "upstream": { - "nodes": [ - { - "host": "` + base.UpstreamIp + `", - "port": 1980, - "weight": 1 - }, - { - "host": "` + base.UpstreamIp + `", - "port": 1981, - "weight": 2 - }, - { - "host": "` + base.UpstreamIp + `", - "port": 1982, - "weight": 3 - } - ], - "type": "roundrobin" - } - }`, - Headers: map[string]string{"Authorization": base.GetToken()}, - ExpectStatus: http.StatusOK, - Sleep: time.Second * 1, - }), - Entry("migrate export auth test", base.HttpTestCase{ - Object: base.ManagerApiExpect(), - Method: http.MethodPost, - Path: "/apisix/admin/migrate/export", - ExpectStatus: http.StatusUnauthorized, - ExpectBody: "request unauthorized", - Sleep: base.SleepTime, - }), - Entry("migrate import auth test", base.HttpTestCase{ - Object: base.ManagerApiExpect(), - Method: http.MethodPost, - Path: "/apisix/admin/migrate/import", - ExpectStatus: http.StatusUnauthorized, - ExpectBody: "request unauthorized", - Sleep: base.SleepTime, - }), - ) - - It("export config success", func() { - req := base.ManagerApiExpect().GET("/apisix/admin/migrate/export") - req.WithHeader("Authorization", base.GetToken()) - resp := req.Expect() - resp.Status(http.StatusOK) - exportData = []byte(resp.Body().Raw()) - data := exportData[:len(exportData)-checksumLength] - checksum := binary.BigEndian.Uint32(exportData[len(exportData)-checksumLength:]) - Expect(checksum).Should(Equal(crc32.ChecksumIEEE(data))) - }) - - It("import config conflict and return", func() { - req := base.ManagerApiExpect().POST("/apisix/admin/migrate/import") - buffer := bytes.NewBuffer(exportData) - req.WithMultipart().WithForm(map[string]string{"mode": "return"}) - req.WithMultipart().WithFile("file", "apisix-config.bak", buffer) - req.WithHeader("Authorization", base.GetToken()) - resp := req.Expect() - resp.Status(http.StatusOK) - rsp := &response{} - err := json.Unmarshal([]byte(resp.Body().Raw()), rsp) - Expect(err).Should(BeNil()) - Expect(rsp.Code).Should(Equal(20001)) - Expect(len(rsp.Data.ConflictItems.Routes)).Should(Equal(1)) - Expect(len(rsp.Data.ConflictItems.Upstreams)).Should(Equal(1)) - Expect(len(rsp.Data.ConflictItems.Services)).Should(Equal(1)) - }) - - It("import config conflict and skip", func() { - req := base.ManagerApiExpect().POST("/apisix/admin/migrate/import") - buffer := bytes.NewBuffer(exportData) - req.WithMultipart().WithForm(map[string]string{"mode": "skip"}) - req.WithMultipart().WithFile("file", "apisix-config.bak", buffer) - req.WithHeader("Authorization", base.GetToken()) - resp := req.Expect() - resp.Status(http.StatusOK) - rsp := &response{} - err := json.Unmarshal([]byte(resp.Body().Raw()), rsp) - Expect(err).Should(BeNil()) - Expect(rsp.Code).Should(Equal(0)) - }) - - It("import config conflict and overwrite", func() { - req := base.ManagerApiExpect().POST("/apisix/admin/migrate/import") - buffer := bytes.NewBuffer(exportData) - req.WithMultipart().WithForm(map[string]string{"mode": "overwrite"}) - req.WithMultipart().WithFile("file", "apisix-config.bak", buffer) - req.WithHeader("Authorization", base.GetToken()) - resp := req.Expect() - resp.Status(http.StatusOK) - rsp := &response{} - err := json.Unmarshal([]byte(resp.Body().Raw()), rsp) - Expect(err).Should(BeNil()) - Expect(rsp.Code).Should(Equal(0)) - }) - - It("request hit route r1", func() { - base.RunTestCase(base.HttpTestCase{ - Object: base.APISIXExpect(), - Method: http.MethodGet, - Path: "/hello_", - ExpectStatus: http.StatusOK, - ExpectBody: "hello world", - Sleep: base.SleepTime, - }) - }) - - DescribeTable("Cleanup config data", - func(tc base.HttpTestCase) { - base.RunTestCase(tc) - }, - Entry("remove test route", base.HttpTestCase{ - Object: base.ManagerApiExpect(), - Method: http.MethodDelete, - Path: "/apisix/admin/routes/r1", - Headers: map[string]string{"Authorization": base.GetToken()}, - ExpectStatus: http.StatusOK, - }), - Entry("remove test upstream", base.HttpTestCase{ - Object: base.ManagerApiExpect(), - Method: http.MethodDelete, - Path: "/apisix/admin/upstreams/u1", - Headers: map[string]string{"Authorization": base.GetToken()}, - ExpectStatus: http.StatusOK, - }), - Entry("remove test service", base.HttpTestCase{ - Object: base.ManagerApiExpect(), - Method: http.MethodDelete, - Path: "/apisix/admin/services/s1", - Headers: map[string]string{"Authorization": base.GetToken()}, - ExpectStatus: http.StatusOK, - }), - ) - - It("delete imported route failed", func() { - base.RunTestCase(base.HttpTestCase{ - Object: base.ManagerApiExpect(), - Method: http.MethodDelete, - Path: "/apisix/admin/routes/r1", - Headers: map[string]string{"Authorization": base.GetToken()}, - ExpectStatus: http.StatusNotFound, - }) - }) - - It("request route r1 not found", func() { - base.RunTestCase(base.HttpTestCase{ - Object: base.APISIXExpect(), - Method: http.MethodGet, - Path: "/hello_", - ExpectStatus: http.StatusNotFound, - Sleep: base.SleepTime, - }) - }) - - It("import config success", func() { - req := base.ManagerApiExpect().POST("/apisix/admin/migrate/import") - buffer := bytes.NewBuffer(exportData) - req.WithMultipart().WithForm(map[string]string{"mode": "return"}) - req.WithMultipart().WithFile("file", "apisix-config.bak", buffer) - req.WithHeader("Authorization", base.GetToken()) - resp := req.Expect() - resp.Status(http.StatusOK) - rsp := &response{} - err := json.Unmarshal([]byte(resp.Body().Raw()), rsp) - Expect(err).Should(BeNil()) - Expect(rsp.Code).Should(Equal(0)) - }) - - It("request hit route r1", func() { - base.RunTestCase(base.HttpTestCase{ - Object: base.APISIXExpect(), - Method: http.MethodGet, - Path: "/hello_", - ExpectStatus: http.StatusOK, - ExpectBody: "hello world", - Sleep: base.SleepTime, - }) - }) - - It("delete imported route success", func() { - base.RunTestCase(base.HttpTestCase{ - Object: base.ManagerApiExpect(), - Method: http.MethodDelete, - Path: "/apisix/admin/routes/r1", - Headers: map[string]string{"Authorization": base.GetToken()}, - ExpectStatus: http.StatusOK, - }) - }) - - It("delete imported upstream success", func() { - base.RunTestCase(base.HttpTestCase{ - Object: base.ManagerApiExpect(), - Method: http.MethodDelete, - Path: "/apisix/admin/upstreams/u1", - Headers: map[string]string{"Authorization": base.GetToken()}, - ExpectStatus: http.StatusOK, - }) - }) - - It("delete imported service success", func() { - base.RunTestCase(base.HttpTestCase{ - Object: base.ManagerApiExpect(), - Method: http.MethodDelete, - Path: "/apisix/admin/services/s1", - Headers: map[string]string{"Authorization": base.GetToken()}, - ExpectStatus: http.StatusOK, - }) - }) - -}) diff --git a/api/test/e2e/oidc/oidc_suite_test.go b/api/test/e2e/oidc/oidc_suite_test.go deleted file mode 100644 index fb4ccafad8..0000000000 --- a/api/test/e2e/oidc/oidc_suite_test.go +++ /dev/null @@ -1,36 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one or more - * contributor license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright ownership. - * The ASF licenses this file to You under the Apache License, Version 2.0 - * (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package oidc_test - -import ( - "testing" - "time" - - "github.com/apisix/manager-api/test/e2e/base" - . "github.com/onsi/ginkgo/v2" - . "github.com/onsi/gomega" -) - -func TestOidc(t *testing.T) { - RegisterFailHandler(Fail) - RunSpecs(t, "OIDC Suite") -} - -var _ = BeforeSuite(func() { - base.CleanAllResource() - time.Sleep(base.SleepTime) -}) diff --git a/api/test/e2e/oidc/oidc_test.go b/api/test/e2e/oidc/oidc_test.go deleted file mode 100644 index b42c94bc02..0000000000 --- a/api/test/e2e/oidc/oidc_test.go +++ /dev/null @@ -1,273 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one or more - * contributor license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright ownership. - * The ASF licenses this file to You under the Apache License, Version 2.0 - * (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package oidc_test - -import ( - "bytes" - "context" - "io/ioutil" - "math/rand" - "net/http" - "net/url" - "strings" - "time" - - "github.com/Nerzal/gocloak/v11" - "github.com/PuerkitoBio/goquery" - . "github.com/onsi/ginkgo/v2" - . "github.com/onsi/gomega" -) - -var _ = Describe("Oidc-Login", func() { - - var ( - OidcCookie []http.Cookie - ) - - Context("test apisix/admin/oidc/login", func() { - It("should return status-code 302", func() { - statusCode, err := accessOidcLogin() - Expect(err).ShouldNot(HaveOccurred(), "do request") - Expect(statusCode).To(Equal(http.StatusFound)) - }) - }) - - Context("test apisix/admin/oidc/callback", func() { - It("should return status-code 200", func() { - statusCode, err := accessOidcCallback(&OidcCookie) - Expect(err).ShouldNot(HaveOccurred(), "do request") - Expect(statusCode).To(Equal(http.StatusOK)) - }) - }) - - Context("access apisix/admin/routes with cookie", func() { - It("should return status-code 200", func() { - statusCode, err := accessRoutesWithCookie(true, OidcCookie) - Expect(err).ShouldNot(HaveOccurred(), "do request") - Expect(statusCode).To(Equal(http.StatusOK)) - }) - }) - - Context("access apisix/admin/oidc/logout with cookie", func() { - It("should return status-code 200", func() { - statusCode, err := accessOidcLogoutWithCookie(true, OidcCookie) - Expect(err).ShouldNot(HaveOccurred(), "do request") - Expect(statusCode).To(Equal(http.StatusOK)) - }) - }) - - Context("access apisix/admin/routes with invalid cookie", func() { - It("should return status-code 401", func() { - statusCode, err := accessRoutesWithCookie(false, OidcCookie) - Expect(err).ShouldNot(HaveOccurred(), "do request") - Expect(statusCode).To(Equal(http.StatusUnauthorized)) - }) - }) - - Context("access apisix/admin/oidc/logout with invalid cookie", func() { - It("should return status-code 403", func() { - statusCode, err := accessOidcLogoutWithCookie(false, OidcCookie) - Expect(err).ShouldNot(HaveOccurred(), "do request") - Expect(statusCode).To(Equal(http.StatusForbidden)) - }) - }) -}) - -func accessOidcLogin() (int, error) { - var err error - var req *http.Request - var resp *http.Response - var Client = &http.Client{ - CheckRedirect: func(req *http.Request, via []*http.Request) error { - return http.ErrUseLastResponse - }, - } - createClientAndUser() - req, _ = http.NewRequest("GET", "http://127.0.0.1:9000/apisix/admin/oidc/login", nil) - resp, err = Client.Do(req) - if err != nil { - return 0, err - } - - // return status-code - return resp.StatusCode, err -} - -func createClientAndUser() string { - client := gocloak.NewClient("http://127.0.0.1:8080") - ctx := context.Background() - token, _ := client.LoginAdmin(ctx, "admin", "admin", "master") - - redirectURIs := []string{"http://127.0.0.1:9000/*"} - _, _ = client.CreateClient(ctx, token.AccessToken, "master", gocloak.Client{ - ClientID: gocloak.StringP("dashboard"), - Secret: gocloak.StringP("dashboard"), - RedirectURIs: &redirectURIs, - }) - - username := GetRandomString(3) - user := gocloak.User{ - FirstName: gocloak.StringP(GetRandomString(3)), - LastName: gocloak.StringP(GetRandomString(3)), - Email: gocloak.StringP(GetRandomString(3)), - Enabled: gocloak.BoolP(true), - Username: gocloak.StringP(username), - } - - id, _ := client.CreateUser(ctx, token.AccessToken, "master", user) - - _ = client.SetPassword(ctx, token.AccessToken, id, "master", "password", false) - return username -} - -func accessOidcCallback(OidcCookie *[]http.Cookie) (int, error) { - - var authenticationUrl string - var loginUrl string - var err error - var req *http.Request - var resp *http.Response - var client = &http.Client{ - CheckRedirect: func(req *http.Request, via []*http.Request) error { - return http.ErrUseLastResponse - }, - } - - username := createClientAndUser() - - // access apisix/admin/oidc/login to get the authentication-url - req, _ = http.NewRequest("GET", "http://127.0.0.1:9000/apisix/admin/oidc/login", nil) - resp, err = client.Do(req) - if err != nil { - return 0, err - } - authenticationUrl = resp.Header.Get("Location") - - // access the authentication-url - req, _ = http.NewRequest("GET", authenticationUrl, nil) - resp, err = client.Do(req) - if err != nil { - return 0, err - } - - // get the login-url from html - body, _ := ioutil.ReadAll(resp.Body) - dom, _ := goquery.NewDocumentFromReader(strings.NewReader(string(body))) - dom.Find("#kc-form-login").Each(func(i int, selection *goquery.Selection) { - loginUrl = selection.Get(0).Attr[2].Val - }) - - // set username & password - formValues := url.Values{} - formValues.Set("username", username) - formValues.Set("password", "password") - formDataStr := formValues.Encode() - formDataBytes := []byte(formDataStr) - formBytesReader := bytes.NewReader(formDataBytes) - //fmt.Printf("loginUrl: %s/n", loginUrl) - req, _ = http.NewRequest("POST", loginUrl, formBytesReader) - req.Header.Set("Content-Type", "application/x-www-form-urlencoded") - // set cookies - cookies := resp.Cookies() - for _, cookie := range cookies { - req.AddCookie(cookie) - } - - // access the login-url to login - resp, err = client.Do(req) - if err != nil { - return 0, err - } - - // access apisix/admin/oidc/login with code - callbackUrl := resp.Header.Get("Location") - req, _ = http.NewRequest("GET", callbackUrl, nil) - resp, err = client.Do(req) - if err != nil { - return 0, err - } - - // save cookie - cookies = resp.Cookies() - for _, cookie := range cookies { - *OidcCookie = append(*OidcCookie, *cookie) - } - - // return status-code - return resp.StatusCode, err -} - -func accessRoutesWithCookie(setCookie bool, OidcCookie []http.Cookie) (int, error) { - var err error - var req *http.Request - var resp *http.Response - var client http.Client - - req, _ = http.NewRequest("GET", "http://127.0.0.1:9000/apisix/admin/routes", nil) - - // set cookie or not - if setCookie { - for _, cookie := range OidcCookie { - req.AddCookie(&cookie) - } - } - - // access apisix/admin/routes - resp, err = client.Do(req) - if err != nil { - return 0, err - } - - // return status-code - return resp.StatusCode, err -} - -func accessOidcLogoutWithCookie(setCookie bool, OidcCookie []http.Cookie) (int, error) { - var err error - var req *http.Request - var resp *http.Response - var client http.Client - - req, _ = http.NewRequest("GET", "http://127.0.0.1:9000/apisix/admin/oidc/logout", nil) - - // set cookie or not - if setCookie { - for _, cookie := range OidcCookie { - req.AddCookie(&cookie) - } - } - - // access apisix/admin/oidc/logout - resp, err = client.Do(req) - if err != nil { - return 0, err - } - - // return status-code - return resp.StatusCode, err -} - -func GetRandomString(l int) string { - str := "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ" - bytes := []byte(str) - var result []byte - r := rand.New(rand.NewSource(time.Now().UnixNano())) - for i := 0; i < l; i++ { - result = append(result, bytes[r.Intn(len(bytes))]) - } - return string(result) -} diff --git a/api/test/e2e/plugin_config/plugin_config_suite_test.go b/api/test/e2e/plugin_config/plugin_config_suite_test.go deleted file mode 100644 index 18db3a457d..0000000000 --- a/api/test/e2e/plugin_config/plugin_config_suite_test.go +++ /dev/null @@ -1,35 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one or more - * contributor license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright ownership. - * The ASF licenses this file to You under the Apache License, Version 2.0 - * (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package plugin_config_test - -import ( - "testing" - "time" - - . "github.com/onsi/ginkgo/v2" - - "github.com/apisix/manager-api/test/e2e/base" -) - -func TestPluginConfig(t *testing.T) { - RunSpecs(t, "Plugin Config Suite") -} - -var _ = BeforeSuite(func() { - base.CleanAllResource() - time.Sleep(base.SleepTime) -}) diff --git a/api/test/e2e/plugin_config/plugin_config_test.go b/api/test/e2e/plugin_config/plugin_config_test.go deleted file mode 100644 index 205de9ff91..0000000000 --- a/api/test/e2e/plugin_config/plugin_config_test.go +++ /dev/null @@ -1,242 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one or more - * contributor license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright ownership. - * The ASF licenses this file to You under the Apache License, Version 2.0 - * (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package plugin_config_test - -import ( - "net/http" - - . "github.com/onsi/ginkgo/v2" - - "github.com/apisix/manager-api/test/e2e/base" -) - -var _ = DescribeTable("Plugin Config", - func(tc base.HttpTestCase) { - base.RunTestCase(tc) - }, - Entry("make sure the route doesn't exist", base.HttpTestCase{ - Object: base.APISIXExpect(), - Method: http.MethodGet, - Path: "/hello", - ExpectStatus: http.StatusNotFound, - ExpectBody: `{"error_msg":"404 Route Not Found"}`, - }), - Entry("create plugin config", base.HttpTestCase{ - Object: base.ManagerApiExpect(), - Path: "/apisix/admin/plugin_configs/1", - Method: http.MethodPut, - Body: `{ - "plugins": { - "response-rewrite": { - "headers": { - "X-VERSION":"1.0" - } - }, - "uri-blocker": { - "block_rules": ["select.+(from|limit)", "(?:(union(.*?)select))"] - } - }, - "labels": { - "version": "v1", - "build": "16" - } - }`, - Headers: map[string]string{"Authorization": base.GetToken()}, - ExpectStatus: http.StatusOK, - }), - Entry("create plugin config by Post", base.HttpTestCase{ - Object: base.ManagerApiExpect(), - Path: "/apisix/admin/plugin_configs", - Method: http.MethodPost, - Body: `{ - "id": "2", - "plugins": { - "response-rewrite": { - "headers": { - "X-VERSION":"22.0" - } - } - }, - "labels": { - "version": "v2", - "build": "17", - "extra": "test" - } - }`, - Headers: map[string]string{"Authorization": base.GetToken()}, - ExpectStatus: http.StatusOK, - }), - Entry("get plugin config", base.HttpTestCase{ - Object: base.ManagerApiExpect(), - Path: "/apisix/admin/plugin_configs/1", - Method: http.MethodGet, - Headers: map[string]string{"Authorization": base.GetToken()}, - ExpectStatus: http.StatusOK, - ExpectBody: `"plugins":{"response-rewrite":{"headers":{"X-VERSION":"1.0"}},"uri-blocker":{"block_rules":["select.+(from|limit)","(?:(union(.*?)select))"]}}`, - Sleep: base.SleepTime, - }), - Entry("search plugin_config list by label ", base.HttpTestCase{ - Object: base.ManagerApiExpect(), - Path: "/apisix/admin/plugin_configs", - Query: "label=build:16", - Method: http.MethodGet, - Headers: map[string]string{"Authorization": base.GetToken()}, - ExpectStatus: http.StatusOK, - ExpectBody: `"labels":{"build":"16","version":"v1"}`, - Sleep: base.SleepTime, - }), - Entry("search plugin_config list by label (only key)", base.HttpTestCase{ - Object: base.ManagerApiExpect(), - Path: "/apisix/admin/plugin_configs", - Query: "label=extra", - Method: http.MethodGet, - Headers: map[string]string{"Authorization": base.GetToken()}, - ExpectStatus: http.StatusOK, - ExpectBody: `"labels":{"build":"17","extra":"test","version":"v2"}`, - Sleep: base.SleepTime, - }), - Entry("create route with the plugin config created before", base.HttpTestCase{ - Object: base.ManagerApiExpect(), - Method: http.MethodPut, - Path: "/apisix/admin/routes/r1", - Body: `{ - "name": "route1", - "uri": "/hello", - "plugin_config_id": "1", - "upstream": { - "type": "roundrobin", - "nodes": [{ - "host": "` + base.UpstreamIp + `", - "port": 1981, - "weight": 1 - }] - } - }`, - Headers: map[string]string{"Authorization": base.GetToken()}, - ExpectStatus: http.StatusOK, - }), - Entry("verify route with header", base.HttpTestCase{ - Object: base.APISIXExpect(), - Method: http.MethodGet, - Path: "/hello", - ExpectStatus: http.StatusOK, - ExpectBody: "hello world", - ExpectHeaders: map[string]string{"X-VERSION": "1.0"}, - Sleep: base.SleepTime, - }), - Entry("verify route that should be blocked", base.HttpTestCase{ - Object: base.APISIXExpect(), - Method: http.MethodGet, - Path: "/hello", - Query: "name=%3Bselect%20from%20sys", - ExpectStatus: http.StatusForbidden, - ExpectHeaders: map[string]string{"X-VERSION": "1.0"}, - Sleep: base.SleepTime, - }), - Entry("update plugin config by patch", base.HttpTestCase{ - Object: base.ManagerApiExpect(), - Path: "/apisix/admin/plugin_configs/1", - Method: http.MethodPatch, - Body: `{ - "plugins": { - "response-rewrite": { - "headers": { - "X-VERSION":"2.0" - } - }, - "uri-blocker": { - "block_rules": ["none"] - } - } - }`, - Headers: map[string]string{"Authorization": base.GetToken()}, - ExpectStatus: http.StatusOK, - }), - Entry("verify patch update", base.HttpTestCase{ - Object: base.APISIXExpect(), - Method: http.MethodGet, - Path: "/hello", - ExpectStatus: http.StatusOK, - ExpectBody: "hello world", - ExpectHeaders: map[string]string{"X-VERSION": "2.0"}, - Sleep: base.SleepTime, - }), - Entry("verify patch update(should not block)", base.HttpTestCase{ - Object: base.APISIXExpect(), - Method: http.MethodGet, - Path: "/hello", - Query: "name=%3Bselect%20from%20sys", - ExpectStatus: http.StatusOK, - ExpectBody: "hello world", - ExpectHeaders: map[string]string{"X-VERSION": "2.0"}, - }), - Entry("update plugin config by sub path patch", base.HttpTestCase{ - Object: base.ManagerApiExpect(), - Path: "/apisix/admin/plugin_configs/1/plugins", - Method: http.MethodPatch, - Body: `{ - "response-rewrite": { - "headers": { - "X-VERSION":"3.0" - } - } - }`, - Headers: map[string]string{"Authorization": base.GetToken()}, - ExpectStatus: http.StatusOK, - }), - Entry("verify patch (sub path)", base.HttpTestCase{ - Object: base.APISIXExpect(), - Method: http.MethodGet, - Path: "/hello", - ExpectStatus: http.StatusOK, - ExpectBody: "hello world", - ExpectHeaders: map[string]string{"X-VERSION": "3.0"}, - Sleep: base.SleepTime, - }), - Entry("delete route", base.HttpTestCase{ - Object: base.ManagerApiExpect(), - Method: http.MethodDelete, - Path: "/apisix/admin/routes/r1", - Headers: map[string]string{"Authorization": base.GetToken()}, - ExpectStatus: http.StatusOK, - }), - Entry("delete plugin config", base.HttpTestCase{ - Object: base.ManagerApiExpect(), - Method: http.MethodDelete, - Path: "/apisix/admin/plugin_configs/1", - Headers: map[string]string{"Authorization": base.GetToken()}, - ExpectStatus: http.StatusOK, - Sleep: base.SleepTime, - }), - Entry("make sure the plugin config has been deleted", base.HttpTestCase{ - Object: base.ManagerApiExpect(), - Method: http.MethodGet, - Path: "/apisix/admin/plugin_configs/1", - Headers: map[string]string{"Authorization": base.GetToken()}, - ExpectStatus: http.StatusNotFound, - ExpectBody: `{"code":10001,"message":"data not found"`, - Sleep: base.SleepTime, - }), - Entry("make sure the route has been deleted", base.HttpTestCase{ - Object: base.APISIXExpect(), - Method: http.MethodGet, - Path: "/hello", - ExpectStatus: http.StatusNotFound, - ExpectBody: `{"error_msg":"404 Route Not Found"}`, - Sleep: base.SleepTime, - }), -) diff --git a/api/test/e2e/proto/proto_suite_test.go b/api/test/e2e/proto/proto_suite_test.go deleted file mode 100644 index a12082ea4b..0000000000 --- a/api/test/e2e/proto/proto_suite_test.go +++ /dev/null @@ -1,37 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one or more - * contributor license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright ownership. - * The ASF licenses this file to You under the Apache License, Version 2.0 - * (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package proto_test - -import ( - "testing" - "time" - - . "github.com/onsi/ginkgo/v2" - . "github.com/onsi/gomega" - - "github.com/apisix/manager-api/test/e2e/base" -) - -func TestRoute(t *testing.T) { - RegisterFailHandler(Fail) - RunSpecs(t, "Proto Suite") -} - -var _ = BeforeSuite(func() { - base.CleanAllResource() - time.Sleep(base.SleepTime) -}) diff --git a/api/test/e2e/proto/proto_test.go b/api/test/e2e/proto/proto_test.go deleted file mode 100644 index 2756d110cb..0000000000 --- a/api/test/e2e/proto/proto_test.go +++ /dev/null @@ -1,263 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one or more - * contributor license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright ownership. - * The ASF licenses this file to You under the Apache License, Version 2.0 - * (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package proto_test - -import ( - "encoding/json" - "net/http" - - . "github.com/onsi/ginkgo/v2" - . "github.com/onsi/gomega" - - "github.com/apisix/manager-api/test/e2e/base" -) - -var correctProtobuf = `syntax = "proto3"; - package helloworld; - service Greeter { - rpc SayHello (HelloRequest) returns (HelloReply) {} - } - message HelloRequest { - string name = 1; - } - message HelloReply { - string message = 1; - }` - -var _ = Describe("Proto", func() { - var _ = DescribeTable("Basic", - func(f func()) { - f() - }, - Entry("create proto success", func() { - createProtoBody := make(map[string]interface{}) - createProtoBody["id"] = 1 - createProtoBody["desc"] = "test_proto1" - createProtoBody["content"] = correctProtobuf - - _createProtoBody, err := json.Marshal(createProtoBody) - Expect(err).To(BeNil()) - - base.RunTestCase(base.HttpTestCase{ - Object: base.ManagerApiExpect(), - Method: http.MethodPost, - Path: "/apisix/admin/proto", - Body: string(_createProtoBody), - Headers: map[string]string{"Authorization": base.GetToken()}, - ExpectStatus: http.StatusOK, - }) - }), - Entry("create proto failed, id existed", func() { - createProtoBody := make(map[string]interface{}) - createProtoBody["id"] = 1 - createProtoBody["desc"] = "test_proto1" - createProtoBody["content"] = correctProtobuf - - _createProtoBody, err := json.Marshal(createProtoBody) - Expect(err).To(BeNil()) - - base.RunTestCase(base.HttpTestCase{ - Object: base.ManagerApiExpect(), - Method: http.MethodPost, - Path: "/apisix/admin/proto", - Body: string(_createProtoBody), - Headers: map[string]string{"Authorization": base.GetToken()}, - ExpectBody: "proto id exists", - ExpectStatus: http.StatusBadRequest, - }) - }), - Entry("update proto success", func() { - updateProtoBody := make(map[string]interface{}) - updateProtoBody["id"] = 1 - updateProtoBody["desc"] = "test_proto1_modify" - updateProtoBody["content"] = correctProtobuf - - _updateProtoBody, err := json.Marshal(updateProtoBody) - Expect(err).To(BeNil()) - - base.RunTestCase(base.HttpTestCase{ - Object: base.ManagerApiExpect(), - Method: http.MethodPut, - Path: "/apisix/admin/proto", - Body: string(_updateProtoBody), - Headers: map[string]string{"Authorization": base.GetToken()}, - ExpectBody: "test_proto1_modify", - ExpectStatus: http.StatusOK, - }) - }), - Entry("list proto", func() { - base.RunTestCase(base.HttpTestCase{ - Object: base.ManagerApiExpect(), - Method: http.MethodGet, - Path: "/apisix/admin/proto", - Headers: map[string]string{"Authorization": base.GetToken()}, - ExpectBody: "test_proto1_modify", - ExpectStatus: http.StatusOK, - }) - }), - Entry("get proto", func() { - base.RunTestCase(base.HttpTestCase{ - Object: base.ManagerApiExpect(), - Method: http.MethodGet, - Path: "/apisix/admin/proto/1", - Headers: map[string]string{"Authorization": base.GetToken()}, - ExpectBody: "test_proto1_modify", - ExpectStatus: http.StatusOK, - }) - }), - Entry("delete not existed proto", func() { - base.RunTestCase(base.HttpTestCase{ - Object: base.ManagerApiExpect(), - Method: http.MethodDelete, - Path: "/apisix/admin/proto/not-exist", - Headers: map[string]string{"Authorization": base.GetToken()}, - ExpectStatus: http.StatusNotFound, - }) - }), - Entry("delete proto", func() { - base.RunTestCase(base.HttpTestCase{ - Object: base.ManagerApiExpect(), - Method: http.MethodDelete, - Path: "/apisix/admin/proto/1", - Headers: map[string]string{"Authorization": base.GetToken()}, - ExpectStatus: http.StatusOK, - }) - }), - ) - - var _ = DescribeTable("Proto with grpc-transcode plugin", - func(f func()) { - f() - }, - Entry("create proto success", func() { - createProtoBody := make(map[string]interface{}) - createProtoBody["id"] = 1 - createProtoBody["desc"] = "test_proto1" - createProtoBody["content"] = correctProtobuf - - _createProtoBody, err := json.Marshal(createProtoBody) - Expect(err).To(BeNil()) - - base.RunTestCase(base.HttpTestCase{ - Object: base.ManagerApiExpect(), - Method: http.MethodPost, - Path: "/apisix/admin/proto", - Body: string(_createProtoBody), - Headers: map[string]string{"Authorization": base.GetToken()}, - ExpectStatus: http.StatusOK, - }) - }), - Entry("create route with grpc-transcode", func() { - createRouteBody := make(map[string]interface{}) - createRouteBody["id"] = 1 - createRouteBody["name"] = "test_route" - createRouteBody["uri"] = "/grpc_test" - createRouteBody["methods"] = []string{"GET", "POST"} - createRouteBody["upstream"] = map[string]interface{}{ - "nodes": []map[string]interface{}{ - { - "host": base.UpstreamGrpcIp, - "port": 50051, - "weight": 1, - }, - }, - "type": "roundrobin", - "scheme": "grpc", - } - createRouteBody["plugins"] = map[string]interface{}{ - "grpc-transcode": map[string]interface{}{ - "method": "SayHello", - "proto_id": "1", - "service": "helloworld.Greeter", - }, - } - - _createRouteBody, err := json.Marshal(createRouteBody) - Expect(err).To(BeNil()) - - base.RunTestCase(base.HttpTestCase{ - Object: base.ManagerApiExpect(), - Method: http.MethodPost, - Path: "/apisix/admin/routes", - Body: string(_createRouteBody), - Headers: map[string]string{"Authorization": base.GetToken()}, - ExpectStatus: http.StatusOK, - }) - }), - Entry("hit GET route for grpc-transcode test", func() { - base.RunTestCase(base.HttpTestCase{ - Object: base.APISIXExpect(), - Method: http.MethodGet, - Path: "/grpc_test", - Query: "name=world", - Headers: map[string]string{"Authorization": base.GetToken()}, - ExpectBody: "{\"message\":\"Hello world\"}", - ExpectStatus: http.StatusOK, - }) - }), - Entry("hit POST route for grpc-transcode test", func() { - base.RunTestCase(base.HttpTestCase{ - Object: base.APISIXExpect(), - Method: http.MethodPost, - Path: "/grpc_test", - Body: "name=world", - Headers: map[string]string{"Authorization": base.GetToken()}, - ExpectBody: "{\"message\":\"Hello world\"}", - ExpectStatus: http.StatusOK, - }) - }), - Entry("hit JSON POST route for grpc-transcode test", func() { - base.RunTestCase(base.HttpTestCase{ - Object: base.APISIXExpect(), - Method: http.MethodPost, - Path: "/grpc_test", - Body: "{\"name\": \"world\"}", - Headers: map[string]string{"Authorization": base.GetToken()}, - ExpectBody: "{\"message\":\"Hello world\"}", - ExpectStatus: http.StatusOK, - }) - }), - Entry("delete route used proto", func() { - base.RunTestCase(base.HttpTestCase{ - Object: base.ManagerApiExpect(), - Method: http.MethodDelete, - Path: "/apisix/admin/proto/1", - Headers: map[string]string{"Authorization": base.GetToken()}, - ExpectBody: "proto used check invalid: route", - ExpectStatus: http.StatusBadRequest, - }) - }), - Entry("delete conflict route", func() { - base.RunTestCase(base.HttpTestCase{ - Object: base.ManagerApiExpect(), - Method: http.MethodDelete, - Path: "/apisix/admin/routes/1", - Headers: map[string]string{"Authorization": base.GetToken()}, - ExpectStatus: http.StatusOK, - }) - }), - Entry("delete proto again", func() { - base.RunTestCase(base.HttpTestCase{ - Object: base.ManagerApiExpect(), - Method: http.MethodDelete, - Path: "/apisix/admin/proto/1", - Headers: map[string]string{"Authorization": base.GetToken()}, - ExpectStatus: http.StatusOK, - }) - }), - ) -}) diff --git a/api/test/e2e/route/host_test.go b/api/test/e2e/route/host_test.go deleted file mode 100644 index 3836883f0d..0000000000 --- a/api/test/e2e/route/host_test.go +++ /dev/null @@ -1,389 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one or more - * contributor license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright ownership. - * The ASF licenses this file to You under the Apache License, Version 2.0 - * (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package route_test - -import ( - "net/http" - - . "github.com/onsi/ginkgo/v2" - - "github.com/apisix/manager-api/test/e2e/base" -) - -var _ = Describe("Route", func() { - DescribeTable("test route with host", - func(tc base.HttpTestCase) { - base.RunTestCase(tc) - }, - Entry("invalid host", base.HttpTestCase{ - Object: base.ManagerApiExpect(), - Path: "/apisix/admin/routes/r1", - Method: http.MethodPut, - Body: `{ - "name": "route1", - "uri": "/hello_", - "host": "$%$foo.com", - "upstream": { - "nodes": { - "` + base.UpstreamIp + `:1980": 1 - }, - "type": "roundrobin" - } - }`, - Headers: map[string]string{"Authorization": base.GetToken()}, - ExpectStatus: http.StatusBadRequest, - }), - Entry("invalid hosts", base.HttpTestCase{ - Object: base.ManagerApiExpect(), - Method: http.MethodPut, - Path: "/apisix/admin/routes/r1", - Body: `{ - "name": "route1", - "uri": "/hello_", - "hosts": ["$%$foo.com", "*.bar.com"], - "upstream": { - "nodes": { - "` + base.UpstreamIp + `:1980": 1 - }, - "type": "roundrobin" - } - }`, - Headers: map[string]string{"Authorization": base.GetToken()}, - ExpectStatus: http.StatusBadRequest, - }), - Entry("create route with host and hosts together at the same time", base.HttpTestCase{ - Object: base.ManagerApiExpect(), - Method: http.MethodPut, - Path: "/apisix/admin/routes/r1", - Body: `{ - "name": "route1", - "uri": "/hello_", - "host": "github.com", - "hosts": ["foo.com", "*.bar.com"], - "upstream": { - "nodes": { - "` + base.UpstreamIp + `:1980": 1 - }, - "type": "roundrobin" - } - }`, - Headers: map[string]string{"Authorization": base.GetToken()}, - ExpectStatus: http.StatusBadRequest, - }), - Entry("hit route not created", base.HttpTestCase{ - Object: base.APISIXExpect(), - Method: http.MethodGet, - Path: "/hello_", - Headers: map[string]string{"Host": "foo.com"}, - ExpectStatus: http.StatusNotFound, - ExpectBody: "{\"error_msg\":\"404 Route Not Found\"}\n", - }), - Entry("hit route not created", base.HttpTestCase{ - Object: base.APISIXExpect(), - Method: http.MethodGet, - Path: "/hello_", - Headers: map[string]string{"Host": "$%$foo.com"}, - ExpectStatus: http.StatusNotFound, - ExpectBody: "{\"error_msg\":\"404 Route Not Found\"}\n", - }), - ) - - DescribeTable("test route with hosts", - func(tc base.HttpTestCase) { - base.RunTestCase(tc) - }, - Entry("make sure route not created", base.HttpTestCase{ - Object: base.APISIXExpect(), - Method: http.MethodGet, - Path: "/hello_", - Headers: map[string]string{"Host": "foo.com"}, - ExpectStatus: http.StatusNotFound, - ExpectBody: "{\"error_msg\":\"404 Route Not Found\"}\n", - }), - Entry("create route", base.HttpTestCase{ - Object: base.ManagerApiExpect(), - Method: http.MethodPut, - Path: "/apisix/admin/routes/r1", - Body: `{ - "name": "route1", - "uri": "/hello_", - "hosts": ["foo.com", "*.bar.com"], - "upstream": { - "nodes": { - "` + base.UpstreamIp + `:1980": 1 - }, - "type": "roundrobin" - } - }`, - Headers: map[string]string{"Authorization": base.GetToken()}, - ExpectStatus: http.StatusOK, - }), - Entry("create route with int uri", base.HttpTestCase{ - Object: base.ManagerApiExpect(), - Method: http.MethodPut, - Path: "/apisix/admin/routes/r1", - Body: `{ - "name": "route2", - "uri": 123456 - }`, - Headers: map[string]string{"Authorization": base.GetToken()}, - ExpectStatus: http.StatusBadRequest, - }), - Entry("hit the route just created - wildcard domain name", base.HttpTestCase{ - Object: base.APISIXExpect(), - Method: http.MethodGet, - Path: "/hello_", - Headers: map[string]string{"Host": "test.bar.com"}, - ExpectStatus: http.StatusOK, - ExpectBody: "hello world\n", - Sleep: base.SleepTime, - }), - Entry("hit the route just created", base.HttpTestCase{ - Object: base.APISIXExpect(), - Method: http.MethodGet, - Path: "/hello_", - Headers: map[string]string{"Host": "foo.com"}, - ExpectStatus: http.StatusOK, - ExpectBody: "hello world\n", - }), - Entry("hit the route not exists", base.HttpTestCase{ - Object: base.APISIXExpect(), - Method: http.MethodGet, - Path: "/hello_111", - Headers: map[string]string{"Host": "foo.com"}, - ExpectStatus: http.StatusNotFound, - ExpectBody: "{\"error_msg\":\"404 Route Not Found\"}\n", - }), - Entry("delete the route just created", base.HttpTestCase{ - Object: base.ManagerApiExpect(), - Method: http.MethodDelete, - Path: "/apisix/admin/routes/r1", - Headers: map[string]string{"Authorization": base.GetToken()}, - ExpectStatus: http.StatusOK, - }), - Entry("hit the route just deleted", base.HttpTestCase{ - Object: base.APISIXExpect(), - Method: http.MethodGet, - Path: "/hello_", - Headers: map[string]string{"Host": "bar.com"}, - ExpectStatus: http.StatusNotFound, - ExpectBody: "{\"error_msg\":\"404 Route Not Found\"}\n", - Sleep: base.SleepTime, - }), - ) - - DescribeTable("update routes with hosts", - func(tc base.HttpTestCase) { - base.RunTestCase(tc) - }, - Entry("hit route that not exist", base.HttpTestCase{ - Object: base.APISIXExpect(), - Method: http.MethodGet, - Path: "/hello", - Headers: map[string]string{"Host": "foo.com"}, - ExpectStatus: http.StatusNotFound, - ExpectBody: "{\"error_msg\":\"404 Route Not Found\"}\n", - }), - Entry("create route with host foo.com", base.HttpTestCase{ - Object: base.ManagerApiExpect(), - Method: http.MethodPut, - Path: "/apisix/admin/routes/r1", - Body: `{ - "name": "route1", - "uri": "/hello", - "methods": ["GET"], - "hosts": ["foo.com"], - "upstream": { - "type": "roundrobin", - "nodes": [{ - "host": "` + base.UpstreamIp + `", - "port": 1980, - "weight": 1 - }] - } - }`, - Headers: map[string]string{"Authorization": base.GetToken()}, - ExpectStatus: http.StatusOK, - ExpectBody: []string{"\"id\":\"r1\"", "\"hosts\":[\"foo.com\"]"}, - }), - Entry("hit the route just create", base.HttpTestCase{ - Object: base.APISIXExpect(), - Method: http.MethodGet, - Path: "/hello", - Headers: map[string]string{"Host": "foo.com"}, - ExpectStatus: http.StatusOK, - Sleep: base.SleepTime, - }), - Entry("update route with host bar.com", base.HttpTestCase{ - Object: base.ManagerApiExpect(), - Method: http.MethodPut, - Path: "/apisix/admin/routes/r1", - Body: `{ - "name": "route1", - "uri": "/hello", - "hosts": ["bar.com"], - "upstream": { - "nodes": { - "` + base.UpstreamIp + `:1980": 1 - }, - "type": "roundrobin" - } - }`, - Headers: map[string]string{"Authorization": base.GetToken()}, - ExpectStatus: http.StatusOK, - ExpectBody: []string{"\"id\":\"r1\"", "\"hosts\":[\"bar.com\"]"}, - }), - Entry("hit the route with host foo.com", base.HttpTestCase{ - Object: base.APISIXExpect(), - Method: http.MethodGet, - Path: "/hello", - Headers: map[string]string{"Host": "foo.com"}, - ExpectStatus: http.StatusNotFound, - Sleep: base.SleepTime, - }), - Entry("hit the route just updated", base.HttpTestCase{ - Object: base.APISIXExpect(), - Method: http.MethodGet, - Path: "/hello", - Headers: map[string]string{"Host": "bar.com"}, - ExpectStatus: http.StatusOK, - ExpectBody: "hello world\n", - }), - Entry("delete route", base.HttpTestCase{ - Object: base.ManagerApiExpect(), - Method: http.MethodDelete, - Path: "/apisix/admin/routes/r1", - Headers: map[string]string{"Authorization": base.GetToken()}, - ExpectStatus: http.StatusOK, - }), - Entry("hit the route just deleted", base.HttpTestCase{ - Object: base.APISIXExpect(), - Method: http.MethodGet, - Path: "/hello", - Headers: map[string]string{"Host": "bar.com"}, - ExpectStatus: http.StatusNotFound, - ExpectBody: "{\"error_msg\":\"404 Route Not Found\"}\n", - Sleep: base.SleepTime, - }), - ) - - DescribeTable("test route with empty array", - func(tc base.HttpTestCase) { - base.RunTestCase(tc) - }, - Entry("create route with empty hosts and host", base.HttpTestCase{ - Object: base.ManagerApiExpect(), - Path: "/apisix/admin/routes/r1", - Method: http.MethodPut, - Body: `{ - "name": "route1", - "uri": "/hello", - "hosts": [], - "host": "test.com", - "upstream": { - "nodes": { - "` + base.UpstreamIp + `:1980": 1 - }, - "type": "roundrobin" - } - }`, - Headers: map[string]string{"Authorization": base.GetToken()}, - ExpectStatus: http.StatusBadRequest, - ExpectBody: `{"code":10000,"message":"schema validate failed: (root): Must validate one and only one schema (oneOf)\n(root): Must validate all the schemas (allOf)\nhosts: Array must have at least 1 items"}`, - }), - Entry("make sure the route not created", base.HttpTestCase{ - Object: base.APISIXExpect(), - Method: http.MethodGet, - Path: "/hello", - Headers: map[string]string{"Host": "test.com"}, - ExpectStatus: http.StatusNotFound, - ExpectBody: `{"error_msg":"404 Route Not Found"}`, - }), - Entry("create route with empty hosts", base.HttpTestCase{ - Object: base.ManagerApiExpect(), - Path: "/apisix/admin/routes/r1", - Method: http.MethodPut, - Body: `{ - "name": "route2", - "uri": "/hello", - "hosts": [], - "upstream": { - "nodes": { - "` + base.UpstreamIp + `:1980": 1 - }, - "type": "roundrobin" - } - }`, - Headers: map[string]string{"Authorization": base.GetToken()}, - ExpectStatus: http.StatusBadRequest, - ExpectBody: `{"code":10000,"message":"schema validate failed: hosts: Array must have at least 1 items"}`, - }), - Entry("make sure the route not created", base.HttpTestCase{ - Object: base.APISIXExpect(), - Method: http.MethodGet, - Path: "/hello", - ExpectStatus: http.StatusNotFound, - ExpectBody: `{"error_msg":"404 Route Not Found"}`, - }), - Entry("create route with empty uris and uri", base.HttpTestCase{ - Object: base.ManagerApiExpect(), - Path: "/apisix/admin/routes/r1", - Method: http.MethodPut, - Body: `{ - "name": "route2", - "uri": "/hello", - "uris": [], - "upstream": { - "nodes": { - "` + base.UpstreamIp + `:1980": 1 - }, - "type": "roundrobin" - } - }`, - Headers: map[string]string{"Authorization": base.GetToken()}, - ExpectStatus: http.StatusBadRequest, - ExpectBody: `{"code":10000,"message":"schema validate failed: (root): Must validate one and only one schema (oneOf)\n(root): Must validate all the schemas (allOf)\nuris: Array must have at least 1 items"}`, - }), - Entry("create route with empty remote_addrs and remote_addr", base.HttpTestCase{ - Object: base.ManagerApiExpect(), - Path: "/apisix/admin/routes/r1", - Method: http.MethodPut, - Body: `{ - "name": "route2", - "uri": "/hello", - "remote_addrs": [], - "remote_addr": "0.0.0.0", - "upstream": { - "nodes": { - "` + base.UpstreamIp + `:1980": 1 - }, - "type": "roundrobin" - } - }`, - Headers: map[string]string{"Authorization": base.GetToken()}, - ExpectStatus: http.StatusBadRequest, - ExpectBody: `{"code":10000,"message":"schema validate failed: (root): Must validate one and only one schema (oneOf)\n(root): Must validate all the schemas (allOf)\nremote_addrs: Array must have at least 1 items"}`, - }), - Entry("make sure the route not created", base.HttpTestCase{ - Object: base.APISIXExpect(), - Method: http.MethodGet, - Path: "/hello", - ExpectStatus: http.StatusNotFound, - ExpectBody: `{"error_msg":"404 Route Not Found"}`, - }), - ) -}) diff --git a/api/test/e2e/route/route_export_test.go b/api/test/e2e/route/route_export_test.go deleted file mode 100644 index 49ef9e2c6c..0000000000 --- a/api/test/e2e/route/route_export_test.go +++ /dev/null @@ -1,2543 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one or more - * contributor license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright ownership. - * The ASF licenses this file to You under the Apache License, Version 2.0 - * (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package route_test - -import ( - "net/http" - "strings" - "time" - - . "github.com/onsi/ginkgo/v2" - "github.com/stretchr/testify/assert" - - "github.com/apisix/manager-api/test/e2e/base" -) - -var _ = Describe("Route", func() { - Context("test route export data empty", func() { - It("Export route when data is empty", func() { - base.RunTestCase(base.HttpTestCase{ - Object: base.ManagerApiExpect(), - Method: http.MethodGet, - Path: "/apisix/admin/export/routes", - Headers: map[string]string{"Authorization": base.GetToken()}, - ExpectStatus: http.StatusOK, - ExpectBody: `{"code":10000,"message":"Route data is empty, cannot be exported","data":null`, - }) - }) - }) - - Context("test route export", func() { - // 1.Export data as the route of URIs Hosts - exportStrR1 := ` - "/hello_": { - "get": { - "operationId": "aaaaGET", - "requestBody": {}, - "responses": { - "default": { - "description": "" - } - }, - "security": [], - "x-apisix-enable_websocket": false, - "x-apisix-hosts": ["foo.com", "*.bar.com"], - "x-apisix-id":"r1", - "x-apisix-labels": { - "build": "16", - "env": "production", - "version": "v2" - }, - "x-apisix-plugins": { - "limit-count": { - "count": 2, - "key": "remote_addr", - "policy": "local", - "rejected_code": 503, - "time_window": 60 - } - }, - "x-apisix-priority": 0, - "x-apisix-status": 1, - "x-apisix-upstream": { - "nodes": { - "` + base.UpstreamIp + `:1980": 1 - }, - "type": "roundrobin" - } - }, - "post": { - "operationId": "aaaaPOST", - "requestBody": {}, - "responses": { - "default": { - "description": "" - } - }, - "security": [], - "x-apisix-enable_websocket": false, - "x-apisix-hosts": ["foo.com", "*.bar.com"], - "x-apisix-id":"r1", - "x-apisix-labels": { - "build": "16", - "env": "production", - "version": "v2" - }, - "x-apisix-plugins": { - "limit-count": { - "count": 2, - "key": "remote_addr", - "policy": "local", - "rejected_code": 503, - "time_window": 60 - } - }, - "x-apisix-priority": 0, - "x-apisix-status": 1, - "x-apisix-upstream": { - "nodes": { - "` + base.UpstreamIp + `:1980": 1 - }, - "type": "roundrobin" - } - } - }` - exportStrR1 = replaceStr(exportStrR1) - It("hit route that not exist", func() { - base.RunTestCase(base.HttpTestCase{ - Object: base.APISIXExpect(), - Method: http.MethodGet, - Path: "/hello_", - Headers: map[string]string{"Host": "foo.com"}, - ExpectStatus: http.StatusNotFound, - ExpectBody: `{"error_msg":"404 Route Not Found"}`, - }) - }) - It("create route with uris and hosts to test whether the uris parsing is correct", func() { - base.RunTestCase(base.HttpTestCase{ - Object: base.ManagerApiExpect(), - Method: http.MethodPut, - Path: "/apisix/admin/routes/r1", - Body: `{ - "name": "aaaa", - "labels": { - "build":"16", - "env":"production", - "version":"v2" - }, - "plugins": { - "limit-count": { - "count": 2, - "time_window": 60, - "rejected_code": 503, - "key": "remote_addr", - "policy": "local" - } - }, - "status": 1, - "uris": ["/hello_"], - "hosts": ["foo.com", "*.bar.com"], - "methods": ["GET", "POST"], - "upstream": { - "nodes": { - "` + base.UpstreamIp + `:1980": 1 - }, - "type": "roundrobin" - } - }`, - Headers: map[string]string{"Authorization": base.GetToken()}, - ExpectStatus: http.StatusOK, - }) - }) - - It("export route", func() { - base.RunTestCase(base.HttpTestCase{ - Object: base.ManagerApiExpect(), - Method: http.MethodGet, - Path: "/apisix/admin/export/routes/r1", - Headers: map[string]string{"Authorization": base.GetToken()}, - ExpectStatus: http.StatusOK, - ExpectBody: `{"components":{},"info":{"title":"RoutesExport","version":"3.0.0"},"openapi":"3.0.0","paths":{` + exportStrR1 + "}}", - }) - }) - // 2.Export data as the route of URI host - exportStrR2 := ` - "/hello2": { - "get": { - "operationId": "aaaa2GET", - "requestBody": {}, - "responses": { - "default": { - "description": "" - } - }, - "security": [], - "x-apisix-enable_websocket": false, - "x-apisix-host": "*.bar.com", - "x-apisix-id":"r2", - "x-apisix-labels": { - "build": "16", - "env": "production", - "version": "v2" - }, - "x-apisix-plugins": { - "limit-count": { - "count": 2, - "key": "remote_addr", - "policy": "local", - "rejected_code": 503, - "time_window": 60 - } - }, - "x-apisix-priority": 0, - "x-apisix-status": 1, - "x-apisix-upstream": { - "nodes": { - "` + base.UpstreamIp + `:1980": 1 - }, - "type": "roundrobin" - } - }, - "post": { - "operationId": "aaaa2POST", - "requestBody": {}, - "responses": { - "default": { - "description": "" - } - }, - "security": [], - "x-apisix-enable_websocket": false, - "x-apisix-host": "*.bar.com", - "x-apisix-id":"r2", - "x-apisix-labels": { - "build": "16", - "env": "production", - "version": "v2" - }, - "x-apisix-plugins": { - "limit-count": { - "count": 2, - "key": "remote_addr", - "policy": "local", - "rejected_code": 503, - "time_window": 60 - } - }, - "x-apisix-priority": 0, - "x-apisix-status": 1, - "x-apisix-upstream": { - "nodes": { - "` + base.UpstreamIp + `:1980": 1 - }, - "type": "roundrobin" - } - } - }` - exportStrR2 = replaceStr(exportStrR2) - - It("hit route2 that not exist", func() { - base.RunTestCase(base.HttpTestCase{ - Object: base.APISIXExpect(), - Method: http.MethodGet, - Path: "/hello2", - Headers: map[string]string{"Host": "bar.com"}, - ExpectStatus: http.StatusNotFound, - ExpectBody: `{"error_msg":"404 Route Not Found"}`, - }) - }) - It("create route2 with uri and host to test whether the uri parsing is correct", func() { - base.RunTestCase(base.HttpTestCase{ - Object: base.ManagerApiExpect(), - Method: http.MethodPut, - Path: "/apisix/admin/routes/r2", - Body: `{ - "name": "aaaa2", - "labels": { - "build":"16", - "env":"production", - "version":"v2" - }, - "plugins": { - "limit-count": { - "count": 2, - "time_window": 60, - "rejected_code": 503, - "key": "remote_addr", - "policy": "local" - } - }, - "status": 1, - "uri": "/hello2", - "host": "*.bar.com", - "methods": ["GET", "POST"], - "upstream": { - "nodes": { - "` + base.UpstreamIp + `:1980": 1 - }, - "type": "roundrobin" - } - }`, - Headers: map[string]string{"Authorization": base.GetToken()}, - ExpectStatus: http.StatusOK, - }) - }) - It("export route2", func() { - base.RunTestCase(base.HttpTestCase{ - Object: base.ManagerApiExpect(), - Method: http.MethodGet, - Path: "/apisix/admin/export/routes/r2", - Headers: map[string]string{"Authorization": base.GetToken()}, - ExpectStatus: http.StatusOK, - ExpectBody: `{"components":{},"info":{"title":"RoutesExport","version":"3.0.0"},"openapi":"3.0.0","paths":{` + exportStrR2 + "}}", - }) - }) - It("export route and route2", func() { - base.RunTestCase(base.HttpTestCase{ - Object: base.ManagerApiExpect(), - Method: http.MethodGet, - Path: "/apisix/admin/export/routes/r1,r2", - Headers: map[string]string{"Authorization": base.GetToken()}, - ExpectStatus: http.StatusOK, - ExpectBody: `{"components":{},"info":{"title":"RoutesExport","version":"3.0.0"},"openapi":"3.0.0","paths":{` + exportStrR2 + "," + exportStrR1 + "}}", - }) - }) - It("use the exportall interface to export all routes", func() { - base.RunTestCase(base.HttpTestCase{ - Object: base.ManagerApiExpect(), - Method: http.MethodGet, - Path: "/apisix/admin/export/routes", - Headers: map[string]string{"Authorization": base.GetToken()}, - ExpectStatus: http.StatusOK, - ExpectBody: `{"components":{},"info":{"title":"RoutesExport","version":"3.0.0"},"openapi":"3.0.0","paths":{` + exportStrR2 + "," + exportStrR1 + "}}", - }) - }) - It("delete the route just created", func() { - base.RunTestCase(base.HttpTestCase{ - Object: base.ManagerApiExpect(), - Method: http.MethodDelete, - Path: "/apisix/admin/routes/r1", - Headers: map[string]string{"Authorization": base.GetToken()}, - ExpectStatus: http.StatusOK, - }) - }) - It("hit the route just deleted", func() { - base.RunTestCase(base.HttpTestCase{ - Object: base.APISIXExpect(), - Method: http.MethodGet, - Path: "/hello_", - Headers: map[string]string{"Host": "bar.com"}, - ExpectStatus: http.StatusNotFound, - ExpectBody: `{"error_msg":"404 Route Not Found"}`, - Sleep: base.SleepTime, - }) - }) - It("delete the route2 just created", func() { - base.RunTestCase(base.HttpTestCase{ - Object: base.ManagerApiExpect(), - Method: http.MethodDelete, - Path: "/apisix/admin/routes/r2", - Headers: map[string]string{"Authorization": base.GetToken()}, - ExpectStatus: http.StatusOK, - }) - }) - It("hit the route2 just deleted", func() { - base.RunTestCase(base.HttpTestCase{ - Object: base.APISIXExpect(), - Method: http.MethodGet, - Path: "/hello2", - Headers: map[string]string{"Host": "bar.com"}, - ExpectStatus: http.StatusNotFound, - ExpectBody: `{"error_msg":"404 Route Not Found"}`, - Sleep: base.SleepTime, - }) - }) - // 4.Create a service that contains complete data and use the service_ id create route - serviceStrS1 := ` - "name": "testservice", - "desc": "testservice_desc", - "upstream": { - "nodes": [{ - "host": "` + base.UpstreamIp + `", - "port": 1980, - "weight": 1 - }], - "type": "roundrobin" - }, - "plugins": { - "limit-count": { - "count": 100, - "key": "remote_addr", - "policy": "local", - "rejected_code": 503, - "time_window": 60 - } - }, - "labels": { - "build": "16", - "env": "production", - "version": "v2" - }, - "enable_websocket": true - ` - serviceStrS1 = replaceStr(serviceStrS1) - - exportStrR3 := ` - "components": {}, - "info": { - "title": "RoutesExport", - "version": "3.0.0" - }, - "openapi": "3.0.0", - "paths": { - "/hello3": { - "get": { - "operationId": "route3GET", - "requestBody": {}, - "responses": { - "default": { - "description": "" - } - }, - "x-apisix-enable_websocket": false, - "x-apisix-id":"r3", - "x-apisix-labels": { - "build": "16", - "env": "production", - "version": "v2" - }, - "x-apisix-plugins": { - "limit-count": { - "count": 100, - "key": "remote_addr", - "policy": "local", - "rejected_code": 503, - "time_window": 60 - } - }, - "x-apisix-priority": 0, - "x-apisix-status": 1, - "x-apisix-upstream": { - "nodes": [{ - "host": "` + base.UpstreamIp + `", - "port": 1980, - "weight": 1 - }], - "type": "roundrobin" - } - } - } - }` - exportStrR3 = replaceStr(exportStrR3) - - It("create service with all options", func() { - base.RunTestCase(base.HttpTestCase{ - Object: base.ManagerApiExpect(), - Method: http.MethodPut, - Path: "/apisix/admin/services/s1", - Headers: map[string]string{"Authorization": base.GetToken()}, - Body: `{ - "name": "testservice", - "desc": "testservice_desc", - "labels": { - "build":"16", - "env":"production", - "version":"v2" - }, - "enable_websocket":true, - "plugins": { - "limit-count": { - "count": 100, - "time_window": 60, - "policy": "local", - "rejected_code": 503, - "key": "remote_addr" - } - }, - "upstream": { - "type": "roundrobin", - "create_time":1602883670, - "update_time":1602893670, - "nodes": [{ - "host": "` + base.UpstreamIp + `", - "port": 1980, - "weight": 1 - }] - } - }`, - ExpectStatus: http.StatusOK, - }) - }) - It("get the service s1", func() { - base.RunTestCase(base.HttpTestCase{ - Object: base.ManagerApiExpect(), - Method: http.MethodGet, - Path: "/apisix/admin/services/s1", - Headers: map[string]string{"Authorization": base.GetToken()}, - ExpectCode: http.StatusOK, - ExpectBody: serviceStrS1, - }) - }) - It("create route3 using the service id just created", func() { - base.RunTestCase(base.HttpTestCase{ - Object: base.ManagerApiExpect(), - Method: http.MethodPut, - Path: "/apisix/admin/routes/r3", - Body: `{ - "name": "route3", - "methods": ["GET"], - "uri": "/hello3", - "service_id": "s1" - }`, - Headers: map[string]string{"Authorization": base.GetToken()}, - ExpectCode: http.StatusOK, - Sleep: base.SleepTime, - }) - }) - It("export route3", func() { - base.RunTestCase(base.HttpTestCase{ - Object: base.ManagerApiExpect(), - Method: http.MethodGet, - Path: "/apisix/admin/export/routes/r3", - Headers: map[string]string{"Authorization": base.GetToken()}, - ExpectCode: http.StatusOK, - ExpectBody: exportStrR3, - }) - }) - It("delete the route3 just created", func() { - base.RunTestCase(base.HttpTestCase{ - Object: base.ManagerApiExpect(), - Method: http.MethodDelete, - Path: "/apisix/admin/routes/r3", - Headers: map[string]string{"Authorization": base.GetToken()}, - ExpectCode: http.StatusOK, - }) - }) - It("hit the route3 just deleted", func() { - base.RunTestCase(base.HttpTestCase{ - Object: base.APISIXExpect(), - Method: http.MethodGet, - Path: "/hello3", - ExpectStatus: http.StatusNotFound, - Sleep: base.SleepTime, - }) - }) - It("delete the service1", func() { - base.RunTestCase(base.HttpTestCase{ - Object: base.ManagerApiExpect(), - Method: http.MethodDelete, - Path: "/apisix/admin/services/s1", - Headers: map[string]string{"Authorization": base.GetToken()}, - ExpectStatus: http.StatusOK, - Sleep: base.SleepTime, - }) - }) - // 5.Create a service containing plugin and a route containing plugin to test the fusion of exported data - // This test case tests the creation of a complete service. - // And create a complete route, export rules as route upstream data for high priority, direct use. - // However, if the data in the service plugins does not exist in the route, it will be fused and exported. - // If it exists, the data in route will be used first - serviceStrS2 := ` - "name": "testservice", - "desc": "testservice_desc", - "upstream": { - "nodes": [{ - "host": "` + base.UpstreamIp + `", - "port": 1980, - "weight": 1 - }], - "type": "roundrobin" - }, - "plugins": { - "limit-count": { - "count": 100, - "key": "remote_addr", - "policy": "local", - "rejected_code": 503, - "time_window": 60 - } - }, - "labels": { - "build": "16", - "env": "production", - "version": "v2" - }, - "enable_websocket": true` - serviceStrS2 = replaceStr(serviceStrS2) - - exportStrR4 := ` - "components": {}, - "info": { - "title": "RoutesExport", - "version": "3.0.0" - }, - "openapi": "3.0.0", - "paths": { - "/hello": { - "get": { - "operationId": "route4GET", - "requestBody": {}, - "responses": { - "default": { - "description": "" - } - }, - "security": [], - "x-apisix-enable_websocket": false, - "x-apisix-id":"r4", - "x-apisix-labels": { - "build": "16", - "env": "production", - "version": "v2" - }, - "x-apisix-plugins": { - "limit-count": { - "count": 100, - "key": "remote_addr", - "policy": "local", - "rejected_code": 503, - "time_window": 60 - }, - "prometheus": { - "disable": false - } - }, - "x-apisix-priority": 0, - "x-apisix-status": 1, - "x-apisix-upstream": { - "nodes": [{ - "host": "` + base.UpstreamIp + `", - "port": 1980, - "weight": 1 - }], - "type": "roundrobin" - } - } - } - }` - exportStrR4 = replaceStr(exportStrR4) - - It("create service with all options", func() { - base.RunTestCase(base.HttpTestCase{ - Object: base.ManagerApiExpect(), - Method: http.MethodPut, - Path: "/apisix/admin/services/s2", - Headers: map[string]string{"Authorization": base.GetToken()}, - Body: `{ - "name": "testservice", - "desc": "testservice_desc", - "labels": { - "build":"16", - "env":"production", - "version":"v2" - }, - "enable_websocket":true, - "plugins": { - "limit-count": { - "count": 100, - "time_window": 60, - "rejected_code": 503, - "key": "remote_addr", - "policy": "local" - } - }, - "upstream": { - "type": "roundrobin", - "create_time":1602883670, - "update_time":1602893670, - "nodes": [{ - "host": "` + base.UpstreamIp + `", - "port": 1980, - "weight": 1 - }] - } - }`, - ExpectStatus: http.StatusOK, - }) - }) - It("get the service s2", func() { - base.RunTestCase(base.HttpTestCase{ - Object: base.ManagerApiExpect(), - Method: http.MethodGet, - Path: "/apisix/admin/services/s2", - Headers: map[string]string{"Authorization": base.GetToken()}, - ExpectStatus: http.StatusOK, - ExpectBody: serviceStrS2, - }) - }) - It("Create Route4 and test the priority merging function of upstream, label and plugin when both service and route are included", func() { - base.RunTestCase(base.HttpTestCase{ - Object: base.ManagerApiExpect(), - Method: http.MethodPut, - Path: "/apisix/admin/routes/r4", - Body: `{ - "name": "route4", - "methods": ["GET"], - "uri": "/hello", - "service_id": "s2", - "enable_websocket":false, - "plugins": { - "prometheus": { - "disable": false - } - }, - "upstream": { - "type": "roundrobin", - "nodes": [{ - "host": "` + base.UpstreamIp + `", - "port": 1980, - "weight": 1 - }] - } - }`, - Headers: map[string]string{"Authorization": base.GetToken()}, - ExpectStatus: http.StatusOK, - Sleep: base.SleepTime, - }) - }) - It("export route4", func() { - base.RunTestCase(base.HttpTestCase{ - Object: base.ManagerApiExpect(), - Method: http.MethodGet, - Path: "/apisix/admin/export/routes/r4", - Headers: map[string]string{"Authorization": base.GetToken()}, - ExpectStatus: http.StatusOK, - ExpectBody: exportStrR4, - }) - }) - It("delete the route4 just created", func() { - base.RunTestCase(base.HttpTestCase{ - Object: base.ManagerApiExpect(), - Method: http.MethodDelete, - Path: "/apisix/admin/routes/r4", - Headers: map[string]string{"Authorization": base.GetToken()}, - ExpectStatus: http.StatusOK, - }) - }) - It("hit the route4 just deleted", func() { - base.RunTestCase(base.HttpTestCase{ - Object: base.APISIXExpect(), - Method: http.MethodGet, - Path: "/hello", - ExpectStatus: http.StatusNotFound, - ExpectBody: `{"error_msg":"404 Route Not Found"}`, - }) - }) - It("delete the service2", func() { - base.RunTestCase(base.HttpTestCase{ - Object: base.ManagerApiExpect(), - Method: http.MethodDelete, - Path: "/apisix/admin/services/s2", - Headers: map[string]string{"Authorization": base.GetToken()}, - ExpectStatus: http.StatusOK, - Sleep: base.SleepTime, - }) - }) - // 6.Create a service according to the upstream ID and a route according to the service ID. - // The test export also contains the upstream. - // Use the upstream of route. - serviceStrS3 := ` - "name": "testservice", - "desc": "testservice_desc", - "upstream_id": "1", - "plugins": { - "limit-count": { - "count": 100, - "key": "remote_addr", - "policy": "local", - "rejected_code": 503, - "time_window": 60 - } - }, - "labels": { - "build": "16", - "env": "production", - "version": "v2" - }, - "enable_websocket": true` - serviceStrS3 = replaceStr(serviceStrS3) - - exportStrR5 := ` - "components": {}, - "info": { - "title": "RoutesExport", - "version": "3.0.0" - }, - "openapi": "3.0.0", - "paths": { - "/hello5": { - "get": { - "operationId": "route5GET", - "requestBody": {}, - "responses": { - "default": { - "description": "" - } - }, - "security": [], - "x-apisix-enable_websocket": false, - "x-apisix-id":"r5", - "x-apisix-labels": { - "build": "16", - "env": "production", - "version": "v2" - }, - "x-apisix-plugins": { - "limit-count": { - "count": 100, - "key": "remote_addr", - "policy": "local", - "rejected_code": 503, - "time_window": 60 - }, - "prometheus": { - "disable": false - } - }, - "x-apisix-priority": 0, - "x-apisix-status": 1, - "x-apisix-upstream": { - "nodes": [{ - "host": "` + base.UpstreamIp + `", - "port": 1981, - "weight": 1 - }], - "type": "roundrobin" - } - } - } - }` - exportStrR5 = replaceStr(exportStrR5) - It("create upstream", func() { - base.RunTestCase(base.HttpTestCase{ - Object: base.ManagerApiExpect(), - Method: http.MethodPut, - Path: "/apisix/admin/upstreams/1", - Headers: map[string]string{"Authorization": base.GetToken()}, - Body: `{ - "nodes": [ - { - "host": "` + base.UpstreamIp + `", - "port": 1980, - "weight": 1 - } - ], - "type": "roundrobin" - }`, - ExpectStatus: http.StatusOK, - }) - }) - It("create service with upstream id", func() { - base.RunTestCase(base.HttpTestCase{ - Object: base.ManagerApiExpect(), - Method: http.MethodPut, - Path: "/apisix/admin/services/s3", - Headers: map[string]string{"Authorization": base.GetToken()}, - Body: `{ - "name": "testservice", - "desc": "testservice_desc", - "labels": { - "build":"16", - "env":"production", - "version":"v2" - }, - "enable_websocket":true, - "plugins": { - "limit-count": { - "count": 100, - "time_window": 60, - "rejected_code": 503, - "key": "remote_addr", - "policy": "local" - } - }, - "upstream_id": "1" - }`, - ExpectStatus: http.StatusOK, - }) - }) - It("get the service s3", func() { - base.RunTestCase(base.HttpTestCase{ - Object: base.ManagerApiExpect(), - Method: http.MethodGet, - Path: "/apisix/admin/services/s3", - Headers: map[string]string{"Authorization": base.GetToken()}, - ExpectStatus: http.StatusOK, - ExpectBody: serviceStrS3, - }) - }) - It("Create a route5 with the id of the service3 created with upstream id", func() { - base.RunTestCase(base.HttpTestCase{ - Object: base.ManagerApiExpect(), - Method: http.MethodPut, - Path: "/apisix/admin/routes/r5", - Body: `{ - "name": "route5", - "methods": ["GET"], - "uri": "/hello5", - "service_id": "s3", - "enable_websocket":false, - "plugins": { - "prometheus": { - "disable": false - } - }, - "upstream": { - "type": "roundrobin", - "nodes": [{ - "host": "` + base.UpstreamIp + `", - "port": 1981, - "weight": 1 - }] - } - }`, - Headers: map[string]string{"Authorization": base.GetToken()}, - ExpectStatus: http.StatusOK, - Sleep: base.SleepTime, - }) - }) - It("export route5", func() { - base.RunTestCase(base.HttpTestCase{ - Object: base.ManagerApiExpect(), - Method: http.MethodGet, - Path: "/apisix/admin/export/routes/r5", - Headers: map[string]string{"Authorization": base.GetToken()}, - ExpectStatus: http.StatusOK, - ExpectBody: exportStrR5, - }) - }) - It("delete the route5 just created", func() { - base.RunTestCase(base.HttpTestCase{ - Object: base.ManagerApiExpect(), - Method: http.MethodDelete, - Path: "/apisix/admin/routes/r5", - Headers: map[string]string{"Authorization": base.GetToken()}, - ExpectStatus: http.StatusOK, - }) - }) - It("hit the route5 just deleted", func() { - base.RunTestCase(base.HttpTestCase{ - Object: base.APISIXExpect(), - Method: http.MethodGet, - Path: "/hello5", - ExpectBody: `{"error_msg":"404 Route Not Found"}`, - Sleep: base.SleepTime, - }) - }) - It("delete the service3", func() { - base.RunTestCase(base.HttpTestCase{ - Object: base.ManagerApiExpect(), - Method: http.MethodDelete, - Path: "/apisix/admin/services/s3", - Headers: map[string]string{"Authorization": base.GetToken()}, - ExpectStatus: http.StatusOK, - Sleep: base.SleepTime, - }) - }) - It("remove upstream", func() { - base.RunTestCase(base.HttpTestCase{ - Object: base.ManagerApiExpect(), - Method: http.MethodDelete, - Path: "/apisix/admin/upstreams/1", - Headers: map[string]string{"Authorization": base.GetToken()}, - ExpectStatus: http.StatusOK, - }) - }) - // 8.Create and export route according to upstream ID - exportStrR8 := ` - "components": {}, - "info": { - "title": "RoutesExport", - "version": "3.0.0" - }, - "openapi": "3.0.0", - "paths": { - "/hello": { - "get": { - "operationId": "route8GET", - "requestBody": {}, - "responses": { - "default": { - "description": "" - } - }, - "security": [], - "x-apisix-enable_websocket": false, - "x-apisix-id":"r8", - "x-apisix-plugins": { - "prometheus": { - "disable": false - } - }, - "x-apisix-priority": 0, - "x-apisix-status": 1, - "x-apisix-upstream": { - "id": "3" - ` - exportStrR8 = replaceStr(exportStrR8) - - It("create upstream3", func() { - base.RunTestCase(base.HttpTestCase{ - Object: base.ManagerApiExpect(), - Method: http.MethodPut, - Path: "/apisix/admin/upstreams/3", - Body: `{ - "nodes": [ - { - "host": "` + base.UpstreamIp + `", - "port": 1980, - "weight": 1 - } - ], - "type": "roundrobin" - }`, - Headers: map[string]string{"Authorization": base.GetToken()}, - ExpectStatus: http.StatusOK, - }) - }) - It("Create a route8 using upstream id", func() { - base.RunTestCase(base.HttpTestCase{ - Object: base.ManagerApiExpect(), - Method: http.MethodPut, - Path: "/apisix/admin/routes/r8", - Body: `{ - "name": "route8", - "methods": ["GET"], - "uri": "/hello", - "enable_websocket":false, - "plugins": { - "prometheus": { - "disable": false - } - }, - "upstream_id": "3" - }`, - Headers: map[string]string{"Authorization": base.GetToken()}, - ExpectStatus: http.StatusOK, - Sleep: base.SleepTime, - }) - }) - It("export route8", func() { - base.RunTestCase(base.HttpTestCase{ - Object: base.ManagerApiExpect(), - Method: http.MethodGet, - Path: "/apisix/admin/export/routes/r8", - Headers: map[string]string{"Authorization": base.GetToken()}, - ExpectStatus: http.StatusOK, - ExpectBody: exportStrR8, - }) - }) - It("delete the route8 just created", func() { - base.RunTestCase(base.HttpTestCase{ - Object: base.ManagerApiExpect(), - Method: http.MethodDelete, - Path: "/apisix/admin/routes/r8", - Headers: map[string]string{"Authorization": base.GetToken()}, - ExpectStatus: http.StatusOK, - }) - }) - It("hit the route8 just deleted", func() { - base.RunTestCase(base.HttpTestCase{ - Object: base.APISIXExpect(), - Method: http.MethodGet, - Path: "/hello", - ExpectStatus: http.StatusNotFound, - ExpectBody: `{"error_msg":"404 Route Not Found"}`, - Sleep: base.SleepTime, - }) - }) - It("remove upstream3", func() { - base.RunTestCase(base.HttpTestCase{ - Object: base.ManagerApiExpect(), - Method: http.MethodDelete, - Path: "/apisix/admin/upstreams/3", - Headers: map[string]string{"Authorization": base.GetToken()}, - ExpectStatus: http.StatusOK, - }) - }) - - // 9.Create service according to upstream1 ID - // Create route according to upstream2 ID and service ID - // Export route - serviceStrS4 := ` - "name": "testservice", - "desc": "testservice_desc", - "upstream_id": "4", - "enable_websocket": true` - serviceStrS4 = replaceStr(serviceStrS4) - - exportStrR9 := ` - "components": {}, - "info": { - "title": "RoutesExport", - "version": "3.0.0" - }, - "openapi": "3.0.0", - "paths": { - "/hello": { - "get": { - "operationId": "route_allGET", - "requestBody": {}, - "responses": { - "default": { - "description": "" - } - }, - "security": [], - "summary": "ๆ‰€ๆœ‰", - "x-apisix-enable_websocket": false, - "x-apisix-id":"r9", - "x-apisix-labels": { - "API_VERSION": "v1", - "test": "1" - }, - "x-apisix-plugins": { - "prometheus": { - "disable": false - } - }, - "x-apisix-priority": 0, - "x-apisix-status": 1, - "x-apisix-upstream": { - "id": "5" - ` - exportStrR9 = replaceStr(exportStrR9) - - It("create upstream4", func() { - base.RunTestCase(base.HttpTestCase{ - Object: base.ManagerApiExpect(), - Method: http.MethodPut, - Path: "/apisix/admin/upstreams/4", - Body: `{ - "nodes": [ - { - "host": "` + base.UpstreamIp + `", - "port": 1980, - "weight": 1 - } - ], - "type": "roundrobin" - }`, - Headers: map[string]string{"Authorization": base.GetToken()}, - ExpectStatus: http.StatusOK, - }) - }) - It("create upstream5", func() { - base.RunTestCase(base.HttpTestCase{ - Object: base.ManagerApiExpect(), - Method: http.MethodPut, - Path: "/apisix/admin/upstreams/5", - Body: `{ - "name": "upstream5", - "nodes": [ - { - "host": "` + base.UpstreamIp + `", - "port": 1981, - "weight": 1 - } - ], - "type": "roundrobin" - }`, - Headers: map[string]string{"Authorization": base.GetToken()}, - ExpectStatus: http.StatusOK, - }) - }) - It("create service", func() { - base.RunTestCase(base.HttpTestCase{ - Object: base.ManagerApiExpect(), - Method: http.MethodPut, - Path: "/apisix/admin/services/s4", - Body: `{ - "name": "testservice", - "desc": "testservice_desc", - "enable_websocket":true, - "upstream_id": "4" - }`, - Headers: map[string]string{"Authorization": base.GetToken()}, - ExpectStatus: http.StatusOK, - }) - }) - It("get the service s4", func() { - base.RunTestCase(base.HttpTestCase{ - Object: base.ManagerApiExpect(), - Method: http.MethodGet, - Path: "/apisix/admin/services/s4", - Headers: map[string]string{"Authorization": base.GetToken()}, - ExpectStatus: http.StatusOK, - ExpectBody: serviceStrS4, - }) - }) - It("Create a route9 using upstream id and service id", func() { - base.RunTestCase(base.HttpTestCase{ - Object: base.ManagerApiExpect(), - Method: http.MethodPut, - Path: "/apisix/admin/routes/r9", - Body: `{ - "name": "route_all", - "desc": "ๆ‰€ๆœ‰", - "status": 1, - "methods": ["GET"], - "priority": 0, - "service_id": "s4", - "labels": { - "test": "1", - "API_VERSION": "v1" - }, - "vars": [ - ["arg_name", "==", "test"] - ], - "uri": "/hello", - "enable_websocket":false, - "plugins": { - "prometheus": { - "disable": false - } - }, - "upstream_id": "5" - }`, - Headers: map[string]string{"Authorization": base.GetToken()}, - ExpectStatus: http.StatusOK, - Sleep: base.SleepTime, - }) - }) - It("export route9", func() { - base.RunTestCase(base.HttpTestCase{ - Object: base.ManagerApiExpect(), - Method: http.MethodGet, - Path: "/apisix/admin/export/routes/r9", - Headers: map[string]string{"Authorization": base.GetToken()}, - ExpectStatus: http.StatusOK, - ExpectBody: exportStrR9, - }) - }) - It("delete the route9 just created", func() { - base.RunTestCase(base.HttpTestCase{ - Object: base.ManagerApiExpect(), - Method: http.MethodDelete, - Path: "/apisix/admin/routes/r9", - Headers: map[string]string{"Authorization": base.GetToken()}, - ExpectStatus: http.StatusOK, - }) - }) - It("hit the route9 just deleted", func() { - base.RunTestCase(base.HttpTestCase{ - Object: base.APISIXExpect(), - Method: http.MethodGet, - Path: "/hello", - ExpectStatus: http.StatusNotFound, - ExpectBody: `{"error_msg":"404 Route Not Found"}`, - Sleep: base.SleepTime, - }) - }) - It("delete the service4", func() { - base.RunTestCase(base.HttpTestCase{ - Object: base.ManagerApiExpect(), - Method: http.MethodDelete, - Path: "/apisix/admin/services/s4", - Headers: map[string]string{"Authorization": base.GetToken()}, - ExpectStatus: http.StatusOK, - Sleep: base.SleepTime, - }) - }) - It("remove upstream4", func() { - base.RunTestCase(base.HttpTestCase{ - Object: base.ManagerApiExpect(), - Method: http.MethodDelete, - Path: "/apisix/admin/upstreams/4", - Headers: map[string]string{"Authorization": base.GetToken()}, - ExpectStatus: http.StatusOK, - }) - }) - It("remove upstream5", func() { - base.RunTestCase(base.HttpTestCase{ - Object: base.ManagerApiExpect(), - Method: http.MethodDelete, - Path: "/apisix/admin/upstreams/5", - Headers: map[string]string{"Authorization": base.GetToken()}, - ExpectStatus: http.StatusOK, - }) - }) - - // 10.Creating route10 using service ID does not contain upstream data - serviceStrS5 := ` - "name": "testservice", - "desc": "testservice_desc", - "upstream_id": "6", - "enable_websocket": true` - serviceStrS5 = replaceStr(serviceStrS5) - - exportStrR10 := ` - "components": {}, - "info": { - "title": "RoutesExport", - "version": "3.0.0" - }, - "openapi": "3.0.0", - "paths": { - "/hello": { - "get": { - "operationId": "route_allGET", - "requestBody": {}, - "responses": { - "default": { - "description": "" - } - }, - "security": [], - "summary": "ๆ‰€ๆœ‰", - "x-apisix-enable_websocket": false, - "x-apisix-id":"r10", - "x-apisix-labels": { - "API_VERSION": "v1", - "test": "1" - }, - "x-apisix-plugins": { - "prometheus": { - "disable": false - } - }, - "x-apisix-priority": 0, - "x-apisix-status": 1, - "x-apisix-upstream": { - "id": "6" - ` - exportStrR10 = replaceStr(exportStrR10) - It("create upstream6", func() { - base.RunTestCase(base.HttpTestCase{ - Object: base.ManagerApiExpect(), - Method: http.MethodPut, - Path: "/apisix/admin/upstreams/6", - Body: `{ - "nodes": [ - { - "host": "` + base.UpstreamIp + `", - "port": 1980, - "weight": 1 - } - ], - "type": "roundrobin" - }`, - Headers: map[string]string{"Authorization": base.GetToken()}, - ExpectStatus: http.StatusOK, - }) - }) - It("create service", func() { - base.RunTestCase(base.HttpTestCase{ - Object: base.ManagerApiExpect(), - Method: http.MethodPut, - Path: "/apisix/admin/services/s5", - Body: `{ - "name": "testservice", - "desc": "testservice_desc", - "enable_websocket":true, - "upstream_id": "6" - }`, - Headers: map[string]string{"Authorization": base.GetToken()}, - ExpectStatus: http.StatusOK, - }) - }) - It("get the service s5", func() { - base.RunTestCase(base.HttpTestCase{ - Object: base.ManagerApiExpect(), - Method: http.MethodGet, - Path: "/apisix/admin/services/s5", - Headers: map[string]string{"Authorization": base.GetToken()}, - ExpectStatus: http.StatusOK, - ExpectBody: serviceStrS5, - }) - }) - It("Creating route10 using service ID does not contain upstream data", func() { - base.RunTestCase(base.HttpTestCase{ - Object: base.ManagerApiExpect(), - Method: http.MethodPut, - Path: "/apisix/admin/routes/r10", - Body: `{ - "name": "route_all", - "desc": "ๆ‰€ๆœ‰", - "status": 1, - "methods": ["GET"], - "priority": 0, - "service_id": "s5", - "labels": { - "test": "1", - "API_VERSION": "v1" - }, - "vars": [ - ["arg_name", "==", "test"] - ], - "uri": "/hello", - "enable_websocket":false, - "plugins": { - "prometheus": { - "disable": false - } - } - }`, - Headers: map[string]string{"Authorization": base.GetToken()}, - ExpectStatus: http.StatusOK, - Sleep: base.SleepTime, - }) - }) - It("export route10", func() { - base.RunTestCase(base.HttpTestCase{ - Object: base.ManagerApiExpect(), - Method: http.MethodGet, - Path: "/apisix/admin/export/routes/r10", - Headers: map[string]string{"Authorization": base.GetToken()}, - ExpectStatus: http.StatusOK, - ExpectBody: exportStrR10, - }) - }) - It("delete the route10 just created", func() { - base.RunTestCase(base.HttpTestCase{ - Object: base.ManagerApiExpect(), - Method: http.MethodDelete, - Path: "/apisix/admin/routes/r10", - Headers: map[string]string{"Authorization": base.GetToken()}, - ExpectStatus: http.StatusOK, - }) - }) - It("hit the route10 just deleted", func() { - base.RunTestCase(base.HttpTestCase{ - Object: base.APISIXExpect(), - Method: http.MethodGet, - Path: "/hello", - ExpectStatus: http.StatusNotFound, - ExpectBody: `{"error_msg":"404 Route Not Found"}`, - Sleep: base.SleepTime, - }) - }) - It("delete the service5", func() { - base.RunTestCase(base.HttpTestCase{ - Object: base.ManagerApiExpect(), - Method: http.MethodDelete, - Path: "/apisix/admin/services/s5", - Headers: map[string]string{"Authorization": base.GetToken()}, - ExpectStatus: http.StatusOK, - Sleep: base.SleepTime, - }) - }) - It("remove upstream6", func() { - base.RunTestCase(base.HttpTestCase{ - Object: base.ManagerApiExpect(), - Method: http.MethodDelete, - Path: "/apisix/admin/upstreams/6", - Headers: map[string]string{"Authorization": base.GetToken()}, - ExpectStatus: http.StatusOK, - }) - }) - }) - Context("test export route with jwt plugin", func() { - It("make sure the route is not created", func() { - base.RunTestCase(base.HttpTestCase{ - Object: base.APISIXExpect(), - Method: http.MethodGet, - Path: "/hello", - Headers: map[string]string{"Authorization": base.GetToken()}, - ExpectStatus: http.StatusNotFound, - ExpectBody: `{"error_msg":"404 Route Not Found"}`, - }) - }) - It("create route", func() { - base.RunTestCase(base.HttpTestCase{ - Object: base.ManagerApiExpect(), - Method: http.MethodPut, - Path: "/apisix/admin/routes/r1", - Body: `{ - "name": "route1", - "uri": "/hello", - "plugins": { - "jwt-auth": {} - }, - "upstream": { - "type": "roundrobin", - "nodes": [{ - "host": "` + base.UpstreamIp + `", - "port": 1980, - "weight": 1 - }] - } - }`, - Headers: map[string]string{"Authorization": base.GetToken()}, - ExpectStatus: http.StatusOK, - ExpectBody: `"code":0`, - }) - }) - It("make sure the consumer is not created", func() { - base.RunTestCase(base.HttpTestCase{ - Object: base.ManagerApiExpect(), - Method: http.MethodGet, - Path: "/apisix/admin/consumers/jack", - Headers: map[string]string{"Authorization": base.GetToken()}, - ExpectStatus: http.StatusNotFound, - }) - }) - It("create consumer", func() { - base.RunTestCase(base.HttpTestCase{ - Object: base.ManagerApiExpect(), - Method: http.MethodPut, - Path: "/apisix/admin/consumers", - Body: `{ - "username": "jack", - "plugins": { - "jwt-auth": { - "key": "user-key", - "secret": "my-secret-key", - "algorithm": "HS256" - } - }, - "desc": "test description" - }`, - Headers: map[string]string{"Authorization": base.GetToken()}, - ExpectStatus: http.StatusOK, - }) - }) - - It("create public api for JWT sign", func() { - base.RunTestCase(base.HttpTestCase{ - Object: base.ManagerApiExpect(), - Method: http.MethodPut, - Path: "/apisix/admin/routes/jwt-sign", - Body: `{ - "name": "jwt-auth", - "uri": "/apisix/plugin/jwt/sign", - "plugins": { - "public-api": {} - }, - "upstream": { - "type": "roundrobin", - "nodes": [{ - "host": "` + base.UpstreamIp + `", - "port": 1980, - "weight": 1 - }] - } - }`, - Headers: map[string]string{"Authorization": base.GetToken()}, - ExpectStatus: http.StatusOK, - ExpectBody: `"code":0`, - }) - }) - - jwtToken := "" - exportStrJWT := "" - It("sign jwt token", func() { - time.Sleep(base.SleepTime) - // sign jwt token - body, status, err := base.HttpGet(base.APISIXHost+"/apisix/plugin/jwt/sign?key=user-key", nil) - assert.Nil(GinkgoT(), err) - assert.Equal(GinkgoT(), http.StatusOK, status) - jwtToken = string(body) - - // sign jwt token with not exists key - _, status, err = base.HttpGet(base.APISIXHost+"/apisix/plugin/jwt/sign?key=not-exist-key", nil) - assert.Nil(GinkgoT(), err) - assert.Equal(GinkgoT(), http.StatusNotFound, status) - - exportStrJWT = ` - "components": { - "securitySchemes": { - "bearerAuth": { - "bearerFormat": "JWT", - "scheme": "bearer", - "type": "http" - } - } - }, - "info": { - "title": "RoutesExport", - "version": "3.0.0" - }, - "openapi": "3.0.0", - ` - exportStrJWT = replaceStr(exportStrJWT) - // verify token and clean test data - }) - It("verify route with correct jwt token", func() { - base.RunTestCase(base.HttpTestCase{ - Object: base.APISIXExpect(), - Method: http.MethodGet, - Path: "/hello", - Headers: map[string]string{"Authorization": jwtToken}, - ExpectStatus: http.StatusOK, - ExpectBody: "hello world", - Sleep: base.SleepTime, - }) - }) - It("export route", func() { - base.RunTestCase(base.HttpTestCase{ - Object: base.ManagerApiExpect(), - Method: http.MethodGet, - Path: "/apisix/admin/export/routes/r1", - Headers: map[string]string{"Authorization": base.GetToken()}, - ExpectStatus: http.StatusOK, - ExpectBody: exportStrJWT, - }) - }) - It("delete consumer", func() { - base.RunTestCase(base.HttpTestCase{ - Object: base.ManagerApiExpect(), - Method: http.MethodDelete, - Path: "/apisix/admin/consumers/jack", - Headers: map[string]string{"Authorization": base.GetToken()}, - ExpectStatus: http.StatusOK, - }) - }) - It("verify route with the jwt token from just deleted consumer", func() { - base.RunTestCase(base.HttpTestCase{ - Object: base.APISIXExpect(), - Method: http.MethodGet, - Path: "/hello", - Headers: map[string]string{"Authorization": jwtToken}, - ExpectStatus: http.StatusUnauthorized, - ExpectBody: `{"message":"Missing related consumer"}`, - Sleep: base.SleepTime, - }) - }) - It("delete route", func() { - base.RunTestCase(base.HttpTestCase{ - Object: base.ManagerApiExpect(), - Method: http.MethodDelete, - Path: "/apisix/admin/routes/r1", - Headers: map[string]string{"Authorization": base.GetToken()}, - ExpectStatus: http.StatusOK, - }) - }) - It("verify the deleted route", func() { - base.RunTestCase(base.HttpTestCase{ - Object: base.APISIXExpect(), - Method: http.MethodGet, - Path: "/hello", - ExpectBody: `{"error_msg":"404 Route Not Found"}`, - ExpectStatus: http.StatusNotFound, - Sleep: base.SleepTime, - }) - }) - exportStrJWTNoAlgorithm := ` - "components": { - "securitySchemes": { - "bearerAuth": { - "bearerFormat": "JWT", - "scheme": "bearer", - "type": "http" - } - } - }, - "info": { - "title": "RoutesExport", - "version": "3.0.0" - }, - "openapi": "3.0.0", - ` - exportStrJWTNoAlgorithm = replaceStr(exportStrJWTNoAlgorithm) - - It("create consumer with jwt (no algorithm)", func() { - base.RunTestCase(base.HttpTestCase{ - Object: base.ManagerApiExpect(), - Method: http.MethodPut, - Path: "/apisix/admin/consumers", - Body: `{ - "username":"consumer_1", - "desc": "test description", - "plugins":{ - "jwt-auth":{ - "exp":86400, - "key":"user-key", - "secret":"my-secret-key" - } - } - }`, - Headers: map[string]string{"Authorization": base.GetToken()}, - ExpectBody: `"code":0`, - ExpectStatus: http.StatusOK, - Sleep: base.SleepTime, - }) - }) - It("get the consumer", func() { - base.RunTestCase(base.HttpTestCase{ - Object: base.ManagerApiExpect(), - Method: http.MethodGet, - Path: "/apisix/admin/consumers/consumer_1", - Headers: map[string]string{"Authorization": base.GetToken()}, - ExpectStatus: http.StatusOK, - ExpectBody: `"username":"consumer_1"`, - Sleep: base.SleepTime, - }) - }) - It("create the route", func() { - base.RunTestCase(base.HttpTestCase{ - Object: base.ManagerApiExpect(), - Method: http.MethodPut, - Path: "/apisix/admin/routes/r1", - Body: `{ - "name": "route1", - "uri": "/hello", - "plugins": { - "jwt-auth": {} - }, - "upstream": { - "type": "roundrobin", - "nodes": [{ - "host": "` + base.UpstreamIp + `", - "port": 1980, - "weight": 1 - }] - } - }`, - Headers: map[string]string{"Authorization": base.GetToken()}, - ExpectStatus: http.StatusOK, - Sleep: base.SleepTime, - }) - }) - - jwttoken := "" - It("sign jwt token", func() { - // sign jwt token - body, status, err := base.HttpGet(base.APISIXHost+"/apisix/plugin/jwt/sign?key=user-key", nil) - assert.Nil(GinkgoT(), err) - assert.Equal(GinkgoT(), http.StatusOK, status) - jwttoken = string(body) - }) - It("hit route with jwt token", func() { - base.RunTestCase(base.HttpTestCase{ - Object: base.APISIXExpect(), - Method: http.MethodGet, - Path: "/hello", - Headers: map[string]string{"Authorization": jwttoken}, - ExpectStatus: http.StatusOK, - ExpectBody: "hello world", - Sleep: base.SleepTime, - }) - }) - It("export route", func() { - base.RunTestCase(base.HttpTestCase{ - Object: base.ManagerApiExpect(), - Method: http.MethodGet, - Path: "/apisix/admin/export/routes/r1", - Headers: map[string]string{"Authorization": base.GetToken()}, - ExpectStatus: http.StatusOK, - ExpectBody: exportStrJWTNoAlgorithm, - }) - }) - It("delete consumer", func() { - base.RunTestCase(base.HttpTestCase{ - Object: base.ManagerApiExpect(), - Method: http.MethodDelete, - Path: "/apisix/admin/consumers/consumer_1", - Headers: map[string]string{"Authorization": base.GetToken()}, - ExpectStatus: http.StatusOK, - ExpectBody: `"code":0`, - }) - }) - It("after delete consumer verify it again", func() { - base.RunTestCase(base.HttpTestCase{ - Object: base.ManagerApiExpect(), - Method: http.MethodGet, - Path: "/apisix/admin/consumers/jack", - Headers: map[string]string{"Authorization": base.GetToken()}, - ExpectStatus: http.StatusNotFound, - Sleep: base.SleepTime, - }) - }) - It("delete the route", func() { - base.RunTestCase(base.HttpTestCase{ - Object: base.ManagerApiExpect(), - Method: http.MethodDelete, - Path: "/apisix/admin/routes/r1", - Headers: map[string]string{"Authorization": base.GetToken()}, - ExpectStatus: http.StatusOK, - }) - }) - }) - - Context("test export route with auth plugin", func() { - It("make sure the route is not created", func() { - base.RunTestCase(base.HttpTestCase{ - Object: base.APISIXExpect(), - Method: http.MethodGet, - Path: "/hello", - ExpectBody: `{"error_msg":"404 Route Not Found"}`, - ExpectStatus: http.StatusNotFound, - }) - }) - It("create route", func() { - base.RunTestCase(base.HttpTestCase{ - Object: base.ManagerApiExpect(), - Method: http.MethodPut, - Path: "/apisix/admin/routes/r1", - Body: `{ - "name": "route1", - "uri": "/hello", - "plugins": { - "key-auth": {}, - "basic-auth": {} - }, - "upstream": { - "type": "roundrobin", - "nodes": [{ - "host": "` + base.UpstreamIp + `", - "port": 1980, - "weight": 1 - }] - } - }`, - Headers: map[string]string{"Authorization": base.GetToken()}, - ExpectBody: `"code":0`, - ExpectStatus: http.StatusOK, - }) - }) - It("make sure the consumer is not created", func() { - base.RunTestCase(base.HttpTestCase{ - Object: base.ManagerApiExpect(), - Method: http.MethodGet, - Path: "/apisix/admin/consumers/jack", - Headers: map[string]string{"Authorization": base.GetToken()}, - ExpectStatus: http.StatusNotFound, - }) - }) - It("create consumer", func() { - base.RunTestCase(base.HttpTestCase{ - Object: base.ManagerApiExpect(), - Method: http.MethodPut, - Path: "/apisix/admin/consumers", - Body: `{ - "username": "jack", - "plugins": { - "key-auth": { - "key": "auth-one" - }, - "basic-auth": { - "username": "jack", - "password": "123456" - } - }, - "desc": "test description" - }`, - Headers: map[string]string{"Authorization": base.GetToken()}, - ExpectStatus: http.StatusOK, - }) - }) - - time.Sleep(base.SleepTime) - - exportStrAuth := ` - "components": { - "securitySchemes": { - "api_key": { - "in": "header", - "name": "X-XSRF-TOKEN", - "type": "apiKey" - }, - "basicAuth": { - "in": "header", - "name": "basicAuth", - "type": "basicAuth" - } - } - }, - "info": { - "title": "RoutesExport", - "version": "3.0.0" - }, - "openapi": "3.0.0"` - time.Sleep(base.SleepTime) - - exportStrAuth = replaceStr(exportStrAuth) - - It("verify route with correct basic-auth and key-auth token", func() { - base.RunTestCase(base.HttpTestCase{ - Object: base.APISIXExpect(), - Method: http.MethodGet, - Path: "/hello", - Headers: map[string]string{"Authorization": "Basic amFjazoxMjM0NTYKIA==", "apikey": "auth-one"}, - ExpectStatus: http.StatusOK, - ExpectBody: "hello world", - Sleep: base.SleepTime, - }) - }) - It("export route", func() { - base.RunTestCase(base.HttpTestCase{ - Object: base.ManagerApiExpect(), - Method: http.MethodGet, - Path: "/apisix/admin/export/routes/r1", - Headers: map[string]string{"Authorization": base.GetToken()}, - ExpectStatus: http.StatusOK, - ExpectBody: exportStrAuth, - }) - }) - It("delete consumer", func() { - base.RunTestCase(base.HttpTestCase{ - Object: base.ManagerApiExpect(), - Method: http.MethodDelete, - Path: "/apisix/admin/consumers/jack", - Headers: map[string]string{"Authorization": base.GetToken()}, - ExpectStatus: http.StatusOK, - }) - }) - It("verify route with the basic-auth and key-auth token from just deleted consumer", func() { - base.RunTestCase(base.HttpTestCase{ - Object: base.APISIXExpect(), - Method: http.MethodGet, - Path: "/hello", - Headers: map[string]string{"Authorization": "Basic amFjazoxMjM0NTYKIA==", "apikey": "auth-one"}, - ExpectStatus: http.StatusUnauthorized, - ExpectBody: `{"message":"Missing related consumer"}`, - Sleep: base.SleepTime, - }) - }) - It("delete route", func() { - base.RunTestCase(base.HttpTestCase{ - Object: base.ManagerApiExpect(), - Method: http.MethodDelete, - Path: "/apisix/admin/routes/r1", - Headers: map[string]string{"Authorization": base.GetToken()}, - ExpectStatus: http.StatusOK, - }) - }) - It("verify the deleted route", func() { - base.RunTestCase(base.HttpTestCase{ - Object: base.APISIXExpect(), - Method: http.MethodGet, - Path: "/hello", - ExpectStatus: http.StatusNotFound, - ExpectBody: `{"error_msg":"404 Route Not Found"}`, - Sleep: base.SleepTime, - }) - }) - }) - Context("test route export label", func() { - // 10.Create a service with label data and a route with label data, and export the route. - // Label is the original data of the route - serviceStrS1 := ` - "name": "testservice", - "desc": "testservice_desc", - "labels": { - "build": "10" - }, - "enable_websocket": true` - serviceStrS1 = replaceStr(serviceStrS1) - - exportStrR1 := ` - "components": {}, - "info": { - "title": "RoutesExport", - "version": "3.0.0" - }, - "openapi": "3.0.0", - "paths": { - "/hello": { - "get": { - "operationId": "route_allGET", - "requestBody": {}, - "responses": { - "default": { - "description": "" - } - }, - "summary": "ๆ‰€ๆœ‰", - "x-apisix-enable_websocket": false, - "x-apisix-id":"r1", - "x-apisix-labels": { - "build": "16", - "env": "production", - "version": "v2" - }, - "x-apisix-priority": 0, - "x-apisix-status": 1, - "x-apisix-vars":[ - [ - "arg_name", - "==", - "test" - ] - ] - }` - exportStrR1 = replaceStr(exportStrR1) - - It("create service", func() { - base.RunTestCase(base.HttpTestCase{ - Object: base.ManagerApiExpect(), - Method: http.MethodPut, - Path: "/apisix/admin/services/s1", - Headers: map[string]string{"Authorization": base.GetToken()}, - Body: `{ - "name": "testservice", - "desc": "testservice_desc", - "enable_websocket":true, - "labels": { - "build": "10" - } - }`, - ExpectStatus: http.StatusOK, - }) - }) - It("get the service s1", func() { - base.RunTestCase(base.HttpTestCase{ - Object: base.ManagerApiExpect(), - Method: http.MethodGet, - Path: "/apisix/admin/services/s1", - Headers: map[string]string{"Authorization": base.GetToken()}, - ExpectCode: http.StatusOK, - ExpectBody: serviceStrS1, - }) - }) - It("Create a service with label data and a route with label data", func() { - base.RunTestCase(base.HttpTestCase{ - Object: base.ManagerApiExpect(), - Method: http.MethodPut, - Path: "/apisix/admin/routes/r1", - Body: `{ - "name": "route_all", - "desc": "ๆ‰€ๆœ‰", - "status": 1, - "methods": ["GET"], - "service_id": "s1", - "labels": { - "build": "16", - "env": "production", - "version": "v2" - }, - "vars": [ - ["arg_name", "==", "test"] - ], - "uri": "/hello" - }`, - Headers: map[string]string{"Authorization": base.GetToken()}, - ExpectCode: http.StatusOK, - Sleep: base.SleepTime, - }) - }) - It("export route1", func() { - base.RunTestCase(base.HttpTestCase{ - Object: base.ManagerApiExpect(), - Method: http.MethodGet, - Path: "/apisix/admin/export/routes/r1", - Headers: map[string]string{"Authorization": base.GetToken()}, - ExpectCode: http.StatusOK, - ExpectBody: exportStrR1, - }) - }) - It("delete the route1 just created", func() { - base.RunTestCase(base.HttpTestCase{ - Object: base.ManagerApiExpect(), - Method: http.MethodDelete, - Path: "/apisix/admin/routes/r1", - Headers: map[string]string{"Authorization": base.GetToken()}, - ExpectCode: http.StatusOK, - }) - }) - It("hit the route1 just deleted", func() { - base.RunTestCase(base.HttpTestCase{ - Object: base.APISIXExpect(), - Method: http.MethodGet, - Path: "/hello", - ExpectStatus: http.StatusNotFound, - ExpectBody: `{"error_msg":"404 Route Not Found"}`, - Sleep: base.SleepTime, - }) - }) - It("delete the service1", func() { - base.RunTestCase(base.HttpTestCase{ - Object: base.ManagerApiExpect(), - Method: http.MethodDelete, - Path: "/apisix/admin/services/s1", - Headers: map[string]string{"Authorization": base.GetToken()}, - ExpectStatus: http.StatusOK, - Sleep: base.SleepTime, - }) - }) - - // 11.Create a service with label data and a route without label data, and export the route. - // Label is the data of the service - serviceStrS2 := ` - "name": "testservice", - "desc": "testservice_desc", - "labels": { - "build": "16", - "env": "production", - "version": "v2" - }, - "enable_websocket": true` - serviceStrS2 = replaceStr(serviceStrS2) - - exportStrR2 := ` - "components": {}, - "info": { - "title": "RoutesExport", - "version": "3.0.0" - }, - "openapi": "3.0.0", - "paths": { - "/hello": { - "get": { - "operationId": "route_allGET", - "requestBody": {}, - "responses": { - "default": { - "description": "" - } - }, - "summary": "ๆ‰€ๆœ‰", - "x-apisix-enable_websocket": false, - "x-apisix-id":"r2", - "x-apisix-labels": { - "build": "16", - "env": "production", - "version": "v2" - }, - "x-apisix-priority": 0, - "x-apisix-status": 1, - "x-apisix-vars":[ - [ - "arg_name", - "==", - "test" - ] - ] - }` - exportStrR2 = replaceStr(exportStrR2) - - It("create service", func() { - base.RunTestCase(base.HttpTestCase{ - Object: base.ManagerApiExpect(), - Method: http.MethodPut, - Path: "/apisix/admin/services/s2", - Headers: map[string]string{"Authorization": base.GetToken()}, - Body: `{ - "name": "testservice", - "desc": "testservice_desc", - "enable_websocket":true, - "labels": { - "build": "16", - "env": "production", - "version": "v2" - } - }`, - ExpectStatus: http.StatusOK, - }) - }) - It("get the service s2", func() { - base.RunTestCase(base.HttpTestCase{ - Object: base.ManagerApiExpect(), - Method: http.MethodGet, - Path: "/apisix/admin/services/s2", - Headers: map[string]string{"Authorization": base.GetToken()}, - ExpectBody: serviceStrS2, - ExpectStatus: http.StatusOK, - }) - }) - It("Create a service with label data and a route without label data", func() { - base.RunTestCase(base.HttpTestCase{ - Object: base.ManagerApiExpect(), - Method: http.MethodPut, - Path: "/apisix/admin/routes/r2", - Body: `{ - "name": "route_all", - "desc": "ๆ‰€ๆœ‰", - "status": 1, - "methods": ["GET"], - "service_id": "s2", - "vars": [ - ["arg_name", "==", "test"] - ], - "uri": "/hello" - }`, - Headers: map[string]string{"Authorization": base.GetToken()}, - ExpectStatus: http.StatusOK, - Sleep: base.SleepTime, - }) - }) - It("export route2", func() { - base.RunTestCase(base.HttpTestCase{ - Object: base.ManagerApiExpect(), - Method: http.MethodGet, - Path: "/apisix/admin/export/routes/r2", - Headers: map[string]string{"Authorization": base.GetToken()}, - ExpectBody: exportStrR2, - ExpectStatus: http.StatusOK, - }) - }) - It("delete the route2 just created", func() { - base.RunTestCase(base.HttpTestCase{ - Object: base.ManagerApiExpect(), - Method: http.MethodDelete, - Path: "/apisix/admin/routes/r2", - Headers: map[string]string{"Authorization": base.GetToken()}, - ExpectStatus: http.StatusOK, - }) - }) - It("hit the route2 just deleted", func() { - base.RunTestCase(base.HttpTestCase{ - Object: base.APISIXExpect(), - Method: http.MethodGet, - Path: "/hello", - ExpectStatus: http.StatusNotFound, - ExpectBody: "{\"error_msg\":\"404 Route Not Found\"}\n", - Sleep: base.SleepTime, - }) - }) - It("delete the service2", func() { - base.RunTestCase(base.HttpTestCase{ - Object: base.ManagerApiExpect(), - Method: http.MethodDelete, - Path: "/apisix/admin/services/s2", - Headers: map[string]string{"Authorization": base.GetToken()}, - ExpectStatus: http.StatusOK, - Sleep: base.SleepTime, - }) - }) - }) - Context("test route export request validation", func() { - // 12.Test export route request_ validation data correctness - exportStrR1 := ` - "components": {}, - "info": { - "title": "RoutesExport", - "version": "3.0.0" - }, - "openapi": "3.0.0", - "paths": { - "/test-test": { - "get": { - "operationId": "route_allGET", - "parameters": [{ - "in": "header", - "name": "test", - "schema": { - "type": "string" - } - }], - "requestBody": { - "content": { - "*/*": { - "schema": { - "properties": { - "boolean_payload": { - "type": "boolean" - }, - "required_payload": { - "type": "string" - } - }, - "required": ["required_payload"], - "type": "object" - } - } - } - }, - "responses": { - "default": { - "description": "" - } - }, - "security": [], - "summary": "ๆ‰€ๆœ‰", - "x-apisix-enable_websocket": false, - "x-apisix-hosts": ["test.com"], - "x-apisix-id":"r1", - "x-apisix-priority": 0, - "x-apisix-status": 1 - } - } - }` - exportStrR1 = replaceStr(exportStrR1) - - It("Create a route containing request_ validation data", func() { - base.RunTestCase(base.HttpTestCase{ - Object: base.ManagerApiExpect(), - Method: http.MethodPut, - Path: "/apisix/admin/routes/r1", - Body: `{ - "uris": ["/test-test"], - "name": "route_all", - "desc": "ๆ‰€ๆœ‰", - "methods": ["GET"], - "hosts": ["test.com"], - "plugins": { - "request-validation": { - "body_schema": { - "properties": { - "boolean_payload": { - "type": "boolean" - }, - "required_payload": { - "type": "string" - } - }, - "required": ["required_payload"], - "type": "object" - }, - "disable": false, - "header_schema": { - "properties": { - "test": { - "enum": "test-enum", - "type": "string" - } - }, - "type": "string" - } - } - }, - "status": 1 - }`, - Headers: map[string]string{"Authorization": base.GetToken()}, - ExpectStatus: http.StatusOK, - Sleep: base.SleepTime, - }) - }) - It("export route1", func() { - base.RunTestCase(base.HttpTestCase{ - Object: base.ManagerApiExpect(), - Method: http.MethodGet, - Path: "/apisix/admin/export/routes/r1", - Headers: map[string]string{"Authorization": base.GetToken()}, - ExpectStatus: http.StatusOK, - ExpectBody: exportStrR1, - }) - }) - It("delete the route1 just created", func() { - base.RunTestCase(base.HttpTestCase{ - Object: base.ManagerApiExpect(), - Method: http.MethodDelete, - Path: "/apisix/admin/routes/r1", - Headers: map[string]string{"Authorization": base.GetToken()}, - ExpectStatus: http.StatusOK, - }) - }) - It("hit the route1 just deleted", func() { - base.RunTestCase(base.HttpTestCase{ - Object: base.APISIXExpect(), - Method: http.MethodGet, - Path: "/hello", - ExpectStatus: http.StatusNotFound, - ExpectBody: `{"error_msg":"404 Route Not Found"}`, - Sleep: base.SleepTime, - }) - }) - }) - Context("test route export equal uri", func() { - // 13.Add suffix when testing the same URI export - exportStrAll := ` - "components": {}, - "info": { - "title": "RoutesExport", - "version": "3.0.0" - }, - "openapi": "3.0.0", - "paths": { - "/test-test": { - "get": { - "operationId": "route_allGET", - "requestBody": {}, - "responses": { - "default": { - "description": "" - } - }, - "summary": "ๆ‰€ๆœ‰", - "x-apisix-enable_websocket": false, - "x-apisix-hosts": ["test.com"], - "x-apisix-id":"r1", - "x-apisix-priority": 0, - "x-apisix-status": 1, - "x-apisix-upstream": { - "nodes": { - "` + base.UpstreamIp + `:1980": 1 - }, - "type": "roundrobin" - } - } - }, - "/test-test-APISIX-REPEAT-URI-2": { - "get": { - "operationId": "route_all2GET", - "requestBody": {}, - "responses": { - "default": { - "description": "" - } - }, - "summary": "ๆ‰€ๆœ‰1", - "x-apisix-enable_websocket": false, - "x-apisix-hosts": ["test.com"], - "x-apisix-id":"r2", - "x-apisix-priority": 0, - "x-apisix-status": 1, - "x-apisix-upstream": { - "nodes": { - "` + base.UpstreamIp + `:1980": 1 - }, - "type": "roundrobin" - } - } - }, - "/test-test-APISIX-REPEAT-URI-3": { - "get": { - "operationId": "route_all3GET", - "requestBody": {}, - "responses": { - "default": { - "description": "" - } - }, - "summary": "ๆ‰€ๆœ‰2", - "x-apisix-enable_websocket": false, - "x-apisix-hosts": ["test.com"], - "x-apisix-id":"r3", - "x-apisix-priority": 0, - "x-apisix-status": 1, - "x-apisix-upstream": { - "nodes": { - "` + base.UpstreamIp + `:1981": 1 - }, - "type": "roundrobin" - } - } - } - }` - exportStrAll = replaceStr(exportStrAll) - - It("Create a route", func() { - base.RunTestCase(base.HttpTestCase{ - Object: base.ManagerApiExpect(), - Method: http.MethodPut, - Path: "/apisix/admin/routes/r1", - Body: `{ - "uris": ["/test-test"], - "name": "route_all", - "desc": "ๆ‰€ๆœ‰", - "methods": ["GET"], - "hosts": ["test.com"], - "status": 1, - "upstream": { - "nodes": { - "` + base.UpstreamIp + `:1980": 1 - }, - "type": "roundrobin" - } - }`, - Headers: map[string]string{"Authorization": base.GetToken()}, - ExpectStatus: http.StatusOK, - Sleep: base.SleepTime, - }) - }) - It("Create a route2", func() { - base.RunTestCase(base.HttpTestCase{ - Object: base.ManagerApiExpect(), - Method: http.MethodPut, - Path: "/apisix/admin/routes/r2", - Body: `{ - "uris": ["/test-test"], - "name": "route_all2", - "desc": "ๆ‰€ๆœ‰1", - "methods": ["GET"], - "hosts": ["test.com"], - "status": 1, - "upstream": { - "nodes": { - "` + base.UpstreamIp + `:1980": 1 - }, - "type": "roundrobin" - } - }`, - Headers: map[string]string{"Authorization": base.GetToken()}, - ExpectStatus: http.StatusOK, - Sleep: base.SleepTime, - }) - }) - It("Create a route3", func() { - base.RunTestCase(base.HttpTestCase{ - Object: base.ManagerApiExpect(), - Method: http.MethodPut, - Path: "/apisix/admin/routes/r3", - Body: `{ - "uris": ["/test-test"], - "name": "route_all3", - "desc": "ๆ‰€ๆœ‰2", - "methods": ["GET"], - "hosts": ["test.com"], - "status": 1, - "upstream": { - "nodes": { - "` + base.UpstreamIp + `:1981": 1 - }, - "type": "roundrobin" - } - }`, - Headers: map[string]string{"Authorization": base.GetToken()}, - ExpectStatus: http.StatusOK, - Sleep: base.SleepTime, - }) - }) - It("use the exportall interface to export all routes", func() { - base.RunTestCase(base.HttpTestCase{ - Object: base.ManagerApiExpect(), - Method: http.MethodGet, - Path: "/apisix/admin/export/routes/r1,r2,r3", - Headers: map[string]string{"Authorization": base.GetToken()}, - ExpectStatus: http.StatusOK, - ExpectBody: exportStrAll, - }) - }) - It("delete the route1 just created", func() { - base.RunTestCase(base.HttpTestCase{ - Object: base.ManagerApiExpect(), - Method: http.MethodDelete, - Path: "/apisix/admin/routes/r1", - Headers: map[string]string{"Authorization": base.GetToken()}, - ExpectStatus: http.StatusOK, - }) - }) - It("hit the route1 just deleted", func() { - base.RunTestCase(base.HttpTestCase{ - Object: base.APISIXExpect(), - Method: http.MethodGet, - Path: "/hello", - Headers: map[string]string{"Authorization": base.GetToken()}, - ExpectBody: `{"error_msg":"404 Route Not Found"}`, - Sleep: base.SleepTime, - }) - }) - It("delete the route2 just created", func() { - base.RunTestCase(base.HttpTestCase{ - Object: base.ManagerApiExpect(), - Method: http.MethodDelete, - Path: "/apisix/admin/routes/r2", - Headers: map[string]string{"Authorization": base.GetToken()}, - ExpectStatus: http.StatusOK, - }) - }) - It("hit the route2 just deleted", func() { - base.RunTestCase(base.HttpTestCase{ - Object: base.APISIXExpect(), - Method: http.MethodGet, - Path: "/hello", - ExpectStatus: http.StatusNotFound, - ExpectBody: `{"error_msg":"404 Route Not Found"}`, - Sleep: base.SleepTime, - }) - }) - }) -}) - -func replaceStr(str string) string { - str = strings.Replace(str, "\n", "", -1) - str = strings.Replace(str, "\t", "", -1) - str = strings.Replace(str, " ", "", -1) - return str -} diff --git a/api/test/e2e/route/route_service_upstream_test.go b/api/test/e2e/route/route_service_upstream_test.go deleted file mode 100644 index 055f0939bf..0000000000 --- a/api/test/e2e/route/route_service_upstream_test.go +++ /dev/null @@ -1,424 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one or more - * contributor license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright ownership. - * The ASF licenses this file to You under the Apache License, Version 2.0 - * (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package route_test - -import ( - "net/http" - "time" - - . "github.com/onsi/ginkgo/v2" - . "github.com/onsi/gomega" - - "github.com/apisix/manager-api/test/e2e/base" -) - -var _ = Describe("create route that not exists service or upstream", func() { - DescribeTable("test create route that not exists service or upstream", - func(tc base.HttpTestCase) { - base.RunTestCase(tc) - }, - Entry("make sure the route has not created", base.HttpTestCase{ - Object: base.APISIXExpect(), - Method: http.MethodGet, - Path: "/hello_", - ExpectStatus: http.StatusNotFound, - ExpectBody: `{"error_msg":"404 Route Not Found"}`, - Sleep: base.SleepTime, - }), - Entry("create route that not exists service", base.HttpTestCase{ - Object: base.ManagerApiExpect(), - Method: http.MethodPut, - Path: "/apisix/admin/routes/r1", - Body: `{ - "name": "route1", - "uri": "/hello_", - "service_id": "not-exists" - }`, - Headers: map[string]string{"Authorization": base.GetToken()}, - ExpectStatus: http.StatusBadRequest, - }), - Entry("verify not-exist route", base.HttpTestCase{ - Object: base.APISIXExpect(), - Method: http.MethodGet, - Path: "/hello_", - ExpectStatus: http.StatusNotFound, - ExpectBody: `{"error_msg":"404 Route Not Found"}`, - Sleep: base.SleepTime, - }), - Entry("create route that not exists upstream", base.HttpTestCase{ - Object: base.ManagerApiExpect(), - Method: http.MethodPut, - Path: "/apisix/admin/routes/r1", - Body: `{ - "name": "route1", - "uri": "/hello_", - "upstream_id": "not-exists" - }`, - Headers: map[string]string{"Authorization": base.GetToken()}, - ExpectStatus: http.StatusBadRequest, - }), - Entry("verify not-exist route", base.HttpTestCase{ - Object: base.APISIXExpect(), - Method: http.MethodGet, - Path: "/hello_", - ExpectStatus: http.StatusNotFound, - ExpectBody: `{"error_msg":"404 Route Not Found"}`, - Sleep: base.SleepTime, - }), - Entry("create route that not exists service and upstream", base.HttpTestCase{ - Object: base.ManagerApiExpect(), - Method: http.MethodPut, - Path: "/apisix/admin/routes/r1", - Body: `{ - "name": "route1", - "uri": "/hello_", - "service_id": "not-exists-service", - "upstream_id": "not-exists-upstream" - }`, - Headers: map[string]string{"Authorization": base.GetToken()}, - ExpectStatus: http.StatusBadRequest, - }), - Entry("verify not-exist route", base.HttpTestCase{ - Object: base.APISIXExpect(), - Method: http.MethodGet, - Path: "/hello_", - ExpectStatus: http.StatusNotFound, - ExpectBody: `{"error_msg":"404 Route Not Found"}`, - Sleep: base.SleepTime, - }), - Entry("create service with not-exist upstream", base.HttpTestCase{ - Object: base.ManagerApiExpect(), - Method: http.MethodPut, - Path: "/apisix/admin/services/100", - Body: `{ - "upstream_id": "not-exists-upstream" - }`, - Headers: map[string]string{"Authorization": base.GetToken()}, - ExpectStatus: http.StatusBadRequest, - }), - Entry("create route with service(service with not exist upstream)", base.HttpTestCase{ - Object: base.ManagerApiExpect(), - Method: http.MethodPut, - Path: "/apisix/admin/routes/r1", - Body: `{ - "name": "route1", - "uri": "/hello_", - "service_id": "not-exists" - }`, - Headers: map[string]string{"Authorization": base.GetToken()}, - ExpectStatus: http.StatusBadRequest, - }), - Entry("verify not-exist route", base.HttpTestCase{ - Object: base.APISIXExpect(), - Method: http.MethodGet, - Path: "/hello_", - ExpectStatus: http.StatusNotFound, - ExpectBody: `{"error_msg":"404 Route Not Found"}`, - Sleep: base.SleepTime, - }), - ) -}) - -var _ = Describe("route create with service", func() { - DescribeTable("test route create with service", - func(tc base.HttpTestCase) { - base.RunTestCase(tc) - }, - Entry("make sure the route has not created", base.HttpTestCase{ - Object: base.APISIXExpect(), - Method: http.MethodGet, - Path: "/server_port", - ExpectStatus: http.StatusNotFound, - ExpectBody: `{"error_msg":"404 Route Not Found"}`, - Sleep: base.SleepTime, - }), - Entry("create service", base.HttpTestCase{ - Object: base.ManagerApiExpect(), - Method: http.MethodPut, - Path: "/apisix/admin/services/200", - Body: `{ - "upstream": { - "type": "roundrobin", - "nodes": [ - { - "host": "` + base.UpstreamIp + `", - "port": 1980, - "weight": 1 - }, - { - "host": "` + base.UpstreamIp + `", - "port": 1981, - "weight": 1 - }, - { - "host": "` + base.UpstreamIp + `", - "port": 1982, - "weight": 1 - } - ] - } - }`, - Headers: map[string]string{"Authorization": base.GetToken()}, - ExpectStatus: http.StatusOK, - }), - Entry("create route using the service just created", base.HttpTestCase{ - Object: base.ManagerApiExpect(), - Method: http.MethodPut, - Path: "/apisix/admin/routes/r1", - Body: `{ - "name": "route1", - "uri": "/server_port", - "service_id": "200" - }`, - Headers: map[string]string{"Authorization": base.GetToken()}, - ExpectStatus: http.StatusOK, - Sleep: base.SleepTime, - }), - ) - It("batch test /server_port api", func() { - // sleep for etcd sync - time.Sleep(time.Duration(300) * time.Millisecond) - - // batch test /server_port api - res := base.BatchTestServerPort(18, nil, "") - - Expect(res["1980"]).Should(Equal(6)) - Expect(res["1981"]).Should(Equal(6)) - Expect(res["1982"]).Should(Equal(6)) - }) - DescribeTable("delete route and service", - func(tc base.HttpTestCase) { - base.RunTestCase(tc) - }, - Entry("delete route", base.HttpTestCase{ - Object: base.ManagerApiExpect(), - Method: http.MethodDelete, - Path: "/apisix/admin/routes/r1", - Headers: map[string]string{"Authorization": base.GetToken()}, - ExpectStatus: http.StatusOK, - }), - Entry("remove service", base.HttpTestCase{ - Object: base.ManagerApiExpect(), - Method: http.MethodDelete, - Path: "/apisix/admin/services/200", - Headers: map[string]string{"Authorization": base.GetToken()}, - ExpectStatus: http.StatusOK, - }), - Entry("make sure the route deleted", base.HttpTestCase{ - Object: base.APISIXExpect(), - Method: http.MethodGet, - Path: "/server_port", - ExpectStatus: http.StatusNotFound, - ExpectBody: `{"error_msg":"404 Route Not Found"}`, - }), - ) -}) - -var _ = Describe("route create upstream", func() { - DescribeTable("test route create upstream", - func(tc base.HttpTestCase) { - base.RunTestCase(tc) - }, - Entry("create upstream", base.HttpTestCase{ - Object: base.ManagerApiExpect(), - Method: http.MethodPut, - Path: "/apisix/admin/upstreams/1", - Body: `{ - "nodes":[ - { - "host": "` + base.UpstreamIp + `", - "port": 1980, - "weight": 1 - }, - { - "host": "` + base.UpstreamIp + `", - "port": 1981, - "weight": 1 - }, - { - "host": "` + base.UpstreamIp + `", - "port": 1982, - "weight": 1 - } - ], - "type": "roundrobin" - }`, - Headers: map[string]string{"Authorization": base.GetToken()}, - ExpectStatus: http.StatusOK, - }), - Entry("make sure the route has not created", base.HttpTestCase{ - Object: base.APISIXExpect(), - Method: http.MethodGet, - Path: "/server_port", - ExpectStatus: http.StatusNotFound, - ExpectBody: `{"error_msg":"404 Route Not Found"}`, - Sleep: base.SleepTime, - }), - Entry("create route using the upstream just created", base.HttpTestCase{ - Object: base.ManagerApiExpect(), - Method: http.MethodPut, - Path: "/apisix/admin/routes/r1", - Body: `{ - "name": "route1", - "uri": "/server_port", - "upstream_id": "1" - }`, - Headers: map[string]string{"Authorization": base.GetToken()}, - ExpectStatus: http.StatusOK, - }), - ) - It("batch test /server_port api", func() { - // sleep for etcd sync - time.Sleep(time.Duration(300) * time.Millisecond) - - // batch test /server_port api - res := base.BatchTestServerPort(12, nil, "") - - Expect(res["1980"]).Should(Equal(4)) - Expect(res["1981"]).Should(Equal(4)) - Expect(res["1982"]).Should(Equal(4)) - }) - DescribeTable("delete route and upstream", - func(tc base.HttpTestCase) { - base.RunTestCase(tc) - }, - Entry("delete route", base.HttpTestCase{ - Object: base.ManagerApiExpect(), - Method: http.MethodDelete, - Path: "/apisix/admin/routes/r1", - Headers: map[string]string{"Authorization": base.GetToken()}, - ExpectStatus: http.StatusOK, - }), - Entry("remove upstream", base.HttpTestCase{ - Object: base.ManagerApiExpect(), - Method: http.MethodDelete, - Path: "/apisix/admin/upstreams/1", - Headers: map[string]string{"Authorization": base.GetToken()}, - ExpectStatus: http.StatusOK, - }), - Entry("make sure the route deleted", base.HttpTestCase{ - Object: base.APISIXExpect(), - Method: http.MethodGet, - Path: "/server_port", - ExpectStatus: http.StatusNotFound, - ExpectBody: `{"error_msg":"404 Route Not Found"}`, - }), - ) -}) - -var _ = Describe("route create with service that contains upstream", func() { - DescribeTable("test route create with service that contains upstream", - func(tc base.HttpTestCase) { - base.RunTestCase(tc) - }, - Entry("make sure the route has not created", base.HttpTestCase{ - Object: base.APISIXExpect(), - Method: http.MethodGet, - Path: "/server_port", - ExpectStatus: http.StatusNotFound, - ExpectBody: `{"error_msg":"404 Route Not Found"}`, - Sleep: base.SleepTime, - }), - Entry("create upstream", base.HttpTestCase{ - Object: base.ManagerApiExpect(), - Method: http.MethodPut, - Path: "/apisix/admin/upstreams/1", - Body: `{ - "nodes":[ - { - "host": "` + base.UpstreamIp + `", - "port": 1980, - "weight": 1 - }, - { - "host": "` + base.UpstreamIp + `", - "port": 1981, - "weight": 1 - }, - { - "host": "` + base.UpstreamIp + `", - "port": 1982, - "weight": 1 - } - ], - "type": "roundrobin" - }`, - Headers: map[string]string{"Authorization": base.GetToken()}, - ExpectStatus: http.StatusOK, - }), - Entry("create service", base.HttpTestCase{ - Object: base.ManagerApiExpect(), - Method: http.MethodPut, - Path: "/apisix/admin/services/200", - Body: `{ - "upstream_id": "1" - }`, - Headers: map[string]string{"Authorization": base.GetToken()}, - ExpectStatus: http.StatusOK, - }), - Entry("create route using the service just created", base.HttpTestCase{ - Object: base.ManagerApiExpect(), - Method: http.MethodPut, - Path: "/apisix/admin/routes/r1", - Body: `{ - "name": "route1", - "uri": "/server_port", - "service_id": "200" - }`, - Headers: map[string]string{"Authorization": base.GetToken()}, - ExpectStatus: http.StatusOK, - Sleep: base.SleepTime, - }), - ) - It("batch test /server_port api", func() { - // sleep for etcd sync - time.Sleep(time.Duration(300) * time.Millisecond) - - // batch test /server_port api - res := base.BatchTestServerPort(18, nil, "") - - Expect(res["1980"]).Should(Equal(6)) - Expect(res["1981"]).Should(Equal(6)) - Expect(res["1982"]).Should(Equal(6)) - }) - DescribeTable("delete route and service", - func(tc base.HttpTestCase) { - base.RunTestCase(tc) - }, - Entry("delete route", base.HttpTestCase{ - Object: base.ManagerApiExpect(), - Method: http.MethodDelete, - Path: "/apisix/admin/routes/r1", - Headers: map[string]string{"Authorization": base.GetToken()}, - ExpectStatus: http.StatusOK, - }), - Entry("remove service", base.HttpTestCase{ - Object: base.ManagerApiExpect(), - Method: http.MethodDelete, - Path: "/apisix/admin/services/200", - Headers: map[string]string{"Authorization": base.GetToken()}, - ExpectStatus: http.StatusOK, - }), - Entry("make sure the route deleted", base.HttpTestCase{ - Object: base.APISIXExpect(), - Method: http.MethodGet, - Path: "/server_port", - ExpectStatus: http.StatusNotFound, - ExpectBody: `{"error_msg":"404 Route Not Found"}`, - }), - ) -}) diff --git a/api/test/e2e/route/route_suite_test.go b/api/test/e2e/route/route_suite_test.go deleted file mode 100644 index e51f6eaa45..0000000000 --- a/api/test/e2e/route/route_suite_test.go +++ /dev/null @@ -1,38 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one or more - * contributor license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright ownership. - * The ASF licenses this file to You under the Apache License, Version 2.0 - * (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package route_test - -import ( - "testing" - "time" - - . "github.com/onsi/ginkgo/v2" - . "github.com/onsi/gomega" - - "github.com/apisix/manager-api/test/e2e/base" -) - -func TestRoute(t *testing.T) { - RegisterFailHandler(Fail) - RunSpecs(t, "Route Suite") -} - -var _ = BeforeSuite(func() { - base.CleanAllResource() - base.RestartManagerAPI() - time.Sleep(5 * time.Second) -}) diff --git a/api/test/e2e/route/route_test.go b/api/test/e2e/route/route_test.go deleted file mode 100644 index 099c27925e..0000000000 --- a/api/test/e2e/route/route_test.go +++ /dev/null @@ -1,336 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one or more - * contributor license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright ownership. - * The ASF licenses this file to You under the Apache License, Version 2.0 - * (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package route_test - -import ( - "net/http" - - . "github.com/onsi/ginkgo/v2" - - "github.com/apisix/manager-api/test/e2e/base" -) - -var _ = Describe("Route", func() { - DescribeTable("test route create and update", - func(tc base.HttpTestCase) { - base.RunTestCase(tc) - }, - Entry("create route1 success", base.HttpTestCase{ - Object: base.ManagerApiExpect(), - Method: http.MethodPut, - Path: "/apisix/admin/routes/r1", - Body: `{ - "name": "route1", - "uri": "/hello_", - "upstream": { - "nodes": { - "` + base.UpstreamIp + `:1980": 1 - }, - "type": "roundrobin" - } - }`, - Headers: map[string]string{"Authorization": base.GetToken()}, - ExpectStatus: http.StatusOK, - }), - Entry("create route2 success", base.HttpTestCase{ - Object: base.ManagerApiExpect(), - Method: http.MethodPut, - Path: "/apisix/admin/routes/r2", - Body: `{ - "name": "route2", - "uri": "/hello_", - "upstream": { - "nodes": { - "` + base.UpstreamIp + `:1980": 1 - }, - "type": "roundrobin" - } - }`, - Headers: map[string]string{"Authorization": base.GetToken()}, - ExpectStatus: http.StatusOK, - }), - Entry("create long name route3 success", base.HttpTestCase{ - Object: base.ManagerApiExpect(), - Method: http.MethodPut, - Path: "/apisix/admin/routes/r3", - Body: `{ - "name": "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ", - "uri": "/hello_", - "upstream": { - "nodes": { - "` + base.UpstreamIp + `:1980": 1 - }, - "type": "roundrobin" - } - }`, - Headers: map[string]string{"Authorization": base.GetToken()}, - ExpectStatus: http.StatusOK, - }), - Entry("create route failed, name existed", base.HttpTestCase{ - Object: base.ManagerApiExpect(), - Method: http.MethodPost, - Path: "/apisix/admin/routes", - Body: `{ - "name": "route2", - "uri": "/hello_", - "upstream": { - "nodes": { - "` + base.UpstreamIp + `:1980": 1 - }, - "type": "roundrobin" - } - }`, - Headers: map[string]string{"Authorization": base.GetToken()}, - ExpectStatus: http.StatusBadRequest, - ExpectBody: `route name exists`, - Sleep: base.SleepTime, - }), - Entry("update route2 failed, name existed", base.HttpTestCase{ - Object: base.ManagerApiExpect(), - Method: http.MethodPut, - Path: "/apisix/admin/routes/r2", - Body: `{ - "name": "route1", - "uri": "/hello_", - "upstream": { - "nodes": { - "` + base.UpstreamIp + `:1980": 1 - }, - "type": "roundrobin" - } - }`, - Headers: map[string]string{"Authorization": base.GetToken()}, - ExpectStatus: http.StatusBadRequest, - ExpectBody: `route name exists`, - }), - Entry("update route2 success, name not change", base.HttpTestCase{ - Object: base.ManagerApiExpect(), - Method: http.MethodPut, - Path: "/apisix/admin/routes/r2", - Body: `{ - "name": "route2", - "uri": "/hello", - "upstream": { - "nodes": { - "` + base.UpstreamIp + `:1980": 1 - }, - "type": "roundrobin" - } - }`, - Headers: map[string]string{"Authorization": base.GetToken()}, - ExpectStatus: http.StatusOK, - }), - Entry("hit route1", base.HttpTestCase{ - Object: base.APISIXExpect(), - Method: http.MethodGet, - Path: "/hello_", - ExpectStatus: http.StatusOK, - ExpectBody: "hello world", - Sleep: base.SleepTime, - }), - Entry("hit route2", base.HttpTestCase{ - Object: base.APISIXExpect(), - Method: http.MethodGet, - Path: "/hello", - ExpectStatus: http.StatusOK, - ExpectBody: "hello world", - }), - Entry("delete route1", base.HttpTestCase{ - Object: base.ManagerApiExpect(), - Method: http.MethodDelete, - Path: "/apisix/admin/routes/r1", - Headers: map[string]string{"Authorization": base.GetToken()}, - ExpectStatus: http.StatusOK, - }), - Entry("delete route2", base.HttpTestCase{ - Object: base.ManagerApiExpect(), - Method: http.MethodDelete, - Path: "/apisix/admin/routes/r2", - Headers: map[string]string{"Authorization": base.GetToken()}, - ExpectStatus: http.StatusOK, - }), - Entry("delete route3", base.HttpTestCase{ - Object: base.ManagerApiExpect(), - Method: http.MethodDelete, - Path: "/apisix/admin/routes/r3", - Headers: map[string]string{"Authorization": base.GetToken()}, - ExpectStatus: http.StatusOK, - }), - Entry("hit route1 that just deleted", base.HttpTestCase{ - Object: base.APISIXExpect(), - Method: http.MethodGet, - Path: "/hello_", - ExpectStatus: http.StatusNotFound, - ExpectBody: "{\"error_msg\":\"404 Route Not Found\"}", - Sleep: base.SleepTime, - }), - Entry("hit route2 that just deleted", base.HttpTestCase{ - Object: base.APISIXExpect(), - Method: http.MethodGet, - Path: "/hello", - ExpectStatus: http.StatusNotFound, - ExpectBody: "{\"error_msg\":\"404 Route Not Found\"}", - Sleep: base.SleepTime, - }), - ) - - DescribeTable("test route patch", - func(tc base.HttpTestCase) { - base.RunTestCase(tc) - }, - Entry("make sure the route not exists", base.HttpTestCase{ - Object: base.APISIXExpect(), - Method: http.MethodGet, - Path: "/hello", - ExpectStatus: http.StatusNotFound, - ExpectBody: "{\"error_msg\":\"404 Route Not Found\"}\n", - }), - Entry("create route", base.HttpTestCase{ - Object: base.ManagerApiExpect(), - Method: http.MethodPut, - Path: "/apisix/admin/routes/r1", - Body: `{ - "name": "route1", - "uri": "/hello", - "upstream": { - "nodes": { - "` + base.UpstreamIp + `:1980": 1 - }, - "type": "roundrobin" - } - }`, - Headers: map[string]string{"Authorization": base.GetToken()}, - ExpectStatus: http.StatusOK, - }), - Entry("hit the route just created", base.HttpTestCase{ - Object: base.APISIXExpect(), - Method: http.MethodGet, - Path: "/hello", - ExpectStatus: http.StatusOK, - ExpectBody: "hello world", - Sleep: base.SleepTime, - }), - Entry("route patch for update status(route offline)", base.HttpTestCase{ - Object: base.ManagerApiExpect(), - Method: http.MethodPatch, - Path: "/apisix/admin/routes/r1", - Body: `{"status":0}`, - Headers: map[string]string{"Authorization": base.GetToken()}, - ExpectStatus: http.StatusOK, - }), - Entry("make sure the route has been offline", base.HttpTestCase{ - Object: base.APISIXExpect(), - Method: http.MethodGet, - Path: "/hello", - ExpectStatus: http.StatusNotFound, - ExpectBody: "{\"error_msg\":\"404 Route Not Found\"}\n", - Sleep: base.SleepTime, - }), - Entry("route patch for update status (route online)", base.HttpTestCase{ - Object: base.ManagerApiExpect(), - Method: http.MethodPatch, - Path: "/apisix/admin/routes/r1/status", - Body: "1", - Headers: map[string]string{ - "Authorization": base.GetToken(), - "Content-Type": "text/plain", - }, - ExpectStatus: http.StatusOK, - }), - Entry("make sure the route has been online", base.HttpTestCase{ - Object: base.APISIXExpect(), - Method: http.MethodGet, - Path: "/hello", - ExpectStatus: http.StatusOK, - ExpectBody: "hello world", - Sleep: base.SleepTime, - }), - Entry("delete route", base.HttpTestCase{ - Object: base.ManagerApiExpect(), - Method: http.MethodDelete, - Path: "/apisix/admin/routes/r1", - Headers: map[string]string{"Authorization": base.GetToken()}, - ExpectStatus: http.StatusOK, - }), - Entry("hit the route just deleted", base.HttpTestCase{ - Object: base.APISIXExpect(), - Method: http.MethodGet, - Path: "/hello", - ExpectStatus: http.StatusNotFound, - ExpectBody: "{\"error_msg\":\"404 Route Not Found\"}\n", - Sleep: base.SleepTime, - }), - ) - - DescribeTable("test route create via POST", - func(tc base.HttpTestCase) { - base.RunTestCase(tc) - }, - Entry("hit route that not exist", base.HttpTestCase{ - Object: base.APISIXExpect(), - Method: http.MethodGet, - Path: "/hello_", - Headers: map[string]string{"Host": "foo.com"}, - ExpectStatus: http.StatusNotFound, - ExpectBody: "{\"error_msg\":\"404 Route Not Found\"}\n", - }), - Entry("create route via HTTP POST", base.HttpTestCase{ - Object: base.ManagerApiExpect(), - Method: http.MethodPost, - Path: "/apisix/admin/routes", - Body: `{ - "id": "r1", - "name": "route1", - "uri": "/hello_", - "hosts": ["foo.com", "*.bar.com"], - "upstream": { - "nodes": { - "` + base.UpstreamIp + `:1980": 1 - }, - "type": "roundrobin" - } - }`, - Headers: map[string]string{"Authorization": base.GetToken()}, - ExpectStatus: http.StatusOK, - ExpectBody: "\"id\":\"r1\"", - }), - Entry("hit the route just created", base.HttpTestCase{ - Object: base.APISIXExpect(), - Method: http.MethodGet, - Path: "/hello_", - Headers: map[string]string{"Host": "foo.com"}, - ExpectStatus: http.StatusOK, - ExpectBody: "hello world\n", - }), - Entry("delete the route just created", base.HttpTestCase{ - Object: base.ManagerApiExpect(), - Method: http.MethodDelete, - Path: "/apisix/admin/routes/r1", - Headers: map[string]string{"Authorization": base.GetToken()}, - ExpectStatus: http.StatusOK, - }), - Entry("hit the route just deleted", base.HttpTestCase{ - Object: base.APISIXExpect(), - Method: http.MethodGet, - Path: "/hello_", - Headers: map[string]string{"Host": "bar.com"}, - ExpectStatus: http.StatusNotFound, - ExpectBody: "{\"error_msg\":\"404 Route Not Found\"}\n", - Sleep: base.SleepTime, - }), - ) -}) diff --git a/api/test/e2e/route/route_with_management_fields_test.go b/api/test/e2e/route/route_with_management_fields_test.go deleted file mode 100644 index 1c927dc189..0000000000 --- a/api/test/e2e/route/route_with_management_fields_test.go +++ /dev/null @@ -1,475 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one or more - * contributor license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright ownership. - * The ASF licenses this file to You under the Apache License, Version 2.0 - * (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package route_test - -import ( - "io/ioutil" - "net/http" - "time" - - . "github.com/onsi/ginkgo/v2" - . "github.com/onsi/gomega" - "github.com/tidwall/gjson" - - "github.com/apisix/manager-api/test/e2e/base" -) - -var _ = Describe("route with management fields", func() { - var ( - createtime, updatetime gjson.Result - ) - - DescribeTable("test for route with name description", - func(tc base.HttpTestCase) { - base.RunTestCase(tc) - }, - Entry("config route with name and desc (r1)", base.HttpTestCase{ - Object: base.ManagerApiExpect(), - Path: "/apisix/admin/routes/r1", - Method: http.MethodPut, - Body: `{ - "name": "route1", - "uri": "/hello", - "name": "jack", - "desc": "config route with name and desc", - "upstream": { - "type": "roundrobin", - "nodes": { - "` + base.UpstreamIp + `:1980": 1 - } - } - }`, - Headers: map[string]string{"Authorization": base.GetToken()}, - ExpectBody: []string{`"code":0`, `"id":"r1"`, `"uri":"/hello"`, `"name":"jack"`, - `"upstream":{"nodes":{"` + base.UpstreamIp + `:1980":1},"type":"roundrobin"}`}, - ExpectStatus: http.StatusOK, - }), - Entry("check route exists by name", base.HttpTestCase{ - Object: base.ManagerApiExpect(), - Method: http.MethodGet, - Path: "/apisix/admin/notexist/routes", - Query: "name=jack", - Headers: map[string]string{"Authorization": base.GetToken()}, - ExpectStatus: http.StatusBadRequest, - ExpectBody: "Route name is reduplicate", - Sleep: base.SleepTime, - }), - Entry("check route exists by name (exclude it self)", base.HttpTestCase{ - Object: base.ManagerApiExpect(), - Method: http.MethodGet, - Path: "/apisix/admin/notexist/routes", - Query: "name=jack&exclude=r1", - Headers: map[string]string{"Authorization": base.GetToken()}, - ExpectStatus: http.StatusOK, - }), - Entry("access the route's uri (r1)", base.HttpTestCase{ - Object: base.APISIXExpect(), - Method: http.MethodGet, - Path: "/hello", - Headers: map[string]string{"Authorization": base.GetToken()}, - ExpectStatus: http.StatusOK, - ExpectBody: "hello world", - }), - Entry("verify the route's content (r1)", base.HttpTestCase{ - Object: base.ManagerApiExpect(), - Path: "/apisix/admin/routes/r1", - Method: http.MethodGet, - Headers: map[string]string{"Authorization": base.GetToken()}, - ExpectStatus: http.StatusOK, - ExpectBody: "\"name\":\"jack\",\"desc\":\"config route with name and desc\"", - }), - ) - - It("get the route information", func() { - time.Sleep(time.Duration(100) * time.Millisecond) - basepath := base.ManagerAPIHost + "/apisix/admin/routes" - request, _ := http.NewRequest("GET", basepath+"/r1", nil) - request.Header.Add("Authorization", base.GetToken()) - resp, err := http.DefaultClient.Do(request) - Expect(err).Should(BeNil()) - defer resp.Body.Close() - respBody, _ := ioutil.ReadAll(resp.Body) - createtime = gjson.Get(string(respBody), "data.create_time") - updatetime = gjson.Get(string(respBody), "data.update_time") - Expect(createtime.Int()).To(SatisfyAll( - BeNumerically(">=", time.Now().Unix()-1), - BeNumerically("<=", time.Now().Unix()+1), - )) - - Expect(updatetime.Int()).To(SatisfyAll( - BeNumerically(">=", time.Now().Unix()-1), - BeNumerically("<=", time.Now().Unix()+1), - )) - }) - - DescribeTable("test for route with name description", - func(tc base.HttpTestCase) { - base.RunTestCase(tc) - }, - Entry("update the route (r1)", base.HttpTestCase{ - Object: base.ManagerApiExpect(), - Path: "/apisix/admin/routes/r1", - Method: http.MethodPut, - Body: `{ - "name": "route1", - "uri": "/hello", - "name": "new jack", - "desc": "new desc", - "upstream": { - "type": "roundrobin", - "nodes": { - "` + base.UpstreamIp + `:1980": 1 - } - } - }`, - Headers: map[string]string{"Authorization": base.GetToken()}, - ExpectStatus: http.StatusOK, - ExpectBody: []string{`"code":0`, `"id":"r1"`, `"uri":"/hello"`, `"name":"new jack"`, - `"upstream":{"nodes":{"` + base.UpstreamIp + `:1980":1},"type":"roundrobin"}`}, - Sleep: time.Duration(2) * time.Second, - }), - Entry("access the route's uri (r1)", base.HttpTestCase{ - Object: base.APISIXExpect(), - Method: http.MethodGet, - Path: "/hello", - Headers: map[string]string{"Authorization": base.GetToken()}, - ExpectStatus: http.StatusOK, - ExpectBody: "hello world", - Sleep: base.SleepTime, - }), - ) - - It("get the updated route information", func() { - time.Sleep(time.Duration(100) * time.Millisecond) - basepath := base.ManagerAPIHost + "/apisix/admin/routes" - - request, _ := http.NewRequest("GET", basepath+"/r1", nil) - request.Header.Add("Authorization", base.GetToken()) - resp, _ := http.DefaultClient.Do(request) - respBody, _ := ioutil.ReadAll(resp.Body) - createtime2 := gjson.Get(string(respBody), "data.create_time") - updatetime2 := gjson.Get(string(respBody), "data.update_time") - //verify the route and compare result - Expect(gjson.Get(string(respBody), "data.name").String()).To(Equal("new jack")) - Expect(gjson.Get(string(respBody), "data.desc").String()).To(Equal("new desc")) - Expect(createtime2.String()).To(Equal(createtime.String())) - Expect(updatetime2.String()).NotTo(Equal(updatetime.String())) - }) - - It("delete the route (r1)", func() { - base.RunTestCase(base.HttpTestCase{ - Object: base.ManagerApiExpect(), - Method: http.MethodDelete, - Path: "/apisix/admin/routes/r1", - Headers: map[string]string{"Authorization": base.GetToken()}, - ExpectStatus: http.StatusOK, - }) - }) - - It("verify delete route (r1) success", func() { - base.RunTestCase(base.HttpTestCase{ - Object: base.APISIXExpect(), - Method: http.MethodGet, - Path: "/hello", - Headers: map[string]string{"Authorization": base.GetToken()}, - ExpectStatus: http.StatusNotFound, - ExpectBody: `{"error_msg":"404 Route Not Found"}`, - Sleep: base.SleepTime, - }) - }) -}) - -var _ = Describe("route with management fields", func() { - DescribeTable("test route with label", - func(tc base.HttpTestCase) { - base.RunTestCase(tc) - }, - Entry("config route with labels (r1)", base.HttpTestCase{ - Object: base.ManagerApiExpect(), - Path: "/apisix/admin/routes/r1", - Method: http.MethodPut, - Body: `{ - "name": "route1", - "uri": "/hello", - "labels": { - "build":"16", - "env":"production", - "version":"v2" - }, - "upstream": { - "type": "roundrobin", - "nodes": { - "` + base.UpstreamIp + `:1980": 1 - } - } - }`, - Headers: map[string]string{"Authorization": base.GetToken()}, - ExpectBody: []string{`"code":0`, `"id":"r1"`, `"uri":"/hello"`, `"name":"route1"`, - `"upstream":{"nodes":{"` + base.UpstreamIp + `:1980":1},"type":"roundrobin"}`, - `"labels":{"build":"16","env":"production","version":"v2"}`}, - ExpectStatus: http.StatusOK, - }), - Entry("access the route's uri (r1)", base.HttpTestCase{ - Object: base.APISIXExpect(), - Method: http.MethodGet, - Path: "/hello", - Headers: map[string]string{"Authorization": base.GetToken()}, - ExpectStatus: http.StatusOK, - ExpectBody: "hello world", - Sleep: base.SleepTime, - }), - Entry("verify the route's detail (r1)", base.HttpTestCase{ - Object: base.ManagerApiExpect(), - Path: "/apisix/admin/routes/r1", - Method: http.MethodGet, - Headers: map[string]string{"Authorization": base.GetToken()}, - ExpectStatus: http.StatusOK, - ExpectBody: `"labels":{"build":"16","env":"production","version":"v2"`, - Sleep: base.SleepTime, - }), - Entry("delete the route (r1)", base.HttpTestCase{ - Object: base.ManagerApiExpect(), - Method: http.MethodDelete, - Path: "/apisix/admin/routes/r1", - Headers: map[string]string{"Authorization": base.GetToken()}, - ExpectStatus: http.StatusOK, - }), - Entry("access the route after delete it", base.HttpTestCase{ - Object: base.APISIXExpect(), - Method: http.MethodGet, - Path: "/hello", - Headers: map[string]string{"Authorization": base.GetToken()}, - ExpectStatus: http.StatusNotFound, - ExpectBody: `{"error_msg":"404 Route Not Found"}`, - Sleep: base.SleepTime, - }), - ) -}) - -var _ = Describe("route with management fields", func() { - DescribeTable("test route search with label", - func(tc base.HttpTestCase) { - base.RunTestCase(tc) - }, - Entry("config route with labels (r1)", base.HttpTestCase{ - Object: base.ManagerApiExpect(), - Path: "/apisix/admin/routes/r1", - Method: http.MethodPut, - Body: `{ - "name": "route1", - "uri": "/hello", - "labels": { - "build":"16", - "env":"production", - "version":"v2" - }, - "upstream": { - "type": "roundrobin", - "nodes": { - "` + base.UpstreamIp + `:1980": 1 - } - } - }`, - Headers: map[string]string{"Authorization": base.GetToken()}, - ExpectBody: []string{`"code":0`, `"id":"r1"`, `"uri":"/hello"`, `"name":"route1"`, - `"upstream":{"nodes":{"` + base.UpstreamIp + `:1980":1},"type":"roundrobin"}`, - `"labels":{"build":"16","env":"production","version":"v2"}`}, - ExpectStatus: http.StatusOK, - }), - Entry("config route with labels (r2)", base.HttpTestCase{ - Object: base.ManagerApiExpect(), - Path: "/apisix/admin/routes/r2", - Method: http.MethodPut, - Body: `{ - "name": "route2", - "uri": "/hello2", - "labels": { - "build":"17", - "env":"dev", - "version":"v2", - "extra": "test" - }, - "upstream": { - "type": "roundrobin", - "nodes": { - "` + base.UpstreamIp + `:1980": 1 - } - } - }`, - Headers: map[string]string{"Authorization": base.GetToken()}, - ExpectBody: []string{`"code":0`, `"id":"r2"`, `"uri":"/hello2"`, `"name":"route2"`, - `"upstream":{"nodes":{"` + base.UpstreamIp + `:1980":1},"type":"roundrobin"}`, - `"labels":{"build":"17","env":"dev","extra":"test","version":"v2"}`, - }, - ExpectStatus: http.StatusOK, - }), - Entry("access the route's uri (r1)", base.HttpTestCase{ - Object: base.APISIXExpect(), - Method: http.MethodGet, - Path: "/hello", - Headers: map[string]string{"Authorization": base.GetToken()}, - ExpectStatus: http.StatusOK, - ExpectBody: "hello world", - Sleep: base.SleepTime, - }), - Entry("verify the route's detail (r1)", base.HttpTestCase{ - Object: base.ManagerApiExpect(), - Path: "/apisix/admin/routes/r1", - Method: http.MethodGet, - Headers: map[string]string{"Authorization": base.GetToken()}, - ExpectStatus: http.StatusOK, - ExpectBody: `"labels":{"build":"16","env":"production","version":"v2"`, - Sleep: base.SleepTime, - }), - Entry("search the route by label", base.HttpTestCase{ - Object: base.ManagerApiExpect(), - Path: "/apisix/admin/routes", - Query: "label=build:16", - Method: http.MethodGet, - Headers: map[string]string{"Authorization": base.GetToken()}, - ExpectStatus: http.StatusOK, - ExpectBody: `"labels":{"build":"16","env":"production","version":"v2"`, - Sleep: base.SleepTime, - }), - Entry("search the route by label (only key)", base.HttpTestCase{ - Object: base.ManagerApiExpect(), - Path: "/apisix/admin/routes", - Query: "label=extra", - Method: http.MethodGet, - Headers: map[string]string{"Authorization": base.GetToken()}, - ExpectStatus: http.StatusOK, - ExpectBody: `"labels":{"build":"17","env":"dev","extra":"test","version":"v2"`, - Sleep: base.SleepTime, - }), - Entry("search the route by label (combination)", base.HttpTestCase{ - Object: base.ManagerApiExpect(), - Path: "/apisix/admin/routes", - Query: "label=extra,build:16", - Method: http.MethodGet, - Headers: map[string]string{"Authorization": base.GetToken()}, - ExpectStatus: http.StatusOK, - ExpectBody: `"total_size":2`, - Sleep: base.SleepTime, - }), - Entry("search the route by label (combination)", base.HttpTestCase{ - Object: base.ManagerApiExpect(), - Path: "/apisix/admin/routes", - Query: "label=build:16,build:17", - Method: http.MethodGet, - Headers: map[string]string{"Authorization": base.GetToken()}, - ExpectStatus: http.StatusOK, - ExpectBody: `"total_size":2`, - Sleep: base.SleepTime, - }), - Entry("delete the route (r1)", base.HttpTestCase{ - Object: base.ManagerApiExpect(), - Method: http.MethodDelete, - Path: "/apisix/admin/routes/r1", - Headers: map[string]string{"Authorization": base.GetToken()}, - ExpectStatus: http.StatusOK, - }), - Entry("delete the route (r2)", base.HttpTestCase{ - Object: base.ManagerApiExpect(), - Method: http.MethodDelete, - Path: "/apisix/admin/routes/r2", - Headers: map[string]string{"Authorization": base.GetToken()}, - ExpectStatus: http.StatusOK, - }), - Entry("access the route after delete it", base.HttpTestCase{ - Object: base.APISIXExpect(), - Method: http.MethodGet, - Path: "/hello", - Headers: map[string]string{"Authorization": base.GetToken()}, - ExpectStatus: http.StatusNotFound, - ExpectBody: `{"error_msg":"404 Route Not Found"}`, - Sleep: base.SleepTime, - }), - ) -}) - -var _ = Describe("route with management fields", func() { - DescribeTable("test route search with create time", - func(tc base.HttpTestCase) { - base.RunTestCase(tc) - }, - Entry("create route with create_time", base.HttpTestCase{ - Object: base.ManagerApiExpect(), - Path: "/apisix/admin/routes/r1", - Method: http.MethodPut, - Body: `{ - "name": "route1", - "uri": "/hello", - "create_time": 1608792721, - "upstream": { - "type": "roundrobin", - "nodes": { - "` + base.UpstreamIp + `:1980": 1 - } - } - }`, - Headers: map[string]string{"Authorization": base.GetToken()}, - ExpectBody: `"message":"we don't accept create_time from client"`, - ExpectStatus: http.StatusBadRequest, - }), - Entry("create route with update_time", base.HttpTestCase{ - Object: base.ManagerApiExpect(), - Path: "/apisix/admin/routes/r1", - Method: http.MethodPut, - Body: `{ - "name": "route1", - "uri": "/hello", - "update_time": 1608792721, - "upstream": { - "type": "roundrobin", - "nodes": { - "` + base.UpstreamIp + `:1980": 1 - } - } - }`, - Headers: map[string]string{"Authorization": base.GetToken()}, - ExpectBody: `"message":"we don't accept update_time from client"`, - ExpectStatus: http.StatusBadRequest, - }), - Entry("create route with create_time and update_time", base.HttpTestCase{ - Object: base.ManagerApiExpect(), - Path: "/apisix/admin/routes/r1", - Method: http.MethodPut, - Body: `{ - "name": "route1", - "uri": "/hello", - "create_time": 1608792721, - "update_time": 1608792721, - "upstream": { - "type": "roundrobin", - "nodes": { - "` + base.UpstreamIp + `:1980": 1 - } - } - }`, - Headers: map[string]string{"Authorization": base.GetToken()}, - ExpectBody: `"message":"we don't accept create_time from client"`, - ExpectStatus: http.StatusBadRequest, - }), - Entry("make sure the route not created", base.HttpTestCase{ - Object: base.APISIXExpect(), - Method: http.MethodGet, - Path: "/hello", - ExpectStatus: http.StatusNotFound, - ExpectBody: `{"error_msg":"404 Route Not Found"}`, - }), - ) -}) diff --git a/api/test/e2e/route/route_with_methods_test.go b/api/test/e2e/route/route_with_methods_test.go deleted file mode 100644 index cb09ab9941..0000000000 --- a/api/test/e2e/route/route_with_methods_test.go +++ /dev/null @@ -1,350 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one or more - * contributor license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright ownership. - * The ASF licenses this file to You under the Apache License, Version 2.0 - * (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package route_test - -import ( - "net/http" - - . "github.com/onsi/ginkgo/v2" - - "github.com/apisix/manager-api/test/e2e/base" -) - -var _ = Describe("route with methods", func() { - DescribeTable("test route with methods", - func(tc base.HttpTestCase) { - base.RunTestCase(tc) - }, - Entry("add route with invalid method", base.HttpTestCase{ - Object: base.ManagerApiExpect(), - Method: http.MethodPut, - Path: "/apisix/admin/routes/r1", - Body: `{ - "name": "route1", - "uri": "/hello", - "methods": ["TEST"], - "upstream": { - "type": "roundrobin", - "nodes": { - "` + base.UpstreamIp + `:1980": 1 - } - } - }`, - Headers: map[string]string{"Authorization": base.GetToken()}, - ExpectStatus: http.StatusBadRequest, - }), - Entry("verify route", base.HttpTestCase{ - Object: base.APISIXExpect(), - Method: http.MethodGet, - Path: "/hello", - Headers: map[string]string{"Authorization": base.GetToken()}, - ExpectStatus: http.StatusNotFound, - Sleep: base.SleepTime, - }), - Entry("add route with valid method", base.HttpTestCase{ - Object: base.ManagerApiExpect(), - Method: http.MethodPut, - Path: "/apisix/admin/routes/r1", - Body: `{ - "name": "route1", - "uri": "/hello", - "methods": ["GET"], - "upstream": { - "type": "roundrobin", - "nodes": { - "` + base.UpstreamIp + `:1980": 1 - } - } - }`, - Headers: map[string]string{"Authorization": base.GetToken()}, - ExpectStatus: http.StatusOK, - }), - Entry("verify route", base.HttpTestCase{ - Object: base.APISIXExpect(), - Method: http.MethodGet, - Path: "/hello", - Headers: map[string]string{"Authorization": base.GetToken()}, - ExpectStatus: http.StatusOK, - ExpectBody: "hello world", - Sleep: base.SleepTime, - }), - Entry("update same route path", base.HttpTestCase{ - Object: base.ManagerApiExpect(), - Method: http.MethodPut, - Path: "/apisix/admin/routes/r1", - Body: `{ - "name": "route1", - "uri": "/hello_", - "methods": ["GET"], - "upstream": { - "type": "roundrobin", - "nodes": { - "` + base.UpstreamIp + `:1980": 1 - } - } - }`, - Headers: map[string]string{"Authorization": base.GetToken()}, - ExpectStatus: http.StatusOK, - }), - Entry("verify old route updated", base.HttpTestCase{ - Object: base.APISIXExpect(), - Method: http.MethodGet, - Path: "/hello", - Headers: map[string]string{"Authorization": base.GetToken()}, - ExpectStatus: http.StatusNotFound, - Sleep: base.SleepTime, - }), - Entry("verify new update applied", base.HttpTestCase{ - Object: base.APISIXExpect(), - Method: http.MethodGet, - Path: "/hello_", - Headers: map[string]string{"Authorization": base.GetToken()}, - ExpectStatus: http.StatusOK, - ExpectBody: "hello world", - Sleep: base.SleepTime, - }), - Entry("delete route", base.HttpTestCase{ - Object: base.ManagerApiExpect(), - Method: http.MethodDelete, - Path: "/apisix/admin/routes/r1", - Headers: map[string]string{"Authorization": base.GetToken()}, - ExpectStatus: http.StatusOK, - }), - Entry("add route with valid methods", base.HttpTestCase{ - Object: base.ManagerApiExpect(), - Method: http.MethodPut, - Path: "/apisix/admin/routes/r1", - Body: `{ - "name": "route1", - "uri": "/hello", - "methods": ["GET", "POST", "PUT", "DELETE", "PATCH"], - "upstream": { - "type": "roundrobin", - "nodes": { - "` + base.UpstreamIp + `:1980": 1 - } - } - }`, - Headers: map[string]string{"Authorization": base.GetToken()}, - ExpectStatus: http.StatusOK, - }), - Entry("verify route by post", base.HttpTestCase{ - Object: base.APISIXExpect(), - Method: http.MethodPost, - Path: "/hello", - Body: `test=test`, - Headers: map[string]string{"Authorization": base.GetToken()}, - ExpectStatus: http.StatusOK, - ExpectBody: "hello world", - Sleep: base.SleepTime, - }), - Entry("verify route by put", base.HttpTestCase{ - Object: base.APISIXExpect(), - Method: http.MethodPut, - Path: "/hello", - Body: `test=test`, - Headers: map[string]string{"Authorization": base.GetToken()}, - ExpectStatus: http.StatusOK, - ExpectBody: "hello world", - Sleep: base.SleepTime, - }), - Entry("verify route by get", base.HttpTestCase{ - Object: base.APISIXExpect(), - Method: http.MethodGet, - Path: "/hello", - Headers: map[string]string{"Authorization": base.GetToken()}, - ExpectStatus: http.StatusOK, - ExpectBody: "hello world", - Sleep: base.SleepTime, - }), - Entry("verify route by delete", base.HttpTestCase{ - Object: base.APISIXExpect(), - Method: http.MethodDelete, - Path: "/hello", - Headers: map[string]string{"Authorization": base.GetToken()}, - ExpectStatus: http.StatusOK, - ExpectBody: "hello world", - Sleep: base.SleepTime, - }), - Entry("verify route by patch", base.HttpTestCase{ - Object: base.APISIXExpect(), - Method: http.MethodPatch, - Path: "/hello", - Body: `test=test`, - Headers: map[string]string{"Authorization": base.GetToken()}, - ExpectStatus: http.StatusOK, - ExpectBody: "hello world", - Sleep: base.SleepTime, - }), - Entry("update route methods to GET only", base.HttpTestCase{ - Object: base.ManagerApiExpect(), - Method: http.MethodPut, - Path: "/apisix/admin/routes/r1", - Body: `{ - "name": "route1", - "uri": "/hello", - "methods": ["GET"], - "upstream": { - "type": "roundrobin", - "nodes": { - "` + base.UpstreamIp + `:1980": 1 - } - } - }`, - Headers: map[string]string{"Authorization": base.GetToken()}, - ExpectStatus: http.StatusOK, - }), - Entry("verify post method isn't working now", base.HttpTestCase{ - Object: base.APISIXExpect(), - Method: http.MethodPost, - Path: "/hello", - Headers: map[string]string{"Authorization": base.GetToken()}, - ExpectStatus: http.StatusNotFound, - Sleep: base.SleepTime, - }), - Entry("verify PUT method isn't working now", base.HttpTestCase{ - Object: base.APISIXExpect(), - Method: http.MethodPut, - Path: "/hello", - Headers: map[string]string{"Authorization": base.GetToken()}, - ExpectStatus: http.StatusNotFound, - Sleep: base.SleepTime, - }), - Entry("verify route by GET only", base.HttpTestCase{ - Object: base.APISIXExpect(), - Method: http.MethodGet, - Path: "/hello", - Headers: map[string]string{"Authorization": base.GetToken()}, - ExpectStatus: http.StatusOK, - ExpectBody: "hello world", - Sleep: base.SleepTime, - }), - Entry("delete route", base.HttpTestCase{ - Object: base.ManagerApiExpect(), - Method: http.MethodDelete, - Path: "/apisix/admin/routes/r1", - Headers: map[string]string{"Authorization": base.GetToken()}, - ExpectStatus: http.StatusOK, - }), - Entry("add route with lower case methods", base.HttpTestCase{ - Object: base.ManagerApiExpect(), - Method: http.MethodPut, - Path: "/apisix/admin/routes/r1", - Body: `{ - "name": "route1", - "uri": "/hello", - "methods": ["GET", "post"], - "upstream": { - "type": "roundrobin", - "nodes": { - "` + base.UpstreamIp + `:1980": 1 - } - } - }`, - Headers: map[string]string{"Authorization": base.GetToken()}, - ExpectStatus: http.StatusBadRequest, - }), - Entry("verify route", base.HttpTestCase{ - Object: base.APISIXExpect(), - Method: http.MethodGet, - Path: "/hello", - Headers: map[string]string{"Authorization": base.GetToken()}, - ExpectStatus: http.StatusNotFound, - Sleep: base.SleepTime, - }), - Entry("add route with methods GET", base.HttpTestCase{ - Object: base.ManagerApiExpect(), - Method: http.MethodPut, - Path: "/apisix/admin/routes/r1", - Body: `{ - "name": "route1", - "uri": "/hello", - "methods": ["GET"], - "upstream": { - "type": "roundrobin", - "nodes": { - "` + base.UpstreamIp + `:1980": 1 - } - } - }`, - Headers: map[string]string{"Authorization": base.GetToken()}, - ExpectStatus: http.StatusOK, - }), - Entry("verify route by get", base.HttpTestCase{ - Object: base.APISIXExpect(), - Method: http.MethodGet, - Path: "/hello", - Headers: map[string]string{"Authorization": base.GetToken()}, - ExpectStatus: http.StatusOK, - ExpectBody: "hello world", - Sleep: base.SleepTime, - }), - Entry("verify route by post", base.HttpTestCase{ - Object: base.APISIXExpect(), - Method: http.MethodPost, - Path: "/hello", - Body: `test=test`, - Headers: map[string]string{"Authorization": base.GetToken()}, - ExpectStatus: http.StatusNotFound, - Sleep: base.SleepTime, - }), - Entry("update route methods to POST", base.HttpTestCase{ - Object: base.ManagerApiExpect(), - Method: http.MethodPut, - Path: "/apisix/admin/routes/r1", - Body: `{ - "name": "route1", - "uri": "/hello", - "methods": ["POST"], - "upstream": { - "type": "roundrobin", - "nodes": { - "` + base.UpstreamIp + `:1980": 1 - } - } - }`, - Headers: map[string]string{"Authorization": base.GetToken()}, - ExpectStatus: http.StatusOK, - }), - Entry("verify route by get", base.HttpTestCase{ - Object: base.APISIXExpect(), - Method: http.MethodGet, - Path: "/hello", - Headers: map[string]string{"Authorization": base.GetToken()}, - ExpectStatus: http.StatusNotFound, - Sleep: base.SleepTime, - }), - Entry("verify route by post", base.HttpTestCase{ - Object: base.APISIXExpect(), - Method: http.MethodPost, - Path: "/hello", - Body: `test=test`, - Headers: map[string]string{"Authorization": base.GetToken()}, - ExpectStatus: http.StatusOK, - ExpectBody: "hello world", - Sleep: base.SleepTime, - }), - Entry("delete route", base.HttpTestCase{ - Object: base.ManagerApiExpect(), - Method: http.MethodDelete, - Path: "/apisix/admin/routes/r1", - Headers: map[string]string{"Authorization": base.GetToken()}, - ExpectStatus: http.StatusOK, - Sleep: base.SleepTime, - }), - ) -}) diff --git a/api/test/e2e/route/route_with_plugin_cors_test.go b/api/test/e2e/route/route_with_plugin_cors_test.go deleted file mode 100644 index eace6eea51..0000000000 --- a/api/test/e2e/route/route_with_plugin_cors_test.go +++ /dev/null @@ -1,222 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one or more - * contributor license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright ownership. - * The ASF licenses this file to You under the Apache License, Version 2.0 - * (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package route_test - -import ( - "net/http" - - . "github.com/onsi/ginkgo/v2" - - "github.com/apisix/manager-api/test/e2e/base" -) - -var _ = Describe("route with plugin cors", func() { - DescribeTable("test route with plugin cors", - func(tc base.HttpTestCase) { - base.RunTestCase(tc) - }, - Entry("make sure the route is not created", base.HttpTestCase{ - Object: base.APISIXExpect(), - Method: http.MethodGet, - Path: "/hello", - ExpectStatus: http.StatusNotFound, - ExpectBody: `{"error_msg":"404 Route Not Found"}`, - }), - Entry("create route with cors default setting", base.HttpTestCase{ - Object: base.ManagerApiExpect(), - Method: http.MethodPut, - Path: "/apisix/admin/routes/r1", - Body: `{ - "name": "route1", - "uri": "/hello", - "plugins": { - "cors": {} - }, - "upstream": { - "type": "roundrobin", - "nodes": [{ - "host": "` + base.UpstreamIp + `", - "port": 1981, - "weight": 1 - }] - } - }`, - Headers: map[string]string{"Authorization": base.GetToken()}, - ExpectStatus: http.StatusOK, - }), - Entry("verify route with cors default setting", base.HttpTestCase{ - Object: base.APISIXExpect(), - Method: http.MethodGet, - Path: "/hello", - ExpectStatus: http.StatusOK, - ExpectHeaders: map[string]string{ - "Access-Control-Allow-Origin": "*", - "Access-Control-Allow-Methods": "*", - }, - ExpectBody: "hello world", - Sleep: base.SleepTime, - }), - Entry("update route with specified setting", base.HttpTestCase{ - Object: base.ManagerApiExpect(), - Method: http.MethodPut, - Path: "/apisix/admin/routes/r1", - Body: `{ - "name": "route1", - "uri": "/hello", - "plugins": { - "cors": { - "allow_origins": "http://sub.domain.com,http://sub2.domain.com", - "allow_methods": "GET,POST", - "allow_headers": "headr1,headr2", - "expose_headers": "ex-headr1,ex-headr2", - "max_age": 50, - "allow_credential": true - } - }, - "upstream": { - "type": "roundrobin", - "nodes": [{ - "host": "` + base.UpstreamIp + `", - "port": 1981, - "weight": 1 - }] - } - }`, - Headers: map[string]string{"Authorization": base.GetToken()}, - ExpectStatus: http.StatusOK, - }), - Entry("verify route with cors specified setting", base.HttpTestCase{ - Object: base.APISIXExpect(), - Method: http.MethodGet, - Path: "/hello", - Headers: map[string]string{ - "Origin": "http://sub2.domain.com", - "resp-vary": "Via", - }, - ExpectStatus: http.StatusOK, - ExpectHeaders: map[string]string{ - "Access-Control-Allow-Origin": "http://sub2.domain.com", - "Access-Control-Allow-Methods": "GET,POST", - "Access-Control-Allow-Headers": "headr1,headr2", - "Access-Control-Expose-Headers": "ex-headr1,ex-headr2", - "Access-Control-Max-Age": "50", - }, - ExpectBody: "hello world", - Sleep: base.SleepTime, - }), - Entry("verify route with cors specified no match origin", base.HttpTestCase{ - Object: base.APISIXExpect(), - Method: http.MethodGet, - Path: "/hello", - Headers: map[string]string{ - "Origin": "http://sub3.domain.com", - }, - ExpectStatus: http.StatusOK, - ExpectHeaders: map[string]string{ - "Access-Control-Allow-Origin": "", - "Access-Control-Allow-Methods": "", - "Access-Control-Allow-Headers": "", - "Access-Control-Expose-Headers": "", - "Access-Control-Max-Age": "", - }, - ExpectBody: "hello world", - Sleep: base.SleepTime, - }), - Entry("verify route with options method", base.HttpTestCase{ - Object: base.APISIXExpect(), - Method: http.MethodOptions, - Headers: map[string]string{ - "Origin": "http://sub2.domain.com", - }, - Path: "/hello", - ExpectStatus: http.StatusOK, - ExpectHeaders: map[string]string{ - "Access-Control-Allow-Origin": "http://sub2.domain.com", - "Access-Control-Allow-Methods": "GET,POST", - "Access-Control-Allow-Headers": "headr1,headr2", - "Access-Control-Expose-Headers": "ex-headr1,ex-headr2", - "Access-Control-Max-Age": "50", - }, - ExpectBody: "", - }), - Entry("update route with cors setting force wildcard", base.HttpTestCase{ - Object: base.ManagerApiExpect(), - Method: http.MethodPut, - Path: "/apisix/admin/routes/r1", - Body: `{ - "name": "route1", - "uri": "/hello", - "plugins": { - "cors": { - "allow_origins": "**", - "allow_methods": "**", - "allow_headers": "**", - "expose_headers": "*" - } - }, - "upstream": { - "type": "roundrobin", - "nodes": [{ - "host": "` + base.UpstreamIp + `", - "port": 1981, - "weight": 1 - }] - } - }`, - Headers: map[string]string{"Authorization": base.GetToken()}, - ExpectStatus: http.StatusOK, - }), - Entry("verify route with cors setting force wildcard", base.HttpTestCase{ - Object: base.APISIXExpect(), - Method: http.MethodGet, - Path: "/hello", - Headers: map[string]string{ - "Origin": "https://sub.domain.com", - "ExternalHeader1": "val", - "ExternalHeader2": "val", - "ExternalHeader3": "val", - "Access-Control-Request-Headers": "req-header1,req-header2", - }, - ExpectStatus: http.StatusOK, - ExpectHeaders: map[string]string{ - "Access-Control-Allow-Origin": "https://sub.domain.com", - "Vary": "Origin", - "Access-Control-Allow-Methods": "GET,POST,PUT,DELETE,PATCH,HEAD,OPTIONS,CONNECT,TRACE", - "Access-Control-Allow-Headers": "req-header1,req-header2", - "Access-Control-Expose-Headers": "*", - "Access-Control-Allow-Credentials": "", - }, - ExpectBody: "hello world", - Sleep: base.SleepTime, - }), - Entry("delete route", base.HttpTestCase{ - Object: base.ManagerApiExpect(), - Method: http.MethodDelete, - Path: "/apisix/admin/routes/r1", - Headers: map[string]string{"Authorization": base.GetToken()}, - ExpectStatus: http.StatusOK, - }), - Entry("make sure the route deleted", base.HttpTestCase{ - Object: base.APISIXExpect(), - Method: http.MethodGet, - Path: "/hello", - ExpectStatus: http.StatusNotFound, - ExpectBody: `{"error_msg":"404 Route Not Found"}`, - Sleep: base.SleepTime, - }), - ) -}) diff --git a/api/test/e2e/route/route_with_plugin_http_logger_test.go b/api/test/e2e/route/route_with_plugin_http_logger_test.go deleted file mode 100644 index a8f52dfbc3..0000000000 --- a/api/test/e2e/route/route_with_plugin_http_logger_test.go +++ /dev/null @@ -1,238 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one or more - * contributor license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright ownership. - * The ASF licenses this file to You under the Apache License, Version 2.0 - * (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package route_test - -import ( - "net/http" - "time" - - . "github.com/onsi/ginkgo/v2" - . "github.com/onsi/gomega" - - "github.com/apisix/manager-api/test/e2e/base" -) - -var _ = Describe("route with plugin http logger", func() { - It("cleanup previous error logs", func() { - base.CleanAPISIXErrorLog() - }) - - DescribeTable("test route with http logger plugin", - func(tc base.HttpTestCase) { - base.RunTestCase(tc) - }, - Entry("make sure the route is not created ", base.HttpTestCase{ - Object: base.APISIXExpect(), - Method: http.MethodGet, - Path: "/hello_", - ExpectStatus: http.StatusNotFound, - ExpectBody: `{"error_msg":"404 Route Not Found"}`, - }), - Entry("create route", base.HttpTestCase{ - Object: base.ManagerApiExpect(), - Method: http.MethodPut, - Path: "/apisix/admin/routes/r1", - Body: `{ - "name": "route1", - "uri": "/hello_", - "plugins": { - "http-logger": { - "uri": "http://` + base.UpstreamIp + `:1982/hello", - "batch_max_size": 1, - "max_retry_count": 1, - "retry_delay": 2, - "buffer_duration": 2, - "inactive_timeout": 2, - "name": "http logger", - "timeout": 3, - "concat_method": "json" - } - }, - "upstream": { - "type": "roundrobin", - "nodes": { - "` + base.UpstreamIp + `:1981": 1 - } - } - }`, - Headers: map[string]string{"Authorization": base.GetToken()}, - ExpectBody: []string{`"code":0`, `"id":"r1"`, `"uri":"/hello_"`, `"name":"route1"`, `"name":"http logger"`}, - ExpectStatus: http.StatusOK, - }), - Entry("access route to trigger log", base.HttpTestCase{ - Object: base.APISIXExpect(), - Method: http.MethodGet, - Path: "/hello_", - ExpectStatus: http.StatusOK, - ExpectBody: "hello world", - Sleep: base.SleepTime, - }), - ) - - It("verify http logger by checking log", func() { - // sleep for process log - time.Sleep(1500 * time.Millisecond) - - // verify http logger by checking log - //todo: should use a fake upstream for confirming whether we got the log data. - logContent := base.ReadAPISIXErrorLog() - Expect(logContent).Should(ContainSubstring("Batch Processor[http logger] successfully processed the entries")) - - // clean log - base.CleanAPISIXErrorLog() - }) - - DescribeTable("test route for unreachable logger uri", - func(tc base.HttpTestCase) { - base.RunTestCase(tc) - }, - Entry("create route with wrong https endpoint", base.HttpTestCase{ - Object: base.ManagerApiExpect(), - Method: http.MethodPut, - Path: "/apisix/admin/routes/r2", - Body: `{ - "name": "route2", - "uri": "/hello", - "plugins": { - "http-logger": { - "uri": "https://127.0.0.1:8888/hello-world-http", - "batch_max_size": 1, - "max_retry_count": 1, - "retry_delay": 2, - "buffer_duration": 2, - "inactive_timeout": 2, - "name": "http logger", - "timeout": 3, - "concat_method": "json" - } - }, - "upstream": { - "type": "roundrobin", - "nodes": { - "` + base.UpstreamIp + `:1982": 1 - } - } - }`, - Headers: map[string]string{"Authorization": base.GetToken()}, - ExpectBody: []string{`"code":0`, `"id":"r2"`, `"uri":"/hello"`, `"name":"route2"`, `"name":"http logger"`}, - ExpectStatus: http.StatusOK, - }), - Entry("access route to trigger log", base.HttpTestCase{ - Object: base.APISIXExpect(), - Method: http.MethodGet, - Path: "/hello", - ExpectStatus: http.StatusOK, - ExpectBody: "hello world", - Sleep: base.SleepTime, - }), - ) - - It("verify http logger by checking log for second route", func() { - // sleep for process log - time.Sleep(1500 * time.Millisecond) - - // verify http logger by checking log - //todo: should use a fake upstream for confirming whether we got the log data. - logContent := base.ReadAPISIXErrorLog() - Expect(logContent).Should(ContainSubstring("Batch Processor[http logger] failed to process entries: failed to connect to host[127.0.0.1] port[8888] connection refused")) - - // clean log - base.CleanAPISIXErrorLog() - }) - - // todo: check disable http logger - Done - DescribeTable("rechecking logger after disabling plugin", - func(tc base.HttpTestCase) { - base.RunTestCase(tc) - }, - Entry("disable route http logger plugin", base.HttpTestCase{ - Object: base.ManagerApiExpect(), - Method: http.MethodPut, - Path: "/apisix/admin/routes/r2", - Body: `{ - "name": "route2", - "uri": "/hello", - "plugins": {}, - "upstream": { - "type": "roundrobin", - "nodes": { - "` + base.UpstreamIp + `:1982": 1 - } - } - }`, - Headers: map[string]string{"Authorization": base.GetToken()}, - ExpectBody: []string{`"code":0`, `"id":"r2"`, `"uri":"/hello"`, `"name":"route2"`}, - ExpectStatus: http.StatusOK, - }), - Entry("access route to trigger log (though should not be triggered)", base.HttpTestCase{ - Object: base.APISIXExpect(), - Method: http.MethodGet, - Path: "/hello", - ExpectStatus: http.StatusOK, - ExpectBody: "hello world", - Sleep: base.SleepTime, - }), - ) - - It("verify http logger has been successfully disabled", func() { - // sleep for process log - time.Sleep(1500 * time.Millisecond) - - // verify http logger by checking log - logContent := base.ReadAPISIXErrorLog() - Expect(logContent).ShouldNot(ContainSubstring("Batch Processor[http logger] successfully processed the entries")) - - // clean log - base.CleanAPISIXErrorLog() - }) - - DescribeTable("cleanup test data", - func(tc base.HttpTestCase) { - base.RunTestCase(tc) - }, - Entry("delete route", base.HttpTestCase{ - Object: base.ManagerApiExpect(), - Method: http.MethodDelete, - Path: "/apisix/admin/routes/r1", - Headers: map[string]string{"Authorization": base.GetToken()}, - ExpectStatus: http.StatusOK, - }), - Entry("make sure the route has been deleted", base.HttpTestCase{ - Object: base.APISIXExpect(), - Method: http.MethodGet, - Path: "/hello_", - ExpectStatus: http.StatusNotFound, - ExpectBody: `{"error_msg":"404 Route Not Found"}`, - Sleep: base.SleepTime, - }), - Entry("delete route 2", base.HttpTestCase{ - Object: base.ManagerApiExpect(), - Method: http.MethodDelete, - Path: "/apisix/admin/routes/r2", - Headers: map[string]string{"Authorization": base.GetToken()}, - ExpectStatus: http.StatusOK, - }), - Entry("make sure the route 2 has been deleted", base.HttpTestCase{ - Object: base.APISIXExpect(), - Method: http.MethodGet, - Path: "/hello", - ExpectStatus: http.StatusNotFound, - ExpectBody: `{"error_msg":"404 Route Not Found"}`, - Sleep: base.SleepTime, - }), - ) -}) diff --git a/api/test/e2e/route/route_with_plugin_jwt_test.go b/api/test/e2e/route/route_with_plugin_jwt_test.go deleted file mode 100644 index c1694a4fe0..0000000000 --- a/api/test/e2e/route/route_with_plugin_jwt_test.go +++ /dev/null @@ -1,330 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one or more - * contributor license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright ownership. - * The ASF licenses this file to You under the Apache License, Version 2.0 - * (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package route_test - -import ( - "io/ioutil" - "net/http" - "time" - - . "github.com/onsi/ginkgo/v2" - . "github.com/onsi/gomega" - - "github.com/apisix/manager-api/test/e2e/base" -) - -var _ = Describe("route with jwt plugin", func() { - var jwtToken string - - DescribeTable("create route and consumer with jwt plugin", - func(tc base.HttpTestCase) { - base.RunTestCase(tc) - }, - Entry("make sure the route is not created ", base.HttpTestCase{ - Object: base.APISIXExpect(), - Method: http.MethodGet, - Path: "/hello", - ExpectStatus: http.StatusNotFound, - ExpectBody: `{"error_msg":"404 Route Not Found"}`, - }), - Entry("create route", base.HttpTestCase{ - Object: base.ManagerApiExpect(), - Method: http.MethodPut, - Path: "/apisix/admin/routes/r1", - Body: `{ - "name": "route1", - "uri": "/hello", - "plugins": { - "jwt-auth": {} - }, - "upstream": { - "type": "roundrobin", - "nodes": { - "` + base.UpstreamIp + `:1981": 1 - } - } - }`, - Headers: map[string]string{"Authorization": base.GetToken()}, - ExpectStatus: http.StatusOK, - ExpectBody: []string{`"code":0`, `"id":"r1"`, `"uri":"/hello"`, `"name":"route1"`, `"jwt-auth":{}`}, - }), - Entry("make sure the consumer is not created", base.HttpTestCase{ - Object: base.ManagerApiExpect(), - Method: http.MethodGet, - Path: "/apisix/admin/consumers/jack", - Headers: map[string]string{"Authorization": base.GetToken()}, - ExpectStatus: http.StatusNotFound, - }), - Entry("create consumer", base.HttpTestCase{ - Object: base.ManagerApiExpect(), - Path: "/apisix/admin/consumers", - Method: http.MethodPut, - Body: `{ - "username": "jack", - "plugins": { - "jwt-auth": { - "key": "user-key", - "secret": "my-secret-key", - "algorithm": "HS256" - } - }, - "desc": "test description" - }`, - Headers: map[string]string{"Authorization": base.GetToken()}, - ExpectStatus: http.StatusOK, - ExpectBody: []string{`"code":0`, `"username":"jack"`, `"key":"user-key"`, `"secret":"my-secret-key"`}, - }), - ) - - It("create public api for JWT sign", func() { - base.RunTestCase(base.HttpTestCase{ - Object: base.ManagerApiExpect(), - Method: http.MethodPut, - Path: "/apisix/admin/routes/jwt-sign", - Body: `{ - "name": "jwt-auth", - "uri": "/apisix/plugin/jwt/sign", - "plugins": { - "public-api": {} - }, - "upstream": { - "type": "roundrobin", - "nodes": [{ - "host": "` + base.UpstreamIp + `", - "port": 1980, - "weight": 1 - }] - } - }`, - Headers: map[string]string{"Authorization": base.GetToken()}, - ExpectStatus: http.StatusOK, - ExpectBody: `"code":0`, - }) - }) - - It("sign jwt token", func() { - time.Sleep(base.SleepTime) - - // sign jwt token - body, status, err := base.HttpGet(base.APISIXHost+"/apisix/plugin/jwt/sign?key=user-key", nil) - Expect(err).To(BeNil()) - Expect(status).To(Equal(http.StatusOK)) - jwtToken = string(body) - // sign jwt token with not exists key - body, status, err = base.HttpGet(base.APISIXHost+"/apisix/plugin/jwt/sign?key=not-exist-key", nil) - Expect(err).To(BeNil()) - Expect(status).To(Equal(http.StatusNotFound)) - }) - - It("verify route with correct jwt token", func() { - base.RunTestCase(base.HttpTestCase{ - Object: base.APISIXExpect(), - Method: http.MethodGet, - Path: "/hello", - Headers: map[string]string{"Authorization": jwtToken}, - ExpectStatus: http.StatusOK, - ExpectBody: "hello world", - }) - }) - - DescribeTable("verify token and clean consumer", - func(tc base.HttpTestCase) { - base.RunTestCase(tc) - }, - Entry("verify route without jwt token", base.HttpTestCase{ - Object: base.APISIXExpect(), - Method: http.MethodGet, - Path: "/hello", - ExpectStatus: http.StatusUnauthorized, - ExpectBody: `{"message":"Missing JWT token in request"}`, - Sleep: base.SleepTime, - }), - Entry("verify route with incorrect jwt token", base.HttpTestCase{ - Object: base.APISIXExpect(), - Method: http.MethodGet, - Path: "/hello", - Headers: map[string]string{"Authorization": "invalid-token"}, - ExpectStatus: http.StatusUnauthorized, - ExpectBody: `{"message":"JWT token invalid"}`, - }), - Entry("delete consumer", base.HttpTestCase{ - Object: base.ManagerApiExpect(), - Method: http.MethodDelete, - Path: "/apisix/admin/consumers/jack", - Headers: map[string]string{"Authorization": base.GetToken()}, - ExpectStatus: http.StatusOK, - }), - ) - - It("verify route with the jwt token from just deleted consumer", func() { - base.RunTestCase(base.HttpTestCase{ - Object: base.APISIXExpect(), - Method: http.MethodGet, - Path: "/hello", - Headers: map[string]string{"Authorization": jwtToken}, - ExpectStatus: http.StatusUnauthorized, - ExpectBody: `{"message":"Missing related consumer"}`, - Sleep: base.SleepTime, - }) - }) - - DescribeTable("cleanup route and verify", - func(tc base.HttpTestCase) { - base.RunTestCase(tc) - }, - Entry("delete route", base.HttpTestCase{ - Object: base.ManagerApiExpect(), - Method: http.MethodDelete, - Path: "/apisix/admin/routes/r1", - Headers: map[string]string{"Authorization": base.GetToken()}, - ExpectStatus: http.StatusOK, - }), - Entry("verify the deleted route", base.HttpTestCase{ - Object: base.APISIXExpect(), - Method: http.MethodGet, - Path: "/hello", - ExpectStatus: http.StatusNotFound, - ExpectBody: `{"error_msg":"404 Route Not Found"}`, - Sleep: base.SleepTime, - }), - ) - - DescribeTable("create route and consumer with jwt (no algorithm)", - func(tc base.HttpTestCase) { - base.RunTestCase(tc) - }, - Entry("create consumer with jwt (no algorithm)", base.HttpTestCase{ - Object: base.ManagerApiExpect(), - Path: "/apisix/admin/consumers", - Method: http.MethodPut, - Body: `{ - "username":"consumer_1", - "desc": "test description", - "plugins":{ - "jwt-auth":{ - "exp":86400, - "key":"user-key", - "secret":"my-secret-key" - } - } - }`, - Headers: map[string]string{"Authorization": base.GetToken()}, - ExpectStatus: http.StatusOK, - ExpectBody: []string{`"code":0`, `"username":"consumer_1"`, - `"jwt-auth":{"exp":86400,"key":"user-key","secret":"my-secret-key"}`}, - }), - Entry("get the consumer", base.HttpTestCase{ - Object: base.ManagerApiExpect(), - Path: "/apisix/admin/consumers/consumer_1", - Method: http.MethodGet, - Headers: map[string]string{"Authorization": base.GetToken()}, - ExpectStatus: http.StatusOK, - ExpectBody: `"username":"consumer_1"`, - Sleep: base.SleepTime, - }), - Entry("create the route", base.HttpTestCase{ - Object: base.ManagerApiExpect(), - Method: http.MethodPut, - Path: "/apisix/admin/routes/r1", - Body: `{ - "name": "route1", - "uri": "/hello", - "plugins": { - "jwt-auth": {} - }, - "upstream": { - "type": "roundrobin", - "nodes": { - "` + base.UpstreamIp + `:1980": 1 - } - } - }`, - Headers: map[string]string{"Authorization": base.GetToken()}, - ExpectBody: []string{`"code":0`, `"id":"r1"`, `"uri":"/hello"`, `"name":"route1"`, `"jwt-auth":{}`}, - ExpectStatus: http.StatusOK, - }), - ) - - It("get the jwt token", func() { - time.Sleep(base.SleepTime) - - request, _ := http.NewRequest("GET", base.APISIXHost+"/apisix/plugin/jwt/sign?key=user-key", nil) - resp, err := http.DefaultClient.Do(request) - Expect(err).To(BeNil()) - defer resp.Body.Close() - Expect(resp.StatusCode).To(Equal(http.StatusOK)) - jwtTokenBytes, _ := ioutil.ReadAll(resp.Body) - jwtToken = string(jwtTokenBytes) - }) - - It("hit route with jwt token", func() { - base.RunTestCase(base.HttpTestCase{ - Object: base.APISIXExpect(), - Method: http.MethodGet, - Path: "/hello", - Headers: map[string]string{"Authorization": jwtToken}, - ExpectStatus: http.StatusOK, - ExpectBody: "hello world", - Sleep: base.SleepTime, - }) - }) - - DescribeTable("cleanup consumer and route", - func(tc base.HttpTestCase) { - base.RunTestCase(tc) - }, - Entry("delete consumer", base.HttpTestCase{ - Object: base.ManagerApiExpect(), - Path: "/apisix/admin/consumers/consumer_1", - Method: http.MethodDelete, - Headers: map[string]string{"Authorization": base.GetToken()}, - ExpectStatus: http.StatusOK, - ExpectBody: `"code":0`, - }), - Entry("after delete consumer verify it again", base.HttpTestCase{ - Object: base.ManagerApiExpect(), - Method: http.MethodGet, - Path: "/apisix/admin/consumers/jack", - Headers: map[string]string{"Authorization": base.GetToken()}, - ExpectStatus: http.StatusNotFound, - ExpectBody: `"message":"data not found"`, - Sleep: base.SleepTime, - }), - Entry("delete the route", base.HttpTestCase{ - Object: base.ManagerApiExpect(), - Method: http.MethodDelete, - Path: "/apisix/admin/routes/r1", - Headers: map[string]string{"Authorization": base.GetToken()}, - ExpectStatus: http.StatusOK, - }), - Entry("delete the route", base.HttpTestCase{ - Object: base.ManagerApiExpect(), - Method: http.MethodDelete, - Path: "/apisix/admin/routes/jwt-sign", - Headers: map[string]string{"Authorization": base.GetToken()}, - ExpectStatus: http.StatusOK, - }), - Entry("verify the deleted route", base.HttpTestCase{ - Object: base.APISIXExpect(), - Method: http.MethodGet, - Path: "/hello", - ExpectStatus: http.StatusNotFound, - ExpectBody: `{"error_msg":"404 Route Not Found"}`, - Sleep: base.SleepTime, - }), - ) -}) diff --git a/api/test/e2e/route/route_with_plugin_limit_count_test.go b/api/test/e2e/route/route_with_plugin_limit_count_test.go deleted file mode 100644 index e0bfcd678f..0000000000 --- a/api/test/e2e/route/route_with_plugin_limit_count_test.go +++ /dev/null @@ -1,418 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one or more - * contributor license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright ownership. - * The ASF licenses this file to You under the Apache License, Version 2.0 - * (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package route_test - -import ( - "net/http" - "time" - - . "github.com/onsi/ginkgo/v2" - - "github.com/apisix/manager-api/test/e2e/base" -) - -var _ = Describe("route with limit plugin", func() { - DescribeTable("test route with limit plugin", - func(tc base.HttpTestCase) { - base.RunTestCase(tc) - }, - Entry("make sure the route is not created", base.HttpTestCase{ - Object: base.APISIXExpect(), - Method: http.MethodGet, - Path: "/hello", - ExpectStatus: http.StatusNotFound, - ExpectBody: `{"error_msg":"404 Route Not Found"}`, - }), - Entry("create route with limit plugin", base.HttpTestCase{ - Object: base.ManagerApiExpect(), - Method: http.MethodPut, - Path: "/apisix/admin/routes/r1", - Body: `{ - "name": "route1", - "uri": "/hello", - "plugins": { - "limit-count": { - "count": 2, - "time_window": 2, - "rejected_code": 503, - "key": "remote_addr", - "policy": "local" - } - }, - "upstream": { - "type": "roundrobin", - "nodes": { - "` + base.UpstreamIp + `:1981": 1 - } - } - }`, - Headers: map[string]string{"Authorization": base.GetToken()}, - ExpectStatus: http.StatusOK, - ExpectBody: `"code":0`, - }), - Entry("verify route that should not be limited", base.HttpTestCase{ - Object: base.APISIXExpect(), - Method: http.MethodGet, - Path: "/hello", - ExpectStatus: http.StatusOK, - ExpectBody: "hello world", - Sleep: base.SleepTime, - }), - Entry("verify route that should not be limited 2", base.HttpTestCase{ - Object: base.APISIXExpect(), - Method: http.MethodGet, - Path: "/hello", - ExpectStatus: http.StatusOK, - ExpectBody: "hello world", - }), - Entry("verify route that should be limited", base.HttpTestCase{ - Object: base.APISIXExpect(), - Method: http.MethodGet, - Path: "/hello", - ExpectStatus: http.StatusServiceUnavailable, - ExpectBody: "503 Service Temporarily Unavailable", - }), - Entry("verify route that should not be limited since time window pass", base.HttpTestCase{ - Object: base.APISIXExpect(), - Method: http.MethodGet, - Path: "/hello", - ExpectStatus: http.StatusOK, - ExpectBody: "hello world", - Sleep: 3 * time.Second, - }), - Entry("delete route", base.HttpTestCase{ - Object: base.ManagerApiExpect(), - Method: http.MethodDelete, - Path: "/apisix/admin/routes/r1", - Headers: map[string]string{"Authorization": base.GetToken()}, - ExpectStatus: http.StatusOK, - }), - Entry("hit the route just deleted", base.HttpTestCase{ - Object: base.APISIXExpect(), - Method: http.MethodGet, - Path: "/hello", - ExpectStatus: http.StatusNotFound, - ExpectBody: `{"error_msg":"404 Route Not Found"}`, - Sleep: base.SleepTime, - }), - ) -}) - -var _ = Describe("route with limit plugin by consumer", func() { - DescribeTable("test route with limit plugin by consumer", - func(tc base.HttpTestCase) { - base.RunTestCase(tc) - }, - Entry("make sure the route is not created", base.HttpTestCase{ - Object: base.APISIXExpect(), - Method: http.MethodGet, - Path: "/hello", - ExpectStatus: http.StatusNotFound, - ExpectBody: `{"error_msg":"404 Route Not Found"}`, - }), - Entry("create route with limit plugin", base.HttpTestCase{ - Object: base.ManagerApiExpect(), - Method: http.MethodPut, - Path: "/apisix/admin/routes/r1", - Body: `{ - "name": "route1", - "uri": "/hello", - "plugins": { - "key-auth": {}, - "limit-count": { - "count": 2, - "time_window": 2, - "rejected_code": 503, - "key": "consumer_name", - "policy": "local" - } - }, - "upstream": { - "type": "roundrobin", - "nodes": { - "` + base.UpstreamIp + `:1981": 1 - } - } - }`, - Headers: map[string]string{"Authorization": base.GetToken()}, - ExpectStatus: http.StatusOK, - ExpectBody: `"code":0`, - }), - Entry("make sure the consumer is not created", base.HttpTestCase{ - Object: base.ManagerApiExpect(), - Method: http.MethodGet, - Path: "/apisix/admin/consumers/jack", - Headers: map[string]string{"Authorization": base.GetToken()}, - ExpectStatus: http.StatusNotFound, - }), - Entry("create consumer", base.HttpTestCase{ - Object: base.ManagerApiExpect(), - Path: "/apisix/admin/consumers", - Method: http.MethodPut, - Body: `{ - "username": "jack", - "plugins": { - "key-auth": { - "key": "auth-jack" - } - } - }`, - Headers: map[string]string{"Authorization": base.GetToken()}, - ExpectStatus: http.StatusOK, - }), - Entry("create consumer 2", base.HttpTestCase{ - Object: base.ManagerApiExpect(), - Path: "/apisix/admin/consumers", - Method: http.MethodPut, - Body: `{ - "username": "pony", - "plugins": { - "key-auth": { - "key": "auth-pony" - } - } - }`, - Headers: map[string]string{"Authorization": base.GetToken()}, - ExpectStatus: http.StatusOK, - }), - Entry("verify route that should not be limited", base.HttpTestCase{ - Object: base.APISIXExpect(), - Method: http.MethodGet, - Path: "/hello", - Headers: map[string]string{"apikey": "auth-jack"}, - ExpectStatus: http.StatusOK, - ExpectBody: "hello world", - Sleep: base.SleepTime, - }), - Entry("verify route that should not be limited 2", base.HttpTestCase{ - Object: base.APISIXExpect(), - Method: http.MethodGet, - Path: "/hello", - Headers: map[string]string{"apikey": "auth-jack"}, - ExpectStatus: http.StatusOK, - ExpectBody: "hello world", - }), - Entry("verify route that should be limited", base.HttpTestCase{ - Object: base.APISIXExpect(), - Method: http.MethodGet, - Path: "/hello", - Headers: map[string]string{"apikey": "auth-jack"}, - ExpectStatus: http.StatusServiceUnavailable, - ExpectBody: "503 Service Temporarily Unavailable", - }), - Entry("verify route that should not be limited (other consumer)", base.HttpTestCase{ - Object: base.APISIXExpect(), - Method: http.MethodGet, - Path: "/hello", - Headers: map[string]string{"apikey": "auth-pony"}, - ExpectStatus: http.StatusOK, - ExpectBody: "hello world", - }), - Entry("verify route that should not be limited since time window pass", base.HttpTestCase{ - Object: base.APISIXExpect(), - Method: http.MethodGet, - Path: "/hello", - Headers: map[string]string{"apikey": "auth-jack"}, - ExpectStatus: http.StatusOK, - ExpectBody: "hello world", - Sleep: 2 * time.Second, - }), - Entry("delete consumer pony", base.HttpTestCase{ - Object: base.ManagerApiExpect(), - Method: http.MethodDelete, - Path: "/apisix/admin/consumers/pony", - Headers: map[string]string{"Authorization": base.GetToken()}, - ExpectStatus: http.StatusOK, - }), - Entry("delete consumer jack", base.HttpTestCase{ - Object: base.ManagerApiExpect(), - Method: http.MethodDelete, - Path: "/apisix/admin/consumers/jack", - Headers: map[string]string{"Authorization": base.GetToken()}, - ExpectStatus: http.StatusOK, - }), - Entry("make sure pony has been deleted", base.HttpTestCase{ - Object: base.APISIXExpect(), - Method: http.MethodGet, - Path: "/hello", - Headers: map[string]string{"apikey": "auth-pony"}, - ExpectStatus: http.StatusUnauthorized, - ExpectBody: `{"message":"Missing related consumer"}`, - Sleep: base.SleepTime, - }), - Entry("make sure jack has been deleted", base.HttpTestCase{ - Object: base.APISIXExpect(), - Method: http.MethodGet, - Path: "/hello", - Headers: map[string]string{"apikey": "auth-jack"}, - ExpectStatus: http.StatusUnauthorized, - ExpectBody: `{"message":"Missing related consumer"}`, - Sleep: base.SleepTime, - }), - Entry("delete route", base.HttpTestCase{ - Object: base.ManagerApiExpect(), - Method: http.MethodDelete, - Path: "/apisix/admin/routes/r1", - Headers: map[string]string{"Authorization": base.GetToken()}, - ExpectStatus: http.StatusOK, - }), - Entry("hit the route just deleted", base.HttpTestCase{ - Object: base.APISIXExpect(), - Method: http.MethodGet, - Path: "/hello", - ExpectStatus: http.StatusNotFound, - ExpectBody: `{"error_msg":"404 Route Not Found"}`, - Sleep: base.SleepTime, - }), - ) -}) - -var _ = Describe("route with limit count and disable", func() { - DescribeTable("test route with limit count and disable", - func(tc base.HttpTestCase) { - base.RunTestCase(tc) - }, - Entry("make sure the route is not created", base.HttpTestCase{ - Object: base.APISIXExpect(), - Method: http.MethodGet, - Path: "/hello", - ExpectStatus: http.StatusNotFound, - ExpectBody: `{"error_msg":"404 Route Not Found"}`, - }), - Entry("create route with limit plugin", base.HttpTestCase{ - Object: base.ManagerApiExpect(), - Method: http.MethodPut, - Path: "/apisix/admin/routes/r1", - Body: `{ - "name": "route1", - "uri": "/hello", - "plugins": { - "limit-count": { - "count": 2, - "time_window": 2, - "rejected_code": 503, - "key": "remote_addr", - "disable": false, - "policy": "local" - } - }, - "upstream": { - "type": "roundrobin", - "nodes": { - "` + base.UpstreamIp + `:1981": 1 - } - } - }`, - Headers: map[string]string{"Authorization": base.GetToken()}, - ExpectStatus: http.StatusOK, - ExpectBody: `"code":0`, - }), - Entry("verify route that should not be limited", base.HttpTestCase{ - Object: base.APISIXExpect(), - Method: http.MethodGet, - Path: "/hello", - ExpectStatus: http.StatusOK, - ExpectBody: "hello world", - Sleep: base.SleepTime, - }), - Entry("verify route that should not be limited 2", base.HttpTestCase{ - Object: base.APISIXExpect(), - Method: http.MethodGet, - Path: "/hello", - ExpectStatus: http.StatusOK, - ExpectBody: "hello world", - }), - Entry("verify route that should be limited", base.HttpTestCase{ - Object: base.APISIXExpect(), - Method: http.MethodGet, - Path: "/hello", - ExpectStatus: http.StatusServiceUnavailable, - ExpectBody: "503 Service Temporarily Unavailable", - }), - Entry("verify route that should not be limited since time window pass", base.HttpTestCase{ - Object: base.APISIXExpect(), - Method: http.MethodGet, - Path: "/hello", - ExpectStatus: http.StatusOK, - ExpectBody: "hello world", - Sleep: 2 * time.Second, - }), - Entry("update route to disable plugin limit-count", base.HttpTestCase{ - Object: base.ManagerApiExpect(), - Method: http.MethodPut, - Path: "/apisix/admin/routes/r1", - Body: `{ - "name": "route1", - "uri": "/hello", - "plugins": { - "limit-count": { - "count": 1, - "time_window": 30, - "rejected_code": 429, - "key": "remote_addr", - "disable": true, - "policy": "local" - } - }, - "upstream": { - "type": "roundrobin", - "nodes": { - "` + base.UpstreamIp + `:1981": 1 - } - } - }`, - Headers: map[string]string{"Authorization": base.GetToken()}, - ExpectStatus: http.StatusOK, - ExpectBody: `"code":0`, - }), - Entry("verify route that should not be limited", base.HttpTestCase{ - Object: base.APISIXExpect(), - Method: http.MethodGet, - Path: "/hello", - ExpectStatus: http.StatusOK, - ExpectBody: "hello world", - Sleep: base.SleepTime, - }), - Entry("verify route that should be limited (exceed config count)", base.HttpTestCase{ - Object: base.APISIXExpect(), - Method: http.MethodGet, - Path: "/hello", - ExpectStatus: http.StatusTooManyRequests, - }), - Entry("verify route that should be limited (exceed config count again)", base.HttpTestCase{ - Object: base.APISIXExpect(), - Method: http.MethodGet, - Path: "/hello", - ExpectStatus: http.StatusTooManyRequests, - }), - Entry("delete route", base.HttpTestCase{ - Object: base.ManagerApiExpect(), - Method: http.MethodDelete, - Path: "/apisix/admin/routes/r1", - Headers: map[string]string{"Authorization": base.GetToken()}, - ExpectStatus: http.StatusOK, - }), - Entry("hit the route just deleted", base.HttpTestCase{ - Object: base.APISIXExpect(), - Method: http.MethodGet, - Path: "/hello", - ExpectStatus: http.StatusNotFound, - ExpectBody: `{"error_msg":"404 Route Not Found"}`, - Sleep: base.SleepTime, - }), - ) -}) diff --git a/api/test/e2e/route/route_with_plugin_orchestration_test.go b/api/test/e2e/route/route_with_plugin_orchestration_test.go deleted file mode 100644 index 3d504697eb..0000000000 --- a/api/test/e2e/route/route_with_plugin_orchestration_test.go +++ /dev/null @@ -1,171 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one or more - * contributor license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright ownership. - * The ASF licenses this file to You under the Apache License, Version 2.0 - * (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package route_test - -import ( - "io/ioutil" - "net/http" - - . "github.com/onsi/ginkgo/v2" - . "github.com/onsi/gomega" - "github.com/tidwall/gjson" - - "github.com/apisix/manager-api/test/e2e/base" -) - -var _ = Describe("route with plugin orchestration", func() { - bytes, err := ioutil.ReadFile("../../testdata/dag-conf.json") - It("panics if readfile dag-conf.json error", func() { - Expect(err).To(BeNil()) - }) - dagConf := string(bytes) - - // invalid dag config that not specified root node - bytes, err = ioutil.ReadFile("../../testdata/invalid-dag-conf.json") - It("panics if readfile invalid-dag-conf.json error", func() { - Expect(err).To(BeNil()) - }) - invalidDagConf := string(bytes) - - DescribeTable("test route with plugin orchestration", - func(tc base.HttpTestCase) { - base.RunTestCase(tc) - }, - Entry("make sure the route is not created", base.HttpTestCase{ - Object: base.APISIXExpect(), - Method: http.MethodGet, - Path: "/hello", - ExpectStatus: http.StatusNotFound, - ExpectBody: `{"error_msg":"404 Route Not Found"}`, - }), - Entry("create route with invalid dag config", base.HttpTestCase{ - Object: base.ManagerApiExpect(), - Method: http.MethodPut, - Path: "/apisix/admin/routes/r1", - Body: invalidDagConf, - Headers: map[string]string{"Authorization": base.GetToken()}, - ExpectStatus: http.StatusBadRequest, - }), - Entry("make sure the route created failed", base.HttpTestCase{ - Object: base.APISIXExpect(), - Method: http.MethodGet, - Path: "/hello", - ExpectStatus: http.StatusNotFound, - ExpectBody: `{"error_msg":"404 Route Not Found"}`, - Sleep: base.SleepTime, - }), - Entry("create route with correct dag config", base.HttpTestCase{ - Object: base.ManagerApiExpect(), - Method: http.MethodPut, - Path: "/apisix/admin/routes/r1", - Body: dagConf, - Headers: map[string]string{"Authorization": base.GetToken()}, - ExpectStatus: http.StatusOK, - }), - Entry("verify the route(should be blocked)", base.HttpTestCase{ - Object: base.APISIXExpect(), - Method: http.MethodGet, - Path: "/hello", - Query: "t=root.exe", - ExpectStatus: http.StatusForbidden, - ExpectBody: `blocked`, - Sleep: base.SleepTime, - }), - Entry("verify the route(should not be blocked)", base.HttpTestCase{ - Object: base.APISIXExpect(), - Method: http.MethodGet, - Path: "/hello", - ExpectStatus: http.StatusOK, - ExpectBody: `hello world`, - }), - Entry("delete route", base.HttpTestCase{ - Object: base.ManagerApiExpect(), - Method: http.MethodDelete, - Path: "/apisix/admin/routes/r1", - Headers: map[string]string{"Authorization": base.GetToken()}, - ExpectStatus: http.StatusOK, - }), - Entry("hit the route just deleted", base.HttpTestCase{ - Object: base.APISIXExpect(), - Method: http.MethodGet, - Path: "/hello", - ExpectStatus: http.StatusNotFound, - ExpectBody: `{"error_msg":"404 Route Not Found"}`, - Sleep: base.SleepTime, - }), - ) - - DescribeTable("test route with plugin orchestration (post method)", - func(tc base.HttpTestCase) { - base.RunTestCase(tc) - }, - Entry("make sure the route is not created", base.HttpTestCase{ - Object: base.APISIXExpect(), - Method: http.MethodGet, - Path: "/hello", - ExpectStatus: http.StatusNotFound, - ExpectBody: `{"error_msg":"404 Route Not Found"}`, - }), - ) - - var routeID string - It("create route with correct dag config by post", func() { - resp, code, err := base.HttpPost(base.ManagerAPIHost+"/apisix/admin/routes", - map[string]string{"Authorization": base.GetToken()}, dagConf) - Expect(err).To(BeNil()) - Expect(code).Should(Equal(200)) - routeID = gjson.Get(string(resp), "data.id").String() - }) - - It("test the route with plugin orchestration", func() { - base.RunTestCase(base.HttpTestCase{ - Desc: "verify the route (should be blocked)", - Object: base.APISIXExpect(), - Method: http.MethodGet, - Path: "/hello", - Query: "t=root.exe", - ExpectStatus: http.StatusForbidden, - ExpectBody: `blocked`, - Sleep: base.SleepTime, - }) - base.RunTestCase(base.HttpTestCase{ - Desc: "verify the route (should not be blocked)", - Object: base.APISIXExpect(), - Method: http.MethodGet, - Path: "/hello", - ExpectStatus: http.StatusOK, - ExpectBody: `hello world`, - }) - base.RunTestCase(base.HttpTestCase{ - Desc: "delete the route by routeID", - Object: base.ManagerApiExpect(), - Method: http.MethodDelete, - Path: "/apisix/admin/routes/" + routeID, - Headers: map[string]string{"Authorization": base.GetToken()}, - ExpectStatus: http.StatusOK, - }) - base.RunTestCase(base.HttpTestCase{ - Desc: "make sure the route has been deleted", - Object: base.APISIXExpect(), - Method: http.MethodGet, - Path: "/hello", - ExpectStatus: http.StatusNotFound, - ExpectBody: `{"error_msg":"404 Route Not Found"}`, - Sleep: base.SleepTime, - }) - }) -}) diff --git a/api/test/e2e/route/route_with_plugin_prometheus_test.go b/api/test/e2e/route/route_with_plugin_prometheus_test.go deleted file mode 100644 index e3b3c1077c..0000000000 --- a/api/test/e2e/route/route_with_plugin_prometheus_test.go +++ /dev/null @@ -1,134 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one or more - * contributor license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright ownership. - * The ASF licenses this file to You under the Apache License, Version 2.0 - * (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package route_test - -import ( - "net/http" - "time" - - . "github.com/onsi/ginkgo/v2" - - "github.com/apisix/manager-api/test/e2e/base" -) - -var _ = DescribeTable("route with plugin prometheus", - func(tc base.HttpTestCase) { - base.RunTestCase(tc) - }, - Entry("make sure the route is not created", base.HttpTestCase{ - Object: base.APISIXExpect(), - Method: http.MethodGet, - Path: "/hello", - ExpectStatus: http.StatusNotFound, - ExpectBody: `{"error_msg":"404 Route Not Found"}`, - }), - Entry("create route with plugin prometheus", base.HttpTestCase{ - Desc: "create route", - Object: base.ManagerApiExpect(), - Method: http.MethodPut, - Path: "/apisix/admin/routes/r1", - Body: `{ - "name": "route1", - "uri": "/hello", - "plugins": { - "prometheus": {} - }, - "upstream": { - "type": "roundrobin", - "nodes": { - "` + base.UpstreamIp + `:1982": 1 - } - } - }`, - Headers: map[string]string{"Authorization": base.GetToken()}, - ExpectStatus: http.StatusOK, - }), - Entry("fetch the prometheus metric data", base.HttpTestCase{ - Object: base.PrometheusExporterExpect(), - Method: http.MethodGet, - Path: "/apisix/prometheus/metrics", - ExpectStatus: http.StatusOK, - ExpectBody: "apisix_etcd_reachable 1", - Sleep: base.SleepTime, - }), - Entry("request from client (200)", base.HttpTestCase{ - Object: base.APISIXExpect(), - Method: http.MethodGet, - Path: "/hello", - ExpectStatus: http.StatusOK, - ExpectBody: "hello world", - }), - Entry("create route that uri not exists in upstream", base.HttpTestCase{ - Object: base.ManagerApiExpect(), - Method: http.MethodPut, - Path: "/apisix/admin/routes/r1", - Body: `{ - "name": "route1", - "uri": "/hello-not-exists", - "plugins": { - "prometheus": {} - }, - "upstream": { - "type": "roundrobin", - "nodes": { - "` + base.UpstreamIp + `:1982": 1 - } - } - }`, - Headers: map[string]string{"Authorization": base.GetToken()}, - ExpectStatus: http.StatusOK, - }), - Entry("request from client (404)", base.HttpTestCase{ - Object: base.APISIXExpect(), - Method: http.MethodGet, - Path: "/hello-not-exists", - ExpectStatus: http.StatusNotFound, - Sleep: base.SleepTime, - }), - Entry("verify the prometheus metric data (apisix_http_status 200)", base.HttpTestCase{ - Object: base.PrometheusExporterExpect(), - Method: http.MethodGet, - Path: "/apisix/prometheus/metrics", - ExpectStatus: http.StatusOK, - ExpectBody: `apisix_http_status{code="200",route="r1",matched_uri="/hello",matched_host="",service="",consumer=""`, - Sleep: 1 * time.Second, - }), - Entry("verify the prometheus metric data (apisix_http_status 404)", base.HttpTestCase{ - Object: base.PrometheusExporterExpect(), - Method: http.MethodGet, - Path: "/apisix/prometheus/metrics", - ExpectStatus: http.StatusOK, - ExpectBody: `apisix_http_status{code="404",route="r1",matched_uri="/hello-not-exists",matched_host="",service="",consumer=""`, - Sleep: base.SleepTime, - }), - Entry("delete route", base.HttpTestCase{ - Object: base.ManagerApiExpect(), - Method: http.MethodDelete, - Path: "/apisix/admin/routes/r1", - Headers: map[string]string{"Authorization": base.GetToken()}, - ExpectStatus: http.StatusOK, - Sleep: base.SleepTime, - }), - Entry("make sure the route deleted", base.HttpTestCase{ - Object: base.APISIXExpect(), - Method: http.MethodGet, - Path: "/hello", - ExpectStatus: http.StatusNotFound, - ExpectBody: `{"error_msg":"404 Route Not Found"}`, - Sleep: base.SleepTime, - }), -) diff --git a/api/test/e2e/route/route_with_plugin_proxy_rewrite_test.go b/api/test/e2e/route/route_with_plugin_proxy_rewrite_test.go deleted file mode 100644 index 82b2efd1cb..0000000000 --- a/api/test/e2e/route/route_with_plugin_proxy_rewrite_test.go +++ /dev/null @@ -1,180 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one or more - * contributor license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright ownership. - * The ASF licenses this file to You under the Apache License, Version 2.0 - * (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package route_test - -import ( - "net/http" - - . "github.com/onsi/ginkgo/v2" - - "github.com/apisix/manager-api/test/e2e/base" -) - -var _ = DescribeTable("route with plugin proxy rewrite", - func(tc base.HttpTestCase) { - base.RunTestCase(tc) - }, - Entry("make sure the route is not created", base.HttpTestCase{ - Object: base.APISIXExpect(), - Method: http.MethodGet, - Path: "/hello", - ExpectStatus: http.StatusNotFound, - ExpectBody: `{"error_msg":"404 Route Not Found"}`, - }), - Entry("create route that will rewrite host and uri", base.HttpTestCase{ - Object: base.ManagerApiExpect(), - Method: http.MethodPut, - Path: "/apisix/admin/routes/r1", - Body: `{ - "name": "route1", - "uri": "/hello", - "plugins": { - "proxy-rewrite": { - "uri": "/plugin_proxy_rewrite", - "host": "test.com" - } - }, - "upstream": { - "type": "roundrobin", - "nodes": { - "` + base.UpstreamIp + `:1982": 1 - } - } - }`, - Headers: map[string]string{"Authorization": base.GetToken()}, - ExpectStatus: http.StatusOK, - }), - Entry("verify route that rewrite host and uri", base.HttpTestCase{ - Object: base.APISIXExpect(), - Method: http.MethodGet, - Path: "/hello", - ExpectStatus: http.StatusOK, - ExpectBody: "uri: /plugin_proxy_rewrite\nhost: test.com", - Sleep: base.SleepTime, - }), - Entry("update route that will rewrite headers", base.HttpTestCase{ - Object: base.ManagerApiExpect(), - Method: http.MethodPut, - Path: "/apisix/admin/routes/r1", - Body: `{ - "name": "route1", - "uri": "/hello", - "plugins": { - "proxy-rewrite": { - "uri": "/uri/plugin_proxy_rewrite", - "headers": { - "X-Api-Version": "v2" - } - } - }, - "upstream": { - "type": "roundrobin", - "nodes": { - "` + base.UpstreamIp + `:1982": 1 - } - } - }`, - Headers: map[string]string{"Authorization": base.GetToken()}, - ExpectStatus: http.StatusOK, - }), - Entry("verify route that rewrite headers", base.HttpTestCase{ - Object: base.APISIXExpect(), - Method: http.MethodGet, - Path: "/hello", - Headers: map[string]string{"X-Api-Version": "v1"}, - ExpectStatus: http.StatusOK, - ExpectBody: "x-api-version: v2", - Sleep: base.SleepTime, - }), - Entry("update route using regex_uri", base.HttpTestCase{ - Object: base.ManagerApiExpect(), - Method: http.MethodPut, - Path: "/apisix/admin/routes/r1", - Body: `{ - "name": "route1", - "uri": "/test/*", - "plugins": { - "proxy-rewrite": { - "regex_uri": ["^/test/(.*)/(.*)/(.*)", "/$1_$2_$3"] - } - }, - "upstream": { - "type": "roundrobin", - "nodes": { - "` + base.UpstreamIp + `:1982": 1 - } - } - }`, - Headers: map[string]string{"Authorization": base.GetToken()}, - ExpectStatus: http.StatusOK, - }), - Entry("verify route that using regex_uri", base.HttpTestCase{ - Object: base.APISIXExpect(), - Method: http.MethodGet, - Path: `/test/plugin/proxy/rewrite`, - ExpectStatus: http.StatusOK, - ExpectBody: "uri: /plugin_proxy_rewrite", - Sleep: base.SleepTime, - }), - Entry("update route that will rewrite args", base.HttpTestCase{ - Object: base.ManagerApiExpect(), - Method: http.MethodPut, - Path: "/apisix/admin/routes/r1", - Body: `{ - "name": "route1", - "uri": "/hello", - "plugins": { - "proxy-rewrite": { - "uri": "/plugin_proxy_rewrite_args?name=api6" - } - }, - "upstream": { - "type": "roundrobin", - "nodes": { - "` + base.UpstreamIp + `:1982": 1 - } - } - }`, - Headers: map[string]string{"Authorization": base.GetToken()}, - ExpectStatus: http.StatusOK, - }), - Entry("verify route that rewrite args", base.HttpTestCase{ - Object: base.APISIXExpect(), - Method: http.MethodGet, - Path: `/hello`, - Query: "name=api7", - ExpectStatus: http.StatusOK, - ExpectBody: "uri: /plugin_proxy_rewrite_args\nname: api6", - Sleep: base.SleepTime, - }), - Entry("delete route", base.HttpTestCase{ - Object: base.ManagerApiExpect(), - Method: http.MethodDelete, - Path: "/apisix/admin/routes/r1", - Headers: map[string]string{"Authorization": base.GetToken()}, - ExpectStatus: http.StatusOK, - Sleep: base.SleepTime, - }), - Entry("make sure the route deleted", base.HttpTestCase{ - Object: base.APISIXExpect(), - Method: http.MethodGet, - Path: "/hello", - ExpectStatus: http.StatusNotFound, - ExpectBody: `{"error_msg":"404 Route Not Found"}`, - Sleep: base.SleepTime, - }), -) diff --git a/api/test/e2e/route/route_with_plugin_uri_blocker_test.go b/api/test/e2e/route/route_with_plugin_uri_blocker_test.go deleted file mode 100644 index d747abfc92..0000000000 --- a/api/test/e2e/route/route_with_plugin_uri_blocker_test.go +++ /dev/null @@ -1,142 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one or more - * contributor license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright ownership. - * The ASF licenses this file to You under the Apache License, Version 2.0 - * (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package route_test - -import ( - "net/http" - - . "github.com/onsi/ginkgo/v2" - - "github.com/apisix/manager-api/test/e2e/base" -) - -var _ = DescribeTable("route with plugin uri blocker", - func(tc base.HttpTestCase) { - base.RunTestCase(tc) - }, - Entry("make sure the route is not created", base.HttpTestCase{ - Object: base.APISIXExpect(), - Method: http.MethodGet, - Path: "/*", - ExpectStatus: http.StatusNotFound, - ExpectBody: `{"error_msg":"404 Route Not Found"}`, - }), - Entry("create route1", base.HttpTestCase{ - Object: base.ManagerApiExpect(), - Method: http.MethodPut, - Path: "/apisix/admin/routes/r1", - Body: `{ - "name": "route1", - "uri": "/*", - "upstream": { - "type": "roundrobin", - "nodes": { - "` + base.UpstreamIp + `:1982": 1 - } - } - }`, - Headers: map[string]string{"Authorization": base.GetToken()}, - ExpectStatus: http.StatusOK, - }), - Entry("make sure the plugin uri blocker is not worked", base.HttpTestCase{ - Object: base.APISIXExpect(), - Method: http.MethodGet, - Path: "/root.exe", - ExpectStatus: http.StatusNotFound, - }), - Entry("update route with uri blocker", base.HttpTestCase{ - Object: base.ManagerApiExpect(), - Method: http.MethodPut, - Path: "/apisix/admin/routes/r1", - Body: `{ - "name": "route1", - "uri": "/*", - "plugins": { - "uri-blocker": { - "block_rules": ["hello"] - } - }, - "upstream": { - "type": "roundrobin", - "nodes": { - "` + base.UpstreamIp + `:1980": 1 - } - } - }`, - Headers: map[string]string{"Authorization": base.GetToken()}, - ExpectStatus: http.StatusOK, - }), - Entry("verify route that block uri", base.HttpTestCase{ - Object: base.APISIXExpect(), - Method: http.MethodGet, - Path: "/hello", - ExpectStatus: http.StatusForbidden, - Sleep: base.SleepTime, - }), - Entry("update route with uri blocker", base.HttpTestCase{ - Object: base.ManagerApiExpect(), - Method: http.MethodPut, - Path: "/apisix/admin/routes/r1", - Body: `{ - "name": "route1", - "uri": "/*", - "plugins": { - "uri-blocker": { - "block_rules": ["robots.txt"] - } - }, - "upstream": { - "type": "roundrobin", - "nodes": { - "` + base.UpstreamIp + `:1980": 1 - } - } - }`, - Headers: map[string]string{"Authorization": base.GetToken()}, - ExpectStatus: http.StatusOK, - }), - Entry("verify route that old block uri rule", base.HttpTestCase{ - Object: base.APISIXExpect(), - Method: http.MethodGet, - Path: "/hello", - ExpectStatus: http.StatusOK, - Sleep: base.SleepTime, - }), - Entry("verify route that block uri", base.HttpTestCase{ - Object: base.APISIXExpect(), - Method: http.MethodGet, - Path: "/robots.txt", - ExpectStatus: http.StatusForbidden, - Sleep: base.SleepTime, - }), - Entry("delete route", base.HttpTestCase{ - Object: base.ManagerApiExpect(), - Method: http.MethodDelete, - Path: "/apisix/admin/routes/r1", - Headers: map[string]string{"Authorization": base.GetToken()}, - ExpectStatus: http.StatusOK, - Sleep: base.SleepTime, - }), - Entry("make sure the route deleted", base.HttpTestCase{ - Object: base.APISIXExpect(), - Method: http.MethodGet, - Path: "/hello", - ExpectStatus: http.StatusNotFound, - ExpectBody: `{"error_msg":"404 Route Not Found"}`, - Sleep: base.SleepTime, - }), -) diff --git a/api/test/e2e/route/route_with_priority_test.go b/api/test/e2e/route/route_with_priority_test.go deleted file mode 100644 index 7504a9f181..0000000000 --- a/api/test/e2e/route/route_with_priority_test.go +++ /dev/null @@ -1,108 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one or more - * contributor license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright ownership. - * The ASF licenses this file to You under the Apache License, Version 2.0 - * (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package route_test - -import ( - "net/http" - - . "github.com/onsi/ginkgo/v2" - - "github.com/apisix/manager-api/test/e2e/base" -) - -var _ = DescribeTable("route with priority test", - func(tc base.HttpTestCase) { - base.RunTestCase(tc) - }, - Entry("add another route with no priority (default 0)", base.HttpTestCase{ - Object: base.ManagerApiExpect(), - Method: http.MethodPut, - Path: "/apisix/admin/routes/r1", - Body: `{ - "name": "route1", - "uri": "/server_port", - "methods": ["GET"], - "upstream": { - "type": "roundrobin", - "nodes": { - "` + base.UpstreamIp + `:1981": 1 - } - } - }`, - Headers: map[string]string{"Authorization": base.GetToken()}, - ExpectStatus: http.StatusOK, - }), - Entry("access the route", base.HttpTestCase{ - Object: base.APISIXExpect(), - Method: http.MethodGet, - Path: "/server_port", - ExpectStatus: http.StatusOK, - ExpectBody: "1981", - Sleep: base.SleepTime, - }), - Entry("add another route with valid priority (1), upstream is different from the others", base.HttpTestCase{ - Object: base.ManagerApiExpect(), - Method: http.MethodPut, - Path: "/apisix/admin/routes/r2", - Body: `{ - "name": "route2", - "uri": "/server_port", - "methods": ["GET"], - "priority": 1, - "upstream": { - "type": "roundrobin", - "nodes": { - "` + base.UpstreamIp + `:1982": 1 - } - } - }`, - Headers: map[string]string{"Authorization": base.GetToken()}, - ExpectStatus: http.StatusOK, - }), - Entry("access the route to determine whether it meets the priority (compare 1 and default)", base.HttpTestCase{ - Object: base.APISIXExpect(), - Method: http.MethodGet, - Path: "/server_port", - ExpectStatus: http.StatusOK, - ExpectBody: "1982", - Sleep: base.SleepTime, - }), - Entry("delete route (r1)", base.HttpTestCase{ - Object: base.ManagerApiExpect(), - Method: http.MethodDelete, - Path: "/apisix/admin/routes/r1", - Headers: map[string]string{"Authorization": base.GetToken()}, - ExpectStatus: http.StatusOK, - Sleep: base.SleepTime, - }), - Entry("delete route (r2)", base.HttpTestCase{ - Object: base.ManagerApiExpect(), - Method: http.MethodDelete, - Path: "/apisix/admin/routes/r2", - Headers: map[string]string{"Authorization": base.GetToken()}, - ExpectStatus: http.StatusOK, - Sleep: base.SleepTime, - }), - Entry("hit the route just delete", base.HttpTestCase{ - Object: base.APISIXExpect(), - Method: http.MethodGet, - Path: "/server_port", - ExpectStatus: http.StatusNotFound, - ExpectBody: "{\"error_msg\":\"404 Route Not Found\"}\n", - Sleep: base.SleepTime, - }), -) diff --git a/api/test/e2e/route/route_with_remote_addr_test.go b/api/test/e2e/route/route_with_remote_addr_test.go deleted file mode 100644 index 04e9eccaad..0000000000 --- a/api/test/e2e/route/route_with_remote_addr_test.go +++ /dev/null @@ -1,280 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one or more - * contributor license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright ownership. - * The ASF licenses this file to You under the Apache License, Version 2.0 - * (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package route_test - -import ( - "net/http" - - . "github.com/onsi/ginkgo/v2" - - "github.com/apisix/manager-api/test/e2e/base" -) - -var _ = Describe("route with valid remote_addr remote_addrs", func() { - DescribeTable("test route with valid remote_addr remote_addrs", - func(tc base.HttpTestCase) { - base.RunTestCase(tc) - }, - Entry("add route with valid remote_addr", base.HttpTestCase{ - Object: base.ManagerApiExpect(), - Method: http.MethodPut, - Path: "/apisix/admin/routes/r1", - Body: `{ - "name": "route1", - "uri": "/hello", - "remote_addr": "172.16.238.1", - "upstream": { - "type": "roundrobin", - "nodes": { - "` + base.UpstreamIp + `:1980": 1 - } - } - }`, - Headers: map[string]string{"Authorization": base.GetToken()}, - ExpectStatus: http.StatusOK, - }), - Entry("verify route", base.HttpTestCase{ - Object: base.APISIXExpect(), - Method: http.MethodGet, - Path: "/hello", - Headers: map[string]string{"Authorization": base.GetToken()}, - ExpectStatus: http.StatusOK, - ExpectBody: "hello world", - Sleep: base.SleepTime, - }), - Entry("update route with valid remote_addr (CIDR)", base.HttpTestCase{ - Object: base.ManagerApiExpect(), - Method: http.MethodPut, - Path: "/apisix/admin/routes/r1", - Body: `{ - "name": "route1", - "uri": "/hello", - "remote_addr": "172.16.238.1/24", - "upstream": { - "type": "roundrobin", - "nodes": { - "` + base.UpstreamIp + `:1980": 1 - } - } - }`, - Headers: map[string]string{"Authorization": base.GetToken()}, - ExpectStatus: http.StatusOK, - }), - Entry("verify route", base.HttpTestCase{ - Object: base.APISIXExpect(), - Method: http.MethodGet, - Path: "/hello", - Headers: map[string]string{"Authorization": base.GetToken()}, - ExpectStatus: http.StatusOK, - ExpectBody: "hello world", - Sleep: base.SleepTime, - }), - Entry("update route with valid remote_addrs", base.HttpTestCase{ - Object: base.ManagerApiExpect(), - Method: http.MethodPut, - Path: "/apisix/admin/routes/r1", - Body: `{ - "name": "route1", - "uri": "/hello", - "remote_addrs": ["172.16.238.1","192.168.0.2/24"], - "upstream": { - "type": "roundrobin", - "nodes": { - "` + base.UpstreamIp + `:1980": 1 - } - } - }`, - Headers: map[string]string{"Authorization": base.GetToken()}, - ExpectStatus: http.StatusOK, - }), - Entry("verify route", base.HttpTestCase{ - Object: base.APISIXExpect(), - Method: http.MethodGet, - Path: "/hello", - Headers: map[string]string{"Authorization": base.GetToken()}, - ExpectStatus: http.StatusOK, - ExpectBody: "hello world", - Sleep: base.SleepTime, - }), - Entry("update remote_addr to not be hit", base.HttpTestCase{ - Object: base.ManagerApiExpect(), - Method: http.MethodPut, - Path: "/apisix/admin/routes/r1", - Body: `{ - "name": "route1", - "uri": "/hello", - "remote_addr": "10.10.10.10", - "upstream": { - "type": "roundrobin", - "nodes": { - "` + base.UpstreamIp + `:1980": 1 - } - } - }`, - Headers: map[string]string{"Authorization": base.GetToken()}, - ExpectStatus: http.StatusOK, - }), - Entry("verify route not found", base.HttpTestCase{ - Object: base.APISIXExpect(), - Method: http.MethodGet, - Path: "/hello", - Headers: map[string]string{"Authorization": base.GetToken()}, - ExpectStatus: http.StatusNotFound, - ExpectBody: `{"error_msg":"404 Route Not Found"}`, - Sleep: base.SleepTime, - }), - Entry("update remote_addrs to not be hit", base.HttpTestCase{ - Object: base.ManagerApiExpect(), - Method: http.MethodPut, - Path: "/apisix/admin/routes/r1", - Body: `{ - "name": "route1", - "uri": "/hello", - "remote_addrs": ["10.10.10.10","11.11.11.1/24"], - "upstream": { - "type": "roundrobin", - "nodes": { - "` + base.UpstreamIp + `:1980": 1 - } - } - }`, - Headers: map[string]string{"Authorization": base.GetToken()}, - ExpectStatus: http.StatusOK, - }), - Entry("verify route not found", base.HttpTestCase{ - Object: base.APISIXExpect(), - Method: http.MethodGet, - Path: "/hello", - Headers: map[string]string{"Authorization": base.GetToken()}, - ExpectStatus: http.StatusNotFound, - ExpectBody: `{"error_msg":"404 Route Not Found"}`, - Sleep: base.SleepTime, - }), - Entry("delete route", base.HttpTestCase{ - Object: base.ManagerApiExpect(), - Method: http.MethodDelete, - Path: "/apisix/admin/routes/r1", - Headers: map[string]string{"Authorization": base.GetToken()}, - ExpectStatus: http.StatusOK, - Sleep: base.SleepTime, - }), - Entry("verify it again after deleting the route", base.HttpTestCase{ - Object: base.APISIXExpect(), - Method: http.MethodGet, - Path: "/hello", - Headers: map[string]string{"Authorization": base.GetToken()}, - ExpectStatus: http.StatusNotFound, - ExpectBody: `{"error_msg":"404 Route Not Found"}`, - Sleep: base.SleepTime, - }), - ) -}) - -var _ = Describe("route with invalid remote_addr", func() { - DescribeTable("route with remote_addr", - func(tc base.HttpTestCase) { - base.RunTestCase(tc) - }, - Entry("config route with invalid remote_addr", base.HttpTestCase{ - Object: base.ManagerApiExpect(), - Method: http.MethodPut, - Path: "/apisix/admin/routes/r1", - Body: `{ - "name": "route1", - "uri": "/hello", - "remote_addr": "127.0.0.", - "upstream": { - "type": "roundrobin", - "nodes": [{ - "host": "` + base.UpstreamIp + `", - "port": 1980, - "weight": 1 - }] - } - }`, - Headers: map[string]string{"Authorization": base.GetToken()}, - ExpectStatus: http.StatusBadRequest, - ExpectBody: "{\"code\":10000,\"message\":\"schema validate failed: remote_addr: Must validate at least one schema (anyOf)\\nremote_addr: Does not match format 'ipv4'\"}", - }), - Entry("verify route", base.HttpTestCase{ - Object: base.APISIXExpect(), - Method: http.MethodGet, - Path: "/hello", - Headers: map[string]string{"Authorization": base.GetToken()}, - ExpectStatus: http.StatusNotFound, - Sleep: base.SleepTime, - }), - Entry("config route with invalid remote_addr", base.HttpTestCase{ - Object: base.ManagerApiExpect(), - Method: http.MethodPut, - Path: "/apisix/admin/routes/r1", - Body: `{ - "name": "route1", - "uri": "/hello", - "remote_addr": "127.0.0.aa", - "upstream": { - "type": "roundrobin", - "nodes": [{ - "host": "` + base.UpstreamIp + `", - "port": 1980, - "weight": 1 - }] - } - }`, - Headers: map[string]string{"Authorization": base.GetToken()}, - ExpectStatus: http.StatusBadRequest, - ExpectBody: "{\"code\":10000,\"message\":\"schema validate failed: remote_addr: Must validate at least one schema (anyOf)\\nremote_addr: Does not match format 'ipv4'\"}", - }), - Entry("verify route", base.HttpTestCase{ - Object: base.APISIXExpect(), - Method: http.MethodGet, - Path: "/hello", - Headers: map[string]string{"Authorization": base.GetToken()}, - ExpectStatus: http.StatusNotFound, - Sleep: base.SleepTime, - }), - Entry("config route with invalid remote_addrs", base.HttpTestCase{ - Object: base.ManagerApiExpect(), - Method: http.MethodPut, - Path: "/apisix/admin/routes/r1", - Body: `{ - "name": "route1", - "uri": "/hello", - "remote_addrs": ["127.0.0.1","192.168.0."], - "upstream": { - "type": "roundrobin", - "nodes": [{ - "host": "` + base.UpstreamIp + `", - "port": 1980, - "weight": 1 - }] - } - }`, - Headers: map[string]string{"Authorization": base.GetToken()}, - ExpectStatus: http.StatusBadRequest, - ExpectBody: "{\"code\":10000,\"message\":\"schema validate failed: remote_addrs.1: Must validate at least one schema (anyOf)\\nremote_addrs.1: Does not match format 'ipv4'\"}", - }), - Entry("verify route", base.HttpTestCase{ - Object: base.APISIXExpect(), - Method: http.MethodGet, - Path: "/hello", - Headers: map[string]string{"Authorization": base.GetToken()}, - ExpectStatus: http.StatusNotFound, - Sleep: base.SleepTime, - }), - ) -}) diff --git a/api/test/e2e/route/route_with_script_luacode_test.go b/api/test/e2e/route/route_with_script_luacode_test.go deleted file mode 100644 index 1a5742d908..0000000000 --- a/api/test/e2e/route/route_with_script_luacode_test.go +++ /dev/null @@ -1,345 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one or more - * contributor license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright ownership. - * The ASF licenses this file to You under the Apache License, Version 2.0 - * (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package route_test - -import ( - "net/http" - "time" - - . "github.com/onsi/ginkgo/v2" - . "github.com/onsi/gomega" - - "github.com/apisix/manager-api/test/e2e/base" -) - -var _ = Describe("route with script lucacode", func() { - It("clean APISIX error log", func() { - base.CleanAPISIXErrorLog() - }) - DescribeTable("test route with script lucacode", - func(tc base.HttpTestCase) { - base.RunTestCase(tc) - }, - Entry("create route with script of valid lua code", base.HttpTestCase{ - Object: base.ManagerApiExpect(), - Method: http.MethodPut, - Path: "/apisix/admin/routes/r1", - Body: `{ - "name": "route1", - "uri": "/hello", - "upstream": { - "type": "roundrobin", - "nodes": { - "` + base.UpstreamIp + `:1980": 1 - } - }, - "script": "local _M = {} \n function _M.access(api_ctx) \n ngx.log(ngx.WARN,\"hit access phase\") \n end \nreturn _M" - }`, - Headers: map[string]string{"Authorization": base.GetToken()}, - ExpectStatus: http.StatusOK, - }), - Entry("get the route", base.HttpTestCase{ - Object: base.ManagerApiExpect(), - Method: http.MethodGet, - Path: "/apisix/admin/routes/r1", - Headers: map[string]string{"Authorization": base.GetToken()}, - ExpectStatus: http.StatusOK, - ExpectBody: "hit access phase", - Sleep: base.SleepTime, - }), - Entry("hit the route", base.HttpTestCase{ - Object: base.APISIXExpect(), - Method: http.MethodGet, - Path: "/hello", - Headers: map[string]string{"Authorization": base.GetToken()}, - ExpectStatus: http.StatusOK, - ExpectBody: "hello world\n", - }), - Entry("update route with script of valid lua code", base.HttpTestCase{ - Object: base.ManagerApiExpect(), - Method: http.MethodPut, - Path: "/apisix/admin/routes/r1", - Body: `{ - "name": "route1", - "uri": "/hello", - "upstream": { - "type": "roundrobin", - "nodes": { - "` + base.UpstreamIp + `:1981": 1 - } - }, - "script": "local _M = {} \n function _M.access(api_ctx) \n ngx.log(ngx.WARN,\"hit access phase\") \n end \nreturn _M" - }`, - Headers: map[string]string{"Authorization": base.GetToken()}, - ExpectStatus: http.StatusOK, - }), - Entry("update route with script of invalid lua code", base.HttpTestCase{ - Object: base.ManagerApiExpect(), - Method: http.MethodPut, - Path: "/apisix/admin/routes/r1", - Body: `{ - "name": "route1", - "uri": "/hello", - "upstream": { - "type": "roundrobin", - "nodes": { - "` + base.UpstreamIp + `:1980": 1 - } - }, - "script": "local _M = {} \n function _M.access(api_ctx) \n ngx.log(ngx.WARN,\"hit access phase\")" - }`, - Headers: map[string]string{"Authorization": base.GetToken()}, - ExpectStatus: http.StatusBadRequest, - }), - Entry("delete the route (r1)", base.HttpTestCase{ - Object: base.ManagerApiExpect(), - Method: http.MethodDelete, - Path: "/apisix/admin/routes/r1", - Headers: map[string]string{"Authorization": base.GetToken()}, - ExpectStatus: http.StatusOK, - Sleep: base.SleepTime, - }), - Entry("hit the route just delete", base.HttpTestCase{ - Object: base.APISIXExpect(), - Method: http.MethodGet, - Path: "/hello", - Headers: map[string]string{"Authorization": base.GetToken()}, - ExpectStatus: http.StatusNotFound, - ExpectBody: `{"error_msg":"404 Route Not Found"}`, - Sleep: base.SleepTime, - }), - Entry("create route with script of invalid lua code", base.HttpTestCase{ - Object: base.ManagerApiExpect(), - Method: http.MethodPut, - Path: "/apisix/admin/routes/r1", - Body: `{ - "name": "route1", - "uri": "/hello", - "upstream": { - "type": "roundrobin", - "nodes": { - "` + base.UpstreamIp + `:1980": 1 - } - }, - "script": "local _M = {} \n function _M.access(api_ctx) \n ngx.log(ngx.WARN,\"hit access phase\")" - }`, - Headers: map[string]string{"Authorization": base.GetToken()}, - ExpectStatus: http.StatusBadRequest, - }), - ) - It("verify the log generated by script set in Step-3 above", func() { - //sleep for process log - time.Sleep(1500 * time.Millisecond) - - //verify the log generated by script set in Step-3 above - logContent := base.ReadAPISIXErrorLog() - Expect(logContent).Should(ContainSubstring(`\"hit access phase\"`)) - - //clean log - base.CleanAPISIXErrorLog() - }) -}) - -var _ = Describe("route with script id", func() { - It("clean APISIX error log", func() { - base.CleanAPISIXErrorLog() - }) - DescribeTable("test route with script id", - func(tc base.HttpTestCase) { - base.RunTestCase(tc) - }, - Entry("create route with invalid script_id - not equal to id", base.HttpTestCase{ - Object: base.ManagerApiExpect(), - Method: http.MethodPost, - Path: "/apisix/admin/routes", - Body: `{ - "id": "r1", - "name": "route1", - "uri": "/hello", - "upstream": { - "type": "roundrobin", - "nodes": { - "` + base.UpstreamIp + `:1980": 1 - } - }, - "script": "local _M = {} \n function _M.access(api_ctx) \n ngx.log(ngx.WARN,\"hit access phase\") \n end \nreturn _M", - "script_id": "not-r1" - }`, - Headers: map[string]string{"Authorization": base.GetToken()}, - ExpectStatus: http.StatusBadRequest, - }), - Entry("create route with invalid script_id - set script_id but without id", base.HttpTestCase{ - Object: base.ManagerApiExpect(), - Method: http.MethodPost, - Path: "/apisix/admin/routes", - Body: `{ - "name": "route1", - "uri": "/hello", - "upstream": { - "type": "roundrobin", - "nodes": { - "` + base.UpstreamIp + `:1980": 1 - } - }, - "script": "local _M = {} \n function _M.access(api_ctx) \n ngx.log(ngx.WARN,\"hit access phase\") \n end \nreturn _M", - "script_id": "r1" - }`, - Headers: map[string]string{"Authorization": base.GetToken()}, - ExpectStatus: http.StatusBadRequest, - }), - Entry("create route with invalid script_id - set script_id but without script", base.HttpTestCase{ - Object: base.ManagerApiExpect(), - Method: http.MethodPost, - Path: "/apisix/admin/routes", - Body: `{ - "id": "r1", - "name": "route1", - "uri": "/hello", - "upstream": { - "type": "roundrobin", - "nodes": { - "` + base.UpstreamIp + `:1980": 1 - } - }, - "script_id": "r1" - }`, - Headers: map[string]string{"Authorization": base.GetToken()}, - ExpectStatus: http.StatusBadRequest, - }), - Entry("create route with valid script_id", base.HttpTestCase{ - Object: base.ManagerApiExpect(), - Method: http.MethodPost, - Path: "/apisix/admin/routes", - Body: `{ - "id": "r1", - "name": "route1", - "uri": "/hello", - "upstream": { - "type": "roundrobin", - "nodes": { - "` + base.UpstreamIp + `:1980": 1 - } - }, - "script": "local _M = {} \n function _M.access(api_ctx) \n ngx.log(ngx.WARN,\"hit access phase\") \n end \nreturn _M", - "script_id": "r1" - }`, - Headers: map[string]string{"Authorization": base.GetToken()}, - ExpectStatus: http.StatusOK, - }), - Entry("get the route", base.HttpTestCase{ - Object: base.ManagerApiExpect(), - Method: http.MethodGet, - Path: "/apisix/admin/routes/r1", - Headers: map[string]string{"Authorization": base.GetToken()}, - ExpectStatus: http.StatusOK, - ExpectBody: "\"script_id\":\"r1\"", - Sleep: base.SleepTime, - }), - Entry("hit the route", base.HttpTestCase{ - Object: base.APISIXExpect(), - Method: http.MethodGet, - Path: "/hello", - Headers: map[string]string{"Authorization": base.GetToken()}, - ExpectStatus: http.StatusOK, - ExpectBody: "hello world\n", - }), - Entry("update route with valid script_id", base.HttpTestCase{ - Object: base.ManagerApiExpect(), - Method: http.MethodPut, - Path: "/apisix/admin/routes/r1", - Body: `{ - "name": "route1", - "uri": "/hello", - "upstream": { - "type": "roundrobin", - "nodes": { - "` + base.UpstreamIp + `:1981": 1 - } - }, - "script": "local _M = {} \n function _M.access(api_ctx) \n ngx.log(ngx.WARN,\"hit access phase\") \n end \nreturn _M", - "script_id": "r1" - }`, - Headers: map[string]string{"Authorization": base.GetToken()}, - ExpectStatus: http.StatusOK, - }), - Entry("update route with invalid script_id - not equal to id", base.HttpTestCase{ - Object: base.ManagerApiExpect(), - Method: http.MethodPut, - Path: "/apisix/admin/routes/r1", - Body: `{ - "name": "route1", - "uri": "/hello", - "upstream": { - "type": "roundrobin", - "nodes": { - "` + base.UpstreamIp + `:1980": 1 - } - }, - "script": "local _M = {} \n function _M.access(api_ctx) \n ngx.log(ngx.WARN,\"hit access phase\")", - "script_id": "not-r1" - }`, - Headers: map[string]string{"Authorization": base.GetToken()}, - ExpectStatus: http.StatusBadRequest, - }), - Entry("update route with invalid script_id - set script_id but without script", base.HttpTestCase{ - Object: base.ManagerApiExpect(), - Method: http.MethodPut, - Path: "/apisix/admin/routes/r1", - Body: `{ - "name": "route1", - "uri": "/hello", - "upstream": { - "type": "roundrobin", - "nodes": { - "` + base.UpstreamIp + `:1980": 1 - } - }, - "script_id": "r1" - }`, - Headers: map[string]string{"Authorization": base.GetToken()}, - ExpectStatus: http.StatusBadRequest, - }), - Entry("delete the route (r1)", base.HttpTestCase{ - Object: base.ManagerApiExpect(), - Method: http.MethodDelete, - Path: "/apisix/admin/routes/r1", - Headers: map[string]string{"Authorization": base.GetToken()}, - ExpectStatus: http.StatusOK, - Sleep: base.SleepTime, - }), - Entry("hit the route just delete", base.HttpTestCase{ - Object: base.APISIXExpect(), - Method: http.MethodGet, - Path: "/hello", - Headers: map[string]string{"Authorization": base.GetToken()}, - ExpectStatus: http.StatusNotFound, - ExpectBody: `{"error_msg":"404 Route Not Found"}`, - Sleep: base.SleepTime, - }), - ) - It("verify the log generated by script set in Step-4 above", func() { - //sleep for process log - time.Sleep(1500 * time.Millisecond) - - //verify the log generated by script set in Step-4 above - logContent := base.ReadAPISIXErrorLog() - Expect(logContent).Should(ContainSubstring(`\"hit access phase\"`)) - - //clean log - base.CleanAPISIXErrorLog() - }) -}) diff --git a/api/test/e2e/route/route_with_uri_uris_test.go b/api/test/e2e/route/route_with_uri_uris_test.go deleted file mode 100644 index fd5a2daa75..0000000000 --- a/api/test/e2e/route/route_with_uri_uris_test.go +++ /dev/null @@ -1,126 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one or more - * contributor license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright ownership. - * The ASF licenses this file to You under the Apache License, Version 2.0 - * (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package route_test - -import ( - "net/http" - - . "github.com/onsi/ginkgo/v2" - - "github.com/apisix/manager-api/test/e2e/base" -) - -var _ = DescribeTable("test route with valid uri uris", - func(tc base.HttpTestCase) { - base.RunTestCase(tc) - }, - Entry("add route with valid uri", base.HttpTestCase{ - Object: base.ManagerApiExpect(), - Path: "/apisix/admin/routes/r1", - Method: http.MethodPut, - Body: `{ - "name": "route1", - "uri": "/hello", - "upstream": { - "nodes": { - "` + base.UpstreamIp + `:1980": 1 - }, - "type": "roundrobin" - } - }`, - Headers: map[string]string{"Authorization": base.GetToken()}, - ExpectStatus: http.StatusOK, - }), - Entry("hit the route (r1)", base.HttpTestCase{ - Object: base.APISIXExpect(), - Method: http.MethodGet, - Path: "/hello", - Headers: map[string]string{"Authorization": base.GetToken()}, - ExpectStatus: http.StatusOK, - ExpectBody: "hello world", - Sleep: base.SleepTime, - }), - Entry("delete the route (r1)", base.HttpTestCase{ - Object: base.ManagerApiExpect(), - Method: http.MethodDelete, - Path: "/apisix/admin/routes/r1", - Headers: map[string]string{"Authorization": base.GetToken()}, - ExpectStatus: http.StatusOK, - Sleep: base.SleepTime, - }), - Entry("hit the route just delete", base.HttpTestCase{ - Object: base.APISIXExpect(), - Method: http.MethodGet, - Path: "/hello", - Headers: map[string]string{"Authorization": base.GetToken()}, - ExpectStatus: http.StatusNotFound, - ExpectBody: `{"error_msg":"404 Route Not Found"}`, - Sleep: base.SleepTime, - }), - Entry("add route with valid uris", base.HttpTestCase{ - Object: base.ManagerApiExpect(), - Method: http.MethodPut, - Path: "/apisix/admin/routes/r1", - Body: `{ - "name": "route1", - "uris": ["/hello","/status"], - "upstream": { - "type": "roundrobin", - "nodes": { - "` + base.UpstreamIp + `:1980": 1 - } - } - }`, - Headers: map[string]string{"Authorization": base.GetToken()}, - ExpectStatus: http.StatusOK, - }), - Entry("hit the route (/hello)", base.HttpTestCase{ - Object: base.APISIXExpect(), - Method: http.MethodGet, - Path: "/hello", - Headers: map[string]string{"Authorization": base.GetToken()}, - ExpectStatus: http.StatusOK, - ExpectBody: "hello world", - Sleep: base.SleepTime, - }), - Entry("hit the route (/status)", base.HttpTestCase{ - Object: base.APISIXExpect(), - Method: http.MethodGet, - Path: "/status", - Headers: map[string]string{"Authorization": base.GetToken()}, - ExpectStatus: http.StatusOK, - ExpectBody: "ok", - Sleep: base.SleepTime, - }), - Entry("delete the route (r1)", base.HttpTestCase{ - Object: base.ManagerApiExpect(), - Method: http.MethodDelete, - Path: "/apisix/admin/routes/r1", - Headers: map[string]string{"Authorization": base.GetToken()}, - ExpectStatus: http.StatusOK, - Sleep: base.SleepTime, - }), - Entry("hit the route just delete", base.HttpTestCase{ - Object: base.APISIXExpect(), - Method: http.MethodGet, - Path: "/hello", - Headers: map[string]string{"Authorization": base.GetToken()}, - ExpectStatus: http.StatusNotFound, - ExpectBody: `{"error_msg":"404 Route Not Found"}`, - Sleep: base.SleepTime, - }), -) diff --git a/api/test/e2e/route/route_with_vars_test.go b/api/test/e2e/route/route_with_vars_test.go deleted file mode 100644 index 04ec226847..0000000000 --- a/api/test/e2e/route/route_with_vars_test.go +++ /dev/null @@ -1,368 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one or more - * contributor license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright ownership. - * The ASF licenses this file to You under the Apache License, Version 2.0 - * (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package route_test - -import ( - "encoding/json" - "net/http" - - . "github.com/onsi/ginkgo/v2" - . "github.com/onsi/gomega" - - "github.com/apisix/manager-api/test/e2e/base" -) - -var upstream map[string]interface{} = map[string]interface{}{ - "type": "roundrobin", - "nodes": []map[string]interface{}{ - { - "host": base.UpstreamIp, - "port": 1980, - "weight": 1, - }, - }, -} - -var _ = Describe("test route with vars (args)", func() { - It("add route with vars (args)", func() { - var createRouteBody map[string]interface{} = map[string]interface{}{ - "name": "route1", - "uri": "/hello", - "vars": [][]string{ - {"arg_name", "==", "aaa"}, - }, - "upstream": upstream, - } - _createRouteBody, err := json.Marshal(createRouteBody) - Expect(err).To(BeNil()) - base.RunTestCase(base.HttpTestCase{ - Object: base.ManagerApiExpect(), - Method: http.MethodPut, - Path: "/apisix/admin/routes/r1", - Body: string(_createRouteBody), - Headers: map[string]string{"Authorization": base.GetToken()}, - ExpectStatus: http.StatusOK, - }) - }) - It("hit the route with right args", func() { - base.RunTestCase(base.HttpTestCase{ - Object: base.APISIXExpect(), - Method: http.MethodGet, - Path: `/hello`, - Query: "name=aaa", - ExpectStatus: http.StatusOK, - ExpectBody: "hello world", - Sleep: base.SleepTime, - }) - }) - It("hit the route with wrong args", func() { - base.RunTestCase(base.HttpTestCase{ - Object: base.APISIXExpect(), - Method: http.MethodGet, - Path: `/hello`, - Query: "name=bbb", - ExpectStatus: http.StatusNotFound, - ExpectBody: `{"error_msg":"404 Route Not Found"}`, - Sleep: base.SleepTime, - }) - }) - It("hit the route with no args", func() { - base.RunTestCase(base.HttpTestCase{ - Object: base.APISIXExpect(), - Method: http.MethodGet, - Path: `/hello`, - ExpectStatus: http.StatusNotFound, - ExpectBody: `{"error_msg":"404 Route Not Found"}`, - Sleep: base.SleepTime, - }) - }) - It("update route with vars (header)", func() { - var createRouteBody map[string]interface{} = map[string]interface{}{ - "name": "route1", - "uri": "/hello", - "vars": [][]string{ - {"http_k", "==", "header"}, - }, - "upstream": upstream, - } - _createRouteBody, err := json.Marshal(createRouteBody) - Expect(err).To(BeNil()) - base.RunTestCase(base.HttpTestCase{ - Object: base.ManagerApiExpect(), - Method: http.MethodPut, - Path: "/apisix/admin/routes/r1", - Body: string(_createRouteBody), - Headers: map[string]string{"Authorization": base.GetToken()}, - ExpectStatus: http.StatusOK, - }) - }) - It("hit the route with right header", func() { - base.RunTestCase(base.HttpTestCase{ - Object: base.APISIXExpect(), - Method: http.MethodGet, - Headers: map[string]string{"k": "header"}, - Path: `/hello`, - ExpectStatus: http.StatusOK, - ExpectBody: "hello world", - Sleep: base.SleepTime, - }) - }) - It("hit the route with wrong header", func() { - base.RunTestCase(base.HttpTestCase{ - Object: base.APISIXExpect(), - Method: http.MethodGet, - Headers: map[string]string{"k": "jack"}, - Path: `/hello`, - ExpectStatus: http.StatusNotFound, - ExpectBody: `{"error_msg":"404 Route Not Found"}`, - Sleep: base.SleepTime, - }) - }) - It("hit the route with no header", func() { - base.RunTestCase(base.HttpTestCase{ - Object: base.APISIXExpect(), - Method: http.MethodGet, - Path: `/hello`, - ExpectStatus: http.StatusNotFound, - ExpectBody: `{"error_msg":"404 Route Not Found"}`, - Sleep: base.SleepTime, - }) - }) - It("update route with vars (cookie)", func() { - var createRouteBody map[string]interface{} = map[string]interface{}{ - "name": "route1", - "uri": "/hello", - "vars": [][]string{ - {"http_cookie", "==", "_octo=GH1.1.572248189.1598928545; _device_id=2c1a1a52074e66a3a008e4b73c690500; logged_in=yes;"}, - }, - "upstream": upstream, - } - _createRouteBody, err := json.Marshal(createRouteBody) - Expect(err).To(BeNil()) - base.RunTestCase(base.HttpTestCase{ - Object: base.ManagerApiExpect(), - Method: http.MethodPut, - Path: "/apisix/admin/routes/r1", - Body: string(_createRouteBody), - Headers: map[string]string{"Authorization": base.GetToken()}, - ExpectStatus: http.StatusOK, - }) - }) - It("hit the route with right Cookie", func() { - base.RunTestCase(base.HttpTestCase{ - Object: base.APISIXExpect(), - Method: http.MethodGet, - Headers: map[string]string{"Cookie": "_octo=GH1.1.572248189.1598928545; _device_id=2c1a1a52074e66a3a008e4b73c690500; logged_in=yes;"}, - Path: `/hello`, - ExpectStatus: http.StatusOK, - ExpectBody: "hello world", - Sleep: base.SleepTime, - }) - }) - It("hit the route with wrong Cookie", func() { - base.RunTestCase(base.HttpTestCase{ - Object: base.APISIXExpect(), - Method: http.MethodGet, - Headers: map[string]string{"Cookie": "jack"}, - Path: `/hello`, - ExpectStatus: http.StatusNotFound, - ExpectBody: `{"error_msg":"404 Route Not Found"}`, - Sleep: base.SleepTime, - }) - }) - It("hit the route with no Cookie", func() { - base.RunTestCase(base.HttpTestCase{ - Object: base.APISIXExpect(), - Method: http.MethodGet, - Path: `/hello`, - ExpectStatus: http.StatusNotFound, - ExpectBody: `{"error_msg":"404 Route Not Found"}`, - Sleep: base.SleepTime, - }) - }) - It("delete route", func() { - base.RunTestCase(base.HttpTestCase{ - Object: base.ManagerApiExpect(), - Method: http.MethodDelete, - Path: "/apisix/admin/routes/r1", - Headers: map[string]string{"Authorization": base.GetToken()}, - ExpectStatus: http.StatusOK, - Sleep: base.SleepTime, - }) - }) - It("hit the route just delete", func() { - base.RunTestCase(base.HttpTestCase{ - Object: base.APISIXExpect(), - Method: http.MethodGet, - Headers: map[string]string{"Cookie": "_octo=GH1.1.572248189.1598928545; _device_id=2c1a1a52074e66a3a008e4b73c690500; logged_in=yes;"}, - Path: `/hello`, - ExpectStatus: http.StatusNotFound, - ExpectBody: `{"error_msg":"404 Route Not Found"}`, - Sleep: base.SleepTime, - }) - }) -}) - -var _ = Describe("test route with multiple vars (args, cookie and header)", func() { - It("add route with multiple vars (args, cookie and header)", func() { - var createRouteBody map[string]interface{} = map[string]interface{}{ - "name": "route1", - "uri": "/hello", - "vars": [][]string{ - {"http_cookie", "==", "_octo=GH1.1.572248189.1598928545; _device_id=2c1a1a52074e66a3a008e4b73c690500; logged_in=yes;"}, - {"http_k", "==", "header"}, - {"arg_name", "==", "aaa"}, - }, - "upstream": upstream, - } - _createRouteBody, err := json.Marshal(createRouteBody) - Expect(err).To(BeNil()) - base.RunTestCase(base.HttpTestCase{ - Object: base.ManagerApiExpect(), - Method: http.MethodPut, - Path: "/apisix/admin/routes/r1", - Body: string(_createRouteBody), - Headers: map[string]string{"Authorization": base.GetToken()}, - ExpectStatus: http.StatusOK, - }) - }) - It("hit the route with right parameters", func() { - base.RunTestCase(base.HttpTestCase{ - Object: base.APISIXExpect(), - Method: http.MethodGet, - Headers: map[string]string{"k": "header", "Cookie": "_octo=GH1.1.572248189.1598928545; _device_id=2c1a1a52074e66a3a008e4b73c690500; logged_in=yes;"}, - Path: `/hello`, - Query: "name=aaa", - ExpectStatus: http.StatusOK, - ExpectBody: "hello world", - Sleep: base.SleepTime, - }) - }) - It("hit the route with wrong arg", func() { - base.RunTestCase(base.HttpTestCase{ - Object: base.APISIXExpect(), - Method: http.MethodGet, - Headers: map[string]string{"k": "header", "Cookie": "_octo=GH1.1.572248189.1598928545; _device_id=2c1a1a52074e66a3a008e4b73c690500; logged_in=yes;"}, - Path: `/hello`, - ExpectStatus: http.StatusNotFound, - ExpectBody: `{"error_msg":"404 Route Not Found"}`, - Sleep: base.SleepTime, - }) - }) - It("hit the route with wrong arg", func() { - base.RunTestCase(base.HttpTestCase{ - Desc: "hit the route with wrong header", - Object: base.APISIXExpect(), - Method: http.MethodGet, - Headers: map[string]string{"k": "test", "Cookie": "_octo=GH1.1.572248189.1598928545; _device_id=2c1a1a52074e66a3a008e4b73c690500; logged_in=yes;"}, - Path: `/hello`, - Query: "name=aaa", - ExpectStatus: http.StatusNotFound, - ExpectBody: `{"error_msg":"404 Route Not Found"}`, - Sleep: base.SleepTime, - }) - }) - It("hit the route with wrong cookie", func() { - base.RunTestCase(base.HttpTestCase{ - Object: base.APISIXExpect(), - Method: http.MethodGet, - Headers: map[string]string{"k": "header", "Cookie": "_octo=GH1.1.572248189.1598928545; logged_in=yes;"}, - Path: `/hello`, - Query: "name=aaa", - ExpectStatus: http.StatusNotFound, - ExpectBody: `{"error_msg":"404 Route Not Found"}`, - Sleep: base.SleepTime, - }) - }) - It("delete route", func() { - base.RunTestCase(base.HttpTestCase{ - Object: base.ManagerApiExpect(), - Method: http.MethodDelete, - Path: "/apisix/admin/routes/r1", - Headers: map[string]string{"Authorization": base.GetToken()}, - ExpectStatus: http.StatusOK, - Sleep: base.SleepTime, - }) - }) - It("hit the route just delete", func() { - base.RunTestCase(base.HttpTestCase{ - Object: base.APISIXExpect(), - Method: http.MethodGet, - Headers: map[string]string{"k": "header", "Cookie": "_octo=GH1.1.572248189.1598928545; _device_id=2c1a1a52074e66a3a008e4b73c690500; logged_in=yes;"}, - Path: `/hello`, - Query: "name=aaa", - ExpectStatus: http.StatusNotFound, - ExpectBody: `{"error_msg":"404 Route Not Found"}`, - Sleep: base.SleepTime, - }) - }) -}) - -var _ = Describe("test route with vars (args is digital)", func() { - It("add route with vars (args is digital)", func() { - var createRouteBody map[string]interface{} = map[string]interface{}{ - "name": "route1", - "uri": "/hello", - "vars": [][]string{ - {"arg_name", "==", "111"}, - }, - "upstream": upstream, - } - _createRouteBody, err := json.Marshal(createRouteBody) - Expect(err).To(BeNil()) - base.RunTestCase(base.HttpTestCase{ - Object: base.ManagerApiExpect(), - Method: http.MethodPut, - Path: "/apisix/admin/routes/r1", - Body: string(_createRouteBody), - Headers: map[string]string{"Authorization": base.GetToken()}, - ExpectStatus: http.StatusOK, - }) - }) - It("verify route", func() { - base.RunTestCase(base.HttpTestCase{ - Object: base.APISIXExpect(), - Method: http.MethodGet, - Path: `/hello`, - Query: "name=111", - ExpectStatus: http.StatusOK, - ExpectBody: "hello world", - Sleep: base.SleepTime, - }) - }) - It("delete the route with vars (args is digital)", func() { - base.RunTestCase(base.HttpTestCase{ - Object: base.ManagerApiExpect(), - Method: http.MethodDelete, - Path: "/apisix/admin/routes/r1", - Headers: map[string]string{"Authorization": base.GetToken()}, - ExpectStatus: http.StatusOK, - Sleep: base.SleepTime, - }) - }) - It("hit route just delete", func() { - base.RunTestCase(base.HttpTestCase{ - Object: base.APISIXExpect(), - Method: http.MethodGet, - Path: `/hello`, - Query: "name=111", - ExpectStatus: http.StatusNotFound, - ExpectBody: `{"error_msg":"404 Route Not Found"}`, - Sleep: base.SleepTime, - }) - }) -}) diff --git a/api/test/e2e/route_online_debug/route_online_debug_suite_test.go b/api/test/e2e/route_online_debug/route_online_debug_suite_test.go deleted file mode 100644 index 095324c154..0000000000 --- a/api/test/e2e/route_online_debug/route_online_debug_suite_test.go +++ /dev/null @@ -1,37 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one or more - * contributor license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright ownership. - * The ASF licenses this file to You under the Apache License, Version 2.0 - * (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package route_online_debug_test - -import ( - "testing" - "time" - - . "github.com/onsi/ginkgo/v2" - . "github.com/onsi/gomega" - - "github.com/apisix/manager-api/test/e2e/base" -) - -func TestRoute(t *testing.T) { - RegisterFailHandler(Fail) - //ginkgo.RunSpecs(t, "route online debug suite") -} - -var _ = BeforeSuite(func() { - base.CleanAllResource() - time.Sleep(base.SleepTime) -}) diff --git a/api/test/e2e/route_online_debug/route_online_debug_test.go b/api/test/e2e/route_online_debug/route_online_debug_test.go deleted file mode 100644 index 5d6219e00c..0000000000 --- a/api/test/e2e/route_online_debug/route_online_debug_test.go +++ /dev/null @@ -1,876 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one or more - * contributor license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright ownership. - * The ASF licenses this file to You under the Apache License, Version 2.0 - * (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package route_online_debug_test - -import ( - "encoding/json" - "io/ioutil" - "net/http" - "path/filepath" - "time" - - . "github.com/onsi/ginkgo/v2" - . "github.com/onsi/gomega" - "github.com/tidwall/gjson" - - "github.com/apisix/manager-api/test/e2e/base" -) - -var upstream map[string]interface{} = map[string]interface{}{ - "type": "roundrobin", - "nodes": []map[string]interface{}{ - { - "host": base.UpstreamIp, - "port": 1980, - "weight": 1, - }, - }, -} - -var _ = Describe("Route_Online_Debug_Route_Not_Exist", func() { - DescribeTable("Route_Online_Debug_Route_Not_Exist", - func(tc base.HttpTestCase) { - base.RunTestCase(tc) - }, - Entry("hit route that not exist", base.HttpTestCase{ - Object: base.APISIXExpect(), - Method: http.MethodGet, - Path: "/hello_", - ExpectStatus: http.StatusNotFound, - ExpectBody: "{\"error_msg\":\"404 Route Not Found\"}\n", - }), - Entry("online debug route with query params", base.HttpTestCase{ - Object: base.ManagerApiExpect(), - Method: http.MethodPost, - Path: "/apisix/admin/debug-request-forwarding", - Headers: map[string]string{ - "Authorization": base.GetToken(), - "online_debug_url": base.APISIXInternalUrl + `/hello_`, - "online_debug_request_protocol": "http", - "online_debug_method": "GET", - "Content-Type": "multipart/form-data", - "online_debug_header_params": `{"test":["test1"]}`, - }, - ExpectStatus: http.StatusOK, - ExpectBody: `{"code":0,"message":"","data":{"code":404,"header":{"Connection":["keep-alive"],"Content-Type":["text/plain; charset=utf-8"]`, - Sleep: base.SleepTime, - }), - ) -}) - -var _ = Describe("Route_Online_Debug_Route_With_Query_Params", func() { - It("hit route that not exist", func() { - base.RunTestCase(base.HttpTestCase{ - Object: base.APISIXExpect(), - Method: http.MethodGet, - Path: "/hello", - ExpectStatus: http.StatusNotFound, - ExpectBody: "{\"error_msg\":\"404 Route Not Found\"}\n", - }) - }) - It("create route with query params", func() { - var routeBody map[string]interface{} = map[string]interface{}{ - "name": "route1", - "uri": "/hello", - "methods": []string{"GET"}, - "vars": []interface{}{ - []string{"arg_name", "==", "aaa"}, - }, - "upstream": upstream, - } - _routeBody, err := json.Marshal(routeBody) - Expect(err).To(BeNil()) - base.RunTestCase(base.HttpTestCase{ - Object: base.ManagerApiExpect(), - Method: http.MethodPut, - Path: "/apisix/admin/routes/r1", - Body: string(_routeBody), - Headers: map[string]string{"Authorization": base.GetToken()}, - ExpectStatus: http.StatusOK, - }) - }) - It("online debug route with query params", func() { - base.RunTestCase(base.HttpTestCase{ - Object: base.ManagerApiExpect(), - Method: http.MethodPost, - Path: "/apisix/admin/debug-request-forwarding", - Headers: map[string]string{ - "Authorization": base.GetToken(), - "online_debug_url": base.APISIXInternalUrl + `/hello?name=aaa`, - "online_debug_request_protocol": "http", - "online_debug_method": "GET", - "Content-Type": "multipart/form-data", - "online_debug_header_params": `{"test":["test1"]}`, - }, - ExpectStatus: http.StatusOK, - ExpectBody: `{"code":0,"message":"","data":{"code":200,"header":{"Connection":["keep-alive"],"Content-Type":["application/octet-stream"],`, - Sleep: base.SleepTime, - }) - }) - It("delete the route just created", func() { - base.RunTestCase(base.HttpTestCase{ - Object: base.ManagerApiExpect(), - Method: http.MethodDelete, - Path: "/apisix/admin/routes/r1", - Headers: map[string]string{"Authorization": base.GetToken()}, - ExpectStatus: http.StatusOK, - }) - }) - It("hit the route just deleted", func() { - base.RunTestCase(base.HttpTestCase{ - Object: base.APISIXExpect(), - Method: http.MethodGet, - Path: "/hello", - ExpectStatus: http.StatusNotFound, - ExpectBody: "{\"error_msg\":\"404 Route Not Found\"}\n", - Sleep: base.SleepTime, - }) - }) -}) - -var _ = Describe("Route_Online_Debug_Route_With_Header_Params", func() { - It("hit route that not exist", func() { - base.RunTestCase(base.HttpTestCase{ - Object: base.APISIXExpect(), - Method: http.MethodGet, - Path: "/hello", - ExpectStatus: http.StatusNotFound, - ExpectBody: "{\"error_msg\":\"404 Route Not Found\"}\n", - }) - }) - It("create route with header params", func() { - var routeBody map[string]interface{} = map[string]interface{}{ - "name": "route1", - "uri": "/hello", - "methods": []string{"GET"}, - "vars": []interface{}{ - []string{"http_version", "==", "v2"}, - }, - "upstream": upstream, - } - _reqRouteBody, err := json.Marshal(routeBody) - Expect(err).To(BeNil()) - base.RunTestCase(base.HttpTestCase{ - Object: base.ManagerApiExpect(), - Method: http.MethodPut, - Path: "/apisix/admin/routes/r1", - Body: string(_reqRouteBody), - Headers: map[string]string{"Authorization": base.GetToken()}, - ExpectStatus: http.StatusOK, - Sleep: base.SleepTime, - }) - }) - It("online debug route with header params", func() { - base.RunTestCase(base.HttpTestCase{ - Object: base.ManagerApiExpect(), - Method: http.MethodPost, - Path: "/apisix/admin/debug-request-forwarding", - Headers: map[string]string{ - "Authorization": base.GetToken(), - "online_debug_url": base.APISIXInternalUrl + `/hello`, - "online_debug_request_protocol": "http", - "online_debug_method": "GET", - "Content-Type": "multipart/form-data", - "online_debug_header_params": `{"test":["test1"],"version":["v2"]}`, - }, - ExpectStatus: http.StatusOK, - ExpectBody: `{"code":0,"message":"","data":{"code":200,"header":{"Connection":["keep-alive"],"Content-Type":["application/octet-stream"],`, - Sleep: base.SleepTime, - }) - }) - It("online debug route with header params(add Content-type to header params to create route)", func() { - var routeBody map[string]interface{} = map[string]interface{}{ - "name": "route2", - "status": 1, - "uri": "/hello_", - "methods": []string{"GET"}, - "upstream": upstream, - } - _reqRouteBody, err := json.Marshal(routeBody) - Expect(err).To(BeNil()) - base.RunTestCase(base.HttpTestCase{ - Object: base.ManagerApiExpect(), - Method: http.MethodPost, - Path: "/apisix/admin/debug-request-forwarding", - Body: string(_reqRouteBody), - Headers: map[string]string{ - "Authorization": base.GetToken(), - "online_debug_url": base.ManagerAPIHost + `/apisix/admin/routes/r2`, - "online_debug_request_protocol": "http", - "online_debug_method": http.MethodPut, - "Content-Type": "text/plain;charset=UTF-8", - "online_debug_header_params": `{"Content-type":["application/json"],"Authorization":["` + base.GetToken() + `"]}`, - }, - ExpectStatus: http.StatusOK, - ExpectBody: `{"code":0,"message":"","data":{"code":200,"header":{"Access-Control-Allow-Credentials":["true"],"Access-Control-Allow-Headers":["Authorization"],"Access-Control-Allow-Methods":["*"],"Access-Control-Allow-Origin":["*"],"Content-Length":["296"],"Content-Type":["application/json"]`, - Sleep: base.SleepTime, - }) - }) - It("hit the route (r2)", func() { - base.RunTestCase(base.HttpTestCase{ - Object: base.APISIXExpect(), - Method: http.MethodGet, - Path: "/hello_", - ExpectStatus: http.StatusOK, - ExpectBody: "hello world\n", - Sleep: base.SleepTime, - }) - }) - It("delete the route just created (r1)", func() { - base.RunTestCase(base.HttpTestCase{ - Object: base.ManagerApiExpect(), - Method: http.MethodDelete, - Path: "/apisix/admin/routes/r1", - Headers: map[string]string{"Authorization": base.GetToken()}, - ExpectStatus: http.StatusOK, - }) - }) - It("delete the route just created (r2)", func() { - base.RunTestCase(base.HttpTestCase{ - Object: base.ManagerApiExpect(), - Method: http.MethodDelete, - Path: "/apisix/admin/routes/r2", - Headers: map[string]string{"Authorization": base.GetToken()}, - ExpectStatus: http.StatusOK, - }) - }) - It("hit the route just deleted (r1)", func() { - base.RunTestCase(base.HttpTestCase{ - Object: base.APISIXExpect(), - Method: http.MethodGet, - Path: "/hello", - ExpectStatus: http.StatusNotFound, - ExpectBody: "{\"error_msg\":\"404 Route Not Found\"}\n", - Sleep: base.SleepTime, - }) - }) - It("hit the route just deleted (r2)", func() { - base.RunTestCase(base.HttpTestCase{ - Object: base.APISIXExpect(), - Method: http.MethodGet, - Path: "/hello_", - ExpectStatus: http.StatusNotFound, - ExpectBody: "{\"error_msg\":\"404 Route Not Found\"}\n", - Sleep: base.SleepTime, - }) - }) -}) - -var _ = Describe("Route_Online_Debug_Route_With_Body_Params", func() { - It("hit route that not exist", func() { - base.RunTestCase(base.HttpTestCase{ - Object: base.APISIXExpect(), - Method: http.MethodGet, - Path: "/hello", - ExpectStatus: http.StatusNotFound, - ExpectBody: "{\"error_msg\":\"404 Route Not Found\"}\n", - }) - }) - It("create route with method POST", func() { - var routeBody map[string]interface{} = map[string]interface{}{ - "name": "route1", - "uri": "/hello", - "methods": []string{"POST"}, - "upstream": upstream, - } - _reqRouteBody, err := json.Marshal(routeBody) - Expect(err).To(BeNil()) - base.RunTestCase(base.HttpTestCase{ - Object: base.ManagerApiExpect(), - Method: http.MethodPut, - Path: "/apisix/admin/routes/r1", - Body: string(_reqRouteBody), - Headers: map[string]string{"Authorization": base.GetToken()}, - ExpectStatus: http.StatusOK, - Sleep: base.SleepTime, - }) - }) - It("online debug route with body params", func() { - base.RunTestCase(base.HttpTestCase{ - Object: base.ManagerApiExpect(), - Method: http.MethodPost, - Path: "/apisix/admin/debug-request-forwarding", - Body: `{ - "name": "test", - "desc": "online debug route with body params" - }`, - Headers: map[string]string{ - "Authorization": base.GetToken(), - "online_debug_url": base.APISIXInternalUrl + `/hello`, - "online_debug_request_protocol": "http", - "online_debug_method": http.MethodPost, - "Content-Type": "application/json", - "online_debug_header_params": `{"test":["test1"]}`, - }, - ExpectStatus: http.StatusOK, - ExpectBody: `{"code":0,"message":"","data":{"code":200,"header":{"Connection":["keep-alive"],"Content-Type":["application/octet-stream"]`, - Sleep: base.SleepTime, - }) - }) - It("delete the route just created", func() { - base.RunTestCase(base.HttpTestCase{ - Object: base.ManagerApiExpect(), - Method: http.MethodDelete, - Path: "/apisix/admin/routes/r1", - Headers: map[string]string{"Authorization": base.GetToken()}, - ExpectStatus: http.StatusOK, - }) - }) - It("hit the route just deleted", func() { - base.RunTestCase(base.HttpTestCase{ - Object: base.APISIXExpect(), - Method: http.MethodGet, - Path: "/hello", - ExpectStatus: http.StatusNotFound, - ExpectBody: "{\"error_msg\":\"404 Route Not Found\"}\n", - Sleep: base.SleepTime, - }) - }) -}) -var _ = Describe("Route_Online_Debug_Route_With_Basic_Auth", func() { - It("hit route that not exist", func() { - base.RunTestCase(base.HttpTestCase{ - Object: base.APISIXExpect(), - Method: http.MethodGet, - Path: "/hello", - ExpectStatus: http.StatusNotFound, - ExpectBody: "{\"error_msg\":\"404 Route Not Found\"}\n", - }) - }) - It("create route enable basic-auth plugin", func() { - var routeBody map[string]interface{} = map[string]interface{}{ - "name": "route1", - "uri": "/hello", - "plugins": map[string]interface{}{ - "basic-auth": map[string]interface{}{}, - }, - "methods": []string{"GET"}, - "upstream": upstream, - } - _reqRouteBody, err := json.Marshal(routeBody) - Expect(err).To(BeNil()) - base.RunTestCase(base.HttpTestCase{ - Object: base.ManagerApiExpect(), - Method: http.MethodPut, - Path: "/apisix/admin/routes/r1", - Body: string(_reqRouteBody), - Headers: map[string]string{"Authorization": base.GetToken()}, - ExpectStatus: http.StatusOK, - Sleep: base.SleepTime, - }) - }) - It("make sure the consumer is not created", func() { - base.RunTestCase(base.HttpTestCase{ - Object: base.ManagerApiExpect(), - Method: http.MethodGet, - Path: "/apisix/admin/consumers/jack", - Headers: map[string]string{"Authorization": base.GetToken()}, - ExpectStatus: http.StatusNotFound, - }) - }) - It("create consumer", func() { - base.RunTestCase(base.HttpTestCase{ - Object: base.ManagerApiExpect(), - Method: http.MethodPut, - Path: "/apisix/admin/consumers", - Body: `{ - "username": "jack", - "plugins": { - "basic-auth": { - "disable": false, - "username": "jack", - "password": "123456" - } - }, - "desc": "test description" - }`, - Headers: map[string]string{"Authorization": base.GetToken()}, - ExpectStatus: http.StatusOK, - Sleep: base.SleepTime, - }) - }) - It("online debug with basic-auth", func() { - base.RunTestCase(base.HttpTestCase{ - Object: base.ManagerApiExpect(), - Method: http.MethodPost, - Path: "/apisix/admin/debug-request-forwarding", - Headers: map[string]string{ - "Authorization": base.GetToken(), - "online_debug_url": base.APISIXInternalUrl + `/hello`, - "online_debug_request_protocol": "http", - "online_debug_method": "GET", - "Content-Type": "multipart/form-data", - "online_debug_header_params": `{"test":["test1"],"Authorization": ["Basic amFjazoxMjM0NTYKIA=="]}`, - }, - ExpectStatus: http.StatusOK, - ExpectBody: `{"code":0,"message":"","data":{"code":200,"header":{"Connection":["keep-alive"],"Content-Type":["application/octet-stream"]`, - }) - }) - It("online debug without basic-auth", func() { - base.RunTestCase(base.HttpTestCase{ - Object: base.ManagerApiExpect(), - Method: http.MethodPost, - Path: "/apisix/admin/debug-request-forwarding", - Headers: map[string]string{ - "Authorization": base.GetToken(), - "online_debug_url": base.APISIXInternalUrl + `/hello`, - "online_debug_request_protocol": "http", - "online_debug_method": "GET", - "Content-Type": "multipart/form-data", - "online_debug_header_params": `{"test":["test1"]}`, - }, - ExpectStatus: http.StatusOK, - ExpectBody: `{"code":0,"message":"","data":{"code":401,"header":{"Connection":["keep-alive"],"Content-Type":["text/plain; charset=utf-8"],`, - }) - }) - It("delete the route just created", func() { - base.RunTestCase(base.HttpTestCase{ - Object: base.ManagerApiExpect(), - Method: http.MethodDelete, - Path: "/apisix/admin/routes/r1", - Headers: map[string]string{"Authorization": base.GetToken()}, - ExpectStatus: http.StatusOK, - }) - }) - It("hit the route just deleted", func() { - base.RunTestCase(base.HttpTestCase{ - Object: base.APISIXExpect(), - Method: http.MethodGet, - Path: "/hello", - ExpectStatus: http.StatusNotFound, - ExpectBody: "{\"error_msg\":\"404 Route Not Found\"}\n", - Sleep: base.SleepTime, - }) - }) - It("delete consumer", func() { - base.RunTestCase(base.HttpTestCase{ - Object: base.ManagerApiExpect(), - Method: http.MethodDelete, - Path: "/apisix/admin/consumers/jack", - Headers: map[string]string{"Authorization": base.GetToken()}, - ExpectStatus: http.StatusOK, - }) - }) -}) - -var _ = Describe("Route_Online_Debug_Route_With_Key_Auth", func() { - It("hit route that not exist", func() { - base.RunTestCase(base.HttpTestCase{ - Object: base.APISIXExpect(), - Method: http.MethodGet, - Path: "/hello", - ExpectStatus: http.StatusNotFound, - ExpectBody: "{\"error_msg\":\"404 Route Not Found\"}\n", - }) - }) - It("create route enable key-auth plugin", func() { - var routeBody map[string]interface{} = map[string]interface{}{ - "name": "route1", - "uri": "/hello", - "plugins": map[string]interface{}{ - "key-auth": map[string]interface{}{}, - }, - "methods": []string{"GET"}, - "upstream": upstream, - } - _reqRouteBody, err := json.Marshal(routeBody) - Expect(err).To(BeNil()) - base.RunTestCase(base.HttpTestCase{ - Object: base.ManagerApiExpect(), - Method: http.MethodPut, - Path: "/apisix/admin/routes/r1", - Body: string(_reqRouteBody), - Headers: map[string]string{"Authorization": base.GetToken()}, - ExpectStatus: http.StatusOK, - Sleep: base.SleepTime, - }) - }) - It("make sure the consumer is not created", func() { - base.RunTestCase(base.HttpTestCase{ - Object: base.ManagerApiExpect(), - Method: http.MethodGet, - Path: "/apisix/admin/consumers/jack", - Headers: map[string]string{"Authorization": base.GetToken()}, - ExpectStatus: http.StatusNotFound, - }) - }) - It("create consumer", func() { - base.RunTestCase(base.HttpTestCase{ - Object: base.ManagerApiExpect(), - Method: http.MethodPut, - Path: "/apisix/admin/consumers", - Body: `{ - "username": "jack", - "plugins": { - "key-auth": { - "key": "user-key" - } - }, - "desc": "test description" - }`, - Headers: map[string]string{"Authorization": base.GetToken()}, - ExpectStatus: http.StatusOK, - }) - }) - It("online debug with key-auth", func() { - base.RunTestCase(base.HttpTestCase{ - Object: base.ManagerApiExpect(), - Method: http.MethodPost, - Path: "/apisix/admin/debug-request-forwarding", - Headers: map[string]string{ - "Authorization": base.GetToken(), - "online_debug_url": base.APISIXInternalUrl + `/hello`, - "online_debug_request_protocol": "http", - "online_debug_method": "GET", - "Content-Type": "multipart/form-data", - "online_debug_header_params": `{"test":["test1"],"apikey":["user-key"]}`, - }, - ExpectStatus: http.StatusOK, - ExpectBody: `{"code":0,"message":"","data":{"code":200,"header":{"Connection":["keep-alive"],"Content-Type":["application/octet-stream"],`, - }) - }) - It("online debug without key-auth", func() { - base.RunTestCase(base.HttpTestCase{ - Object: base.ManagerApiExpect(), - Method: http.MethodPost, - Path: "/apisix/admin/debug-request-forwarding", - Headers: map[string]string{ - "Authorization": base.GetToken(), - "online_debug_url": base.APISIXInternalUrl + `/hello`, - "online_debug_request_protocol": "http", - "online_debug_method": "GET", - "Content-Type": "multipart/form-data", - "online_debug_header_params": `{"test":["test1"]}`, - }, - ExpectStatus: http.StatusOK, - ExpectBody: `{"code":0,"message":"","data":{"code":401,"header":{"Connection":["keep-alive"],"Content-Type":["text/plain; charset=utf-8"]`, - }) - }) - It("delete the route just created", func() { - base.RunTestCase(base.HttpTestCase{ - Object: base.ManagerApiExpect(), - Method: http.MethodDelete, - Path: "/apisix/admin/routes/r1", - Headers: map[string]string{"Authorization": base.GetToken()}, - ExpectStatus: http.StatusOK, - }) - }) - It("hit the route just deleted", func() { - base.RunTestCase(base.HttpTestCase{ - Object: base.APISIXExpect(), - Method: http.MethodGet, - Path: "/hello", - ExpectStatus: http.StatusNotFound, - ExpectBody: "{\"error_msg\":\"404 Route Not Found\"}\n", - Sleep: base.SleepTime, - }) - }) - It("delete consumer", func() { - base.RunTestCase(base.HttpTestCase{ - Object: base.ManagerApiExpect(), - Method: http.MethodDelete, - Path: "/apisix/admin/consumers/jack", - Headers: map[string]string{"Authorization": base.GetToken()}, - ExpectStatus: http.StatusOK, - }) - }) -}) - -var _ = Describe("Route_Online_Debug_Route_With_JWT_Auth", func() { - It("hit route that not exist", func() { - base.RunTestCase(base.HttpTestCase{ - Object: base.APISIXExpect(), - Method: http.MethodGet, - Path: "/hello", - ExpectStatus: http.StatusNotFound, - ExpectBody: "{\"error_msg\":\"404 Route Not Found\"}\n", - }) - }) - It("create route enable jwt-auth plugin", func() { - var routeBody map[string]interface{} = map[string]interface{}{ - "name": "route1", - "uri": "/hello", - "plugins": map[string]interface{}{ - "jwt-auth": map[string]interface{}{}, - }, - "methods": []string{"GET"}, - "upstream": upstream, - } - _reqRouteBody, err := json.Marshal(routeBody) - Expect(err).To(BeNil()) - base.RunTestCase(base.HttpTestCase{ - Object: base.ManagerApiExpect(), - Method: http.MethodPut, - Path: "/apisix/admin/routes/r1", - Body: string(_reqRouteBody), - Headers: map[string]string{"Authorization": base.GetToken()}, - ExpectStatus: http.StatusOK, - Sleep: base.SleepTime, - }) - }) - It("make sure the consumer is not created", func() { - base.RunTestCase(base.HttpTestCase{ - Object: base.ManagerApiExpect(), - Method: http.MethodGet, - Path: "/apisix/admin/consumers/jack", - Headers: map[string]string{"Authorization": base.GetToken()}, - ExpectStatus: http.StatusNotFound, - }) - }) - It("create consumer", func() { - base.RunTestCase(base.HttpTestCase{ - Object: base.ManagerApiExpect(), - Method: http.MethodPut, - Path: "/apisix/admin/consumers", - Body: `{ - "username": "jack", - "plugins": { - "jwt-auth": { - "key": "user-key", - "secret": "my-secret-key", - "algorithm": "HS256" - } - }, - "desc": "test description" - }`, - Headers: map[string]string{"Authorization": base.GetToken()}, - ExpectStatus: http.StatusOK, - }) - }) - It("online debug with JWT-auth", func() { - jsonStr := `{"test":["test1"]}` - var _headerParams map[string]interface{} - err := json.Unmarshal([]byte(jsonStr), &_headerParams) - Expect(err).To(BeNil()) - jwtToken := base.GetJwtToken("user-key") - l := []string{jwtToken} - _headerParams["Authorization"] = l - headerParams, err := json.Marshal(_headerParams) - Expect(err).To(BeNil()) - base.RunTestCase(base.HttpTestCase{ - Object: base.ManagerApiExpect(), - Method: http.MethodPost, - Path: "/apisix/admin/debug-request-forwarding", - Headers: map[string]string{ - "Authorization": base.GetToken(), - "online_debug_url": base.APISIXInternalUrl + `/hello`, - "online_debug_request_protocol": "http", - "online_debug_method": "GET", - "Content-Type": "multipart/form-data", - "online_debug_header_params": string(headerParams), - }, - ExpectStatus: http.StatusOK, - ExpectBody: `{"code":0,"message":"","data":{"code":200,"header":{"Connection":["keep-alive"],"Content-Type":["application/octet-stream"],`, - }) - }) - It("online debug without JWT-auth", func() { - base.RunTestCase(base.HttpTestCase{ - Object: base.ManagerApiExpect(), - Method: http.MethodPost, - Path: "/apisix/admin/debug-request-forwarding", - Headers: map[string]string{ - "Authorization": base.GetToken(), - "online_debug_url": base.APISIXInternalUrl + `/hello`, - "online_debug_request_protocol": "http", - "online_debug_method": "GET", - "Content-Type": "multipart/form-data", - "online_debug_header_params": `{"test":["test1"]}`, - }, - ExpectStatus: http.StatusOK, - ExpectBody: `{"code":0,"message":"","data":{"code":401,"header":{"Connection":["keep-alive"],"Content-Type":["text/plain; charset=utf-8"],`, - }) - }) - It("delete the route just created", func() { - base.RunTestCase(base.HttpTestCase{ - Object: base.ManagerApiExpect(), - Method: http.MethodDelete, - Path: "/apisix/admin/routes/r1", - Headers: map[string]string{"Authorization": base.GetToken()}, - ExpectStatus: http.StatusOK, - }) - }) - It("hit the route just deleted", func() { - base.RunTestCase(base.HttpTestCase{ - Object: base.APISIXExpect(), - Method: http.MethodGet, - Path: "/hello", - ExpectStatus: http.StatusNotFound, - ExpectBody: "{\"error_msg\":\"404 Route Not Found\"}\n", - Sleep: base.SleepTime, - }) - }) - It("delete consumer", func() { - base.RunTestCase(base.HttpTestCase{ - Object: base.ManagerApiExpect(), - Method: http.MethodDelete, - Path: "/apisix/admin/consumers/jack", - Headers: map[string]string{"Authorization": base.GetToken()}, - ExpectStatus: http.StatusOK, - }) - }) -}) - -var _ = Describe("Route_Online_Debug_Route_With_Files", func() { - It("hit route that not exist", func() { - base.RunTestCase(base.HttpTestCase{ - Object: base.APISIXExpect(), - Method: http.MethodGet, - Path: "/hello", - ExpectStatus: http.StatusNotFound, - ExpectBody: "{\"error_msg\":\"404 Route Not Found\"}\n", - }) - }) - It("create route enable basic-auth plugin", func() { - var routeBody map[string]interface{} = map[string]interface{}{ - "name": "route1", - "uri": "/hello_", - "methods": []string{"POST"}, - "upstream": upstream, - } - _reqRouteBody, err := json.Marshal(routeBody) - Expect(err).To(BeNil()) - base.RunTestCase(base.HttpTestCase{ - Object: base.ManagerApiExpect(), - Method: http.MethodPut, - Path: "/apisix/admin/routes/r2", - Body: string(_reqRouteBody), - Headers: map[string]string{"Authorization": base.GetToken()}, - ExpectStatus: http.StatusOK, - Sleep: base.SleepTime, - }) - }) - It("online debug with file", func() { - path, err := filepath.Abs("../../testdata/import/default.yaml") - Expect(err).To(BeNil()) - files := []base.UploadFile{ - {Name: "file", Filepath: path}, - } - - headers := map[string]string{} - - jsonStr := `{"test":["test1"]}` - var _headerParams map[string]interface{} - err = json.Unmarshal([]byte(jsonStr), &_headerParams) - Expect(err).To(BeNil()) - l := []string{base.GetToken()} - _headerParams["Authorization"] = l - headerParams, err := json.Marshal(_headerParams) - Expect(err).To(BeNil()) - - basePath := base.ManagerAPIHost + "/apisix/admin/debug-request-forwarding" - requestBody, requestContentType, err := base.GetReader(headers, "multipart/form-data", files) - Expect(err).To(BeNil()) - httpRequest, err := http.NewRequest(http.MethodPost, basePath, requestBody) - Expect(err).To(BeNil()) - httpRequest.Header.Add("Content-Type", requestContentType) - httpRequest.Header.Add("Authorization", base.GetToken()) - httpRequest.Header.Add("online_debug_request_protocol", "http") - httpRequest.Header.Add("online_debug_url", base.ManagerAPIHost+`/apisix/admin/import/routes`) - httpRequest.Header.Add("online_debug_method", http.MethodPost) - httpRequest.Header.Add("online_debug_header_params", string(headerParams)) - client := &http.Client{} - resp, err := client.Do(httpRequest) - Expect(err).To(BeNil()) - - defer resp.Body.Close() - - respBody, err := ioutil.ReadAll(resp.Body) - Expect(err).To(BeNil()) - realBody := gjson.Get(string(respBody), "data") - // todo get successful result and compare - Expect(realBody.String()).Should(ContainSubstring(`"data":{"paths":1,"routes":1}`)) - }) - - It("verify the route just imported and delete data", func() { - time.Sleep(time.Duration(500) * time.Millisecond) - request, _ := http.NewRequest("GET", base.ManagerAPIHost+"/apisix/admin/routes", nil) - request.Header.Add("Authorization", base.GetToken()) - resp, err := http.DefaultClient.Do(request) - Expect(err).To(BeNil()) - defer resp.Body.Close() - respBody, _ := ioutil.ReadAll(resp.Body) - list := gjson.Get(string(respBody), "data.rows").Value().([]interface{}) - - var tests []base.HttpTestCase - for _, item := range list { - route := item.(map[string]interface{}) - tc := base.HttpTestCase{ - Desc: "route patch for update status(online)", - Object: base.ManagerApiExpect(), - Method: http.MethodPatch, - Path: "/apisix/admin/routes/" + route["id"].(string), - Body: `{"status":1}`, - Headers: map[string]string{"Authorization": base.GetToken()}, - ExpectStatus: http.StatusOK, - Sleep: base.SleepTime, - } - tests = append(tests, tc) - } - - // verify route - tests = append(tests, base.HttpTestCase{ - Desc: "verify the route just imported", - Object: base.APISIXExpect(), - Method: http.MethodGet, - Path: "/hello", - ExpectStatus: http.StatusOK, - ExpectBody: "hello world", - Sleep: base.SleepTime, - }) - - // delete test data - for _, item := range list { - route := item.(map[string]interface{}) - tc := base.HttpTestCase{ - Desc: "delete route", - Object: base.ManagerApiExpect(), - Method: http.MethodDelete, - Path: "/apisix/admin/routes/" + route["id"].(string), - Headers: map[string]string{"Authorization": base.GetToken()}, - ExpectStatus: http.StatusOK, - } - tests = append(tests, tc) - } - - for _, tc := range tests { - base.RunTestCase(tc) - } - - }) - - It("hit the route just deleted", func() { - base.RunTestCase(base.HttpTestCase{ - Object: base.APISIXExpect(), - Method: http.MethodGet, - Path: "/hello", - ExpectStatus: http.StatusNotFound, - ExpectBody: "{\"error_msg\":\"404 Route Not Found\"}\n", - Sleep: base.SleepTime, - }) - }) - - It("hit the route just deleted", func() { - base.RunTestCase(base.HttpTestCase{ - Object: base.APISIXExpect(), - Method: http.MethodGet, - Path: "/hello_", - ExpectStatus: http.StatusNotFound, - ExpectBody: "{\"error_msg\":\"404 Route Not Found\"}\n", - Sleep: base.SleepTime, - }) - }) -}) diff --git a/api/test/e2e/schema/plugin_test.go b/api/test/e2e/schema/plugin_test.go deleted file mode 100644 index 58b750558d..0000000000 --- a/api/test/e2e/schema/plugin_test.go +++ /dev/null @@ -1,53 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one or more - * contributor license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright ownership. - * The ASF licenses this file to You under the Apache License, Version 2.0 - * (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package schema_test - -import ( - "net/http" - - . "github.com/onsi/ginkgo/v2" - - "github.com/apisix/manager-api/test/e2e/base" -) - -var _ = Describe("Plugin List", func() { - DescribeTable("test plugin basic", - func(testCase base.HttpTestCase) { - base.RunTestCase(testCase) - }, - Entry("get all plugins", base.HttpTestCase{ - Object: base.ManagerApiExpect(), - Method: http.MethodGet, - Path: "/apisix/admin/plugins", - Query: "all=true", - Headers: map[string]string{"Authorization": base.GetToken()}, - ExpectStatus: http.StatusOK, - ExpectBody: []string{"request-id", "syslog", "echo", "proxy-mirror"}, - Sleep: base.SleepTime, - }), - Entry("get all plugins", base.HttpTestCase{ - Object: base.ManagerApiExpect(), - Method: http.MethodGet, - Path: "/apisix/admin/plugins", - Query: "all=false", - Headers: map[string]string{"Authorization": base.GetToken()}, - ExpectStatus: http.StatusOK, - ExpectBody: []string{"request-id", "syslog", "echo", "proxy-mirror"}, - Sleep: base.SleepTime, - }), - ) -}) diff --git a/api/test/e2e/schema/schema_suite_test.go b/api/test/e2e/schema/schema_suite_test.go deleted file mode 100644 index dc35b678b0..0000000000 --- a/api/test/e2e/schema/schema_suite_test.go +++ /dev/null @@ -1,36 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one or more - * contributor license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright ownership. - * The ASF licenses this file to You under the Apache License, Version 2.0 - * (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package schema_test - -import ( - "testing" - "time" - - "github.com/apisix/manager-api/test/e2e/base" - . "github.com/onsi/ginkgo/v2" - . "github.com/onsi/gomega" -) - -func TestSchema(t *testing.T) { - RegisterFailHandler(Fail) - RunSpecs(t, "Schema Suite") -} - -var _ = BeforeSuite(func() { - base.CleanAllResource() - time.Sleep(base.SleepTime) -}) diff --git a/api/test/e2e/schema/schema_test.go b/api/test/e2e/schema/schema_test.go deleted file mode 100644 index 960a69cd17..0000000000 --- a/api/test/e2e/schema/schema_test.go +++ /dev/null @@ -1,79 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one or more - * contributor license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright ownership. - * The ASF licenses this file to You under the Apache License, Version 2.0 - * (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package schema_test - -import ( - "net/http" - - . "github.com/onsi/ginkgo/v2" - - "github.com/apisix/manager-api/test/e2e/base" -) - -var _ = Describe("Schema Test", func() { - DescribeTable("test schema basic", - func(testCase base.HttpTestCase) { - base.RunTestCase(testCase) - }, - Entry("get consumer schema of plugin", base.HttpTestCase{ - Object: base.ManagerApiExpect(), - Method: http.MethodGet, - Path: "/apisix/admin/schema/plugins/jwt-auth", - Query: "schema_type=consumer", - Headers: map[string]string{"Authorization": base.GetToken()}, - ExpectStatus: http.StatusOK, - ExpectBody: `{"dependencies":{"algorithm":{"oneOf":[{"properties":{"algorithm":{"default":"HS256","enum":["HS256","HS512"]}}},{"properties":{"algorithm":{"enum":["ES256","RS256"]},"private_key":{"type":"string"},"public_key":{"type":"string"}},"required":["private_key","public_key"]},{"properties":{"algorithm":{"enum":["ES256","RS256"]},"vault":{"properties":{},"type":"object"}},"required":["vault"]}]}},"properties":{"algorithm":{"default":"HS256","enum":["ES256","HS256","HS512","RS256"],"type":"string"},"base64_secret":{"default":false,"type":"boolean"},"exp":{"default":86400,"minimum":1,"type":"integer"},"key":{"type":"string"},"lifetime_grace_period":{"default":0,"minimum":0,"type":"integer"},"secret":{"type":"string"},"vault":{"properties":{},"type":"object"}},"required":["key"],"type":"object"}`, - Sleep: base.SleepTime, - }), - Entry("get route schema of plugin", base.HttpTestCase{ - Object: base.ManagerApiExpect(), - Method: http.MethodGet, - Path: "/apisix/admin/schema/plugins/jwt-auth", - Headers: map[string]string{"Authorization": base.GetToken()}, - ExpectStatus: http.StatusOK, - ExpectBody: `{"$comment":"this is a mark for our injected plugin schema","properties":{"_meta":{"properties":{"disable":{"type":"boolean"},"error_response":{"oneOf":[{"type":"string"},{"type":"object"}]},"filter":{"description":"filter determines whether the plugin needs to be executed at runtime","type":"array"},"priority":{"description":"priority of plugins by customized order","type":"integer"}},"type":"object"},"cookie":{"default":"jwt","type":"string"},"header":{"default":"authorization","type":"string"},"query":{"default":"jwt","type":"string"}},"type":"object"}`, - Sleep: base.SleepTime, - }), - Entry("get schema of non-existent plugin", base.HttpTestCase{ - Object: base.ManagerApiExpect(), - Method: http.MethodGet, - Path: "/apisix/admin/schema/plugins/non-existent", - Headers: map[string]string{"Authorization": base.GetToken()}, - ExpectStatus: http.StatusNotFound, - ExpectBody: `schema of plugins non-existent not found`, - Sleep: base.SleepTime, - }), - Entry("get schema of consumer", base.HttpTestCase{ - Object: base.ManagerApiExpect(), - Method: http.MethodGet, - Path: "/apisix/admin/schemas/consumer", - Headers: map[string]string{"Authorization": base.GetToken()}, - ExpectStatus: http.StatusOK, - ExpectBody: `{"properties":{"create_time":{"type":"integer"},"desc":{"maxLength":256,"type":"string"},"group_id":{"anyOf":[{"maxLength":64,"minLength":1,"pattern":"^[a-zA-Z0-9-_.]+$","type":"string"},{"minimum":1,"type":"integer"}]},"labels":{"description":"key/value pairs to specify attributes","patternProperties":{".*":{"description":"value of label","maxLength":64,"minLength":1,"pattern":"^\\S+$","type":"string"}},"type":"object"},"plugins":{"type":"object"},"update_time":{"type":"integer"},"username":{"maxLength":100,"minLength":1,"pattern":"^[a-zA-Z0-9_]+$","type":"string"}},"required":["username"],"type":"object"}`, - Sleep: base.SleepTime, - }), - Entry("get schema of non-existent resources", base.HttpTestCase{ - Object: base.ManagerApiExpect(), - Method: http.MethodGet, - Path: "/apisix/admin/schemas/non-existent", - Headers: map[string]string{"Authorization": base.GetToken()}, - ExpectStatus: http.StatusNotFound, - ExpectBody: `schema of non-existent not found`, - Sleep: base.SleepTime, - }), - ) -}) diff --git a/api/test/e2e/server_info/server_info_suite_test.go b/api/test/e2e/server_info/server_info_suite_test.go deleted file mode 100644 index ff2d49e3b9..0000000000 --- a/api/test/e2e/server_info/server_info_suite_test.go +++ /dev/null @@ -1,37 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one or more - * contributor license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright ownership. - * The ASF licenses this file to You under the Apache License, Version 2.0 - * (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package server_info_test - -import ( - "testing" - "time" - - . "github.com/onsi/ginkgo/v2" - . "github.com/onsi/gomega" - - "github.com/apisix/manager-api/test/e2e/base" -) - -func TestServerInfo(t *testing.T) { - RegisterFailHandler(Fail) - RunSpecs(t, "Server Info Suite") -} - -var _ = BeforeSuite(func() { - base.CleanAllResource() - time.Sleep(base.SleepTime) -}) diff --git a/api/test/e2e/server_info/server_info_test.go b/api/test/e2e/server_info/server_info_test.go deleted file mode 100644 index c84e05665a..0000000000 --- a/api/test/e2e/server_info/server_info_test.go +++ /dev/null @@ -1,125 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one or more - * contributor license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright ownership. - * The ASF licenses this file to You under the Apache License, Version 2.0 - * (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package server_info_test - -import ( - "net/http" - "time" - - . "github.com/onsi/ginkgo/v2" - - "github.com/apisix/manager-api/test/e2e/base" -) - -var _ = Describe("server info test", func() { - DescribeTable("get server info", - func(tc base.HttpTestCase) { - time.Sleep(2 * time.Second) - base.RunTestCase(tc) - }, - Entry("get server info(apisix-server1)", base.HttpTestCase{ - Object: base.ManagerApiExpect(), - Path: "/apisix/admin/server_info/apisix-server1", - Method: http.MethodGet, - Headers: map[string]string{"Authorization": base.GetToken()}, - ExpectStatus: http.StatusOK, - ExpectBody: "\"hostname\":\"apisix_server1\"", - }), - Entry("get server info(apisix-server2)", base.HttpTestCase{ - Object: base.ManagerApiExpect(), - Path: "/apisix/admin/server_info/apisix-server2", - Method: http.MethodGet, - Headers: map[string]string{"Authorization": base.GetToken()}, - ExpectStatus: http.StatusOK, - ExpectBody: "\"hostname\":\"apisix_server2\"", - }), - ) - - DescribeTable("get server info list", - func(tc base.HttpTestCase) { - base.RunTestCase(tc) - }, - Entry("list all server info", base.HttpTestCase{ - Object: base.ManagerApiExpect(), - Path: "/apisix/admin/server_info", - Method: http.MethodGet, - Headers: map[string]string{"Authorization": base.GetToken()}, - ExpectStatus: http.StatusOK, - ExpectBody: "\"total_size\":2", - }), - Entry("list server info with hostname", base.HttpTestCase{ - Object: base.ManagerApiExpect(), - Path: "/apisix/admin/server_info", - Query: "hostname=apisix_", - Method: http.MethodGet, - Headers: map[string]string{"Authorization": base.GetToken()}, - ExpectStatus: http.StatusOK, - ExpectBody: "\"total_size\":2", - }), - Entry("list server info with hostname", base.HttpTestCase{ - Object: base.ManagerApiExpect(), - Path: "/apisix/admin/server_info", - Query: "hostname=apisix_server2", - Method: http.MethodGet, - Headers: map[string]string{"Authorization": base.GetToken()}, - ExpectStatus: http.StatusOK, - ExpectBody: "\"total_size\":1", - }), - ) -}) - -var _ = Describe("server info test omitEmptyValue", func() { - DescribeTable("server info get omitEmptyValue", - func(tc base.HttpTestCase) { - time.Sleep(2 * time.Second) - base.RunTestCase(tc) - }, - Entry("get server info", base.HttpTestCase{ - Object: base.ManagerApiExpect(), - Path: "/apisix/admin/server_info/apisix-server1", - Method: http.MethodGet, - Headers: map[string]string{"Authorization": base.GetToken()}, - ExpectStatus: http.StatusOK, - UnexpectBody: []string{"\"create_time\":", "\"update_time\":"}, - }), - ) - - DescribeTable("server info list omitEmptyValue", - func(tc base.HttpTestCase) { - base.RunTestCase(tc) - }, - Entry("list all server info", base.HttpTestCase{ - Object: base.ManagerApiExpect(), - Path: "/apisix/admin/server_info", - Method: http.MethodGet, - Headers: map[string]string{"Authorization": base.GetToken()}, - ExpectStatus: http.StatusOK, - ExpectBody: "\"total_size\":2", - UnexpectBody: []string{"\"create_time\":", "\"update_time\":"}, - }), - Entry("list server info with hostname", base.HttpTestCase{ - Object: base.ManagerApiExpect(), - Path: "/apisix/admin/server_info", - Query: "hostname=apisix_", - Method: http.MethodGet, - Headers: map[string]string{"Authorization": base.GetToken()}, - ExpectStatus: http.StatusOK, - ExpectBody: "\"total_size\":2", - UnexpectBody: []string{"\"create_time\":", "\"update_time\":"}, - }), - ) -}) diff --git a/api/test/e2e/service/service_suite_test.go b/api/test/e2e/service/service_suite_test.go deleted file mode 100644 index 8c8d670319..0000000000 --- a/api/test/e2e/service/service_suite_test.go +++ /dev/null @@ -1,37 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one or more - * contributor license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright ownership. - * The ASF licenses this file to You under the Apache License, Version 2.0 - * (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package service_test - -import ( - "testing" - "time" - - . "github.com/onsi/ginkgo/v2" - . "github.com/onsi/gomega" - - "github.com/apisix/manager-api/test/e2e/base" -) - -func TestRoute(t *testing.T) { - RegisterFailHandler(Fail) - RunSpecs(t, "Service Suite") -} - -var _ = BeforeSuite(func() { - base.CleanAllResource() - time.Sleep(base.SleepTime) -}) diff --git a/api/test/e2e/service/service_test.go b/api/test/e2e/service/service_test.go deleted file mode 100644 index 1b854a70b6..0000000000 --- a/api/test/e2e/service/service_test.go +++ /dev/null @@ -1,740 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one or more - * contributor license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright ownership. - * The ASF licenses this file to You under the Apache License, Version 2.0 - * (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package service_test - -import ( - "encoding/json" - "net/http" - "time" - - . "github.com/onsi/ginkgo/v2" - . "github.com/onsi/gomega" - - "github.com/apisix/manager-api/test/e2e/base" -) - -var _ = Describe("create service without plugin", func() { - var createServiceBody map[string]interface{} = map[string]interface{}{ - "name": "testservice", - "upstream": map[string]interface{}{ - "type": "roundrobin", - "nodes": []map[string]interface{}{ - { - "host": base.UpstreamIp, - "port": 1980, - "weight": 1, - }, - { - "host": base.UpstreamIp, - "port": 1981, - "weight": 2, - }, - { - "host": base.UpstreamIp, - "port": 1982, - "weight": 3, - }, - }, - }, - } - It("create service without plugin", func() { - _createServiceBody, err := json.Marshal(createServiceBody) - Expect(err).To(BeNil()) - base.RunTestCase(base.HttpTestCase{ - Object: base.ManagerApiExpect(), - Method: http.MethodPut, - Path: "/apisix/admin/services/s1", - Headers: map[string]string{"Authorization": base.GetToken()}, - Body: string(_createServiceBody), - ExpectStatus: http.StatusOK, - ExpectBody: []string{"\"id\":\"s1\"", "\"name\":\"testservice\""}, - }) - }) - It("create service2 success", func() { - createServiceBody["name"] = "testservice2" - _createServiceBody, err := json.Marshal(createServiceBody) - Expect(err).To(BeNil()) - base.RunTestCase(base.HttpTestCase{ - Object: base.ManagerApiExpect(), - Method: http.MethodPut, - Path: "/apisix/admin/services/s2", - Headers: map[string]string{"Authorization": base.GetToken()}, - Body: string(_createServiceBody), - ExpectStatus: http.StatusOK, - ExpectBody: []string{"\"id\":\"s2\"", "\"name\":\"testservice2\""}, - }) - }) - It("create service failed, name existed", func() { - createServiceBody["name"] = "testservice2" - _createServiceBody, err := json.Marshal(createServiceBody) - Expect(err).To(BeNil()) - base.RunTestCase(base.HttpTestCase{ - Object: base.ManagerApiExpect(), - Method: http.MethodPost, - Path: "/apisix/admin/services", - Headers: map[string]string{"Authorization": base.GetToken()}, - Body: string(_createServiceBody), - ExpectStatus: http.StatusBadRequest, - ExpectBody: `service name exists`, - Sleep: base.SleepTime, - }) - }) - It("update service failed, name existed", func() { - createServiceBody["name"] = "testservice2" - _createServiceBody, err := json.Marshal(createServiceBody) - Expect(err).To(BeNil()) - base.RunTestCase(base.HttpTestCase{ - Object: base.ManagerApiExpect(), - Method: http.MethodPut, - Path: "/apisix/admin/services/s1", - Headers: map[string]string{"Authorization": base.GetToken()}, - Body: string(_createServiceBody), - ExpectStatus: http.StatusBadRequest, - ExpectBody: `service name exists`, - }) - }) - It("get the service s1", func() { - base.RunTestCase(base.HttpTestCase{ - Object: base.ManagerApiExpect(), - Method: http.MethodGet, - Path: "/apisix/admin/services/s1", - Headers: map[string]string{"Authorization": base.GetToken()}, - ExpectCode: http.StatusOK, - ExpectBody: "\"name\":\"testservice\",\"upstream\":{\"nodes\":[{\"host\":\"" + base.UpstreamIp + "\",\"port\":1980,\"weight\":1},{\"host\":\"" + base.UpstreamIp + "\",\"port\":1981,\"weight\":2},{\"host\":\"" + base.UpstreamIp + "\",\"port\":1982,\"weight\":3}],\"type\":\"roundrobin\"}", - Sleep: base.SleepTime, - }) - }) - It("create route using the service just created", func() { - base.RunTestCase(base.HttpTestCase{ - Object: base.ManagerApiExpect(), - Method: http.MethodPut, - Path: "/apisix/admin/routes/r1", - Body: `{ - "name": "route1", - "uri": "/server_port", - "service_id": "s1" - }`, - Headers: map[string]string{"Authorization": base.GetToken()}, - ExpectStatus: http.StatusOK, - Sleep: base.SleepTime, - }) - }) - It("batch test /server_port api", func() { - time.Sleep(time.Duration(500) * time.Millisecond) - res := base.BatchTestServerPort(18, nil, "") - Expect(res["1980"]).Should(Equal(3)) - Expect(res["1981"]).Should(Equal(6)) - Expect(res["1982"]).Should(Equal(9)) - }) - It("delete route", func() { - base.RunTestCase(base.HttpTestCase{ - Object: base.ManagerApiExpect(), - Method: http.MethodDelete, - Path: "/apisix/admin/routes/r1", - Headers: map[string]string{"Authorization": base.GetToken()}, - ExpectStatus: http.StatusOK, - }) - }) - It("delete service", func() { - base.RunTestCase(base.HttpTestCase{ - Object: base.ManagerApiExpect(), - Method: http.MethodDelete, - Path: "/apisix/admin/services/s1", - Headers: map[string]string{"Authorization": base.GetToken()}, - ExpectStatus: http.StatusOK, - Sleep: base.SleepTime, - }) - }) - It("delete service2", func() { - base.RunTestCase(base.HttpTestCase{ - Object: base.ManagerApiExpect(), - Method: http.MethodDelete, - Path: "/apisix/admin/services/s2", - Headers: map[string]string{"Authorization": base.GetToken()}, - ExpectStatus: http.StatusOK, - Sleep: base.SleepTime, - }) - }) - It("hit the route just deleted", func() { - base.RunTestCase(base.HttpTestCase{ - Object: base.APISIXExpect(), - Method: http.MethodGet, - Path: "/server_port", - ExpectStatus: http.StatusNotFound, - ExpectBody: "{\"error_msg\":\"404 Route Not Found\"}\n", - Sleep: base.SleepTime, - }) - }) -}) - -var _ = Describe("create service with plugin", func() { - It("create service without plugin", func() { - var createServiceBody map[string]interface{} = map[string]interface{}{ - "name": "testservice", - "plugins": map[string]interface{}{ - "limit-count": map[string]interface{}{ - "count": 100, - "time_window": 60, - "rejected_code": 503, - "key": "remote_addr", - "policy": "local", - }, - }, - "upstream": map[string]interface{}{ - "type": "roundrobin", - "nodes": []map[string]interface{}{ - { - "host": base.UpstreamIp, - "port": 1980, - "weight": 1, - }, - }, - }, - } - _createServiceBody, err := json.Marshal(createServiceBody) - Expect(err).To(BeNil()) - base.RunTestCase(base.HttpTestCase{ - Object: base.ManagerApiExpect(), - Method: http.MethodPut, - Path: "/apisix/admin/services/s1", - Headers: map[string]string{"Authorization": base.GetToken()}, - Body: string(_createServiceBody), - ExpectStatus: http.StatusOK, - ExpectBody: []string{"\"id\":\"s1\"", "\"name\":\"testservice\""}, - }) - }) - It("get the service s1", func() { - base.RunTestCase(base.HttpTestCase{ - Object: base.ManagerApiExpect(), - Method: http.MethodGet, - Path: "/apisix/admin/services/s1", - Headers: map[string]string{"Authorization": base.GetToken()}, - ExpectCode: http.StatusOK, - ExpectBody: "\"upstream\":{\"nodes\":[{\"host\":\"" + base.UpstreamIp + "\",\"port\":1980,\"weight\":1}],\"type\":\"roundrobin\"},\"plugins\":{\"limit-count\":{\"count\":100,\"key\":\"remote_addr\",\"policy\":\"local\",\"rejected_code\":503,\"time_window\":60}}", - Sleep: base.SleepTime, - }) - }) - It("create route using the service just created", func() { - base.RunTestCase(base.HttpTestCase{ - Object: base.ManagerApiExpect(), - Method: http.MethodPut, - Path: "/apisix/admin/routes/r1", - Body: `{ - "name": "route1", - "uri": "/server_port", - "service_id": "s1" - }`, - Headers: map[string]string{"Authorization": base.GetToken()}, - ExpectStatus: http.StatusOK, - Sleep: base.SleepTime, - }) - }) - It(" hit routes and check the response header", func() { - time.Sleep(time.Duration(500) * time.Millisecond) - basepath := base.APISIXHost - request, err := http.NewRequest("GET", basepath+"/server_port", nil) - Expect(err).To(BeNil()) - request.Header.Add("Authorization", base.GetToken()) - resp, err := http.DefaultClient.Do(request) - Expect(err).To(BeNil()) - defer resp.Body.Close() - Expect(resp.StatusCode).Should(Equal(200)) - Expect(resp.Header["X-Ratelimit-Limit"][0]).Should(Equal("100")) - Expect(resp.Header["X-Ratelimit-Remaining"][0]).Should(Equal("99")) - }) - It("delete route", func() { - base.RunTestCase(base.HttpTestCase{ - Object: base.ManagerApiExpect(), - Method: http.MethodDelete, - Path: "/apisix/admin/routes/r1", - Headers: map[string]string{"Authorization": base.GetToken()}, - ExpectStatus: http.StatusOK, - }) - }) - It("delete service", func() { - base.RunTestCase(base.HttpTestCase{ - Object: base.ManagerApiExpect(), - Method: http.MethodDelete, - Path: "/apisix/admin/services/s1", - Headers: map[string]string{"Authorization": base.GetToken()}, - ExpectStatus: http.StatusOK, - Sleep: base.SleepTime, - }) - }) - It("hit the route just deleted", func() { - base.RunTestCase(base.HttpTestCase{ - Object: base.APISIXExpect(), - Method: http.MethodGet, - Path: "/server_port", - ExpectStatus: http.StatusNotFound, - ExpectBody: "{\"error_msg\":\"404 Route Not Found\"}\n", - Sleep: base.SleepTime, - }) - }) -}) - -var _ = Describe("create service with all options via POST method", func() { - It("create service with all options via POST method", func() { - var createServiceBody map[string]interface{} = map[string]interface{}{ - "id": "s2", - "name": "testservice22", - "desc": "testservice_desc", - "labels": map[string]interface{}{ - "build": "16", - "env": "production", - "version": "v2", - }, - "enable_websocket": true, - "plugins": map[string]interface{}{ - "limit-count": map[string]interface{}{ - "count": 100, - "time_window": 60, - "rejected_code": 503, - "key": "remote_addr", - "policy": "local", - }, - }, - "upstream": map[string]interface{}{ - "type": "roundrobin", - "create_time": 1602883670, - "update_time": 1602893670, - "nodes": []map[string]interface{}{ - { - "host": base.UpstreamIp, - "port": 1980, - "weight": 1, - }, - }, - }, - } - _createServiceBody, err := json.Marshal(createServiceBody) - Expect(err).To(BeNil()) - base.RunTestCase(base.HttpTestCase{ - Desc: "create service with all options via POST method", - Object: base.ManagerApiExpect(), - Method: http.MethodPost, - Path: "/apisix/admin/services", - Headers: map[string]string{"Authorization": base.GetToken()}, - Body: string(_createServiceBody), - ExpectStatus: http.StatusOK, - ExpectBody: "\"id\":\"s2\"", - }) - }) - It("get the service s2", func() { - base.RunTestCase(base.HttpTestCase{ - Object: base.ManagerApiExpect(), - Method: http.MethodGet, - Path: "/apisix/admin/services/s2", - Headers: map[string]string{"Authorization": base.GetToken()}, - ExpectCode: http.StatusOK, - ExpectBody: "\"name\":\"testservice22\",\"desc\":\"testservice_desc\",\"upstream\":{\"nodes\":[{\"host\":\"" + base.UpstreamIp + "\",\"port\":1980,\"weight\":1}],\"type\":\"roundrobin\"},\"plugins\":{\"limit-count\":{\"count\":100,\"key\":\"remote_addr\",\"policy\":\"local\",\"rejected_code\":503,\"time_window\":60}},\"labels\":{\"build\":\"16\",\"env\":\"production\",\"version\":\"v2\"},\"enable_websocket\":true}", - Sleep: base.SleepTime, - }) - }) - It("create route using the service just created", func() { - base.RunTestCase(base.HttpTestCase{ - Object: base.ManagerApiExpect(), - Method: http.MethodPut, - Path: "/apisix/admin/routes/r1", - Body: `{ - "name": "route1", - "uri": "/hello", - "service_id": "s2" - }`, - Headers: map[string]string{"Authorization": base.GetToken()}, - ExpectStatus: http.StatusOK, - Sleep: base.SleepTime, - }) - }) - It("verify route", func() { - base.RunTestCase(base.HttpTestCase{ - Object: base.APISIXExpect(), - Method: http.MethodGet, - Path: "/hello", - ExpectStatus: http.StatusOK, - ExpectBody: "hello world", - Sleep: base.SleepTime, - }) - }) - It("delete route", func() { - base.RunTestCase(base.HttpTestCase{ - Object: base.ManagerApiExpect(), - Method: http.MethodDelete, - Path: "/apisix/admin/routes/r1", - Headers: map[string]string{"Authorization": base.GetToken()}, - ExpectStatus: http.StatusOK, - }) - }) - It("delete service", func() { - base.RunTestCase(base.HttpTestCase{ - Object: base.ManagerApiExpect(), - Method: http.MethodDelete, - Path: "/apisix/admin/services/s2", - Headers: map[string]string{"Authorization": base.GetToken()}, - ExpectStatus: http.StatusOK, - Sleep: base.SleepTime, - }) - }) - It("hit the route just deleted", func() { - base.RunTestCase(base.HttpTestCase{ - Object: base.APISIXExpect(), - Method: http.MethodGet, - Path: "/hello", - ExpectStatus: http.StatusNotFound, - ExpectBody: "{\"error_msg\":\"404 Route Not Found\"}\n", - Sleep: base.SleepTime, - }) - }) -}) - -var _ = Describe("service update use patch method", func() { - It("create service without plugin", func() { - var createServiceBody map[string]interface{} = map[string]interface{}{ - "name": "testservice", - "upstream": map[string]interface{}{ - "type": "roundrobin", - "nodes": []map[string]interface{}{ - { - "host": base.UpstreamIp, - "port": 1980, - "weight": 1, - }, - }, - }, - } - _createServiceBody, err := json.Marshal(createServiceBody) - Expect(err).To(BeNil()) - base.RunTestCase(base.HttpTestCase{ - Desc: "create service without plugin", - Object: base.ManagerApiExpect(), - Method: http.MethodPut, - Path: "/apisix/admin/services/s5", - Headers: map[string]string{"Authorization": base.GetToken()}, - Body: string(_createServiceBody), - ExpectStatus: http.StatusOK, - ExpectBody: "\"name\":\"testservice\",\"upstream\":{\"nodes\":[{\"host\":\"" + base.UpstreamIp + "\",\"port\":1980,\"weight\":1}],\"type\":\"roundrobin\"}}", - }) - }) - It("update service use patch method", func() { - var createServiceBody map[string]interface{} = map[string]interface{}{ - "name": "testpatch", - "upstream": map[string]interface{}{ - "type": "roundrobin", - "nodes": []map[string]interface{}{ - { - "host": base.UpstreamIp, - "port": 1981, - "weight": 1, - }, - }, - }, - } - _createServiceBody, err := json.Marshal(createServiceBody) - Expect(err).To(BeNil()) - base.RunTestCase(base.HttpTestCase{ - Object: base.ManagerApiExpect(), - Method: http.MethodPatch, - Path: "/apisix/admin/services/s5", - Body: string(_createServiceBody), - Headers: map[string]string{"Authorization": base.GetToken()}, - ExpectStatus: http.StatusOK, - Sleep: base.SleepTime, - }) - }) - It("get the service s5", func() { - base.RunTestCase(base.HttpTestCase{ - Object: base.ManagerApiExpect(), - Method: http.MethodGet, - Path: "/apisix/admin/services/s5", - Headers: map[string]string{"Authorization": base.GetToken()}, - ExpectCode: http.StatusOK, - ExpectBody: "\"name\":\"testpatch\",\"upstream\":{\"nodes\":[{\"host\":\"" + base.UpstreamIp + "\",\"port\":1981,\"weight\":1}],\"type\":\"roundrobin\"}}", - Sleep: base.SleepTime, - }) - }) - It("Update service using path parameter patch method", func() { - var createUpstreamBody map[string]interface{} = map[string]interface{}{ - "type": "roundrobin", - "nodes": []map[string]interface{}{ - { - "host": base.UpstreamIp, - "port": 1980, - "weight": 1, - }, - }, - } - _createUpstreamBody, err := json.Marshal(createUpstreamBody) - Expect(err).To(BeNil()) - base.RunTestCase(base.HttpTestCase{ - Object: base.ManagerApiExpect(), - Method: http.MethodPatch, - Path: "/apisix/admin/services/s5/upstream", - Body: string(_createUpstreamBody), - Headers: map[string]string{ - "Authorization": base.GetToken(), - "Content-Type": "text/plain", - }, - ExpectStatus: http.StatusOK, - }) - }) - It("get service data", func() { - base.RunTestCase(base.HttpTestCase{ - Object: base.ManagerApiExpect(), - Method: http.MethodGet, - Path: "/apisix/admin/services/s5", - Headers: map[string]string{"Authorization": base.GetToken()}, - ExpectStatus: http.StatusOK, - ExpectBody: "\"name\":\"testpatch\",\"upstream\":{\"nodes\":[{\"host\":\"" + base.UpstreamIp + "\",\"port\":1980,\"weight\":1}],\"type\":\"roundrobin\"}}", - Sleep: base.SleepTime, - }) - }) - It("delete service", func() { - base.RunTestCase(base.HttpTestCase{ - Object: base.ManagerApiExpect(), - Method: http.MethodDelete, - Path: "/apisix/admin/services/s5", - Headers: map[string]string{"Authorization": base.GetToken()}, - ExpectStatus: http.StatusOK, - }) - }) -}) - -var _ = Describe("test service delete", func() { - var createServiceBody map[string]interface{} = map[string]interface{}{ - "name": "testservice", - "upstream": map[string]interface{}{ - "type": "roundrobin", - "nodes": []map[string]interface{}{ - { - "host": base.UpstreamIp, - "port": 1980, - "weight": 1, - }, - }, - }, - } - _createServiceBody, err := json.Marshal(createServiceBody) - Expect(err).To(BeNil()) - - DescribeTable("test service delete", - func(tc base.HttpTestCase) { - base.RunTestCase(tc) - }, - Entry("create service without plugin", base.HttpTestCase{ - Desc: "create service without plugin", - Object: base.ManagerApiExpect(), - Method: http.MethodPut, - Path: "/apisix/admin/services/s1", - Headers: map[string]string{"Authorization": base.GetToken()}, - Body: string(_createServiceBody), - ExpectStatus: http.StatusOK, - ExpectBody: "\"name\":\"testservice\",\"upstream\":{\"nodes\":[{\"host\":\"" + base.UpstreamIp + "\",\"port\":1980,\"weight\":1}],\"type\":\"roundrobin\"}}", - }), - Entry("create route use service s1", base.HttpTestCase{ - Desc: "create route use service s1", - Object: base.ManagerApiExpect(), - Method: http.MethodPut, - Path: "/apisix/admin/routes/r1", - Body: `{ - "id": "r1", - "name": "route1", - "uri": "/hello", - "upstream": { - "type": "roundrobin", - "nodes": { - "` + base.UpstreamIp + `:1980": 1 - } - }, - "service_id": "s1" - }`, - Headers: map[string]string{"Authorization": base.GetToken()}, - ExpectStatus: http.StatusOK, - ExpectBody: "\"service_id\":\"s1\"", - }), - Entry("hit route on apisix", base.HttpTestCase{ - Object: base.APISIXExpect(), - Method: http.MethodGet, - Path: "/hello", - ExpectStatus: http.StatusOK, - ExpectBody: "hello world", - Sleep: base.SleepTime, - }), - Entry("delete service failed", base.HttpTestCase{ - Desc: "delete service failed", - Object: base.ManagerApiExpect(), - Method: http.MethodDelete, - Path: "/apisix/admin/services/s1", - Headers: map[string]string{"Authorization": base.GetToken()}, - ExpectStatus: http.StatusBadRequest, - ExpectBody: "route: route1 is using this service", - }), - Entry("delete route first", base.HttpTestCase{ - Desc: "delete route first", - Object: base.ManagerApiExpect(), - Method: http.MethodDelete, - Path: "/apisix/admin/routes/r1", - Headers: map[string]string{"Authorization": base.GetToken()}, - ExpectStatus: http.StatusOK, - }), - Entry("check route exist", base.HttpTestCase{ - Desc: "check route exist", - Object: base.ManagerApiExpect(), - Method: http.MethodDelete, - Path: "/apisix/admin/routes/r1", - Headers: map[string]string{"Authorization": base.GetToken()}, - ExpectStatus: http.StatusNotFound, - }), - Entry("delete service success", base.HttpTestCase{ - Desc: "delete service success", - Object: base.ManagerApiExpect(), - Method: http.MethodDelete, - Path: "/apisix/admin/services/s1", - Headers: map[string]string{"Authorization": base.GetToken()}, - ExpectStatus: http.StatusOK, - }), - Entry("check the service exist", base.HttpTestCase{ - Desc: "check the exist", - Object: base.ManagerApiExpect(), - Method: http.MethodGet, - Path: "/apisix/admin/services/s1", - Headers: map[string]string{"Authorization": base.GetToken()}, - ExpectStatus: http.StatusNotFound, - })) -}) - -var _ = Describe("test service with hosts", func() { - var createServiceBody = map[string]interface{}{ - "name": "testservice", - "upstream": map[string]interface{}{ - "type": "roundrobin", - "nodes": []map[string]interface{}{ - { - "host": base.UpstreamIp, - "port": 1980, - "weight": 1, - }, - }, - }, - "hosts": []string{ - "test.com", - "test1.com", - }, - } - _createServiceBody, err := json.Marshal(createServiceBody) - Expect(err).To(BeNil()) - - var createRouteBody = map[string]interface{}{ - "id": "r1", - "name": "route1", - "uri": "/hello", - "upstream": map[string]interface{}{ - "type": "roundrobin", - "nodes": map[string]interface{}{ - base.UpstreamIp + ":1980": 1, - }, - }, - "service_id": "s1", - } - _createRouteBody, err := json.Marshal(createRouteBody) - Expect(err).To(BeNil()) - - DescribeTable("test service with hosts", - func(tc func() base.HttpTestCase) { - base.RunTestCase(tc()) - }, - Entry("create service with hosts params", func() base.HttpTestCase { - return base.HttpTestCase{ - Desc: "create service with hosts params", - Object: base.ManagerApiExpect(), - Method: http.MethodPut, - Path: "/apisix/admin/services/s1", - Headers: map[string]string{"Authorization": base.GetToken()}, - Body: string(_createServiceBody), - ExpectStatus: http.StatusOK, - } - }), - Entry("create route use service s1", func() base.HttpTestCase { - return base.HttpTestCase{ - Desc: "create route use service s1", - Object: base.ManagerApiExpect(), - Method: http.MethodPut, - Path: "/apisix/admin/routes/r1", - Body: string(_createRouteBody), - Headers: map[string]string{"Authorization": base.GetToken()}, - ExpectStatus: http.StatusOK, - } - }), - Entry("hit route by test.com", func() base.HttpTestCase { - return base.HttpTestCase{ - Object: base.APISIXExpect(), - Method: http.MethodGet, - Path: "/hello", - Headers: map[string]string{ - "Host": "test.com", - }, - ExpectStatus: http.StatusOK, - ExpectBody: "hello world", - Sleep: base.SleepTime, - } - }), - Entry("hit route by test1.com", func() base.HttpTestCase { - return base.HttpTestCase{ - Object: base.APISIXExpect(), - Method: http.MethodGet, - Path: "/hello", - Headers: map[string]string{ - "Host": "test1.com", - }, - ExpectStatus: http.StatusOK, - ExpectBody: "hello world", - Sleep: base.SleepTime, - } - }), - Entry("hit route by test2.com", func() base.HttpTestCase { - return base.HttpTestCase{ - Object: base.APISIXExpect(), - Method: http.MethodGet, - Path: "/hello", - Headers: map[string]string{ - "Host": "test2.com", - }, - ExpectStatus: http.StatusNotFound, - Sleep: base.SleepTime, - } - }), - Entry("delete route", func() base.HttpTestCase { - return base.HttpTestCase{ - Desc: "delete route first", - Object: base.ManagerApiExpect(), - Method: http.MethodDelete, - Path: "/apisix/admin/routes/r1", - Headers: map[string]string{"Authorization": base.GetToken()}, - ExpectStatus: http.StatusOK, - } - }), - Entry("delete service", func() base.HttpTestCase { - return base.HttpTestCase{ - Desc: "delete service success", - Object: base.ManagerApiExpect(), - Method: http.MethodDelete, - Path: "/apisix/admin/services/s1", - Headers: map[string]string{"Authorization": base.GetToken()}, - ExpectStatus: http.StatusOK, - } - }), - ) -}) diff --git a/api/test/e2e/ssl/ssl_suite_test.go b/api/test/e2e/ssl/ssl_suite_test.go deleted file mode 100644 index 87b3f81cc1..0000000000 --- a/api/test/e2e/ssl/ssl_suite_test.go +++ /dev/null @@ -1,37 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one or more - * contributor license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright ownership. - * The ASF licenses this file to You under the Apache License, Version 2.0 - * (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package ssl_test - -import ( - "testing" - "time" - - . "github.com/onsi/ginkgo/v2" - . "github.com/onsi/gomega" - - "github.com/apisix/manager-api/test/e2e/base" -) - -func TestSSL(t *testing.T) { - RegisterFailHandler(Fail) - RunSpecs(t, "SSL Suite") -} - -var _ = BeforeSuite(func() { - base.CleanAllResource() - time.Sleep(base.SleepTime) -}) diff --git a/api/test/e2e/ssl/ssl_test.go b/api/test/e2e/ssl/ssl_test.go deleted file mode 100644 index b204b22c24..0000000000 --- a/api/test/e2e/ssl/ssl_test.go +++ /dev/null @@ -1,291 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one or more - * contributor license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright ownership. - * The ASF licenses this file to You under the Apache License, Version 2.0 - * (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package ssl_test - -import ( - "context" - "crypto/tls" - "encoding/json" - "fmt" - "io/ioutil" - "net" - "net/http" - "time" - - . "github.com/onsi/ginkgo/v2" - . "github.com/onsi/gomega" - - "github.com/apisix/manager-api/test/e2e/base" -) - -var _ = Describe("SSL Basic", func() { - var ( - testCert []byte - testKey []byte - apisixKey []byte - validBody []byte - validBody2 []byte - invalidBody []byte - createRouteBody []byte - ) - - var err error - testCert, err = ioutil.ReadFile("../../certs/test2.crt") - Expect(err).To(BeNil()) - testKey, err = ioutil.ReadFile("../../certs/test2.key") - Expect(err).To(BeNil()) - apisixKey, err = ioutil.ReadFile("../../certs/apisix.key") - Expect(err).To(BeNil()) - - validBody, err = json.Marshal(map[string]interface{}{ - "id": "1", - "cert": string(testCert), - "key": string(testKey), - "labels": map[string]string{ - "build": "16", - "env": "production", - "version": "v3", - }, - }) - Expect(err).To(BeNil()) - validBody2, err = json.Marshal(map[string]interface{}{ - "id": "1", - "cert": string(testCert), - "key": string(testKey), - "labels": map[string]string{ - "build": "16", - "env": "production", - "version": "v2", - }, - }) - Expect(err).To(BeNil()) - - invalidBody, err = json.Marshal(map[string]string{ - "id": "1", - "cert": string(testCert), - "key": string(apisixKey), - }) - Expect(err).To(BeNil()) - - tempBody := map[string]interface{}{ - "name": "route1", - "uri": "/hello_", - "hosts": []string{"test2.com", "*.test2.com"}, - "upstream": map[string]interface{}{ - "nodes": []map[string]interface{}{ - { - "host": base.UpstreamIp, - "port": 1980, - "weight": 1, - }, - }, - "type": "roundrobin", - }, - } - createRouteBody, err = json.Marshal(tempBody) - Expect(err).To(BeNil()) - - It("without certificate", func() { - // Before configuring SSL, make a HTTPS request - http.DefaultTransport.(*http.Transport).TLSClientConfig = &tls.Config{InsecureSkipVerify: true} - http.DefaultTransport.(*http.Transport).DialContext = func(ctx context.Context, network, addr string) (net.Conn, error) { - if addr == "www.test2.com:9443" { - addr = "127.0.0.1:9443" - } - dialer := &net.Dialer{} - return dialer.DialContext(ctx, network, addr) - } - - _, err := http.Get("https://www.test2.com:9443") - Expect(fmt.Sprintf("%s", err)).Should(Equal("Get \"https://www.test2.com:9443\": remote error: tls: internal error")) - }) - - DescribeTable("test ssl basic", func(testCase base.HttpTestCase) { - base.RunTestCase(testCase) - }, - Entry("create ssl failed", base.HttpTestCase{ - Object: base.ManagerApiExpect(), - Method: http.MethodPost, - Path: "/apisix/admin/ssl", - Body: string(invalidBody), - Headers: map[string]string{"Authorization": base.GetToken()}, - ExpectStatus: http.StatusBadRequest, - ExpectBody: "SSL parse failed: key and cert don't match", - Sleep: base.SleepTime, - }), - Entry("create ssl successfully", base.HttpTestCase{ - Object: base.ManagerApiExpect(), - Method: http.MethodPost, - Path: "/apisix/admin/ssl", - Body: string(validBody), - Headers: map[string]string{"Authorization": base.GetToken()}, - ExpectStatus: http.StatusOK, - Sleep: base.SleepTime, - }), - Entry("validate ssl cert and key (valid)", base.HttpTestCase{ - Object: base.ManagerApiExpect(), - Method: http.MethodPost, - Path: "/apisix/admin/check_ssl_cert", - Body: string(validBody), - Headers: map[string]string{"Authorization": base.GetToken()}, - ExpectBody: "\"code\":0,\"message\":\"\"", - ExpectStatus: http.StatusOK, - }), - Entry("validate ssl cert and key (valid)", base.HttpTestCase{ - Object: base.ManagerApiExpect(), - Method: http.MethodPost, - Path: "/apisix/admin/check_ssl_cert", - Body: string(invalidBody), - Headers: map[string]string{"Authorization": base.GetToken()}, - ExpectBody: "key and cert don't match", - ExpectStatus: http.StatusOK, - }), - Entry("check ssl labels", base.HttpTestCase{ - Object: base.ManagerApiExpect(), - Method: http.MethodGet, - Path: "/apisix/admin/ssl/1", - Headers: map[string]string{"Authorization": base.GetToken()}, - ExpectStatus: http.StatusOK, - ExpectBody: "\"labels\":{\"build\":\"16\",\"env\":\"production\",\"version\":\"v3\"", - }), - Entry("update ssl", base.HttpTestCase{ - Object: base.ManagerApiExpect(), - Method: http.MethodPut, - Path: "/apisix/admin/ssl/1", - Body: string(validBody2), - Headers: map[string]string{"Authorization": base.GetToken()}, - ExpectStatus: http.StatusOK, - Sleep: base.SleepTime, - }), - Entry("check ssl labels", base.HttpTestCase{ - Object: base.ManagerApiExpect(), - Method: http.MethodGet, - Path: "/apisix/admin/ssl/1", - Headers: map[string]string{"Authorization": base.GetToken()}, - ExpectStatus: http.StatusOK, - ExpectBody: "\"labels\":{\"build\":\"16\",\"env\":\"production\",\"version\":\"v2\"", - Sleep: base.SleepTime, - }), - Entry("check host exist", base.HttpTestCase{ - Object: base.ManagerApiExpect(), - Method: http.MethodPost, - Path: "/apisix/admin/check_ssl_exists", - Body: `{"hosts": ["www.test2.com"]}`, - Headers: map[string]string{"Authorization": base.GetToken()}, - ExpectStatus: http.StatusOK, - }), - Entry("check host not exist", base.HttpTestCase{ - Object: base.ManagerApiExpect(), - Method: http.MethodPost, - Path: "/apisix/admin/check_ssl_exists", - Body: `{"hosts": ["www.test3.com"]}`, - Headers: map[string]string{"Authorization": base.GetToken()}, - ExpectStatus: http.StatusNotFound, - ExpectBody: "SSL cert not exists for sni๏ผšwww.test3.com", - }), - Entry("create route", base.HttpTestCase{ - Object: base.ManagerApiExpect(), - Method: http.MethodPut, - Path: "/apisix/admin/routes/r1", - Body: string(createRouteBody), - Headers: map[string]string{"Authorization": base.GetToken()}, - ExpectStatus: http.StatusOK, - }), - Entry("get the route just created to trigger removing `key`", base.HttpTestCase{ - Object: base.ManagerApiExpect(), - Method: http.MethodGet, - Path: "/apisix/admin/routes/r1", - Headers: map[string]string{"Authorization": base.GetToken()}, - ExpectStatus: http.StatusOK, - Sleep: base.SleepTime, - }), - Entry("hit the route just created using HTTPS", base.HttpTestCase{ - Object: base.APISIXHTTPSExpect(), - Method: http.MethodGet, - Path: "/hello_", - ExpectStatus: http.StatusOK, - Headers: map[string]string{"Host": "www.test2.com"}, - ExpectBody: "hello world\n", - Sleep: base.SleepTime, - }), - Entry("disable SSL", base.HttpTestCase{ - Object: base.ManagerApiExpect(), - Method: http.MethodPatch, - Path: "/apisix/admin/ssl/1", - Body: `{ - "status": 0 - }`, - Headers: map[string]string{"Authorization": base.GetToken()}, - ExpectStatus: http.StatusOK, - ExpectBody: "\"status\":0", - }), - ) - - It("test disable SSL HTTPS request", func() { - // try again after disable SSL, make a HTTPS request - time.Sleep(time.Duration(500) * time.Millisecond) - _, err := http.Get("https://www.test2.com:9443") - Expect(fmt.Sprintf("%s", err)).Should(Equal("Get \"https://www.test2.com:9443\": remote error: tls: internal error")) - }) - - DescribeTable("test ssl basic", func(testCase base.HttpTestCase) { - base.RunTestCase(testCase) - }, - Entry("enable SSL", base.HttpTestCase{ - Object: base.ManagerApiExpect(), - Method: http.MethodPatch, - Path: "/apisix/admin/ssl/1/status", - Body: `1`, - Headers: map[string]string{ - "Authorization": base.GetToken(), - "Content-Type": "text/plain", - }, - ExpectStatus: http.StatusOK, - ExpectBody: "\"status\":1", - }), - Entry("hit the route using HTTPS, make sure enable successful", base.HttpTestCase{ - Object: base.APISIXHTTPSExpect(), - Method: http.MethodGet, - Path: "/hello_", - Headers: map[string]string{"Host": "www.test2.com"}, - ExpectStatus: http.StatusOK, - ExpectBody: "hello world\n", - Sleep: base.SleepTime, - }), - Entry("delete SSL", base.HttpTestCase{ - Object: base.ManagerApiExpect(), - Method: http.MethodDelete, - Path: "/apisix/admin/ssl/1", - Headers: map[string]string{"Authorization": base.GetToken()}, - ExpectStatus: http.StatusOK, - }), - Entry("delete route", base.HttpTestCase{ - Object: base.ManagerApiExpect(), - Method: http.MethodDelete, - Path: "/apisix/admin/routes/r1", - Headers: map[string]string{"Authorization": base.GetToken()}, - ExpectStatus: http.StatusOK, - }), - Entry("hit the route just deleted", base.HttpTestCase{ - Object: base.APISIXExpect(), - Method: http.MethodGet, - Path: "/hello_", - ExpectStatus: http.StatusNotFound, - ExpectBody: "{\"error_msg\":\"404 Route Not Found\"}\n", - Sleep: base.SleepTime, - })) -}) diff --git a/api/test/e2e/stream_route/stream_route_suite_test.go b/api/test/e2e/stream_route/stream_route_suite_test.go deleted file mode 100644 index a3ffa975c6..0000000000 --- a/api/test/e2e/stream_route/stream_route_suite_test.go +++ /dev/null @@ -1,38 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one or more - * contributor license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright ownership. - * The ASF licenses this file to You under the Apache License, Version 2.0 - * (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package stream_route_test - -import ( - "testing" - "time" - - . "github.com/onsi/ginkgo/v2" - . "github.com/onsi/gomega" - - "github.com/apisix/manager-api/test/e2e/base" -) - -func TestStreamRoute(t *testing.T) { - RegisterFailHandler(Fail) - RunSpecs(t, "Stream Route Suite") -} - -var _ = BeforeSuite(func() { - base.CleanAllResource() - base.RestartManagerAPI() - time.Sleep(base.SleepTime) -}) diff --git a/api/test/e2e/stream_route/stream_route_test.go b/api/test/e2e/stream_route/stream_route_test.go deleted file mode 100644 index 3d8c549146..0000000000 --- a/api/test/e2e/stream_route/stream_route_test.go +++ /dev/null @@ -1,290 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one or more - * contributor license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright ownership. - * The ASF licenses this file to You under the Apache License, Version 2.0 - * (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package stream_route_test - -import ( - "encoding/json" - "io/ioutil" - "net" - "net/http" - "time" - - . "github.com/onsi/ginkgo/v2" - . "github.com/onsi/gomega" - - "github.com/apisix/manager-api/test/e2e/base" -) - -var _ = Describe("Stream Route", func() { - DescribeTable("test stream route data CURD", - func(tc base.HttpTestCase) { - base.RunTestCase(tc) - }, - // base case - Entry("create stream route", base.HttpTestCase{ - Object: base.ManagerApiExpect(), - Method: http.MethodPost, - Path: "/apisix/admin/stream_routes", - Body: `{ - "id": "sr1", - "remote_addr": "127.0.0.1", - "server_addr": "127.0.0.1", - "server_port": 10090, - "sni": "test.com", - "upstream": { - "nodes": { - "` + base.UpstreamIp + `:1980": 1 - }, - "type": "roundrobin" - } - }`, - Headers: map[string]string{"Authorization": base.GetToken()}, - ExpectStatus: http.StatusOK, - }), - Entry("get stream route #1", base.HttpTestCase{ - Object: base.ManagerApiExpect(), - Method: http.MethodGet, - Path: "/apisix/admin/stream_routes/sr1", - Headers: map[string]string{"Authorization": base.GetToken()}, - ExpectStatus: http.StatusOK, - ExpectBody: `"server_port":10090`, - }), - Entry("update stream route", base.HttpTestCase{ - Object: base.ManagerApiExpect(), - Method: http.MethodPut, - Path: "/apisix/admin/stream_routes/sr1", - Headers: map[string]string{"Authorization": base.GetToken()}, - Body: `{ - "id": "sr1", - "server_port": 10091, - "upstream": { - "nodes": { - "` + base.UpstreamIp + `:1980": 1 - }, - "type": "roundrobin" - } - }`, - ExpectStatus: http.StatusOK, - ExpectBody: `"server_port":10091`, - }), - Entry("get stream route #2", base.HttpTestCase{ - Object: base.ManagerApiExpect(), - Method: http.MethodGet, - Path: "/apisix/admin/stream_routes/sr1", - Headers: map[string]string{"Authorization": base.GetToken()}, - ExpectStatus: http.StatusOK, - ExpectBody: `"server_port":10091`, - }), - Entry("hit stream route", base.HttpTestCase{ - Object: base.APISIXStreamProxyExpect(10091, ""), - Method: http.MethodGet, - Path: "/hello", - ExpectStatus: http.StatusOK, - ExpectBody: "hello world", - }), - Entry("delete stream route", base.HttpTestCase{ - Object: base.ManagerApiExpect(), - Method: http.MethodDelete, - Path: "/apisix/admin/stream_routes/sr1", - Headers: map[string]string{"Authorization": base.GetToken()}, - ExpectStatus: http.StatusOK, - }), - ) - - DescribeTable("test stream route with HTTP upstream", - func(tc base.HttpTestCase) { - base.RunTestCase(tc) - }, - Entry("create stream route", base.HttpTestCase{ - Object: base.ManagerApiExpect(), - Method: http.MethodPost, - Path: "/apisix/admin/stream_routes", - Body: `{ - "id": "sr1", - "server_port": 10090, - "upstream": { - "nodes": { - "` + base.UpstreamIp + `:1980": 1 - }, - "type": "roundrobin" - } - }`, - Headers: map[string]string{"Authorization": base.GetToken()}, - ExpectStatus: http.StatusOK, - }), - Entry("hit stream route", base.HttpTestCase{ - Object: base.APISIXStreamProxyExpect(10090, ""), - Method: http.MethodGet, - Path: "/hello", - ExpectStatus: http.StatusOK, - ExpectBody: "hello world", - }), - Entry("delete stream route", base.HttpTestCase{ - Object: base.ManagerApiExpect(), - Method: http.MethodDelete, - Path: "/apisix/admin/stream_routes/sr1", - Headers: map[string]string{"Authorization": base.GetToken()}, - ExpectStatus: http.StatusOK, - }), - ) - - // prepare ssl certificate - apisixCert, err := ioutil.ReadFile("../../certs/apisix.crt") - Expect(err).To(BeNil()) - apisixKey, err := ioutil.ReadFile("../../certs/apisix.key") - Expect(err).To(BeNil()) - apisixSSLBody, err := json.Marshal(map[string]string{"cert": string(apisixCert), "key": string(apisixKey)}) - Expect(err).To(BeNil()) - DescribeTable("test stream route with HTTPS upstream", - func(tc base.HttpTestCase) { - base.RunTestCase(tc) - }, - Entry("create ssl cert", base.HttpTestCase{ - Object: base.ManagerApiExpect(), - Method: http.MethodPost, - Path: "/apisix/admin/ssl", - Body: string(apisixSSLBody), - Headers: map[string]string{"Authorization": base.GetToken()}, - ExpectStatus: http.StatusOK, - }), - Entry("create stream route", base.HttpTestCase{ - Object: base.ManagerApiExpect(), - Method: http.MethodPost, - Path: "/apisix/admin/stream_routes", - Body: `{ - "id": "sr1", - "server_port": 10093, - "sni": "test.com", - "upstream": { - "nodes": { - "` + base.UpstreamIp + `:1980": 1 - }, - "type": "roundrobin" - } - }`, - Headers: map[string]string{"Authorization": base.GetToken()}, - ExpectStatus: http.StatusOK, - }), - Entry("hit stream route through https", base.HttpTestCase{ - Object: base.APISIXStreamProxyExpect(10093, "test.com"), - Method: http.MethodGet, - Path: "/hello", - ExpectStatus: http.StatusOK, - ExpectBody: "hello world", - }), - ) - - Describe("test stream route with TCP upstream", func() { - It("create stream route", func() { - base.RunTestCase(base.HttpTestCase{ - Object: base.ManagerApiExpect(), - Method: http.MethodPost, - Path: "/apisix/admin/stream_routes", - Body: `{ - "id": "sr1tcp", - "server_port": 10090, - "upstream": { - "nodes": { - "` + base.UpstreamIp + `:1991": 1 - }, - "type": "roundrobin" - } - }`, - Headers: map[string]string{"Authorization": base.GetToken()}, - ExpectStatus: http.StatusOK, - }) - }) - It("hit stream route through tcp", func() { - time.Sleep(base.SleepTime) - conn, err := net.Dial("tcp", "127.0.0.1:1991") - Expect(err).To(BeNil()) - - _ = conn.SetDeadline(time.Now().Add(time.Second * 3)) - - _, err = conn.Write([]byte("world")) - Expect(err).To(BeNil()) - - result := make([]byte, 11) - n, err := conn.Read(result) - Expect(n).Should(BeNumerically("==", 11)) - Expect(err).To(BeNil()) - Expect(string(result)).To(ContainSubstring("hello world")) - - err = conn.Close() - Expect(err).To(BeNil()) - }) - }) - - Describe("test stream route with UDP upstream", func() { - It("create stream route", func() { - base.RunTestCase(base.HttpTestCase{ - Object: base.ManagerApiExpect(), - Method: http.MethodPost, - Path: "/apisix/admin/stream_routes", - Body: `{ - "id": "sr1udp", - "server_port": 10095, - "upstream": { - "nodes": { - "` + base.UpstreamIp + `:1992": 1 - }, - "type": "roundrobin" - } - }`, - Headers: map[string]string{"Authorization": base.GetToken()}, - ExpectStatus: http.StatusOK, - }) - }) - It("hit stream route through udp", func() { - time.Sleep(base.SleepTime) - conn, err := net.Dial("udp", "127.0.0.1:10095") - Expect(err).To(BeNil()) - - _ = conn.SetDeadline(time.Now().Add(time.Second * 3)) - - _, err = conn.Write([]byte("world")) - Expect(err).To(BeNil()) - - result := make([]byte, 11) - n, err := conn.Read(result) - Expect(n).Should(BeNumerically("==", 11)) - Expect(err).To(BeNil()) - Expect(string(result)).To(ContainSubstring("hello world")) - - err = conn.Close() - Expect(err).To(BeNil()) - }) - }) - - DescribeTable("test stream route data CURD exception", - func(tc base.HttpTestCase) { - base.RunTestCase(tc) - }, - Entry("create stream route with upstream id not found", base.HttpTestCase{ - Object: base.ManagerApiExpect(), - Method: http.MethodPost, - Path: "/apisix/admin/stream_routes", - Body: `{ - "id": "sr1", - "server_port": 10090, - "upstream_id": "u1" - }`, - Headers: map[string]string{"Authorization": base.GetToken()}, - ExpectStatus: http.StatusBadRequest, - }), - ) -}) diff --git a/api/test/e2e/system_config/system_config_suite_test.go b/api/test/e2e/system_config/system_config_suite_test.go deleted file mode 100644 index 96a874aa5d..0000000000 --- a/api/test/e2e/system_config/system_config_suite_test.go +++ /dev/null @@ -1,36 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one or more - * contributor license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright ownership. - * The ASF licenses this file to You under the Apache License, Version 2.0 - * (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package system_config_test - -import ( - "testing" - "time" - - "github.com/apisix/manager-api/test/e2e/base" - . "github.com/onsi/ginkgo/v2" - . "github.com/onsi/gomega" -) - -func TestSystemConfig(t *testing.T) { - RegisterFailHandler(Fail) - RunSpecs(t, "System Config Suite") -} - -var _ = BeforeSuite(func() { - base.CleanAllResource() - time.Sleep(base.SleepTime) -}) diff --git a/api/test/e2e/system_config/system_config_test.go b/api/test/e2e/system_config/system_config_test.go deleted file mode 100644 index 0ce1b5b738..0000000000 --- a/api/test/e2e/system_config/system_config_test.go +++ /dev/null @@ -1,125 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one or more - * contributor license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright ownership. - * The ASF licenses this file to You under the Apache License, Version 2.0 - * (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package system_config_test - -import ( - "net/http" - - . "github.com/onsi/ginkgo/v2" - - "github.com/apisix/manager-api/test/e2e/base" -) - -var _ = Describe("system config", func() { - DescribeTable("test system config data CURD", - func(tc base.HttpTestCase) { - base.RunTestCase(tc) - }, - - Entry("get system config should get not found error", base.HttpTestCase{ - Object: base.ManagerApiExpect(), - Method: http.MethodGet, - Path: "/apisix/admin/system_config/grafana", - Headers: map[string]string{"Authorization": base.GetToken()}, - ExpectStatus: http.StatusNotFound, - }), - - Entry("create system config should get schema validate failed error", base.HttpTestCase{ - Object: base.ManagerApiExpect(), - Method: http.MethodPost, - Path: "/apisix/admin/system_config", - Body: `{ - "config_name": "", - "payload": {"url":"http://127.0.0.1:3000"} - }`, - Headers: map[string]string{"Authorization": base.GetToken()}, - ExpectStatus: http.StatusBadRequest, - }), - - Entry("create system config should success", base.HttpTestCase{ - Object: base.ManagerApiExpect(), - Method: http.MethodPost, - Path: "/apisix/admin/system_config", - Body: `{ - "config_name": "grafana", - "payload": {"url":"http://127.0.0.1:3000"} - }`, - Headers: map[string]string{"Authorization": base.GetToken()}, - ExpectStatus: http.StatusOK, - ExpectBody: "\"config_name\":\"grafana\",\"payload\":{\"url\":\"http://127.0.0.1:3000\"}", - }), - - Entry("after create system config get config should succeed", base.HttpTestCase{ - Object: base.ManagerApiExpect(), - Method: http.MethodGet, - Path: "/apisix/admin/system_config/grafana", - Headers: map[string]string{"Authorization": base.GetToken()}, - ExpectStatus: http.StatusOK, - ExpectBody: "\"config_name\":\"grafana\",\"payload\":{\"url\":\"http://127.0.0.1:3000\"}", - }), - - Entry("update system config should get schema validate failed error", base.HttpTestCase{ - Object: base.ManagerApiExpect(), - Method: http.MethodPut, - Path: "/apisix/admin/system_config", - Body: `{ - "config_name": "", - "payload": {"url":"http://127.0.0.1:2000"} - }`, - Headers: map[string]string{"Authorization": base.GetToken()}, - ExpectStatus: http.StatusBadRequest, - }), - - Entry("update system config should success", base.HttpTestCase{ - Object: base.ManagerApiExpect(), - Method: http.MethodPut, - Path: "/apisix/admin/system_config", - Body: `{ - "config_name": "grafana", - "payload": {"url":"http://127.0.0.1:2000"} - }`, - Headers: map[string]string{"Authorization": base.GetToken()}, - ExpectStatus: http.StatusOK, - ExpectBody: "\"config_name\":\"grafana\",\"payload\":{\"url\":\"http://127.0.0.1:2000\"}", - }), - - Entry("after update system config get config should succeed", base.HttpTestCase{ - Object: base.ManagerApiExpect(), - Method: http.MethodGet, - Path: "/apisix/admin/system_config/grafana", - Headers: map[string]string{"Authorization": base.GetToken()}, - ExpectStatus: http.StatusOK, - ExpectBody: "\"config_name\":\"grafana\",\"payload\":{\"url\":\"http://127.0.0.1:2000\"}", - }), - - Entry("delete system config should success", base.HttpTestCase{ - Object: base.ManagerApiExpect(), - Method: http.MethodDelete, - Path: "/apisix/admin/system_config/grafana", - Headers: map[string]string{"Authorization": base.GetToken()}, - ExpectStatus: http.StatusOK, - }), - - Entry("get system config should get not found error", base.HttpTestCase{ - Object: base.ManagerApiExpect(), - Method: http.MethodGet, - Path: "/apisix/admin/system_config/grafana", - Headers: map[string]string{"Authorization": base.GetToken()}, - ExpectStatus: http.StatusNotFound, - }), - ) -}) diff --git a/api/test/e2e/upstream/upstream_chash_hash_on_test.go b/api/test/e2e/upstream/upstream_chash_hash_on_test.go deleted file mode 100644 index 2be1b4c3df..0000000000 --- a/api/test/e2e/upstream/upstream_chash_hash_on_test.go +++ /dev/null @@ -1,579 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one or more - * contributor license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright ownership. - * The ASF licenses this file to You under the Apache License, Version 2.0 - * (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package upstream_test - -import ( - "encoding/json" - "io/ioutil" - "net/http" - "strconv" - "time" - - . "github.com/onsi/ginkgo/v2" - . "github.com/onsi/gomega" - - "github.com/apisix/manager-api/test/e2e/base" -) - -var nodes []map[string]interface{} = []map[string]interface{}{ - { - "host": base.UpstreamIp, - "port": 1980, - "weight": 1, - "priority": 10, - }, - { - "host": base.UpstreamIp, - "port": 1981, - "weight": 1, - "priority": 10, - }, -} - -var _ = Describe("Upstream chash hash on custom header", func() { - It("create chash upstream with hash_on (custom_header)", func() { - createUpstreamBody := make(map[string]interface{}) - createUpstreamBody["nodes"] = nodes - createUpstreamBody["type"] = "chash" - createUpstreamBody["key"] = "custom_header" - createUpstreamBody["hash_on"] = "header" - _createUpstreamBody, err := json.Marshal(createUpstreamBody) - Expect(err).To(BeNil()) - base.RunTestCase(base.HttpTestCase{ - Object: base.ManagerApiExpect(), - Method: http.MethodPut, - Path: "/apisix/admin/upstreams/1", - Body: string(_createUpstreamBody), - Headers: map[string]string{"Authorization": base.GetToken()}, - ExpectStatus: http.StatusOK, - }) - }) - It("create route using the upstream just created", func() { - base.RunTestCase(base.HttpTestCase{ - Object: base.ManagerApiExpect(), - Method: http.MethodPut, - Path: "/apisix/admin/routes/1", - Body: `{ - "name": "route1", - "uri": "/server_port", - "upstream_id": "1" - }`, - Headers: map[string]string{"Authorization": base.GetToken()}, - ExpectStatus: http.StatusOK, - Sleep: base.SleepTime, - }) - }) - It("hit routes(upstream hash_on (custom_header))", func() { - time.Sleep(time.Duration(500) * time.Millisecond) - basepath := base.APISIXHost - res := map[string]int{} - for i := 0; i <= 3; i++ { - url := basepath + "/server_port?var=2&var2=" + strconv.Itoa(i) - req, err := http.NewRequest("GET", url, nil) - Expect(err).To(BeNil()) - req.Header.Add("custom_header", `custom-one`) - resp, err := http.DefaultClient.Do(req) - Expect(err).To(BeNil()) - defer resp.Body.Close() - respBody, err := ioutil.ReadAll(resp.Body) - Expect(err).To(BeNil()) - body := string(respBody) - if _, ok := res[body]; !ok { - res[body] = 1 - } else { - res[body]++ - } - } - // it is possible to hit any one of upstreams, and only one will be hit - Expect(res["1980"] == 4 || res["1981"] == 4).Should(BeTrue()) - }) - It("delete route", func() { - base.RunTestCase(base.HttpTestCase{ - Object: base.ManagerApiExpect(), - Method: http.MethodDelete, - Path: "/apisix/admin/routes/1", - Headers: map[string]string{"Authorization": base.GetToken()}, - ExpectStatus: http.StatusOK, - }) - }) - It("delete upstream", func() { - base.RunTestCase(base.HttpTestCase{ - Object: base.ManagerApiExpect(), - Method: http.MethodDelete, - Path: "/apisix/admin/upstreams/1", - Headers: map[string]string{"Authorization": base.GetToken()}, - ExpectStatus: http.StatusOK, - }) - }) - It("hit the route just deleted", func() { - base.RunTestCase(base.HttpTestCase{ - Object: base.APISIXExpect(), - Method: http.MethodGet, - Path: "/server_port", - ExpectStatus: http.StatusNotFound, - ExpectBody: "{\"error_msg\":\"404 Route Not Found\"}\n", - Sleep: base.SleepTime, - }) - }) -}) - -var _ = Describe("Upstream chash hash on cookie", func() { - It("create chash upstream with hash_on (cookie)", func() { - createUpstreamBody := make(map[string]interface{}) - createUpstreamBody["nodes"] = nodes - createUpstreamBody["type"] = "chash" - createUpstreamBody["key"] = "custom_cookie" - createUpstreamBody["hash_on"] = "cookie" - _createUpstreamBody, err := json.Marshal(createUpstreamBody) - Expect(err).To(BeNil()) - base.RunTestCase(base.HttpTestCase{ - Object: base.ManagerApiExpect(), - Method: http.MethodPut, - Path: "/apisix/admin/upstreams/1", - Body: string(_createUpstreamBody), - Headers: map[string]string{"Authorization": base.GetToken()}, - ExpectStatus: http.StatusOK, - }) - }) - It("create route using the upstream just created", func() { - base.RunTestCase(base.HttpTestCase{ - Object: base.ManagerApiExpect(), - Method: http.MethodPut, - Path: "/apisix/admin/routes/1", - Body: `{ - "name": "route1", - "uri": "/server_port", - "upstream_id": "1" - }`, - Headers: map[string]string{"Authorization": base.GetToken()}, - ExpectStatus: http.StatusOK, - Sleep: base.SleepTime, - }) - }) - It("hit routes(upstream hash_on (custom_cookie))", func() { - time.Sleep(time.Duration(500) * time.Millisecond) - basepath := base.APISIXHost - res := map[string]int{} - for i := 0; i <= 3; i++ { - url := basepath + "/server_port" - req, err := http.NewRequest("GET", url, nil) - Expect(err).To(BeNil()) - req.Header.Add("Cookie", `custom-cookie=cuscookie`) - resp, err := http.DefaultClient.Do(req) - Expect(err).To(BeNil()) - defer resp.Body.Close() - respBody, err := ioutil.ReadAll(resp.Body) - Expect(err).To(BeNil()) - body := string(respBody) - if _, ok := res[body]; !ok { - res[body] = 1 - } else { - res[body]++ - } - } - // it is possible to hit any one of upstreams, and only one will be hit - Expect(res["1980"] == 4 || res["1981"] == 4).Should(BeTrue()) - }) - It("hit routes(upstream hash_on (miss_custom_cookie))", func() { - time.Sleep(time.Duration(500) * time.Millisecond) - basepath := base.APISIXHost - res := map[string]int{} - for i := 0; i <= 3; i++ { - url := basepath + "/server_port" - req, err := http.NewRequest("GET", url, nil) - Expect(err).To(BeNil()) - req.Header.Add("Cookie", `miss-custom-cookie=cuscookie`) - resp, err := http.DefaultClient.Do(req) - Expect(err).To(BeNil()) - defer resp.Body.Close() - respBody, err := ioutil.ReadAll(resp.Body) - Expect(err).To(BeNil()) - body := string(respBody) - if _, ok := res[body]; !ok { - res[body] = 1 - } else { - res[body]++ - } - } - // it is possible to hit any one of upstreams, and only one will be hit - Expect(res["1980"] == 4 || res["1981"] == 4).Should(BeTrue()) - }) - It("delete route", func() { - base.RunTestCase(base.HttpTestCase{ - Object: base.ManagerApiExpect(), - Method: http.MethodDelete, - Path: "/apisix/admin/routes/1", - Headers: map[string]string{"Authorization": base.GetToken()}, - ExpectStatus: http.StatusOK, - }) - }) - It("delete upstream", func() { - base.RunTestCase(base.HttpTestCase{ - Object: base.ManagerApiExpect(), - Method: http.MethodDelete, - Path: "/apisix/admin/upstreams/1", - Headers: map[string]string{"Authorization": base.GetToken()}, - ExpectStatus: http.StatusOK, - }) - }) - It("hit the route just deleted", func() { - base.RunTestCase(base.HttpTestCase{ - Object: base.APISIXExpect(), - Method: http.MethodGet, - Path: "/server_port", - ExpectStatus: http.StatusNotFound, - ExpectBody: "{\"error_msg\":\"404 Route Not Found\"}\n", - Sleep: base.SleepTime, - }) - }) -}) - -var _ = Describe("Upstream key contains uppercase letters and hyphen", func() { - It("create chash upstream with key contains uppercase letters and hyphen", func() { - createUpstreamBody := make(map[string]interface{}) - createUpstreamBody["nodes"] = nodes - createUpstreamBody["type"] = "chash" - createUpstreamBody["key"] = "X-Sessionid" - createUpstreamBody["hash_on"] = "header" - _createUpstreamBody, err := json.Marshal(createUpstreamBody) - Expect(err).To(BeNil()) - base.RunTestCase(base.HttpTestCase{ - Object: base.ManagerApiExpect(), - Method: http.MethodPut, - Path: "/apisix/admin/upstreams/1", - Body: string(_createUpstreamBody), - Headers: map[string]string{"Authorization": base.GetToken()}, - ExpectStatus: http.StatusOK, - }) - }) - It("create route using the upstream just created", func() { - base.RunTestCase(base.HttpTestCase{ - Object: base.ManagerApiExpect(), - Method: http.MethodPut, - Path: "/apisix/admin/routes/1", - Body: `{ - "name": "route1", - "uri": "/server_port", - "upstream_id": "1" - }`, - Headers: map[string]string{"Authorization": base.GetToken()}, - ExpectStatus: http.StatusOK, - Sleep: base.SleepTime, - }) - }) - It("hit routes(upstream hash_on (X-Sessionid)", func() { - time.Sleep(time.Duration(500) * time.Millisecond) - basepath := base.APISIXHost - res := map[string]int{} - for i := 0; i <= 15; i++ { - url := basepath + "/server_port" - req, err := http.NewRequest("GET", url, nil) - req.Header.Add("X-Sessionid", `chash_val_`+strconv.Itoa(i)) - resp, err := http.DefaultClient.Do(req) - Expect(err).To(BeNil()) - defer resp.Body.Close() - respBody, err := ioutil.ReadAll(resp.Body) - body := string(respBody) - if _, ok := res[body]; !ok { - res[body] = 1 - } else { - res[body]++ - } - } - // the X-Sessionid of each request is different, the weight of upstreams are the same, so these requests will be sent to each upstream equally - Expect(res["1980"] == 8 && res["1981"] == 8).Should(BeTrue()) - }) - It("delete route", func() { - base.RunTestCase(base.HttpTestCase{ - Object: base.ManagerApiExpect(), - Method: http.MethodDelete, - Path: "/apisix/admin/routes/1", - Headers: map[string]string{"Authorization": base.GetToken()}, - ExpectStatus: http.StatusOK, - }) - }) - It("delete upstream", func() { - base.RunTestCase(base.HttpTestCase{ - Object: base.ManagerApiExpect(), - Method: http.MethodDelete, - Path: "/apisix/admin/upstreams/1", - Headers: map[string]string{"Authorization": base.GetToken()}, - ExpectStatus: http.StatusOK, - }) - }) - It("hit the route just deleted", func() { - base.RunTestCase(base.HttpTestCase{ - Object: base.APISIXExpect(), - Method: http.MethodGet, - Path: "/server_port", - ExpectStatus: http.StatusNotFound, - ExpectBody: "{\"error_msg\":\"404 Route Not Found\"}\n", - Sleep: base.SleepTime, - }) - }) -}) - -var _ = Describe("Upstream chash hash on consumer", func() { - It("create consumer with key-auth", func() { - base.RunTestCase(base.HttpTestCase{ - Object: base.ManagerApiExpect(), - Method: http.MethodPut, - Path: "/apisix/admin/consumers", - Body: `{ - "username": "jack", - "plugins": { - "key-auth": { - "key": "auth-jack" - } - } - }`, - Headers: map[string]string{"Authorization": base.GetToken()}, - ExpectStatus: http.StatusOK, - }) - }) - It("create chash upstream with hash_on (consumer)", func() { - createUpstreamBody := make(map[string]interface{}) - createUpstreamBody["nodes"] = nodes - createUpstreamBody["type"] = "chash" - createUpstreamBody["hash_on"] = "consumer" - _createUpstreamBody, err := json.Marshal(createUpstreamBody) - Expect(err).To(BeNil()) - base.RunTestCase(base.HttpTestCase{ - Object: base.ManagerApiExpect(), - Method: http.MethodPut, - Path: "/apisix/admin/upstreams/1", - Body: string(_createUpstreamBody), - Headers: map[string]string{"Authorization": base.GetToken()}, - ExpectStatus: http.StatusOK, - }) - }) - It("create route using the upstream just created", func() { - base.RunTestCase(base.HttpTestCase{ - Object: base.ManagerApiExpect(), - Method: http.MethodPut, - Path: "/apisix/admin/routes/1", - Body: `{ - "name": "route1", - "uri": "/server_port", - "plugins": { - "key-auth": {} - }, - "upstream_id": "1" - }`, - Headers: map[string]string{"Authorization": base.GetToken()}, - ExpectStatus: http.StatusOK, - Sleep: base.SleepTime, - }) - }) - It("hit routes(upstream hash_on (consumer))", func() { - time.Sleep(time.Duration(500) * time.Millisecond) - basepath := base.APISIXHost - res := map[string]int{} - for i := 0; i <= 3; i++ { - url := basepath + "/server_port" - req, err := http.NewRequest("GET", url, nil) - Expect(err).To(BeNil()) - req.Header.Add("apikey", `auth-jack`) - resp, err := http.DefaultClient.Do(req) - Expect(err).To(BeNil()) - defer resp.Body.Close() - respBody, err := ioutil.ReadAll(resp.Body) - Expect(err).To(BeNil()) - body := string(respBody) - if _, ok := res[body]; !ok { - res[body] = 1 - } else { - res[body]++ - } - } - // it is possible to hit any one of upstreams, and only one will be hit - Expect(res["1980"] == 4 || res["1981"] == 4).Should(BeTrue()) - }) - It("delete consumer", func() { - base.RunTestCase(base.HttpTestCase{ - Object: base.ManagerApiExpect(), - Method: http.MethodDelete, - Path: "/apisix/admin/consumers/jack", - Headers: map[string]string{"Authorization": base.GetToken()}, - ExpectStatus: http.StatusOK, - }) - }) - It("delete route", func() { - base.RunTestCase(base.HttpTestCase{ - Object: base.ManagerApiExpect(), - Method: http.MethodDelete, - Path: "/apisix/admin/routes/1", - Headers: map[string]string{"Authorization": base.GetToken()}, - ExpectStatus: http.StatusOK, - }) - }) - It("delete upstream", func() { - base.RunTestCase(base.HttpTestCase{ - Object: base.ManagerApiExpect(), - Method: http.MethodDelete, - Path: "/apisix/admin/upstreams/1", - Headers: map[string]string{"Authorization": base.GetToken()}, - ExpectStatus: http.StatusOK, - }) - }) - It("hit the route just deleted", func() { - base.RunTestCase(base.HttpTestCase{ - Object: base.APISIXExpect(), - Method: http.MethodGet, - Path: "/server_port", - ExpectStatus: http.StatusNotFound, - ExpectBody: "{\"error_msg\":\"404 Route Not Found\"}\n", - Sleep: base.SleepTime, - }) - }) -}) - -var _ = Describe("Upstream chash hash on wrong key", func() { - It("verify upstream with wrong key", func() { - createUpstreamBody := make(map[string]interface{}) - createUpstreamBody["nodes"] = nodes - createUpstreamBody["type"] = "chash" - createUpstreamBody["key"] = "not_support" - _createUpstreamBody, err := json.Marshal(createUpstreamBody) - Expect(err).To(BeNil()) - base.RunTestCase(base.HttpTestCase{ - Object: base.ManagerApiExpect(), - Method: http.MethodPut, - Path: "/apisix/admin/upstreams/2", - Body: string(_createUpstreamBody), - Headers: map[string]string{"Authorization": base.GetToken()}, - ExpectStatus: http.StatusBadRequest, - ExpectBody: "schema validate failed: (root): Does not match pattern '^((uri|server_name|server_addr|request_uri|remote_port|remote_addr|query_string|host|hostname)|arg_[0-9a-zA-z_-]+)", - }) - }) - It("verify upstream with wrong key", func() { - base.RunTestCase(base.HttpTestCase{ - Object: base.ManagerApiExpect(), - Method: http.MethodGet, - Path: "/apisix/admin/upstreams/2", - Headers: map[string]string{"Authorization": base.GetToken()}, - ExpectStatus: http.StatusNotFound, - Sleep: base.SleepTime, - }) - }) -}) - -var _ = Describe("Upstream chash hash on vars", func() { - It("create chash upstream hash_on (vars)", func() { - createUpstreamBody := make(map[string]interface{}) - createUpstreamBody["nodes"] = nodes - createUpstreamBody["type"] = "chash" - createUpstreamBody["hash_on"] = "vars" - createUpstreamBody["key"] = "arg_device_id" - _createUpstreamBody, err := json.Marshal(createUpstreamBody) - Expect(err).To(BeNil()) - base.RunTestCase(base.HttpTestCase{ - Object: base.ManagerApiExpect(), - Method: http.MethodPut, - Path: "/apisix/admin/upstreams/1", - Body: string(_createUpstreamBody), - Headers: map[string]string{"Authorization": base.GetToken()}, - ExpectStatus: http.StatusOK, - }) - }) - - It("verify upstream", func() { - base.RunTestCase(base.HttpTestCase{ - Object: base.ManagerApiExpect(), - Method: http.MethodGet, - Path: "/apisix/admin/upstreams/1", - Headers: map[string]string{"Authorization": base.GetToken()}, - ExpectStatus: http.StatusOK, - ExpectBody: "\"nodes\":[{\"host\":\"" + base.UpstreamIp + "\",\"port\":1980,\"weight\":1,\"priority\":10},{\"host\":\"" + base.UpstreamIp + "\",\"port\":1981,\"weight\":1,\"priority\":10}],\"type\":\"chash\",\"hash_on\":\"vars\",\"key\":\"arg_device_id\"", - Sleep: base.SleepTime, - }) - }) - It("create route using the upstream just created", func() { - base.RunTestCase(base.HttpTestCase{ - Object: base.ManagerApiExpect(), - Method: http.MethodPut, - Path: "/apisix/admin/routes/1", - Body: `{ - "name": "route1", - "uri": "/server_port", - "upstream_id": "1" - }`, - Headers: map[string]string{"Authorization": base.GetToken()}, - ExpectStatus: http.StatusOK, - Sleep: base.SleepTime, - }) - }) - It("verify route", func() { - base.RunTestCase(base.HttpTestCase{ - Object: base.ManagerApiExpect(), - Method: http.MethodGet, - Path: "/apisix/admin/routes/1", - Headers: map[string]string{"Authorization": base.GetToken()}, - ExpectStatus: http.StatusOK, - ExpectBody: "\"uri\":\"/server_port\",\"name\":\"route1\",\"upstream_id\":\"1\"", - Sleep: base.SleepTime, - }) - }) - It("hit routes(upstream hash_on (var))", func() { - time.Sleep(time.Duration(500) * time.Millisecond) - basepath := base.APISIXHost - res := map[string]int{} - for i := 0; i <= 17; i++ { - url := basepath + "/server_port?device_id=" + strconv.Itoa(i) - req, err := http.NewRequest("GET", url, nil) - resp, err := http.DefaultClient.Do(req) - Expect(err).To(BeNil()) - defer resp.Body.Close() - respBody, err := ioutil.ReadAll(resp.Body) - body := string(respBody) - if _, ok := res[body]; !ok { - res[body] = 1 - } else { - res[body]++ - } - } - Expect(res["1980"] == 9 && res["1981"] == 9).Should(BeTrue()) - }) - It("delete route", func() { - base.RunTestCase(base.HttpTestCase{ - Object: base.ManagerApiExpect(), - Method: http.MethodDelete, - Path: "/apisix/admin/routes/1", - Headers: map[string]string{"Authorization": base.GetToken()}, - ExpectStatus: http.StatusOK, - }) - }) - It("delete upstream", func() { - base.RunTestCase(base.HttpTestCase{ - Object: base.ManagerApiExpect(), - Method: http.MethodDelete, - Path: "/apisix/admin/upstreams/1", - Headers: map[string]string{"Authorization": base.GetToken()}, - ExpectStatus: http.StatusOK, - }) - }) - It("hit the route just deleted", func() { - base.RunTestCase(base.HttpTestCase{ - Object: base.APISIXExpect(), - Method: http.MethodGet, - Path: "/server_port", - ExpectStatus: http.StatusNotFound, - ExpectBody: "{\"error_msg\":\"404 Route Not Found\"}\n", - Sleep: base.SleepTime, - }) - }) -}) diff --git a/api/test/e2e/upstream/upstream_chash_query_string_arg_xxx_test.go b/api/test/e2e/upstream/upstream_chash_query_string_arg_xxx_test.go deleted file mode 100644 index e0c88a48f1..0000000000 --- a/api/test/e2e/upstream/upstream_chash_query_string_arg_xxx_test.go +++ /dev/null @@ -1,226 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one or more - * contributor license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright ownership. - * The ASF licenses this file to You under the Apache License, Version 2.0 - * (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package upstream_test - -import ( - "encoding/json" - "io/ioutil" - "net/http" - "sort" - "strconv" - "time" - - . "github.com/onsi/ginkgo/v2" - . "github.com/onsi/gomega" - - "github.com/apisix/manager-api/test/e2e/base" -) - -var createUpstreamBody map[string]interface{} = map[string]interface{}{ - "nodes": []map[string]interface{}{ - { - "host": base.UpstreamIp, - "port": 1980, - "weight": 1, - }, - { - "host": base.UpstreamIp, - "port": 1981, - "weight": 1, - }, - { - "host": base.UpstreamIp, - "port": 1982, - "weight": 1, - }, - }, - "type": "chash", -} - -var _ = Describe("Upstream chash query string", func() { - It("create chash upstream with key (query_string)", func() { - createUpstreamBody["key"] = "query_string" - _createUpstreamBody, err := json.Marshal(createUpstreamBody) - Expect(err).To(BeNil()) - base.RunTestCase(base.HttpTestCase{ - Object: base.ManagerApiExpect(), - Method: http.MethodPut, - Path: "/apisix/admin/upstreams/1", - Body: string(_createUpstreamBody), - Headers: map[string]string{"Authorization": base.GetToken()}, - ExpectStatus: http.StatusOK, - ExpectBody: []string{"\"id\":\"1\"", "\"key\":\"query_string\""}, - }) - }) - It("create route using the upstream just created", func() { - base.RunTestCase(base.HttpTestCase{ - Object: base.ManagerApiExpect(), - Method: http.MethodPut, - Path: "/apisix/admin/routes/1", - Body: `{ - "name": "route1", - "uri": "/server_port", - "upstream_id": "1" - }`, - Headers: map[string]string{"Authorization": base.GetToken()}, - ExpectStatus: http.StatusOK, - Sleep: base.SleepTime, - }) - }) - It("hit routes(upstream query_string)", func() { - time.Sleep(time.Duration(500) * time.Millisecond) - basepath := base.APISIXHost - res := map[string]int{} - for i := 0; i < 180; i++ { - url := basepath + "/server_port?var=2&var2=" + strconv.Itoa(i) - req, err := http.NewRequest("GET", url, nil) - Expect(err).To(BeNil()) - resp, err := http.DefaultClient.Do(req) - Expect(err).To(BeNil()) - defer resp.Body.Close() - respBody, err := ioutil.ReadAll(resp.Body) - Expect(err).To(BeNil()) - body := string(respBody) - if _, ok := res[body]; !ok { - res[body] = 1 - } else { - res[body]++ - } - } - var counts []int - for _, value := range res { - counts = append(counts, value) - } - sort.Ints(counts) - Expect(float64(counts[2]-counts[0]) / float64(counts[1])).Should(BeNumerically("<", 0.4)) - }) - It("delete route", func() { - base.RunTestCase(base.HttpTestCase{ - Object: base.ManagerApiExpect(), - Method: http.MethodDelete, - Path: "/apisix/admin/routes/1", - Headers: map[string]string{"Authorization": base.GetToken()}, - ExpectStatus: http.StatusOK, - }) - }) - It("delete upstream", func() { - base.RunTestCase(base.HttpTestCase{ - Object: base.ManagerApiExpect(), - Method: http.MethodDelete, - Path: "/apisix/admin/upstreams/1", - Headers: map[string]string{"Authorization": base.GetToken()}, - ExpectStatus: http.StatusOK, - }) - }) - It("hit the route just deleted", func() { - base.RunTestCase(base.HttpTestCase{ - Object: base.APISIXExpect(), - Method: http.MethodGet, - Path: "/hello", - ExpectStatus: http.StatusNotFound, - ExpectBody: "{\"error_msg\":\"404 Route Not Found\"}\n", - Sleep: base.SleepTime, - }) - }) -}) - -var _ = Describe("Upstream chash query string", func() { - It("create chash upstream with key (arg_xxx)", func() { - createUpstreamBody["key"] = "arg_device_id" - _createUpstreamBody, err := json.Marshal(createUpstreamBody) - Expect(err).To(BeNil()) - base.RunTestCase(base.HttpTestCase{ - Object: base.ManagerApiExpect(), - Method: http.MethodPut, - Path: "/apisix/admin/upstreams/1", - Body: string(_createUpstreamBody), - Headers: map[string]string{"Authorization": base.GetToken()}, - ExpectStatus: http.StatusOK, - ExpectBody: []string{"\"id\":\"1\"", "\"key\":\"arg_device_id\""}, - }) - }) - It("create route using the upstream just created", func() { - base.RunTestCase(base.HttpTestCase{ - Object: base.ManagerApiExpect(), - Method: http.MethodPut, - Path: "/apisix/admin/routes/1", - Body: `{ - "name": "route1", - "uri": "/server_port", - "upstream_id": "1" - }`, - Headers: map[string]string{"Authorization": base.GetToken()}, - ExpectStatus: http.StatusOK, - Sleep: base.SleepTime, - }) - }) - It("hit routes(upstream arg_device_id)", func() { - time.Sleep(time.Duration(500) * time.Millisecond) - basepath := base.APISIXHost - res := map[string]int{} - for i := 0; i <= 17; i++ { - url := basepath + "/server_port?device_id=" + strconv.Itoa(i) - req, err := http.NewRequest("GET", url, nil) - Expect(err).To(BeNil()) - resp, err := http.DefaultClient.Do(req) - Expect(err).To(BeNil()) - defer resp.Body.Close() - respBody, err := ioutil.ReadAll(resp.Body) - Expect(err).To(BeNil()) - body := string(respBody) - if _, ok := res[body]; !ok { - res[body] = 1 - } else { - res[body]++ - } - } - var counts []int - for _, value := range res { - counts = append(counts, value) - } - sort.Ints(counts) - Expect(float64(counts[2]-counts[0]) / float64(counts[1])).Should(BeNumerically("<", 0.4)) - }) - It("delete route", func() { - base.RunTestCase(base.HttpTestCase{ - Object: base.ManagerApiExpect(), - Method: http.MethodDelete, - Path: "/apisix/admin/routes/1", - Headers: map[string]string{"Authorization": base.GetToken()}, - ExpectStatus: http.StatusOK, - }) - }) - It("delete upstream", func() { - base.RunTestCase(base.HttpTestCase{ - Object: base.ManagerApiExpect(), - Method: http.MethodDelete, - Path: "/apisix/admin/upstreams/1", - Headers: map[string]string{"Authorization": base.GetToken()}, - ExpectStatus: http.StatusOK, - }) - }) - It("hit the route just deleted", func() { - base.RunTestCase(base.HttpTestCase{ - Object: base.APISIXExpect(), - Method: http.MethodGet, - Path: "/hello", - ExpectStatus: http.StatusNotFound, - ExpectBody: "{\"error_msg\":\"404 Route Not Found\"}\n", - Sleep: base.SleepTime, - }) - }) -}) diff --git a/api/test/e2e/upstream/upstream_keepalive_pool_test.go b/api/test/e2e/upstream/upstream_keepalive_pool_test.go deleted file mode 100644 index a443c97c45..0000000000 --- a/api/test/e2e/upstream/upstream_keepalive_pool_test.go +++ /dev/null @@ -1,149 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one or more - * contributor license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright ownership. - * The ASF licenses this file to You under the Apache License, Version 2.0 - * (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package upstream_test - -import ( - "encoding/json" - "net/http" - - . "github.com/onsi/ginkgo/v2" - . "github.com/onsi/gomega" - - "github.com/apisix/manager-api/test/e2e/base" -) - -// just test for schema check -var _ = Describe("Upstream keepalive pool", func() { - It("create upstream with keepalive pool", func() { - createUpstreamBody := make(map[string]interface{}) - createUpstreamBody["nodes"] = []map[string]interface{}{ - { - "host": base.UpstreamIp, - "port": 1980, - "weight": 1, - }, - } - createUpstreamBody["type"] = "roundrobin" - createUpstreamBody["keepalive_pool"] = map[string]interface{}{ - "size": 320, - "requests": 1000, - "idle_timeout": 60, - } - _createUpstreamBody, err := json.Marshal(createUpstreamBody) - Expect(err).To(BeNil()) - base.RunTestCase(base.HttpTestCase{ - Object: base.ManagerApiExpect(), - Method: http.MethodPut, - Path: "/apisix/admin/upstreams/kp", - Body: string(_createUpstreamBody), - Headers: map[string]string{"Authorization": base.GetToken()}, - ExpectStatus: http.StatusOK, - }) - }) - It("delete upstream", func() { - base.RunTestCase(base.HttpTestCase{ - Object: base.ManagerApiExpect(), - Method: http.MethodDelete, - Path: "/apisix/admin/upstreams/kp", - Headers: map[string]string{"Authorization": base.GetToken()}, - ExpectStatus: http.StatusOK, - }) - }) -}) - -// Test idle timeout zero and nil -var _ = Describe("Test Upstream keepalive pool", func() { - It("create upstream with idle_timeout zero", func() { - base.RunTestCase(base.HttpTestCase{ - Object: base.ManagerApiExpect(), - Method: http.MethodPut, - Path: "/apisix/admin/upstreams/zero_idle_timeout", - Body: `{ - "name":"upstream1", - "nodes":[{ - "host": "` + base.UpstreamIp + `", - "port": 1980, - "weight": 1 - }], - "keepalive_pool":{ - "size": 320, - "requests": 1000, - "idle_timeout": 0 - }, - "type":"roundrobin" - }`, - Headers: map[string]string{"Authorization": base.GetToken()}, - ExpectStatus: http.StatusOK, - }) - }) - - It("get upstream with idle_timeout zero", func() { - base.RunTestCase(base.HttpTestCase{ - Object: base.ManagerApiExpect(), - Method: http.MethodGet, - Path: "/apisix/admin/upstreams/zero_idle_timeout", - Headers: map[string]string{"Authorization": base.GetToken()}, - ExpectStatus: http.StatusOK, - ExpectBody: []string{`"id":"zero_idle_timeout"`, `"idle_timeout":0`, `"name":"upstream1"`}, - }) - }) - - It("create upstream with idle_timeout nil", func() { - base.RunTestCase(base.HttpTestCase{ - Object: base.ManagerApiExpect(), - Method: http.MethodPut, - Path: "/apisix/admin/upstreams/nil_idle_timeout", - Body: `{ - "name":"upstream2", - "nodes":[{ - "host":"` + base.UpstreamIp + `", - "port":1980, - "weight":1 - }], - "keepalive_pool":{ - "size": 320, - "requests": 1000 - }, - "type":"roundrobin" - }`, - Headers: map[string]string{"Authorization": base.GetToken()}, - ExpectStatus: http.StatusOK, - }) - }) - - It("get upstream with idle_timeout nil", func() { - base.RunTestCase(base.HttpTestCase{ - Object: base.ManagerApiExpect(), - Method: http.MethodGet, - Path: "/apisix/admin/upstreams/nil_idle_timeout", - Headers: map[string]string{"Authorization": base.GetToken()}, - ExpectStatus: http.StatusOK, - ExpectBody: []string{`"id":"nil_idle_timeout"`, `"name":"upstream2"`}, - UnexpectBody: []string{`"idle_timeout":0`}, - }) - }) - - It("delete upstream", func() { - base.RunTestCase(base.HttpTestCase{ - Object: base.ManagerApiExpect(), - Method: http.MethodDelete, - Path: "/apisix/admin/upstreams/zero_idle_timeout,nil_idle_timeout", - Headers: map[string]string{"Authorization": base.GetToken()}, - ExpectStatus: http.StatusOK, - }) - }) -}) diff --git a/api/test/e2e/upstream/upstream_priority_test.go b/api/test/e2e/upstream/upstream_priority_test.go deleted file mode 100644 index 6125b098dc..0000000000 --- a/api/test/e2e/upstream/upstream_priority_test.go +++ /dev/null @@ -1,174 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one or more - * contributor license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright ownership. - * The ASF licenses this file to You under the Apache License, Version 2.0 - * (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package upstream_test - -import ( - "encoding/json" - "net/http" - "time" - - . "github.com/onsi/ginkgo/v2" - . "github.com/onsi/gomega" - - "github.com/apisix/manager-api/test/e2e/base" -) - -// just test for schema check -var _ = Describe("Upstream priority", func() { - It("create upstream with priority", func() { - createUpstreamBody := make(map[string]interface{}) - createUpstreamBody["nodes"] = []map[string]interface{}{ - { - "host": base.UpstreamIp, - "port": 1980, - "weight": 1, - "priority": 10, - }, - } - createUpstreamBody["type"] = "roundrobin" - createUpstreamBody["retries"] = 5 - createUpstreamBody["retry_timeout"] = 5.5 - _createUpstreamBody, err := json.Marshal(createUpstreamBody) - Expect(err).To(BeNil()) - base.RunTestCase(base.HttpTestCase{ - Object: base.ManagerApiExpect(), - Method: http.MethodPut, - Path: "/apisix/admin/upstreams/priority", - Body: string(_createUpstreamBody), - Headers: map[string]string{"Authorization": base.GetToken()}, - ExpectStatus: http.StatusOK, - }) - }) - It("delete upstream", func() { - base.RunTestCase(base.HttpTestCase{ - Object: base.ManagerApiExpect(), - Method: http.MethodDelete, - Path: "/apisix/admin/upstreams/priority", - Headers: map[string]string{"Authorization": base.GetToken()}, - ExpectStatus: http.StatusOK, - }) - }) -}) - -// test node priority -var _ = Describe("Upstream priority", func() { - It("create upstream with priority", func() { - base.RunTestCase(base.HttpTestCase{ - Object: base.ManagerApiExpect(), - Method: http.MethodPut, - Path: "/apisix/admin/upstreams/1", - Body: `{ - "nodes":[ - { - "host": "` + base.UpstreamIp + `", - "port": 1980, - "weight": 1, - "priority": 1 - }, - { - "host": "` + base.UpstreamIp + `", - "port": 1981, - "weight": 1, - "priority": 2 - } - ], - "type": "roundrobin" - }`, - Headers: map[string]string{"Authorization": base.GetToken()}, - ExpectStatus: http.StatusOK, - }) - }) - It("create route using the upstream", func() { - base.RunTestCase(base.HttpTestCase{ - Object: base.ManagerApiExpect(), - Method: http.MethodPut, - Path: "/apisix/admin/routes/1", - Body: `{ - "name": "route1", - "uri": "/server_port", - "upstream_id": "1" - }`, - Headers: map[string]string{"Authorization": base.GetToken()}, - ExpectStatus: http.StatusOK, - Sleep: base.SleepTime, - }) - }) - It("batch test /server_port api", func() { - // sleep for etcd sync - time.Sleep(time.Duration(300) * time.Millisecond) - - // batch test /server_port api - res := base.BatchTestServerPort(12, nil, "") - - Expect(res["1980"]).Should(Equal(0)) - Expect(res["1981"]).Should(Equal(12)) - }) - It("update upstream with priority", func() { - base.RunTestCase(base.HttpTestCase{ - Object: base.ManagerApiExpect(), - Method: http.MethodPut, - Path: "/apisix/admin/upstreams/1", - Body: `{ - "nodes":[ - { - "host": "` + base.UpstreamIp + `", - "port": 1980, - "weight": 1, - "priority": 3 - }, - { - "host": "` + base.UpstreamIp + `", - "port": 1981, - "weight": 1, - "priority": 2 - } - ], - "type": "roundrobin" - }`, - Headers: map[string]string{"Authorization": base.GetToken()}, - ExpectStatus: http.StatusOK, - }) - }) - It("batch test /server_port api", func() { - // sleep for etcd sync - time.Sleep(time.Duration(300) * time.Millisecond) - - // batch test /server_port api - res := base.BatchTestServerPort(12, nil, "") - - Expect(res["1980"]).Should(Equal(12)) - Expect(res["1981"]).Should(Equal(0)) - }) - It("delete route", func() { - base.RunTestCase(base.HttpTestCase{ - Object: base.ManagerApiExpect(), - Method: http.MethodDelete, - Path: "/apisix/admin/routes/1", - Headers: map[string]string{"Authorization": base.GetToken()}, - ExpectStatus: http.StatusOK, - }) - }) - It("delete upstream", func() { - base.RunTestCase(base.HttpTestCase{ - Object: base.ManagerApiExpect(), - Method: http.MethodDelete, - Path: "/apisix/admin/upstreams/1", - Headers: map[string]string{"Authorization": base.GetToken()}, - ExpectStatus: http.StatusOK, - }) - }) -}) diff --git a/api/test/e2e/upstream/upstream_retry_test.go b/api/test/e2e/upstream/upstream_retry_test.go deleted file mode 100644 index ecef0c1b16..0000000000 --- a/api/test/e2e/upstream/upstream_retry_test.go +++ /dev/null @@ -1,132 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one or more - * contributor license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright ownership. - * The ASF licenses this file to You under the Apache License, Version 2.0 - * (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package upstream_test - -import ( - "encoding/json" - "net/http" - - . "github.com/onsi/ginkgo/v2" - . "github.com/onsi/gomega" - - "github.com/apisix/manager-api/test/e2e/base" -) - -// just test for schema check -var _ = Describe("Upstream keepalive pool", func() { - It("create upstream with keepalive pool", func() { - createUpstreamBody := make(map[string]interface{}) - createUpstreamBody["nodes"] = []map[string]interface{}{ - { - "host": base.UpstreamIp, - "port": 1980, - "weight": 1, - }, - } - createUpstreamBody["type"] = "roundrobin" - createUpstreamBody["retries"] = 5 - createUpstreamBody["retry_timeout"] = 5.5 - _createUpstreamBody, err := json.Marshal(createUpstreamBody) - Expect(err).To(BeNil()) - base.RunTestCase(base.HttpTestCase{ - Object: base.ManagerApiExpect(), - Method: http.MethodPut, - Path: "/apisix/admin/upstreams/retry", - Body: string(_createUpstreamBody), - Headers: map[string]string{"Authorization": base.GetToken()}, - ExpectStatus: http.StatusOK, - }) - }) - It("delete upstream", func() { - base.RunTestCase(base.HttpTestCase{ - Object: base.ManagerApiExpect(), - Method: http.MethodDelete, - Path: "/apisix/admin/upstreams/retry", - Headers: map[string]string{"Authorization": base.GetToken()}, - ExpectStatus: http.StatusOK, - }) - }) - It("zero retry field", func() { - createUpstreamBody := make(map[string]interface{}) - createUpstreamBody["nodes"] = []map[string]interface{}{ - { - "host": base.UpstreamIp, - "port": 1980, - "weight": 1, - }} - createUpstreamBody["type"] = "roundrobin" - createUpstreamBody["retries"] = 0 - createUpstreamBody["retry_timeout"] = 5.5 - _createUpstreamBody, err := json.Marshal(createUpstreamBody) - Expect(err).To(BeNil()) - base.RunTestCase(base.HttpTestCase{ - Object: base.ManagerApiExpect(), - Method: http.MethodPut, - Path: "/apisix/admin/upstreams/zero-retry", - Body: string(_createUpstreamBody), - Headers: map[string]string{"Authorization": base.GetToken()}, - ExpectStatus: http.StatusOK, - }) - base.RunTestCase(base.HttpTestCase{ - Object: base.ManagerApiExpect(), - Method: http.MethodGet, - Path: "/apisix/admin/upstreams/zero-retry", - Body: string(_createUpstreamBody), - Headers: map[string]string{"Authorization": base.GetToken()}, - ExpectStatus: http.StatusOK, - ExpectBody: `"retries":0`, - }) - }) - It("nil retry field", func() { - createUpstreamBody := make(map[string]interface{}) - createUpstreamBody["nodes"] = []map[string]interface{}{ - { - "host": base.UpstreamIp, - "port": 1980, - "weight": 1, - }} - createUpstreamBody["type"] = "roundrobin" - _createUpstreamBody, err := json.Marshal(createUpstreamBody) - Expect(err).To(BeNil()) - base.RunTestCase(base.HttpTestCase{ - Object: base.ManagerApiExpect(), - Method: http.MethodPut, - Path: "/apisix/admin/upstreams/zero-retry", - Body: string(_createUpstreamBody), - Headers: map[string]string{"Authorization": base.GetToken()}, - ExpectStatus: http.StatusOK, - }) - base.RunTestCase(base.HttpTestCase{ - Object: base.ManagerApiExpect(), - Method: http.MethodGet, - Path: "/apisix/admin/upstreams/zero-retry", - Headers: map[string]string{"Authorization": base.GetToken()}, - ExpectStatus: http.StatusOK, - UnexpectBody: `"retries"`, - }) - }) - It("delete upstream", func() { - base.RunTestCase(base.HttpTestCase{ - Object: base.ManagerApiExpect(), - Method: http.MethodDelete, - Path: "/apisix/admin/upstreams/zero-retry", - Headers: map[string]string{"Authorization": base.GetToken()}, - ExpectStatus: http.StatusOK, - }) - }) - -}) diff --git a/api/test/e2e/upstream/upstream_suite_test.go b/api/test/e2e/upstream/upstream_suite_test.go deleted file mode 100644 index dd5e8be264..0000000000 --- a/api/test/e2e/upstream/upstream_suite_test.go +++ /dev/null @@ -1,37 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one or more - * contributor license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright ownership. - * The ASF licenses this file to You under the Apache License, Version 2.0 - * (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package upstream_test - -import ( - "testing" - "time" - - . "github.com/onsi/ginkgo/v2" - . "github.com/onsi/gomega" - - "github.com/apisix/manager-api/test/e2e/base" -) - -func TestRoute(t *testing.T) { - RegisterFailHandler(Fail) - RunSpecs(t, "Upstream Suite") -} - -var _ = BeforeSuite(func() { - base.CleanAllResource() - time.Sleep(base.SleepTime) -}) diff --git a/api/test/e2e/upstream/upstream_test.go b/api/test/e2e/upstream/upstream_test.go deleted file mode 100644 index 69fe19ce45..0000000000 --- a/api/test/e2e/upstream/upstream_test.go +++ /dev/null @@ -1,1025 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one or more - * contributor license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright ownership. - * The ASF licenses this file to You under the Apache License, Version 2.0 - * (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package upstream_test - -import ( - "encoding/json" - "io/ioutil" - "net/http" - "time" - - . "github.com/onsi/ginkgo/v2" - . "github.com/onsi/gomega" - - "github.com/apisix/manager-api/test/e2e/base" -) - -var _ = Describe("Upstream", func() { - It("create route failed, using non-existent upstream_id", func() { - base.RunTestCase(base.HttpTestCase{ - Object: base.ManagerApiExpect(), - Method: http.MethodPut, - Path: "/apisix/admin/routes/r1", - Body: `{ - "name": "route1", - "uri": "/hello", - "upstream_id": "not-exists" - }`, - Headers: map[string]string{"Authorization": base.GetToken()}, - ExpectStatus: http.StatusBadRequest, - }) - }) - It("create upstream success", func() { - createUpstreamBody := make(map[string]interface{}) - createUpstreamBody["name"] = "upstream1" - createUpstreamBody["nodes"] = []map[string]interface{}{ - { - "host": base.UpstreamIp, - "port": 1980, - "weight": 1, - }, - } - createUpstreamBody["type"] = "roundrobin" - _createUpstreamBody, err := json.Marshal(createUpstreamBody) - Expect(err).To(BeNil()) - base.RunTestCase(base.HttpTestCase{ - Object: base.ManagerApiExpect(), - Method: http.MethodPut, - Path: "/apisix/admin/upstreams/1", - Body: string(_createUpstreamBody), - Headers: map[string]string{"Authorization": base.GetToken()}, - ExpectStatus: http.StatusOK, - }) - }) - It("create upstream2 success", func() { - createUpstreamBody := make(map[string]interface{}) - createUpstreamBody["name"] = "upstream2" - createUpstreamBody["nodes"] = []map[string]interface{}{ - { - "host": base.UpstreamIp, - "port": 1980, - "weight": 1, - }, - } - createUpstreamBody["type"] = "roundrobin" - _createUpstreamBody, err := json.Marshal(createUpstreamBody) - Expect(err).To(BeNil()) - base.RunTestCase(base.HttpTestCase{ - Object: base.ManagerApiExpect(), - Method: http.MethodPut, - Path: "/apisix/admin/upstreams/2", - Body: string(_createUpstreamBody), - Headers: map[string]string{"Authorization": base.GetToken()}, - ExpectStatus: http.StatusOK, - }) - }) - It("create upstream3 success when pass host is 'node' and nodes without port", func() { - By("create upstream3", func() { - createUpstreamBody := make(map[string]interface{}) - createUpstreamBody["name"] = "upstream3" - createUpstreamBody["nodes"] = map[string]float64{base.UpstreamIp: 100} - createUpstreamBody["type"] = "roundrobin" - createUpstreamBody["pass_host"] = "node" - - _createUpstreamBody, err := json.Marshal(createUpstreamBody) - Expect(err).To(BeNil()) - base.RunTestCase(base.HttpTestCase{ - Object: base.ManagerApiExpect(), - Method: http.MethodPut, - Path: "/apisix/admin/upstreams/3", - Body: string(_createUpstreamBody), - Headers: map[string]string{"Authorization": base.GetToken()}, - ExpectStatus: http.StatusOK, - }) - }) - - By("create route using the upstream3", func() { - base.RunTestCase(base.HttpTestCase{ - Object: base.ManagerApiExpect(), - Method: http.MethodPut, - Path: "/apisix/admin/routes/1", - Body: `{ - "name": "route1", - "uri": "/hello", - "upstream_id": "3" - }`, - Headers: map[string]string{"Authorization": base.GetToken()}, - ExpectStatus: http.StatusOK, - Sleep: base.SleepTime, - }) - }) - - By("hit the route just created", func() { - base.RunTestCase(base.HttpTestCase{ - Object: base.APISIXExpect(), - Method: http.MethodGet, - Path: "/hello", - ExpectStatus: http.StatusOK, - ExpectBody: "hello", - Sleep: base.SleepTime, - }) - }) - - By("delete route", func() { - base.RunTestCase(base.HttpTestCase{ - Object: base.ManagerApiExpect(), - Method: http.MethodDelete, - Path: "/apisix/admin/routes/1", - Headers: map[string]string{"Authorization": base.GetToken()}, - ExpectStatus: http.StatusOK, - }) - }) - }) - It("create upstream failed, name existed", func() { - createUpstreamBody := make(map[string]interface{}) - createUpstreamBody["name"] = "upstream2" - createUpstreamBody["nodes"] = []map[string]interface{}{ - { - "host": base.UpstreamIp, - "port": 1980, - "weight": 1, - }, - } - createUpstreamBody["type"] = "roundrobin" - _createUpstreamBody, err := json.Marshal(createUpstreamBody) - Expect(err).To(BeNil()) - base.RunTestCase(base.HttpTestCase{ - Object: base.ManagerApiExpect(), - Method: http.MethodPost, - Path: "/apisix/admin/upstreams", - Body: string(_createUpstreamBody), - Headers: map[string]string{"Authorization": base.GetToken()}, - ExpectStatus: http.StatusBadRequest, - ExpectBody: `upstream name exists`, - Sleep: base.SleepTime, - }) - }) - It("update upstream failed, name existed", func() { - createUpstreamBody := make(map[string]interface{}) - createUpstreamBody["name"] = "upstream1" - createUpstreamBody["nodes"] = []map[string]interface{}{ - { - "host": base.UpstreamIp, - "port": 1980, - "weight": 1, - }, - } - createUpstreamBody["type"] = "roundrobin" - _createUpstreamBody, err := json.Marshal(createUpstreamBody) - Expect(err).To(BeNil()) - base.RunTestCase(base.HttpTestCase{ - Object: base.ManagerApiExpect(), - Method: http.MethodPut, - Path: "/apisix/admin/upstreams/2", - Body: string(_createUpstreamBody), - Headers: map[string]string{"Authorization": base.GetToken()}, - ExpectStatus: http.StatusBadRequest, - ExpectBody: `upstream name exists`, - }) - }) - It("update upstream success", func() { - createUpstreamBody := make(map[string]interface{}) - createUpstreamBody["name"] = "upstream22" - createUpstreamBody["nodes"] = []map[string]interface{}{ - { - "host": base.UpstreamIp, - "port": 1980, - "weight": 1, - }, - } - createUpstreamBody["type"] = "roundrobin" - _createUpstreamBody, err := json.Marshal(createUpstreamBody) - Expect(err).To(BeNil()) - base.RunTestCase(base.HttpTestCase{ - Object: base.ManagerApiExpect(), - Method: http.MethodPut, - Path: "/apisix/admin/upstreams/2", - Body: string(_createUpstreamBody), - Headers: map[string]string{"Authorization": base.GetToken()}, - ExpectStatus: http.StatusOK, - }) - }) - It("check upstream exists by name", func() { - base.RunTestCase(base.HttpTestCase{ - Object: base.ManagerApiExpect(), - Method: http.MethodGet, - Path: "/apisix/admin/notexist/upstreams", - Query: "name=upstream1", - Headers: map[string]string{"Authorization": base.GetToken()}, - ExpectStatus: http.StatusBadRequest, - ExpectBody: "Upstream name is reduplicate", - Sleep: base.SleepTime, - }) - }) - It("upstream name list", func() { - base.RunTestCase(base.HttpTestCase{ - Object: base.ManagerApiExpect(), - Method: http.MethodGet, - Path: "/apisix/admin/names/upstreams", - Headers: map[string]string{"Authorization": base.GetToken()}, - ExpectStatus: http.StatusOK, - ExpectBody: `"name":"upstream1"`, - Sleep: base.SleepTime, - }) - }) - It("check upstream exists by name (exclude it self)", func() { - base.RunTestCase(base.HttpTestCase{ - Object: base.ManagerApiExpect(), - Method: http.MethodGet, - Path: "/apisix/admin/notexist/upstreams", - Query: "name=upstream1&exclude=1", - Headers: map[string]string{"Authorization": base.GetToken()}, - ExpectStatus: http.StatusOK, - Sleep: base.SleepTime, - }) - }) - It("create route using the upstream just created", func() { - base.RunTestCase(base.HttpTestCase{ - Object: base.ManagerApiExpect(), - Method: http.MethodPut, - Path: "/apisix/admin/routes/1", - Body: `{ - "name": "route1", - "uri": "/hello", - "upstream_id": "1" - }`, - Headers: map[string]string{"Authorization": base.GetToken()}, - ExpectStatus: http.StatusOK, - Sleep: base.SleepTime, - }) - }) - It("hit the route just created", func() { - base.RunTestCase(base.HttpTestCase{ - Object: base.APISIXExpect(), - Method: http.MethodGet, - Path: "/hello", - ExpectStatus: http.StatusOK, - ExpectBody: "hello world", - Sleep: base.SleepTime, - }) - }) - It("delete not exist upstream", func() { - base.RunTestCase(base.HttpTestCase{ - Object: base.ManagerApiExpect(), - Method: http.MethodDelete, - Path: "/apisix/admin/upstreams/not-exist", - Headers: map[string]string{"Authorization": base.GetToken()}, - ExpectStatus: http.StatusNotFound, - }) - }) - It("delete route", func() { - base.RunTestCase(base.HttpTestCase{ - Object: base.ManagerApiExpect(), - Method: http.MethodDelete, - Path: "/apisix/admin/routes/1", - Headers: map[string]string{"Authorization": base.GetToken()}, - ExpectStatus: http.StatusOK, - }) - }) - It("delete upstream", func() { - base.RunTestCase(base.HttpTestCase{ - Object: base.ManagerApiExpect(), - Method: http.MethodDelete, - Path: "/apisix/admin/upstreams/1", - Headers: map[string]string{"Authorization": base.GetToken()}, - ExpectStatus: http.StatusOK, - }) - }) - It("delete upstream2", func() { - base.RunTestCase(base.HttpTestCase{ - Object: base.ManagerApiExpect(), - Method: http.MethodDelete, - Path: "/apisix/admin/upstreams/2", - Headers: map[string]string{"Authorization": base.GetToken()}, - ExpectStatus: http.StatusOK, - }) - }) - It("delete upstream3", func() { - base.RunTestCase(base.HttpTestCase{ - Object: base.ManagerApiExpect(), - Method: http.MethodDelete, - Path: "/apisix/admin/upstreams/3", - Headers: map[string]string{"Authorization": base.GetToken()}, - ExpectStatus: http.StatusOK, - }) - }) - It("hit the route just deleted", func() { - base.RunTestCase(base.HttpTestCase{ - Object: base.APISIXExpect(), - Method: http.MethodGet, - Path: "/hello", - ExpectStatus: http.StatusNotFound, - ExpectBody: "{\"error_msg\":\"404 Route Not Found\"}\n", - Sleep: base.SleepTime, - }) - }) -}) - -var _ = DescribeTable("Upstream update with domain", - func(tc base.HttpTestCase) { - base.RunTestCase(tc) - }, - Entry("create upstream success", base.HttpTestCase{ - Object: base.ManagerApiExpect(), - Method: http.MethodPut, - Path: "/apisix/admin/upstreams/1", - Body: `{ - "name": "upstream1", - "nodes": [{ - "host": "` + base.UpstreamIp + `", - "port": 1980, - "weight": 1, - "priority": 10 - }], - "type": "roundrobin" - }`, - Headers: map[string]string{"Authorization": base.GetToken()}, - ExpectStatus: http.StatusOK, - ExpectBody: `"code":0`, - Sleep: base.SleepTime, - }), - Entry("create route using the upstream(use proxy rewriteproxy rewrite plugin)", base.HttpTestCase{ - Object: base.ManagerApiExpect(), - Method: http.MethodPut, - Path: "/apisix/admin/routes/1", - Body: `{ - "name": "route1", - "uri": "/*", - "upstream_id": "1" - }`, - Headers: map[string]string{"Authorization": base.GetToken()}, - ExpectStatus: http.StatusOK, - Sleep: base.SleepTime, - }), - Entry("update upstream with domain", base.HttpTestCase{ - Object: base.ManagerApiExpect(), - Method: http.MethodPut, - Path: "/apisix/admin/upstreams/1", - Body: `{ - "name": "upstream1", - "nodes": [{ - "host": "` + base.UpstreamHTTPBinIp + `", - "port": 80, - "weight": 1, - "priority": 10 - }], - "type": "roundrobin", - "pass_host": "node" - }`, - Headers: map[string]string{"Authorization": base.GetToken()}, - ExpectStatus: http.StatusOK, - Sleep: base.SleepTime, - }), - Entry("hit the route using upstream 1", base.HttpTestCase{ - Object: base.APISIXExpect(), - Method: http.MethodGet, - Headers: map[string]string{ - "Host": "iamhttpbin.org", - }, - Path: "/anything", - ExpectStatus: http.StatusOK, - ExpectBody: "http://iamhttpbin.org/anything", - Sleep: base.SleepTime, - }), - Entry("delete route", base.HttpTestCase{ - Object: base.ManagerApiExpect(), - Method: http.MethodDelete, - Path: "/apisix/admin/routes/1", - Headers: map[string]string{"Authorization": base.GetToken()}, - ExpectStatus: http.StatusOK, - }), - Entry("delete upstream", base.HttpTestCase{ - Object: base.ManagerApiExpect(), - Method: http.MethodDelete, - Path: "/apisix/admin/upstreams/1", - Headers: map[string]string{"Authorization": base.GetToken()}, - ExpectStatus: http.StatusOK, - }), - Entry("hit the route just deleted", base.HttpTestCase{ - Object: base.APISIXExpect(), - Method: http.MethodGet, - Path: "/get", - ExpectStatus: http.StatusNotFound, - ExpectBody: `{"error_msg":"404 Route Not Found"}`, - Sleep: base.SleepTime, - }), -) - -var _ = Describe("Upstream chash remote addr", func() { - It("create chash upstream with key (remote_addr)", func() { - createUpstreamBody := make(map[string]interface{}) - createUpstreamBody["nodes"] = []map[string]interface{}{ - { - "host": base.UpstreamIp, - "port": 1980, - "weight": 1, - }, - { - "host": base.UpstreamIp, - "port": 1981, - "weight": 1, - }, - { - "host": base.UpstreamIp, - "port": 1982, - "weight": 1, - }, - } - createUpstreamBody["type"] = "chash" - createUpstreamBody["hash_on"] = "header" - createUpstreamBody["key"] = "remote_addr" - _createUpstreamBody, err := json.Marshal(createUpstreamBody) - Expect(err).To(BeNil()) - base.RunTestCase(base.HttpTestCase{ - Object: base.ManagerApiExpect(), - Method: http.MethodPut, - Path: "/apisix/admin/upstreams/1", - Body: string(_createUpstreamBody), - Headers: map[string]string{"Authorization": base.GetToken()}, - ExpectStatus: http.StatusOK, - }) - }) - - It("create route using the upstream just created", func() { - base.RunTestCase(base.HttpTestCase{ - Object: base.ManagerApiExpect(), - Method: http.MethodPut, - Path: "/apisix/admin/routes/1", - Body: `{ - "name": "route1", - "uri": "/server_port", - "upstream_id": "1" - }`, - Headers: map[string]string{"Authorization": base.GetToken()}, - ExpectStatus: http.StatusOK, - Sleep: base.SleepTime, - }) - }) - - It("hit routes(upstream weight 1)", func() { - time.Sleep(time.Duration(500) * time.Millisecond) - basepath := base.APISIXHost - request, err := http.NewRequest("GET", basepath+"/server_port", nil) - Expect(err).To(BeNil()) - request.Header.Add("Authorization", base.GetToken()) - res := map[string]int{} - for i := 0; i < 18; i++ { - resp, err := http.DefaultClient.Do(request) - Expect(err).To(BeNil()) - defer resp.Body.Close() - respBody, err := ioutil.ReadAll(resp.Body) - Expect(err).To(BeNil()) - body := string(respBody) - if _, ok := res[body]; !ok { - res[body] = 1 - } else { - res[body]++ - } - } - Expect(res["1982"]).Should(Equal(18)) - }) - - It("create chash upstream with key (remote_addr, weight equal 0 or 1)", func() { - createUpstreamBody := make(map[string]interface{}) - createUpstreamBody["nodes"] = []map[string]interface{}{ - { - "host": base.UpstreamIp, - "port": 1980, - "weight": 1, - }, - { - "host": base.UpstreamIp, - "port": 1981, - "weight": 0, - }, - { - "host": base.UpstreamIp, - "port": 1982, - "weight": 0, - }, - } - createUpstreamBody["type"] = "chash" - createUpstreamBody["hash_on"] = "header" - createUpstreamBody["key"] = "remote_addr" - _createUpstreamBody, err := json.Marshal(createUpstreamBody) - Expect(err).To(BeNil()) - base.RunTestCase(base.HttpTestCase{ - Object: base.ManagerApiExpect(), - Method: http.MethodPut, - Path: "/apisix/admin/upstreams/1", - Body: string(_createUpstreamBody), - Headers: map[string]string{"Authorization": base.GetToken()}, - ExpectStatus: http.StatusOK, - }) - }) - It("create route using the upstream just created", func() { - base.RunTestCase(base.HttpTestCase{ - Object: base.ManagerApiExpect(), - Method: http.MethodPut, - Path: "/apisix/admin/routes/1", - Body: `{ - "name": "route1", - "uri": "/server_port", - "upstream_id": "1" - }`, - Headers: map[string]string{"Authorization": base.GetToken()}, - ExpectStatus: http.StatusOK, - Sleep: base.SleepTime, - }) - }) - It("hit routes(remote_addr, weight equal 0 or 1)", func() { - time.Sleep(time.Duration(500) * time.Millisecond) - basepath := base.APISIXHost - request, err := http.NewRequest("GET", basepath+"/server_port", nil) - Expect(err).To(BeNil()) - request.Header.Add("Authorization", base.GetToken()) - count := 0 - for i := 0; i <= 17; i++ { - resp, err := http.DefaultClient.Do(request) - Expect(err).To(BeNil()) - defer resp.Body.Close() - respBody, err := ioutil.ReadAll(resp.Body) - Expect(err).To(BeNil()) - if string(respBody) == "1980" { - count++ - } - } - Expect(count).Should(Equal(18)) - }) - It("create chash upstream with key (remote_addr, all weight equal 0)", func() { - createUpstreamBody := make(map[string]interface{}) - createUpstreamBody["nodes"] = []map[string]interface{}{ - { - "host": base.UpstreamIp, - "port": 1980, - "weight": 0, - }, - { - "host": base.UpstreamIp, - "port": 1982, - "weight": 0, - }, - } - createUpstreamBody["type"] = "chash" - createUpstreamBody["hash_on"] = "header" - createUpstreamBody["key"] = "remote_addr" - _createUpstreamBody, err := json.Marshal(createUpstreamBody) - Expect(err).To(BeNil()) - - base.RunTestCase(base.HttpTestCase{ - Object: base.ManagerApiExpect(), - Method: http.MethodPut, - Path: "/apisix/admin/upstreams/1", - Body: string(_createUpstreamBody), - Headers: map[string]string{"Authorization": base.GetToken()}, - ExpectStatus: http.StatusOK, - }) - }) - It("create route using the upstream just created", func() { - base.RunTestCase(base.HttpTestCase{ - Object: base.ManagerApiExpect(), - Method: http.MethodPut, - Path: "/apisix/admin/routes/1", - Body: `{ - "name": "route1", - "uri": "/server_port", - "upstream_id": "1" - }`, - Headers: map[string]string{"Authorization": base.GetToken()}, - ExpectStatus: http.StatusOK, - Sleep: base.SleepTime, - }) - }) - It("hit the route(remote_addr, all weight equal 0)", func() { - base.RunTestCase(base.HttpTestCase{ - Object: base.APISIXExpect(), - Method: http.MethodGet, - Path: "/server_port", - ExpectStatus: http.StatusBadGateway, - ExpectBody: "502 Bad Gateway", - Sleep: base.SleepTime, - }) - }) - It("create chash upstream u2", func() { - base.RunTestCase(base.HttpTestCase{ - Object: base.ManagerApiExpect(), - Method: http.MethodPut, - Path: "/apisix/admin/upstreams/u2", - Body: `{ - "retries": 1, - "timeout": { - "connect":15, - "send":15, - "read":15 - }, - "type":"roundrobin", - "scheme": "https", - "service_name": "USER-SERVICE", - "discovery_type": "eureka", - "name": "upstream-for-test", - "desc": "hello world" - }`, - Headers: map[string]string{"Authorization": base.GetToken()}, - ExpectStatus: http.StatusOK, - }) - }) - It("get the upstream to verify config", func() { - base.RunTestCase(base.HttpTestCase{ - Object: base.ManagerApiExpect(), - Method: http.MethodGet, - Path: "/apisix/admin/upstreams/u2", - Headers: map[string]string{"Authorization": base.GetToken()}, - ExpectStatus: http.StatusOK, - ExpectBody: `"type":"roundrobin","scheme":"https","discovery_type":"eureka"`, - Sleep: base.SleepTime, - }) - }) - It("delete route", func() { - base.RunTestCase(base.HttpTestCase{ - Object: base.ManagerApiExpect(), - Method: http.MethodDelete, - Path: "/apisix/admin/routes/1", - Headers: map[string]string{"Authorization": base.GetToken()}, - ExpectStatus: http.StatusOK, - }) - }) - It("delete upstream", func() { - base.RunTestCase(base.HttpTestCase{ - Object: base.ManagerApiExpect(), - Method: http.MethodDelete, - Path: "/apisix/admin/upstreams/1", - Headers: map[string]string{"Authorization": base.GetToken()}, - ExpectStatus: http.StatusOK, - }) - }) - It("delete upstream u2", func() { - base.RunTestCase(base.HttpTestCase{ - Object: base.ManagerApiExpect(), - Method: http.MethodDelete, - Path: "/apisix/admin/upstreams/u2", - Headers: map[string]string{"Authorization": base.GetToken()}, - ExpectStatus: http.StatusOK, - }) - }) - It("hit the route just deleted", func() { - base.RunTestCase(base.HttpTestCase{ - Object: base.APISIXExpect(), - Method: http.MethodGet, - Path: "/server_port", - ExpectStatus: http.StatusNotFound, - ExpectBody: "{\"error_msg\":\"404 Route Not Found\"}\n", - Sleep: base.SleepTime, - }) - }) -}) - -var _ = Describe("Upstream create via post", func() { - It("create upstream via POST", func() { - createUpstreamBody := make(map[string]interface{}) - createUpstreamBody["id"] = "u1" - createUpstreamBody["nodes"] = []map[string]interface{}{ - { - "host": base.UpstreamIp, - "port": 1981, - "weight": 1, - }, - } - createUpstreamBody["type"] = "roundrobin" - _createUpstreamBody, err := json.Marshal(createUpstreamBody) - Expect(err).To(BeNil()) - base.RunTestCase(base.HttpTestCase{ - Object: base.ManagerApiExpect(), - Method: http.MethodPost, - Path: "/apisix/admin/upstreams", - Body: string(_createUpstreamBody), - Headers: map[string]string{"Authorization": base.GetToken()}, - ExpectStatus: http.StatusOK, - // should return id and other request body content - ExpectBody: []string{"\"id\":\"u1\"", "\"type\":\"roundrobin\""}, - }) - }) - It("create route using the upstream just created", func() { - base.RunTestCase(base.HttpTestCase{ - Object: base.ManagerApiExpect(), - Method: http.MethodPut, - Path: "/apisix/admin/routes/1", - Body: `{ - "name": "route1", - "uri": "/hello", - "upstream_id": "u1" - }`, - Headers: map[string]string{"Authorization": base.GetToken()}, - ExpectStatus: http.StatusOK, - Sleep: base.SleepTime, - }) - }) - It("hit the route just created", func() { - base.RunTestCase(base.HttpTestCase{ - Object: base.APISIXExpect(), - Method: http.MethodGet, - Path: "/hello", - ExpectStatus: http.StatusOK, - ExpectBody: "hello world", - Sleep: base.SleepTime, - }) - }) - It("delete route", func() { - base.RunTestCase(base.HttpTestCase{ - Object: base.ManagerApiExpect(), - Method: http.MethodDelete, - Path: "/apisix/admin/routes/1", - Headers: map[string]string{"Authorization": base.GetToken()}, - ExpectStatus: http.StatusOK, - }) - }) - It("delete upstream", func() { - base.RunTestCase(base.HttpTestCase{ - Object: base.ManagerApiExpect(), - Method: http.MethodDelete, - Path: "/apisix/admin/upstreams/u1", - Headers: map[string]string{"Authorization": base.GetToken()}, - ExpectStatus: http.StatusOK, - }) - }) - It("hit the route just deleted", func() { - base.RunTestCase(base.HttpTestCase{ - Object: base.APISIXExpect(), - Method: http.MethodGet, - Path: "/hello", - ExpectStatus: http.StatusNotFound, - ExpectBody: "{\"error_msg\":\"404 Route Not Found\"}\n", - Sleep: base.SleepTime, - }) - }) -}) - -var _ = Describe("Upstream update use patch method", func() { - It("create upstream via POST", func() { - createUpstreamBody := make(map[string]interface{}) - createUpstreamBody["id"] = "u1" - createUpstreamBody["nodes"] = []map[string]interface{}{ - { - "host": base.UpstreamIp, - "port": 1981, - "weight": 1, - }, - } - createUpstreamBody["type"] = "roundrobin" - _createUpstreamBody, err := json.Marshal(createUpstreamBody) - Expect(err).To(BeNil()) - base.RunTestCase(base.HttpTestCase{ - Object: base.ManagerApiExpect(), - Method: http.MethodPost, - Path: "/apisix/admin/upstreams", - Body: string(_createUpstreamBody), - Headers: map[string]string{"Authorization": base.GetToken()}, - ExpectStatus: http.StatusOK, - // should return id and other request body content - ExpectBody: []string{"\"id\":\"u1\"", "\"type\":\"roundrobin\""}, - }) - }) - It("update upstream use patch method", func() { - createUpstreamBody := make(map[string]interface{}) - createUpstreamBody["nodes"] = []map[string]interface{}{ - { - "host": base.UpstreamIp, - "port": 1981, - "weight": 1, - "priority": 10, - }, - } - createUpstreamBody["type"] = "roundrobin" - _createUpstreamBody, err := json.Marshal(createUpstreamBody) - Expect(err).To(BeNil()) - base.RunTestCase(base.HttpTestCase{ - Object: base.ManagerApiExpect(), - Method: http.MethodPatch, - Path: "/apisix/admin/upstreams/u1", - Body: string(_createUpstreamBody), - Headers: map[string]string{"Authorization": base.GetToken()}, - ExpectStatus: http.StatusOK, - }) - }) - It("get upstream data", func() { - base.RunTestCase(base.HttpTestCase{ - Object: base.ManagerApiExpect(), - Method: http.MethodGet, - Path: "/apisix/admin/upstreams/u1", - Headers: map[string]string{"Authorization": base.GetToken()}, - ExpectStatus: http.StatusOK, - ExpectBody: "nodes\":[{\"host\":\"" + base.UpstreamIp + "\",\"port\":1981,\"weight\":1,\"priority\":10}],\"type\":\"roundrobin\"}", - }) - }) - It("Upstream update use patch method", func() { - var nodes []map[string]interface{} = []map[string]interface{}{ - { - "host": base.UpstreamIp, - "port": 1980, - "weight": 1, - "priority": 10, - }, - } - _nodes, err := json.Marshal(nodes) - Expect(err).To(BeNil()) - base.RunTestCase(base.HttpTestCase{ - Object: base.ManagerApiExpect(), - Method: http.MethodPatch, - Path: "/apisix/admin/upstreams/u1/nodes", - Body: string(_nodes), - Headers: map[string]string{ - "Authorization": base.GetToken(), - "Content-Type": "text/plain", - }, - ExpectStatus: http.StatusOK, - }) - }) - It("get upstream data", func() { - - base.RunTestCase(base.HttpTestCase{ - Object: base.ManagerApiExpect(), - Method: http.MethodGet, - Path: "/apisix/admin/upstreams/u1", - Headers: map[string]string{"Authorization": base.GetToken()}, - ExpectStatus: http.StatusOK, - ExpectBody: "nodes\":[{\"host\":\"" + base.UpstreamIp + "\",\"port\":1980,\"weight\":1,\"priority\":10}],\"type\":\"roundrobin\"}", - }) - }) - It("delete upstream", func() { - base.RunTestCase(base.HttpTestCase{ - Object: base.ManagerApiExpect(), - Method: http.MethodDelete, - Path: "/apisix/admin/upstreams/u1", - Headers: map[string]string{"Authorization": base.GetToken()}, - ExpectStatus: http.StatusOK, - }) - }) -}) - -var _ = Describe("test upstream delete (route is in use)", func() { - DescribeTable("test upstream delete", - func(tc base.HttpTestCase) { - base.RunTestCase(tc) - }, - Entry("create upstream without plugin", base.HttpTestCase{ - Desc: "create upstream without plugin", - Object: base.ManagerApiExpect(), - Method: http.MethodPut, - Path: "/apisix/admin/upstreams/u1", - Headers: map[string]string{"Authorization": base.GetToken()}, - Body: `{ - "nodes": { - "` + base.UpstreamIp + `:1980": 1 - }, - "type": "roundrobin" - }`, - ExpectStatus: http.StatusOK, - ExpectBody: "\"id\":\"u1\"", - }), - Entry("create route use upstream r1", base.HttpTestCase{ - Desc: "create route use upstream u1", - Object: base.ManagerApiExpect(), - Method: http.MethodPut, - Path: "/apisix/admin/routes/r1", - Body: `{ - "name": "route1", - "id": "r1", - "uri": "/hello", - "upstream_id": "u1" - }`, - Headers: map[string]string{"Authorization": base.GetToken()}, - ExpectStatus: http.StatusOK, - ExpectBody: "\"upstream_id\":\"u1\"", - }), - Entry("delete upstream failed", base.HttpTestCase{ - Desc: "delete upstream failed", - Object: base.ManagerApiExpect(), - Method: http.MethodDelete, - Path: "/apisix/admin/upstreams/u1", - Headers: map[string]string{"Authorization": base.GetToken()}, - ExpectStatus: http.StatusBadRequest, - ExpectBody: "route: route1 is using this upstream", - }), - Entry("delete route first", base.HttpTestCase{ - Desc: "delete route first", - Object: base.ManagerApiExpect(), - Method: http.MethodDelete, - Path: "/apisix/admin/routes/r1", - Headers: map[string]string{"Authorization": base.GetToken()}, - ExpectStatus: http.StatusOK, - }), - Entry("check route exist", base.HttpTestCase{ - Desc: "check route exist", - Object: base.ManagerApiExpect(), - Method: http.MethodGet, - Path: "/apisix/admin/routes/r1", - Headers: map[string]string{"Authorization": base.GetToken()}, - ExpectStatus: http.StatusNotFound, - }), - Entry("delete upstream success", base.HttpTestCase{ - Desc: "delete upstream success", - Object: base.ManagerApiExpect(), - Method: http.MethodDelete, - Path: "/apisix/admin/upstreams/u1", - Headers: map[string]string{"Authorization": base.GetToken()}, - ExpectStatus: http.StatusOK, - }), - Entry("check upstream exist", base.HttpTestCase{ - Desc: "check upstream exist", - Object: base.ManagerApiExpect(), - Method: http.MethodGet, - Path: "/apisix/admin/upstreams/u1", - Headers: map[string]string{"Authorization": base.GetToken()}, - ExpectStatus: http.StatusNotFound, - })) -}) - -var _ = Describe("test upstream delete (service is in use)", func() { - DescribeTable("test upstream delete", - func(tc base.HttpTestCase) { - base.RunTestCase(tc) - }, - Entry("create upstream without plugin", base.HttpTestCase{ - Desc: "create upstream without plugin", - Object: base.ManagerApiExpect(), - Method: http.MethodPut, - Path: "/apisix/admin/upstreams/u1", - Headers: map[string]string{"Authorization": base.GetToken()}, - Body: `{ - "nodes": { - "` + base.UpstreamIp + `:1980": 1 - }, - "type": "roundrobin" - }`, - ExpectStatus: http.StatusOK, - ExpectBody: "\"id\":\"u1\"", - }), - Entry("create service use upstream r1", base.HttpTestCase{ - Desc: "create service use upstream r1", - Object: base.ManagerApiExpect(), - Method: http.MethodPut, - Path: "/apisix/admin/services/s1", - Body: `{ - "id": "s1", - "name": "service1", - "upstream_id": "u1" - }`, - Headers: map[string]string{"Authorization": base.GetToken()}, - ExpectStatus: http.StatusOK, - ExpectBody: "\"upstream_id\":\"u1\"", - }), - Entry("delete upstream failed", base.HttpTestCase{ - Desc: "delete upstream failed", - Object: base.ManagerApiExpect(), - Method: http.MethodDelete, - Path: "/apisix/admin/upstreams/u1", - Headers: map[string]string{"Authorization": base.GetToken()}, - ExpectStatus: http.StatusBadRequest, - ExpectBody: "service: service1 is using this upstream", - }), - Entry("delete service first", base.HttpTestCase{ - Desc: "delete service first", - Object: base.ManagerApiExpect(), - Method: http.MethodDelete, - Path: "/apisix/admin/services/s1", - Headers: map[string]string{"Authorization": base.GetToken()}, - ExpectStatus: http.StatusOK, - }), - Entry("check service exist", base.HttpTestCase{ - Desc: "check service exist", - Object: base.ManagerApiExpect(), - Method: http.MethodGet, - Path: "/apisix/admin/services/s1", - Headers: map[string]string{"Authorization": base.GetToken()}, - ExpectStatus: http.StatusNotFound, - }), - Entry("delete upstream success", base.HttpTestCase{ - Desc: "delete upstream success", - Object: base.ManagerApiExpect(), - Method: http.MethodDelete, - Path: "/apisix/admin/upstreams/u1", - Headers: map[string]string{"Authorization": base.GetToken()}, - ExpectStatus: http.StatusOK, - }), - Entry("check upstream exist", base.HttpTestCase{ - Desc: "check upstream exist", - Object: base.ManagerApiExpect(), - Method: http.MethodGet, - Path: "/apisix/admin/upstreams/u1", - Headers: map[string]string{"Authorization": base.GetToken()}, - ExpectStatus: http.StatusNotFound, - })) -}) diff --git a/api/test/e2e/version/version_suite_test.go b/api/test/e2e/version/version_suite_test.go deleted file mode 100644 index 2c9be355d2..0000000000 --- a/api/test/e2e/version/version_suite_test.go +++ /dev/null @@ -1,36 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one or more - * contributor license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright ownership. - * The ASF licenses this file to You under the Apache License, Version 2.0 - * (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package version_test - -import ( - "testing" - "time" - - "github.com/apisix/manager-api/test/e2e/base" - . "github.com/onsi/ginkgo/v2" - . "github.com/onsi/gomega" -) - -func TestVersion(t *testing.T) { - RegisterFailHandler(Fail) - RunSpecs(t, "Version Suite") -} - -var _ = BeforeSuite(func() { - base.CleanAllResource() - time.Sleep(base.SleepTime) -}) diff --git a/api/test/e2e/version/version_test.go b/api/test/e2e/version/version_test.go deleted file mode 100644 index b29d94eb5b..0000000000 --- a/api/test/e2e/version/version_test.go +++ /dev/null @@ -1,40 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one or more - * contributor license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright ownership. - * The ASF licenses this file to You under the Apache License, Version 2.0 - * (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package version_test - -import ( - "net/http" - - . "github.com/onsi/ginkgo/v2" - - "github.com/apisix/manager-api/test/e2e/base" -) - -var _ = Describe("Version", func() { - DescribeTable("version test", - func(tc base.HttpTestCase) { - base.RunTestCase(tc) - }, - Entry("get version", base.HttpTestCase{ - Object: base.ManagerApiExpect(), - Method: http.MethodGet, - Path: "/apisix/admin/tool/version", - ExpectStatus: http.StatusOK, - ExpectBody: []string{"commit_hash", "\"version\""}, - }), - ) -}) diff --git a/api/test/shell/cli_test.sh b/api/test/shell/cli_test.sh deleted file mode 100755 index 101edda1b6..0000000000 --- a/api/test/shell/cli_test.sh +++ /dev/null @@ -1,606 +0,0 @@ -#!/usr/bin/env bats - -# -# Licensed to the Apache Software Foundation (ASF) under one or more -# contributor license agreements. See the NOTICE file distributed with -# this work for additional information regarding copyright ownership. -# The ASF licenses this file to You under the Apache License, Version 2.0 -# (the "License"); you may not use this file except in compliance with -# the License. You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -# - -# !!!NOTICE!!! -# Try to run cli tests (such as virtual machines) in an isolated environment. -# This test will register the systemd service and run the test. Although -# environmental cleaning will eventually be running, it may still pollute -# your environment. -# -# For developers who have not modified the CLI part of the program, you do -# not need to run this test locally, but rely on the test results of GitHub -# workflow. If you have any improvement suggestions for this test case, you -# are welcome to submit a PR for modification. - -VERSION=$(cat ./VERSION) -KERNEL=$(uname -s) -CONF_FILE="/usr/local/apisix-dashboard/conf/conf.yaml" -APISIX_PROFILE_CONF_FILE="/usr/local/apisix-dashboard/conf/conf-test.yaml" -LOG_FILE="/usr/local/apisix-dashboard/logs/error.log" -ACCESS_LOG_FILE="/usr/local/apisix-dashboard/logs/access.log" -SERVICE_NAME="apisix-dashboard" - -ETCD_VERSION="3.4.20" - -if [[ -f ../.githash ]]; then - GITHASH=$(cat ../.githash) - if [[ ! $GITHASH =~ ^[a-z0-9]{7}$ ]]; then - echo "failed: verify .githash content failed" - exit 1 - fi -else - GITHASH=$(HASH="ref: HEAD"; while [[ $HASH == ref\:* ]]; do HASH="$(cat "../.git/$(echo $HASH | cut -d \ -f 2)")"; done; echo ${HASH:0:7}) -fi - -recover_conf() { - run cp -rf ./conf/conf.yaml ${CONF_FILE} - run cp -rf ./conf/conf.yaml ${APISIX_PROFILE_CONF_FILE} - [ "$status" -eq 0 ] -} -check_logfile() { - [ -f $LOG_FILE ] -} -clean_logfile() { - echo > $LOG_FILE -} - -recover_service_file() { - run cp -f ./service/apisix-dashboard.service /usr/lib/systemd/system/${SERVICE_NAME}.service - run systemctl daemon-reload - [ "$status" -eq 0 ] -} - -start_dashboard() { - run systemctl start ${SERVICE_NAME} - [ "$status" -eq 0 ] - sleep $1 -} - -stop_dashboard() { - run systemctl stop ${SERVICE_NAME} - [ "$status" -eq 0 ] - sleep $1 -} - -start_etcd() { - run docker run -d --name etcd -p 2379:2379 -e ALLOW_NONE_AUTHENTICATION=yes bitnami/etcd:${ETCD_VERSION} - [ "$status" -eq 0 ] -} - -stop_etcd() { - run docker stop etcd && docker rm etcd - [ "$status" -eq 0 ] -} - -### Test Case -#pre -@test "Build and Deploy APISIX Dashboard Manager API" { - run go build -o ./manager-api -ldflags "-X github.com/apisix/manager-api/internal/utils.version=${VERSION} -X github.com/apisix/manager-api/internal/utils.gitHash=${GITHASH}" ./main.go - [ "$status" -eq 0 ] - - # prepare service files - mkdir -p /usr/local/apisix-dashboard/conf /usr/local/apisix-dashboard/logs - cp ./conf/* /usr/local/apisix-dashboard/conf - cp ./manager-api /usr/local/apisix-dashboard - - # create systemd service - cp ./service/apisix-dashboard.service /usr/lib/systemd/system/${SERVICE_NAME}.service - run systemctl daemon-reload - [ "$status" -eq 0 ] - - # create etcd container - start_etcd -} - -#1 -@test "Check warn log level" { - start_dashboard 3 - - stop_dashboard 6 - - check_logfile - - [ $(grep -c "INFO" "${LOG_FILE}") -eq '0' ] - - clean_logfile -} - -#2 -@test "Check info log level and signal" { - if [[ $KERNEL = "Darwin" ]]; then - sed -i "" 's/level: warn/level: info/' ${CONF_FILE} - else - sed -i 's/level: warn/level: info/' ${CONF_FILE} - fi - - start_dashboard 3 - - stop_dashboard 6 - - check_logfile - - [ $(grep -c "server receive terminated" "${LOG_FILE}") -eq '1' ] - - clean_logfile -} - -#3 -@test "Check start info" { - LOGLEVEL=$(cat "$CONF_FILE" | awk '$1=="level:"{print $2}') - HOST=$(cat "$CONF_FILE" | awk '$1=="host:"{print $2}') - PORT=$(cat "$CONF_FILE" | awk '$1=="port:"{print $2}') - start_dashboard 3 - - run systemctl status ${SERVICE_NAME} - - [ $(echo "$output" | grep -c "The manager-api is running successfully\!") -eq '1' ] - [ $(echo "$output" | grep -c -w "${VERSION}") -eq '1' ] - [ $(echo "$output" | grep -c "${GITHASH}") -eq '1' ] - [ $(echo "$output" | grep -c "${LOGLEVEL}") -eq '1' ] - [ $(echo "$output" | grep -c "${HOST}:${PORT}") -eq '1' ] - - stop_dashboard 6 -} - -#4 -@test "Check version sub-command" { - run /usr/local/apisix-dashboard/manager-api version - - [ $(echo "$output" | grep -c "$VERSION") -eq '1' ] - [ $(echo "$output" | grep -c "$GITHASH") -eq '1' ] -} - -#5 -@test "Check static file server" { - # create html directory - mkdir -p /usr/local/apisix-dashboard/html - echo "hi~" >> /usr/local/apisix-dashboard/html/index.html - - # start Manager API - start_dashboard 3 - - # request index page - result=$(curl "http://127.0.0.1:9000") - [ "$result" = "hi~" ] - - stop_dashboard 6 - - recover_conf -} - -#6 -@test "Check invalid etcd endpoint" { - recover_conf - - if [[ $KERNEL = "Darwin" ]]; then - sed -i "" 's/127.0.0.1:2379/0.0.0.0:0/' ${CONF_FILE} - else - sed -i 's/127.0.0.1:2379/0.0.0.0:0/' ${CONF_FILE} - fi - - start_dashboard 6 - - run journalctl -u ${SERVICE_NAME}.service -n 30 - - [ $(echo "$output" | grep -c "Error while dialing dial tcp") -eq '1' ] - - stop_dashboard 6 -} - -#7 -@test "Check assess log" { - recover_conf - - start_dashboard 3 - - run curl http://127.0.0.1:9000/apisix/admin/user/login -H "Content-Type: application/json" -d '{"username":"admin", "password": "admin"}' - [ "$status" -eq 0 ] - - stop_dashboard 6 - - [ $(grep -c "/apisix/admin/user/login" "${ACCESS_LOG_FILE}") -ne '0' ] - - # check logging middleware - [ $(grep -c "filter/logging.go" "${ACCESS_LOG_FILE}") -ne '0' ] -} - -#8 -@test "Check ip allow list" { - recover_conf - - if [[ $KERNEL = "Darwin" ]]; then - sed -i "" 's@- 127.0.0.1 @- 10.0.0.1 @' ${CONF_FILE} - else - sed -i 's@- 127.0.0.1 @- 10.0.0.1 @' ${CONF_FILE} - fi - - start_dashboard 3 - - run curl -k -i -m 20 -o /dev/null -s -w %{http_code} "http://127.0.0.1:9000" - [ "$output" -eq 403 ] - - stop_dashboard 6 -} - -#9 -@test "Check HTTPS server" { - recover_conf - - if [[ $KERNEL = "Darwin" ]]; then - sed -i "" 's@# ssl:@ssl:@' ${CONF_FILE} - sed -i "" 's@# port: 9001@ port: 9001@' ${CONF_FILE} - sed -i "" "s@# cert: \"/tmp/cert/example.crt\"@ cert: \"$(pwd)/test/certs/test2.crt\"@" ${CONF_FILE} - sed -i "" "s@# key: \"/tmp/cert/example.key\"@ cert: \"$(pwd)/test/certs/test2.key\"@" ${CONF_FILE} - else - sed -i 's@# ssl:@ssl:@' ${CONF_FILE} - sed -i 's@# port: 9001@ port: 9001@' ${CONF_FILE} - sed -i "s@# cert: \"/tmp/cert/example.crt\"@ cert: \"$(pwd)/test/certs/test2.crt\"@" ${CONF_FILE} - sed -i "s@# key: \"/tmp/cert/example.key\"@ key: \"$(pwd)/test/certs/test2.key\"@" ${CONF_FILE} - fi - - start_dashboard 3 - - run curl -k -i -m 20 -o /dev/null -s -w %{http_code} --resolve 'www.test2.com:9001:127.0.0.1' "https://www.test2.com:9001/apisix/admin/tool/version" - [ "$output" -eq 200 ] - - stop_dashboard 6 -} - -#10 -@test "Check etcd basic auth" { - recover_conf - - # add root user - curl -L http://localhost:2379/v3/auth/user/add -X POST -d '{"name": "root", "password": "root"}' - - # add root role - curl -L http://localhost:2379/v3/auth/role/add -d '{"name": "root"}' - - # grant root role to root user - curl -L http://localhost:2379/v3/auth/user/grant -d '{"user": "root", "role": "root"}' - - # enable auth - curl -L http://localhost:2379/v3/auth/enable -d '{}' - - start_dashboard 3 - - [ $(grep -c "etcdserver: user name is empty" ${LOG_FILE}) -ne '0' ] - - stop_dashboard 6 - - # modify etcd auth config - if [[ $KERNEL = "Darwin" ]]; then - sed -i "" '1,$s/# username: "root" # ignore etcd username if not enable etcd auth/username: "root"/g' ${CONF_FILE} - sed -i "" '1,$s/# password: "123456" # ignore etcd password if not enable etcd auth/password: "root"/g' ${CONF_FILE} - else - sed -i '1,$s/# username: "root" # ignore etcd username if not enable etcd auth/username: "root"/g' ${CONF_FILE} - sed -i '1,$s/# password: "123456" # ignore etcd password if not enable etcd auth/password: "root"/g' ${CONF_FILE} - fi - - start_dashboard 3 - - # validate process is right by requesting login api - run curl http://127.0.0.1:9000/apisix/admin/user/login -H "Content-Type: application/json" -d '{"username":"admin", "password": "admin"}' - token=$(echo "$output" | sed 's/{/\n/g' | sed 's/,/\n/g' | grep "token" | sed 's/:/\n/g' | sed '1d' | sed 's/}//g' | sed 's/"//g') - - [ -n "${token}" ] - - # more validation to make sure it's ok to access etcd - run curl -ig -XPUT http://127.0.0.1:9000/apisix/admin/consumers -i -H "Content-Type: application/json" -H "Authorization: $token" -d '{"username":"etcd_basic_auth_test"}' - respCode=$(echo "$output" | sed 's/{/\n/g'| sed 's/,/\n/g' | grep "code" | sed 's/:/\n/g' | sed '1d') - respMessage=$(echo "$output" | sed 's/{/\n/g'| sed 's/,/\n/g' | grep "message" | sed 's/:/\n/g' | sed '1d') - - [ "$respCode" = "0" ] - [ "$respMessage" = "\"\"" ] - - run curl "http://127.0.0.1:9000/apisix/admin/tool/version" - - [ $(echo "$output" | grep -c "${VERSION}") -eq '1' ] - [ $(echo "$output" | grep -c "${GITHASH}") -eq '1' ] - - check_logfile - - stop_dashboard 6 - - # disable etcd basic auth - run curl -L http://localhost:2379/v3/auth/authenticate -X POST -d '{"name": "root", "password": "root"}' - etcd_token=$(echo "$output" |grep -oE "token\".*\"(.*)\""|awk -F[:\"] '{print $4}') - [ -n "${etcd_token}" ] - - run curl -L http://localhost:2379/v3/auth/disable -H "Authorization: ${etcd_token}" -X POST -d '' - [ "$status" -eq 0 ] -} - -#11 -@test "Check etcd prefix" { - recover_conf - - start_dashboard 3 - - run curl http://127.0.0.1:9000/apisix/admin/user/login -H "Content-Type: application/json" -d '{"username":"admin", "password": "admin"}' - [ "$status" -eq 0 ] - - token=$(echo "$output" | sed 's/{/\n/g' | sed 's/,/\n/g' | grep "token" | sed 's/:/\n/g' | sed '1d' | sed 's/}//g' | sed 's/"//g') - [ -n "${token}" ] - - prefix="/apisix" - key_base64=$(echo -n $prefix/consumers/etcd_prefix_test | base64) - - run curl -ig -XPUT http://127.0.0.1:9000/apisix/admin/consumers -i -H "Content-Type: application/json" -H "Authorization: $token" -d '{"username":"etcd_prefix_test"}' - [ "$status" -eq 0 ] - - run curl -L http://localhost:2379/v3/kv/range -X POST -d '{"key": "'"${key_base64}"'"}' - [ "$status" -eq 0 ] - - count=$(echo "$output" | grep -oE "count.*([0-9]+)" | awk -F\" '{print $3}') - [ "$count" ] - [ "$count" -eq 1 ] - - stop_dashboard 6 - - recover_conf - - # modify etcd prefix config to /apisix-test - if [[ $KERNEL = "Darwin" ]]; then - sed -i "" '1,$s/# prefix: \/apisix.*/prefix: \/apisix-test/g' ${CONF_FILE} - else - sed -i '1,$s/# prefix: \/apisix.*/prefix: \/apisix-test/g' ${CONF_FILE} - fi - - start_dashboard 3 - - run curl http://127.0.0.1:9000/apisix/admin/user/login -H "Content-Type: application/json" -d '{"username":"admin", "password": "admin"}' - [ "$status" -eq 0 ] - - token=$(echo "$output" | sed 's/{/\n/g' | sed 's/,/\n/g' | grep "token" | sed 's/:/\n/g' | sed '1d' | sed 's/}//g' | sed 's/"//g') - [ -n "${token}" ] - - prefix="/apisix-test" - key_base64=$(echo -n $prefix/consumers/etcd_prefix_test | base64) - - run curl -ig -XPUT http://127.0.0.1:9000/apisix/admin/consumers -i -H "Content-Type: application/json" -H "Authorization: $token" -d '{"username":"etcd_prefix_test"}' - [ "$status" -eq 0 ] - - run curl -L http://localhost:2379/v3/kv/range -X POST -d '{"key": "'"${key_base64}"'"}' - [ "$status" -eq 0 ] - - count=$(echo "$output" | grep -oE "count.*([0-9]+)" | awk -F\" '{print $3}') - [ "$count" ] - [ "$count" -eq 1 ] - - stop_dashboard 6 -} - -#12 -@test "Check etcd mTLS" { - recover_conf - - run ./etcd-v3.4.20-linux-amd64/etcd --name infra0 --data-dir infra0 \ - --client-cert-auth --trusted-ca-file=$(pwd)/test/certs/mtls_ca.pem --cert-file=$(pwd)/test/certs/mtls_server.pem --key-file=$(pwd)/test/certs/mtls_server-key.pem \ - --advertise-client-urls https://127.0.0.1:3379 --listen-client-urls https://127.0.0.1:3379 --listen-peer-urls http://127.0.0.1:3380 & - - if [[ $KERNEL = "Darwin" ]]; then - sed -i "" "s@key_file: \"\"@key_file: \"$(pwd)/test/certs/mtls_client-key.pem\"@g" ${CONF_FILE} - sed -i "" "s@cert_file: \"\"@cert_file: \"$(pwd)/test/certs/mtls_client.pem\"@g" ${CONF_FILE} - sed -i "" "s@ca_file: \"\"@ca_file: \"$(pwd)/test/certs/mtls_ca.pem\"@g" ${CONF_FILE} - sed -i "" 's/127.0.0.1:2379/127.0.0.1:3379/' ${CONF_FILE} - else - sed -i "s@key_file: \"\"@key_file: \"$(pwd)/test/certs/mtls_client-key.pem\"@g" ${CONF_FILE} - sed -i "s@cert_file: \"\"@cert_file: \"$(pwd)/test/certs/mtls_client.pem\"@g" ${CONF_FILE} - sed -i "s@ca_file: \"\"@ca_file: \"$(pwd)/test/certs/mtls_ca.pem\"@g" ${CONF_FILE} - sed -i 's/127.0.0.1:2379/127.0.0.1:3379/' ${CONF_FILE} - fi - - start_dashboard 3 - - run curl http://127.0.0.1:9000/apisix/admin/user/login -H "Content-Type: application/json" -d '{"username":"admin", "password": "admin"}' - [ "$status" -eq 0 ] - - token=$(echo "$output" | sed 's/{/\n/g' | sed 's/,/\n/g' | grep "token" | sed 's/:/\n/g' | sed '1d' | sed 's/}//g' | sed 's/"//g') - [ -n "${token}" ] - - run curl -ig -XPUT http://127.0.0.1:9000/apisix/admin/consumers -i -H "Content-Type: application/json" -H "Authorization: $token" -d '{"username":"etcd_mtls_test"}' - respCode=$(echo "$output" | sed 's/{/\n/g'| sed 's/,/\n/g' | grep "code" | sed 's/:/\n/g' | sed '1d') - respMessage=$(echo "$output" | sed 's/{/\n/g'| sed 's/,/\n/g' | grep "message" | sed 's/:/\n/g' | sed '1d') - - [ "$respCode" = "0" ] - [ "$respMessage" = "\"\"" ] - - stop_dashboard 6 -} - -#13 -@test "Check etcd bad data" { - recover_conf - - run ./etcd-v3.4.20-linux-amd64/etcdctl put /apisix/routes/unique1 "{\"id\":}" - [ "$status" -eq 0 ] - sleep 2 - - start_dashboard 3 - - run journalctl -u ${SERVICE_NAME}.service -n 30 - - [ $(echo "$output" | grep -c "Error occurred while initializing logical store: /apisix/routes, err: json unmarshal failed") -eq '1' ] - - run ./etcd-v3.4.20-linux-amd64/etcdctl del /apisix/routes/unique1 - [ "$status" -eq 0 ] - - stop_dashboard 6 -} - - -#14 -@test "Check APISIX_PROFILE" { - recover_conf - - start_dashboard 3 - - run journalctl -u ${SERVICE_NAME}.service -n 30 - [ $(echo "$output" | grep -c "conf.yaml") -eq '1' ] - - stop_dashboard 3 - - sed -i 's#-c /usr/local/apisix-dashboard/conf/conf.yaml##g' /usr/lib/systemd/system/${SERVICE_NAME}.service - sed -i '$a\Environment=APISIX_PROFILE=test' /usr/lib/systemd/system/${SERVICE_NAME}.service - run systemctl daemon-reload - - start_dashboard 3 - - run journalctl -u ${SERVICE_NAME}.service -n 30 - [ $(echo "$output" | grep -c "conf-test.yaml") -eq '1' ] - - stop_dashboard 3 - - recover_service_file -} - -#15 -@test "Check Security configuration" { - recover_conf - - start_dashboard 3 - - # check response header without custom header - run curl -i http://127.0.0.1:9000 - - [ $(echo "$output" | grep -c "X-Frame-Options: deny") -eq '1' ] - - stop_dashboard 6 - - sed -i 's@# security:@security:@' ${CONF_FILE} - sed -i 's@# x_frame_options: "deny"@ x_frame_options: "test"@' ${CONF_FILE} - - start_dashboard 3 - - # check response header with custom header - run curl -i http://127.0.0.1:9000 - - [ $(echo "$output" | grep -c "X-Frame-Options: test") -eq '1' ] - - stop_dashboard 6 -} - -#16 -@test "Check etcd auto re-watch" { - recover_conf - clean_logfile - - # change log level - if [[ $KERNEL = "Darwin" ]]; then - sed -i "" 's/level: warn/level: info/' ${CONF_FILE} - else - sed -i 's/level: warn/level: info/' ${CONF_FILE} - fi - - start_dashboard 15 - - [ "$(grep -c "etcd connection is fine" ${LOG_FILE})" -ge '1' ] - - run docker stop etcd - - sleep 30 - - [ "$(grep -c "etcd connection loss detected" ${LOG_FILE})" -ge '1' ] - - run docker start etcd - - sleep 20 - - [ "$(grep -c "etcd connection recovered" ${LOG_FILE})" -ge '1' ] - [ "$(grep -c "etcd store reinitializing" ${LOG_FILE})" -ge '1' ] - - stop_dashboard 6 -} - -#17 -@test "Check etcd sync auto recovery" { - recover_conf - - sed -i 's/level: warn/level: info/' ${CONF_FILE} - - start_dashboard 15 - - [ "$(grep -c "etcd connection is fine" ${LOG_FILE})" -ge '1' ] - - # stop and remove old etcd container - stop_etcd - - sleep 30 - - [ "$(grep -c "etcd connection loss detected" ${LOG_FILE})" -ge '1' ] - - # create temporary dir for etcd persistence - tmp="$(sudo su - runner sh -c 'mktemp -d')" - - # create etcd other port and enable persistence - # the etcd on a new port for etcdctl access but not for dashboard - run docker run -d --name etcd -p 12379:2379 -e ALLOW_NONE_AUTHENTICATION=yes -v "${tmp}:/bitnami/etcd" bitnami/etcd:${ETCD_VERSION} - [ "$status" -eq 0 ] - - # write some test data - run ./etcd-v3.4.20-linux-amd64/etcdctl --endpoints 127.0.0.1:12379 put /apisix/routes/new1 '{"uri":"/new1","upstream":{"nodes":{"127.0.0.1:80":1},"type":"roundrobin"}}' - [ "$status" -eq 0 ] - run ./etcd-v3.4.20-linux-amd64/etcdctl --endpoints 127.0.0.1:12379 put /apisix/routes/new2 '{"uri":"/new2","upstream":{"nodes":{"127.0.0.1:80":1},"type":"roundrobin"}}' - [ "$status" -eq 0 ] - - stop_etcd - - # create etcd with existence etcd data - run docker run -d --name etcd -p 2379:2379 -e ALLOW_NONE_AUTHENTICATION=yes -v "${tmp}:/bitnami/etcd" bitnami/etcd:${ETCD_VERSION} - [ "$status" -eq 0 ] - - sleep 20 - - [ "$(grep -c "etcd connection recovered" ${LOG_FILE})" -ge '1' ] - [ "$(grep -c "etcd store reinitializing" ${LOG_FILE})" -ge '1' ] - - # wait data reload - sleep 10 - - # access manager api and check routes - run curl http://127.0.0.1:9000/apisix/admin/user/login -H "Content-Type: application/json" -d '{"username":"admin", "password": "admin"}' - token=$(echo "$output" | sed 's/{/\n/g' | sed 's/,/\n/g' | grep "token" | sed 's/:/\n/g' | sed '1d' | sed 's/}//g' | sed 's/"//g') - [ -n "${token}" ] - - run curl -ig -XGET http://127.0.0.1:9000/apisix/admin/routes -i -H "Content-Type: application/json" -H "Authorization: $token" - respCode=$(echo "$output" | sed 's/{/\n/g'| sed 's/,/\n/g' | grep "code" | sed 's/:/\n/g' | sed '1d') - [ "$respCode" = "0" ] - [ "$(echo "$output" | grep -c "/new1")" -eq '1' ] - [ "$(echo "$output" | grep -c "/new2")" -eq '1' ] - - stop_dashboard 6 -} - -#post -@test "Clean test environment" { - # kill etcd - pkill -f etcd - - # stop dashboard service - stop_dashboard 0 - - # clean configure and log files - rm -rf /usr/local/apisix-dashboard - rm /usr/lib/systemd/system/${SERVICE_NAME}.service - - # reload systemd services - run systemctl daemon-reload - [ "$status" -eq 0 ] -} diff --git a/api/test/shell/manager_smoking.sh b/api/test/shell/manager_smoking.sh deleted file mode 100755 index a2af62c156..0000000000 --- a/api/test/shell/manager_smoking.sh +++ /dev/null @@ -1,86 +0,0 @@ -#!/usr/bin/env bash - -# -# Licensed to the Apache Software Foundation (ASF) under one or more -# contributor license agreements. See the NOTICE file distributed with -# this work for additional information regarding copyright ownership. -# The ASF licenses this file to You under the Apache License, Version 2.0 -# (the "License"); you may not use this file except in compliance with -# the License. You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -# - -set -ex - -helpFunction() -{ - echo "" - echo "Usage: $0 -s true" - echo -e "\t-s whether skip docker, true or false" - echo -e "\t-h helper info" - exit 1 -} - -while getopts "s:h:" opt -do - case "$opt" in - s ) skip="$OPTARG" ;; - ? ) helpFunction ;; - esac -done - -if [ -z "$skip" ] -then - echo "Some parameters are empty"; - helpFunction; -fi - -if "$skip" -then - echo "skip docker check" -else - # Version output - verline=$(docker logs docker-deploy_managerapi_1 | grep -E "^Version : [A-Za-z0-9\-\_\.]+") - if [ -z "$verline" ];then - echo "no Version output" - exit 1 - fi - # Version output - hashline=$(docker logs docker-deploy_managerapi_1 | grep -E "^GitHash : [A-Za-z0-9\-\_\.]+") - if [ -z "$hashline" ];then - echo "no GitHash output" - exit 1 - fi -fi - - -# web page -curl http://127.0.0.1:9000 -code=$(curl -k -i -m 20 -o /dev/null -s -w %{http_code} http://127.0.0.1:9000) -if [ ! $code -eq 200 ]; then - echo "failed: failed to custom port" - exit 1 -fi - -# login -resp=$(curl http://127.0.0.1:9000/apisix/admin/user/login -X POST -d '{"username":"admin", "password": "admin"}') -token=$(echo "${resp}" | sed 's/{/\n/g' | sed 's/,/\n/g' | grep "token" | sed 's/:/\n/g' | sed '1d' | sed 's/}//g' | sed 's/"//g') -if [ -z "${token}" ]; then - echo "login failed" - exit 1 -fi - -# plugin orchestration -echo $token -code=$(curl -k -i -m 20 -o /dev/null -s -w %{http_code} http://127.0.0.1:9000/apisix/admin/routes/1 -X PUT -i -H "Authorization: $token" -d '{"id":"1","name":"route1","uri":"/index.html","upstream":{"type":"roundrobin","nodes":[{"host":"www.test.com","port":80,"weight":1}]},"script":{"rule":{"root":"451106f8-560c-43a4-acf2-2a6ed0ea57b8","451106f8-560c-43a4-acf2-2a6ed0ea57b8":[["code==403","b93d622c-92ef-48b4-b6bb-57e1ce893ee3"],["","988ef5c2-c896-4606-a666-3d4cbe24a731"]]},"conf":{"451106f8-560c-43a4-acf2-2a6ed0ea57b8":{"name":"uri-blocker","conf":{"block_rules":["root.exe","root.m+"],"rejected_code":403}},"988ef5c2-c896-4606-a666-3d4cbe24a731":{"name":"kafka-logger","conf":{"batch_max_size":1000,"broker_list":{},"buffer_duration":60,"inactive_timeout":5,"include_req_body":false,"kafka_topic":"1","key":"2","max_retry_count":0,"name":"kafkalogger","retry_delay":1,"timeout":3}},"b93d622c-92ef-48b4-b6bb-57e1ce893ee3":{"name":"fault-injection","conf":{"abort":{"body":"200","http_status":300},"delay":{"duration":500}}}},"chart":{}}}') -if [ ! $code -eq 200 ]; then - echo "failed to create route" - exit 1 -fi diff --git a/api/test/shell/wait_for_services.sh b/api/test/shell/wait_for_services.sh deleted file mode 100755 index b5270d3110..0000000000 --- a/api/test/shell/wait_for_services.sh +++ /dev/null @@ -1,30 +0,0 @@ -#!/usr/bin/env bash - -# -# Licensed to the Apache Software Foundation (ASF) under one or more -# contributor license agreements. See the NOTICE file distributed with -# this work for additional information regarding copyright ownership. -# The ASF licenses this file to You under the Apache License, Version 2.0 -# (the "License"); you may not use this file except in compliance with -# the License. You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -# - -i=1 -timeout=60 -while ! curl -s 127.0.0.1:12800 >/dev/null; do - if [[ "$i" -gt "$timeout" ]]; then - echo "timeout occurred after waiting $timeout seconds" - exit 1 - fi - sleep 1 - echo "waited skywalking for $i seconds.." - ((i++)); -done diff --git a/api/test/testdata/dag-conf.json b/api/test/testdata/dag-conf.json deleted file mode 100644 index e0840dbdf7..0000000000 --- a/api/test/testdata/dag-conf.json +++ /dev/null @@ -1,331 +0,0 @@ -{ - "name": "route-with-plugin-orchestration", - "uri": "/hello*", - "upstream": { - "type": "roundrobin", - "nodes": [{ - "host": "upstream", - "port": 1980, - "weight": 1 - }] - }, - "script":{ - "rule":{ - "root":"451106f8-560c-43a4-acf2-2a6ed0ea57b8", - "451106f8-560c-43a4-acf2-2a6ed0ea57b8":[ - [ - "code == 403", - "b93d622c-92ef-48b4-b6bb-57e1ce893ee3" - ], - [ - "", - "988ef5c2-c896-4606-a666-3d4cbe24a731" - ] - ] - }, - "conf":{ - "451106f8-560c-43a4-acf2-2a6ed0ea57b8":{ - "name":"uri-blocker", - "conf":{ - "block_rules":[ - "root.exe", - "root.m+" - ], - "rejected_code":403 - } - }, - "988ef5c2-c896-4606-a666-3d4cbe24a731":{ - "name":"kafka-logger", - "conf":{ - "batch_max_size":1000, - "broker_list":{ - - }, - "buffer_duration":60, - "inactive_timeout":5, - "include_req_body":false, - "kafka_topic":"1", - "key":"2", - "max_retry_count":0, - "name":"kafka logger", - "retry_delay":1, - "timeout":3 - } - }, - "b93d622c-92ef-48b4-b6bb-57e1ce893ee3":{ - "name":"fault-injection", - "conf":{ - "abort":{ - "body":"blocked", - "http_status":403 - }, - "delay":{ - "duration":2 - } - } - } - }, - "chart":{ - "hovered":{ - - }, - "links":{ - "3a110c30-d6f3-40b1-a8ac-b828cfaa2489":{ - "from":{ - "nodeId":"3365eca3-4bc8-4769-bab3-1485dfd6a43c", - "portId":"port3" - }, - "id":"3a110c30-d6f3-40b1-a8ac-b828cfaa2489", - "to":{ - "nodeId":"b93d622c-92ef-48b4-b6bb-57e1ce893ee3", - "portId":"port1" - } - }, - "c1958993-c1ef-44b1-bb32-7fc6f34870c2":{ - "from":{ - "nodeId":"3365eca3-4bc8-4769-bab3-1485dfd6a43c", - "portId":"port2" - }, - "id":"c1958993-c1ef-44b1-bb32-7fc6f34870c2", - "to":{ - "nodeId":"988ef5c2-c896-4606-a666-3d4cbe24a731", - "portId":"port1" - } - }, - "f9c42bf6-c8aa-4e86-8498-8dfbc5c53c23":{ - "from":{ - "nodeId":"451106f8-560c-43a4-acf2-2a6ed0ea57b8", - "portId":"port2" - }, - "id":"f9c42bf6-c8aa-4e86-8498-8dfbc5c53c23", - "to":{ - "nodeId":"3365eca3-4bc8-4769-bab3-1485dfd6a43c", - "portId":"port1" - } - } - }, - "nodes":{ - "3365eca3-4bc8-4769-bab3-1485dfd6a43c":{ - "id":"3365eca3-4bc8-4769-bab3-1485dfd6a43c", - "orientation":0, - "ports":{ - "port1":{ - "id":"port1", - "position":{ - "x":107, - "y":0 - }, - "type":"input" - }, - "port2":{ - "id":"port2", - "position":{ - "x":92, - "y":96 - }, - "properties":{ - "value":"no" - }, - "type":"output" - }, - "port3":{ - "id":"port3", - "position":{ - "x":122, - "y":96 - }, - "properties":{ - "value":"yes" - }, - "type":"output" - } - }, - "position":{ - "x":750.2627969928922, - "y":301.0370335799397 - }, - "properties":{ - "customData":{ - "name":"code == 403", - "type":1 - } - }, - "size":{ - "height":96, - "width":214 - }, - "type":"conditions" - }, - "451106f8-560c-43a4-acf2-2a6ed0ea57b8":{ - "id":"451106f8-560c-43a4-acf2-2a6ed0ea57b8", - "orientation":0, - "ports":{ - "port1":{ - "id":"port1", - "position":{ - "x":100, - "y":0 - }, - "properties":{ - "custom":"property" - }, - "type":"input" - }, - "port2":{ - "id":"port2", - "position":{ - "x":100, - "y":96 - }, - "properties":{ - "custom":"property" - }, - "type":"output" - } - }, - "position":{ - "x":741.5684544145346, - "y":126.75879247285502 - }, - "properties":{ - "customData":{ - "data":{ - "block_rules":[ - "root.exe", - "root.m+" - ], - "rejected_code":403 - }, - "name":"uri-blocker", - "type":0 - } - }, - "size":{ - "height":96, - "width":201 - }, - "type":"uri-blocker" - }, - "988ef5c2-c896-4606-a666-3d4cbe24a731":{ - "id":"988ef5c2-c896-4606-a666-3d4cbe24a731", - "orientation":0, - "ports":{ - "port1":{ - "id":"port1", - "position":{ - "x":106, - "y":0 - }, - "properties":{ - "custom":"property" - }, - "type":"input" - }, - "port2":{ - "id":"port2", - "position":{ - "x":106, - "y":96 - }, - "properties":{ - "custom":"property" - }, - "type":"output" - } - }, - "position":{ - "x":607.9687500000001, - "y":471.17788461538447 - }, - "properties":{ - "customData":{ - "data":{ - "batch_max_size":1000, - "broker_list":{ - - }, - "buffer_duration":60, - "inactive_timeout":5, - "include_req_body":false, - "kafka_topic":"1", - "key":"2", - "max_retry_count":0, - "name":"kafka logger", - "retry_delay":1, - "timeout":3 - }, - "name":"kafka-logger", - "type":0 - } - }, - "size":{ - "height":96, - "width":212 - }, - "type":"kafka-logger" - }, - "b93d622c-92ef-48b4-b6bb-57e1ce893ee3":{ - "id":"b93d622c-92ef-48b4-b6bb-57e1ce893ee3", - "orientation":0, - "ports":{ - "port1":{ - "id":"port1", - "position":{ - "x":110, - "y":0 - }, - "properties":{ - "custom":"property" - }, - "type":"input" - }, - "port2":{ - "id":"port2", - "position":{ - "x":110, - "y":96 - }, - "properties":{ - "custom":"property" - }, - "type":"output" - } - }, - "position":{ - "x":988.9074986362261, - "y":478.62041800736495 - }, - "properties":{ - "customData":{ - "data":{ - "abort":{ - "body":"200", - "http_status":300 - }, - "delay":{ - "duration":500 - } - }, - "name":"fault-injection", - "type":0 - } - }, - "size":{ - "height":96, - "width":219 - }, - "type":"fault-injection" - } - }, - "offset":{ - "x":-376.83, - "y":87.98 - }, - "scale":0.832, - "selected":{ - "id":"b93d622c-92ef-48b4-b6bb-57e1ce893ee3", - "type":"node" - } - } - } -} diff --git a/api/test/testdata/import/Postman-API101.yaml b/api/test/testdata/import/Postman-API101.yaml deleted file mode 100644 index 1f0467d409..0000000000 --- a/api/test/testdata/import/Postman-API101.yaml +++ /dev/null @@ -1,152 +0,0 @@ -# -# Licensed to the Apache Software Foundation (ASF) under one or more -# contributor license agreements. See the NOTICE file distributed with -# this work for additional information regarding copyright ownership. -# The ASF licenses this file to You under the Apache License, Version 2.0 -# (the "License"); you may not use this file except in compliance with -# the License. You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -# -### -# This is the definition file for the Postman API101 sample. -# It is converted from Postman Collection using postman2openapi. -openapi: 3.0.0 -info: - title: API 101 - description: >- - API 101 template for learning API request basics. Follow along with the - webinar / video or just open the first request and hit **Send**! - version: 1.0.0 -servers: - - url: https://api-101.glitch.me - - url: http://{{apiurl}} -components: - securitySchemes: - apikeyAuth: - type: http - scheme: apikey -paths: - /customers: - get: - tags: - - default - summary: Get all customers - parameters: - - name: user-id - in: header - schema: - type: string - example: '{{userId}}' - responses: - '200': - description: Successful response - content: - application/json: {} - /customer: - get: - tags: - - default - summary: Get one customer - parameters: - - name: user-id - in: header - schema: - type: string - example: '{{userId}}' - - name: id - in: query - schema: - type: integer - example: '1' - responses: - '200': - description: Successful response - content: - application/json: {} - post: - tags: - - default - summary: Add new customer - requestBody: - content: - application/json: - schema: - type: object - example: - name: Dorothy Zborna - type: Individual - security: - - apikeyAuth: [] - parameters: - - name: user-id - in: header - schema: - type: string - example: '{{userId}}' - responses: - '200': - description: Successful response - content: - application/json: {} - /customer/{customer_id}: - put: - tags: - - default - summary: Update customer - requestBody: - content: - application/json: - schema: - type: object - example: - name: Sophia Petrillo - type: Individual - security: - - apikeyAuth: [] - parameters: - - name: user-id - in: header - schema: - type: string - example: '{{userId}}' - - name: customer_id - in: path - schema: - type: integer - required: true - example: '1311' - responses: - '200': - description: Successful response - content: - application/json: {} - delete: - tags: - - default - summary: Remove customer - security: - - apikeyAuth: [] - parameters: - - name: user-id - in: header - schema: - type: string - example: '{{userId}}' - - name: customer_id - in: path - schema: - type: integer - required: true - example: '1310' - responses: - '200': - description: Successful response - content: - application/json: {} diff --git a/api/test/testdata/import/default.json b/api/test/testdata/import/default.json deleted file mode 100644 index a9ebb82ed4..0000000000 --- a/api/test/testdata/import/default.json +++ /dev/null @@ -1,40 +0,0 @@ -{ - "openapi": "3.0.0", - "info": { - "version": "1.0.0-oas3", - "description": "test desc", - "license": { - "name": "Apache License 2.0", - "url": "http://www.apache.org/licenses/LICENSE-2.0" - }, - "title": "test title" - }, - "paths": { - "/hello": { - "post": { - "x-api-limit": 20, - "description": "hello world.", - "operationId": "hello", - "x-apisix-upstream": { - "type": "roundrobin", - "nodes": [ - { - "host": "172.16.238.20", - "port": 1980, - "weight": 1, - "priority": 10 - } - ] - }, - "responses": { - "200": { - "description": "list response" - }, - "default": { - "description": "unexpected error" - } - } - } - } - } -} diff --git a/api/test/testdata/import/default.yaml b/api/test/testdata/import/default.yaml deleted file mode 100644 index 0b15d3acd5..0000000000 --- a/api/test/testdata/import/default.yaml +++ /dev/null @@ -1,45 +0,0 @@ -# -# Licensed to the Apache Software Foundation (ASF) under one or more -# contributor license agreements. See the NOTICE file distributed with -# this work for additional information regarding copyright ownership. -# The ASF licenses this file to You under the Apache License, Version 2.0 -# (the "License"); you may not use this file except in compliance with -# the License. You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -# -# If you want to set the specified configuration value, you can set the new -# in this file. For example if you want to specify the etcd address: -# -openapi: 3.0.0 -info: - version: 1.0.0-oas3 - description: test desc - license: - name: Apache License 2.0 - url: 'http://www.apache.org/licenses/LICENSE-2.0' - title: test title -paths: - /hello: - get: - x-api-limit: 20 - description: hello world. - operationId: hello - x-apisix-upstream: - type: roundrobin - nodes: - - host: 172.16.238.20 - port: 1980 - weight: 1 - priority: 10 - responses: - '200': - description: list response - default: - description: unexpected error diff --git a/api/test/testdata/import/httpbin.yaml b/api/test/testdata/import/httpbin.yaml deleted file mode 100644 index cdd392c829..0000000000 --- a/api/test/testdata/import/httpbin.yaml +++ /dev/null @@ -1,36 +0,0 @@ -# -# Licensed to the Apache Software Foundation (ASF) under one or more -# contributor license agreements. See the NOTICE file distributed with -# this work for additional information regarding copyright ownership. -# The ASF licenses this file to You under the Apache License, Version 2.0 -# (the "License"); you may not use this file except in compliance with -# the License. You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -# -# If you want to set the specified configuration value, you can set the new -# in this file. For example if you want to specify the etcd address: -# -openapi: 3.0.0 -info: - title: httpbin - version: 1.0.0 -servers: - - url: https://httpbin.org -paths: - /get: - get: - tags: - - default - summary: GET request - responses: - '200': - description: Successful response - content: - application/json: {} diff --git a/api/test/testdata/import/multi-routes.yaml b/api/test/testdata/import/multi-routes.yaml deleted file mode 100644 index 9f43cc9347..0000000000 --- a/api/test/testdata/import/multi-routes.yaml +++ /dev/null @@ -1,231 +0,0 @@ -# -# Licensed to the Apache Software Foundation (ASF) under one or more -# contributor license agreements. See the NOTICE file distributed with -# this work for additional information regarding copyright ownership. -# The ASF licenses this file to You under the Apache License, Version 2.0 -# (the "License"); you may not use this file except in compliance with -# the License. You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -# -# If you want to set the specified configuration value, you can set the new -# in this file. For example if you want to specify the etcd address: -# -components: {} -info: - title: RoutesExport - version: 3.0.0 -openapi: 3.0.0 -paths: - /get: - delete: - operationId: api1DELETE - requestBody: {} - responses: - default: - description: '' - x-apisix-enableWebsocket: false - x-apisix-labels: - API_VERSION: v2 - dev: test - x-apisix-plugins: - proxy-rewrite: - disable: false - scheme: http - x-apisix-priority: 0 - x-apisix-status: 1 - x-apisix-upstream: - nodes: - - host: 172.16.238.20 - port: 80 - weight: 1 - priority: 10 - timeout: - connect: 6000 - read: 6000 - send: 6000 - type: roundrobin - pass_host: node - x-apisix-vars: [] - get: - operationId: api1GET - requestBody: {} - responses: - default: - description: '' - x-apisix-enableWebsocket: false - x-apisix-labels: - API_VERSION: v2 - dev: test - x-apisix-plugins: - proxy-rewrite: - disable: false - scheme: http - x-apisix-priority: 0 - x-apisix-status: 1 - x-apisix-upstream: - nodes: - - host: 172.16.238.20 - port: 80 - weight: 1 - priority: 10 - timeout: - connect: 6000 - read: 6000 - send: 6000 - type: roundrobin - pass_host: node - x-apisix-vars: [] - head: - operationId: api1HEAD - requestBody: {} - responses: - default: - description: '' - x-apisix-enableWebsocket: false - x-apisix-labels: - API_VERSION: v2 - dev: test - x-apisix-plugins: - proxy-rewrite: - disable: false - scheme: http - x-apisix-priority: 0 - x-apisix-status: 1 - x-apisix-upstream: - nodes: - - host: 172.16.238.20 - port: 80 - weight: 1 - priority: 10 - timeout: - connect: 6000 - read: 6000 - send: 6000 - type: roundrobin - pass_host: node - x-apisix-vars: [] - patch: - operationId: api1PATCH - requestBody: {} - responses: - default: - description: '' - x-apisix-enableWebsocket: false - x-apisix-labels: - API_VERSION: v2 - dev: test - x-apisix-plugins: - proxy-rewrite: - disable: false - scheme: http - x-apisix-priority: 0 - x-apisix-status: 1 - x-apisix-upstream: - nodes: - - host: 172.16.238.20 - port: 80 - weight: 1 - priority: 10 - timeout: - connect: 6000 - read: 6000 - send: 6000 - type: roundrobin - pass_host: node - x-apisix-vars: [] - post: - operationId: api1POST - requestBody: {} - responses: - default: - description: '' - x-apisix-enableWebsocket: false - x-apisix-labels: - API_VERSION: v2 - dev: test - x-apisix-plugins: - proxy-rewrite: - disable: false - scheme: http - x-apisix-priority: 0 - x-apisix-status: 1 - x-apisix-upstream: - nodes: - - host: 172.16.238.20 - port: 80 - weight: 1 - priority: 10 - timeout: - connect: 6000 - read: 6000 - send: 6000 - type: roundrobin - pass_host: node - x-apisix-vars: [] - put: - operationId: api1PUT - requestBody: {} - responses: - default: - description: '' - x-apisix-enableWebsocket: false - x-apisix-labels: - API_VERSION: v2 - dev: test - x-apisix-plugins: - proxy-rewrite: - disable: false - scheme: http - x-apisix-priority: 0 - x-apisix-status: 1 - x-apisix-upstream: - nodes: - - host: 172.16.238.20 - port: 80 - weight: 1 - priority: 10 - timeout: - connect: 6000 - read: 6000 - send: 6000 - type: roundrobin - pass_host: node - x-apisix-vars: [] - /post: - post: - operationId: test_postPOST - requestBody: {} - responses: - default: - description: '' - security: [] - x-apisix-enableWebsocket: false - x-apisix-labels: - API_VERSION: v1 - version: v1 - x-apisix-plugins: - proxy-rewrite: - disable: false - scheme: http - x-apisix-priority: 0 - x-apisix-status: 1 - x-apisix-upstream: - nodes: - - host: 172.16.238.20 - port: 80 - weight: 1 - priority: 10 - timeout: - connect: 6000 - read: 6000 - send: 6000 - type: roundrobin - pass_host: node - x-apisix-vars: [] diff --git a/api/test/testdata/import/with-plugins.yaml b/api/test/testdata/import/with-plugins.yaml deleted file mode 100644 index 0e8ecfb630..0000000000 --- a/api/test/testdata/import/with-plugins.yaml +++ /dev/null @@ -1,81 +0,0 @@ -# -# Licensed to the Apache Software Foundation (ASF) under one or more -# contributor license agreements. See the NOTICE file distributed with -# this work for additional information regarding copyright ownership. -# The ASF licenses this file to You under the Apache License, Version 2.0 -# (the "License"); you may not use this file except in compliance with -# the License. You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -# -# If you want to set the specified configuration value, you can set the new -# in this file. For example if you want to specify the etcd address: -# - -components: - securitySchemes: - basicAuth: - type: http - scheme: basic -info: - version: "1" - description: |- - test desc - license: - name: Apache License 2.0 - url: http://www.apache.org/licenses/LICENSE-2.0 - title: |- - test title -paths: - /hello: - post: - x-api-limit: 20 - description: |- - hello world. - operationId: hello - x-apisix-upstream: - type: roundrobin - nodes: - - host: "172.16.238.20" - port: 1980 - weight: 1 - priority: 10 - parameters: - - name: id - in: header - description: ID of pet to use - required: true - schema: - type: string - style: simple - - requestBody: - content: - 'application/x-www-form-urlencoded': - schema: - properties: - name: - description: Update pet's name - type: string - status: - description: Updated status of the pet - type: string - required: - - status - - security: - - basicAuth: [] - - responses: - 200: - description: list response - default: - description: unexpected error - -openapi: 3.0.0 diff --git a/api/test/testdata/import/with-service-id.yaml b/api/test/testdata/import/with-service-id.yaml deleted file mode 100644 index 985b02ebd5..0000000000 --- a/api/test/testdata/import/with-service-id.yaml +++ /dev/null @@ -1,39 +0,0 @@ -# -# Licensed to the Apache Software Foundation (ASF) under one or more -# contributor license agreements. See the NOTICE file distributed with -# this work for additional information regarding copyright ownership. -# The ASF licenses this file to You under the Apache License, Version 2.0 -# (the "License"); you may not use this file except in compliance with -# the License. You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -# -# If you want to set the specified configuration value, you can set the new -# in this file. For example if you want to specify the etcd address: -# -openapi: 3.0.0 -info: - version: 1.0.0-oas3 - description: test desc - license: - name: Apache License 2.0 - url: 'http://www.apache.org/licenses/LICENSE-2.0' - title: test title -paths: - /hello: - get: - x-api-limit: 20 - description: hello world. - operationId: hello - x-apisix-service_id: service1 - responses: - '200': - description: list response - default: - description: unexpected error diff --git a/api/test/testdata/import/with-upstream-id.yaml b/api/test/testdata/import/with-upstream-id.yaml deleted file mode 100644 index dc25276577..0000000000 --- a/api/test/testdata/import/with-upstream-id.yaml +++ /dev/null @@ -1,39 +0,0 @@ -# -# Licensed to the Apache Software Foundation (ASF) under one or more -# contributor license agreements. See the NOTICE file distributed with -# this work for additional information regarding copyright ownership. -# The ASF licenses this file to You under the Apache License, Version 2.0 -# (the "License"); you may not use this file except in compliance with -# the License. You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -# -# If you want to set the specified configuration value, you can set the new -# in this file. For example if you want to specify the etcd address: -# -openapi: 3.0.0 -info: - version: 1.0.0-oas3 - description: test desc - license: - name: Apache License 2.0 - url: 'http://www.apache.org/licenses/LICENSE-2.0' - title: test title -paths: - /hello: - get: - x-api-limit: 20 - description: hello world. - operationId: hello - x-apisix-upstream_id: upstream1 - responses: - '200': - description: list response - default: - description: unexpected error diff --git a/api/test/testdata/invalid-dag-conf.json b/api/test/testdata/invalid-dag-conf.json deleted file mode 100644 index 260e06a3db..0000000000 --- a/api/test/testdata/invalid-dag-conf.json +++ /dev/null @@ -1,56 +0,0 @@ -{ - "name": "route-with-plugin-orchestration", - "uri": "/hello", - "upstream": { - "type": "roundrobin", - "nodes": [{ - "host": "172.16.238.20", - "port": 1980, - "weight": 1 - }] - }, - "script":{ - "rule":{ - "11-22-33-44":[ - [ - "code == 503", - "yy-uu-ii-oo" - ], - [ - "", - "vv-cc-xx-zz" - ] - ] - }, - "conf":{ - "11-22-33-44":{ - "name": "limit-count", - "conf": { - "count":2, - "time_window":60, - "rejected_code":503, - "key":"remote_addr", - "policy": "local" - } - }, - "yy-uu-ii-oo":{ - "name": "response-rewrite", - "conf": { - "body":"request has been limited", - "headers":{ - "X-limit-status": "limited" - } - } - }, - "vv-cc-xx-zz":{ - "name": "response-rewrite", - "conf": { - "body":"normal request", - "headers":{ - "X-limit-status": "normal" - } - } - } - } - } -} diff --git a/docs/assets/images/architecture.png b/docs/assets/images/architecture.png deleted file mode 100644 index d73ab5326a..0000000000 Binary files a/docs/assets/images/architecture.png and /dev/null differ diff --git a/docs/assets/images/manager-api.png b/docs/assets/images/manager-api.png deleted file mode 100644 index 460c87e45c..0000000000 Binary files a/docs/assets/images/manager-api.png and /dev/null differ diff --git a/docs/assets/images/metrics-cn.png b/docs/assets/images/metrics-cn.png deleted file mode 100644 index 225154f253..0000000000 Binary files a/docs/assets/images/metrics-cn.png and /dev/null differ diff --git a/docs/assets/images/modules/data_loader/intro.png b/docs/assets/images/modules/data_loader/intro.png deleted file mode 100644 index 3a0331cc66..0000000000 Binary files a/docs/assets/images/modules/data_loader/intro.png and /dev/null differ diff --git a/docs/assets/images/modules/data_loader/openapi3-1.png b/docs/assets/images/modules/data_loader/openapi3-1.png deleted file mode 100644 index cb6e3d2ace..0000000000 Binary files a/docs/assets/images/modules/data_loader/openapi3-1.png and /dev/null differ diff --git a/docs/assets/images/modules/data_loader/openapi3-2.png b/docs/assets/images/modules/data_loader/openapi3-2.png deleted file mode 100644 index 1153533330..0000000000 Binary files a/docs/assets/images/modules/data_loader/openapi3-2.png and /dev/null differ diff --git a/docs/assets/images/modules/data_loader/openapi3-3.png b/docs/assets/images/modules/data_loader/openapi3-3.png deleted file mode 100644 index 3a58fd31cd..0000000000 Binary files a/docs/assets/images/modules/data_loader/openapi3-3.png and /dev/null differ diff --git a/docs/assets/images/modules/data_loader/openapi3-4.png b/docs/assets/images/modules/data_loader/openapi3-4.png deleted file mode 100644 index 0a66347592..0000000000 Binary files a/docs/assets/images/modules/data_loader/openapi3-4.png and /dev/null differ diff --git a/docs/assets/images/modules/data_loader/openapi3-5.png b/docs/assets/images/modules/data_loader/openapi3-5.png deleted file mode 100644 index 1769c5c170..0000000000 Binary files a/docs/assets/images/modules/data_loader/openapi3-5.png and /dev/null differ diff --git a/docs/assets/images/modules/data_loader/openapi3-6.png b/docs/assets/images/modules/data_loader/openapi3-6.png deleted file mode 100644 index 476c9f5ae2..0000000000 Binary files a/docs/assets/images/modules/data_loader/openapi3-6.png and /dev/null differ diff --git a/docs/assets/images/modules/data_loader/openapi3-7.png b/docs/assets/images/modules/data_loader/openapi3-7.png deleted file mode 100644 index 97d0427b87..0000000000 Binary files a/docs/assets/images/modules/data_loader/openapi3-7.png and /dev/null differ diff --git a/docs/assets/images/modules/data_loader/openapi3-8.png b/docs/assets/images/modules/data_loader/openapi3-8.png deleted file mode 100644 index 1372e50e93..0000000000 Binary files a/docs/assets/images/modules/data_loader/openapi3-8.png and /dev/null differ diff --git a/docs/assets/images/modules/data_loader/openapi3-9.png b/docs/assets/images/modules/data_loader/openapi3-9.png deleted file mode 100644 index 504f1ec9a6..0000000000 Binary files a/docs/assets/images/modules/data_loader/openapi3-9.png and /dev/null differ diff --git a/docs/assets/images/route-create-done-list-cn.png b/docs/assets/images/route-create-done-list-cn.png deleted file mode 100644 index 164a5abafd..0000000000 Binary files a/docs/assets/images/route-create-done-list-cn.png and /dev/null differ diff --git a/docs/assets/images/route-create-done-list-en.png b/docs/assets/images/route-create-done-list-en.png deleted file mode 100644 index 3982888be2..0000000000 Binary files a/docs/assets/images/route-create-done-list-en.png and /dev/null differ diff --git a/docs/assets/images/route-create-step1-cn.png b/docs/assets/images/route-create-step1-cn.png deleted file mode 100644 index 39dbfc5a4c..0000000000 Binary files a/docs/assets/images/route-create-step1-cn.png and /dev/null differ diff --git a/docs/assets/images/route-create-step2-cn.png b/docs/assets/images/route-create-step2-cn.png deleted file mode 100644 index fe9fad405b..0000000000 Binary files a/docs/assets/images/route-create-step2-cn.png and /dev/null differ diff --git a/docs/assets/images/route-create-step3-cn.png b/docs/assets/images/route-create-step3-cn.png deleted file mode 100644 index 5836f5b47b..0000000000 Binary files a/docs/assets/images/route-create-step3-cn.png and /dev/null differ diff --git a/docs/assets/images/route-create-step4-cn.png b/docs/assets/images/route-create-step4-cn.png deleted file mode 100644 index e1e4007d80..0000000000 Binary files a/docs/assets/images/route-create-step4-cn.png and /dev/null differ diff --git a/docs/assets/images/route-debug-basic-auth.png b/docs/assets/images/route-debug-basic-auth.png deleted file mode 100644 index 531aba894e..0000000000 Binary files a/docs/assets/images/route-debug-basic-auth.png and /dev/null differ diff --git a/docs/assets/images/route-debug-body-params.png b/docs/assets/images/route-debug-body-params.png deleted file mode 100644 index e8d2416934..0000000000 Binary files a/docs/assets/images/route-debug-body-params.png and /dev/null differ diff --git a/docs/assets/images/route-debug-header-params.png b/docs/assets/images/route-debug-header-params.png deleted file mode 100644 index 3eebfeeae1..0000000000 Binary files a/docs/assets/images/route-debug-header-params.png and /dev/null differ diff --git a/docs/assets/images/route-debug-published.png b/docs/assets/images/route-debug-published.png deleted file mode 100644 index e4dc11823c..0000000000 Binary files a/docs/assets/images/route-debug-published.png and /dev/null differ diff --git a/docs/assets/images/route-debug-query-params.png b/docs/assets/images/route-debug-query-params.png deleted file mode 100644 index 3dd517de39..0000000000 Binary files a/docs/assets/images/route-debug-query-params.png and /dev/null differ diff --git a/docs/assets/images/route-list-en.png b/docs/assets/images/route-list-en.png deleted file mode 100644 index 62064261d0..0000000000 Binary files a/docs/assets/images/route-list-en.png and /dev/null differ diff --git a/docs/assets/images/setting-cn.png b/docs/assets/images/setting-cn.png deleted file mode 100644 index a22670d6e3..0000000000 Binary files a/docs/assets/images/setting-cn.png and /dev/null differ diff --git a/docs/assets/images/ssl-list-cn.png b/docs/assets/images/ssl-list-cn.png deleted file mode 100644 index 1fb699b8e9..0000000000 Binary files a/docs/assets/images/ssl-list-cn.png and /dev/null differ diff --git a/docs/assets/images/ssl-list-en.png b/docs/assets/images/ssl-list-en.png deleted file mode 100644 index fac503181b..0000000000 Binary files a/docs/assets/images/ssl-list-en.png and /dev/null differ diff --git a/docs/en/latest/FAQ.md b/docs/en/latest/FAQ.md deleted file mode 100644 index 52133c0655..0000000000 --- a/docs/en/latest/FAQ.md +++ /dev/null @@ -1,138 +0,0 @@ ---- -title: FAQ ---- - - - -### 1. Vue.js version of the Dashboard - -If you need a Vue.js build of the Apache APISIX Dashboard 1.0, use the [master-vue branch](https://github.com/apache/apisix-dashboard/tree/master-vue). - -### 2. What are the differences between Dashboard version 2.0 and version 1.5? - -The 2.0 version of the dashboard removed MySQL from [version 1.5](https://github.com/apache/apisix-dashboard/tree/backup-1.5-latest) and will operate directly on etcd. - -### 3. Etcd compatibility issues - -If you are using Apache APISIX below v2.0, be aware that the data from the etcd v2 API is [not compatible](https://etcd.io/docs/v3.4.0/op-guide/v2-migration/) with the data from the v3 API. Apache APISIX Dashboard v2.0 and above uses the etcd v3 API, and apisix 1.5 and below uses the etcd v2 API. - -### 4. After modifying the plugin schema or creating a custom plugin in Apache APISIX, why can't I find it on the dashboard? - -Since the Dashboard caches the jsonschema data of the plugins in Apache APISIX, you need to synchronize the data in the Dashboard after you create your custom plugins in Apache APISIX, which currently **only supports manual operation**. Please follow the following guide. - -1. Confirm that your APISIX is running and has enabled control API (enabled by default and only runs local access) - Refer to the beginning in: - [https://apisix.apache.org/docs/apisix/control-api](https://apisix.apache.org/docs/apisix/control-api) - -2. Execute the following commands to export jsonchema on your APISIX server (if it is configured for non-local access, it does not need to be executed on your APISIX server, and the access IP and port should be modified accordingly) - -```sh -curl 127.0.0.1:9090/v1/schema > schema.json -``` - -Refer to [https://apisix.apache.org/docs/apisix/control-api#get-v1schema](https://apisix.apache.org/docs/apisix/control-api#get-v1schema) - -3. Copy the exported `schema.json` to the `conf` directory in the Dashboard working directory (About working directory, please refer to https://github.com/apache/apisix-dashboard/blob/master/docs/en/latest/deploy.md#working-directory) - -4. Restart the Manager API - -### 5. How to write API documentation - -We use [go-swagger](https://github.com/go-swagger/go-swagger) to generate Swagger 2.0 documents, and then convert them to markdown format so that they can be viewed directly in the github repository. Specific steps are as follows: - -1. Write comments according to [Specification](https://goswagger.io/use/spec.html). For details, please refer to the existing example `api/internal/handler/route/route.go` in this project. - -2. Use the `go-swagger` tool to generate Swagger 2.0 documents. - -```shell -$ swagger generate spec -o ./docs/en/latest/api/api.yaml --scan-models -``` - -3. Use the `swagger-markdown` tool to convert Swagger 2.0 documents into markdown documents. - -```shell -$ swagger-markdown -i ./docs/en/latest/api/api.yaml -``` - -### 6. How to allow all IPs to access APISIX Dashboard - -1. Allow all IPv4 access - -By default, the IPv4 range of `127.0.0.0/24` is allowed to access `APISIX Dashboard`. If you want to allow all IPv4 access, then just configure `conf.allow_list` in the configuration file of `conf/conf.yaml` as follows: - -```yaml -conf: - allow_list: - - 0.0.0.0/0 -``` - -2. Allow all IPv6 access - -By default, the IPv6 range of `::1` is allowed to access `APISIX Dashboard`. If you want to allow all IPv6 access, then just configure `conf.allow_list` in the configuration file of `conf/conf.yaml` as follows: - -```yaml -conf: - allow_list: - - ::/0 -``` - -3. Allow all IP access - -If you want to allow all IPs to access `APISIX Dashboard`, you only need to do the following configuration in the configuration file of `conf/conf.yaml`: - -```yaml -conf: - allow_list: -``` - -Restart `manager-api`, all IPs can access `APISIX Dashboard`. - -Note: You can use this method in development and test environment to allow all IPs to access your `APISIX Dashboard` instance, but it is not safe to use it in a production environment. In production environment, please only authorize specific IP addresses or address ranges to access your instance. - -### 7. What is the default strategy when import a duplicate route? - -Currently we reject import duplicate route, that is to say when you import a route which has the same attributes, all of the `uri` `uris` `host` `hosts` `remote_addr` `remote_addrs` `priority` `vars` and `filter_func`, as the existing route, you will get an error while importing a route from OAS3.0. - -### 8. APISIX dashboard add grafana cross-domain problem - -Modifying the Grafana configuration: - -1. Enable anonymous access: - -```shell -# grep 'auth.anonymous' -A 3 defaults.ini -[auth.anonymous] -# enable anonymous access -enabled = true -``` - -2. Allow access via iframe - -```shell -# grep 'allow_embedding' defaults.ini -allow_embedding = true -ยทยทยท -``` - -### 9. APISIX dashboard configured domain name, the embedded Grafana can't login - -If the domain name of the address is configured as HTTPS, the embedded grafana will jump to the login page after logging in. You can refer to this solution: - -It's best for Grafana to configure the domain name in the same way. Otherwise there will be problems with address resolution. diff --git a/docs/en/latest/I18N_USER_GUIDE.md b/docs/en/latest/I18N_USER_GUIDE.md deleted file mode 100644 index bf7419f355..0000000000 --- a/docs/en/latest/I18N_USER_GUIDE.md +++ /dev/null @@ -1,136 +0,0 @@ ---- -title: i18n User Guide ---- - - - -The Apache APISIX Dashboard uses [@umijs/plugin-locale](https://umijs.org/plugins/plugin-locale) to solve the i18n issues, in order to make the i18n more clear and reasonable, we would recommend to obey the following rules - -## Location of locale configuration - -- Please put **the global locales** under `src/locales`. -- Please put **each page's locale file** under `src/pages/$PAGE/locales` folder. -- Please put **the Component's locale file** under `src/components/$COMPONENT/locales` folder, and we **MUST** import them manually - -## How to name the key for each locale filed - -the key can be like this : [basicModule].[moduleName].[elementName].[...desc] - -- what's the first tow levels? e.g: `app.pwa`, `page.consumer`, `component.actionBar` - -- The subkeys are divided into $element + $description style e.g: `app.pwa.message.offline`, `component.actionBar.button.nextStep` - - - If the the text is the part of a element, we can use [elementNameProps] e.g: `page.consumer.proTableColumns.username`. - - If there are two or more same level part locales of a element, we can add number suffix e.g: `page.route.form.itemRulesExtraMessage1.path`, `page.route.form.itemRulesExtraMessage2.path`. - -- common texts, we should not repeat in other part, and the common locale key omit [elementName] would be better. - - - If the text is used in two or more places inside the module, we would recommend sharing the text in the module, e.g:`page.route.parameterPosition`. - - If the text is used in two or more places between modules, we would recommend sharing the text globally, and add`global`as the moduleName,git e.g:`component.global.confirm`. - -## Global locale keys - -we have already defined many global keys, before you do i18n, you can refer to [those](https://github.com/apache/apisix-dashboard/blob/master/web/src/locales/zh-CN/component.ts). - -## Recommended subkey naming - -- **Form** - -| element | props | locale subKey | -| --------- | -------------- | ----------------------------- | -| Form.Item | label | form.itemLabel | -| Form.Item | rules.required | form.itemRulesRequiredMessage | -| Form.Item | rules.pattern | form.itemRulesPatternMessage | -| Form.Item | extra | form.itemExtraMessage | - -**Example:** - -```js -'page.route.form.itemRulesExtraMessage.parameterName': 'ไป…ๆ”ฏๆŒๅญ—ๆฏๅ’Œๆ•ฐๅญ—๏ผŒไธ”ๅช่ƒฝไปฅๅญ—ๆฏๅผ€ๅคด', -'page.route.form.itemRulesPatternMessage.apiNameRule': 'ๆœ€ๅคง้•ฟๅบฆๅบ”ๅฐไบŽ100', -``` - -- **Input** - -| element | props | locale subKey | -| ------- | ----------- | ----------------- | -| Input | placeholder | input.placeholder | - -**Example:** - -```js -'page.route.input.placeholder.parameterNameHttpHeader': '่ฏทๆฑ‚ๅคด้”ฎๅ๏ผŒไพ‹ๅฆ‚๏ผšHOST', -``` - -- **Button** - -| element | props | locale subKey | -| ------- | ----- | ------------- | -| Button | null | button | - -**Example:** - -```js -'page.route.button.returnList': '่ฟ”ๅ›ž่ทฏ็”ฑๅˆ—่กจ', -``` - -- **Steps** - -| element | props | locale subKey | -| ---------- | ----- | --------------- | -| Steps.step | title | steps.stepTitle | - -**Example:** - -```js -'page.route.steps.stepTitle.defineApiRequest': '่ฎพ็ฝฎ่ทฏ็”ฑไฟกๆฏ', -``` - -- **Select** - -| element | props | locale subKey | -| ------------- | ----- | ------------- | -| Select.Option | null | select.option | - -**Example:** - -```js -'page.route.select.option.enableHttps': 'ๅฏ็”จ HTTPS', -``` - -- **Radio** - -| element | props | locale subKey | -| ------- | ----- | ------------- | -| Radio | null | radio | - -**Example:** - -```js -'page.route.radio.staySame': 'ไฟๆŒๅŽŸๆ ท', -``` - -- **ProTable** - -| element | props | locale subKey | -| -------- | ------------- | --------------------- | -| ProTable | columns.title | proTable.columnsTitle | - -_ProTable usually appears in conjunction with forms, and columns title are same with form item label, so we recommend these title keys to be the common key in modules._ diff --git a/docs/en/latest/IMPORT_OPENAPI_USER_GUIDE.md b/docs/en/latest/IMPORT_OPENAPI_USER_GUIDE.md deleted file mode 100644 index bd49c14ed4..0000000000 --- a/docs/en/latest/IMPORT_OPENAPI_USER_GUIDE.md +++ /dev/null @@ -1,446 +0,0 @@ ---- -title: Import OpenAPI Guide ---- - - - -## Overview - -The OpenAPI Specification (OAS) defines a standard, language-agnostic interface to RESTful APIs which allows both humans and computers to discover and understand the capabilities of the service without access to source code, documentation, or through network traffic inspection. - -Apache APISIX Dashboard supports importing [OpenApi3.0](https://swagger.io/specification/)(we will use OAS3.0 for short) files, both `json` and `yaml` are supported, to create one or more Routes. Currently we support most of the OpenApi specifications, but there are some differences, which are in terms of compatibility and extended fields. - -## Extended fields - -There are some fields required in APISIX Route but are not included in the properties of OAS3.0, in order to provide convenience for extending custom route entities based on OAS3.0, we added some extended fields such as upstream, plugins, hosts and so on. All extensions start with x-apisix. - -| Extended fields | APISIX Route Properties | -| ------------------------- | ----------------------- | -| x-apisix-plugins | plugins | -| x-apisix-script | script | -| x-apisix-upstream | upstream | -| x-apisix-host | host | -| x-apisix-hosts | hosts | -| x-apisix-remote_addr | remote_addr | -| x-apisix-priority | priority | -| x-apisix-vars | vars | -| x-apisix-filter_func | filter_func | -| x-apisix-labels | labels | -| x-apisix-enable_websocket | enable_websocket | -| x-apisix-status | status | -| x-apisix-service_id | service_id | -| x-apisix-upstream_id | upstream_id | - -Please pay attention that we only extended the first level of the field, and the sub level fields will still keep the same. Just take `x-apisix-upstream` for an example. - -```yaml -... -# we add x-apisix-upstream as an extended field in OAS3.0 to stand for upstream -x-apisix-upstream: - # the sub fields of x-apisix-upstream still keeps the same as upstream's sub fields - type: roundrobin - nodes: - - host: 172.16.238.20 - port: 1980 - weight: 1 -... -``` - -See [reference](https://apisix.apache.org/docs/apisix/admin-api/#route) for more details of the APISIX Route Properties - -## OAS3.0 Compatibility - -When we import routes from OAS3.0, some fields in OAS3.0 will be missed because there are not corresponding fields in APISIX's Route: - -1. [API General Info](https://swagger.io/docs/specification/api-general-info/): used to describe the general information about your API, some times, a OAS3.0 file contains a series of apis which belong to a app, so this info is different from the api's name and extra basic info. - -**Example:** - -```yaml -# this part of information will be missed -openapi: 3.0.0 -info: - version: 1.0.0-oas3 - description: test desc - license: - name: Apache License 2.0 - url: 'http://www.apache.org/licenses/LICENSE-2.0' - title: test title -... -``` - -2. [API server and base path](https://swagger.io/docs/specification/api-host-and-base-path/): upstream url + url prefix(options). - -**Example:** - -```yaml -# this part of information will be missed -... -servers: - - url: https://api.example.com/v1 -... -``` - -3. [Path params](https://swagger.io/docs/specification/describing-parameters/): api params described in path. - -**Example:** - -```yaml -# no matter how many path parameters in the uri -# we will got the route with uri like `/get/*` after import route from OAS3.0 file -... -paths: - /get/{id}/{name}: - delete: - operationId: api1DELETE -... -``` - -4. [Query params](https://swagger.io/docs/specification/describing-parameters/): api params described in query. - -**Example:** - -```yaml -... -paths: - /users: - get: - summary: Get a user by ID - # this part of information will be missed - parameters: - - in: path - name: userId - schema: - type: integer - required: true - description: Numeric ID of the user to get -... -``` - -5. [Responses description and links](https://swagger.io/docs/specification/describing-responses/): Define the responses for a API operations. - -**Example:** - -```yaml -... -paths: - /hello: - get: - description: hello world. - operationId: hello - x-apisix-service_id: service1 - # this part of information will be missed - responses: - '200': - description: list response - default: - description: unexpected error -... -``` - -## Examples about how to Configure the OAS3.0 in different user scenarios - -### Configure a basic published route - -_notice: the default `status` of the imported route will be `unpublished`, which means the route can not be accessed, if you want to import a `published` route, you should add `x-apisix-status: 1` in you OAS3.0 file_ - -```yaml -openapi: 3.0.0 -info: - version: 1.0.0-oas3 - description: test desc - license: - name: Apache License 2.0 - url: 'http://www.apache.org/licenses/LICENSE-2.0' - title: test title -paths: - /hello: # route uri - get: # route method - description: hello world. # route desc - operationId: hello # route name - x-apisix-upstream: # route upstream - type: roundrobin - nodes: - - host: 172.16.238.20 - port: 1980 - weight: 1 - x-apisix-status: 1 # the route will be published after imported - responses: - '200': - description: list response - default: - description: unexpected error -``` - -### configure a route with plugins - -_notice: most plugins supported by extended field `x-apisix-plugins`_ - -```yaml -openapi: 3.0.0 -info: - version: 1.0.0-oas3 - description: test desc - license: - name: Apache License 2.0 - url: 'http://www.apache.org/licenses/LICENSE-2.0' - title: test title -paths: - /hello: - get: - description: hello world. - operationId: hello - x-apisix-upstream: - type: roundrobin - nodes: - - host: 172.16.238.20 - port: 1980 - weight: 1 - x-apisix-plugins: - limit-count: - count: 2 - time_window: 60 - rejected_code: 503 - key: remote_addr - policy: local - responses: - '200': - description: list response - default: - description: unexpected error -``` - -### configure a route with parameters validation - -_notice: for plugin [request-validation](https://apisix.apache.org/docs/apisix/plugins/request-validation), we will use [Parameter Serialization](https://swagger.io/docs/specification/serialization/) for header parameters validation and [Describing Request Body](https://swagger.io/docs/specification/describing-request-body/) for body parameters validation in OAS3.0_ - -```yaml -openapi: 3.0.0 -info: - version: "1" - description: |- - test desc - license: - name: Apache License 2.0 - url: http://www.apache.org/licenses/LICENSE-2.0 - title: |- - test title -paths: - /hello: - post: - description: |- - hello world. - operationId: hello - x-apisix-upstream: - type: roundrobin - nodes: - - host: "172.16.238.20" - port: 1980 - weight: 1 - parameters: - - name: id - in: header - description: ID of pet to use - required: true - schema: - type: string - style: simple - - requestBody: - content: - 'application/x-www-form-urlencoded': - schema: - properties: - name: - description: Update pet's name - type: string - status: - description: Updated status of the pet - type: string - required: - - status - responses: - 200: - description: list response - default: - description: unexpected error -``` - -### configure a route with auth plugins - -_notice: for plugin [basic-auth](https://apisix.apache.org/docs/apisix/plugins/basic-auth)ใ€[jwt-auth](https://apisix.apache.org/docs/apisix/plugins/jwt-auth) and [key-auth](https://apisix.apache.org/docs/apisix/plugins/key-auth) we will use [Authentication](https://swagger.io/docs/specification/authentication/) in OAS3.0_ - -```yaml -components: - securitySchemes: - basicAuth: - type: http - scheme: basic - BearerAuth: - type: http - scheme: bearer - bearerFormat: JWT - ApiKeyAuth: - type: apiKey - in: header - name: X-API-Key -openapi: 3.0.0 -info: - version: "1" - description: |- - test desc - license: - name: Apache License 2.0 - url: http://www.apache.org/licenses/LICENSE-2.0 - title: |- - test title -paths: - /hello: - post: - description: |- - hello world. - operationId: hello - x-apisix-upstream: - type: roundrobin - nodes: - - host: "172.16.238.20" - port: 1980 - weight: 1 - security: - - basicAuth: [] - - ApiKeyAuth: [] - - BearerAuth: [] - responses: - 200: - description: list response - default: - description: unexpected error -``` - -### configure a route with exist service or upstream - -_notice: if the `service_id` or `upstream_id` does not exist in APISIX, import route from the config file will get an error_ - -```yaml -openapi: 3.0.0 -info: - version: 1.0.0-oas3 - description: test desc - license: - name: Apache License 2.0 - url: 'http://www.apache.org/licenses/LICENSE-2.0' - title: test title -paths: - /hello: - get: - description: hello world. - operationId: hello - x-apisix-service_id: service1 - responses: - '200': - description: list response - default: - description: unexpected error -``` - -### configure more than one route - -```yaml -info: - title: RoutesExport - version: 3.0.0 -openapi: 3.0.0 -paths: - /get: - delete: - operationId: api1Delete - requestBody: {} - responses: - default: - description: '' - x-apisix-enableWebsocket: false - x-apisix-labels: - API_VERSION: v2 - dev: test - x-apisix-plugins: - proxy-rewrite: - disable: false - scheme: https - x-apisix-priority: 0 - x-apisix-status: 1 - x-apisix-upstream: - nodes: - - host: httpbin.org - port: 443 - weight: 1 - type: roundrobin - pass_host: node - x-apisix-vars: [] - get: - operationId: api1Get - requestBody: {} - responses: - default: - description: '' - x-apisix-enableWebsocket: false - x-apisix-labels: - API_VERSION: v2 - dev: test - x-apisix-plugins: - proxy-rewrite: - disable: false - scheme: https - x-apisix-priority: 0 - x-apisix-status: 1 - x-apisix-upstream: - nodes: - - host: httpbin.org - port: 443 - weight: 1 - type: roundrobin - pass_host: node - x-apisix-vars: [] - /post: - post: - operationId: test_post - requestBody: {} - responses: - default: - description: '' - security: [] - x-apisix-enableWebsocket: false - x-apisix-labels: - API_VERSION: v1 - version: v1 - x-apisix-plugins: - proxy-rewrite: - disable: false - scheme: https - x-apisix-priority: 0 - x-apisix-status: 1 - x-apisix-upstream: - nodes: - - host: httpbin.org - port: 443 - weight: 1 - type: roundrobin - pass_host: node - x-apisix-vars: [] -``` diff --git a/docs/en/latest/USER_GUIDE.md b/docs/en/latest/USER_GUIDE.md deleted file mode 100644 index 0cb0377080..0000000000 --- a/docs/en/latest/USER_GUIDE.md +++ /dev/null @@ -1,56 +0,0 @@ ---- -title: User Guide ---- - - - -The following are parts of the modules' snapshot. - -## Dashboard - -We support the monitor page by referencing it in [iframe](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/iframe). Before accessing [Grafana](https://grafana.com/), please Enable [`allow_embedding=true`](https://grafana.com/docs/grafana/latest/administration/configuration/#allow_embedding), which defaults to `false`. This causes the browser to fail to render Grafana pages properly due to security policies. - -Solving this problem requires you to configure some csp rules. Please check the default configuration options for details. You can refer to this [link](https://github.com/apache/apisix-dashboard/blob/master/api/conf/conf.yaml) for the recommand rule. - -![Dashboard-en](https://user-images.githubusercontent.com/40708551/112922395-0eed0380-912a-11eb-8c92-4c67d2bae4a8.png) - -## Route - -The Route module aims to control routes by UI instead of calling APIs. - -### List - -![route-list](https://user-images.githubusercontent.com/40708551/112922389-0c8aa980-912a-11eb-8c45-b13192b3775d.png) - -### Create - -![route-create-step1-en](https://user-images.githubusercontent.com/40708551/112922912-ef0a0f80-912a-11eb-9d33-63d7215f7cfd.png) - -![route-create-step2-en](https://user-images.githubusercontent.com/40708551/112923105-44462100-912b-11eb-8e1f-6548a6c28c35.png) - -![route-create-step3-en](https://user-images.githubusercontent.com/40708551/112923140-545e0080-912b-11eb-9aef-d26b2c564efe.png) - -![route-create-step4-en](https://user-images.githubusercontent.com/40708551/112923257-971fd880-912b-11eb-820c-1f2ca381304a.png) - -![route-create-done-list-en](https://user-images.githubusercontent.com/40708551/112923280-a0a94080-912b-11eb-8b83-3960778ecf8a.png) - -## Setting - -![setting](https://user-images.githubusercontent.com/40708551/112923561-22996980-912c-11eb-926f-45177500eb65.png) diff --git a/docs/en/latest/api/api.md b/docs/en/latest/api/api.md deleted file mode 100644 index 2b43da6321..0000000000 --- a/docs/en/latest/api/api.md +++ /dev/null @@ -1,428 +0,0 @@ ---- -title: API doc of Manager API ---- - - - -Manager API directly operates ETCD and provides data management for Apache APISIX, provides APIs for Front-end or other clients. - -**License:** [Apache License 2.0](http://www.apache.org/licenses/LICENSE-2.0) - -### /apisix/admin/migrate/export - -#### GET - -##### Summary - -Export a config file for migrate. - -##### Parameters - -None. - -##### Responses - -A file for download. - -### /apisix/admin/migrate/import - -##### Summary - -Import the config file for restore config. - -#### POST - -##### Parameters (FORM) - -| Name | Located in | Description | Required | Schema | -| ---- | ---------- | --------------------------------------- | -------- | ------ | -| mode | body(form) | import mode (return, skip or overwrite) | Yes | string | -| file | body(form) | file to upload | Yes | string | - -##### Responses - -| Code | Description | Schema | -| ----- | --------------- | --------------------- | -| 0 | import success | [ApiError](#ApiError) | -| 20001 | Config conflict | [ApiError](#ApiError) | - -### /apisix/admin/check_ssl_cert - -#### POST - -##### Summary - -verify SSL cert and key. - -##### Parameters - -| Name | Located in | Description | Required | Schema | -| ---- | ---------- | ----------- | -------- | ------ | -| cert | body | cert of SSL | Yes | string | -| key | body | key of SSL | Yes | string | - -##### Responses - -| Code | Description | Schema | -| ------- | ----------------- | --------------------- | -| 0 | SSL verify passed | [ApiError](#ApiError) | -| default | unexpected error | [ApiError](#ApiError) | - -### /apisix/admin/check_ssl_exists - -#### POST - -##### Summary - -Check whether the SSL exists. - -##### Parameters - -| Name | Located in | Description | Required | Schema | -| ---- | ---------- | ----------- | -------- | ------ | -| cert | body | cert of SSL | Yes | string | -| key | body | key of SSL | Yes | string | - -##### Responses - -| Code | Description | Schema | -| ------- | ---------------- | --------------------- | -| 0 | SSL exists | [ApiError](#ApiError) | -| default | unexpected error | [ApiError](#ApiError) | - -### /apisix/admin/consumers - -#### GET - -##### Summary - -Return the consumer list according to the specified page number and page size, and can search consumers by username. - -##### Parameters - -| Name | Located in | Description | Required | Schema | -| --------- | ---------- | -------------------- | -------- | ------- | -| page | query | page number | No | integer | -| page_size | query | page size | No | integer | -| username | query | username of consumer | No | string | - -##### Responses - -| Code | Description | Schema | -| ------- | ---------------- | ------------------------- | -| 0 | list response | [ [consumer](#consumer) ] | -| default | unexpected error | [ApiError](#ApiError) | - -### /apisix/admin/notexist/routes - -#### GET - -##### Summary - -Return result of route exists checking by name and exclude id. - -##### Parameters - -| Name | Located in | Description | Required | Schema | -| ------- | ---------- | --------------------------------- | -------- | ------ | -| name | query | name of route | No | string | -| exclude | query | id of route that exclude checking | No | string | - -##### Responses - -| Code | Description | Schema | -| ------- | ---------------- | --------------------- | -| 0 | route not exists | [ApiError](#ApiError) | -| default | unexpected error | [ApiError](#ApiError) | - -### /apisix/admin/routes - -#### GET - -##### Summary - -Return the route list according to the specified page number and page size, and can search routes by name and uri. - -##### Parameters - -| Name | Located in | Description | Required | Schema | -| --------- | ---------- | -------------- | -------- | ------- | -| page | query | page number | No | integer | -| page_size | query | page size | No | integer | -| name | query | name of route | No | string | -| uri | query | uri of route | No | string | -| label | query | label of route | No | string | - -##### Responses - -| Code | Description | Schema | -| ------- | ---------------- | --------------------- | -| 0 | list response | [ [route](#route) ] | -| default | unexpected error | [ApiError](#ApiError) | - -### /apisix/admin/services - -#### GET - -##### Summary - -Return the service list according to the specified page number and page size, and can search services by name. - -##### Parameters - -| Name | Located in | Description | Required | Schema | -| --------- | ---------- | --------------- | -------- | ------- | -| page | query | page number | No | integer | -| page_size | query | page size | No | integer | -| name | query | name of service | No | string | - -##### Responses - -| Code | Description | Schema | -| ------- | ---------------- | ----------------------- | -| 0 | list response | [ [service](#service) ] | -| default | unexpected error | [ApiError](#ApiError) | - -### /apisix/admin/export/routes/{ids} - -#### Summary - -Export specific or all routes as OpenAPI schema. - -##### Parameters - -| Name | Located in | Description | Required | Schema | -|------------|------------|---------------------------------------------------------------------------------------------------------------------------------------------| -------- | ------- | -| ids | path | To export specific routes, please provide the route IDs separated by commas. If you leave the ids field empty, all routes will be exported. | No | integer | - -##### Responses - -| Code | Description | Schema | -| ------- |----------------------|-------------------------------------------------------------------------------------------------------| -| 0 | openapi json content | [ [OpenAPI schema](https://github.com/OAI/OpenAPI-Specification/blob/main/schemas/v3.0/schema.json) ] | -| default | unexpected error | [ApiError](#ApiError) | - -### /apisix/admin/ssl - -#### GET - -##### Summary - -Return the SSL list according to the specified page number and page size, and can SSLs search by sni. - -##### Parameters - -| Name | Located in | Description | Required | Schema | -| --------- | ---------- | ----------- | -------- | ------- | -| page | query | page number | No | integer | -| page_size | query | page size | No | integer | -| sni | query | sni of SSL | No | string | - -##### Responses - -| Code | Description | Schema | -| ------- | ---------------- | --------------------- | -| 0 | list response | [ [ssl](#ssl) ] | -| default | unexpected error | [ApiError](#ApiError) | - -### /apisix/admin/upstreams - -#### GET - -##### Summary - -Return the upstream list according to the specified page number and page size, and can search upstreams by name. - -##### Parameters - -| Name | Located in | Description | Required | Schema | -| --------- | ---------- | ---------------- | -------- | ------- | -| page | query | page number | No | integer | -| page_size | query | page size | No | integer | -| name | query | name of upstream | No | string | - -##### Responses - -| Code | Description | Schema | -| ------- | ---------------- | ------------------------- | -| 0 | list response | [ [upstream](#upstream) ] | -| default | unexpected error | [ApiError](#ApiError) | - -### /apisix/admin/user/login - -#### POST - -##### Summary - -user login. - -##### Parameters - -| Name | Located in | Description | Required | Schema | -| -------- | ---------- | ----------- | -------- | ------ | -| username | body | user name | Yes | string | -| password | body | password | Yes | string | - -##### Responses - -| Code | Description | Schema | -| ------- | ---------------- | --------------------- | -| 0 | login success | [ApiError](#ApiError) | -| default | unexpected error | [ApiError](#ApiError) | - -### Models - -#### ApiError - -| Name | Type | Description | Required | -| ------- | ------ | ---------------- | -------- | -| code | long | response code | No | -| message | string | response message | No | - -#### BaseInfo - -| Name | Type | Description | Required | -| ----------- | ------ | ----------- | -------- | -| create_time | long | | No | -| id | object | | No | -| update_time | long | | No | - -#### Consumer - -| Name | Type | Description | Required | -| ----------- | ------ | ----------- | -------- | -| create_time | long | | No | -| desc | string | | No | -| id | object | | No | -| labels | object | | No | -| plugins | object | | No | -| update_time | long | | No | -| username | string | | No | - -#### LoginInput - -| Name | Type | Description | Required | -| -------- | ------ | ----------- | -------- | -| password | string | password | No | -| username | string | user name | No | - -#### Route - -| Name | Type | Description | Required | -| ---------------- | --------------------------- | ----------- | -------- | -| create_time | long | | No | -| desc | string | | No | -| enable_websocket | boolean | | No | -| filter_func | string | | No | -| host | string | | No | -| hosts | [ string ] | | No | -| id | object | | No | -| labels | object | | No | -| methods | [ string ] | | No | -| name | string | | No | -| plugins | object | | No | -| priority | long | | No | -| remote_addr | string | | No | -| remote_addrs | [ string ] | | No | -| script | object | | No | -| service_id | object | | No | -| service_protocol | string | | No | -| update_time | long | | No | -| upstream | [UpstreamDef](#UpstreamDef) | | No | -| upstream_id | object | | No | -| uri | string | | No | -| uris | [ string ] | | No | -| vars | object | | No | - -#### SSL - -| Name | Type | Description | Required | -| -------------- | ---------- | ----------- | -------- | -| cert | string | | No | -| certs | [ string ] | | No | -| create_time | long | | No | -| exptime | long | | No | -| id | object | | No | -| key | string | | No | -| keys | [ string ] | | No | -| labels | object | | No | -| sni | string | | No | -| snis | [ string ] | | No | -| status | long | | No | -| update_time | long | | No | -| validity_end | long | | No | -| validity_start | long | | No | - -#### Service - -| Name | Type | Description | Required | -| ---------------- | --------------------------- | ----------- | -------- | -| create_time | long | | No | -| desc | string | | No | -| enable_websocket | boolean | | No | -| id | object | | No | -| labels | object | | No | -| name | string | | No | -| plugins | object | | No | -| script | string | | No | -| update_time | long | | No | -| upstream | [UpstreamDef](#UpstreamDef) | | No | -| upstream_id | object | | No | - -#### Upstream - -| Name | Type | Description | Required | -| ------------------- | ------ | ----------- | -------- | -| checks | object | | No | -| create_time | long | | No | -| desc | string | | No | -| hash_on | string | | No | -| id | object | | No | -| k8s_deployment_info | object | | No | -| key | string | | No | -| labels | object | | No | -| name | string | | No | -| nodes | object | | No | -| pass_host | string | | No | -| retries | long | | No | -| service_name | string | | No | -| timeout | object | | No | -| type | string | | No | -| update_time | long | | No | -| upstream_host | string | | No | - -#### UpstreamDef - -| Name | Type | Description | Required | -| ------------------- | ------ | ----------- | -------- | -| checks | object | | No | -| desc | string | | No | -| hash_on | string | | No | -| k8s_deployment_info | object | | No | -| key | string | | No | -| labels | object | | No | -| name | string | | No | -| nodes | object | | No | -| pass_host | string | | No | -| retries | long | | No | -| service_name | string | | No | -| timeout | object | | No | -| type | string | | No | -| upstream_host | string | | No | diff --git a/docs/en/latest/back-end-tests.md b/docs/en/latest/back-end-tests.md deleted file mode 100644 index 4840335ed0..0000000000 --- a/docs/en/latest/back-end-tests.md +++ /dev/null @@ -1,299 +0,0 @@ ---- -title: Backend Tests ---- - - - -This document provides the details of setting up the environment for running the tests locally with the guide for the writing unit & E2E tests for the backend. - -## Table of Contents - -- [Running E2E Tests Locally](#running-e2e-tests-locally) - - [Start with source code](#start-with-source-code) - - [Start with docker-compose](#start-with-docker-compose) - - [Start tests](#start-tests) -- [Writing Unit & E2E (End to End) Tests](#writing-unit-&-e2e-(end-to-end)-tests) - - [Writing Unit Tests](#writing-unit-tests) - - [Writing E2E Tests](#writing-e2e-tests) - -## Running E2E Tests Locally - -## Start with source code - -1. To run back end E2E test, please start the `manager-api`, `apisix`, `etcd` and `upstream-node` at first. - -2. To start the `manager-api` project locally, please refer to [develop](./develop.md) web section. - -3. To start the etcd locally, please refer to [etcd start](https://apisix.apache.org/docs/apisix/install-dependencies/) web section. - -4. To start the `apisix` project locally, please refer to [apisix start](https://github.com/apache/apisix#get-started) web section. - -5. To start the `upstream-node` locally, please install docker in the local environment and execute the command. - - ```sh - docker run -d --name upstream -v /(Your apisix-dashboard folder path)/api/test/docker/upstream.conf:/etc/nginx/conf.d/default.conf:ro -p 80:80 -p 1980:1980 -p 1981:1981 -p 1982:1982 -p 1983:1983 -p 1984:1984 johz/upstream:v2.0 - ``` - -6. After all the services are started, you can start the back-end E2E test. - -7. The `upstream-node` IP is temporarily changed to the local IP address. After the test, it should be changed to GitHub upstream node IP. If the test case does not involve the upstream node, it does not need to be modified. - - ```sh - # Local E2E test create route example - { - "uris": ["/test-test"], - "name": "route_all", - "desc": "test", - "methods": ["GET"], - "hosts": ["test.com"], - "status": 1, - "upstream": { - "nodes": { - # upstream node IP is required for local test - "(local ip):1981": 1 - }, - "type": "roundrobin" - } - } - - # GitHub E2E test create route example - { - "uris": ["/test-test"], - "name": "route_all", - "desc": "test", - "methods": ["GET"], - "hosts": ["test.com"], - "status": 1, - "upstream": { - "nodes": { - "172.16.238.20:1981": 1 - }, - "type": "roundrobin" - } - } - ``` - -[Back to TOC](#table-of-contents) - -## Start with docker-compose - -1. [install docker-compose](https://docs.docker.com/compose/install/) - - **NOTE:** In order to run docker compose locally, please change the values of `listen.host` and `etcd.endpoints` within `./api/conf/conf.yaml` as follows: - - ```sh - listen: - host: 0.0.0.0 - port: 9000 - etcd: - endpoints: - - 172.16.238.10:2379 - - 172.16.238.11:2379 - - 172.16.238.12:2379 - ``` - -2. Use `docker-compose` to run services such as `manager-api`, `apisix`, `etcd` and `upstream-node`, run the command. - - ```sh - cd /(Your apisix-dashboard folder path)/api/test/docker - # Download the apisix dockerfile - curl -o Dockerfile-apisix https://raw.githubusercontent.com/apache/apisix-docker/master/alpine/Dockerfile - docker-compose up -d - ``` - -3. When you use `docker-compose` to run the local E2E test and need to update the main code, you need to execute the command to close the cluster. - - ```sh - cd /(Your apisix-dashboard folder path)/api/test/docker - # -v: Remove links to mount volumes and volumes - docker-compose down -v - # If you don't want to remove the link between mount volume and volume, you can use - docker-compose stop [serviceName] - ``` - -4. Then you need to delete the image of the `manage-api`, rebuild the image of the `manage-api`, and start the cluster after the image is successfully built. - (Only if you have altered/added any core functionalities in `manager-api`, for simply adding/deleting a test case/file, rebuilding is not required). - -** For ease of access and to avoid the repetitive hassle for setting up the required configurations, we have provided a `setup.sh` script -which is inside `api/test/docker` directory. You can directly run, delete and build services along with update and revert `conf.yaml` through the script. -For more details, run - - ```sh - ./setup.sh help - ``` - -(If you are setting up the environment for the first time, please go with the described manual steps. It'll help you to get the idea of what's going on in the background). - -[Back to TOC](#table-of-contents) - -## Start tests - -1. After all the services are started, you can start the back-end E2E test. - - **NOTE:** Sometimes we need to delete the etcd store info. Otherwise, it will make the test failed. - - - Enter the E2E folder and execute the command to test all E2E test files. - - ```sh - cd /(Your apisix-dashboard folder path)/api/test/e2e - go test -v - ``` - - - You can also do E2E test on a single file. - - ```sh - cd /(Your apisix-dashboard folder path)/api/test/e2e - go test -v E2E-test-file.go base.go - ``` - -2. Currently, a lot of tests has been migrated to E2ENEW folder using the ginkgo testing framework for its ability to provide -high expressiveness which makes reading and writing tests a pleasure. - - - Enter the E2ENEW folder and execute the command to run all the E2ENEW test suites recursively. - - ```sh - cd /(Your apisix-dashboard folder path)/api/test/e2enew - ginkgo -r - ``` - - - You can also run a single E2ENEW test suite using ginkgo. - - ```sh - cd /(Your apisix-dashboard folder path)/api/test/e2enew/(path of the specific test suite) - ginkgo -r - ``` - -[Back to TOC](#table-of-contents) - -## Writing Unit & E2E (End to End) Tests - -## Writing Unit Tests - -Currently, all the unit tests for `manager-api` have been written using Go's built-in testing package. There is nothing new about it. You can directly add tests in the existing `_test.go` file or create a new one. There is one thing that needs to be addressed that is, since `manager-api` largely depends on handling data from etcd, in some cases, you need to write some feature that depends on storing & retrieval of information on and out of etcd. In such a scenario, you should write your unit tests using `store.MockInterface` instead of directly depending upon etcd. - -The `MockInterface` embeds `mock.Mock` object from [mock](https://pkg.go.dev/github.com/stretchr/testify/mock) package by testify. If helps in simulating function calls of an object with desired inputs as arguments and outputs as return values. Currently, all the unit tests in `route`, `service`, `ssl` and `upstream` handlers uses mock interface. For e.g. - -```go -mStore := &store.MockInterface{} -mStore.On("", mock.Anything) - .Run(func(args mock.Arguments) { - //arguments assertions or anything - //gets executed before returning - }) - .Return("") -``` - -You may tinker with the mentioned tests to get an idea of how it works or go through the [docs](https://pkg.go.dev/github.com/stretchr/testify/mock#pkg-index). - -[Back to TOC](#table-of-contents) - -## Writing E2E Tests - -Currently, the backend of apisix-dashboard have two types of e2e tests. One is plain e2e, the other is e2enew, where in the first one, tests are written using Go's built-in, native testing package, for the later, the tests are grouped into test suites and are evaluated using [ginkgo](https://onsi.github.io/ginkgo/) - a testing framework which helps in writing more expressive tests such that reading and writing tests give a pleasant experience. - -**Actively, we are migrating all of our e2e tests to e2enew module. So we are no more accepting tests inside e2e module, and any new tests must be added into the e2enew module by using ginkgo following the BDD style testing. If you have any query regarding it, please discuss your concerns with the community, we would be happy to address those. - -For value assertion, we are using the [assert](https://pkg.go.dev/github.com/stretchr/testify@v1.7.0/assert) package by testify. It provides lots of easy to use functions for assertion where the first argument is `*testing.T` object which can be obtained from `ginkgo.GinkgoT()`. - -If you are creating any test which requires making HTTP calls to any of the following node which involves `manager-api` or `apisix`, after setting up the environment (please refer [Running E2E Tests Locally](#running-e2e-tests-locally) for the details), you can use the `HttpTestCase` struct which provides a nice interface to make the calls along with performing necessary checks on the response. Here's a brief description of the most used fields of the struct, - -```go -type HttpTestCase struct { - Desc string // Description about the test case. - Object *httpexpect.Expect // returns a httpexpect object i.e. on which host the request is going to be made. - Method string // HTTP request methods ( GET, POST, PATCH, PUT, DELETE, OPTIONS). - Path string // the route path of that host - Query string // Query params - Body string // The request Body. Commonly used in POST, PUT, PATCH. - Headers map[string]string // Request headers. Include authorization header for secure routes. - ExpectStatus int // Expected HTTP status code from the response - ExpectCode int // Code generated by the host. Generally 0 for http.StatusOK. - ExpectMessage string // The response message provided in the response by the host. - ExpectBody interface{} // The expected message body as a response. - Sleep time.Duration //ms // Cooldown period before making next request. -} -``` - -Now to run a test use the `RunTestCase(tc HttpTestCase)` method which is provided into the base package inside the `e2enew` module. - -**NOTE:** E2ENEW also provides standalone methods for making HTTP request for GET, POST, PUT, DELETE methods along with making a POST request with `multipart/form` data. The method signatures are stated below - -- `HttpGet(url string, headers map[string]string) ([]byte, int, error)` -- `HttpPost(url string, headers map[string]string, reqBody string) ([]byte, int, error)` -- `HttpPut(url string, headers map[string]string, reqBody string) ([]byte, int, error)` -- `HttpDelete(url string, headers map[string]string) ([]byte, int, error)` - -Now coming back to writing e2enew tests, - -*If you are new to ginkgo it's always recommended going through the official [docs](https://onsi.github.io/ginkgo/) first. - -- To create a new tests' suite, create the new directory under `e2enew` module. Then for the initial bootstrapping use, - - ```sh - mkdir #inside e2enew - cd - ginkgo bootstrap # Generates _suite_test.go - #to add tests in separate files - ginkgo generate #Generates _test.go - ``` - -- This can be done manually, however, grouping similar tests in specific test files is always recommended. Please try to separate tests in separate test files. - -- We use different ginkgo containers for writing tests which includes `Describe`, `It`, `AfterSuite`, `BeforeEach` etc. [ [ref](https://onsi.github.io/ginkgo/#structuring-your-specs) ]. For eg, adding a few logically similar tests inside an existing test suite may looks like - - ```go - var _ = ginkgo.Describe("", func() { - ginkgo.It("", func() { - //Testing logic & assertions - }) - ginkgo.It("", func() { - //Testing logic & assertions - }) - }) - ``` - - here the `Describe` container is grouping similar tests through multiple `It` blocks by making extensive use of closures to give the syntax a high expressiveness. - -- Though depending upon the scenario, it is always recommended to use ginkgo's table-driven tests for running the independent `HttpTestCase` using `table.DescribeTable` and `table.Entry` [ [ref](https://pkg.go.dev/github.com/onsi/ginkgo/extensions/table) ]. For eg, - - ```go - var _ = ginkgo.Describe("", func() { - table.DescribeTable("", - func(tc base.HttpTestCase) { - base.RunTestCase(tc) - }, - table.Entry("", base.HttpTestCase{ - //Fill the fields - }), - table.Entry("", base.HttpTestCase{ - //Fill the fields - }), - }) - - table.DescribeTable("", func () { - ... - }) - - }) - ``` - -- FYI, internally ginkgo reduces each table entries to `It` block and run all the `It` blocks concurrently/parallel. Ginkgo auto recovers from panics inside `It` blocks only, so always put your assertions inside `It` containers. - -[Back to TOC](#table-of-contents) diff --git a/docs/en/latest/config.json b/docs/en/latest/config.json deleted file mode 100644 index 7d3b343935..0000000000 --- a/docs/en/latest/config.json +++ /dev/null @@ -1,43 +0,0 @@ -{ - "version": "3.0.0", - "sidebar": [ - { - "type": "category", - "label": "Getting Started", - "items": ["USER_GUIDE", "api/api"] - }, - { - "type": "category", - "label": "Installation", - "items": ["install", "deploy-with-docker"] - }, - { - "type": "category", - "label": "Contributing", - "items": ["develop", "I18N_USER_GUIDE", "front-end-e2e", "back-end-tests"] - }, - { - "type": "doc", - "id": "FAQ" - }, - { - "type": "category", - "label": "Others", - "items": [ - { - "type": "category", - "label": "Import / Export", - "items": [ - "modules/data_loader", - "modules/data_loader/openapi3" - ] - } - ] - }, - { - "type": "link", - "label": "CHANGELOG", - "href": "https://github.com/apache/apisix-dashboard/blob/master/CHANGELOG.md" - } - ] -} diff --git a/docs/en/latest/deploy-with-docker.md b/docs/en/latest/deploy-with-docker.md deleted file mode 100644 index 9633b66a01..0000000000 --- a/docs/en/latest/deploy-with-docker.md +++ /dev/null @@ -1,86 +0,0 @@ ---- -title: Rebuild Docker image ---- - - - -**NOTE:** We support Docker Image, please visit [DockerHub](https://hub.docker.com/r/apache/apisix-dashboard) for more information. The following steps are for building Docker Image manually. - -To build the Dashboard with Docker, you simply download the `Dockerfile` file from the **root directory** to your device (no need to download all source codes) then follow this guide. - -The `manager-api` and `web` will be included in this build guide product. - -## Prerequisites - -Before using Docker to build images and start containers, make sure that the following dependencies are installed and running in your environment. - -1. [Docker](https://docs.docker.com/engine/install/) -2. [etcd](https://etcd.io/docs/v3.4.0/dl-build/) 3.4.0+ - -## Build - -```sh -# Execute the build command in the directory where the Dockerfile is located (by default, the project root), specifying the tag manually. -$ docker build -t apisix-dashboard:$tag . - -# For users in mainland China, the `ENABLE_PROXY` parameter can be provided to speed up module downloads. -$ docker build -t apisix-dashboard:$tag . --build-arg ENABLE_PROXY=true - -## Launch - -1. Preparing configuration files - -Before starting the container, the configuration file `conf.yaml` needs to be prepared inside the **host** to override the default [configuration file](https://github.com/apache/apisix-dashboard/blob/master/api/conf/conf.yaml) inside the container. - -Kindly note: - -- Only when `conf.listen.host` is `0.0.0.0` can the external network access the services within the container. -- `conf.etcd.endpoints` must be able to access the `etcd` service within the container. For example: use `host.docker.internal:2379` so that the container can access `etcd` on the host network. - -2. Launch the Dashboard - -```sh -# /path/to/conf.yaml Requires an absolute path pointing to the configuration file mentioned above. -$ docker run -d -p 9000:9000 -v /path/to/conf.yaml:/usr/local/apisix-dashboard/conf/conf.yaml --name apisix-dashboard apisix-dashboard:$tag -``` - -3. Check if the container started successfully - -```sh -$ docker ps -a -``` - -If the container `apisix-dashboard` is ok, visit `http://127.0.0.1:9000` to use the dashboard with GUI, where the default username and password are `admin`. - -4. Stop - -```sh -$ docker stop apisix-dashboard -``` - -## Other - -1. Caching is not recommended when building a image multiple times. - -```sh -$ docker build -t apisix-dashboard:$tag . --no-cache=true -``` - -2. It is not recommended to use multiple instances at the same time. When using multiple instances, each instance generates and holds a JWT token, which will lead to verification conflicts. diff --git a/docs/en/latest/develop.md b/docs/en/latest/develop.md deleted file mode 100644 index 1a7c3afeba..0000000000 --- a/docs/en/latest/develop.md +++ /dev/null @@ -1,94 +0,0 @@ ---- -title: Development Guide ---- - - - -The Dashboard contains both `manager-api` and `web` parts, so you need to start the development environment separately. - -## Prerequisites - -Before development, refer to this [guide](./install.md) to install dependencies. - -## Clone the project - -```sh -$ git clone -b release/3.0 https://github.com/apache/apisix-dashboard.git -``` - -## Start developing - -```sh -$ cd apisix-dashboard -``` - -### manager-api - -1. Please change the configuration in `api/conf/conf.yaml`. - -2. In the root directory, launch development mode. - -```sh -$ make api-run -``` - -3. In the root directory, stop development mode. - -```sh -$ make api-stop -``` - -4. Please refer to the [FAQ](./FAQ.md) about the problem of displaying exception in the dashboard after adding custom plugins or modifying plugin's schema. - -5. If writing an back end E2E test, please refer to the [Back End E2E Writing Guide](./back-end-tests.md) - -### web - -1. Go to the `web` directory. - -```sh -$ cd ./web -``` - -2. Please change the `manager-api` address in the `web/.env` file. If you follow this guidelines, the address may need to be set as below. - -> All commands here are for Linux environment, other systems please use the corresponding commands for your platform. You are also welcome to contribute your own methods. - -```bash -echo "SERVE_URL_DEV=http://localhost:9000" > web/.env -``` - -If you don't want to create the file, you can also export the variable. - -```bash -export SERVE_URL_DEV=http://localhost:9000 -``` - -3. Launch development mode - -```sh -$ yarn install - -$ yarn start -``` - -> If there is an error about gyp during yarn install, please ignore it and go ahead! - -4. If writing an front end E2E test, please refer to the [Front End E2E Writing Guide](./front-end-e2e.md) diff --git a/docs/en/latest/front-end-e2e.md b/docs/en/latest/front-end-e2e.md deleted file mode 100644 index c1b330bc76..0000000000 --- a/docs/en/latest/front-end-e2e.md +++ /dev/null @@ -1,52 +0,0 @@ ---- -title: Frontend E2E ---- - - - -This project uses [Cypress](https://www.cypress.io/) as the front-end E2E test framework. - -1. To start the front-end project locally, please refer to [develop](./develop.md) web section. - -2. Open Cypress test-runner. For the use of test-runner, please refer to [test-runner](https://docs.cypress.io/guides/core-concepts/test-runner.html#) Overview. - - ```sh - yarn cypress:open-dev - ``` - -3. Write your test examples: please refer to the test examples in the `/web/cypress` directory, or see [RWA](https://github.com/cypress-io/cypress-realworld-app) for more examples. - -To make it easy for users to develop front-end E2E cases, we use the remote manager-api by default. If you want to use the local manager-api, please read the following instructions: - -1. Start the local manager-api service, please refer to [develop](./develop.md) manager-api section. - -2. To start the front-end project locally, please refer to [develop](./develop.md) web section. NOTE: You need to change `yarn start` to `yarn start:e2e` when you start. - -3. Open Cypress test-runner. - - ```sh - yarn cypress:open - ``` - -Reference links: - -- Cypress API Documents: https://docs.cypress.io/api/api/table-of-contents.html - -- Cypress Best Practices: https://docs.cypress.io/guides/references/best-practices.html#Organizing-Tests-Logging-In-Controlling-State diff --git a/docs/en/latest/install.md b/docs/en/latest/install.md deleted file mode 100644 index 42ea4c107f..0000000000 --- a/docs/en/latest/install.md +++ /dev/null @@ -1,170 +0,0 @@ ---- -title: Basic Deploy ---- - - - -Installing Apache APISIX Dashboard on Linux is easy. -Now, we provide Docker image and RPM installation package. - -## Docker {#docker} - -We recommend using Docker to run Dashboard: - -```shell -docker pull apache/apisix-dashboard -docker run -d --name dashboard \ - -p 9000:9000 \ - -v :/usr/local/apisix-dashboard/conf/conf.yaml \ - apache/apisix-dashboard -``` - -:::note -Please replace `` with the **absolute path** to your [configuration file](https://github.com/apache/apisix-dashboard/blob/master/api/conf/conf.yaml). -::: - -## RPM {#rpm} - -**NOTE:** Only CentOS 7 is supported currently. - -### Install - -```shell -# 1. install RPM package -sudo yum install -y https://github.com/apache/apisix-dashboard/releases/download/v3.0.0/apisix-dashboard-3.0.0-0.el7.x86_64.rpm -``` - -### Launch - -```shell -# run dashboard in the shell -sudo manager-api -p /usr/local/apisix/dashboard/ - -# or run dashboard as a service -systemctl start apisix-dashboard -``` - -Without changing the configuration, visit `http://127.0.0.1:9000` to use the dashboard with GUI, where the default username and password are `admin`. - -## Source {#source} - -The Dashboard project contains both `manager-api` and `web`, but `web` is _optional_. - -The `manager-api` and `web` will be included in this build guide product. - -### Prerequisites {#source-prerequisites} - -Before using source codes to build, make sure that the following dependencies are installed in your environment. - -For `manager-api`: - -1. [Golang](https://golang.org/dl/) 1.16+ - -> Tip: For users in mainland China, you can use the following command to speed up the module downloads. - -```sh -$ go env -w GOPROXY=https://goproxy.cn,direct -``` - -For `web`: - -1. [Node.js](https://nodejs.org/en/download/) current LTS (14.x+) -2. [Yarn](https://yarnpkg.com/getting-started/install) - -### Download {#source-download} - -```shell -git clone -b release/3.0 https://github.com/apache/apisix-dashboard.git && cd apisix-dashboard -``` - -### Build {#source-build} - -```shell -cd apisix-dashboard -make build -``` - -When the build is complete, the results are stored in the root `output` directory. - -Note: `make build` will build `manager-api` and `web`, use the `make help` command to see more commands. - -### Launch {#source-launch} - -1. After the build is complete and before you start, make sure the following dependencies are installed and running in your environment. - -- [etcd](https://etcd.io/docs/v3.4.0/dl-build/) 3.4.0+ - -2. Check and modify the configuration information in `output/conf/conf.yaml` according to your deployment environment. - -3. Launch the Dashboard - -```shell -cd ./output - -./manager-api -``` - -4. Without changing the configuration, visit `http://127.0.0.1:9000` to use the dashboard with GUI, where the default username and password are `admin`. - -### Service {#source-service} - -You will need to handle your own service management when deploying using the source code compilation method. We provide a service file template for operating systems that use the Systemd service manager. - -1. Install - -```shell -mkdir -p /usr/local/apisix-dashboard -cp -rf ./output/* /usr/local/apisix-dashboard -``` - -2. Create service unit - -Copy the following or use this [**file**](https://github.com/apache/apisix-dashboard/tree/master/service/apisix-dashboard.service) directly, you need to copy it to the `/usr/lib/systemd/system` directory and execute the `systemctl daemon-reload` command. - -```shell -# copy service unit -cp ./api/service/apisix-dashboard.service /usr/lib/systemd/system/apisix-dashboard.service -systemctl daemon-reload - -# or: If you need to modify the service unit, you can use the following command -echo "[Unit] -Description=apisix-dashboard -Conflicts=apisix-dashboard.service -After=network-online.target - -[Service] -WorkingDirectory=/usr/local/apisix-dashboard -ExecStart=/usr/local/apisix-dashboard/manager-api -c /usr/local/apisix-dashboard/conf/conf.yaml" > /usr/lib/systemd/system/apisix-dashboard.service -``` - -3. Manage service - -You can use the following command to manage the service. - -```shell -# start apisix-dashboard -systemctl start apisix-dashboard - -# stop apisix-dashboard -systemctl stop apisix-dashboard - -# check apisix-dashboard status -systemctl status apisix-dashboard -``` diff --git a/docs/en/latest/modules/data_loader.md b/docs/en/latest/modules/data_loader.md deleted file mode 100644 index 0001edaabc..0000000000 --- a/docs/en/latest/modules/data_loader.md +++ /dev/null @@ -1,75 +0,0 @@ ---- -title: Data Loader -keywords: -- APISIX -- APISIX Dashboard -- Data Loader -- OpenAPI -description: This document contains information about the Apache APISIX Dashboard data loader framework. ---- - - - -## What is Data Loader - -The data loader is an abstraction of the data import and export functionality under different specification definitions. - -Based on it, developers can easily implement the ability to export APISIX data entities to generate different data files, and parse the data files and convert them into APISIX data entities for import into APISIX. - -## Definition - -```go -type Loader interface { - // Import accepts data and converts it into entity data sets - Import(input interface{}) (*DataSets, error) - - // Export accepts entity data sets and converts it into a specific format - Export(data DataSets) (interface{}, error) -} -``` - -Implement the above functions to complete the data conversion between Raw data format and DataSets data set, APISIX Dashboard will use DataSets data format as the intermediate format for importing and exporting according to it. - -Developers can look at the code in [api/internal/handler/data_loader/loader/loader.go](https://github.com/apache/apisix-dashboard/blob/master/api/internal/handler/data_loader/loader/loader.go) for the definition of the DataSets structure and the Data Loader interface. - -## Supported data loader - -- [OpenAPI 3](data_loader/openapi3.md): Currently only data import is supported - -## How to support other data loader - -Extending the data loader is simple and requires some development in the backend and frontend. - -### Implement data loader conversion logic (back-end) - -Create a structure that implements the above interface, which contains two parts. - -- Import function: complete the parsing and conversion from raw format `[]byte` uploaded by the user to DataSets structure [api/internal/handler/data_loader/loader/openapi3/import.go](https://github.com/apache/apisix-dashboard/blob/master/api/internal/handler/data_loader/loader/openapi3/import.go) -- Export function: complete the generation of raw format for DataSets structure data inputted from APISIX Dashboard - -Adds a new item to the data loader list of the import and export handler. - -### Add front UI support for new data loader (front-end) - -Adds the previously created data loader to the frontend selector. Refer to [this](https://github.com/apache/apisix-dashboard/blob/master/web/src/pages/Route/components/DataLoader/Import.tsx#L167-L172) for more details. - -:::note -When you implement a data loader that requires partial input of custom parameters, you can create a form for it to enter data. Refer to [this](https://github.com/apache/apisix-dashboard/blob/master/web/src/pages/Route/components/DataLoader/loader/OpenAPI3.tsx) for more details. -::: diff --git a/docs/en/latest/modules/data_loader/openapi3.md b/docs/en/latest/modules/data_loader/openapi3.md deleted file mode 100644 index 433aaf78a4..0000000000 --- a/docs/en/latest/modules/data_loader/openapi3.md +++ /dev/null @@ -1,115 +0,0 @@ ---- -title: OpenAPI 3 -keywords: -- APISIX -- APISIX Dashboard -- Data Loader -- OpenAPI -description: This document contains information about the OpenAPI 3 data loader. ---- - - - -## Overview - -OpenAPI 3 data loader currently supports importing standard OpenAPI 3 documentation to generate individual paths as routes and upstreams in Apache APISIX. - -## Configuration - -| Name | Type | Default | Description | -|--------------|---------|---------|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| -| merge_method | boolean | true | HTTP method merge. When an OpenAPI 3 Path has multiple HTTP methods, this parameter controls whether they are merged or not. When multiple HTTP methods have different detailed configurations, such as `securitySchema` etc., this parameter can be turned off to prevent the APISIX Dashboard importer from merging them into a single APISIX route, which will generate a route for each HTTP method for each Path. | - -## Usage - -### Import - -1. Open the import drawer menu - -![Open menu](../../../../assets/images/modules/data_loader/intro.png) - -2. Configure import parameters - -Select `OpenAPI 3` in the data loader type and set a task name for this import, which will determine the name of the route and upstream generated by this import. - -:::note - -The current `OpenAPI 3` generates routes and upstream names according to this rule. - -- When the task name is `demo` and the HTTP method merge is enabled. - -| OpenAPI 3 Path & Method | APISIX route name | APISIX route uri | APISIX route methods | -|-------------------------|--------------------|------------------|----------------------| -| GET /hello1 | demo_hello1 | /hello1 | GET | -| GET & POST /hello2 | demo_hello2 | /hello2 | GET, POST | -| PUT /hello3/{name} | demo_hello3/{name} | /hello3 | PUT | - -- When the task name is `demo` and HTTP method merging is disabled. - -| OpenAPI 3 Path & Method | APISIX route name | APISIX route uri | APISIX route methods | -|-------------------------|--------------------------|------------------|----------------------| -| GET /hello1 | demo_hello1_GET | /hello1 | GET | -| PUT & DELETE /hello2 | demo_hello2_PUT | /hello2 | PUT | -| | demo_hello2_DELETE | /hello2 | DELETE | -| PATCH /hello3/{name} | demo_hello3/{name}_PATCH | /hello3 | PATCH | - -Generate an empty upstream named `demo`, which has no node data configured for fields such as `nodes`, so users can modify it manually to meet their needs. - -::: - -The following shows the parameters for the `OpenAPI 3` data loader, which currently has the Merge HTTP Methods configuration. - -Finally, select an OpenAPI 3 documentation file in Upload section and submit the form. - -![Set import parameters](../../../../assets/images/modules/data_loader/openapi3-1.png) - -:::note -Only one OpenAPI 3 documentation file can be selected at this time. -::: - -3. After submitting and viewing the import results - -When the import successful, the generated routes and the number of upstreams will be displayed. - -![Imported successfully](../../../../assets/images/modules/data_loader/openapi3-2.png) - -When the import fails, the number of errors and the reason for the error are displayed. - -![Import failure](../../../../assets/images/modules/data_loader/openapi3-3.png) - -4. View the generated routes - -![Route List](../../../../assets/images/modules/data_loader/openapi3-4.png) - -5. Modify upstream configuration - -The import process generates an upstream named `demo` with the same name as the import task. - -![Upstream List](../../../../assets/images/modules/data_loader/openapi3-5.png) - -It does not have an upstream node configured and cannot be requested properly yet, so you need to modify its node or service discovery configuration. Remove this default node and add node information according to your own service. - -![Upstream configuration](../../../../assets/images/modules/data_loader/openapi3-6.png) - -Save the upstream configuration. - -6. Test API - -Use the test tool to call the API to determine if it is configured correctly. diff --git a/eslint.config.js b/eslint.config.js new file mode 100644 index 0000000000..1f138d22fd --- /dev/null +++ b/eslint.config.js @@ -0,0 +1,71 @@ +import js from '@eslint/js' +import globals from 'globals' +import react from 'eslint-plugin-react' +import reactHooks from 'eslint-plugin-react-hooks' +import reactRefresh from 'eslint-plugin-react-refresh' +import tseslint from 'typescript-eslint' +import unusedImports from 'eslint-plugin-unused-imports' + +export default tseslint.config( + { ignores: ['dist', 'src/routeTree.gen.ts'] }, + { + extends: [js.configs.recommended, ...tseslint.configs.recommended], + files: ['**/*.{ts,tsx}'], + languageOptions: { + ecmaVersion: 2020, + globals: globals.browser, + }, + plugins: { + 'react-hooks': reactHooks, + 'react-refresh': reactRefresh, + 'react': react, + "unused-imports": unusedImports, + }, + "settings": { + "react": { + "version": "detect", + } + }, + rules: { + "no-console": "warn", + "no-unused-vars": "off", // or "@typescript-eslint/no-unused-vars": "off", + "unused-imports/no-unused-imports": "error", + "unused-imports/no-unused-vars": [ + "warn", + { + "vars": "all", + "varsIgnorePattern": "^_", + "args": "after-used", + "argsIgnorePattern": "^_", + }, + ], + ...react.configs.flat.recommended.rules, + ...react.configs.flat['jsx-runtime'].rules, + ...reactHooks.configs.recommended.rules, + 'react-refresh/only-export-components': [ + 'warn', + { allowConstantExport: true }, + ], + "react/jsx-curly-brace-presence": [ + "error", + { + "props": "never", + "children": "never" + } + ], + "react/no-unescaped-entities": [ + "error", + { + "forbid": [">", "}"] + } + ], + "react/no-children-prop": ["error", { + "allowFunctions": true + }], + "react/self-closing-comp": ["error", { + "component": true, + "html": true + }] + }, + }, +) diff --git a/index.html b/index.html new file mode 100644 index 0000000000..3557107918 --- /dev/null +++ b/index.html @@ -0,0 +1,13 @@ + + + + + + + Apache APISIX Dashboard + + +
+ + + diff --git a/licenses/LICENSE-ant-design-pro.txt b/licenses/LICENSE-ant-design-pro.txt deleted file mode 100644 index c17ba2fb84..0000000000 --- a/licenses/LICENSE-ant-design-pro.txt +++ /dev/null @@ -1,21 +0,0 @@ -MIT License - -Copyright (c) 2019 Alipay.inc - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. diff --git a/licenses/LICENSE-antvis-x6.txt b/licenses/LICENSE-antvis-x6.txt deleted file mode 100644 index c17ba2fb84..0000000000 --- a/licenses/LICENSE-antvis-x6.txt +++ /dev/null @@ -1,21 +0,0 @@ -MIT License - -Copyright (c) 2019 Alipay.inc - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. diff --git a/licenses/LICENSE-json.lua.txt b/licenses/LICENSE-json.lua.txt deleted file mode 100644 index 39ddd05cbf..0000000000 --- a/licenses/LICENSE-json.lua.txt +++ /dev/null @@ -1,19 +0,0 @@ -Copyright (c) 2020 rxi - -Permission is hereby granted, free of charge, to any person obtaining a copy of -this software and associated documentation files (the "Software"), to deal in -the Software without restriction, including without limitation the rights to -use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies -of the Software, and to permit persons to whom the Software is furnished to do -so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. diff --git a/package.json b/package.json new file mode 100644 index 0000000000..bb9a57fe43 --- /dev/null +++ b/package.json @@ -0,0 +1,80 @@ +{ + "name": "apisix-dashboard", + "private": true, + "version": "0.0.0", + "type": "module", + "scripts": { + "dev": "vite", + "build": "tsc -b && vite build", + "lint": "eslint .", + "preview": "vite preview", + "prepare": "husky" + }, + "dependencies": { + "@ant-design/pro-components": "^2.8.7", + "@ant-design/v5-patch-for-react-19": "^1.0.3", + "@hookform/resolvers": "^5.0.1", + "@mantine/core": "^7.17.4", + "@mantine/hooks": "^7.17.4", + "@mantine/modals": "^7.17.4", + "@mantine/notifications": "^7.17.4", + "@monaco-editor/react": "^4.7.0", + "@tanstack/react-query": "^5.74.4", + "@tanstack/react-router": "^1.116.0", + "antd": "^5.24.8", + "axios": "^1.8.4", + "clsx": "^2.1.1", + "dayjs": "^1.11.13", + "fast-clean": "^1.4.0", + "i18next": "^25.0.1", + "immer": "^10.1.1", + "mobx": "^6.13.7", + "mobx-react-lite": "^4.1.0", + "monaco-editor": "^0.52.2", + "nanoid": "^5.1.5", + "qs": "^6.14.0", + "rambdax": "^11.3.1", + "react": "^19.0.0", + "react-dom": "^19.0.0", + "react-hook-form": "^7.56.1", + "react-i18next": "^15.4.1", + "react-use": "^17.6.0", + "zod": "^3.24.3", + "zod-empty": "^1.4.4" + }, + "devDependencies": { + "@eslint/js": "^9.22.0", + "@hookform/devtools": "^4.4.0", + "@iconify-json/material-symbols": "^1.2.20", + "@svgr/core": "^8.1.0", + "@svgr/plugin-jsx": "^8.1.0", + "@tanstack/react-query-devtools": "^5.74.4", + "@tanstack/react-router-devtools": "^1.116.0", + "@tanstack/router-plugin": "^1.116.1", + "@types/node": "^22.14.1", + "@types/qs": "^6.9.18", + "@types/react": "^19.0.10", + "@types/react-dom": "^19.0.4", + "@vitejs/plugin-react-swc": "^3.9.0", + "eslint": "^9.22.0", + "eslint-plugin-react": "^7.37.5", + "eslint-plugin-react-hooks": "^5.2.0", + "eslint-plugin-react-refresh": "^0.4.19", + "eslint-plugin-unused-imports": "^4.1.4", + "globals": "^16.0.0", + "husky": "^9.1.7", + "lint-staged": "^15.5.1", + "postcss-preset-mantine": "^1.17.0", + "postcss-simple-vars": "^7.0.1", + "typescript": "~5.7.2", + "typescript-eslint": "^8.26.1", + "unplugin-icons": "^22.1.0", + "vite": "^6.3.1", + "vite-tsconfig-paths": "^5.1.4" + }, + "lint-staged": { + "*.{js,jsx,ts,tsx}": [ + "eslint --fix --max-warnings=0 --no-warn-ignored" + ] + } +} diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml new file mode 100644 index 0000000000..1c9f4fccb8 --- /dev/null +++ b/pnpm-lock.yaml @@ -0,0 +1,7148 @@ +lockfileVersion: '9.0' + +settings: + autoInstallPeers: true + excludeLinksFromLockfile: false + +importers: + + .: + dependencies: + '@ant-design/pro-components': + specifier: ^2.8.7 + version: 2.8.7(antd@5.24.8(react-dom@19.1.0(react@19.1.0))(react@19.1.0))(rc-field-form@2.7.0(react-dom@19.1.0(react@19.1.0))(react@19.1.0))(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + '@ant-design/v5-patch-for-react-19': + specifier: ^1.0.3 + version: 1.0.3(antd@5.24.8(react-dom@19.1.0(react@19.1.0))(react@19.1.0))(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + '@hookform/resolvers': + specifier: ^5.0.1 + version: 5.0.1(react-hook-form@7.56.1(react@19.1.0)) + '@mantine/core': + specifier: ^7.17.4 + version: 7.17.4(@mantine/hooks@7.17.4(react@19.1.0))(@types/react@19.1.2)(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + '@mantine/hooks': + specifier: ^7.17.4 + version: 7.17.4(react@19.1.0) + '@mantine/modals': + specifier: ^7.17.4 + version: 7.17.4(@mantine/core@7.17.4(@mantine/hooks@7.17.4(react@19.1.0))(@types/react@19.1.2)(react-dom@19.1.0(react@19.1.0))(react@19.1.0))(@mantine/hooks@7.17.4(react@19.1.0))(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + '@mantine/notifications': + specifier: ^7.17.4 + version: 7.17.4(@mantine/core@7.17.4(@mantine/hooks@7.17.4(react@19.1.0))(@types/react@19.1.2)(react-dom@19.1.0(react@19.1.0))(react@19.1.0))(@mantine/hooks@7.17.4(react@19.1.0))(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + '@monaco-editor/react': + specifier: ^4.7.0 + version: 4.7.0(monaco-editor@0.52.2)(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + '@tanstack/react-query': + specifier: ^5.74.4 + version: 5.74.4(react@19.1.0) + '@tanstack/react-router': + specifier: ^1.116.0 + version: 1.116.0(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + antd: + specifier: ^5.24.8 + version: 5.24.8(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + axios: + specifier: ^1.8.4 + version: 1.8.4 + clsx: + specifier: ^2.1.1 + version: 2.1.1 + dayjs: + specifier: ^1.11.13 + version: 1.11.13 + fast-clean: + specifier: ^1.4.0 + version: 1.4.0 + i18next: + specifier: ^25.0.1 + version: 25.0.1(typescript@5.7.3) + immer: + specifier: ^10.1.1 + version: 10.1.1 + mobx: + specifier: ^6.13.7 + version: 6.13.7 + mobx-react-lite: + specifier: ^4.1.0 + version: 4.1.0(mobx@6.13.7)(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + monaco-editor: + specifier: ^0.52.2 + version: 0.52.2 + nanoid: + specifier: ^5.1.5 + version: 5.1.5 + qs: + specifier: ^6.14.0 + version: 6.14.0 + rambdax: + specifier: ^11.3.1 + version: 11.3.1 + react: + specifier: ^19.0.0 + version: 19.1.0 + react-dom: + specifier: ^19.0.0 + version: 19.1.0(react@19.1.0) + react-hook-form: + specifier: ^7.56.1 + version: 7.56.1(react@19.1.0) + react-i18next: + specifier: ^15.4.1 + version: 15.4.1(i18next@25.0.1(typescript@5.7.3))(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + react-use: + specifier: ^17.6.0 + version: 17.6.0(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + zod: + specifier: ^3.24.3 + version: 3.24.3 + zod-empty: + specifier: ^1.4.4 + version: 1.4.4(typescript@5.7.3)(zod@3.24.3) + devDependencies: + '@eslint/js': + specifier: ^9.22.0 + version: 9.25.1 + '@hookform/devtools': + specifier: ^4.4.0 + version: 4.4.0(@types/react@19.1.2)(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + '@iconify-json/material-symbols': + specifier: ^1.2.20 + version: 1.2.20 + '@svgr/core': + specifier: ^8.1.0 + version: 8.1.0(typescript@5.7.3) + '@svgr/plugin-jsx': + specifier: ^8.1.0 + version: 8.1.0(@svgr/core@8.1.0(typescript@5.7.3)) + '@tanstack/react-query-devtools': + specifier: ^5.74.4 + version: 5.74.4(@tanstack/react-query@5.74.4(react@19.1.0))(react@19.1.0) + '@tanstack/react-router-devtools': + specifier: ^1.116.0 + version: 1.116.0(@tanstack/react-router@1.116.0(react-dom@19.1.0(react@19.1.0))(react@19.1.0))(@tanstack/router-core@1.115.3)(csstype@3.1.3)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)(tiny-invariant@1.3.3) + '@tanstack/router-plugin': + specifier: ^1.116.1 + version: 1.116.1(@tanstack/react-router@1.116.0(react-dom@19.1.0(react@19.1.0))(react@19.1.0))(vite@6.3.2(@types/node@22.14.1)(sugarss@4.0.1(postcss@8.5.3))(tsx@4.19.3)(yaml@2.7.1)) + '@types/node': + specifier: ^22.14.1 + version: 22.14.1 + '@types/qs': + specifier: ^6.9.18 + version: 6.9.18 + '@types/react': + specifier: ^19.0.10 + version: 19.1.2 + '@types/react-dom': + specifier: ^19.0.4 + version: 19.1.2(@types/react@19.1.2) + '@vitejs/plugin-react-swc': + specifier: ^3.9.0 + version: 3.9.0(vite@6.3.2(@types/node@22.14.1)(sugarss@4.0.1(postcss@8.5.3))(tsx@4.19.3)(yaml@2.7.1)) + eslint: + specifier: ^9.22.0 + version: 9.25.1 + eslint-plugin-react: + specifier: ^7.37.5 + version: 7.37.5(eslint@9.25.1) + eslint-plugin-react-hooks: + specifier: ^5.2.0 + version: 5.2.0(eslint@9.25.1) + eslint-plugin-react-refresh: + specifier: ^0.4.19 + version: 0.4.20(eslint@9.25.1) + eslint-plugin-unused-imports: + specifier: ^4.1.4 + version: 4.1.4(@typescript-eslint/eslint-plugin@8.31.0(@typescript-eslint/parser@8.31.0(eslint@9.25.1)(typescript@5.7.3))(eslint@9.25.1)(typescript@5.7.3))(eslint@9.25.1) + globals: + specifier: ^16.0.0 + version: 16.0.0 + husky: + specifier: ^9.1.7 + version: 9.1.7 + lint-staged: + specifier: ^15.5.1 + version: 15.5.1 + postcss-preset-mantine: + specifier: ^1.17.0 + version: 1.17.0(postcss@8.5.3) + postcss-simple-vars: + specifier: ^7.0.1 + version: 7.0.1(postcss@8.5.3) + typescript: + specifier: ~5.7.2 + version: 5.7.3 + typescript-eslint: + specifier: ^8.26.1 + version: 8.31.0(eslint@9.25.1)(typescript@5.7.3) + unplugin-icons: + specifier: ^22.1.0 + version: 22.1.0(@svgr/core@8.1.0(typescript@5.7.3)) + vite: + specifier: ^6.3.1 + version: 6.3.2(@types/node@22.14.1)(sugarss@4.0.1(postcss@8.5.3))(tsx@4.19.3)(yaml@2.7.1) + vite-tsconfig-paths: + specifier: ^5.1.4 + version: 5.1.4(typescript@5.7.3)(vite@6.3.2(@types/node@22.14.1)(sugarss@4.0.1(postcss@8.5.3))(tsx@4.19.3)(yaml@2.7.1)) + +packages: + + '@ampproject/remapping@2.3.0': + resolution: {integrity: sha512-30iZtAPgz+LTIYoeivqYo853f02jBYSd5uGnGpkFV0M3xOt9aN73erkgYAmZU43x4VfqcnLxW9Kpg3R5LC4YYw==} + engines: {node: '>=6.0.0'} + + '@ant-design/colors@7.2.0': + resolution: {integrity: sha512-bjTObSnZ9C/O8MB/B4OUtd/q9COomuJAR2SYfhxLyHvCKn4EKwCN3e+fWGMo7H5InAyV0wL17jdE9ALrdOW/6A==} + + '@ant-design/cssinjs-utils@1.1.3': + resolution: {integrity: sha512-nOoQMLW1l+xR1Co8NFVYiP8pZp3VjIIzqV6D6ShYF2ljtdwWJn5WSsH+7kvCktXL/yhEtWURKOfH5Xz/gzlwsg==} + peerDependencies: + react: '>=16.9.0' + react-dom: '>=16.9.0' + + '@ant-design/cssinjs@1.23.0': + resolution: {integrity: sha512-7GAg9bD/iC9ikWatU9ym+P9ugJhi/WbsTWzcKN6T4gU0aehsprtke1UAaaSxxkjjmkJb3llet/rbUSLPgwlY4w==} + peerDependencies: + react: '>=16.0.0' + react-dom: '>=16.0.0' + + '@ant-design/fast-color@2.0.6': + resolution: {integrity: sha512-y2217gk4NqL35giHl72o6Zzqji9O7vHh9YmhUVkPtAOpoTCH4uWxo/pr4VE8t0+ChEPs0qo4eJRC5Q1eXWo3vA==} + engines: {node: '>=8.x'} + + '@ant-design/icons-svg@4.4.2': + resolution: {integrity: sha512-vHbT+zJEVzllwP+CM+ul7reTEfBR0vgxFe7+lREAsAA7YGsYpboiq2sQNeQeRvh09GfQgs/GyFEvZpJ9cLXpXA==} + + '@ant-design/icons@5.6.1': + resolution: {integrity: sha512-0/xS39c91WjPAZOWsvi1//zjx6kAp4kxWwctR6kuU6p133w8RU0D2dSCvZC19uQyharg/sAvYxGYWl01BbZZfg==} + engines: {node: '>=8'} + peerDependencies: + react: '>=16.0.0' + react-dom: '>=16.0.0' + + '@ant-design/pro-card@2.9.7': + resolution: {integrity: sha512-uDDYowmYH1ldRfG8Mb4QOwcEEz6ptRBQDLO1tkVADCRkdOMwz82xlZneR4uVuFyKcuNmgHzarYNncozBKhFuaA==} + peerDependencies: + antd: ^4.24.15 || ^5.11.2 + react: '>=17.0.0' + + '@ant-design/pro-components@2.8.7': + resolution: {integrity: sha512-QhibkPsUJryEjI1QmwUn+XCngGHidu0ekvricL6TIEvPgP+AUAca29XutN5+Mmn8Xfja1ca9HFTHTgFoV74Z7Q==} + peerDependencies: + antd: ^4.24.15 || ^5.11.2 + react: '>=17.0.0' + react-dom: '>=17.0.0' + + '@ant-design/pro-descriptions@2.6.7': + resolution: {integrity: sha512-fgn2d0kDWUODGDWKpgziZuuqPlmIoKxQFJY9Yg4nbaRp8GDDKZeSSqgvW+OxjpYM8dxq31fiz1dZlZnOPoYKpg==} + peerDependencies: + antd: ^4.24.15 || ^5.11.2 + react: '>=17.0.0' + + '@ant-design/pro-field@3.0.4': + resolution: {integrity: sha512-nJSng/6/pPZFdiFeTtZcBQLNrHg9tIeiKFR1+zzbnQbI3qBOFP9aBZS/+LwkQZcI2G71vrRgz2x5OhHb7AX0wQ==} + peerDependencies: + antd: ^4.24.15 || ^5.11.2 + react: '>=17.0.0' + + '@ant-design/pro-form@2.31.7': + resolution: {integrity: sha512-0TCtIC/ynbLPoes8sLBFwFbi0tkeNmSU6the2EcyKIKDLfWHDbfkLM1OSFrzv3QD+H8OgFWMkTSOjhMOKSsOBg==} + peerDependencies: + antd: ^4.24.15 || ^5.11.2 + rc-field-form: '>=1.22.0' + react: '>=17.0.0' + react-dom: '>=17.0.0' + + '@ant-design/pro-layout@7.22.4': + resolution: {integrity: sha512-X2WO4L2itXemX4zhS+0NG+8kXQD5SX9sG+zjx/15BmIO3FvsUGqOHgoCg0vhd424EiyPj7WtdMZJ39G1xdgDwA==} + peerDependencies: + antd: ^4.24.15 || ^5.11.2 + react: '>=17.0.0' + react-dom: '>=17.0.0' + + '@ant-design/pro-list@2.6.7': + resolution: {integrity: sha512-6k/En7pioMgepho/1HMf2DAnkSTZiat1lDg2ggCok2lhSgqXzir7x22ewJQRgPvEiVb6/qqaFQNd7a8dnrFj1w==} + peerDependencies: + antd: ^4.24.15 || ^5.11.2 + react: '>=17.0.0' + react-dom: '>=17.0.0' + + '@ant-design/pro-provider@2.15.4': + resolution: {integrity: sha512-DBX0JNUNOYXAucVqd/zTdqtXckCDqr2Lo85KIku2YzWdhptDPDZRTNqL04JShjGejDl8fzwQ8yREHgVUfzn6Gg==} + peerDependencies: + antd: ^4.24.15 || ^5.11.2 + react: '>=17.0.0' + react-dom: '>=17.0.0' + + '@ant-design/pro-skeleton@2.2.1': + resolution: {integrity: sha512-3M2jNOZQZWEDR8pheY00OkHREfb0rquvFZLCa6DypGmiksiuuYuR9Y4iA82ZF+mva2FmpHekdwbje/GpbxqBeg==} + peerDependencies: + antd: ^4.24.15 || ^5.11.2 + react: '>=17.0.0' + react-dom: '>=17.0.0' + + '@ant-design/pro-table@3.19.0': + resolution: {integrity: sha512-nL25734d5q5oqtmG7Apn2TNJUnJE8m9dkopXMQdoNZnv8qeRQLBH+i5cZT1yh7FIO8z6QLXleg+KnR/cI7VRRw==} + peerDependencies: + antd: ^4.24.15 || ^5.11.2 + rc-field-form: '>=1.22.0' + react: '>=17.0.0' + react-dom: '>=17.0.0' + + '@ant-design/pro-utils@2.17.0': + resolution: {integrity: sha512-hHKUISjMEoS+E5ltJWyvNTrlEA3IimZNxtDrEhorRIbgVYAlmEN5Mj/ESSofzDM3+UlxiI5+A/Y6IHkByTfDEA==} + peerDependencies: + antd: ^4.24.15 || ^5.11.2 + react: '>=17.0.0' + react-dom: '>=17.0.0' + + '@ant-design/react-slick@1.1.2': + resolution: {integrity: sha512-EzlvzE6xQUBrZuuhSAFTdsr4P2bBBHGZwKFemEfq8gIGyIQCxalYfZW/T2ORbtQx5rU69o+WycP3exY/7T1hGA==} + peerDependencies: + react: '>=16.9.0' + + '@ant-design/v5-patch-for-react-19@1.0.3': + resolution: {integrity: sha512-iWfZuSUl5kuhqLUw7jJXUQFMMkM7XpW7apmKzQBQHU0cpifYW4A79xIBt9YVO5IBajKpPG5UKP87Ft7Yrw1p/w==} + engines: {node: '>=12.x'} + peerDependencies: + antd: '>=5.22.6' + react: '>=19.0.0' + react-dom: '>=19.0.0' + + '@antfu/install-pkg@1.0.0': + resolution: {integrity: sha512-xvX6P/lo1B3ej0OsaErAjqgFYzYVcJpamjLAFLYh9vRJngBrMoUG7aVnrGTeqM7yxbyTD5p3F2+0/QUEh8Vzhw==} + + '@antfu/utils@8.1.1': + resolution: {integrity: sha512-Mex9nXf9vR6AhcXmMrlz/HVgYYZpVGJ6YlPgwl7UnaFpnshXs6EK/oa5Gpf3CzENMjkvEx2tQtntGnb7UtSTOQ==} + + '@babel/code-frame@7.26.2': + resolution: {integrity: sha512-RJlIHRueQgwWitWgF8OdFYGZX328Ax5BCemNGlqHfplnRT9ESi8JkFlvaVYbS+UubVY6dpv87Fs2u5M29iNFVQ==} + engines: {node: '>=6.9.0'} + + '@babel/compat-data@7.26.8': + resolution: {integrity: sha512-oH5UPLMWR3L2wEFLnFJ1TZXqHufiTKAiLfqw5zkhS4dKXLJ10yVztfil/twG8EDTA4F/tvVNw9nOl4ZMslB8rQ==} + engines: {node: '>=6.9.0'} + + '@babel/core@7.26.10': + resolution: {integrity: sha512-vMqyb7XCDMPvJFFOaT9kxtiRh42GwlZEg1/uIgtZshS5a/8OaduUfCi7kynKgc3Tw/6Uo2D+db9qBttghhmxwQ==} + engines: {node: '>=6.9.0'} + + '@babel/generator@7.27.0': + resolution: {integrity: sha512-VybsKvpiN1gU1sdMZIp7FcqphVVKEwcuj02x73uvcHE0PTihx1nlBcowYWhDwjpoAXRv43+gDzyggGnn1XZhVw==} + engines: {node: '>=6.9.0'} + + '@babel/helper-compilation-targets@7.27.0': + resolution: {integrity: sha512-LVk7fbXml0H2xH34dFzKQ7TDZ2G4/rVTOrq9V+icbbadjbVxxeFeDsNHv2SrZeWoA+6ZiTyWYWtScEIW07EAcA==} + engines: {node: '>=6.9.0'} + + '@babel/helper-module-imports@7.25.9': + resolution: {integrity: sha512-tnUA4RsrmflIM6W6RFTLFSXITtl0wKjgpnLgXyowocVPrbYrLUXSBXDgTs8BlbmIzIdlBySRQjINYs2BAkiLtw==} + engines: {node: '>=6.9.0'} + + '@babel/helper-module-transforms@7.26.0': + resolution: {integrity: sha512-xO+xu6B5K2czEnQye6BHA7DolFFmS3LB7stHZFaOLb1pAwO1HWLS8fXA+eh0A2yIvltPVmx3eNNDBJA2SLHXFw==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0 + + '@babel/helper-plugin-utils@7.26.5': + resolution: {integrity: sha512-RS+jZcRdZdRFzMyr+wcsaqOmld1/EqTghfaBGQQd/WnRdzdlvSZ//kF7U8VQTxf1ynZ4cjUcYgjVGx13ewNPMg==} + engines: {node: '>=6.9.0'} + + '@babel/helper-string-parser@7.25.9': + resolution: {integrity: sha512-4A/SCr/2KLd5jrtOMFzaKjVtAei3+2r/NChoBNoZ3EyP/+GlhoaEGoWOZUmFmoITP7zOJyHIMm+DYRd8o3PvHA==} + engines: {node: '>=6.9.0'} + + '@babel/helper-validator-identifier@7.25.9': + resolution: {integrity: sha512-Ed61U6XJc3CVRfkERJWDz4dJwKe7iLmmJsbOGu9wSloNSFttHV0I8g6UAgb7qnK5ly5bGLPd4oXZlxCdANBOWQ==} + engines: {node: '>=6.9.0'} + + '@babel/helper-validator-option@7.25.9': + resolution: {integrity: sha512-e/zv1co8pp55dNdEcCynfj9X7nyUKUXoUEwfXqaZt0omVOmDe9oOTdKStH4GmAw6zxMFs50ZayuMfHDKlO7Tfw==} + engines: {node: '>=6.9.0'} + + '@babel/helpers@7.27.0': + resolution: {integrity: sha512-U5eyP/CTFPuNE3qk+WZMxFkp/4zUzdceQlfzf7DdGdhp+Fezd7HD+i8Y24ZuTMKX3wQBld449jijbGq6OdGNQg==} + engines: {node: '>=6.9.0'} + + '@babel/parser@7.27.0': + resolution: {integrity: sha512-iaepho73/2Pz7w2eMS0Q5f83+0RKI7i4xmiYeBmDzfRVbQtTOG7Ts0S4HzJVsTMGI9keU8rNfuZr8DKfSt7Yyg==} + engines: {node: '>=6.0.0'} + hasBin: true + + '@babel/plugin-syntax-jsx@7.25.9': + resolution: {integrity: sha512-ld6oezHQMZsZfp6pWtbjaNDF2tiiCYYDqQszHt5VV437lewP9aSi2Of99CK0D0XB21k7FLgnLcmQKyKzynfeAA==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-syntax-typescript@7.25.9': + resolution: {integrity: sha512-hjMgRy5hb8uJJjUcdWunWVcoi9bGpJp8p5Ol1229PoN6aytsLwNMgmdftO23wnCLMfVmTwZDWMPNq/D1SY60JQ==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/runtime@7.27.0': + resolution: {integrity: sha512-VtPOkrdPHZsKc/clNqyi9WUA8TINkZ4cGk63UUE3u4pmB2k+ZMQRDuIOagv8UVd6j7k0T3+RRIb7beKTebNbcw==} + engines: {node: '>=6.9.0'} + + '@babel/template@7.27.0': + resolution: {integrity: sha512-2ncevenBqXI6qRMukPlXwHKHchC7RyMuu4xv5JBXRfOGVcTy1mXCD12qrp7Jsoxll1EV3+9sE4GugBVRjT2jFA==} + engines: {node: '>=6.9.0'} + + '@babel/traverse@7.27.0': + resolution: {integrity: sha512-19lYZFzYVQkkHkl4Cy4WrAVcqBkgvV2YM2TU3xG6DIwO7O3ecbDPfW3yM3bjAGcqcQHi+CCtjMR3dIEHxsd6bA==} + engines: {node: '>=6.9.0'} + + '@babel/types@7.27.0': + resolution: {integrity: sha512-H45s8fVLYjbhFH62dIJ3WtmJ6RSPt/3DRO0ZcT2SUiYiQyz3BLVb9ADEnLl91m74aQPS3AzzeajZHYOalWe3bg==} + engines: {node: '>=6.9.0'} + + '@chenshuai2144/sketch-color@1.0.9': + resolution: {integrity: sha512-obzSy26cb7Pm7OprWyVpgMpIlrZpZ0B7vbrU0RMbvRg0YAI890S5Xy02Aj1Nhl4+KTbi1lVYHt6HQP8Hm9s+1w==} + peerDependencies: + react: '>=16.12.0' + + '@ctrl/tinycolor@3.6.1': + resolution: {integrity: sha512-SITSV6aIXsuVNV3f3O0f2n/cgyEDWoSqtZMYiAmcsYHydcKrOz3gUxB/iXd/Qf08+IZX4KpgNbvUdMBmWz+kcA==} + engines: {node: '>=10'} + + '@dnd-kit/accessibility@3.1.1': + resolution: {integrity: sha512-2P+YgaXF+gRsIihwwY1gCsQSYnu9Zyj2py8kY5fFvUM1qm2WA2u639R6YNVfU4GWr+ZM5mqEsfHZZLoRONbemw==} + peerDependencies: + react: '>=16.8.0' + + '@dnd-kit/core@6.3.1': + resolution: {integrity: sha512-xkGBRQQab4RLwgXxoqETICr6S5JlogafbhNsidmrkVv2YRs5MLwpjoF2qpiGjQt8S9AoxtIV603s0GIUpY5eYQ==} + peerDependencies: + react: '>=16.8.0' + react-dom: '>=16.8.0' + + '@dnd-kit/modifiers@6.0.1': + resolution: {integrity: sha512-rbxcsg3HhzlcMHVHWDuh9LCjpOVAgqbV78wLGI8tziXY3+qcMQ61qVXIvNKQFuhj75dSfD+o+PYZQ/NUk2A23A==} + peerDependencies: + '@dnd-kit/core': ^6.0.6 + react: '>=16.8.0' + + '@dnd-kit/sortable@7.0.2': + resolution: {integrity: sha512-wDkBHHf9iCi1veM834Gbk1429bd4lHX4RpAwT0y2cHLf246GAvU2sVw/oxWNpPKQNQRQaeGXhAVgrOl1IT+iyA==} + peerDependencies: + '@dnd-kit/core': ^6.0.7 + react: '>=16.8.0' + + '@dnd-kit/utilities@3.2.2': + resolution: {integrity: sha512-+MKAJEOfaBe5SmV6t34p80MMKhjvUz0vRrvVJbPT0WElzaOJ/1xs+D+KDv+tD/NE5ujfrChEcshd4fLn0wpiqg==} + peerDependencies: + react: '>=16.8.0' + + '@emotion/babel-plugin@11.13.5': + resolution: {integrity: sha512-pxHCpT2ex+0q+HH91/zsdHkw/lXd468DIN2zvfvLtPKLLMo6gQj7oLObq8PhkrxOZb/gGCq03S3Z7PDhS8pduQ==} + + '@emotion/cache@11.14.0': + resolution: {integrity: sha512-L/B1lc/TViYk4DcpGxtAVbx0ZyiKM5ktoIyafGkH6zg/tj+mA+NE//aPYKG0k8kCHSHVJrpLpcAlOBEXQ3SavA==} + + '@emotion/hash@0.8.0': + resolution: {integrity: sha512-kBJtf7PH6aWwZ6fka3zQ0p6SBYzx4fl1LoZXE2RrnYST9Xljm7WfKJrU4g/Xr3Beg72MLrp1AWNUmuYJTL7Cow==} + + '@emotion/hash@0.9.2': + resolution: {integrity: sha512-MyqliTZGuOm3+5ZRSaaBGP3USLw6+EGykkwZns2EPC5g8jJ4z9OrdZY9apkl3+UP9+sdz76YYkwCKP5gh8iY3g==} + + '@emotion/is-prop-valid@1.3.1': + resolution: {integrity: sha512-/ACwoqx7XQi9knQs/G0qKvv5teDMhD7bXYns9N/wM8ah8iNb8jZ2uNO0YOgiq2o2poIvVtJS2YALasQuMSQ7Kw==} + + '@emotion/memoize@0.9.0': + resolution: {integrity: sha512-30FAj7/EoJ5mwVPOWhAyCX+FPfMDrVecJAM+Iw9NRoSl4BBAQeqj4cApHHUXOVvIPgLVDsCFoz/hGD+5QQD1GQ==} + + '@emotion/react@11.14.0': + resolution: {integrity: sha512-O000MLDBDdk/EohJPFUqvnp4qnHeYkVP5B0xEG0D/L7cOKP9kefu2DXn8dj74cQfsEzUqh+sr1RzFqiL1o+PpA==} + peerDependencies: + '@types/react': '*' + react: '>=16.8.0' + peerDependenciesMeta: + '@types/react': + optional: true + + '@emotion/serialize@1.3.3': + resolution: {integrity: sha512-EISGqt7sSNWHGI76hC7x1CksiXPahbxEOrC5RjmFRJTqLyEK9/9hZvBbiYn70dw4wuwMKiEMCUlR6ZXTSWQqxA==} + + '@emotion/sheet@1.4.0': + resolution: {integrity: sha512-fTBW9/8r2w3dXWYM4HCB1Rdp8NLibOw2+XELH5m5+AkWiL/KqYX6dc0kKYlaYyKjrQ6ds33MCdMPEwgs2z1rqg==} + + '@emotion/styled@11.14.0': + resolution: {integrity: sha512-XxfOnXFffatap2IyCeJyNov3kiDQWoR08gPUQxvbL7fxKryGBKUZUkG6Hz48DZwVrJSVh9sJboyV1Ds4OW6SgA==} + peerDependencies: + '@emotion/react': ^11.0.0-rc.0 + '@types/react': '*' + react: '>=16.8.0' + peerDependenciesMeta: + '@types/react': + optional: true + + '@emotion/unitless@0.10.0': + resolution: {integrity: sha512-dFoMUuQA20zvtVTuxZww6OHoJYgrzfKM1t52mVySDJnMSEa08ruEvdYQbhvyu6soU+NeLVd3yKfTfT0NeV6qGg==} + + '@emotion/unitless@0.7.5': + resolution: {integrity: sha512-OWORNpfjMsSSUBVrRBVGECkhWcULOAJz9ZW8uK9qgxD+87M7jHRcvh/A96XXNhXTLmKcoYSQtBEX7lHMO7YRwg==} + + '@emotion/use-insertion-effect-with-fallbacks@1.2.0': + resolution: {integrity: sha512-yJMtVdH59sxi/aVJBpk9FQq+OR8ll5GT8oWd57UpeaKEVGab41JWaCFA7FRLoMLloOZF/c/wsPoe+bfGmRKgDg==} + peerDependencies: + react: '>=16.8.0' + + '@emotion/utils@1.4.2': + resolution: {integrity: sha512-3vLclRofFziIa3J2wDh9jjbkUz9qk5Vi3IZ/FSTKViB0k+ef0fPV7dYrUIugbgupYDx7v9ud/SjrtEP8Y4xLoA==} + + '@emotion/weak-memoize@0.4.0': + resolution: {integrity: sha512-snKqtPW01tN0ui7yu9rGv69aJXr/a/Ywvl11sUjNtEcRc+ng/mQriFL0wLXMef74iHa/EkftbDzU9F8iFbH+zg==} + + '@esbuild/aix-ppc64@0.25.2': + resolution: {integrity: sha512-wCIboOL2yXZym2cgm6mlA742s9QeJ8DjGVaL39dLN4rRwrOgOyYSnOaFPhKZGLb2ngj4EyfAFjsNJwPXZvseag==} + engines: {node: '>=18'} + cpu: [ppc64] + os: [aix] + + '@esbuild/android-arm64@0.25.2': + resolution: {integrity: sha512-5ZAX5xOmTligeBaeNEPnPaeEuah53Id2tX4c2CVP3JaROTH+j4fnfHCkr1PjXMd78hMst+TlkfKcW/DlTq0i4w==} + engines: {node: '>=18'} + cpu: [arm64] + os: [android] + + '@esbuild/android-arm@0.25.2': + resolution: {integrity: sha512-NQhH7jFstVY5x8CKbcfa166GoV0EFkaPkCKBQkdPJFvo5u+nGXLEH/ooniLb3QI8Fk58YAx7nsPLozUWfCBOJA==} + engines: {node: '>=18'} + cpu: [arm] + os: [android] + + '@esbuild/android-x64@0.25.2': + resolution: {integrity: sha512-Ffcx+nnma8Sge4jzddPHCZVRvIfQ0kMsUsCMcJRHkGJ1cDmhe4SsrYIjLUKn1xpHZybmOqCWwB0zQvsjdEHtkg==} + engines: {node: '>=18'} + cpu: [x64] + os: [android] + + '@esbuild/darwin-arm64@0.25.2': + resolution: {integrity: sha512-MpM6LUVTXAzOvN4KbjzU/q5smzryuoNjlriAIx+06RpecwCkL9JpenNzpKd2YMzLJFOdPqBpuub6eVRP5IgiSA==} + engines: {node: '>=18'} + cpu: [arm64] + os: [darwin] + + '@esbuild/darwin-x64@0.25.2': + resolution: {integrity: sha512-5eRPrTX7wFyuWe8FqEFPG2cU0+butQQVNcT4sVipqjLYQjjh8a8+vUTfgBKM88ObB85ahsnTwF7PSIt6PG+QkA==} + engines: {node: '>=18'} + cpu: [x64] + os: [darwin] + + '@esbuild/freebsd-arm64@0.25.2': + resolution: {integrity: sha512-mLwm4vXKiQ2UTSX4+ImyiPdiHjiZhIaE9QvC7sw0tZ6HoNMjYAqQpGyui5VRIi5sGd+uWq940gdCbY3VLvsO1w==} + engines: {node: '>=18'} + cpu: [arm64] + os: [freebsd] + + '@esbuild/freebsd-x64@0.25.2': + resolution: {integrity: sha512-6qyyn6TjayJSwGpm8J9QYYGQcRgc90nmfdUb0O7pp1s4lTY+9D0H9O02v5JqGApUyiHOtkz6+1hZNvNtEhbwRQ==} + engines: {node: '>=18'} + cpu: [x64] + os: [freebsd] + + '@esbuild/linux-arm64@0.25.2': + resolution: {integrity: sha512-gq/sjLsOyMT19I8obBISvhoYiZIAaGF8JpeXu1u8yPv8BE5HlWYobmlsfijFIZ9hIVGYkbdFhEqC0NvM4kNO0g==} + engines: {node: '>=18'} + cpu: [arm64] + os: [linux] + + '@esbuild/linux-arm@0.25.2': + resolution: {integrity: sha512-UHBRgJcmjJv5oeQF8EpTRZs/1knq6loLxTsjc3nxO9eXAPDLcWW55flrMVc97qFPbmZP31ta1AZVUKQzKTzb0g==} + engines: {node: '>=18'} + cpu: [arm] + os: [linux] + + '@esbuild/linux-ia32@0.25.2': + resolution: {integrity: sha512-bBYCv9obgW2cBP+2ZWfjYTU+f5cxRoGGQ5SeDbYdFCAZpYWrfjjfYwvUpP8MlKbP0nwZ5gyOU/0aUzZ5HWPuvQ==} + engines: {node: '>=18'} + cpu: [ia32] + os: [linux] + + '@esbuild/linux-loong64@0.25.2': + resolution: {integrity: sha512-SHNGiKtvnU2dBlM5D8CXRFdd+6etgZ9dXfaPCeJtz+37PIUlixvlIhI23L5khKXs3DIzAn9V8v+qb1TRKrgT5w==} + engines: {node: '>=18'} + cpu: [loong64] + os: [linux] + + '@esbuild/linux-mips64el@0.25.2': + resolution: {integrity: sha512-hDDRlzE6rPeoj+5fsADqdUZl1OzqDYow4TB4Y/3PlKBD0ph1e6uPHzIQcv2Z65u2K0kpeByIyAjCmjn1hJgG0Q==} + engines: {node: '>=18'} + cpu: [mips64el] + os: [linux] + + '@esbuild/linux-ppc64@0.25.2': + resolution: {integrity: sha512-tsHu2RRSWzipmUi9UBDEzc0nLc4HtpZEI5Ba+Omms5456x5WaNuiG3u7xh5AO6sipnJ9r4cRWQB2tUjPyIkc6g==} + engines: {node: '>=18'} + cpu: [ppc64] + os: [linux] + + '@esbuild/linux-riscv64@0.25.2': + resolution: {integrity: sha512-k4LtpgV7NJQOml/10uPU0s4SAXGnowi5qBSjaLWMojNCUICNu7TshqHLAEbkBdAszL5TabfvQ48kK84hyFzjnw==} + engines: {node: '>=18'} + cpu: [riscv64] + os: [linux] + + '@esbuild/linux-s390x@0.25.2': + resolution: {integrity: sha512-GRa4IshOdvKY7M/rDpRR3gkiTNp34M0eLTaC1a08gNrh4u488aPhuZOCpkF6+2wl3zAN7L7XIpOFBhnaE3/Q8Q==} + engines: {node: '>=18'} + cpu: [s390x] + os: [linux] + + '@esbuild/linux-x64@0.25.2': + resolution: {integrity: sha512-QInHERlqpTTZ4FRB0fROQWXcYRD64lAoiegezDunLpalZMjcUcld3YzZmVJ2H/Cp0wJRZ8Xtjtj0cEHhYc/uUg==} + engines: {node: '>=18'} + cpu: [x64] + os: [linux] + + '@esbuild/netbsd-arm64@0.25.2': + resolution: {integrity: sha512-talAIBoY5M8vHc6EeI2WW9d/CkiO9MQJ0IOWX8hrLhxGbro/vBXJvaQXefW2cP0z0nQVTdQ/eNyGFV1GSKrxfw==} + engines: {node: '>=18'} + cpu: [arm64] + os: [netbsd] + + '@esbuild/netbsd-x64@0.25.2': + resolution: {integrity: sha512-voZT9Z+tpOxrvfKFyfDYPc4DO4rk06qamv1a/fkuzHpiVBMOhpjK+vBmWM8J1eiB3OLSMFYNaOaBNLXGChf5tg==} + engines: {node: '>=18'} + cpu: [x64] + os: [netbsd] + + '@esbuild/openbsd-arm64@0.25.2': + resolution: {integrity: sha512-dcXYOC6NXOqcykeDlwId9kB6OkPUxOEqU+rkrYVqJbK2hagWOMrsTGsMr8+rW02M+d5Op5NNlgMmjzecaRf7Tg==} + engines: {node: '>=18'} + cpu: [arm64] + os: [openbsd] + + '@esbuild/openbsd-x64@0.25.2': + resolution: {integrity: sha512-t/TkWwahkH0Tsgoq1Ju7QfgGhArkGLkF1uYz8nQS/PPFlXbP5YgRpqQR3ARRiC2iXoLTWFxc6DJMSK10dVXluw==} + engines: {node: '>=18'} + cpu: [x64] + os: [openbsd] + + '@esbuild/sunos-x64@0.25.2': + resolution: {integrity: sha512-cfZH1co2+imVdWCjd+D1gf9NjkchVhhdpgb1q5y6Hcv9TP6Zi9ZG/beI3ig8TvwT9lH9dlxLq5MQBBgwuj4xvA==} + engines: {node: '>=18'} + cpu: [x64] + os: [sunos] + + '@esbuild/win32-arm64@0.25.2': + resolution: {integrity: sha512-7Loyjh+D/Nx/sOTzV8vfbB3GJuHdOQyrOryFdZvPHLf42Tk9ivBU5Aedi7iyX+x6rbn2Mh68T4qq1SDqJBQO5Q==} + engines: {node: '>=18'} + cpu: [arm64] + os: [win32] + + '@esbuild/win32-ia32@0.25.2': + resolution: {integrity: sha512-WRJgsz9un0nqZJ4MfhabxaD9Ft8KioqU3JMinOTvobbX6MOSUigSBlogP8QB3uxpJDsFS6yN+3FDBdqE5lg9kg==} + engines: {node: '>=18'} + cpu: [ia32] + os: [win32] + + '@esbuild/win32-x64@0.25.2': + resolution: {integrity: sha512-kM3HKb16VIXZyIeVrM1ygYmZBKybX8N4p754bw390wGO3Tf2j4L2/WYL+4suWujpgf6GBYs3jv7TyUivdd05JA==} + engines: {node: '>=18'} + cpu: [x64] + os: [win32] + + '@eslint-community/eslint-utils@4.6.1': + resolution: {integrity: sha512-KTsJMmobmbrFLe3LDh0PC2FXpcSYJt/MLjlkh/9LEnmKYLSYmT/0EW9JWANjeoemiuZrmogti0tW5Ch+qNUYDw==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + peerDependencies: + eslint: ^6.0.0 || ^7.0.0 || >=8.0.0 + + '@eslint-community/regexpp@4.12.1': + resolution: {integrity: sha512-CCZCDJuduB9OUkFkY2IgppNZMi2lBQgD2qzwXkEia16cge2pijY/aXi96CJMquDMn3nJdlPV1A5KrJEXwfLNzQ==} + engines: {node: ^12.0.0 || ^14.0.0 || >=16.0.0} + + '@eslint/config-array@0.20.0': + resolution: {integrity: sha512-fxlS1kkIjx8+vy2SjuCB94q3htSNrufYTXubwiBFeaQHbH6Ipi43gFJq2zCMt6PHhImH3Xmr0NksKDvchWlpQQ==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + '@eslint/config-helpers@0.2.1': + resolution: {integrity: sha512-RI17tsD2frtDu/3dmI7QRrD4bedNKPM08ziRYaC5AhkGrzIAJelm9kJU1TznK+apx6V+cqRz8tfpEeG3oIyjxw==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + '@eslint/core@0.13.0': + resolution: {integrity: sha512-yfkgDw1KR66rkT5A8ci4irzDysN7FRpq3ttJolR88OqQikAWqwA8j5VZyas+vjyBNFIJ7MfybJ9plMILI2UrCw==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + '@eslint/eslintrc@3.3.1': + resolution: {integrity: sha512-gtF186CXhIl1p4pJNGZw8Yc6RlshoePRvE0X91oPGb3vZ8pM3qOS9W9NGPat9LziaBV7XrJWGylNQXkGcnM3IQ==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + '@eslint/js@9.25.1': + resolution: {integrity: sha512-dEIwmjntEx8u3Uvv+kr3PDeeArL8Hw07H9kyYxCjnM9pBjfEhk6uLXSchxxzgiwtRhhzVzqmUSDFBOi1TuZ7qg==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + '@eslint/object-schema@2.1.6': + resolution: {integrity: sha512-RBMg5FRL0I0gs51M/guSAj5/e14VQ4tpZnQNWwuDT66P14I43ItmPfIZRhO9fUVIPOAQXU47atlywZ/czoqFPA==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + '@eslint/plugin-kit@0.2.8': + resolution: {integrity: sha512-ZAoA40rNMPwSm+AeHpCq8STiNAwzWLJuP8Xv4CHIc9wv/PSuExjMrmjfYNj682vW0OOiZ1HKxzvjQr9XZIisQA==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + '@floating-ui/core@1.6.9': + resolution: {integrity: sha512-uMXCuQ3BItDUbAMhIXw7UPXRfAlOAvZzdK9BWpE60MCn+Svt3aLn9jsPTi/WNGlRUu2uI0v5S7JiIUsbsvh3fw==} + + '@floating-ui/dom@1.6.13': + resolution: {integrity: sha512-umqzocjDgNRGTuO7Q8CU32dkHkECqI8ZdMZ5Swb6QAM0t5rnlrN3lGo1hdpscRd3WS8T6DKYK4ephgIH9iRh3w==} + + '@floating-ui/react-dom@2.1.2': + resolution: {integrity: sha512-06okr5cgPzMNBy+Ycse2A6udMi4bqwW/zgBF/rwjcNqWkyr82Mcg8b0vjX8OJpZFy/FKjJmw6wV7t44kK6kW7A==} + peerDependencies: + react: '>=16.8.0' + react-dom: '>=16.8.0' + + '@floating-ui/react@0.26.28': + resolution: {integrity: sha512-yORQuuAtVpiRjpMhdc0wJj06b9JFjrYF4qp96j++v2NBpbi6SEGF7donUJ3TMieerQ6qVkAv1tgr7L4r5roTqw==} + peerDependencies: + react: '>=16.8.0' + react-dom: '>=16.8.0' + + '@floating-ui/utils@0.2.9': + resolution: {integrity: sha512-MDWhGtE+eHw5JW7lq4qhc5yRLS11ERl1c7Z6Xd0a58DozHES6EnNNwUWbMiG4J9Cgj053Bhk8zvlhFYKVhULwg==} + + '@hookform/devtools@4.4.0': + resolution: {integrity: sha512-Mtlic+uigoYBPXlfvPBfiYYUZuyMrD3pTjDpVIhL6eCZTvQkHsKBSKeZCvXWUZr8fqrkzDg27N+ZuazLKq6Vmg==} + peerDependencies: + react: ^16.8.0 || ^17 || ^18 || ^19 + react-dom: ^16.8.0 || ^17 || ^18 || ^19 + + '@hookform/resolvers@5.0.1': + resolution: {integrity: sha512-u/+Jp83luQNx9AdyW2fIPGY6Y7NG68eN2ZW8FOJYL+M0i4s49+refdJdOp/A9n9HFQtQs3HIDHQvX3ZET2o7YA==} + peerDependencies: + react-hook-form: ^7.55.0 + + '@humanfs/core@0.19.1': + resolution: {integrity: sha512-5DyQ4+1JEUzejeK1JGICcideyfUbGixgS9jNgex5nqkW+cY7WZhxBigmieN5Qnw9ZosSNVC9KQKyb+GUaGyKUA==} + engines: {node: '>=18.18.0'} + + '@humanfs/node@0.16.6': + resolution: {integrity: sha512-YuI2ZHQL78Q5HbhDiBA1X4LmYdXCKCMQIfw0pw7piHJwyREFebJUvrQN4cMssyES6x+vfUbx1CIpaQUKYdQZOw==} + engines: {node: '>=18.18.0'} + + '@humanwhocodes/module-importer@1.0.1': + resolution: {integrity: sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==} + engines: {node: '>=12.22'} + + '@humanwhocodes/retry@0.3.1': + resolution: {integrity: sha512-JBxkERygn7Bv/GbN5Rv8Ul6LVknS+5Bp6RgDC/O8gEBU/yeH5Ui5C/OlWrTb6qct7LjjfT6Re2NxB0ln0yYybA==} + engines: {node: '>=18.18'} + + '@humanwhocodes/retry@0.4.2': + resolution: {integrity: sha512-xeO57FpIu4p1Ri3Jq/EXq4ClRm86dVF2z/+kvFnyqVYRavTZmaFaUBbWCOuuTh0o/g7DSsk6kc2vrS4Vl5oPOQ==} + engines: {node: '>=18.18'} + + '@iconify-json/material-symbols@1.2.20': + resolution: {integrity: sha512-+KqOT+3fD+LC2FbWiV8gd4+JLMiVUtmqrjzpKN1ji7rfMQTwvYJ94RT0WQlmL+vfDNJ5MTRe3rBzzJyvIH/aSg==} + + '@iconify/types@2.0.0': + resolution: {integrity: sha512-+wluvCrRhXrhyOmRDJ3q8mux9JkKy5SJ/v8ol2tu4FVjyYvtEzkc/3pK15ET6RKg4b4w4BmTk1+gsCUhf21Ykg==} + + '@iconify/utils@2.3.0': + resolution: {integrity: sha512-GmQ78prtwYW6EtzXRU1rY+KwOKfz32PD7iJh6Iyqw68GiKuoZ2A6pRtzWONz5VQJbp50mEjXh/7NkumtrAgRKA==} + + '@jridgewell/gen-mapping@0.3.8': + resolution: {integrity: sha512-imAbBGkb+ebQyxKgzv5Hu2nmROxoDOXHh80evxdoXNOrvAnVx7zimzc1Oo5h9RlfV4vPXaE2iM5pOFbvOCClWA==} + engines: {node: '>=6.0.0'} + + '@jridgewell/resolve-uri@3.1.2': + resolution: {integrity: sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==} + engines: {node: '>=6.0.0'} + + '@jridgewell/set-array@1.2.1': + resolution: {integrity: sha512-R8gLRTZeyp03ymzP/6Lil/28tGeGEzhx1q2k703KGWRAI1VdvPIXdG70VJc2pAMw3NA6JKL5hhFu1sJX0Mnn/A==} + engines: {node: '>=6.0.0'} + + '@jridgewell/sourcemap-codec@1.5.0': + resolution: {integrity: sha512-gv3ZRaISU3fjPAgNsriBRqGWQL6quFx04YMPW/zD8XMLsU32mhCCbfbO6KZFLjvYpCZ8zyDEgqsgf+PwPaM7GQ==} + + '@jridgewell/trace-mapping@0.3.25': + resolution: {integrity: sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ==} + + '@mantine/core@7.17.4': + resolution: {integrity: sha512-Ea4M/98jxgIWCuxCdM0YIotVYjfLTGQsfIA6zDg0LsClgjo/ZLnnh4zbi+bLNgM+GGjP4ju7gv4MZvaTKuLO8g==} + peerDependencies: + '@mantine/hooks': 7.17.4 + react: ^18.x || ^19.x + react-dom: ^18.x || ^19.x + + '@mantine/hooks@7.17.4': + resolution: {integrity: sha512-PBcJxDAfGm8k1/JJmaDcxzRVQ3JSE1iXGktbgGz+qEOJmCxwbbAYe+CtGFFgi1xX2bPZ+7dtRr/+XFhnKtt/aw==} + peerDependencies: + react: ^18.x || ^19.x + + '@mantine/modals@7.17.4': + resolution: {integrity: sha512-KQYzLCQRBs9bq0svdpSda8fgxmqrwEy4tgvoXpmlr02srsyySvpOxhXmAUZsjPZapG+D97sYi7BscVZKZoIqgA==} + peerDependencies: + '@mantine/core': 7.17.4 + '@mantine/hooks': 7.17.4 + react: ^18.x || ^19.x + react-dom: ^18.x || ^19.x + + '@mantine/notifications@7.17.4': + resolution: {integrity: sha512-YxNmnZSfIG69lPMFItOZZsizYL3DsOLVUSPkkJILG5pW2F798dc4IA5mhRIbdmzDEx0ArWHJ7gsdd3Vmm5ubPg==} + peerDependencies: + '@mantine/core': 7.17.4 + '@mantine/hooks': 7.17.4 + react: ^18.x || ^19.x + react-dom: ^18.x || ^19.x + + '@mantine/store@7.17.4': + resolution: {integrity: sha512-a/EecHPtYVxhu3oMX9uTymGolmOBWxW8Qs4fLCjiazEJbS1ScI4lS71GK/SuOa2rGuuOJkaotpyritbx3paIRg==} + peerDependencies: + react: ^18.x || ^19.x + + '@monaco-editor/loader@1.5.0': + resolution: {integrity: sha512-hKoGSM+7aAc7eRTRjpqAZucPmoNOC4UUbknb/VNoTkEIkCPhqV8LfbsgM1webRM7S/z21eHEx9Fkwx8Z/C/+Xw==} + + '@monaco-editor/react@4.7.0': + resolution: {integrity: sha512-cyzXQCtO47ydzxpQtCGSQGOC8Gk3ZUeBXFAxD+CWXYFo5OqZyZUonFl0DwUlTyAfRHntBfw2p3w4s9R6oe1eCA==} + peerDependencies: + monaco-editor: '>= 0.25.0 < 1' + react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 + react-dom: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 + + '@nodelib/fs.scandir@2.1.5': + resolution: {integrity: sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==} + engines: {node: '>= 8'} + + '@nodelib/fs.stat@2.0.5': + resolution: {integrity: sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==} + engines: {node: '>= 8'} + + '@nodelib/fs.walk@1.2.8': + resolution: {integrity: sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==} + engines: {node: '>= 8'} + + '@rc-component/async-validator@5.0.4': + resolution: {integrity: sha512-qgGdcVIF604M9EqjNF0hbUTz42bz/RDtxWdWuU5EQe3hi7M8ob54B6B35rOsvX5eSvIHIzT9iH1R3n+hk3CGfg==} + engines: {node: '>=14.x'} + + '@rc-component/color-picker@2.0.1': + resolution: {integrity: sha512-WcZYwAThV/b2GISQ8F+7650r5ZZJ043E57aVBFkQ+kSY4C6wdofXgB0hBx+GPGpIU0Z81eETNoDUJMr7oy/P8Q==} + peerDependencies: + react: '>=16.9.0' + react-dom: '>=16.9.0' + + '@rc-component/context@1.4.0': + resolution: {integrity: sha512-kFcNxg9oLRMoL3qki0OMxK+7g5mypjgaaJp/pkOis/6rVxma9nJBF/8kCIuTYHUQNr0ii7MxqE33wirPZLJQ2w==} + peerDependencies: + react: '>=16.9.0' + react-dom: '>=16.9.0' + + '@rc-component/mini-decimal@1.1.0': + resolution: {integrity: sha512-jS4E7T9Li2GuYwI6PyiVXmxTiM6b07rlD9Ge8uGZSCz3WlzcG5ZK7g5bbuKNeZ9pgUuPK/5guV781ujdVpm4HQ==} + engines: {node: '>=8.x'} + + '@rc-component/mutate-observer@1.1.0': + resolution: {integrity: sha512-QjrOsDXQusNwGZPf4/qRQasg7UFEj06XiCJ8iuiq/Io7CrHrgVi6Uuetw60WAMG1799v+aM8kyc+1L/GBbHSlw==} + engines: {node: '>=8.x'} + peerDependencies: + react: '>=16.9.0' + react-dom: '>=16.9.0' + + '@rc-component/portal@1.1.2': + resolution: {integrity: sha512-6f813C0IsasTZms08kfA8kPAGxbbkYToa8ALaiDIGGECU4i9hj8Plgbx0sNJDrey3EtHO30hmdaxtT0138xZcg==} + engines: {node: '>=8.x'} + peerDependencies: + react: '>=16.9.0' + react-dom: '>=16.9.0' + + '@rc-component/qrcode@1.0.0': + resolution: {integrity: sha512-L+rZ4HXP2sJ1gHMGHjsg9jlYBX/SLN2D6OxP9Zn3qgtpMWtO2vUfxVFwiogHpAIqs54FnALxraUy/BCO1yRIgg==} + engines: {node: '>=8.x'} + peerDependencies: + react: '>=16.9.0' + react-dom: '>=16.9.0' + + '@rc-component/tour@1.15.1': + resolution: {integrity: sha512-Tr2t7J1DKZUpfJuDZWHxyxWpfmj8EZrqSgyMZ+BCdvKZ6r1UDsfU46M/iWAAFBy961Ssfom2kv5f3UcjIL2CmQ==} + engines: {node: '>=8.x'} + peerDependencies: + react: '>=16.9.0' + react-dom: '>=16.9.0' + + '@rc-component/trigger@2.2.6': + resolution: {integrity: sha512-/9zuTnWwhQ3S3WT1T8BubuFTT46kvnXgaERR9f4BTKyn61/wpf/BvbImzYBubzJibU707FxwbKszLlHjcLiv1Q==} + engines: {node: '>=8.x'} + peerDependencies: + react: '>=16.9.0' + react-dom: '>=16.9.0' + + '@rollup/rollup-android-arm-eabi@4.40.0': + resolution: {integrity: sha512-+Fbls/diZ0RDerhE8kyC6hjADCXA1K4yVNlH0EYfd2XjyH0UGgzaQ8MlT0pCXAThfxv3QUAczHaL+qSv1E4/Cg==} + cpu: [arm] + os: [android] + + '@rollup/rollup-android-arm64@4.40.0': + resolution: {integrity: sha512-PPA6aEEsTPRz+/4xxAmaoWDqh67N7wFbgFUJGMnanCFs0TV99M0M8QhhaSCks+n6EbQoFvLQgYOGXxlMGQe/6w==} + cpu: [arm64] + os: [android] + + '@rollup/rollup-darwin-arm64@4.40.0': + resolution: {integrity: sha512-GwYOcOakYHdfnjjKwqpTGgn5a6cUX7+Ra2HeNj/GdXvO2VJOOXCiYYlRFU4CubFM67EhbmzLOmACKEfvp3J1kQ==} + cpu: [arm64] + os: [darwin] + + '@rollup/rollup-darwin-x64@4.40.0': + resolution: {integrity: sha512-CoLEGJ+2eheqD9KBSxmma6ld01czS52Iw0e2qMZNpPDlf7Z9mj8xmMemxEucinev4LgHalDPczMyxzbq+Q+EtA==} + cpu: [x64] + os: [darwin] + + '@rollup/rollup-freebsd-arm64@4.40.0': + resolution: {integrity: sha512-r7yGiS4HN/kibvESzmrOB/PxKMhPTlz+FcGvoUIKYoTyGd5toHp48g1uZy1o1xQvybwwpqpe010JrcGG2s5nkg==} + cpu: [arm64] + os: [freebsd] + + '@rollup/rollup-freebsd-x64@4.40.0': + resolution: {integrity: sha512-mVDxzlf0oLzV3oZOr0SMJ0lSDd3xC4CmnWJ8Val8isp9jRGl5Dq//LLDSPFrasS7pSm6m5xAcKaw3sHXhBjoRw==} + cpu: [x64] + os: [freebsd] + + '@rollup/rollup-linux-arm-gnueabihf@4.40.0': + resolution: {integrity: sha512-y/qUMOpJxBMy8xCXD++jeu8t7kzjlOCkoxxajL58G62PJGBZVl/Gwpm7JK9+YvlB701rcQTzjUZ1JgUoPTnoQA==} + cpu: [arm] + os: [linux] + + '@rollup/rollup-linux-arm-musleabihf@4.40.0': + resolution: {integrity: sha512-GoCsPibtVdJFPv/BOIvBKO/XmwZLwaNWdyD8TKlXuqp0veo2sHE+A/vpMQ5iSArRUz/uaoj4h5S6Pn0+PdhRjg==} + cpu: [arm] + os: [linux] + + '@rollup/rollup-linux-arm64-gnu@4.40.0': + resolution: {integrity: sha512-L5ZLphTjjAD9leJzSLI7rr8fNqJMlGDKlazW2tX4IUF9P7R5TMQPElpH82Q7eNIDQnQlAyiNVfRPfP2vM5Avvg==} + cpu: [arm64] + os: [linux] + + '@rollup/rollup-linux-arm64-musl@4.40.0': + resolution: {integrity: sha512-ATZvCRGCDtv1Y4gpDIXsS+wfFeFuLwVxyUBSLawjgXK2tRE6fnsQEkE4csQQYWlBlsFztRzCnBvWVfcae/1qxQ==} + cpu: [arm64] + os: [linux] + + '@rollup/rollup-linux-loongarch64-gnu@4.40.0': + resolution: {integrity: sha512-wG9e2XtIhd++QugU5MD9i7OnpaVb08ji3P1y/hNbxrQ3sYEelKJOq1UJ5dXczeo6Hj2rfDEL5GdtkMSVLa/AOg==} + cpu: [loong64] + os: [linux] + + '@rollup/rollup-linux-powerpc64le-gnu@4.40.0': + resolution: {integrity: sha512-vgXfWmj0f3jAUvC7TZSU/m/cOE558ILWDzS7jBhiCAFpY2WEBn5jqgbqvmzlMjtp8KlLcBlXVD2mkTSEQE6Ixw==} + cpu: [ppc64] + os: [linux] + + '@rollup/rollup-linux-riscv64-gnu@4.40.0': + resolution: {integrity: sha512-uJkYTugqtPZBS3Z136arevt/FsKTF/J9dEMTX/cwR7lsAW4bShzI2R0pJVw+hcBTWF4dxVckYh72Hk3/hWNKvA==} + cpu: [riscv64] + os: [linux] + + '@rollup/rollup-linux-riscv64-musl@4.40.0': + resolution: {integrity: sha512-rKmSj6EXQRnhSkE22+WvrqOqRtk733x3p5sWpZilhmjnkHkpeCgWsFFo0dGnUGeA+OZjRl3+VYq+HyCOEuwcxQ==} + cpu: [riscv64] + os: [linux] + + '@rollup/rollup-linux-s390x-gnu@4.40.0': + resolution: {integrity: sha512-SpnYlAfKPOoVsQqmTFJ0usx0z84bzGOS9anAC0AZ3rdSo3snecihbhFTlJZ8XMwzqAcodjFU4+/SM311dqE5Sw==} + cpu: [s390x] + os: [linux] + + '@rollup/rollup-linux-x64-gnu@4.40.0': + resolution: {integrity: sha512-RcDGMtqF9EFN8i2RYN2W+64CdHruJ5rPqrlYw+cgM3uOVPSsnAQps7cpjXe9be/yDp8UC7VLoCoKC8J3Kn2FkQ==} + cpu: [x64] + os: [linux] + + '@rollup/rollup-linux-x64-musl@4.40.0': + resolution: {integrity: sha512-HZvjpiUmSNx5zFgwtQAV1GaGazT2RWvqeDi0hV+AtC8unqqDSsaFjPxfsO6qPtKRRg25SisACWnJ37Yio8ttaw==} + cpu: [x64] + os: [linux] + + '@rollup/rollup-win32-arm64-msvc@4.40.0': + resolution: {integrity: sha512-UtZQQI5k/b8d7d3i9AZmA/t+Q4tk3hOC0tMOMSq2GlMYOfxbesxG4mJSeDp0EHs30N9bsfwUvs3zF4v/RzOeTQ==} + cpu: [arm64] + os: [win32] + + '@rollup/rollup-win32-ia32-msvc@4.40.0': + resolution: {integrity: sha512-+m03kvI2f5syIqHXCZLPVYplP8pQch9JHyXKZ3AGMKlg8dCyr2PKHjwRLiW53LTrN/Nc3EqHOKxUxzoSPdKddA==} + cpu: [ia32] + os: [win32] + + '@rollup/rollup-win32-x64-msvc@4.40.0': + resolution: {integrity: sha512-lpPE1cLfP5oPzVjKMx10pgBmKELQnFJXHgvtHCtuJWOv8MxqdEIMNtgHgBFf7Ea2/7EuVwa9fodWUfXAlXZLZQ==} + cpu: [x64] + os: [win32] + + '@standard-schema/utils@0.3.0': + resolution: {integrity: sha512-e7Mew686owMaPJVNNLs55PUvgz371nKgwsc4vxE49zsODpJEnxgxRo2y/OKrqueavXgZNMDVj3DdHFlaSAeU8g==} + + '@svgr/babel-plugin-add-jsx-attribute@8.0.0': + resolution: {integrity: sha512-b9MIk7yhdS1pMCZM8VeNfUlSKVRhsHZNMl5O9SfaX0l0t5wjdgu4IDzGB8bpnGBBOjGST3rRFVsaaEtI4W6f7g==} + engines: {node: '>=14'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@svgr/babel-plugin-remove-jsx-attribute@8.0.0': + resolution: {integrity: sha512-BcCkm/STipKvbCl6b7QFrMh/vx00vIP63k2eM66MfHJzPr6O2U0jYEViXkHJWqXqQYjdeA9cuCl5KWmlwjDvbA==} + engines: {node: '>=14'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@svgr/babel-plugin-remove-jsx-empty-expression@8.0.0': + resolution: {integrity: sha512-5BcGCBfBxB5+XSDSWnhTThfI9jcO5f0Ai2V24gZpG+wXF14BzwxxdDb4g6trdOux0rhibGs385BeFMSmxtS3uA==} + engines: {node: '>=14'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@svgr/babel-plugin-replace-jsx-attribute-value@8.0.0': + resolution: {integrity: sha512-KVQ+PtIjb1BuYT3ht8M5KbzWBhdAjjUPdlMtpuw/VjT8coTrItWX6Qafl9+ji831JaJcu6PJNKCV0bp01lBNzQ==} + engines: {node: '>=14'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@svgr/babel-plugin-svg-dynamic-title@8.0.0': + resolution: {integrity: sha512-omNiKqwjNmOQJ2v6ge4SErBbkooV2aAWwaPFs2vUY7p7GhVkzRkJ00kILXQvRhA6miHnNpXv7MRnnSjdRjK8og==} + engines: {node: '>=14'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@svgr/babel-plugin-svg-em-dimensions@8.0.0': + resolution: {integrity: sha512-mURHYnu6Iw3UBTbhGwE/vsngtCIbHE43xCRK7kCw4t01xyGqb2Pd+WXekRRoFOBIY29ZoOhUCTEweDMdrjfi9g==} + engines: {node: '>=14'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@svgr/babel-plugin-transform-react-native-svg@8.1.0': + resolution: {integrity: sha512-Tx8T58CHo+7nwJ+EhUwx3LfdNSG9R2OKfaIXXs5soiy5HtgoAEkDay9LIimLOcG8dJQH1wPZp/cnAv6S9CrR1Q==} + engines: {node: '>=14'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@svgr/babel-plugin-transform-svg-component@8.0.0': + resolution: {integrity: sha512-DFx8xa3cZXTdb/k3kfPeaixecQLgKh5NVBMwD0AQxOzcZawK4oo1Jh9LbrcACUivsCA7TLG8eeWgrDXjTMhRmw==} + engines: {node: '>=12'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@svgr/babel-preset@8.1.0': + resolution: {integrity: sha512-7EYDbHE7MxHpv4sxvnVPngw5fuR6pw79SkcrILHJ/iMpuKySNCl5W1qcwPEpU+LgyRXOaAFgH0KhwD18wwg6ug==} + engines: {node: '>=14'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@svgr/core@8.1.0': + resolution: {integrity: sha512-8QqtOQT5ACVlmsvKOJNEaWmRPmcojMOzCz4Hs2BGG/toAp/K38LcsMRyLp349glq5AzJbCEeimEoxaX6v/fLrA==} + engines: {node: '>=14'} + + '@svgr/hast-util-to-babel-ast@8.0.0': + resolution: {integrity: sha512-EbDKwO9GpfWP4jN9sGdYwPBU0kdomaPIL2Eu4YwmgP+sJeXT+L7bMwJUBnhzfH8Q2qMBqZ4fJwpCyYsAN3mt2Q==} + engines: {node: '>=14'} + + '@svgr/plugin-jsx@8.1.0': + resolution: {integrity: sha512-0xiIyBsLlr8quN+WyuxooNW9RJ0Dpr8uOnH/xrCVO8GLUcwHISwj1AG0k+LFzteTkAA0GbX0kj9q6Dk70PTiPA==} + engines: {node: '>=14'} + peerDependencies: + '@svgr/core': '*' + + '@swc/core-darwin-arm64@1.11.21': + resolution: {integrity: sha512-v6gjw9YFWvKulCw3ZA1dY+LGMafYzJksm1mD4UZFZ9b36CyHFowYVYug1ajYRIRqEvvfIhHUNV660zTLoVFR8g==} + engines: {node: '>=10'} + cpu: [arm64] + os: [darwin] + + '@swc/core-darwin-x64@1.11.21': + resolution: {integrity: sha512-CUiTiqKlzskwswrx9Ve5NhNoab30L1/ScOfQwr1duvNlFvarC8fvQSgdtpw2Zh3MfnfNPpyLZnYg7ah4kbT9JQ==} + engines: {node: '>=10'} + cpu: [x64] + os: [darwin] + + '@swc/core-linux-arm-gnueabihf@1.11.21': + resolution: {integrity: sha512-YyBTAFM/QPqt1PscD8hDmCLnqPGKmUZpqeE25HXY8OLjl2MUs8+O4KjwPZZ+OGxpdTbwuWFyMoxjcLy80JODvg==} + engines: {node: '>=10'} + cpu: [arm] + os: [linux] + + '@swc/core-linux-arm64-gnu@1.11.21': + resolution: {integrity: sha512-DQD+ooJmwpNsh4acrftdkuwl5LNxxg8U4+C/RJNDd7m5FP9Wo4c0URi5U0a9Vk/6sQNh9aSGcYChDpqCDWEcBw==} + engines: {node: '>=10'} + cpu: [arm64] + os: [linux] + + '@swc/core-linux-arm64-musl@1.11.21': + resolution: {integrity: sha512-y1L49+snt1a1gLTYPY641slqy55QotPdtRK9Y6jMi4JBQyZwxC8swWYlQWb+MyILwxA614fi62SCNZNznB3XSA==} + engines: {node: '>=10'} + cpu: [arm64] + os: [linux] + + '@swc/core-linux-x64-gnu@1.11.21': + resolution: {integrity: sha512-NesdBXv4CvVEaFUlqKj+GA4jJMNUzK2NtKOrUNEtTbXaVyNiXjFCSaDajMTedEB0jTAd9ybB0aBvwhgkJUWkWA==} + engines: {node: '>=10'} + cpu: [x64] + os: [linux] + + '@swc/core-linux-x64-musl@1.11.21': + resolution: {integrity: sha512-qFV60pwpKVOdmX67wqQzgtSrUGWX9Cibnp1CXyqZ9Mmt8UyYGvmGu7p6PMbTyX7vdpVUvWVRf8DzrW2//wmVHg==} + engines: {node: '>=10'} + cpu: [x64] + os: [linux] + + '@swc/core-win32-arm64-msvc@1.11.21': + resolution: {integrity: sha512-DJJe9k6gXR/15ZZVLv1SKhXkFst8lYCeZRNHH99SlBodvu4slhh/MKQ6YCixINRhCwliHrpXPym8/5fOq8b7Ig==} + engines: {node: '>=10'} + cpu: [arm64] + os: [win32] + + '@swc/core-win32-ia32-msvc@1.11.21': + resolution: {integrity: sha512-TqEXuy6wedId7bMwLIr9byds+mKsaXVHctTN88R1UIBPwJA92Pdk0uxDgip0pEFzHB/ugU27g6d8cwUH3h2eIw==} + engines: {node: '>=10'} + cpu: [ia32] + os: [win32] + + '@swc/core-win32-x64-msvc@1.11.21': + resolution: {integrity: sha512-BT9BNNbMxdpUM1PPAkYtviaV0A8QcXttjs2MDtOeSqqvSJaPtyM+Fof2/+xSwQDmDEFzbGCcn75M5+xy3lGqpA==} + engines: {node: '>=10'} + cpu: [x64] + os: [win32] + + '@swc/core@1.11.21': + resolution: {integrity: sha512-/Y3BJLcwd40pExmdar8MH2UGGvCBrqNN7hauOMckrEX2Ivcbv3IMhrbGX4od1dnF880Ed8y/E9aStZCIQi0EGw==} + engines: {node: '>=10'} + peerDependencies: + '@swc/helpers': '>=0.5.17' + peerDependenciesMeta: + '@swc/helpers': + optional: true + + '@swc/counter@0.1.3': + resolution: {integrity: sha512-e2BR4lsJkkRlKZ/qCHPw9ZaSxc0MVUd7gtbtaB7aMvHeJVYe8sOB8DBZkP2DtISHGSku9sCK6T6cnY0CtXrOCQ==} + + '@swc/types@0.1.21': + resolution: {integrity: sha512-2YEtj5HJVbKivud9N4bpPBAyZhj4S2Ipe5LkUG94alTpr7in/GU/EARgPAd3BwU+YOmFVJC2+kjqhGRi3r0ZpQ==} + + '@tanstack/history@1.115.0': + resolution: {integrity: sha512-K7JJNrRVvyjAVnbXOH2XLRhFXDkeP54Kt2P4FR1Kl2KDGlIbkua5VqZQD2rot3qaDrpufyUa63nuLai1kOLTsQ==} + engines: {node: '>=12'} + + '@tanstack/query-core@5.74.4': + resolution: {integrity: sha512-YuG0A0+3i9b2Gfo9fkmNnkUWh5+5cFhWBN0pJAHkHilTx6A0nv8kepkk4T4GRt4e5ahbtFj2eTtkiPcVU1xO4A==} + + '@tanstack/query-devtools@5.73.3': + resolution: {integrity: sha512-hBQyYwsOuO7QOprK75NzfrWs/EQYjgFA0yykmcvsV62q0t6Ua97CU3sYgjHx0ZvxkXSOMkY24VRJ5uv9f5Ik4w==} + + '@tanstack/react-query-devtools@5.74.4': + resolution: {integrity: sha512-PGCAcytQMmeagoeGG45ccBhrC1x0/5OlNjsM1FAb9OfsQZIhPzjwjhGcwmMu6TbT4RIHgvjxLwC5NHgkUwJQzw==} + peerDependencies: + '@tanstack/react-query': ^5.74.4 + react: ^18 || ^19 + + '@tanstack/react-query@5.74.4': + resolution: {integrity: sha512-mAbxw60d4ffQ4qmRYfkO1xzRBPUEf/72Dgo3qqea0J66nIKuDTLEqQt0ku++SDFlMGMnB6uKDnEG1xD/TDse4Q==} + peerDependencies: + react: ^18 || ^19 + + '@tanstack/react-router-devtools@1.116.0': + resolution: {integrity: sha512-PsJZWPjcmwZGe71kUvH4bI1ozkv1FgBuBEE0hTYlTCSJ3uG+qv3ndGEI+AiFyuF5OStrbfg0otW1OxeNq5vdGQ==} + engines: {node: '>=12'} + peerDependencies: + '@tanstack/react-router': ^1.116.0 + react: '>=18.0.0 || >=19.0.0' + react-dom: '>=18.0.0 || >=19.0.0' + + '@tanstack/react-router@1.116.0': + resolution: {integrity: sha512-ZBAg5Q6zJf0mnP9DYPiaaQ/wLDH2ujCMi/2RllpH86VUkdkyvQQzpAyKoiYJ891wh9OPgj6W6tPrzB4qy5FpRA==} + engines: {node: '>=12'} + peerDependencies: + react: '>=18.0.0 || >=19.0.0' + react-dom: '>=18.0.0 || >=19.0.0' + + '@tanstack/react-store@0.7.0': + resolution: {integrity: sha512-S/Rq17HaGOk+tQHV/yrePMnG1xbsKZIl/VsNWnNXt4XW+tTY8dTlvpJH2ZQ3GRALsusG5K6Q3unAGJ2pd9W/Ng==} + peerDependencies: + react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 + react-dom: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 + + '@tanstack/router-core@1.115.3': + resolution: {integrity: sha512-gynHs72LHVg05fuJTwZZYhDL4VNEAK0sXz7IqiBv7a3qsYeEmIZsGaFr9sVjTkuF1kbrFBdJd5JYutzBh9Uuhw==} + engines: {node: '>=12'} + + '@tanstack/router-devtools-core@1.115.3': + resolution: {integrity: sha512-VBdgw1qxeOD/6FlZ9gitrWPUKGW83CuAW31gf32E0dxL7sIXP+yEFyPlNsVlENan1oSaEuV8tjKkuq5s4MfaPw==} + engines: {node: '>=12'} + peerDependencies: + '@tanstack/router-core': ^1.115.3 + csstype: ^3.0.10 + solid-js: '>=1.9.5' + tiny-invariant: ^1.3.3 + peerDependenciesMeta: + csstype: + optional: true + + '@tanstack/router-generator@1.116.0': + resolution: {integrity: sha512-XhCp85zP87G2bpSXnosiP3fiMo8HMQD2mvWqFFTFKz87WocabQYGlfhmNYWmBwI50EuS7Ph9lwXsSkV0oKh0xw==} + engines: {node: '>=12'} + peerDependencies: + '@tanstack/react-router': ^1.116.0 + peerDependenciesMeta: + '@tanstack/react-router': + optional: true + + '@tanstack/router-plugin@1.116.1': + resolution: {integrity: sha512-9A8DAyRejTzvkVOzgVPUY6l2aH7xOMEXSJJtV9GNbi4NtE6AXUCoFe3mtvYnHSzRqAUMCO0wnfVENCjXQoQYZw==} + engines: {node: '>=12'} + peerDependencies: + '@rsbuild/core': '>=1.0.2' + '@tanstack/react-router': ^1.116.0 + vite: '>=5.0.0 || >=6.0.0' + vite-plugin-solid: ^2.11.2 + webpack: '>=5.92.0' + peerDependenciesMeta: + '@rsbuild/core': + optional: true + '@tanstack/react-router': + optional: true + vite: + optional: true + vite-plugin-solid: + optional: true + webpack: + optional: true + + '@tanstack/router-utils@1.115.0': + resolution: {integrity: sha512-Dng4y+uLR9b5zPGg7dHReHOTHQa6x+G6nCoZshsDtWrYsrdCcJEtLyhwZ5wG8OyYS6dVr/Cn+E5Bd2b6BhJ89w==} + engines: {node: '>=12'} + + '@tanstack/store@0.7.0': + resolution: {integrity: sha512-CNIhdoUsmD2NolYuaIs8VfWM467RK6oIBAW4nPEKZhg1smZ+/CwtCdpURgp7nxSqOaV9oKkzdWD80+bC66F/Jg==} + + '@tanstack/virtual-file-routes@1.115.0': + resolution: {integrity: sha512-XLUh1Py3AftcERrxkxC5Y5m5mfllRH3YR6YVlyjFgI2Tc2Ssy2NKmQFQIafoxfW459UJ8Dn81nWKETEIJifE4g==} + engines: {node: '>=12'} + + '@types/babel__core@7.20.5': + resolution: {integrity: sha512-qoQprZvz5wQFJwMDqeseRXWv3rqMvhgpbXFfVyWhbx9X47POIA6i/+dXefEmZKoAgOaTdaIgNSMqMIU61yRyzA==} + + '@types/babel__generator@7.27.0': + resolution: {integrity: sha512-ufFd2Xi92OAVPYsy+P4n7/U7e68fex0+Ee8gSG9KX7eo084CWiQ4sdxktvdl0bOPupXtVJPY19zk6EwWqUQ8lg==} + + '@types/babel__template@7.4.4': + resolution: {integrity: sha512-h/NUaSyG5EyxBIp8YRxo4RMe2/qQgvyowRwVMzhYhBCONbW8PUsg4lkFMrhgZhUe5z3L3MiLDuvyJ/CaPa2A8A==} + + '@types/babel__traverse@7.20.7': + resolution: {integrity: sha512-dkO5fhS7+/oos4ciWxyEyjWe48zmG6wbCheo/G2ZnHx4fs3EU6YC6UM8rk56gAjNJ9P3MTH2jo5jb92/K6wbng==} + + '@types/estree@1.0.7': + resolution: {integrity: sha512-w28IoSUCJpidD/TGviZwwMJckNESJZXFu7NBZ5YJ4mEUnNraUn9Pm8HSZm/jDF1pDWYKspWE7oVphigUPRakIQ==} + + '@types/js-cookie@2.2.7': + resolution: {integrity: sha512-aLkWa0C0vO5b4Sr798E26QgOkss68Un0bLjs7u9qxzPT5CG+8DuNTffWES58YzJs3hrVAOs1wonycqEBqNJubA==} + + '@types/json-schema@7.0.15': + resolution: {integrity: sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==} + + '@types/lodash@4.17.16': + resolution: {integrity: sha512-HX7Em5NYQAXKW+1T+FiuG27NGwzJfCX3s1GjOa7ujxZa52kjJLOr4FUxT+giF6Tgxv1e+/czV/iTtBw27WTU9g==} + + '@types/node@22.14.1': + resolution: {integrity: sha512-u0HuPQwe/dHrItgHHpmw3N2fYCR6x4ivMNbPHRkBVP4CvN+kiRrKHWk3i8tXiO/joPwXLMYvF9TTF0eqgHIuOw==} + + '@types/parse-json@4.0.2': + resolution: {integrity: sha512-dISoDXWWQwUquiKsyZ4Ng+HX2KsPL7LyHKHQwgGFEA3IaKac4Obd+h2a/a6waisAoepJlBcx9paWqjA8/HVjCw==} + + '@types/qs@6.9.18': + resolution: {integrity: sha512-kK7dgTYDyGqS+e2Q4aK9X3D7q234CIZ1Bv0q/7Z5IwRDoADNU81xXJK/YVyLbLTZCoIwUoDoffFeF+p/eIklAA==} + + '@types/react-dom@19.1.2': + resolution: {integrity: sha512-XGJkWF41Qq305SKWEILa1O8vzhb3aOo3ogBlSmiqNko/WmRb6QIaweuZCXjKygVDXpzXb5wyxKTSOsmkuqj+Qw==} + peerDependencies: + '@types/react': ^19.0.0 + + '@types/react@19.1.2': + resolution: {integrity: sha512-oxLPMytKchWGbnQM9O7D67uPa9paTNxO7jVoNMXgkkErULBPhPARCfkKL9ytcIJJRGjbsVwW4ugJzyFFvm/Tiw==} + + '@typescript-eslint/eslint-plugin@8.31.0': + resolution: {integrity: sha512-evaQJZ/J/S4wisevDvC1KFZkPzRetH8kYZbkgcTRyql3mcKsf+ZFDV1BVWUGTCAW5pQHoqn5gK5b8kn7ou9aFQ==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + peerDependencies: + '@typescript-eslint/parser': ^8.0.0 || ^8.0.0-alpha.0 + eslint: ^8.57.0 || ^9.0.0 + typescript: '>=4.8.4 <5.9.0' + + '@typescript-eslint/parser@8.31.0': + resolution: {integrity: sha512-67kYYShjBR0jNI5vsf/c3WG4u+zDnCTHTPqVMQguffaWWFs7artgwKmfwdifl+r6XyM5LYLas/dInj2T0SgJyw==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + peerDependencies: + eslint: ^8.57.0 || ^9.0.0 + typescript: '>=4.8.4 <5.9.0' + + '@typescript-eslint/scope-manager@8.31.0': + resolution: {integrity: sha512-knO8UyF78Nt8O/B64i7TlGXod69ko7z6vJD9uhSlm0qkAbGeRUSudcm0+K/4CrRjrpiHfBCjMWlc08Vav1xwcw==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + '@typescript-eslint/type-utils@8.31.0': + resolution: {integrity: sha512-DJ1N1GdjI7IS7uRlzJuEDCgDQix3ZVYVtgeWEyhyn4iaoitpMBX6Ndd488mXSx0xah/cONAkEaYyylDyAeHMHg==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + peerDependencies: + eslint: ^8.57.0 || ^9.0.0 + typescript: '>=4.8.4 <5.9.0' + + '@typescript-eslint/types@8.31.0': + resolution: {integrity: sha512-Ch8oSjVyYyJxPQk8pMiP2FFGYatqXQfQIaMp+TpuuLlDachRWpUAeEu1u9B/v/8LToehUIWyiKcA/w5hUFRKuQ==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + '@typescript-eslint/typescript-estree@8.31.0': + resolution: {integrity: sha512-xLmgn4Yl46xi6aDSZ9KkyfhhtnYI15/CvHbpOy/eR5NWhK/BK8wc709KKwhAR0m4ZKRP7h07bm4BWUYOCuRpQQ==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + peerDependencies: + typescript: '>=4.8.4 <5.9.0' + + '@typescript-eslint/utils@8.31.0': + resolution: {integrity: sha512-qi6uPLt9cjTFxAb1zGNgTob4x9ur7xC6mHQJ8GwEzGMGE9tYniublmJaowOJ9V2jUzxrltTPfdG2nKlWsq0+Ww==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + peerDependencies: + eslint: ^8.57.0 || ^9.0.0 + typescript: '>=4.8.4 <5.9.0' + + '@typescript-eslint/visitor-keys@8.31.0': + resolution: {integrity: sha512-QcGHmlRHWOl93o64ZUMNewCdwKGU6WItOU52H0djgNmn1EOrhVudrDzXz4OycCRSCPwFCDrE2iIt5vmuUdHxuQ==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + '@umijs/route-utils@4.0.1': + resolution: {integrity: sha512-+1ixf1BTOLuH+ORb4x8vYMPeIt38n9q0fJDwhv9nSxrV46mxbLF0nmELIo9CKQB2gHfuC4+hww6xejJ6VYnBHQ==} + + '@umijs/use-params@1.0.9': + resolution: {integrity: sha512-QlN0RJSBVQBwLRNxbxjQ5qzqYIGn+K7USppMoIOVlf7fxXHsnQZ2bEsa6Pm74bt6DVQxpUE8HqvdStn6Y9FV1w==} + peerDependencies: + react: '*' + + '@vitejs/plugin-react-swc@3.9.0': + resolution: {integrity: sha512-jYFUSXhwMCYsh/aQTgSGLIN3Foz5wMbH9ahb0Zva//UzwZYbMiZd7oT3AU9jHT9DLswYDswsRwPU9jVF3yA48Q==} + peerDependencies: + vite: ^4 || ^5 || ^6 + + '@xobotyi/scrollbar-width@1.9.5': + resolution: {integrity: sha512-N8tkAACJx2ww8vFMneJmaAgmjAG1tnVBZJRLRcx061tmsLRZHSEZSLuGWnwPtunsSLvSqXQ2wfp7Mgqg1I+2dQ==} + + acorn-jsx@5.3.2: + resolution: {integrity: sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==} + peerDependencies: + acorn: ^6.0.0 || ^7.0.0 || ^8.0.0 + + acorn@8.14.1: + resolution: {integrity: sha512-OvQ/2pUDKmgfCg++xsTX1wGxfTaszcHVcTctW4UJB4hibJx2HXxxO5UmVgyjMa+ZDsiaf5wWLXYpRWMmBI0QHg==} + engines: {node: '>=0.4.0'} + hasBin: true + + add-dom-event-listener@1.1.0: + resolution: {integrity: sha512-WCxx1ixHT0GQU9hb0KI/mhgRQhnU+U3GvwY6ZvVjYq8rsihIGoaIOUbY0yMPBxLH5MDtr0kz3fisWGNcbWW7Jw==} + + ajv@6.12.6: + resolution: {integrity: sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==} + + ansi-escapes@7.0.0: + resolution: {integrity: sha512-GdYO7a61mR0fOlAsvC9/rIHf7L96sBc6dEWzeOu+KAea5bZyQRPIpojrVoI4AXGJS/ycu/fBTdLrUkA4ODrvjw==} + engines: {node: '>=18'} + + ansi-regex@6.1.0: + resolution: {integrity: sha512-7HSX4QQb4CspciLpVFwyRe79O3xsIZDDLER21kERQ71oaPodF8jL725AgJMFAYbooIqolJoRLuM81SpeUkpkvA==} + engines: {node: '>=12'} + + ansi-styles@4.3.0: + resolution: {integrity: sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==} + engines: {node: '>=8'} + + ansi-styles@6.2.1: + resolution: {integrity: sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug==} + engines: {node: '>=12'} + + ansis@3.17.0: + resolution: {integrity: sha512-0qWUglt9JEqLFr3w1I1pbrChn1grhaiAR2ocX1PP/flRmxgtwTzPFFFnfIlD6aMOLQZgSuCRlidD70lvx8yhzg==} + engines: {node: '>=14'} + + antd@5.24.8: + resolution: {integrity: sha512-vJcW81WSRq+ymBKTiA3NE+FddmiqTAKxdWVRZU+HnLLrRrIz896svcUxXFPa7M4mH9HqyeJ5JPOHsne4sQAC1A==} + peerDependencies: + react: '>=16.9.0' + react-dom: '>=16.9.0' + + anymatch@3.1.3: + resolution: {integrity: sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==} + engines: {node: '>= 8'} + + argparse@2.0.1: + resolution: {integrity: sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==} + + array-buffer-byte-length@1.0.2: + resolution: {integrity: sha512-LHE+8BuR7RYGDKvnrmcuSq3tDcKv9OFEXQt/HpbZhY7V6h0zlUXutnAD82GiFx9rdieCMjkvtcsPqBwgUl1Iiw==} + engines: {node: '>= 0.4'} + + array-includes@3.1.8: + resolution: {integrity: sha512-itaWrbYbqpGXkGhZPGUulwnhVf5Hpy1xiCFsGqyIGglbBxmG5vSjxQen3/WGOjPpNEv1RtBLKxbmVXm8HpJStQ==} + engines: {node: '>= 0.4'} + + array.prototype.findlast@1.2.5: + resolution: {integrity: sha512-CVvd6FHg1Z3POpBLxO6E6zr+rSKEQ9L6rZHAaY7lLfhKsWYUBBOuMs0e9o24oopj6H+geRCX0YJ+TJLBK2eHyQ==} + engines: {node: '>= 0.4'} + + array.prototype.flat@1.3.3: + resolution: {integrity: sha512-rwG/ja1neyLqCuGZ5YYrznA62D4mZXg0i1cIskIUKSiqF3Cje9/wXAls9B9s1Wa2fomMsIv8czB8jZcPmxCXFg==} + engines: {node: '>= 0.4'} + + array.prototype.flatmap@1.3.3: + resolution: {integrity: sha512-Y7Wt51eKJSyi80hFrJCePGGNo5ktJCslFuboqJsbf57CCPcm5zztluPlc4/aD8sWsKvlwatezpV4U1efk8kpjg==} + engines: {node: '>= 0.4'} + + array.prototype.tosorted@1.1.4: + resolution: {integrity: sha512-p6Fx8B7b7ZhL/gmUsAy0D15WhvDccw3mnGNbZpi3pmeJdxtWsj2jEaI4Y6oo3XiHfzuSgPwKc04MYt6KgvC/wA==} + engines: {node: '>= 0.4'} + + arraybuffer.prototype.slice@1.0.4: + resolution: {integrity: sha512-BNoCY6SXXPQ7gF2opIP4GBE+Xw7U+pHMYKuzjgCN3GwiaIR09UUeKfheyIry77QtrCBlC0KK0q5/TER/tYh3PQ==} + engines: {node: '>= 0.4'} + + async-function@1.0.0: + resolution: {integrity: sha512-hsU18Ae8CDTR6Kgu9DYf0EbCr/a5iGL0rytQDobUcdpYOKokk8LEjVphnXkDkgpi0wYVsqrXuP0bZxJaTqdgoA==} + engines: {node: '>= 0.4'} + + asynckit@0.4.0: + resolution: {integrity: sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==} + + available-typed-arrays@1.0.7: + resolution: {integrity: sha512-wvUjBtSGN7+7SjNpq/9M2Tg350UZD3q62IFZLbRAR1bSMlCo1ZaeW+BJ+D090e4hIIZLBcTDWe4Mh4jvUDajzQ==} + engines: {node: '>= 0.4'} + + axios@1.8.4: + resolution: {integrity: sha512-eBSYY4Y68NNlHbHBMdeDmKNtDgXWhQsJcGqzO3iLUM0GraQFSS9cVgPX5I9b3lbdFKyYoAEGAZF1DwhTaljNAw==} + + babel-dead-code-elimination@1.0.10: + resolution: {integrity: sha512-DV5bdJZTzZ0zn0DC24v3jD7Mnidh6xhKa4GfKCbq3sfW8kaWhDdZjP3i81geA8T33tdYqWKw4D3fVv0CwEgKVA==} + + babel-plugin-macros@3.1.0: + resolution: {integrity: sha512-Cg7TFGpIr01vOQNODXOOaGz2NpCU5gl8x1qJFbb6hbZxR7XrcE2vtbAsTAbJ7/xwJtUuJEw8K8Zr/AE0LHlesg==} + engines: {node: '>=10', npm: '>=6'} + + balanced-match@1.0.2: + resolution: {integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==} + + binary-extensions@2.3.0: + resolution: {integrity: sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==} + engines: {node: '>=8'} + + brace-expansion@1.1.11: + resolution: {integrity: sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==} + + brace-expansion@2.0.1: + resolution: {integrity: sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==} + + braces@3.0.3: + resolution: {integrity: sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==} + engines: {node: '>=8'} + + browserslist@4.24.4: + resolution: {integrity: sha512-KDi1Ny1gSePi1vm0q4oxSF8b4DR44GF4BbmS2YdhPLOEqd8pDviZOGH/GsmRwoWJ2+5Lr085X7naowMwKHDG1A==} + engines: {node: ^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7} + hasBin: true + + call-bind-apply-helpers@1.0.2: + resolution: {integrity: sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==} + engines: {node: '>= 0.4'} + + call-bind@1.0.8: + resolution: {integrity: sha512-oKlSFMcMwpUg2ednkhQ454wfWiU/ul3CkJe/PEHcTKuiX6RpbehUiFMXu13HalGZxfUwCQzZG747YXBn1im9ww==} + engines: {node: '>= 0.4'} + + call-bound@1.0.4: + resolution: {integrity: sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==} + engines: {node: '>= 0.4'} + + callsites@3.1.0: + resolution: {integrity: sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==} + engines: {node: '>=6'} + + camelcase-css@2.0.1: + resolution: {integrity: sha512-QOSvevhslijgYwRx6Rv7zKdMF8lbRmx+uQGx2+vDc+KI/eBnsy9kit5aj23AgGu3pa4t9AgwbnXWqS+iOY+2aA==} + engines: {node: '>= 6'} + + camelcase@6.3.0: + resolution: {integrity: sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA==} + engines: {node: '>=10'} + + caniuse-lite@1.0.30001715: + resolution: {integrity: sha512-7ptkFGMm2OAOgvZpwgA4yjQ5SQbrNVGdRjzH0pBdy1Fasvcr+KAeECmbCAECzTuDuoX0FCY8KzUxjf9+9kfZEw==} + + chalk@4.1.2: + resolution: {integrity: sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==} + engines: {node: '>=10'} + + chalk@5.4.1: + resolution: {integrity: sha512-zgVZuo2WcZgfUEmsn6eO3kINexW8RAE4maiQ8QNs8CtpPCSyMiYsULR3HQYkm3w8FIA3SberyMJMSldGsW+U3w==} + engines: {node: ^12.17.0 || ^14.13 || >=16.0.0} + + chokidar@3.6.0: + resolution: {integrity: sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==} + engines: {node: '>= 8.10.0'} + + classnames@2.5.1: + resolution: {integrity: sha512-saHYOzhIQs6wy2sVxTM6bUDsQO4F50V9RQ22qBpEdCW+I+/Wmke2HOl6lS6dTpdxVhb88/I6+Hs+438c3lfUow==} + + cli-cursor@5.0.0: + resolution: {integrity: sha512-aCj4O5wKyszjMmDT4tZj93kxyydN/K5zPWSCe6/0AV/AA1pqe5ZBIw0a2ZfPQV7lL5/yb5HsUreJ6UFAF1tEQw==} + engines: {node: '>=18'} + + cli-truncate@4.0.0: + resolution: {integrity: sha512-nPdaFdQ0h/GEigbPClz11D0v/ZJEwxmeVZGeMo3Z5StPtUTkA9o1lD6QwoirYiSDzbcwn2XcjwmCp68W1IS4TA==} + engines: {node: '>=18'} + + clsx@2.1.1: + resolution: {integrity: sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA==} + engines: {node: '>=6'} + + color-convert@2.0.1: + resolution: {integrity: sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==} + engines: {node: '>=7.0.0'} + + color-name@1.1.4: + resolution: {integrity: sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==} + + colorette@2.0.20: + resolution: {integrity: sha512-IfEDxwoWIjkeXL1eXcDiow4UbKjhLdq6/EuSVR9GMN7KVH3r9gQ83e73hsz1Nd1T3ijd5xv1wcWRYO+D6kCI2w==} + + combined-stream@1.0.8: + resolution: {integrity: sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==} + engines: {node: '>= 0.8'} + + commander@13.1.0: + resolution: {integrity: sha512-/rFeCpNJQbhSZjGVwO9RFV3xPqbnERS8MmIQzCtD/zl6gpJuV/bMLuN92oG3F7d8oDEHHRrujSXNUr8fpjntKw==} + engines: {node: '>=18'} + + compute-scroll-into-view@3.1.1: + resolution: {integrity: sha512-VRhuHOLoKYOy4UbilLbUzbYg93XLjv2PncJC50EuTWPA3gaja1UjBsUP/D/9/juV3vQFr6XBEzn9KCAHdUvOHw==} + + concat-map@0.0.1: + resolution: {integrity: sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==} + + confbox@0.1.8: + resolution: {integrity: sha512-RMtmw0iFkeR4YV+fUOSucriAQNb9g8zFR52MWCtl+cCZOFRNL6zeB395vPzFhEjjn4fMxXudmELnl/KF/WrK6w==} + + confbox@0.2.2: + resolution: {integrity: sha512-1NB+BKqhtNipMsov4xI/NnhCKp9XG9NamYp5PVm9klAT0fsrNPjaFICsCFhNhwZJKNh7zB/3q8qXz0E9oaMNtQ==} + + convert-source-map@1.9.0: + resolution: {integrity: sha512-ASFBup0Mz1uyiIjANan1jzLQami9z1PoYSZCiiYW2FczPbenXc45FZdBZLzOT+r6+iciuEModtmCti+hjaAk0A==} + + convert-source-map@2.0.0: + resolution: {integrity: sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==} + + copy-to-clipboard@3.3.3: + resolution: {integrity: sha512-2KV8NhB5JqC3ky0r9PMCAZKbUHSwtEo4CwCs0KXgruG43gX5PMqDEBbVU4OUzw2MuAWUfsuFmWvEKG5QRfSnJA==} + + cosmiconfig@7.1.0: + resolution: {integrity: sha512-AdmX6xUzdNASswsFtmwSt7Vj8po9IuqXm0UXz7QKPuEUmPB4XyjGfaAr2PSuELMwkRMVH1EpIkX5bTZGRB3eCA==} + engines: {node: '>=10'} + + cosmiconfig@8.3.6: + resolution: {integrity: sha512-kcZ6+W5QzcJ3P1Mt+83OUv/oHFqZHIx8DuxG6eZ5RGMERoLqp4BuGjhHLYGK+Kf5XVkQvqBSmAy/nGWN3qDgEA==} + engines: {node: '>=14'} + peerDependencies: + typescript: '>=4.9.5' + peerDependenciesMeta: + typescript: + optional: true + + cross-spawn@7.0.6: + resolution: {integrity: sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==} + engines: {node: '>= 8'} + + css-in-js-utils@3.1.0: + resolution: {integrity: sha512-fJAcud6B3rRu+KHYk+Bwf+WFL2MDCJJ1XG9x137tJQ0xYxor7XziQtuGFbWNdqrvF4Tk26O3H73nfVqXt/fW1A==} + + css-tree@1.1.3: + resolution: {integrity: sha512-tRpdppF7TRazZrjJ6v3stzv93qxRcSsFmW6cX0Zm2NVKpxE1WV1HblnghVv9TreireHkqI/VDEsfolRF1p6y7Q==} + engines: {node: '>=8.0.0'} + + cssesc@3.0.0: + resolution: {integrity: sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg==} + engines: {node: '>=4'} + hasBin: true + + csstype@3.1.3: + resolution: {integrity: sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==} + + data-view-buffer@1.0.2: + resolution: {integrity: sha512-EmKO5V3OLXh1rtK2wgXRansaK1/mtVdTUEiEI0W8RkvgT05kfxaH29PliLnpLP73yYO6142Q72QNa8Wx/A5CqQ==} + engines: {node: '>= 0.4'} + + data-view-byte-length@1.0.2: + resolution: {integrity: sha512-tuhGbE6CfTM9+5ANGf+oQb72Ky/0+s3xKUpHvShfiz2RxMFgFPjsXuRLBVMtvMs15awe45SRb83D6wH4ew6wlQ==} + engines: {node: '>= 0.4'} + + data-view-byte-offset@1.0.1: + resolution: {integrity: sha512-BS8PfmtDGnrgYdOonGZQdLZslWIeCGFP9tpan0hi1Co2Zr2NKADsvGYA8XxuG/4UWgJ6Cjtv+YJnB6MM69QGlQ==} + engines: {node: '>= 0.4'} + + dayjs@1.11.13: + resolution: {integrity: sha512-oaMBel6gjolK862uaPQOVTA7q3TZhuSvuMQAAglQDOWYO9A91IrAOUJEyKVlqJlHE0vq5p5UXxzdPfMH/x6xNg==} + + debug@4.4.0: + resolution: {integrity: sha512-6WTZ/IxCY/T6BALoZHaE4ctp9xm+Z5kY/pzYaCHRFeyVhojxlrm+46y68HA6hr0TcwEssoxNiDEUJQjfPZ/RYA==} + engines: {node: '>=6.0'} + peerDependencies: + supports-color: '*' + peerDependenciesMeta: + supports-color: + optional: true + + deep-is@0.1.4: + resolution: {integrity: sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==} + + define-data-property@1.1.4: + resolution: {integrity: sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A==} + engines: {node: '>= 0.4'} + + define-properties@1.2.1: + resolution: {integrity: sha512-8QmQKqEASLd5nx0U1B1okLElbUuuttJ/AnYmRXbbbGDWh6uS208EjD4Xqq/I9wK7u0v6O08XhTWnt5XtEbR6Dg==} + engines: {node: '>= 0.4'} + + delayed-stream@1.0.0: + resolution: {integrity: sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==} + engines: {node: '>=0.4.0'} + + dequal@2.0.3: + resolution: {integrity: sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA==} + engines: {node: '>=6'} + + detect-node-es@1.1.0: + resolution: {integrity: sha512-ypdmJU/TbBby2Dxibuv7ZLW3Bs1QEmM7nHjEANfohJLvE0XVujisn1qPJcZxg+qDucsr+bP6fLD1rPS3AhJ7EQ==} + + diff@7.0.0: + resolution: {integrity: sha512-PJWHUb1RFevKCwaFA9RlG5tCd+FO5iRh9A8HEtkmBH2Li03iJriB6m6JIN4rGz3K3JLawI7/veA1xzRKP6ISBw==} + engines: {node: '>=0.3.1'} + + doctrine@2.1.0: + resolution: {integrity: sha512-35mSku4ZXK0vfCuHEDAwt55dg2jNajHZ1odvF+8SSr82EsZY4QmXfuWso8oEd8zRhVObSN18aM0CjSdoBX7zIw==} + engines: {node: '>=0.10.0'} + + dom-helpers@5.2.1: + resolution: {integrity: sha512-nRCa7CK3VTrM2NmGkIy4cbK7IZlgBE/PYMn55rrXefr5xXDP0LdtfPnblFDoVdcAfslJ7or6iqAUnx0CCGIWQA==} + + dot-case@3.0.4: + resolution: {integrity: sha512-Kv5nKlh6yRrdrGvxeJ2e5y2eRUpkUosIW4A2AS38zwSz27zu7ufDwQPi5Jhs3XAlGNetl3bmnGhQsMtkKJnj3w==} + + dunder-proto@1.0.1: + resolution: {integrity: sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==} + engines: {node: '>= 0.4'} + + electron-to-chromium@1.5.140: + resolution: {integrity: sha512-o82Rj+ONp4Ip7Cl1r7lrqx/pXhbp/lh9DpKcMNscFJdh8ebyRofnc7Sh01B4jx403RI0oqTBvlZ7OBIZLMr2+Q==} + + emoji-regex@10.4.0: + resolution: {integrity: sha512-EC+0oUMY1Rqm4O6LLrgjtYDvcVYTy7chDnM4Q7030tP4Kwj3u/pR6gP9ygnp2CJMK5Gq+9Q2oqmrFJAz01DXjw==} + + entities@4.5.0: + resolution: {integrity: sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==} + engines: {node: '>=0.12'} + + environment@1.1.0: + resolution: {integrity: sha512-xUtoPkMggbz0MPyPiIWr1Kp4aeWJjDZ6SMvURhimjdZgsRuDplF5/s9hcgGhyXMhs+6vpnuoiZ2kFiu3FMnS8Q==} + engines: {node: '>=18'} + + error-ex@1.3.2: + resolution: {integrity: sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==} + + error-stack-parser@2.1.4: + resolution: {integrity: sha512-Sk5V6wVazPhq5MhpO+AUxJn5x7XSXGl1R93Vn7i+zS15KDVxQijejNCrz8340/2bgLBjR9GtEG8ZVKONDjcqGQ==} + + es-abstract@1.23.9: + resolution: {integrity: sha512-py07lI0wjxAC/DcfK1S6G7iANonniZwTISvdPzk9hzeH0IZIshbuuFxLIU96OyF89Yb9hiqWn8M/bY83KY5vzA==} + engines: {node: '>= 0.4'} + + es-define-property@1.0.1: + resolution: {integrity: sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==} + engines: {node: '>= 0.4'} + + es-errors@1.3.0: + resolution: {integrity: sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==} + engines: {node: '>= 0.4'} + + es-iterator-helpers@1.2.1: + resolution: {integrity: sha512-uDn+FE1yrDzyC0pCo961B2IHbdM8y/ACZsKD4dG6WqrjV53BADjwa7D+1aom2rsNVfLyDgU/eigvlJGJ08OQ4w==} + engines: {node: '>= 0.4'} + + es-object-atoms@1.1.1: + resolution: {integrity: sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==} + engines: {node: '>= 0.4'} + + es-set-tostringtag@2.1.0: + resolution: {integrity: sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==} + engines: {node: '>= 0.4'} + + es-shim-unscopables@1.1.0: + resolution: {integrity: sha512-d9T8ucsEhh8Bi1woXCf+TIKDIROLG5WCkxg8geBCbvk22kzwC5G2OnXVMO6FUsvQlgUUXQ2itephWDLqDzbeCw==} + engines: {node: '>= 0.4'} + + es-to-primitive@1.3.0: + resolution: {integrity: sha512-w+5mJ3GuFL+NjVtJlvydShqE1eN3h3PbI7/5LAsYJP/2qtuMXjfL2LpHSRqo4b4eSF5K/DH1JXKUAHSB2UW50g==} + engines: {node: '>= 0.4'} + + esbuild@0.25.2: + resolution: {integrity: sha512-16854zccKPnC+toMywC+uKNeYSv+/eXkevRAfwRD/G9Cleq66m8XFIrigkbvauLLlCfDL45Q2cWegSg53gGBnQ==} + engines: {node: '>=18'} + hasBin: true + + escalade@3.2.0: + resolution: {integrity: sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==} + engines: {node: '>=6'} + + escape-string-regexp@4.0.0: + resolution: {integrity: sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==} + engines: {node: '>=10'} + + eslint-plugin-react-hooks@5.2.0: + resolution: {integrity: sha512-+f15FfK64YQwZdJNELETdn5ibXEUQmW1DZL6KXhNnc2heoy/sg9VJJeT7n8TlMWouzWqSWavFkIhHyIbIAEapg==} + engines: {node: '>=10'} + peerDependencies: + eslint: ^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0-0 || ^9.0.0 + + eslint-plugin-react-refresh@0.4.20: + resolution: {integrity: sha512-XpbHQ2q5gUF8BGOX4dHe+71qoirYMhApEPZ7sfhF/dNnOF1UXnCMGZf79SFTBO7Bz5YEIT4TMieSlJBWhP9WBA==} + peerDependencies: + eslint: '>=8.40' + + eslint-plugin-react@7.37.5: + resolution: {integrity: sha512-Qteup0SqU15kdocexFNAJMvCJEfa2xUKNV4CC1xsVMrIIqEy3SQ/rqyxCWNzfrd3/ldy6HMlD2e0JDVpDg2qIA==} + engines: {node: '>=4'} + peerDependencies: + eslint: ^3 || ^4 || ^5 || ^6 || ^7 || ^8 || ^9.7 + + eslint-plugin-unused-imports@4.1.4: + resolution: {integrity: sha512-YptD6IzQjDardkl0POxnnRBhU1OEePMV0nd6siHaRBbd+lyh6NAhFEobiznKU7kTsSsDeSD62Pe7kAM1b7dAZQ==} + peerDependencies: + '@typescript-eslint/eslint-plugin': ^8.0.0-0 || ^7.0.0 || ^6.0.0 || ^5.0.0 + eslint: ^9.0.0 || ^8.0.0 + peerDependenciesMeta: + '@typescript-eslint/eslint-plugin': + optional: true + + eslint-scope@8.3.0: + resolution: {integrity: sha512-pUNxi75F8MJ/GdeKtVLSbYg4ZI34J6C0C7sbL4YOp2exGwen7ZsuBqKzUhXd0qMQ362yET3z+uPwKeg/0C2XCQ==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + eslint-visitor-keys@3.4.3: + resolution: {integrity: sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + + eslint-visitor-keys@4.2.0: + resolution: {integrity: sha512-UyLnSehNt62FFhSwjZlHmeokpRK59rcz29j+F1/aDgbkbRTk7wIc9XzdoasMUbRNKDM0qQt/+BJ4BrpFeABemw==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + eslint@9.25.1: + resolution: {integrity: sha512-E6Mtz9oGQWDCpV12319d59n4tx9zOTXSTmc8BLVxBx+G/0RdM5MvEEJLU9c0+aleoePYYgVTOsRblx433qmhWQ==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + hasBin: true + peerDependencies: + jiti: '*' + peerDependenciesMeta: + jiti: + optional: true + + espree@10.3.0: + resolution: {integrity: sha512-0QYC8b24HWY8zjRnDTL6RiHfDbAWn63qb4LMj1Z4b076A4une81+z03Kg7l7mn/48PUTqoLptSXez8oknU8Clg==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + esquery@1.6.0: + resolution: {integrity: sha512-ca9pw9fomFcKPvFLXhBKUK90ZvGibiGOvRJNbjljY7s7uq/5YO4BOzcYtJqExdx99rF6aAcnRxHmcUHcz6sQsg==} + engines: {node: '>=0.10'} + + esrecurse@4.3.0: + resolution: {integrity: sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==} + engines: {node: '>=4.0'} + + estraverse@5.3.0: + resolution: {integrity: sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==} + engines: {node: '>=4.0'} + + esutils@2.0.3: + resolution: {integrity: sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==} + engines: {node: '>=0.10.0'} + + eventemitter3@5.0.1: + resolution: {integrity: sha512-GWkBvjiSZK87ELrYOSESUYeVIc9mvLLf/nXalMOS5dYrgZq9o5OVkbZAVM06CVxYsCwH9BDZFPlQTlPA1j4ahA==} + + execa@8.0.1: + resolution: {integrity: sha512-VyhnebXciFV2DESc+p6B+y0LjSm0krU4OgJN44qFAhBY0TJ+1V61tYD2+wHusZ6F9n5K+vl8k0sTy7PEfV4qpg==} + engines: {node: '>=16.17'} + + exsolve@1.0.5: + resolution: {integrity: sha512-pz5dvkYYKQ1AHVrgOzBKWeP4u4FRb3a6DNK2ucr0OoNwYIU4QWsJ+NM36LLzORT+z845MzKHHhpXiUF5nvQoJg==} + + fast-clean@1.4.0: + resolution: {integrity: sha512-mweRAmVkEwSvyqsG2l8PYQ2l5D+ONMGYpIqx/PkNcX0uM4A1MzYAqpopTsmyBRZ990yvgEAf4NeXqDVFYRDjng==} + + fast-deep-equal@3.1.3: + resolution: {integrity: sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==} + + fast-glob@3.3.3: + resolution: {integrity: sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg==} + engines: {node: '>=8.6.0'} + + fast-json-stable-stringify@2.1.0: + resolution: {integrity: sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==} + + fast-levenshtein@2.0.6: + resolution: {integrity: sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==} + + fast-shallow-equal@1.0.0: + resolution: {integrity: sha512-HPtaa38cPgWvaCFmRNhlc6NG7pv6NUHqjPgVAkWGoB9mQMwYB27/K0CvOM5Czy+qpT3e8XJ6Q4aPAnzpNpzNaw==} + + fastest-stable-stringify@2.0.2: + resolution: {integrity: sha512-bijHueCGd0LqqNK9b5oCMHc0MluJAx0cwqASgbWMvkO01lCYgIhacVRLcaDz3QnyYIRNJRDwMb41VuT6pHJ91Q==} + + fastq@1.19.1: + resolution: {integrity: sha512-GwLTyxkCXjXbxqIhTsMI2Nui8huMPtnxg7krajPJAjnEG/iiOS7i+zCtWGZR9G0NBKbXKh6X9m9UIsYX/N6vvQ==} + + fdir@6.4.4: + resolution: {integrity: sha512-1NZP+GK4GfuAv3PqKvxQRDMjdSRZjnkq7KfhlNrCNNlZ0ygQFpebfrnfnq/W7fpUnAv9aGWmY1zKx7FYL3gwhg==} + peerDependencies: + picomatch: ^3 || ^4 + peerDependenciesMeta: + picomatch: + optional: true + + file-entry-cache@8.0.0: + resolution: {integrity: sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ==} + engines: {node: '>=16.0.0'} + + fill-range@7.1.1: + resolution: {integrity: sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==} + engines: {node: '>=8'} + + find-root@1.1.0: + resolution: {integrity: sha512-NKfW6bec6GfKc0SGx1e07QZY9PE99u0Bft/0rzSD5k3sO/vwkVUpDUKVm5Gpp5Ue3YfShPFTX2070tDs5kB9Ng==} + + find-up@5.0.0: + resolution: {integrity: sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==} + engines: {node: '>=10'} + + flat-cache@4.0.1: + resolution: {integrity: sha512-f7ccFPK3SXFHpx15UIGyRJ/FJQctuKZ0zVuN3frBo4HnK3cay9VEW0R6yPYFHC0AgqhukPzKjq22t5DmAyqGyw==} + engines: {node: '>=16'} + + flatted@3.3.3: + resolution: {integrity: sha512-GX+ysw4PBCz0PzosHDepZGANEuFCMLrnRTiEy9McGjmkCQYwRq4A/X786G/fjM/+OjsWSU1ZrY5qyARZmO/uwg==} + + follow-redirects@1.15.9: + resolution: {integrity: sha512-gew4GsXizNgdoRyqmyfMHyAmXsZDk6mHkSxZFCzW9gwlbtOW44CDtYavM+y+72qD/Vq2l550kMF52DT8fOLJqQ==} + engines: {node: '>=4.0'} + peerDependencies: + debug: '*' + peerDependenciesMeta: + debug: + optional: true + + for-each@0.3.5: + resolution: {integrity: sha512-dKx12eRCVIzqCxFGplyFKJMPvLEWgmNtUrpTiJIR5u97zEhRG8ySrtboPHZXx7daLxQVrl643cTzbab2tkQjxg==} + engines: {node: '>= 0.4'} + + form-data@4.0.2: + resolution: {integrity: sha512-hGfm/slu0ZabnNt4oaRZ6uREyfCj6P4fT/n6A1rGV+Z0VdGXjfOhVUpkn6qVQONHGIFwmveGXyDs75+nr6FM8w==} + engines: {node: '>= 6'} + + fsevents@2.3.3: + resolution: {integrity: sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==} + engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0} + os: [darwin] + + function-bind@1.1.2: + resolution: {integrity: sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==} + + function.prototype.name@1.1.8: + resolution: {integrity: sha512-e5iwyodOHhbMr/yNrc7fDYG4qlbIvI5gajyzPnb5TCwyhjApznQh1BMFou9b30SevY43gCJKXycoCBjMbsuW0Q==} + engines: {node: '>= 0.4'} + + functions-have-names@1.2.3: + resolution: {integrity: sha512-xckBUXyTIqT97tq2x2AMb+g163b5JFysYk0x4qxNFwbfQkmNZoiRHb6sPzI9/QV33WeuvVYBUIiD4NzNIyqaRQ==} + + gensync@1.0.0-beta.2: + resolution: {integrity: sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==} + engines: {node: '>=6.9.0'} + + get-east-asian-width@1.3.0: + resolution: {integrity: sha512-vpeMIQKxczTD/0s2CdEWHcb0eeJe6TFjxb+J5xgX7hScxqrGuyjmv4c1D4A/gelKfyox0gJJwIHF+fLjeaM8kQ==} + engines: {node: '>=18'} + + get-intrinsic@1.3.0: + resolution: {integrity: sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==} + engines: {node: '>= 0.4'} + + get-nonce@1.0.1: + resolution: {integrity: sha512-FJhYRoDaiatfEkUK8HKlicmu/3SGFD51q3itKDGoSTysQJBnfOcxU5GxnhE1E6soB76MbT0MBtnKJuXyAx+96Q==} + engines: {node: '>=6'} + + get-proto@1.0.1: + resolution: {integrity: sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==} + engines: {node: '>= 0.4'} + + get-stream@8.0.1: + resolution: {integrity: sha512-VaUJspBffn/LMCJVoMvSAdmscJyS1auj5Zulnn5UoYcY531UWmdwhRWkcGKnGU93m5HSXP9LP2usOryrBtQowA==} + engines: {node: '>=16'} + + get-symbol-description@1.1.0: + resolution: {integrity: sha512-w9UMqWwJxHNOvoNzSJ2oPF5wvYcvP7jUvYzhp67yEhTi17ZDBBC1z9pTdGuzjD+EFIqLSYRweZjqfiPzQ06Ebg==} + engines: {node: '>= 0.4'} + + get-tsconfig@4.10.0: + resolution: {integrity: sha512-kGzZ3LWWQcGIAmg6iWvXn0ei6WDtV26wzHRMwDSzmAbcXrTEXxHy6IehI6/4eT6VRKyMP1eF1VqwrVUmE/LR7A==} + + glob-parent@5.1.2: + resolution: {integrity: sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==} + engines: {node: '>= 6'} + + glob-parent@6.0.2: + resolution: {integrity: sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==} + engines: {node: '>=10.13.0'} + + globals@11.12.0: + resolution: {integrity: sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==} + engines: {node: '>=4'} + + globals@14.0.0: + resolution: {integrity: sha512-oahGvuMGQlPw/ivIYBjVSrWAfWLBeku5tpPE2fOPLi+WHffIWbuh2tCjhyQhTBPMf5E9jDEH4FOmTYgYwbKwtQ==} + engines: {node: '>=18'} + + globals@15.15.0: + resolution: {integrity: sha512-7ACyT3wmyp3I61S4fG682L0VA2RGD9otkqGJIwNUMF1SWUombIIk+af1unuDYgMm082aHYwD+mzJvv9Iu8dsgg==} + engines: {node: '>=18'} + + globals@16.0.0: + resolution: {integrity: sha512-iInW14XItCXET01CQFqudPOWP2jYMl7T+QRQT+UNcR/iQncN/F0UNpgd76iFkBPgNQb4+X3LV9tLJYzwh+Gl3A==} + engines: {node: '>=18'} + + globalthis@1.0.4: + resolution: {integrity: sha512-DpLKbNU4WylpxJykQujfCcwYWiV/Jhm50Goo0wrVILAv5jOr9d+H+UR3PhSCD2rCCEIg0uc+G+muBTwD54JhDQ==} + engines: {node: '>= 0.4'} + + globrex@0.1.2: + resolution: {integrity: sha512-uHJgbwAMwNFf5mLst7IWLNg14x1CkeqglJb/K3doi4dw6q2IvAAmM/Y81kevy83wP+Sst+nutFTYOGg3d1lsxg==} + + goober@2.1.16: + resolution: {integrity: sha512-erjk19y1U33+XAMe1VTvIONHYoSqE4iS7BYUZfHaqeohLmnC0FdxEh7rQU+6MZ4OajItzjZFSRtVANrQwNq6/g==} + peerDependencies: + csstype: ^3.0.10 + + gopd@1.2.0: + resolution: {integrity: sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==} + engines: {node: '>= 0.4'} + + graphemer@1.4.0: + resolution: {integrity: sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==} + + has-bigints@1.1.0: + resolution: {integrity: sha512-R3pbpkcIqv2Pm3dUwgjclDRVmWpTJW2DcMzcIhEXEx1oh/CEMObMm3KLmRJOdvhM7o4uQBnwr8pzRK2sJWIqfg==} + engines: {node: '>= 0.4'} + + has-flag@4.0.0: + resolution: {integrity: sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==} + engines: {node: '>=8'} + + has-property-descriptors@1.0.2: + resolution: {integrity: sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg==} + + has-proto@1.2.0: + resolution: {integrity: sha512-KIL7eQPfHQRC8+XluaIw7BHUwwqL19bQn4hzNgdr+1wXoU0KKj6rufu47lhY7KbJR2C6T6+PfyN0Ea7wkSS+qQ==} + engines: {node: '>= 0.4'} + + has-symbols@1.1.0: + resolution: {integrity: sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==} + engines: {node: '>= 0.4'} + + has-tostringtag@1.0.2: + resolution: {integrity: sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==} + engines: {node: '>= 0.4'} + + hasown@2.0.2: + resolution: {integrity: sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==} + engines: {node: '>= 0.4'} + + hoist-non-react-statics@3.3.2: + resolution: {integrity: sha512-/gGivxi8JPKWNm/W0jSmzcMPpfpPLc3dY/6GxhX2hQ9iGj3aDfklV4ET7NjKpSinLpJ5vafa9iiGIEZg10SfBw==} + + html-parse-stringify@3.0.1: + resolution: {integrity: sha512-KknJ50kTInJ7qIScF3jeaFRpMpE8/lfiTdzf/twXyPBLAGrLRTmkz3AdTnKeh40X8k9L2fdYwEp/42WGXIRGcg==} + + human-signals@5.0.0: + resolution: {integrity: sha512-AXcZb6vzzrFAUE61HnN4mpLqd/cSIwNQjtNWR0euPm6y0iqx3G4gOXaIDdtdDwZmhwe82LA6+zinmW4UBWVePQ==} + engines: {node: '>=16.17.0'} + + husky@9.1.7: + resolution: {integrity: sha512-5gs5ytaNjBrh5Ow3zrvdUUY+0VxIuWVL4i9irt6friV+BqdCfmV11CQTWMiBYWHbXhco+J1kHfTOUkePhCDvMA==} + engines: {node: '>=18'} + hasBin: true + + hyphenate-style-name@1.1.0: + resolution: {integrity: sha512-WDC/ui2VVRrz3jOVi+XtjqkDjiVjTtFaAGiW37k6b+ohyQ5wYDOGkvCZa8+H0nx3gyvv0+BST9xuOgIyGQ00gw==} + + i18next@25.0.1: + resolution: {integrity: sha512-8S8PyZbrymJZn3DaN70/34JYWNhsqrU6yA4MuzcygJBv+41dgNMocEA8h+kV1P7MCc1ll03lOTOIXE7mpNCicw==} + peerDependencies: + typescript: ^5 + peerDependenciesMeta: + typescript: + optional: true + + ignore@5.3.2: + resolution: {integrity: sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==} + engines: {node: '>= 4'} + + immer@10.1.1: + resolution: {integrity: sha512-s2MPrmjovJcoMaHtx6K11Ra7oD05NT97w1IC5zpMkT6Atjr7H8LjaDd81iIxUYpMKSRRNMJE703M1Fhr/TctHw==} + + import-fresh@3.3.1: + resolution: {integrity: sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ==} + engines: {node: '>=6'} + + imurmurhash@0.1.4: + resolution: {integrity: sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==} + engines: {node: '>=0.8.19'} + + inline-style-prefixer@7.0.1: + resolution: {integrity: sha512-lhYo5qNTQp3EvSSp3sRvXMbVQTLrvGV6DycRMJ5dm2BLMiJ30wpXKdDdgX+GmJZ5uQMucwRKHamXSst3Sj/Giw==} + + internal-slot@1.1.0: + resolution: {integrity: sha512-4gd7VpWNQNB4UKKCFFVcp1AVv+FMOgs9NKzjHKusc8jTMhd5eL1NqQqOpE0KzMds804/yHlglp3uxgluOqAPLw==} + engines: {node: '>= 0.4'} + + is-array-buffer@3.0.5: + resolution: {integrity: sha512-DDfANUiiG2wC1qawP66qlTugJeL5HyzMpfr8lLK+jMQirGzNod0B12cFB/9q838Ru27sBwfw78/rdoU7RERz6A==} + engines: {node: '>= 0.4'} + + is-arrayish@0.2.1: + resolution: {integrity: sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==} + + is-async-function@2.1.1: + resolution: {integrity: sha512-9dgM/cZBnNvjzaMYHVoxxfPj2QXt22Ev7SuuPrs+xav0ukGB0S6d4ydZdEiM48kLx5kDV+QBPrpVnFyefL8kkQ==} + engines: {node: '>= 0.4'} + + is-bigint@1.1.0: + resolution: {integrity: sha512-n4ZT37wG78iz03xPRKJrHTdZbe3IicyucEtdRsV5yglwc3GyUfbAfpSeD0FJ41NbUNSt5wbhqfp1fS+BgnvDFQ==} + engines: {node: '>= 0.4'} + + is-binary-path@2.1.0: + resolution: {integrity: sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==} + engines: {node: '>=8'} + + is-boolean-object@1.2.2: + resolution: {integrity: sha512-wa56o2/ElJMYqjCjGkXri7it5FbebW5usLw/nPmCMs5DeZ7eziSYZhSmPRn0txqeW4LnAmQQU7FgqLpsEFKM4A==} + engines: {node: '>= 0.4'} + + is-callable@1.2.7: + resolution: {integrity: sha512-1BC0BVFhS/p0qtw6enp8e+8OD0UrK0oFLztSjNzhcKA3WDuJxxAPXzPuPtKkjEY9UUoEWlX/8fgKeu2S8i9JTA==} + engines: {node: '>= 0.4'} + + is-core-module@2.16.1: + resolution: {integrity: sha512-UfoeMA6fIJ8wTYFEUjelnaGI67v6+N7qXJEvQuIGa99l4xsCruSYOVSQ0uPANn4dAzm8lkYPaKLrrijLq7x23w==} + engines: {node: '>= 0.4'} + + is-data-view@1.0.2: + resolution: {integrity: sha512-RKtWF8pGmS87i2D6gqQu/l7EYRlVdfzemCJN/P3UOs//x1QE7mfhvzHIApBTRf7axvT6DMGwSwBXYCT0nfB9xw==} + engines: {node: '>= 0.4'} + + is-date-object@1.1.0: + resolution: {integrity: sha512-PwwhEakHVKTdRNVOw+/Gyh0+MzlCl4R6qKvkhuvLtPMggI1WAHt9sOwZxQLSGpUaDnrdyDsomoRgNnCfKNSXXg==} + engines: {node: '>= 0.4'} + + is-extglob@2.1.1: + resolution: {integrity: sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==} + engines: {node: '>=0.10.0'} + + is-finalizationregistry@1.1.1: + resolution: {integrity: sha512-1pC6N8qWJbWoPtEjgcL2xyhQOP491EQjeUo3qTKcmV8YSDDJrOepfG8pcC7h/QgnQHYSv0mJ3Z/ZWxmatVrysg==} + engines: {node: '>= 0.4'} + + is-fullwidth-code-point@4.0.0: + resolution: {integrity: sha512-O4L094N2/dZ7xqVdrXhh9r1KODPJpFms8B5sGdJLPy664AgvXsreZUyCQQNItZRDlYug4xStLjNp/sz3HvBowQ==} + engines: {node: '>=12'} + + is-fullwidth-code-point@5.0.0: + resolution: {integrity: sha512-OVa3u9kkBbw7b8Xw5F9P+D/T9X+Z4+JruYVNapTjPYZYUznQ5YfWeFkOj606XYYW8yugTfC8Pj0hYqvi4ryAhA==} + engines: {node: '>=18'} + + is-generator-function@1.1.0: + resolution: {integrity: sha512-nPUB5km40q9e8UfN/Zc24eLlzdSf9OfKByBw9CIdw4H1giPMeA0OIJvbchsCu4npfI2QcMVBsGEBHKZ7wLTWmQ==} + engines: {node: '>= 0.4'} + + is-glob@4.0.3: + resolution: {integrity: sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==} + engines: {node: '>=0.10.0'} + + is-map@2.0.3: + resolution: {integrity: sha512-1Qed0/Hr2m+YqxnM09CjA2d/i6YZNfF6R2oRAOj36eUdS6qIV/huPJNSEpKbupewFs+ZsJlxsjjPbc0/afW6Lw==} + engines: {node: '>= 0.4'} + + is-number-object@1.1.1: + resolution: {integrity: sha512-lZhclumE1G6VYD8VHe35wFaIif+CTy5SJIi5+3y4psDgWu4wPDoBhF8NxUOinEc7pHgiTsT6MaBb92rKhhD+Xw==} + engines: {node: '>= 0.4'} + + is-number@7.0.0: + resolution: {integrity: sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==} + engines: {node: '>=0.12.0'} + + is-regex@1.2.1: + resolution: {integrity: sha512-MjYsKHO5O7mCsmRGxWcLWheFqN9DJ/2TmngvjKXihe6efViPqc274+Fx/4fYj/r03+ESvBdTXK0V6tA3rgez1g==} + engines: {node: '>= 0.4'} + + is-set@2.0.3: + resolution: {integrity: sha512-iPAjerrse27/ygGLxw+EBR9agv9Y6uLeYVJMu+QNCoouJ1/1ri0mGrcWpfCqFZuzzx3WjtwxG098X+n4OuRkPg==} + engines: {node: '>= 0.4'} + + is-shared-array-buffer@1.0.4: + resolution: {integrity: sha512-ISWac8drv4ZGfwKl5slpHG9OwPNty4jOWPRIhBpxOoD+hqITiwuipOQ2bNthAzwA3B4fIjO4Nln74N0S9byq8A==} + engines: {node: '>= 0.4'} + + is-stream@3.0.0: + resolution: {integrity: sha512-LnQR4bZ9IADDRSkvpqMGvt/tEJWclzklNgSw48V5EAaAeDd6qGvN8ei6k5p0tvxSR171VmGyHuTiAOfxAbr8kA==} + engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} + + is-string@1.1.1: + resolution: {integrity: sha512-BtEeSsoaQjlSPBemMQIrY1MY0uM6vnS1g5fmufYOtnxLGUZM2178PKbhsk7Ffv58IX+ZtcvoGwccYsh0PglkAA==} + engines: {node: '>= 0.4'} + + is-symbol@1.1.1: + resolution: {integrity: sha512-9gGx6GTtCQM73BgmHQXfDmLtfjjTUDSyoxTCbp5WtoixAhfgsDirWIcVQ/IHpvI5Vgd5i/J5F7B9cN/WlVbC/w==} + engines: {node: '>= 0.4'} + + is-typed-array@1.1.15: + resolution: {integrity: sha512-p3EcsicXjit7SaskXHs1hA91QxgTw46Fv6EFKKGS5DRFLD8yKnohjF3hxoju94b/OcMZoQukzpPpBE9uLVKzgQ==} + engines: {node: '>= 0.4'} + + is-weakmap@2.0.2: + resolution: {integrity: sha512-K5pXYOm9wqY1RgjpL3YTkF39tni1XajUIkawTLUo9EZEVUFga5gSQJF8nNS7ZwJQ02y+1YCNYcMh+HIf1ZqE+w==} + engines: {node: '>= 0.4'} + + is-weakref@1.1.1: + resolution: {integrity: sha512-6i9mGWSlqzNMEqpCp93KwRS1uUOodk2OJ6b+sq7ZPDSy2WuI5NFIxp/254TytR8ftefexkWn5xNiHUNpPOfSew==} + engines: {node: '>= 0.4'} + + is-weakset@2.0.4: + resolution: {integrity: sha512-mfcwb6IzQyOKTs84CQMrOwW4gQcaTOAWJ0zzJCl2WSPDrWk/OzDaImWFH3djXhb24g4eudZfLRozAvPGw4d9hQ==} + engines: {node: '>= 0.4'} + + isarray@2.0.5: + resolution: {integrity: sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw==} + + isexe@2.0.0: + resolution: {integrity: sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==} + + iterator.prototype@1.1.5: + resolution: {integrity: sha512-H0dkQoCa3b2VEeKQBOxFph+JAbcrQdE7KC0UkqwpLmv2EC4P41QXP+rqo9wYodACiG5/WM5s9oDApTU8utwj9g==} + engines: {node: '>= 0.4'} + + js-cookie@2.2.1: + resolution: {integrity: sha512-HvdH2LzI/EAZcUwA8+0nKNtWHqS+ZmijLA30RwZA0bo7ToCckjK5MkGhjED9KoRcXO6BaGI3I9UIzSA1FKFPOQ==} + + js-tokens@4.0.0: + resolution: {integrity: sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==} + + js-yaml@4.1.0: + resolution: {integrity: sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==} + hasBin: true + + jsesc@3.1.0: + resolution: {integrity: sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==} + engines: {node: '>=6'} + hasBin: true + + json-buffer@3.0.1: + resolution: {integrity: sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==} + + json-parse-even-better-errors@2.3.1: + resolution: {integrity: sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==} + + json-schema-traverse@0.4.1: + resolution: {integrity: sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==} + + json-stable-stringify-without-jsonify@1.0.1: + resolution: {integrity: sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==} + + json2mq@0.2.0: + resolution: {integrity: sha512-SzoRg7ux5DWTII9J2qkrZrqV1gt+rTaoufMxEzXbS26Uid0NwaJd123HcoB80TgubEppxxIGdNxCx50fEoEWQA==} + + json5@2.2.3: + resolution: {integrity: sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==} + engines: {node: '>=6'} + hasBin: true + + jsx-ast-utils@3.3.5: + resolution: {integrity: sha512-ZZow9HBI5O6EPgSJLUb8n2NKgmVWTwCvHGwFuJlMjvLFqlGG6pjirPhtdsseaLZjSibD8eegzmYpUZwoIlj2cQ==} + engines: {node: '>=4.0'} + + just-clone@6.2.0: + resolution: {integrity: sha512-1IynUYEc/HAwxhi3WDpIpxJbZpMCvvrrmZVqvj9EhpvbH8lls7HhdhiByjL7DkAaWlLIzpC0Xc/VPvy/UxLNjA==} + + keyv@4.5.4: + resolution: {integrity: sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==} + + kolorist@1.8.0: + resolution: {integrity: sha512-Y+60/zizpJ3HRH8DCss+q95yr6145JXZo46OTpFvDZWLfRCE4qChOyk1b26nMaNpfHHgxagk9dXT5OP0Tfe+dQ==} + + levn@0.4.1: + resolution: {integrity: sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==} + engines: {node: '>= 0.8.0'} + + lilconfig@3.1.3: + resolution: {integrity: sha512-/vlFKAoH5Cgt3Ie+JLhRbwOsCQePABiU3tJ1egGvyQ+33R/vcwM2Zl2QR/LzjsBeItPt3oSVXapn+m4nQDvpzw==} + engines: {node: '>=14'} + + lines-and-columns@1.2.4: + resolution: {integrity: sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==} + + lint-staged@15.5.1: + resolution: {integrity: sha512-6m7u8mue4Xn6wK6gZvSCQwBvMBR36xfY24nF5bMTf2MHDYG6S3yhJuOgdYVw99hsjyDt2d4z168b3naI8+NWtQ==} + engines: {node: '>=18.12.0'} + hasBin: true + + listr2@8.3.2: + resolution: {integrity: sha512-vsBzcU4oE+v0lj4FhVLzr9dBTv4/fHIa57l+GCwovP8MoFNZJTOhGU8PXd4v2VJCbECAaijBiHntiekFMLvo0g==} + engines: {node: '>=18.0.0'} + + little-state-machine@4.8.1: + resolution: {integrity: sha512-liPHqaWMQ7rzZryQUDnbZ1Gclnnai3dIyaJ0nAgwZRXMzqbYrydrlCI0NDojRUbE5VYh5vu6hygEUZiH77nQkQ==} + peerDependencies: + react: ^16.8.0 || ^17 || ^18 || ^19 + + local-pkg@1.1.1: + resolution: {integrity: sha512-WunYko2W1NcdfAFpuLUoucsgULmgDBRkdxHxWQ7mK0cQqwPiy8E1enjuRBrhLtZkB5iScJ1XIPdhVEFK8aOLSg==} + engines: {node: '>=14'} + + locate-path@6.0.0: + resolution: {integrity: sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==} + engines: {node: '>=10'} + + lodash-es@4.17.21: + resolution: {integrity: sha512-mKnC+QJ9pWVzv+C4/U3rRsHapFfHvQFoFB92e52xeyGMcX6/OlIl78je1u8vePzYZSkkogMPJ2yjxxsb89cxyw==} + + lodash.merge@4.6.2: + resolution: {integrity: sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==} + + lodash@4.17.21: + resolution: {integrity: sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==} + + log-update@6.1.0: + resolution: {integrity: sha512-9ie8ItPR6tjY5uYJh8K/Zrv/RMZ5VOlOWvtZdEHYSTFKZfIBPQa9tOAEeAWhd+AnIneLJ22w5fjOYtoutpWq5w==} + engines: {node: '>=18'} + + loose-envify@1.4.0: + resolution: {integrity: sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==} + hasBin: true + + lower-case@2.0.2: + resolution: {integrity: sha512-7fm3l3NAF9WfN6W3JOmf5drwpVqX78JtoGJ3A6W0a6ZnldM41w2fV5D490psKFTpMds8TJse/eHLFFsNHHjHgg==} + + lru-cache@5.1.1: + resolution: {integrity: sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==} + + math-intrinsics@1.1.0: + resolution: {integrity: sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==} + engines: {node: '>= 0.4'} + + mdn-data@2.0.14: + resolution: {integrity: sha512-dn6wd0uw5GsdswPFfsgMp5NSB0/aDe6fK94YJV/AJDYXL6HVLWBsxeq7js7Ad+mU2K9LAlwpk6kN2D5mwCPVow==} + + merge-stream@2.0.0: + resolution: {integrity: sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==} + + merge2@1.4.1: + resolution: {integrity: sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==} + engines: {node: '>= 8'} + + micromatch@4.0.8: + resolution: {integrity: sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==} + engines: {node: '>=8.6'} + + mime-db@1.52.0: + resolution: {integrity: sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==} + engines: {node: '>= 0.6'} + + mime-types@2.1.35: + resolution: {integrity: sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==} + engines: {node: '>= 0.6'} + + mimic-fn@4.0.0: + resolution: {integrity: sha512-vqiC06CuhBTUdZH+RYl8sFrL096vA45Ok5ISO6sE/Mr1jRbGH4Csnhi8f3wKVl7x8mO4Au7Ir9D3Oyv1VYMFJw==} + engines: {node: '>=12'} + + mimic-function@5.0.1: + resolution: {integrity: sha512-VP79XUPxV2CigYP3jWwAUFSku2aKqBH7uTAapFWCBqutsbmDo96KY5o8uh6U+/YSIn5OxJnXp73beVkpqMIGhA==} + engines: {node: '>=18'} + + minimatch@3.1.2: + resolution: {integrity: sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==} + + minimatch@9.0.5: + resolution: {integrity: sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==} + engines: {node: '>=16 || 14 >=14.17'} + + mlly@1.7.4: + resolution: {integrity: sha512-qmdSIPC4bDJXgZTCR7XosJiNKySV7O215tsPtDN9iEO/7q/76b/ijtgRu/+epFXSJhijtTCCGp3DWS549P3xKw==} + + mobx-react-lite@4.1.0: + resolution: {integrity: sha512-QEP10dpHHBeQNv1pks3WnHRCem2Zp636lq54M2nKO2Sarr13pL4u6diQXf65yzXUn0mkk18SyIDCm9UOJYTi1w==} + peerDependencies: + mobx: ^6.9.0 + react: ^16.8.0 || ^17 || ^18 || ^19 + react-dom: '*' + react-native: '*' + peerDependenciesMeta: + react-dom: + optional: true + react-native: + optional: true + + mobx@6.13.7: + resolution: {integrity: sha512-aChaVU/DO5aRPmk1GX8L+whocagUUpBQqoPtJk+cm7UOXUk87J4PeWCh6nNmTTIfEhiR9DI/+FnA8dln/hTK7g==} + + monaco-editor@0.52.2: + resolution: {integrity: sha512-GEQWEZmfkOGLdd3XK8ryrfWz3AIP8YymVXiPHEdewrUq7mh0qrKrfHLNCXcbB6sTnMLnOZ3ztSiKcciFUkIJwQ==} + + ms@2.1.3: + resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==} + + nano-css@5.6.2: + resolution: {integrity: sha512-+6bHaC8dSDGALM1HJjOHVXpuastdu2xFoZlC77Jh4cg+33Zcgm+Gxd+1xsnpZK14eyHObSp82+ll5y3SX75liw==} + peerDependencies: + react: '*' + react-dom: '*' + + nanoid@3.3.11: + resolution: {integrity: sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==} + engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1} + hasBin: true + + nanoid@5.1.5: + resolution: {integrity: sha512-Ir/+ZpE9fDsNH0hQ3C68uyThDXzYcim2EqcZ8zn8Chtt1iylPT9xXJB0kPCnqzgcEGikO9RxSrh63MsmVCU7Fw==} + engines: {node: ^18 || >=20} + hasBin: true + + natural-compare@1.4.0: + resolution: {integrity: sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==} + + no-case@3.0.4: + resolution: {integrity: sha512-fgAN3jGAh+RoxUGZHTSOLJIqUc2wmoBwGR4tbpNAKmmovFoWq0OdRkb0VkldReO2a2iBT/OEulG9XSUc10r3zg==} + + node-releases@2.0.19: + resolution: {integrity: sha512-xxOWJsBKtzAq7DY0J+DTzuz58K8e7sJbdgwkbMWQe8UYB6ekmsQ45q0M/tJDsGaZmbC+l7n57UV8Hl5tHxO9uw==} + + normalize-path@3.0.0: + resolution: {integrity: sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==} + engines: {node: '>=0.10.0'} + + npm-run-path@5.3.0: + resolution: {integrity: sha512-ppwTtiJZq0O/ai0z7yfudtBpWIoxM8yE6nHi1X47eFR2EWORqfbu6CnPlNsjeN683eT0qG6H/Pyf9fCcvjnnnQ==} + engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} + + object-assign@4.1.1: + resolution: {integrity: sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==} + engines: {node: '>=0.10.0'} + + object-inspect@1.13.4: + resolution: {integrity: sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew==} + engines: {node: '>= 0.4'} + + object-keys@1.1.1: + resolution: {integrity: sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==} + engines: {node: '>= 0.4'} + + object.assign@4.1.7: + resolution: {integrity: sha512-nK28WOo+QIjBkDduTINE4JkF/UJJKyf2EJxvJKfblDpyg0Q+pkOHNTL0Qwy6NP6FhE/EnzV73BxxqcJaXY9anw==} + engines: {node: '>= 0.4'} + + object.entries@1.1.9: + resolution: {integrity: sha512-8u/hfXFRBD1O0hPUjioLhoWFHRmt6tKA4/vZPyckBr18l1KE9uHrFaFaUi8MDRTpi4uak2goyPTSNJLXX2k2Hw==} + engines: {node: '>= 0.4'} + + object.fromentries@2.0.8: + resolution: {integrity: sha512-k6E21FzySsSK5a21KRADBd/NGneRegFO5pLHfdQLpRDETUNJueLXs3WCzyQ3tFRDYgbq3KHGXfTbi2bs8WQ6rQ==} + engines: {node: '>= 0.4'} + + object.values@1.2.1: + resolution: {integrity: sha512-gXah6aZrcUxjWg2zR2MwouP2eHlCBzdV4pygudehaKXSGW4v2AsRQUK+lwwXhii6KFZcunEnmSUoYp5CXibxtA==} + engines: {node: '>= 0.4'} + + onetime@6.0.0: + resolution: {integrity: sha512-1FlR+gjXK7X+AsAHso35MnyN5KqGwJRi/31ft6x0M194ht7S+rWAvd7PHss9xSKMzE0asv1pyIHaJYq+BbacAQ==} + engines: {node: '>=12'} + + onetime@7.0.0: + resolution: {integrity: sha512-VXJjc87FScF88uafS3JllDgvAm+c/Slfz06lorj2uAY34rlUu0Nt+v8wreiImcrgAjjIHp1rXpTDlLOGw29WwQ==} + engines: {node: '>=18'} + + optionator@0.9.4: + resolution: {integrity: sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==} + engines: {node: '>= 0.8.0'} + + own-keys@1.0.1: + resolution: {integrity: sha512-qFOyK5PjiWZd+QQIh+1jhdb9LpxTF0qs7Pm8o5QHYZ0M3vKqSqzsZaEB6oWlxZ+q2sJBMI/Ktgd2N5ZwQoRHfg==} + engines: {node: '>= 0.4'} + + p-limit@3.1.0: + resolution: {integrity: sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==} + engines: {node: '>=10'} + + p-locate@5.0.0: + resolution: {integrity: sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==} + engines: {node: '>=10'} + + package-manager-detector@0.2.11: + resolution: {integrity: sha512-BEnLolu+yuz22S56CU1SUKq3XC3PkwD5wv4ikR4MfGvnRVcmzXR9DwSlW2fEamyTPyXHomBJRzgapeuBvRNzJQ==} + + parent-module@1.0.1: + resolution: {integrity: sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==} + engines: {node: '>=6'} + + parse-json@5.2.0: + resolution: {integrity: sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==} + engines: {node: '>=8'} + + path-exists@4.0.0: + resolution: {integrity: sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==} + engines: {node: '>=8'} + + path-key@3.1.1: + resolution: {integrity: sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==} + engines: {node: '>=8'} + + path-key@4.0.0: + resolution: {integrity: sha512-haREypq7xkM7ErfgIyA0z+Bj4AGKlMSdlQE2jvJo6huWD1EdkKYV+G/T4nq0YEF2vgTT8kqMFKo1uHn950r4SQ==} + engines: {node: '>=12'} + + path-parse@1.0.7: + resolution: {integrity: sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==} + + path-to-regexp@8.2.0: + resolution: {integrity: sha512-TdrF7fW9Rphjq4RjrW0Kp2AW0Ahwu9sRGTkS6bvDi0SCwZlEZYmcfDbEsTz8RVk0EHIS/Vd1bv3JhG+1xZuAyQ==} + engines: {node: '>=16'} + + path-type@4.0.0: + resolution: {integrity: sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==} + engines: {node: '>=8'} + + pathe@2.0.3: + resolution: {integrity: sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==} + + picocolors@1.1.1: + resolution: {integrity: sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==} + + picomatch@2.3.1: + resolution: {integrity: sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==} + engines: {node: '>=8.6'} + + picomatch@4.0.2: + resolution: {integrity: sha512-M7BAV6Rlcy5u+m6oPhAPFgJTzAioX/6B0DxyvDlo9l8+T3nLKbrczg2WLUyzd45L8RqfUMyGPzekbMvX2Ldkwg==} + engines: {node: '>=12'} + + pidtree@0.6.0: + resolution: {integrity: sha512-eG2dWTVw5bzqGRztnHExczNxt5VGsE6OwTeCG3fdUf9KBsZzO3R5OIIIzWR+iZA0NtZ+RDVdaoE2dK1cn6jH4g==} + engines: {node: '>=0.10'} + hasBin: true + + pkg-types@1.3.1: + resolution: {integrity: sha512-/Jm5M4RvtBFVkKWRu2BLUTNP8/M2a+UwuAX+ae4770q1qVGtfjG+WTCupoZixokjmHiry8uI+dlY8KXYV5HVVQ==} + + pkg-types@2.1.0: + resolution: {integrity: sha512-wmJwA+8ihJixSoHKxZJRBQG1oY8Yr9pGLzRmSsNms0iNWyHHAlZCa7mmKiFR10YPZuz/2k169JiS/inOjBCZ2A==} + + possible-typed-array-names@1.1.0: + resolution: {integrity: sha512-/+5VFTchJDoVj3bhoqi6UeymcD00DAwb1nJwamzPvHEszJ4FpF6SNNbUbOS8yI56qHzdV8eK0qEfOSiodkTdxg==} + engines: {node: '>= 0.4'} + + postcss-js@4.0.1: + resolution: {integrity: sha512-dDLF8pEO191hJMtlHFPRa8xsizHaM82MLfNkUHdUtVEV3tgTp5oj+8qbEqYM57SLfc74KSbw//4SeJma2LRVIw==} + engines: {node: ^12 || ^14 || >= 16} + peerDependencies: + postcss: ^8.4.21 + + postcss-mixins@9.0.4: + resolution: {integrity: sha512-XVq5jwQJDRu5M1XGkdpgASqLk37OqkH4JCFDXl/Dn7janOJjCTEKL+36cnRVy7bMtoBzALfO7bV7nTIsFnUWLA==} + engines: {node: '>=14.0'} + peerDependencies: + postcss: ^8.2.14 + + postcss-nested@6.2.0: + resolution: {integrity: sha512-HQbt28KulC5AJzG+cZtj9kvKB93CFCdLvog1WFLf1D+xmMvPGlBstkpTEZfK5+AN9hfJocyBFCNiqyS48bpgzQ==} + engines: {node: '>=12.0'} + peerDependencies: + postcss: ^8.2.14 + + postcss-preset-mantine@1.17.0: + resolution: {integrity: sha512-ji1PMDBUf2Vsx/HE5faMSs1+ff6qE6YRulTr4Ja+6HD3gop8rSMTCYdpN7KrdsEg079kfBKkO/PaKhG9uR0zwQ==} + peerDependencies: + postcss: '>=8.0.0' + + postcss-selector-parser@6.1.2: + resolution: {integrity: sha512-Q8qQfPiZ+THO/3ZrOrO0cJJKfpYCagtMUkXbnEfmgUjwXg6z/WBeOyS9APBBPCTSiDV+s4SwQGu8yFsiMRIudg==} + engines: {node: '>=4'} + + postcss-simple-vars@7.0.1: + resolution: {integrity: sha512-5GLLXaS8qmzHMOjVxqkk1TZPf1jMqesiI7qLhnlyERalG0sMbHIbJqrcnrpmZdKCLglHnRHoEBB61RtGTsj++A==} + engines: {node: '>=14.0'} + peerDependencies: + postcss: ^8.2.1 + + postcss@8.5.3: + resolution: {integrity: sha512-dle9A3yYxlBSrt8Fu+IpjGT8SY8hN0mlaA6GY8t0P5PjIOZemULz/E2Bnm/2dcUOena75OTNkHI76uZBNUUq3A==} + engines: {node: ^10 || ^12 || >=14} + + prelude-ls@1.2.1: + resolution: {integrity: sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==} + engines: {node: '>= 0.8.0'} + + prettier@3.5.3: + resolution: {integrity: sha512-QQtaxnoDJeAkDvDKWCLiwIXkTgRhwYDEQCghU9Z6q03iyek/rxRh/2lC3HB7P8sWT2xC/y5JDctPLBIGzHKbhw==} + engines: {node: '>=14'} + hasBin: true + + prop-types@15.8.1: + resolution: {integrity: sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==} + + proxy-from-env@1.1.0: + resolution: {integrity: sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==} + + punycode@2.3.1: + resolution: {integrity: sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==} + engines: {node: '>=6'} + + qs@6.14.0: + resolution: {integrity: sha512-YWWTjgABSKcvs/nWBi9PycY/JiPJqOD4JA6o9Sej2AtvSGarXxKC3OQSk4pAarbdQlKAh5D4FCQkJNkW+GAn3w==} + engines: {node: '>=0.6'} + + quansync@0.2.10: + resolution: {integrity: sha512-t41VRkMYbkHyCYmOvx/6URnN80H7k4X0lLdBMGsz+maAwrJQYB1djpV6vHrQIBE0WBSGqhtEHrK9U3DWWH8v7A==} + + queue-microtask@1.2.3: + resolution: {integrity: sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==} + + rambdax@11.3.1: + resolution: {integrity: sha512-ecsDpTQZuzZD16hPGpkja3klaho4I0tRp5IkjmUUrR7tNnw5RP9K/eiPfHev4HrRNr4OoUetIL/OOWFmeYls7A==} + + rc-cascader@3.33.1: + resolution: {integrity: sha512-Kyl4EJ7ZfCBuidmZVieegcbFw0RcU5bHHSbtEdmuLYd0fYHCAiYKZ6zon7fWAVyC6rWWOOib0XKdTSf7ElC9rg==} + peerDependencies: + react: '>=16.9.0' + react-dom: '>=16.9.0' + + rc-checkbox@3.5.0: + resolution: {integrity: sha512-aOAQc3E98HteIIsSqm6Xk2FPKIER6+5vyEFMZfo73TqM+VVAIqOkHoPjgKLqSNtVLWScoaM7vY2ZrGEheI79yg==} + peerDependencies: + react: '>=16.9.0' + react-dom: '>=16.9.0' + + rc-collapse@3.9.0: + resolution: {integrity: sha512-swDdz4QZ4dFTo4RAUMLL50qP0EY62N2kvmk2We5xYdRwcRn8WcYtuetCJpwpaCbUfUt5+huLpVxhvmnK+PHrkA==} + peerDependencies: + react: '>=16.9.0' + react-dom: '>=16.9.0' + + rc-dialog@9.6.0: + resolution: {integrity: sha512-ApoVi9Z8PaCQg6FsUzS8yvBEQy0ZL2PkuvAgrmohPkN3okps5WZ5WQWPc1RNuiOKaAYv8B97ACdsFU5LizzCqg==} + peerDependencies: + react: '>=16.9.0' + react-dom: '>=16.9.0' + + rc-drawer@7.2.0: + resolution: {integrity: sha512-9lOQ7kBekEJRdEpScHvtmEtXnAsy+NGDXiRWc2ZVC7QXAazNVbeT4EraQKYwCME8BJLa8Bxqxvs5swwyOepRwg==} + peerDependencies: + react: '>=16.9.0' + react-dom: '>=16.9.0' + + rc-dropdown@4.2.1: + resolution: {integrity: sha512-YDAlXsPv3I1n42dv1JpdM7wJ+gSUBfeyPK59ZpBD9jQhK9jVuxpjj3NmWQHOBceA1zEPVX84T2wbdb2SD0UjmA==} + peerDependencies: + react: '>=16.11.0' + react-dom: '>=16.11.0' + + rc-field-form@2.7.0: + resolution: {integrity: sha512-hgKsCay2taxzVnBPZl+1n4ZondsV78G++XVsMIJCAoioMjlMQR9YwAp7JZDIECzIu2Z66R+f4SFIRrO2DjDNAA==} + engines: {node: '>=8.x'} + peerDependencies: + react: '>=16.9.0' + react-dom: '>=16.9.0' + + rc-image@7.11.1: + resolution: {integrity: sha512-XuoWx4KUXg7hNy5mRTy1i8c8p3K8boWg6UajbHpDXS5AlRVucNfTi5YxTtPBTBzegxAZpvuLfh3emXFt6ybUdA==} + peerDependencies: + react: '>=16.9.0' + react-dom: '>=16.9.0' + + rc-input-number@9.5.0: + resolution: {integrity: sha512-bKaEvB5tHebUURAEXw35LDcnRZLq3x1k7GxfAqBMzmpHkDGzjAtnUL8y4y5N15rIFIg5IJgwr211jInl3cipag==} + peerDependencies: + react: '>=16.9.0' + react-dom: '>=16.9.0' + + rc-input@1.8.0: + resolution: {integrity: sha512-KXvaTbX+7ha8a/k+eg6SYRVERK0NddX8QX7a7AnRvUa/rEH0CNMlpcBzBkhI0wp2C8C4HlMoYl8TImSN+fuHKA==} + peerDependencies: + react: '>=16.0.0' + react-dom: '>=16.0.0' + + rc-mentions@2.20.0: + resolution: {integrity: sha512-w8HCMZEh3f0nR8ZEd466ATqmXFCMGMN5UFCzEUL0bM/nGw/wOS2GgRzKBcm19K++jDyuWCOJOdgcKGXU3fXfbQ==} + peerDependencies: + react: '>=16.9.0' + react-dom: '>=16.9.0' + + rc-menu@9.16.1: + resolution: {integrity: sha512-ghHx6/6Dvp+fw8CJhDUHFHDJ84hJE3BXNCzSgLdmNiFErWSOaZNsihDAsKq9ByTALo/xkNIwtDFGIl6r+RPXBg==} + peerDependencies: + react: '>=16.9.0' + react-dom: '>=16.9.0' + + rc-motion@2.9.5: + resolution: {integrity: sha512-w+XTUrfh7ArbYEd2582uDrEhmBHwK1ZENJiSJVb7uRxdE7qJSYjbO2eksRXmndqyKqKoYPc9ClpPh5242mV1vA==} + peerDependencies: + react: '>=16.9.0' + react-dom: '>=16.9.0' + + rc-notification@5.6.4: + resolution: {integrity: sha512-KcS4O6B4qzM3KH7lkwOB7ooLPZ4b6J+VMmQgT51VZCeEcmghdeR4IrMcFq0LG+RPdnbe/ArT086tGM8Snimgiw==} + engines: {node: '>=8.x'} + peerDependencies: + react: '>=16.9.0' + react-dom: '>=16.9.0' + + rc-overflow@1.4.1: + resolution: {integrity: sha512-3MoPQQPV1uKyOMVNd6SZfONi+f3st0r8PksexIdBTeIYbMX0Jr+k7pHEDvsXtR4BpCv90/Pv2MovVNhktKrwvw==} + peerDependencies: + react: '>=16.9.0' + react-dom: '>=16.9.0' + + rc-pagination@5.1.0: + resolution: {integrity: sha512-8416Yip/+eclTFdHXLKTxZvn70duYVGTvUUWbckCCZoIl3jagqke3GLsFrMs0bsQBikiYpZLD9206Ej4SOdOXQ==} + peerDependencies: + react: '>=16.9.0' + react-dom: '>=16.9.0' + + rc-picker@4.11.3: + resolution: {integrity: sha512-MJ5teb7FlNE0NFHTncxXQ62Y5lytq6sh5nUw0iH8OkHL/TjARSEvSHpr940pWgjGANpjCwyMdvsEV55l5tYNSg==} + engines: {node: '>=8.x'} + peerDependencies: + date-fns: '>= 2.x' + dayjs: '>= 1.x' + luxon: '>= 3.x' + moment: '>= 2.x' + react: '>=16.9.0' + react-dom: '>=16.9.0' + peerDependenciesMeta: + date-fns: + optional: true + dayjs: + optional: true + luxon: + optional: true + moment: + optional: true + + rc-progress@4.0.0: + resolution: {integrity: sha512-oofVMMafOCokIUIBnZLNcOZFsABaUw8PPrf1/y0ZBvKZNpOiu5h4AO9vv11Sw0p4Hb3D0yGWuEattcQGtNJ/aw==} + peerDependencies: + react: '>=16.9.0' + react-dom: '>=16.9.0' + + rc-rate@2.13.1: + resolution: {integrity: sha512-QUhQ9ivQ8Gy7mtMZPAjLbxBt5y9GRp65VcUyGUMF3N3fhiftivPHdpuDIaWIMOTEprAjZPC08bls1dQB+I1F2Q==} + engines: {node: '>=8.x'} + peerDependencies: + react: '>=16.9.0' + react-dom: '>=16.9.0' + + rc-resize-observer@0.2.6: + resolution: {integrity: sha512-YX6nYnd6fk7zbuvT6oSDMKiZjyngjHoy+fz+vL3Tez38d/G5iGdaDJa2yE7345G6sc4Mm1IGRUIwclvltddhmA==} + peerDependencies: + react: '>=16.9.0' + react-dom: '>=16.9.0' + + rc-resize-observer@1.4.3: + resolution: {integrity: sha512-YZLjUbyIWox8E9i9C3Tm7ia+W7euPItNWSPX5sCcQTYbnwDb5uNpnLHQCG1f22oZWUhLw4Mv2tFmeWe68CDQRQ==} + peerDependencies: + react: '>=16.9.0' + react-dom: '>=16.9.0' + + rc-segmented@2.7.0: + resolution: {integrity: sha512-liijAjXz+KnTRVnxxXG2sYDGd6iLL7VpGGdR8gwoxAXy2KglviKCxLWZdjKYJzYzGSUwKDSTdYk8brj54Bn5BA==} + peerDependencies: + react: '>=16.0.0' + react-dom: '>=16.0.0' + + rc-select@14.16.6: + resolution: {integrity: sha512-YPMtRPqfZWOm2XGTbx5/YVr1HT0vn//8QS77At0Gjb3Lv+Lbut0IORJPKLWu1hQ3u4GsA0SrDzs7nI8JG7Zmyg==} + engines: {node: '>=8.x'} + peerDependencies: + react: '*' + react-dom: '*' + + rc-slider@11.1.8: + resolution: {integrity: sha512-2gg/72YFSpKP+Ja5AjC5DPL1YnV8DEITDQrcc1eASrUYjl0esptaBVJBh5nLTXCCp15eD8EuGjwezVGSHhs9tQ==} + engines: {node: '>=8.x'} + peerDependencies: + react: '>=16.9.0' + react-dom: '>=16.9.0' + + rc-steps@6.0.1: + resolution: {integrity: sha512-lKHL+Sny0SeHkQKKDJlAjV5oZ8DwCdS2hFhAkIjuQt1/pB81M0cA0ErVFdHq9+jmPmFw1vJB2F5NBzFXLJxV+g==} + engines: {node: '>=8.x'} + peerDependencies: + react: '>=16.9.0' + react-dom: '>=16.9.0' + + rc-switch@4.1.0: + resolution: {integrity: sha512-TI8ufP2Az9oEbvyCeVE4+90PDSljGyuwix3fV58p7HV2o4wBnVToEyomJRVyTaZeqNPAp+vqeo4Wnj5u0ZZQBg==} + peerDependencies: + react: '>=16.9.0' + react-dom: '>=16.9.0' + + rc-table@7.50.4: + resolution: {integrity: sha512-Y+YuncnQqoS5e7yHvfvlv8BmCvwDYDX/2VixTBEhkMDk9itS9aBINp4nhzXFKiBP/frG4w0pS9d9Rgisl0T1Bw==} + engines: {node: '>=8.x'} + peerDependencies: + react: '>=16.9.0' + react-dom: '>=16.9.0' + + rc-tabs@15.6.0: + resolution: {integrity: sha512-SQ99Yjc9ewrJCUwoWPKq0FeGL2znWsqPhfcZgsHz1R7bkA2rMNe7CPgOiJkwppdJ98wkLhzs9vPrv21QOE1RyQ==} + engines: {node: '>=8.x'} + peerDependencies: + react: '>=16.9.0' + react-dom: '>=16.9.0' + + rc-textarea@1.10.0: + resolution: {integrity: sha512-ai9IkanNuyBS4x6sOL8qu/Ld40e6cEs6pgk93R+XLYg0mDSjNBGey6/ZpDs5+gNLD7urQ14po3V6Ck2dJLt9SA==} + peerDependencies: + react: '>=16.9.0' + react-dom: '>=16.9.0' + + rc-tooltip@6.4.0: + resolution: {integrity: sha512-kqyivim5cp8I5RkHmpsp1Nn/Wk+1oeloMv9c7LXNgDxUpGm+RbXJGL+OPvDlcRnx9DBeOe4wyOIl4OKUERyH1g==} + peerDependencies: + react: '>=16.9.0' + react-dom: '>=16.9.0' + + rc-tree-select@5.27.0: + resolution: {integrity: sha512-2qTBTzwIT7LRI1o7zLyrCzmo5tQanmyGbSaGTIf7sYimCklAToVVfpMC6OAldSKolcnjorBYPNSKQqJmN3TCww==} + peerDependencies: + react: '*' + react-dom: '*' + + rc-tree@5.13.1: + resolution: {integrity: sha512-FNhIefhftobCdUJshO7M8uZTA9F4OPGVXqGfZkkD/5soDeOhwO06T/aKTrg0WD8gRg/pyfq+ql3aMymLHCTC4A==} + engines: {node: '>=10.x'} + peerDependencies: + react: '*' + react-dom: '*' + + rc-upload@4.8.1: + resolution: {integrity: sha512-toEAhwl4hjLAI1u8/CgKWt30BR06ulPa4iGQSMvSXoHzO88gPCslxqV/mnn4gJU7PDoltGIC9Eh+wkeudqgHyw==} + peerDependencies: + react: '>=16.9.0' + react-dom: '>=16.9.0' + + rc-util@4.21.1: + resolution: {integrity: sha512-Z+vlkSQVc1l8O2UjR3WQ+XdWlhj5q9BMQNLk2iOBch75CqPfrJyGtcWMcnhRlNuDu0Ndtt4kLVO8JI8BrABobg==} + + rc-util@5.44.4: + resolution: {integrity: sha512-resueRJzmHG9Q6rI/DfK6Kdv9/Lfls05vzMs1Sk3M2P+3cJa+MakaZyWY8IPfehVuhPJFKrIY1IK4GqbiaiY5w==} + peerDependencies: + react: '>=16.9.0' + react-dom: '>=16.9.0' + + rc-virtual-list@3.18.5: + resolution: {integrity: sha512-1FuxVSxhzTj3y8k5xMPbhXCB0t2TOiI3Tq+qE2Bu+GGV7f+ECVuQl4OUg6lZ2qT5fordTW7CBpr9czdzXCI7Pg==} + engines: {node: '>=8.x'} + peerDependencies: + react: '>=16.9.0' + react-dom: '>=16.9.0' + + react-dom@19.1.0: + resolution: {integrity: sha512-Xs1hdnE+DyKgeHJeJznQmYMIBG3TKIHJJT95Q58nHLSrElKlGQqDTR2HQ9fx5CN/Gk6Vh/kupBTDLU11/nDk/g==} + peerDependencies: + react: ^19.1.0 + + react-hook-form@7.56.1: + resolution: {integrity: sha512-qWAVokhSpshhcEuQDSANHx3jiAEFzu2HAaaQIzi/r9FNPm1ioAvuJSD4EuZzWd7Al7nTRKcKPnBKO7sRn+zavQ==} + engines: {node: '>=18.0.0'} + peerDependencies: + react: ^16.8.0 || ^17 || ^18 || ^19 + + react-i18next@15.4.1: + resolution: {integrity: sha512-ahGab+IaSgZmNPYXdV1n+OYky95TGpFwnKRflX/16dY04DsYYKHtVLjeny7sBSCREEcoMbAgSkFiGLF5g5Oofw==} + peerDependencies: + i18next: '>= 23.2.3' + react: '>= 16.8.0' + react-dom: '*' + react-native: '*' + peerDependenciesMeta: + react-dom: + optional: true + react-native: + optional: true + + react-is@16.13.1: + resolution: {integrity: sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==} + + react-is@18.3.1: + resolution: {integrity: sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==} + + react-lifecycles-compat@3.0.4: + resolution: {integrity: sha512-fBASbA6LnOU9dOU2eW7aQ8xmYBSXUIWr+UmF9b1efZBazGNO+rcXT/icdKnYm2pTwcRylVUYwW7H1PHfLekVzA==} + + react-number-format@5.4.4: + resolution: {integrity: sha512-wOmoNZoOpvMminhifQYiYSTCLUDOiUbBunrMrMjA+dV52sY+vck1S4UhR6PkgnoCquvvMSeJjErXZ4qSaWCliA==} + peerDependencies: + react: ^0.14 || ^15.0.0 || ^16.0.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 + react-dom: ^0.14 || ^15.0.0 || ^16.0.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 + + react-remove-scroll-bar@2.3.8: + resolution: {integrity: sha512-9r+yi9+mgU33AKcj6IbT9oRCO78WriSj6t/cF8DWBZJ9aOGPOTEDvdUDz1FwKim7QXWwmHqtdHnRJfhAxEG46Q==} + engines: {node: '>=10'} + peerDependencies: + '@types/react': '*' + react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 + peerDependenciesMeta: + '@types/react': + optional: true + + react-remove-scroll@2.6.3: + resolution: {integrity: sha512-pnAi91oOk8g8ABQKGF5/M9qxmmOPxaAnopyTHYfqYEwJhyFrbbBtHuSgtKEoH0jpcxx5o3hXqH1mNd9/Oi+8iQ==} + engines: {node: '>=10'} + peerDependencies: + '@types/react': '*' + react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + + react-simple-animate@3.5.3: + resolution: {integrity: sha512-Ob+SmB5J1tXDEZyOe2Hf950K4M8VaWBBmQ3cS2BUnTORqHjhK0iKG8fB+bo47ZL15t8d3g/Y0roiqH05UBjG7A==} + peerDependencies: + react-dom: ^16.8.0 || ^17 || ^18 || ^19 + + react-style-singleton@2.2.3: + resolution: {integrity: sha512-b6jSvxvVnyptAiLjbkWLE/lOnR4lfTtDAl+eUC7RZy+QQWc6wRzIV2CE6xBuMmDxc2qIihtDCZD5NPOFl7fRBQ==} + engines: {node: '>=10'} + peerDependencies: + '@types/react': '*' + react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + + react-textarea-autosize@8.5.9: + resolution: {integrity: sha512-U1DGlIQN5AwgjTyOEnI1oCcMuEr1pv1qOtklB2l4nyMGbHzWrI0eFsYK0zos2YWqAolJyG0IWJaqWmWj5ETh0A==} + engines: {node: '>=10'} + peerDependencies: + react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 + + react-transition-group@4.4.5: + resolution: {integrity: sha512-pZcd1MCJoiKiBR2NRxeCRg13uCXbydPnmB4EOeRrY7480qNWO8IIgQG6zlDkm6uRMsURXPuKq0GWtiM59a5Q6g==} + peerDependencies: + react: '>=16.6.0' + react-dom: '>=16.6.0' + + react-universal-interface@0.6.2: + resolution: {integrity: sha512-dg8yXdcQmvgR13RIlZbTRQOoUrDciFVoSBZILwjE2LFISxZZ8loVJKAkuzswl5js8BHda79bIb2b84ehU8IjXw==} + peerDependencies: + react: '*' + tslib: '*' + + react-use@17.6.0: + resolution: {integrity: sha512-OmedEScUMKFfzn1Ir8dBxiLLSOzhKe/dPZwVxcujweSj45aNM7BEGPb9BEVIgVEqEXx6f3/TsXzwIktNgUR02g==} + peerDependencies: + react: '*' + react-dom: '*' + + react@19.1.0: + resolution: {integrity: sha512-FS+XFBNvn3GTAWq26joslQgWNoFu08F4kl0J4CgdNKADkdSGXQyTCnKteIAJy96Br6YbpEU1LSzV5dYtjMkMDg==} + engines: {node: '>=0.10.0'} + + reactcss@1.2.3: + resolution: {integrity: sha512-KiwVUcFu1RErkI97ywr8nvx8dNOpT03rbnma0SSalTYjkrPYaEajR4a/MRt6DZ46K6arDRbWMNHF+xH7G7n/8A==} + peerDependencies: + react: '*' + + readdirp@3.6.0: + resolution: {integrity: sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==} + engines: {node: '>=8.10.0'} + + reflect.getprototypeof@1.0.10: + resolution: {integrity: sha512-00o4I+DVrefhv+nX0ulyi3biSHCPDe+yLv5o/p6d/UVlirijB8E16FtfwSAi4g3tcqrQ4lRAqQSoFEZJehYEcw==} + engines: {node: '>= 0.4'} + + regenerator-runtime@0.14.1: + resolution: {integrity: sha512-dYnhHh0nJoMfnkZs6GmmhFknAGRrLznOu5nc9ML+EJxGvrx6H7teuevqVqCuPcPK//3eDrrjQhehXVx9cnkGdw==} + + regexp.prototype.flags@1.5.4: + resolution: {integrity: sha512-dYqgNSZbDwkaJ2ceRd9ojCGjBq+mOm9LmtXnAnEGyHhN/5R7iDW2TRw3h+o/jCFxus3P2LfWIIiwowAjANm7IA==} + engines: {node: '>= 0.4'} + + resize-observer-polyfill@1.5.1: + resolution: {integrity: sha512-LwZrotdHOo12nQuZlHEmtuXdqGoOD0OhaxopaNFxWzInpEgaLWoVuAMbTzixuosCx2nEG58ngzW3vxdWoxIgdg==} + + resolve-from@4.0.0: + resolution: {integrity: sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==} + engines: {node: '>=4'} + + resolve-pkg-maps@1.0.0: + resolution: {integrity: sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw==} + + resolve@1.22.10: + resolution: {integrity: sha512-NPRy+/ncIMeDlTAsuqwKIiferiawhefFJtkNSW0qZJEqMEb+qBt/77B/jGeeek+F0uOeN05CDa6HXbbIgtVX4w==} + engines: {node: '>= 0.4'} + hasBin: true + + resolve@2.0.0-next.5: + resolution: {integrity: sha512-U7WjGVG9sH8tvjW5SmGbQuui75FiyjAX72HX15DwBBwF9dNiQZRQAg9nnPhYy+TUnE0+VcrttuvNI8oSxZcocA==} + hasBin: true + + restore-cursor@5.1.0: + resolution: {integrity: sha512-oMA2dcrw6u0YfxJQXm342bFKX/E4sG9rbTzO9ptUcR/e8A33cHuvStiYOwH7fszkZlZ1z/ta9AAoPk2F4qIOHA==} + engines: {node: '>=18'} + + reusify@1.1.0: + resolution: {integrity: sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw==} + engines: {iojs: '>=1.0.0', node: '>=0.10.0'} + + rfdc@1.4.1: + resolution: {integrity: sha512-q1b3N5QkRUWUl7iyylaaj3kOpIT0N2i9MqIEQXP73GVsN9cw3fdx8X63cEmWhJGi2PPCF23Ijp7ktmd39rawIA==} + + rollup@4.40.0: + resolution: {integrity: sha512-Noe455xmA96nnqH5piFtLobsGbCij7Tu+tb3c1vYjNbTkfzGqXqQXG3wJaYXkRZuQ0vEYN4bhwg7QnIrqB5B+w==} + engines: {node: '>=18.0.0', npm: '>=8.0.0'} + hasBin: true + + rtl-css-js@1.16.1: + resolution: {integrity: sha512-lRQgou1mu19e+Ya0LsTvKrVJ5TYUbqCVPAiImX3UfLTenarvPUl1QFdvu5Z3PYmHT9RCcwIfbjRQBntExyj3Zg==} + + run-parallel@1.2.0: + resolution: {integrity: sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==} + + safe-array-concat@1.1.3: + resolution: {integrity: sha512-AURm5f0jYEOydBj7VQlVvDrjeFgthDdEF5H1dP+6mNpoXOMo1quQqJ4wvJDyRZ9+pO3kGWoOdmV08cSv2aJV6Q==} + engines: {node: '>=0.4'} + + safe-push-apply@1.0.0: + resolution: {integrity: sha512-iKE9w/Z7xCzUMIZqdBsp6pEQvwuEebH4vdpjcDWnyzaI6yl6O9FHvVpmGelvEHNsoY6wGblkxR6Zty/h00WiSA==} + engines: {node: '>= 0.4'} + + safe-regex-test@1.1.0: + resolution: {integrity: sha512-x/+Cz4YrimQxQccJf5mKEbIa1NzeCRNI5Ecl/ekmlYaampdNLPalVyIcCZNNH3MvmqBugV5TMYZXv0ljslUlaw==} + engines: {node: '>= 0.4'} + + safe-stable-stringify@2.5.0: + resolution: {integrity: sha512-b3rppTKm9T+PsVCBEOUR46GWI7fdOs00VKZ1+9c1EWDaDMvjQc6tUwuFyIprgGgTcWoVHSKrU8H31ZHA2e0RHA==} + engines: {node: '>=10'} + + scheduler@0.26.0: + resolution: {integrity: sha512-NlHwttCI/l5gCPR3D1nNXtWABUmBwvZpEQiD4IXSbIDq8BzLIK/7Ir5gTFSGZDUu37K5cMNp0hFtzO38sC7gWA==} + + screenfull@5.2.0: + resolution: {integrity: sha512-9BakfsO2aUQN2K9Fdbj87RJIEZ82Q9IGim7FqM5OsebfoFC6ZHXgDq/KvniuLTPdeM8wY2o6Dj3WQ7KeQCj3cA==} + engines: {node: '>=0.10.0'} + + scroll-into-view-if-needed@3.1.0: + resolution: {integrity: sha512-49oNpRjWRvnU8NyGVmUaYG4jtTkNonFZI86MmGRDqBphEK2EXT9gdEUoQPZhuBM8yWHxCWbobltqYO5M4XrUvQ==} + + semver@6.3.1: + resolution: {integrity: sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==} + hasBin: true + + semver@7.7.1: + resolution: {integrity: sha512-hlq8tAfn0m/61p4BVRcPzIGr6LKiMwo4VM6dGi6pt4qcRkmNzTcWq6eCEjEh+qXjkMDvPlOFFSGwQjoEa6gyMA==} + engines: {node: '>=10'} + hasBin: true + + seroval-plugins@1.2.1: + resolution: {integrity: sha512-H5vs53+39+x4Udwp4J5rNZfgFuA+Lt+uU+09w1gYBVWomtAl98B+E9w7yC05Xc81/HgLvJdlyqJbU0fJCKCmdw==} + engines: {node: '>=10'} + peerDependencies: + seroval: ^1.0 + + seroval@1.2.1: + resolution: {integrity: sha512-yBxFFs3zmkvKNmR0pFSU//rIsYjuX418TnlDmc2weaq5XFDqDIV/NOMPBoLrbxjLH42p4UzRuXHryXh9dYcKcw==} + engines: {node: '>=10'} + + set-function-length@1.2.2: + resolution: {integrity: sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg==} + engines: {node: '>= 0.4'} + + set-function-name@2.0.2: + resolution: {integrity: sha512-7PGFlmtwsEADb0WYyvCMa1t+yke6daIG4Wirafur5kcf+MhUnPms1UeR0CKQdTZD81yESwMHbtn+TR+dMviakQ==} + engines: {node: '>= 0.4'} + + set-harmonic-interval@1.0.1: + resolution: {integrity: sha512-AhICkFV84tBP1aWqPwLZqFvAwqEoVA9kxNMniGEUvzOlm4vLmOFLiTT3UZ6bziJTy4bOVpzWGTfSCbmaayGx8g==} + engines: {node: '>=6.9'} + + set-proto@1.0.0: + resolution: {integrity: sha512-RJRdvCo6IAnPdsvP/7m6bsQqNnn1FCBX5ZNtFL98MmFF/4xAIJTIg1YbHW5DC2W5SKZanrC6i4HsJqlajw/dZw==} + engines: {node: '>= 0.4'} + + shallowequal@1.1.0: + resolution: {integrity: sha512-y0m1JoUZSlPAjXVtPPW70aZWfIL/dSP7AFkRnniLCrK/8MDKog3TySTBmckD+RObVxH0v4Tox67+F14PdED2oQ==} + + shebang-command@2.0.0: + resolution: {integrity: sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==} + engines: {node: '>=8'} + + shebang-regex@3.0.0: + resolution: {integrity: sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==} + engines: {node: '>=8'} + + side-channel-list@1.0.0: + resolution: {integrity: sha512-FCLHtRD/gnpCiCHEiJLOwdmFP+wzCmDEkc9y7NsYxeF4u7Btsn1ZuwgwJGxImImHicJArLP4R0yX4c2KCrMrTA==} + engines: {node: '>= 0.4'} + + side-channel-map@1.0.1: + resolution: {integrity: sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA==} + engines: {node: '>= 0.4'} + + side-channel-weakmap@1.0.2: + resolution: {integrity: sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A==} + engines: {node: '>= 0.4'} + + side-channel@1.1.0: + resolution: {integrity: sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw==} + engines: {node: '>= 0.4'} + + signal-exit@4.1.0: + resolution: {integrity: sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==} + engines: {node: '>=14'} + + slice-ansi@5.0.0: + resolution: {integrity: sha512-FC+lgizVPfie0kkhqUScwRu1O/lF6NOgJmlCgK+/LYxDCTk8sGelYaHDhFcDN+Sn3Cv+3VSa4Byeo+IMCzpMgQ==} + engines: {node: '>=12'} + + slice-ansi@7.1.0: + resolution: {integrity: sha512-bSiSngZ/jWeX93BqeIAbImyTbEihizcwNjFoRUIY/T1wWQsfsm2Vw1agPKylXvQTU7iASGdHhyqRlqQzfz+Htg==} + engines: {node: '>=18'} + + snake-case@3.0.4: + resolution: {integrity: sha512-LAOh4z89bGQvl9pFfNF8V146i7o7/CqFPbqzYgP+yYzDIDeS9HaNFtXABamRW+AQzEVODcvE79ljJ+8a9YSdMg==} + + solid-js@1.9.5: + resolution: {integrity: sha512-ogI3DaFcyn6UhYhrgcyRAMbu/buBJitYQASZz5WzfQVPP10RD2AbCoRZ517psnezrasyCbWzIxZ6kVqet768xw==} + + source-map-js@1.2.1: + resolution: {integrity: sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==} + engines: {node: '>=0.10.0'} + + source-map@0.5.6: + resolution: {integrity: sha512-MjZkVp0NHr5+TPihLcadqnlVoGIoWo4IBHptutGh9wI3ttUYvCG26HkSuDi+K6lsZ25syXJXcctwgyVCt//xqA==} + engines: {node: '>=0.10.0'} + + source-map@0.5.7: + resolution: {integrity: sha512-LbrmJOMUSdEVxIKvdcJzQC+nQhe8FUZQTXQy6+I75skNgn3OoQ0DZA8YnFa7gp8tqtL3KPf1kmo0R5DoApeSGQ==} + engines: {node: '>=0.10.0'} + + source-map@0.6.1: + resolution: {integrity: sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==} + engines: {node: '>=0.10.0'} + + stack-generator@2.0.10: + resolution: {integrity: sha512-mwnua/hkqM6pF4k8SnmZ2zfETsRUpWXREfA/goT8SLCV4iOFa4bzOX2nDipWAZFPTjLvQB82f5yaodMVhK0yJQ==} + + stackframe@1.3.4: + resolution: {integrity: sha512-oeVtt7eWQS+Na6F//S4kJ2K2VbRlS9D43mAlMyVpVWovy9o+jfgH8O9agzANzaiLjclA0oYzUXEM4PurhSUChw==} + + stacktrace-gps@3.1.2: + resolution: {integrity: sha512-GcUgbO4Jsqqg6RxfyTHFiPxdPqF+3LFmQhm7MgCuYQOYuWyqxo5pwRPz5d/u6/WYJdEnWfK4r+jGbyD8TSggXQ==} + + stacktrace-js@2.0.2: + resolution: {integrity: sha512-Je5vBeY4S1r/RnLydLl0TBTi3F2qdfWmYsGvtfZgEI+SCprPppaIhQf5nGcal4gI4cGpCV/duLcAzT1np6sQqg==} + + state-local@1.0.7: + resolution: {integrity: sha512-HTEHMNieakEnoe33shBYcZ7NX83ACUjCu8c40iOGEZsngj9zRnkqS9j1pqQPXwobB0ZcVTk27REb7COQ0UR59w==} + + string-argv@0.3.2: + resolution: {integrity: sha512-aqD2Q0144Z+/RqG52NeHEkZauTAUWJO8c6yTftGJKO3Tja5tUgIfmIl6kExvhtxSDP7fXB6DvzkfMpCd/F3G+Q==} + engines: {node: '>=0.6.19'} + + string-convert@0.2.1: + resolution: {integrity: sha512-u/1tdPl4yQnPBjnVrmdLo9gtuLvELKsAoRapekWggdiQNvvvum+jYF329d84NAa660KQw7pB2n36KrIKVoXa3A==} + + string-width@7.2.0: + resolution: {integrity: sha512-tsaTIkKW9b4N+AEj+SVA+WhJzV7/zMhcSu78mLKWSk7cXMOSHsBKFWUs0fWwq8QyK3MgJBQRX6Gbi4kYbdvGkQ==} + engines: {node: '>=18'} + + string.prototype.matchall@4.0.12: + resolution: {integrity: sha512-6CC9uyBL+/48dYizRf7H7VAYCMCNTBeM78x/VTUe9bFEaxBepPJDa1Ow99LqI/1yF7kuy7Q3cQsYMrcjGUcskA==} + engines: {node: '>= 0.4'} + + string.prototype.repeat@1.0.0: + resolution: {integrity: sha512-0u/TldDbKD8bFCQ/4f5+mNRrXwZ8hg2w7ZR8wa16e8z9XpePWl3eGEcUD0OXpEH/VJH/2G3gjUtR3ZOiBe2S/w==} + + string.prototype.trim@1.2.10: + resolution: {integrity: sha512-Rs66F0P/1kedk5lyYyH9uBzuiI/kNRmwJAR9quK6VOtIpZ2G+hMZd+HQbbv25MgCA6gEffoMZYxlTod4WcdrKA==} + engines: {node: '>= 0.4'} + + string.prototype.trimend@1.0.9: + resolution: {integrity: sha512-G7Ok5C6E/j4SGfyLCloXTrngQIQU3PWtXGst3yM7Bea9FRURf1S42ZHlZZtsNque2FN2PoUhfZXYLNWwEr4dLQ==} + engines: {node: '>= 0.4'} + + string.prototype.trimstart@1.0.8: + resolution: {integrity: sha512-UXSH262CSZY1tfu3G3Secr6uGLCFVPMhIqHjlgCUtCCcgihYc/xKs9djMTMUOb2j1mVSeU8EU6NWc/iQKU6Gfg==} + engines: {node: '>= 0.4'} + + strip-ansi@7.1.0: + resolution: {integrity: sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==} + engines: {node: '>=12'} + + strip-final-newline@3.0.0: + resolution: {integrity: sha512-dOESqjYr96iWYylGObzd39EuNTa5VJxyvVAEm5Jnh7KGo75V43Hk1odPQkNDyXNmUR6k+gEiDVXnjB8HJ3crXw==} + engines: {node: '>=12'} + + strip-json-comments@3.1.1: + resolution: {integrity: sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==} + engines: {node: '>=8'} + + stylis@4.2.0: + resolution: {integrity: sha512-Orov6g6BB1sDfYgzWfTHDOxamtX1bE/zo104Dh9e6fqJ3PooipYyfJ0pUmrZO2wAvO8YbEyeFrkV91XTsGMSrw==} + + stylis@4.3.6: + resolution: {integrity: sha512-yQ3rwFWRfwNUY7H5vpU0wfdkNSnvnJinhF9830Swlaxl03zsOjCfmX0ugac+3LtK0lYSgwL/KXc8oYL3mG4YFQ==} + + sugarss@4.0.1: + resolution: {integrity: sha512-WCjS5NfuVJjkQzK10s8WOBY+hhDxxNt/N6ZaGwxFZ+wN3/lKKFSaaKUNecULcTTvE4urLcKaZFQD8vO0mOZujw==} + engines: {node: '>=12.0'} + peerDependencies: + postcss: ^8.3.3 + + supports-color@7.2.0: + resolution: {integrity: sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==} + engines: {node: '>=8'} + + supports-preserve-symlinks-flag@1.0.0: + resolution: {integrity: sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==} + engines: {node: '>= 0.4'} + + svg-parser@2.0.4: + resolution: {integrity: sha512-e4hG1hRwoOdRb37cIMSgzNsxyzKfayW6VOflrwvR+/bzrkyxY/31WkbgnQpgtrNp1SdpJvpUAGTa/ZoiPNDuRQ==} + + swr@2.3.3: + resolution: {integrity: sha512-dshNvs3ExOqtZ6kJBaAsabhPdHyeY4P2cKwRCniDVifBMoG/SVI7tfLWqPXriVspf2Rg4tPzXJTnwaihIeFw2A==} + peerDependencies: + react: ^16.11.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 + + tabbable@6.2.0: + resolution: {integrity: sha512-Cat63mxsVJlzYvN51JmVXIgNoUokrIaT2zLclCXjRd8boZ0004U4KCs/sToJ75C6sdlByWxpYnb5Boif1VSFew==} + + throttle-debounce@3.0.1: + resolution: {integrity: sha512-dTEWWNu6JmeVXY0ZYoPuH5cRIwc0MeGbJwah9KUNYSJwommQpCzTySTpEe8Gs1J23aeWEuAobe4Ag7EHVt/LOg==} + engines: {node: '>=10'} + + throttle-debounce@5.0.2: + resolution: {integrity: sha512-B71/4oyj61iNH0KeCamLuE2rmKuTO5byTOSVwECM5FA7TiAiAW+UqTKZ9ERueC4qvgSttUhdmq1mXC3kJqGX7A==} + engines: {node: '>=12.22'} + + tiny-invariant@1.3.3: + resolution: {integrity: sha512-+FbBPE1o9QAYvviau/qC5SE3caw21q3xkvWKBtja5vgqOWIHHJ3ioaq1VPfn/Szqctz2bU/oYeKd9/z5BL+PVg==} + + tiny-warning@1.0.3: + resolution: {integrity: sha512-lBN9zLN/oAf68o3zNXYrdCt1kP8WsiGW8Oo2ka41b2IM5JL/S1CTyX1rW0mb/zSuJun0ZUrDxx4sqvYS2FWzPA==} + + tinycolor2@1.6.0: + resolution: {integrity: sha512-XPaBkWQJdsf3pLKJV9p4qN/S+fm2Oj8AIPo1BTUhg5oxkvm9+SVEGFdhyOz7tTdUTfvxMiAs4sp6/eZO2Ew+pw==} + + tinyexec@0.3.2: + resolution: {integrity: sha512-KQQR9yN7R5+OSwaK0XQoj22pwHoTlgYqmUscPYoknOoWCWfj/5/ABTMRi69FrKU5ffPVh5QcFikpWJI/P1ocHA==} + + tinyglobby@0.2.13: + resolution: {integrity: sha512-mEwzpUgrLySlveBwEVDMKk5B57bhLPYovRfPAXD5gA/98Opn0rCDj3GtLwFvCvH5RK9uPCExUROW5NjDwvqkxw==} + engines: {node: '>=12.0.0'} + + to-regex-range@5.0.1: + resolution: {integrity: sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==} + engines: {node: '>=8.0'} + + toggle-selection@1.0.6: + resolution: {integrity: sha512-BiZS+C1OS8g/q2RRbJmy59xpyghNBqrr6k5L/uKBGRsTfxmu3ffiRnd8mlGPUVayg8pvfi5urfnu8TU7DVOkLQ==} + + ts-api-utils@2.1.0: + resolution: {integrity: sha512-CUgTZL1irw8u29bzrOD/nH85jqyc74D6SshFgujOIA7osm2Rz7dYH77agkx7H4FBNxDq7Cjf+IjaX/8zwFW+ZQ==} + engines: {node: '>=18.12'} + peerDependencies: + typescript: '>=4.8.4' + + ts-easing@0.2.0: + resolution: {integrity: sha512-Z86EW+fFFh/IFB1fqQ3/+7Zpf9t2ebOAxNI/V6Wo7r5gqiqtxmgTlQ1qbqQcjLKYeSHPTsEmvlJUDg/EuL0uHQ==} + + tsconfck@3.1.5: + resolution: {integrity: sha512-CLDfGgUp7XPswWnezWwsCRxNmgQjhYq3VXHM0/XIRxhVrKw0M1if9agzryh1QS3nxjCROvV+xWxoJO1YctzzWg==} + engines: {node: ^18 || >=20} + hasBin: true + peerDependencies: + typescript: ^5.0.0 + peerDependenciesMeta: + typescript: + optional: true + + tslib@2.8.1: + resolution: {integrity: sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==} + + tsx@4.19.3: + resolution: {integrity: sha512-4H8vUNGNjQ4V2EOoGw005+c+dGuPSnhpPBPHBtsZdGZBk/iJb4kguGlPWaZTZ3q5nMtFOEsY0nRDlh9PJyd6SQ==} + engines: {node: '>=18.0.0'} + hasBin: true + + type-check@0.4.0: + resolution: {integrity: sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==} + engines: {node: '>= 0.8.0'} + + type-fest@4.40.0: + resolution: {integrity: sha512-ABHZ2/tS2JkvH1PEjxFDTUWC8dB5OsIGZP4IFLhR293GqT5Y5qB1WwL2kMPYhQW9DVgVD8Hd7I8gjwPIf5GFkw==} + engines: {node: '>=16'} + + typed-array-buffer@1.0.3: + resolution: {integrity: sha512-nAYYwfY3qnzX30IkA6AQZjVbtK6duGontcQm1WSG1MD94YLqK0515GNApXkoxKOWMusVssAHWLh9SeaoefYFGw==} + engines: {node: '>= 0.4'} + + typed-array-byte-length@1.0.3: + resolution: {integrity: sha512-BaXgOuIxz8n8pIq3e7Atg/7s+DpiYrxn4vdot3w9KbnBhcRQq6o3xemQdIfynqSeXeDrF32x+WvfzmOjPiY9lg==} + engines: {node: '>= 0.4'} + + typed-array-byte-offset@1.0.4: + resolution: {integrity: sha512-bTlAFB/FBYMcuX81gbL4OcpH5PmlFHqlCCpAl8AlEzMz5k53oNDvN8p1PNOWLEmI2x4orp3raOFB51tv9X+MFQ==} + engines: {node: '>= 0.4'} + + typed-array-length@1.0.7: + resolution: {integrity: sha512-3KS2b+kL7fsuk/eJZ7EQdnEmQoaho/r6KUef7hxvltNA5DR8NAUM+8wJMbJyZ4G9/7i3v5zPBIMN5aybAh2/Jg==} + engines: {node: '>= 0.4'} + + typescript-eslint@8.31.0: + resolution: {integrity: sha512-u+93F0sB0An8WEAPtwxVhFby573E8ckdjwUUQUj9QA4v8JAvgtoDdIyYR3XFwFHq2W1KJ1AurwJCO+w+Y1ixyQ==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + peerDependencies: + eslint: ^8.57.0 || ^9.0.0 + typescript: '>=4.8.4 <5.9.0' + + typescript@5.7.3: + resolution: {integrity: sha512-84MVSjMEHP+FQRPy3pX9sTVV/INIex71s9TL2Gm5FG/WG1SqXeKyZ0k7/blY/4FdOzI12CBy1vGc4og/eus0fw==} + engines: {node: '>=14.17'} + hasBin: true + + ufo@1.6.1: + resolution: {integrity: sha512-9a4/uxlTWJ4+a5i0ooc1rU7C7YOw3wT+UGqdeNNHWnOF9qcMBgLRS+4IYUqbczewFx4mLEig6gawh7X6mFlEkA==} + + unbox-primitive@1.1.0: + resolution: {integrity: sha512-nWJ91DjeOkej/TA8pXQ3myruKpKEYgqvpw9lz4OPHj/NWFNluYrjbz9j01CJ8yKQd2g4jFoOkINCTW2I5LEEyw==} + engines: {node: '>= 0.4'} + + undici-types@6.21.0: + resolution: {integrity: sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==} + + unplugin-icons@22.1.0: + resolution: {integrity: sha512-ect2ZNtk1Zgwb0NVHd0C1IDW/MV+Jk/xaq4t8o6rYdVS3+L660ZdD5kTSQZvsgdwCvquRw+/wYn75hsweRjoIA==} + peerDependencies: + '@svgr/core': '>=7.0.0' + '@svgx/core': ^1.0.1 + '@vue/compiler-sfc': ^3.0.2 || ^2.7.0 + svelte: ^3.0.0 || ^4.0.0 || ^5.0.0 + vue-template-compiler: ^2.6.12 + vue-template-es2015-compiler: ^1.9.0 + peerDependenciesMeta: + '@svgr/core': + optional: true + '@svgx/core': + optional: true + '@vue/compiler-sfc': + optional: true + svelte: + optional: true + vue-template-compiler: + optional: true + vue-template-es2015-compiler: + optional: true + + unplugin@2.3.2: + resolution: {integrity: sha512-3n7YA46rROb3zSj8fFxtxC/PqoyvYQ0llwz9wtUPUutr9ig09C8gGo5CWCwHrUzlqC1LLR43kxp5vEIyH1ac1w==} + engines: {node: '>=18.12.0'} + + update-browserslist-db@1.1.3: + resolution: {integrity: sha512-UxhIZQ+QInVdunkDAaiazvvT/+fXL5Osr0JZlJulepYu6Jd7qJtDZjlur0emRlT71EN3ScPoE7gvsuIKKNavKw==} + hasBin: true + peerDependencies: + browserslist: '>= 4.21.0' + + uri-js@4.4.1: + resolution: {integrity: sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==} + + use-callback-ref@1.3.3: + resolution: {integrity: sha512-jQL3lRnocaFtu3V00JToYz/4QkNWswxijDaCVNZRiRTO3HQDLsdu1ZtmIUvV4yPp+rvWm5j0y0TG/S61cuijTg==} + engines: {node: '>=10'} + peerDependencies: + '@types/react': '*' + react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + + use-composed-ref@1.4.0: + resolution: {integrity: sha512-djviaxuOOh7wkj0paeO1Q/4wMZ8Zrnag5H6yBvzN7AKKe8beOaED9SF5/ByLqsku8NP4zQqsvM2u3ew/tJK8/w==} + peerDependencies: + '@types/react': '*' + react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 + peerDependenciesMeta: + '@types/react': + optional: true + + use-deep-compare-effect@1.8.1: + resolution: {integrity: sha512-kbeNVZ9Zkc0RFGpfMN3MNfaKNvcLNyxOAAd9O4CBZ+kCBXXscn9s/4I+8ytUER4RDpEYs5+O6Rs4PqiZ+rHr5Q==} + engines: {node: '>=10', npm: '>=6'} + peerDependencies: + react: '>=16.13' + + use-isomorphic-layout-effect@1.2.0: + resolution: {integrity: sha512-q6ayo8DWoPZT0VdG4u3D3uxcgONP3Mevx2i2b0434cwWBoL+aelL1DzkXI6w3PhTZzUeR2kaVlZn70iCiseP6w==} + peerDependencies: + '@types/react': '*' + react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 + peerDependenciesMeta: + '@types/react': + optional: true + + use-latest@1.3.0: + resolution: {integrity: sha512-mhg3xdm9NaM8q+gLT8KryJPnRFOz1/5XPBhmDEVZK1webPzDjrPk7f/mbpeLqTgB9msytYWANxgALOCJKnLvcQ==} + peerDependencies: + '@types/react': '*' + react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 + peerDependenciesMeta: + '@types/react': + optional: true + + use-sidecar@1.1.3: + resolution: {integrity: sha512-Fedw0aZvkhynoPYlA5WXrMCAMm+nSWdZt6lzJQ7Ok8S6Q+VsHmHpRWndVRJ8Be0ZbkfPc5LRYH+5XrzXcEeLRQ==} + engines: {node: '>=10'} + peerDependencies: + '@types/react': '*' + react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + + use-sync-external-store@1.5.0: + resolution: {integrity: sha512-Rb46I4cGGVBmjamjphe8L/UnvJD+uPPtTkNvX5mZgqdbavhI4EbgIWJiIHXJ8bc/i9EQGPRh4DwEURJ552Do0A==} + peerDependencies: + react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 + + util-deprecate@1.0.2: + resolution: {integrity: sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==} + + uuid@8.3.2: + resolution: {integrity: sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==} + hasBin: true + + vite-tsconfig-paths@5.1.4: + resolution: {integrity: sha512-cYj0LRuLV2c2sMqhqhGpaO3LretdtMn/BVX4cPLanIZuwwrkVl+lK84E/miEXkCHWXuq65rhNN4rXsBcOB3S4w==} + peerDependencies: + vite: '*' + peerDependenciesMeta: + vite: + optional: true + + vite@6.3.2: + resolution: {integrity: sha512-ZSvGOXKGceizRQIZSz7TGJ0pS3QLlVY/9hwxVh17W3re67je1RKYzFHivZ/t0tubU78Vkyb9WnHPENSBCzbckg==} + engines: {node: ^18.0.0 || ^20.0.0 || >=22.0.0} + hasBin: true + peerDependencies: + '@types/node': ^18.0.0 || ^20.0.0 || >=22.0.0 + jiti: '>=1.21.0' + less: '*' + lightningcss: ^1.21.0 + sass: '*' + sass-embedded: '*' + stylus: '*' + sugarss: '*' + terser: ^5.16.0 + tsx: ^4.8.1 + yaml: ^2.4.2 + peerDependenciesMeta: + '@types/node': + optional: true + jiti: + optional: true + less: + optional: true + lightningcss: + optional: true + sass: + optional: true + sass-embedded: + optional: true + stylus: + optional: true + sugarss: + optional: true + terser: + optional: true + tsx: + optional: true + yaml: + optional: true + + void-elements@3.1.0: + resolution: {integrity: sha512-Dhxzh5HZuiHQhbvTW9AMetFfBHDMYpo23Uo9btPXgdYP+3T5S+p+jgNy7spra+veYhBP2dCSgxR/i2Y02h5/6w==} + engines: {node: '>=0.10.0'} + + warning@4.0.3: + resolution: {integrity: sha512-rpJyN222KWIvHJ/F53XSZv0Zl/accqHR8et1kpaMTD/fLCRxtV8iX8czMzY7sVZupTI3zcUTg8eycS2kNF9l6w==} + + webpack-virtual-modules@0.6.2: + resolution: {integrity: sha512-66/V2i5hQanC51vBQKPH4aI8NMAcBW59FVBs+rC7eGHupMyfn34q7rZIE+ETlJ+XTevqfUhVVBgSUNSW2flEUQ==} + + which-boxed-primitive@1.1.1: + resolution: {integrity: sha512-TbX3mj8n0odCBFVlY8AxkqcHASw3L60jIuF8jFP78az3C2YhmGvqbHBpAjTRH2/xqYunrJ9g1jSyjCjpoWzIAA==} + engines: {node: '>= 0.4'} + + which-builtin-type@1.2.1: + resolution: {integrity: sha512-6iBczoX+kDQ7a3+YJBnh3T+KZRxM/iYNPXicqk66/Qfm1b93iu+yOImkg0zHbj5LNOcNv1TEADiZ0xa34B4q6Q==} + engines: {node: '>= 0.4'} + + which-collection@1.0.2: + resolution: {integrity: sha512-K4jVyjnBdgvc86Y6BkaLZEN933SwYOuBFkdmBu9ZfkcAbdVbpITnDmjvZ/aQjRXQrv5EPkTnD1s39GiiqbngCw==} + engines: {node: '>= 0.4'} + + which-typed-array@1.1.19: + resolution: {integrity: sha512-rEvr90Bck4WZt9HHFC4DJMsjvu7x+r6bImz0/BrbWb7A2djJ8hnZMrWnHo9F8ssv0OMErasDhftrfROTyqSDrw==} + engines: {node: '>= 0.4'} + + which@2.0.2: + resolution: {integrity: sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==} + engines: {node: '>= 8'} + hasBin: true + + word-wrap@1.2.5: + resolution: {integrity: sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==} + engines: {node: '>=0.10.0'} + + wrap-ansi@9.0.0: + resolution: {integrity: sha512-G8ura3S+3Z2G+mkgNRq8dqaFZAuxfsxpBB8OCTGRTCtp+l/v9nbFNmCUP1BZMts3G1142MsZfn6eeUKrr4PD1Q==} + engines: {node: '>=18'} + + yallist@3.1.1: + resolution: {integrity: sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==} + + yaml@1.10.2: + resolution: {integrity: sha512-r3vXyErRCYJ7wg28yvBY5VSoAF8ZvlcW9/BwUzEtUsjvX/DKs24dIkuwjtuprwJJHsbyUbLApepYTR1BN4uHrg==} + engines: {node: '>= 6'} + + yaml@2.7.1: + resolution: {integrity: sha512-10ULxpnOCQXxJvBgxsn9ptjq6uviG/htZKk9veJGhlqn3w/DxQ631zFF+nlQXLwmImeS5amR2dl2U8sg6U9jsQ==} + engines: {node: '>= 14'} + hasBin: true + + yocto-queue@0.1.0: + resolution: {integrity: sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==} + engines: {node: '>=10'} + + zod-empty@1.4.4: + resolution: {integrity: sha512-TXHS6P/HoFsF4pft4d/6LvQ2meCkgMrAtdG7Hz9RzGt3qBjTCWK+A616MmJBSxzP3mCp8EXxyMIyA1vVzyEJgA==} + engines: {node: '>=20.10.0', npm: '>=8.1.0'} + peerDependencies: + typescript: 5.x + zod: 3.x + + zod@3.24.3: + resolution: {integrity: sha512-HhY1oqzWCQWuUqvBFnsyrtZRhyPeR7SUGv+C4+MsisMuVfSPx8HpwWqH8tRahSlt6M3PiFAcoeFhZAqIXTxoSg==} + +snapshots: + + '@ampproject/remapping@2.3.0': + dependencies: + '@jridgewell/gen-mapping': 0.3.8 + '@jridgewell/trace-mapping': 0.3.25 + + '@ant-design/colors@7.2.0': + dependencies: + '@ant-design/fast-color': 2.0.6 + + '@ant-design/cssinjs-utils@1.1.3(react-dom@19.1.0(react@19.1.0))(react@19.1.0)': + dependencies: + '@ant-design/cssinjs': 1.23.0(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + '@babel/runtime': 7.27.0 + rc-util: 5.44.4(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + react: 19.1.0 + react-dom: 19.1.0(react@19.1.0) + + '@ant-design/cssinjs@1.23.0(react-dom@19.1.0(react@19.1.0))(react@19.1.0)': + dependencies: + '@babel/runtime': 7.27.0 + '@emotion/hash': 0.8.0 + '@emotion/unitless': 0.7.5 + classnames: 2.5.1 + csstype: 3.1.3 + rc-util: 5.44.4(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + react: 19.1.0 + react-dom: 19.1.0(react@19.1.0) + stylis: 4.3.6 + + '@ant-design/fast-color@2.0.6': + dependencies: + '@babel/runtime': 7.27.0 + + '@ant-design/icons-svg@4.4.2': {} + + '@ant-design/icons@5.6.1(react-dom@19.1.0(react@19.1.0))(react@19.1.0)': + dependencies: + '@ant-design/colors': 7.2.0 + '@ant-design/icons-svg': 4.4.2 + '@babel/runtime': 7.27.0 + classnames: 2.5.1 + rc-util: 5.44.4(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + react: 19.1.0 + react-dom: 19.1.0(react@19.1.0) + + '@ant-design/pro-card@2.9.7(antd@5.24.8(react-dom@19.1.0(react@19.1.0))(react@19.1.0))(react-dom@19.1.0(react@19.1.0))(react@19.1.0)': + dependencies: + '@ant-design/cssinjs': 1.23.0(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + '@ant-design/icons': 5.6.1(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + '@ant-design/pro-provider': 2.15.4(antd@5.24.8(react-dom@19.1.0(react@19.1.0))(react@19.1.0))(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + '@ant-design/pro-utils': 2.17.0(antd@5.24.8(react-dom@19.1.0(react@19.1.0))(react@19.1.0))(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + '@babel/runtime': 7.27.0 + antd: 5.24.8(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + classnames: 2.5.1 + rc-resize-observer: 1.4.3(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + rc-util: 5.44.4(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + react: 19.1.0 + transitivePeerDependencies: + - react-dom + + '@ant-design/pro-components@2.8.7(antd@5.24.8(react-dom@19.1.0(react@19.1.0))(react@19.1.0))(rc-field-form@2.7.0(react-dom@19.1.0(react@19.1.0))(react@19.1.0))(react-dom@19.1.0(react@19.1.0))(react@19.1.0)': + dependencies: + '@ant-design/pro-card': 2.9.7(antd@5.24.8(react-dom@19.1.0(react@19.1.0))(react@19.1.0))(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + '@ant-design/pro-descriptions': 2.6.7(antd@5.24.8(react-dom@19.1.0(react@19.1.0))(react@19.1.0))(rc-field-form@2.7.0(react-dom@19.1.0(react@19.1.0))(react@19.1.0))(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + '@ant-design/pro-field': 3.0.4(antd@5.24.8(react-dom@19.1.0(react@19.1.0))(react@19.1.0))(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + '@ant-design/pro-form': 2.31.7(antd@5.24.8(react-dom@19.1.0(react@19.1.0))(react@19.1.0))(rc-field-form@2.7.0(react-dom@19.1.0(react@19.1.0))(react@19.1.0))(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + '@ant-design/pro-layout': 7.22.4(antd@5.24.8(react-dom@19.1.0(react@19.1.0))(react@19.1.0))(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + '@ant-design/pro-list': 2.6.7(antd@5.24.8(react-dom@19.1.0(react@19.1.0))(react@19.1.0))(rc-field-form@2.7.0(react-dom@19.1.0(react@19.1.0))(react@19.1.0))(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + '@ant-design/pro-provider': 2.15.4(antd@5.24.8(react-dom@19.1.0(react@19.1.0))(react@19.1.0))(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + '@ant-design/pro-skeleton': 2.2.1(antd@5.24.8(react-dom@19.1.0(react@19.1.0))(react@19.1.0))(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + '@ant-design/pro-table': 3.19.0(antd@5.24.8(react-dom@19.1.0(react@19.1.0))(react@19.1.0))(rc-field-form@2.7.0(react-dom@19.1.0(react@19.1.0))(react@19.1.0))(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + '@ant-design/pro-utils': 2.17.0(antd@5.24.8(react-dom@19.1.0(react@19.1.0))(react@19.1.0))(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + '@babel/runtime': 7.27.0 + antd: 5.24.8(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + react: 19.1.0 + react-dom: 19.1.0(react@19.1.0) + transitivePeerDependencies: + - rc-field-form + + '@ant-design/pro-descriptions@2.6.7(antd@5.24.8(react-dom@19.1.0(react@19.1.0))(react@19.1.0))(rc-field-form@2.7.0(react-dom@19.1.0(react@19.1.0))(react@19.1.0))(react-dom@19.1.0(react@19.1.0))(react@19.1.0)': + dependencies: + '@ant-design/pro-field': 3.0.4(antd@5.24.8(react-dom@19.1.0(react@19.1.0))(react@19.1.0))(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + '@ant-design/pro-form': 2.31.7(antd@5.24.8(react-dom@19.1.0(react@19.1.0))(react@19.1.0))(rc-field-form@2.7.0(react-dom@19.1.0(react@19.1.0))(react@19.1.0))(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + '@ant-design/pro-provider': 2.15.4(antd@5.24.8(react-dom@19.1.0(react@19.1.0))(react@19.1.0))(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + '@ant-design/pro-skeleton': 2.2.1(antd@5.24.8(react-dom@19.1.0(react@19.1.0))(react@19.1.0))(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + '@ant-design/pro-utils': 2.17.0(antd@5.24.8(react-dom@19.1.0(react@19.1.0))(react@19.1.0))(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + '@babel/runtime': 7.27.0 + antd: 5.24.8(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + rc-resize-observer: 0.2.6(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + rc-util: 5.44.4(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + react: 19.1.0 + transitivePeerDependencies: + - rc-field-form + - react-dom + + '@ant-design/pro-field@3.0.4(antd@5.24.8(react-dom@19.1.0(react@19.1.0))(react@19.1.0))(react-dom@19.1.0(react@19.1.0))(react@19.1.0)': + dependencies: + '@ant-design/icons': 5.6.1(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + '@ant-design/pro-provider': 2.15.4(antd@5.24.8(react-dom@19.1.0(react@19.1.0))(react@19.1.0))(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + '@ant-design/pro-utils': 2.17.0(antd@5.24.8(react-dom@19.1.0(react@19.1.0))(react@19.1.0))(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + '@babel/runtime': 7.27.0 + '@chenshuai2144/sketch-color': 1.0.9(react@19.1.0) + antd: 5.24.8(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + classnames: 2.5.1 + dayjs: 1.11.13 + lodash: 4.17.21 + lodash-es: 4.17.21 + rc-util: 5.44.4(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + react: 19.1.0 + swr: 2.3.3(react@19.1.0) + transitivePeerDependencies: + - react-dom + + '@ant-design/pro-form@2.31.7(antd@5.24.8(react-dom@19.1.0(react@19.1.0))(react@19.1.0))(rc-field-form@2.7.0(react-dom@19.1.0(react@19.1.0))(react@19.1.0))(react-dom@19.1.0(react@19.1.0))(react@19.1.0)': + dependencies: + '@ant-design/icons': 5.6.1(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + '@ant-design/pro-field': 3.0.4(antd@5.24.8(react-dom@19.1.0(react@19.1.0))(react@19.1.0))(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + '@ant-design/pro-provider': 2.15.4(antd@5.24.8(react-dom@19.1.0(react@19.1.0))(react@19.1.0))(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + '@ant-design/pro-utils': 2.17.0(antd@5.24.8(react-dom@19.1.0(react@19.1.0))(react@19.1.0))(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + '@babel/runtime': 7.27.0 + '@chenshuai2144/sketch-color': 1.0.9(react@19.1.0) + '@umijs/use-params': 1.0.9(react@19.1.0) + antd: 5.24.8(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + classnames: 2.5.1 + dayjs: 1.11.13 + lodash: 4.17.21 + lodash-es: 4.17.21 + rc-field-form: 2.7.0(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + rc-resize-observer: 1.4.3(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + rc-util: 5.44.4(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + react: 19.1.0 + react-dom: 19.1.0(react@19.1.0) + + '@ant-design/pro-layout@7.22.4(antd@5.24.8(react-dom@19.1.0(react@19.1.0))(react@19.1.0))(react-dom@19.1.0(react@19.1.0))(react@19.1.0)': + dependencies: + '@ant-design/cssinjs': 1.23.0(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + '@ant-design/icons': 5.6.1(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + '@ant-design/pro-provider': 2.15.4(antd@5.24.8(react-dom@19.1.0(react@19.1.0))(react@19.1.0))(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + '@ant-design/pro-utils': 2.17.0(antd@5.24.8(react-dom@19.1.0(react@19.1.0))(react@19.1.0))(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + '@babel/runtime': 7.27.0 + '@umijs/route-utils': 4.0.1 + '@umijs/use-params': 1.0.9(react@19.1.0) + antd: 5.24.8(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + classnames: 2.5.1 + lodash: 4.17.21 + lodash-es: 4.17.21 + path-to-regexp: 8.2.0 + rc-resize-observer: 1.4.3(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + rc-util: 5.44.4(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + react: 19.1.0 + react-dom: 19.1.0(react@19.1.0) + swr: 2.3.3(react@19.1.0) + warning: 4.0.3 + + '@ant-design/pro-list@2.6.7(antd@5.24.8(react-dom@19.1.0(react@19.1.0))(react@19.1.0))(rc-field-form@2.7.0(react-dom@19.1.0(react@19.1.0))(react@19.1.0))(react-dom@19.1.0(react@19.1.0))(react@19.1.0)': + dependencies: + '@ant-design/cssinjs': 1.23.0(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + '@ant-design/icons': 5.6.1(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + '@ant-design/pro-card': 2.9.7(antd@5.24.8(react-dom@19.1.0(react@19.1.0))(react@19.1.0))(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + '@ant-design/pro-field': 3.0.4(antd@5.24.8(react-dom@19.1.0(react@19.1.0))(react@19.1.0))(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + '@ant-design/pro-table': 3.19.0(antd@5.24.8(react-dom@19.1.0(react@19.1.0))(react@19.1.0))(rc-field-form@2.7.0(react-dom@19.1.0(react@19.1.0))(react@19.1.0))(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + '@ant-design/pro-utils': 2.17.0(antd@5.24.8(react-dom@19.1.0(react@19.1.0))(react@19.1.0))(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + '@babel/runtime': 7.27.0 + antd: 5.24.8(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + classnames: 2.5.1 + dayjs: 1.11.13 + rc-resize-observer: 1.4.3(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + rc-util: 4.21.1 + react: 19.1.0 + react-dom: 19.1.0(react@19.1.0) + transitivePeerDependencies: + - rc-field-form + + '@ant-design/pro-provider@2.15.4(antd@5.24.8(react-dom@19.1.0(react@19.1.0))(react@19.1.0))(react-dom@19.1.0(react@19.1.0))(react@19.1.0)': + dependencies: + '@ant-design/cssinjs': 1.23.0(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + '@babel/runtime': 7.27.0 + '@ctrl/tinycolor': 3.6.1 + antd: 5.24.8(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + dayjs: 1.11.13 + rc-util: 5.44.4(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + react: 19.1.0 + react-dom: 19.1.0(react@19.1.0) + swr: 2.3.3(react@19.1.0) + + '@ant-design/pro-skeleton@2.2.1(antd@5.24.8(react-dom@19.1.0(react@19.1.0))(react@19.1.0))(react-dom@19.1.0(react@19.1.0))(react@19.1.0)': + dependencies: + '@babel/runtime': 7.27.0 + antd: 5.24.8(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + react: 19.1.0 + react-dom: 19.1.0(react@19.1.0) + + '@ant-design/pro-table@3.19.0(antd@5.24.8(react-dom@19.1.0(react@19.1.0))(react@19.1.0))(rc-field-form@2.7.0(react-dom@19.1.0(react@19.1.0))(react@19.1.0))(react-dom@19.1.0(react@19.1.0))(react@19.1.0)': + dependencies: + '@ant-design/cssinjs': 1.23.0(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + '@ant-design/icons': 5.6.1(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + '@ant-design/pro-card': 2.9.7(antd@5.24.8(react-dom@19.1.0(react@19.1.0))(react@19.1.0))(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + '@ant-design/pro-field': 3.0.4(antd@5.24.8(react-dom@19.1.0(react@19.1.0))(react@19.1.0))(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + '@ant-design/pro-form': 2.31.7(antd@5.24.8(react-dom@19.1.0(react@19.1.0))(react@19.1.0))(rc-field-form@2.7.0(react-dom@19.1.0(react@19.1.0))(react@19.1.0))(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + '@ant-design/pro-provider': 2.15.4(antd@5.24.8(react-dom@19.1.0(react@19.1.0))(react@19.1.0))(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + '@ant-design/pro-utils': 2.17.0(antd@5.24.8(react-dom@19.1.0(react@19.1.0))(react@19.1.0))(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + '@babel/runtime': 7.27.0 + '@dnd-kit/core': 6.3.1(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + '@dnd-kit/modifiers': 6.0.1(@dnd-kit/core@6.3.1(react-dom@19.1.0(react@19.1.0))(react@19.1.0))(react@19.1.0) + '@dnd-kit/sortable': 7.0.2(@dnd-kit/core@6.3.1(react-dom@19.1.0(react@19.1.0))(react@19.1.0))(react@19.1.0) + '@dnd-kit/utilities': 3.2.2(react@19.1.0) + antd: 5.24.8(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + classnames: 2.5.1 + dayjs: 1.11.13 + lodash: 4.17.21 + lodash-es: 4.17.21 + rc-field-form: 2.7.0(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + rc-resize-observer: 1.4.3(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + rc-util: 5.44.4(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + react: 19.1.0 + react-dom: 19.1.0(react@19.1.0) + + '@ant-design/pro-utils@2.17.0(antd@5.24.8(react-dom@19.1.0(react@19.1.0))(react@19.1.0))(react-dom@19.1.0(react@19.1.0))(react@19.1.0)': + dependencies: + '@ant-design/icons': 5.6.1(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + '@ant-design/pro-provider': 2.15.4(antd@5.24.8(react-dom@19.1.0(react@19.1.0))(react@19.1.0))(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + '@babel/runtime': 7.27.0 + antd: 5.24.8(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + classnames: 2.5.1 + dayjs: 1.11.13 + lodash: 4.17.21 + lodash-es: 4.17.21 + rc-util: 5.44.4(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + react: 19.1.0 + react-dom: 19.1.0(react@19.1.0) + safe-stable-stringify: 2.5.0 + swr: 2.3.3(react@19.1.0) + + '@ant-design/react-slick@1.1.2(react@19.1.0)': + dependencies: + '@babel/runtime': 7.27.0 + classnames: 2.5.1 + json2mq: 0.2.0 + react: 19.1.0 + resize-observer-polyfill: 1.5.1 + throttle-debounce: 5.0.2 + + '@ant-design/v5-patch-for-react-19@1.0.3(antd@5.24.8(react-dom@19.1.0(react@19.1.0))(react@19.1.0))(react-dom@19.1.0(react@19.1.0))(react@19.1.0)': + dependencies: + antd: 5.24.8(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + react: 19.1.0 + react-dom: 19.1.0(react@19.1.0) + + '@antfu/install-pkg@1.0.0': + dependencies: + package-manager-detector: 0.2.11 + tinyexec: 0.3.2 + + '@antfu/utils@8.1.1': {} + + '@babel/code-frame@7.26.2': + dependencies: + '@babel/helper-validator-identifier': 7.25.9 + js-tokens: 4.0.0 + picocolors: 1.1.1 + + '@babel/compat-data@7.26.8': {} + + '@babel/core@7.26.10': + dependencies: + '@ampproject/remapping': 2.3.0 + '@babel/code-frame': 7.26.2 + '@babel/generator': 7.27.0 + '@babel/helper-compilation-targets': 7.27.0 + '@babel/helper-module-transforms': 7.26.0(@babel/core@7.26.10) + '@babel/helpers': 7.27.0 + '@babel/parser': 7.27.0 + '@babel/template': 7.27.0 + '@babel/traverse': 7.27.0 + '@babel/types': 7.27.0 + convert-source-map: 2.0.0 + debug: 4.4.0 + gensync: 1.0.0-beta.2 + json5: 2.2.3 + semver: 6.3.1 + transitivePeerDependencies: + - supports-color + + '@babel/generator@7.27.0': + dependencies: + '@babel/parser': 7.27.0 + '@babel/types': 7.27.0 + '@jridgewell/gen-mapping': 0.3.8 + '@jridgewell/trace-mapping': 0.3.25 + jsesc: 3.1.0 + + '@babel/helper-compilation-targets@7.27.0': + dependencies: + '@babel/compat-data': 7.26.8 + '@babel/helper-validator-option': 7.25.9 + browserslist: 4.24.4 + lru-cache: 5.1.1 + semver: 6.3.1 + + '@babel/helper-module-imports@7.25.9': + dependencies: + '@babel/traverse': 7.27.0 + '@babel/types': 7.27.0 + transitivePeerDependencies: + - supports-color + + '@babel/helper-module-transforms@7.26.0(@babel/core@7.26.10)': + dependencies: + '@babel/core': 7.26.10 + '@babel/helper-module-imports': 7.25.9 + '@babel/helper-validator-identifier': 7.25.9 + '@babel/traverse': 7.27.0 + transitivePeerDependencies: + - supports-color + + '@babel/helper-plugin-utils@7.26.5': {} + + '@babel/helper-string-parser@7.25.9': {} + + '@babel/helper-validator-identifier@7.25.9': {} + + '@babel/helper-validator-option@7.25.9': {} + + '@babel/helpers@7.27.0': + dependencies: + '@babel/template': 7.27.0 + '@babel/types': 7.27.0 + + '@babel/parser@7.27.0': + dependencies: + '@babel/types': 7.27.0 + + '@babel/plugin-syntax-jsx@7.25.9(@babel/core@7.26.10)': + dependencies: + '@babel/core': 7.26.10 + '@babel/helper-plugin-utils': 7.26.5 + + '@babel/plugin-syntax-typescript@7.25.9(@babel/core@7.26.10)': + dependencies: + '@babel/core': 7.26.10 + '@babel/helper-plugin-utils': 7.26.5 + + '@babel/runtime@7.27.0': + dependencies: + regenerator-runtime: 0.14.1 + + '@babel/template@7.27.0': + dependencies: + '@babel/code-frame': 7.26.2 + '@babel/parser': 7.27.0 + '@babel/types': 7.27.0 + + '@babel/traverse@7.27.0': + dependencies: + '@babel/code-frame': 7.26.2 + '@babel/generator': 7.27.0 + '@babel/parser': 7.27.0 + '@babel/template': 7.27.0 + '@babel/types': 7.27.0 + debug: 4.4.0 + globals: 11.12.0 + transitivePeerDependencies: + - supports-color + + '@babel/types@7.27.0': + dependencies: + '@babel/helper-string-parser': 7.25.9 + '@babel/helper-validator-identifier': 7.25.9 + + '@chenshuai2144/sketch-color@1.0.9(react@19.1.0)': + dependencies: + react: 19.1.0 + reactcss: 1.2.3(react@19.1.0) + tinycolor2: 1.6.0 + + '@ctrl/tinycolor@3.6.1': {} + + '@dnd-kit/accessibility@3.1.1(react@19.1.0)': + dependencies: + react: 19.1.0 + tslib: 2.8.1 + + '@dnd-kit/core@6.3.1(react-dom@19.1.0(react@19.1.0))(react@19.1.0)': + dependencies: + '@dnd-kit/accessibility': 3.1.1(react@19.1.0) + '@dnd-kit/utilities': 3.2.2(react@19.1.0) + react: 19.1.0 + react-dom: 19.1.0(react@19.1.0) + tslib: 2.8.1 + + '@dnd-kit/modifiers@6.0.1(@dnd-kit/core@6.3.1(react-dom@19.1.0(react@19.1.0))(react@19.1.0))(react@19.1.0)': + dependencies: + '@dnd-kit/core': 6.3.1(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + '@dnd-kit/utilities': 3.2.2(react@19.1.0) + react: 19.1.0 + tslib: 2.8.1 + + '@dnd-kit/sortable@7.0.2(@dnd-kit/core@6.3.1(react-dom@19.1.0(react@19.1.0))(react@19.1.0))(react@19.1.0)': + dependencies: + '@dnd-kit/core': 6.3.1(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + '@dnd-kit/utilities': 3.2.2(react@19.1.0) + react: 19.1.0 + tslib: 2.8.1 + + '@dnd-kit/utilities@3.2.2(react@19.1.0)': + dependencies: + react: 19.1.0 + tslib: 2.8.1 + + '@emotion/babel-plugin@11.13.5': + dependencies: + '@babel/helper-module-imports': 7.25.9 + '@babel/runtime': 7.27.0 + '@emotion/hash': 0.9.2 + '@emotion/memoize': 0.9.0 + '@emotion/serialize': 1.3.3 + babel-plugin-macros: 3.1.0 + convert-source-map: 1.9.0 + escape-string-regexp: 4.0.0 + find-root: 1.1.0 + source-map: 0.5.7 + stylis: 4.2.0 + transitivePeerDependencies: + - supports-color + + '@emotion/cache@11.14.0': + dependencies: + '@emotion/memoize': 0.9.0 + '@emotion/sheet': 1.4.0 + '@emotion/utils': 1.4.2 + '@emotion/weak-memoize': 0.4.0 + stylis: 4.2.0 + + '@emotion/hash@0.8.0': {} + + '@emotion/hash@0.9.2': {} + + '@emotion/is-prop-valid@1.3.1': + dependencies: + '@emotion/memoize': 0.9.0 + + '@emotion/memoize@0.9.0': {} + + '@emotion/react@11.14.0(@types/react@19.1.2)(react@19.1.0)': + dependencies: + '@babel/runtime': 7.27.0 + '@emotion/babel-plugin': 11.13.5 + '@emotion/cache': 11.14.0 + '@emotion/serialize': 1.3.3 + '@emotion/use-insertion-effect-with-fallbacks': 1.2.0(react@19.1.0) + '@emotion/utils': 1.4.2 + '@emotion/weak-memoize': 0.4.0 + hoist-non-react-statics: 3.3.2 + react: 19.1.0 + optionalDependencies: + '@types/react': 19.1.2 + transitivePeerDependencies: + - supports-color + + '@emotion/serialize@1.3.3': + dependencies: + '@emotion/hash': 0.9.2 + '@emotion/memoize': 0.9.0 + '@emotion/unitless': 0.10.0 + '@emotion/utils': 1.4.2 + csstype: 3.1.3 + + '@emotion/sheet@1.4.0': {} + + '@emotion/styled@11.14.0(@emotion/react@11.14.0(@types/react@19.1.2)(react@19.1.0))(@types/react@19.1.2)(react@19.1.0)': + dependencies: + '@babel/runtime': 7.27.0 + '@emotion/babel-plugin': 11.13.5 + '@emotion/is-prop-valid': 1.3.1 + '@emotion/react': 11.14.0(@types/react@19.1.2)(react@19.1.0) + '@emotion/serialize': 1.3.3 + '@emotion/use-insertion-effect-with-fallbacks': 1.2.0(react@19.1.0) + '@emotion/utils': 1.4.2 + react: 19.1.0 + optionalDependencies: + '@types/react': 19.1.2 + transitivePeerDependencies: + - supports-color + + '@emotion/unitless@0.10.0': {} + + '@emotion/unitless@0.7.5': {} + + '@emotion/use-insertion-effect-with-fallbacks@1.2.0(react@19.1.0)': + dependencies: + react: 19.1.0 + + '@emotion/utils@1.4.2': {} + + '@emotion/weak-memoize@0.4.0': {} + + '@esbuild/aix-ppc64@0.25.2': + optional: true + + '@esbuild/android-arm64@0.25.2': + optional: true + + '@esbuild/android-arm@0.25.2': + optional: true + + '@esbuild/android-x64@0.25.2': + optional: true + + '@esbuild/darwin-arm64@0.25.2': + optional: true + + '@esbuild/darwin-x64@0.25.2': + optional: true + + '@esbuild/freebsd-arm64@0.25.2': + optional: true + + '@esbuild/freebsd-x64@0.25.2': + optional: true + + '@esbuild/linux-arm64@0.25.2': + optional: true + + '@esbuild/linux-arm@0.25.2': + optional: true + + '@esbuild/linux-ia32@0.25.2': + optional: true + + '@esbuild/linux-loong64@0.25.2': + optional: true + + '@esbuild/linux-mips64el@0.25.2': + optional: true + + '@esbuild/linux-ppc64@0.25.2': + optional: true + + '@esbuild/linux-riscv64@0.25.2': + optional: true + + '@esbuild/linux-s390x@0.25.2': + optional: true + + '@esbuild/linux-x64@0.25.2': + optional: true + + '@esbuild/netbsd-arm64@0.25.2': + optional: true + + '@esbuild/netbsd-x64@0.25.2': + optional: true + + '@esbuild/openbsd-arm64@0.25.2': + optional: true + + '@esbuild/openbsd-x64@0.25.2': + optional: true + + '@esbuild/sunos-x64@0.25.2': + optional: true + + '@esbuild/win32-arm64@0.25.2': + optional: true + + '@esbuild/win32-ia32@0.25.2': + optional: true + + '@esbuild/win32-x64@0.25.2': + optional: true + + '@eslint-community/eslint-utils@4.6.1(eslint@9.25.1)': + dependencies: + eslint: 9.25.1 + eslint-visitor-keys: 3.4.3 + + '@eslint-community/regexpp@4.12.1': {} + + '@eslint/config-array@0.20.0': + dependencies: + '@eslint/object-schema': 2.1.6 + debug: 4.4.0 + minimatch: 3.1.2 + transitivePeerDependencies: + - supports-color + + '@eslint/config-helpers@0.2.1': {} + + '@eslint/core@0.13.0': + dependencies: + '@types/json-schema': 7.0.15 + + '@eslint/eslintrc@3.3.1': + dependencies: + ajv: 6.12.6 + debug: 4.4.0 + espree: 10.3.0 + globals: 14.0.0 + ignore: 5.3.2 + import-fresh: 3.3.1 + js-yaml: 4.1.0 + minimatch: 3.1.2 + strip-json-comments: 3.1.1 + transitivePeerDependencies: + - supports-color + + '@eslint/js@9.25.1': {} + + '@eslint/object-schema@2.1.6': {} + + '@eslint/plugin-kit@0.2.8': + dependencies: + '@eslint/core': 0.13.0 + levn: 0.4.1 + + '@floating-ui/core@1.6.9': + dependencies: + '@floating-ui/utils': 0.2.9 + + '@floating-ui/dom@1.6.13': + dependencies: + '@floating-ui/core': 1.6.9 + '@floating-ui/utils': 0.2.9 + + '@floating-ui/react-dom@2.1.2(react-dom@19.1.0(react@19.1.0))(react@19.1.0)': + dependencies: + '@floating-ui/dom': 1.6.13 + react: 19.1.0 + react-dom: 19.1.0(react@19.1.0) + + '@floating-ui/react@0.26.28(react-dom@19.1.0(react@19.1.0))(react@19.1.0)': + dependencies: + '@floating-ui/react-dom': 2.1.2(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + '@floating-ui/utils': 0.2.9 + react: 19.1.0 + react-dom: 19.1.0(react@19.1.0) + tabbable: 6.2.0 + + '@floating-ui/utils@0.2.9': {} + + '@hookform/devtools@4.4.0(@types/react@19.1.2)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)': + dependencies: + '@emotion/react': 11.14.0(@types/react@19.1.2)(react@19.1.0) + '@emotion/styled': 11.14.0(@emotion/react@11.14.0(@types/react@19.1.2)(react@19.1.0))(@types/react@19.1.2)(react@19.1.0) + '@types/lodash': 4.17.16 + little-state-machine: 4.8.1(react@19.1.0) + lodash: 4.17.21 + react: 19.1.0 + react-dom: 19.1.0(react@19.1.0) + react-simple-animate: 3.5.3(react-dom@19.1.0(react@19.1.0)) + use-deep-compare-effect: 1.8.1(react@19.1.0) + uuid: 8.3.2 + transitivePeerDependencies: + - '@types/react' + - supports-color + + '@hookform/resolvers@5.0.1(react-hook-form@7.56.1(react@19.1.0))': + dependencies: + '@standard-schema/utils': 0.3.0 + react-hook-form: 7.56.1(react@19.1.0) + + '@humanfs/core@0.19.1': {} + + '@humanfs/node@0.16.6': + dependencies: + '@humanfs/core': 0.19.1 + '@humanwhocodes/retry': 0.3.1 + + '@humanwhocodes/module-importer@1.0.1': {} + + '@humanwhocodes/retry@0.3.1': {} + + '@humanwhocodes/retry@0.4.2': {} + + '@iconify-json/material-symbols@1.2.20': + dependencies: + '@iconify/types': 2.0.0 + + '@iconify/types@2.0.0': {} + + '@iconify/utils@2.3.0': + dependencies: + '@antfu/install-pkg': 1.0.0 + '@antfu/utils': 8.1.1 + '@iconify/types': 2.0.0 + debug: 4.4.0 + globals: 15.15.0 + kolorist: 1.8.0 + local-pkg: 1.1.1 + mlly: 1.7.4 + transitivePeerDependencies: + - supports-color + + '@jridgewell/gen-mapping@0.3.8': + dependencies: + '@jridgewell/set-array': 1.2.1 + '@jridgewell/sourcemap-codec': 1.5.0 + '@jridgewell/trace-mapping': 0.3.25 + + '@jridgewell/resolve-uri@3.1.2': {} + + '@jridgewell/set-array@1.2.1': {} + + '@jridgewell/sourcemap-codec@1.5.0': {} + + '@jridgewell/trace-mapping@0.3.25': + dependencies: + '@jridgewell/resolve-uri': 3.1.2 + '@jridgewell/sourcemap-codec': 1.5.0 + + '@mantine/core@7.17.4(@mantine/hooks@7.17.4(react@19.1.0))(@types/react@19.1.2)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)': + dependencies: + '@floating-ui/react': 0.26.28(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + '@mantine/hooks': 7.17.4(react@19.1.0) + clsx: 2.1.1 + react: 19.1.0 + react-dom: 19.1.0(react@19.1.0) + react-number-format: 5.4.4(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + react-remove-scroll: 2.6.3(@types/react@19.1.2)(react@19.1.0) + react-textarea-autosize: 8.5.9(@types/react@19.1.2)(react@19.1.0) + type-fest: 4.40.0 + transitivePeerDependencies: + - '@types/react' + + '@mantine/hooks@7.17.4(react@19.1.0)': + dependencies: + react: 19.1.0 + + '@mantine/modals@7.17.4(@mantine/core@7.17.4(@mantine/hooks@7.17.4(react@19.1.0))(@types/react@19.1.2)(react-dom@19.1.0(react@19.1.0))(react@19.1.0))(@mantine/hooks@7.17.4(react@19.1.0))(react-dom@19.1.0(react@19.1.0))(react@19.1.0)': + dependencies: + '@mantine/core': 7.17.4(@mantine/hooks@7.17.4(react@19.1.0))(@types/react@19.1.2)(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + '@mantine/hooks': 7.17.4(react@19.1.0) + react: 19.1.0 + react-dom: 19.1.0(react@19.1.0) + + '@mantine/notifications@7.17.4(@mantine/core@7.17.4(@mantine/hooks@7.17.4(react@19.1.0))(@types/react@19.1.2)(react-dom@19.1.0(react@19.1.0))(react@19.1.0))(@mantine/hooks@7.17.4(react@19.1.0))(react-dom@19.1.0(react@19.1.0))(react@19.1.0)': + dependencies: + '@mantine/core': 7.17.4(@mantine/hooks@7.17.4(react@19.1.0))(@types/react@19.1.2)(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + '@mantine/hooks': 7.17.4(react@19.1.0) + '@mantine/store': 7.17.4(react@19.1.0) + react: 19.1.0 + react-dom: 19.1.0(react@19.1.0) + react-transition-group: 4.4.5(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + + '@mantine/store@7.17.4(react@19.1.0)': + dependencies: + react: 19.1.0 + + '@monaco-editor/loader@1.5.0': + dependencies: + state-local: 1.0.7 + + '@monaco-editor/react@4.7.0(monaco-editor@0.52.2)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)': + dependencies: + '@monaco-editor/loader': 1.5.0 + monaco-editor: 0.52.2 + react: 19.1.0 + react-dom: 19.1.0(react@19.1.0) + + '@nodelib/fs.scandir@2.1.5': + dependencies: + '@nodelib/fs.stat': 2.0.5 + run-parallel: 1.2.0 + + '@nodelib/fs.stat@2.0.5': {} + + '@nodelib/fs.walk@1.2.8': + dependencies: + '@nodelib/fs.scandir': 2.1.5 + fastq: 1.19.1 + + '@rc-component/async-validator@5.0.4': + dependencies: + '@babel/runtime': 7.27.0 + + '@rc-component/color-picker@2.0.1(react-dom@19.1.0(react@19.1.0))(react@19.1.0)': + dependencies: + '@ant-design/fast-color': 2.0.6 + '@babel/runtime': 7.27.0 + classnames: 2.5.1 + rc-util: 5.44.4(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + react: 19.1.0 + react-dom: 19.1.0(react@19.1.0) + + '@rc-component/context@1.4.0(react-dom@19.1.0(react@19.1.0))(react@19.1.0)': + dependencies: + '@babel/runtime': 7.27.0 + rc-util: 5.44.4(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + react: 19.1.0 + react-dom: 19.1.0(react@19.1.0) + + '@rc-component/mini-decimal@1.1.0': + dependencies: + '@babel/runtime': 7.27.0 + + '@rc-component/mutate-observer@1.1.0(react-dom@19.1.0(react@19.1.0))(react@19.1.0)': + dependencies: + '@babel/runtime': 7.27.0 + classnames: 2.5.1 + rc-util: 5.44.4(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + react: 19.1.0 + react-dom: 19.1.0(react@19.1.0) + + '@rc-component/portal@1.1.2(react-dom@19.1.0(react@19.1.0))(react@19.1.0)': + dependencies: + '@babel/runtime': 7.27.0 + classnames: 2.5.1 + rc-util: 5.44.4(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + react: 19.1.0 + react-dom: 19.1.0(react@19.1.0) + + '@rc-component/qrcode@1.0.0(react-dom@19.1.0(react@19.1.0))(react@19.1.0)': + dependencies: + '@babel/runtime': 7.27.0 + classnames: 2.5.1 + rc-util: 5.44.4(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + react: 19.1.0 + react-dom: 19.1.0(react@19.1.0) + + '@rc-component/tour@1.15.1(react-dom@19.1.0(react@19.1.0))(react@19.1.0)': + dependencies: + '@babel/runtime': 7.27.0 + '@rc-component/portal': 1.1.2(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + '@rc-component/trigger': 2.2.6(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + classnames: 2.5.1 + rc-util: 5.44.4(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + react: 19.1.0 + react-dom: 19.1.0(react@19.1.0) + + '@rc-component/trigger@2.2.6(react-dom@19.1.0(react@19.1.0))(react@19.1.0)': + dependencies: + '@babel/runtime': 7.27.0 + '@rc-component/portal': 1.1.2(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + classnames: 2.5.1 + rc-motion: 2.9.5(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + rc-resize-observer: 1.4.3(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + rc-util: 5.44.4(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + react: 19.1.0 + react-dom: 19.1.0(react@19.1.0) + + '@rollup/rollup-android-arm-eabi@4.40.0': + optional: true + + '@rollup/rollup-android-arm64@4.40.0': + optional: true + + '@rollup/rollup-darwin-arm64@4.40.0': + optional: true + + '@rollup/rollup-darwin-x64@4.40.0': + optional: true + + '@rollup/rollup-freebsd-arm64@4.40.0': + optional: true + + '@rollup/rollup-freebsd-x64@4.40.0': + optional: true + + '@rollup/rollup-linux-arm-gnueabihf@4.40.0': + optional: true + + '@rollup/rollup-linux-arm-musleabihf@4.40.0': + optional: true + + '@rollup/rollup-linux-arm64-gnu@4.40.0': + optional: true + + '@rollup/rollup-linux-arm64-musl@4.40.0': + optional: true + + '@rollup/rollup-linux-loongarch64-gnu@4.40.0': + optional: true + + '@rollup/rollup-linux-powerpc64le-gnu@4.40.0': + optional: true + + '@rollup/rollup-linux-riscv64-gnu@4.40.0': + optional: true + + '@rollup/rollup-linux-riscv64-musl@4.40.0': + optional: true + + '@rollup/rollup-linux-s390x-gnu@4.40.0': + optional: true + + '@rollup/rollup-linux-x64-gnu@4.40.0': + optional: true + + '@rollup/rollup-linux-x64-musl@4.40.0': + optional: true + + '@rollup/rollup-win32-arm64-msvc@4.40.0': + optional: true + + '@rollup/rollup-win32-ia32-msvc@4.40.0': + optional: true + + '@rollup/rollup-win32-x64-msvc@4.40.0': + optional: true + + '@standard-schema/utils@0.3.0': {} + + '@svgr/babel-plugin-add-jsx-attribute@8.0.0(@babel/core@7.26.10)': + dependencies: + '@babel/core': 7.26.10 + + '@svgr/babel-plugin-remove-jsx-attribute@8.0.0(@babel/core@7.26.10)': + dependencies: + '@babel/core': 7.26.10 + + '@svgr/babel-plugin-remove-jsx-empty-expression@8.0.0(@babel/core@7.26.10)': + dependencies: + '@babel/core': 7.26.10 + + '@svgr/babel-plugin-replace-jsx-attribute-value@8.0.0(@babel/core@7.26.10)': + dependencies: + '@babel/core': 7.26.10 + + '@svgr/babel-plugin-svg-dynamic-title@8.0.0(@babel/core@7.26.10)': + dependencies: + '@babel/core': 7.26.10 + + '@svgr/babel-plugin-svg-em-dimensions@8.0.0(@babel/core@7.26.10)': + dependencies: + '@babel/core': 7.26.10 + + '@svgr/babel-plugin-transform-react-native-svg@8.1.0(@babel/core@7.26.10)': + dependencies: + '@babel/core': 7.26.10 + + '@svgr/babel-plugin-transform-svg-component@8.0.0(@babel/core@7.26.10)': + dependencies: + '@babel/core': 7.26.10 + + '@svgr/babel-preset@8.1.0(@babel/core@7.26.10)': + dependencies: + '@babel/core': 7.26.10 + '@svgr/babel-plugin-add-jsx-attribute': 8.0.0(@babel/core@7.26.10) + '@svgr/babel-plugin-remove-jsx-attribute': 8.0.0(@babel/core@7.26.10) + '@svgr/babel-plugin-remove-jsx-empty-expression': 8.0.0(@babel/core@7.26.10) + '@svgr/babel-plugin-replace-jsx-attribute-value': 8.0.0(@babel/core@7.26.10) + '@svgr/babel-plugin-svg-dynamic-title': 8.0.0(@babel/core@7.26.10) + '@svgr/babel-plugin-svg-em-dimensions': 8.0.0(@babel/core@7.26.10) + '@svgr/babel-plugin-transform-react-native-svg': 8.1.0(@babel/core@7.26.10) + '@svgr/babel-plugin-transform-svg-component': 8.0.0(@babel/core@7.26.10) + + '@svgr/core@8.1.0(typescript@5.7.3)': + dependencies: + '@babel/core': 7.26.10 + '@svgr/babel-preset': 8.1.0(@babel/core@7.26.10) + camelcase: 6.3.0 + cosmiconfig: 8.3.6(typescript@5.7.3) + snake-case: 3.0.4 + transitivePeerDependencies: + - supports-color + - typescript + + '@svgr/hast-util-to-babel-ast@8.0.0': + dependencies: + '@babel/types': 7.27.0 + entities: 4.5.0 + + '@svgr/plugin-jsx@8.1.0(@svgr/core@8.1.0(typescript@5.7.3))': + dependencies: + '@babel/core': 7.26.10 + '@svgr/babel-preset': 8.1.0(@babel/core@7.26.10) + '@svgr/core': 8.1.0(typescript@5.7.3) + '@svgr/hast-util-to-babel-ast': 8.0.0 + svg-parser: 2.0.4 + transitivePeerDependencies: + - supports-color + + '@swc/core-darwin-arm64@1.11.21': + optional: true + + '@swc/core-darwin-x64@1.11.21': + optional: true + + '@swc/core-linux-arm-gnueabihf@1.11.21': + optional: true + + '@swc/core-linux-arm64-gnu@1.11.21': + optional: true + + '@swc/core-linux-arm64-musl@1.11.21': + optional: true + + '@swc/core-linux-x64-gnu@1.11.21': + optional: true + + '@swc/core-linux-x64-musl@1.11.21': + optional: true + + '@swc/core-win32-arm64-msvc@1.11.21': + optional: true + + '@swc/core-win32-ia32-msvc@1.11.21': + optional: true + + '@swc/core-win32-x64-msvc@1.11.21': + optional: true + + '@swc/core@1.11.21': + dependencies: + '@swc/counter': 0.1.3 + '@swc/types': 0.1.21 + optionalDependencies: + '@swc/core-darwin-arm64': 1.11.21 + '@swc/core-darwin-x64': 1.11.21 + '@swc/core-linux-arm-gnueabihf': 1.11.21 + '@swc/core-linux-arm64-gnu': 1.11.21 + '@swc/core-linux-arm64-musl': 1.11.21 + '@swc/core-linux-x64-gnu': 1.11.21 + '@swc/core-linux-x64-musl': 1.11.21 + '@swc/core-win32-arm64-msvc': 1.11.21 + '@swc/core-win32-ia32-msvc': 1.11.21 + '@swc/core-win32-x64-msvc': 1.11.21 + + '@swc/counter@0.1.3': {} + + '@swc/types@0.1.21': + dependencies: + '@swc/counter': 0.1.3 + + '@tanstack/history@1.115.0': {} + + '@tanstack/query-core@5.74.4': {} + + '@tanstack/query-devtools@5.73.3': {} + + '@tanstack/react-query-devtools@5.74.4(@tanstack/react-query@5.74.4(react@19.1.0))(react@19.1.0)': + dependencies: + '@tanstack/query-devtools': 5.73.3 + '@tanstack/react-query': 5.74.4(react@19.1.0) + react: 19.1.0 + + '@tanstack/react-query@5.74.4(react@19.1.0)': + dependencies: + '@tanstack/query-core': 5.74.4 + react: 19.1.0 + + '@tanstack/react-router-devtools@1.116.0(@tanstack/react-router@1.116.0(react-dom@19.1.0(react@19.1.0))(react@19.1.0))(@tanstack/router-core@1.115.3)(csstype@3.1.3)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)(tiny-invariant@1.3.3)': + dependencies: + '@tanstack/react-router': 1.116.0(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + '@tanstack/router-devtools-core': 1.115.3(@tanstack/router-core@1.115.3)(csstype@3.1.3)(solid-js@1.9.5)(tiny-invariant@1.3.3) + react: 19.1.0 + react-dom: 19.1.0(react@19.1.0) + solid-js: 1.9.5 + transitivePeerDependencies: + - '@tanstack/router-core' + - csstype + - tiny-invariant + + '@tanstack/react-router@1.116.0(react-dom@19.1.0(react@19.1.0))(react@19.1.0)': + dependencies: + '@tanstack/history': 1.115.0 + '@tanstack/react-store': 0.7.0(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + '@tanstack/router-core': 1.115.3 + jsesc: 3.1.0 + react: 19.1.0 + react-dom: 19.1.0(react@19.1.0) + tiny-invariant: 1.3.3 + tiny-warning: 1.0.3 + + '@tanstack/react-store@0.7.0(react-dom@19.1.0(react@19.1.0))(react@19.1.0)': + dependencies: + '@tanstack/store': 0.7.0 + react: 19.1.0 + react-dom: 19.1.0(react@19.1.0) + use-sync-external-store: 1.5.0(react@19.1.0) + + '@tanstack/router-core@1.115.3': + dependencies: + '@tanstack/history': 1.115.0 + '@tanstack/store': 0.7.0 + tiny-invariant: 1.3.3 + + '@tanstack/router-devtools-core@1.115.3(@tanstack/router-core@1.115.3)(csstype@3.1.3)(solid-js@1.9.5)(tiny-invariant@1.3.3)': + dependencies: + '@tanstack/router-core': 1.115.3 + clsx: 2.1.1 + goober: 2.1.16(csstype@3.1.3) + solid-js: 1.9.5 + tiny-invariant: 1.3.3 + optionalDependencies: + csstype: 3.1.3 + + '@tanstack/router-generator@1.116.0(@tanstack/react-router@1.116.0(react-dom@19.1.0(react@19.1.0))(react@19.1.0))': + dependencies: + '@tanstack/virtual-file-routes': 1.115.0 + prettier: 3.5.3 + tsx: 4.19.3 + zod: 3.24.3 + optionalDependencies: + '@tanstack/react-router': 1.116.0(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + + '@tanstack/router-plugin@1.116.1(@tanstack/react-router@1.116.0(react-dom@19.1.0(react@19.1.0))(react@19.1.0))(vite@6.3.2(@types/node@22.14.1)(sugarss@4.0.1(postcss@8.5.3))(tsx@4.19.3)(yaml@2.7.1))': + dependencies: + '@babel/core': 7.26.10 + '@babel/plugin-syntax-jsx': 7.25.9(@babel/core@7.26.10) + '@babel/plugin-syntax-typescript': 7.25.9(@babel/core@7.26.10) + '@babel/template': 7.27.0 + '@babel/traverse': 7.27.0 + '@babel/types': 7.27.0 + '@tanstack/router-core': 1.115.3 + '@tanstack/router-generator': 1.116.0(@tanstack/react-router@1.116.0(react-dom@19.1.0(react@19.1.0))(react@19.1.0)) + '@tanstack/router-utils': 1.115.0 + '@tanstack/virtual-file-routes': 1.115.0 + '@types/babel__core': 7.20.5 + '@types/babel__template': 7.4.4 + '@types/babel__traverse': 7.20.7 + babel-dead-code-elimination: 1.0.10 + chokidar: 3.6.0 + unplugin: 2.3.2 + zod: 3.24.3 + optionalDependencies: + '@tanstack/react-router': 1.116.0(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + vite: 6.3.2(@types/node@22.14.1)(sugarss@4.0.1(postcss@8.5.3))(tsx@4.19.3)(yaml@2.7.1) + transitivePeerDependencies: + - supports-color + + '@tanstack/router-utils@1.115.0': + dependencies: + '@babel/generator': 7.27.0 + '@babel/parser': 7.27.0 + ansis: 3.17.0 + diff: 7.0.0 + + '@tanstack/store@0.7.0': {} + + '@tanstack/virtual-file-routes@1.115.0': {} + + '@types/babel__core@7.20.5': + dependencies: + '@babel/parser': 7.27.0 + '@babel/types': 7.27.0 + '@types/babel__generator': 7.27.0 + '@types/babel__template': 7.4.4 + '@types/babel__traverse': 7.20.7 + + '@types/babel__generator@7.27.0': + dependencies: + '@babel/types': 7.27.0 + + '@types/babel__template@7.4.4': + dependencies: + '@babel/parser': 7.27.0 + '@babel/types': 7.27.0 + + '@types/babel__traverse@7.20.7': + dependencies: + '@babel/types': 7.27.0 + + '@types/estree@1.0.7': {} + + '@types/js-cookie@2.2.7': {} + + '@types/json-schema@7.0.15': {} + + '@types/lodash@4.17.16': {} + + '@types/node@22.14.1': + dependencies: + undici-types: 6.21.0 + + '@types/parse-json@4.0.2': {} + + '@types/qs@6.9.18': {} + + '@types/react-dom@19.1.2(@types/react@19.1.2)': + dependencies: + '@types/react': 19.1.2 + + '@types/react@19.1.2': + dependencies: + csstype: 3.1.3 + + '@typescript-eslint/eslint-plugin@8.31.0(@typescript-eslint/parser@8.31.0(eslint@9.25.1)(typescript@5.7.3))(eslint@9.25.1)(typescript@5.7.3)': + dependencies: + '@eslint-community/regexpp': 4.12.1 + '@typescript-eslint/parser': 8.31.0(eslint@9.25.1)(typescript@5.7.3) + '@typescript-eslint/scope-manager': 8.31.0 + '@typescript-eslint/type-utils': 8.31.0(eslint@9.25.1)(typescript@5.7.3) + '@typescript-eslint/utils': 8.31.0(eslint@9.25.1)(typescript@5.7.3) + '@typescript-eslint/visitor-keys': 8.31.0 + eslint: 9.25.1 + graphemer: 1.4.0 + ignore: 5.3.2 + natural-compare: 1.4.0 + ts-api-utils: 2.1.0(typescript@5.7.3) + typescript: 5.7.3 + transitivePeerDependencies: + - supports-color + + '@typescript-eslint/parser@8.31.0(eslint@9.25.1)(typescript@5.7.3)': + dependencies: + '@typescript-eslint/scope-manager': 8.31.0 + '@typescript-eslint/types': 8.31.0 + '@typescript-eslint/typescript-estree': 8.31.0(typescript@5.7.3) + '@typescript-eslint/visitor-keys': 8.31.0 + debug: 4.4.0 + eslint: 9.25.1 + typescript: 5.7.3 + transitivePeerDependencies: + - supports-color + + '@typescript-eslint/scope-manager@8.31.0': + dependencies: + '@typescript-eslint/types': 8.31.0 + '@typescript-eslint/visitor-keys': 8.31.0 + + '@typescript-eslint/type-utils@8.31.0(eslint@9.25.1)(typescript@5.7.3)': + dependencies: + '@typescript-eslint/typescript-estree': 8.31.0(typescript@5.7.3) + '@typescript-eslint/utils': 8.31.0(eslint@9.25.1)(typescript@5.7.3) + debug: 4.4.0 + eslint: 9.25.1 + ts-api-utils: 2.1.0(typescript@5.7.3) + typescript: 5.7.3 + transitivePeerDependencies: + - supports-color + + '@typescript-eslint/types@8.31.0': {} + + '@typescript-eslint/typescript-estree@8.31.0(typescript@5.7.3)': + dependencies: + '@typescript-eslint/types': 8.31.0 + '@typescript-eslint/visitor-keys': 8.31.0 + debug: 4.4.0 + fast-glob: 3.3.3 + is-glob: 4.0.3 + minimatch: 9.0.5 + semver: 7.7.1 + ts-api-utils: 2.1.0(typescript@5.7.3) + typescript: 5.7.3 + transitivePeerDependencies: + - supports-color + + '@typescript-eslint/utils@8.31.0(eslint@9.25.1)(typescript@5.7.3)': + dependencies: + '@eslint-community/eslint-utils': 4.6.1(eslint@9.25.1) + '@typescript-eslint/scope-manager': 8.31.0 + '@typescript-eslint/types': 8.31.0 + '@typescript-eslint/typescript-estree': 8.31.0(typescript@5.7.3) + eslint: 9.25.1 + typescript: 5.7.3 + transitivePeerDependencies: + - supports-color + + '@typescript-eslint/visitor-keys@8.31.0': + dependencies: + '@typescript-eslint/types': 8.31.0 + eslint-visitor-keys: 4.2.0 + + '@umijs/route-utils@4.0.1': {} + + '@umijs/use-params@1.0.9(react@19.1.0)': + dependencies: + react: 19.1.0 + + '@vitejs/plugin-react-swc@3.9.0(vite@6.3.2(@types/node@22.14.1)(sugarss@4.0.1(postcss@8.5.3))(tsx@4.19.3)(yaml@2.7.1))': + dependencies: + '@swc/core': 1.11.21 + vite: 6.3.2(@types/node@22.14.1)(sugarss@4.0.1(postcss@8.5.3))(tsx@4.19.3)(yaml@2.7.1) + transitivePeerDependencies: + - '@swc/helpers' + + '@xobotyi/scrollbar-width@1.9.5': {} + + acorn-jsx@5.3.2(acorn@8.14.1): + dependencies: + acorn: 8.14.1 + + acorn@8.14.1: {} + + add-dom-event-listener@1.1.0: + dependencies: + object-assign: 4.1.1 + + ajv@6.12.6: + dependencies: + fast-deep-equal: 3.1.3 + fast-json-stable-stringify: 2.1.0 + json-schema-traverse: 0.4.1 + uri-js: 4.4.1 + + ansi-escapes@7.0.0: + dependencies: + environment: 1.1.0 + + ansi-regex@6.1.0: {} + + ansi-styles@4.3.0: + dependencies: + color-convert: 2.0.1 + + ansi-styles@6.2.1: {} + + ansis@3.17.0: {} + + antd@5.24.8(react-dom@19.1.0(react@19.1.0))(react@19.1.0): + dependencies: + '@ant-design/colors': 7.2.0 + '@ant-design/cssinjs': 1.23.0(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + '@ant-design/cssinjs-utils': 1.1.3(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + '@ant-design/fast-color': 2.0.6 + '@ant-design/icons': 5.6.1(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + '@ant-design/react-slick': 1.1.2(react@19.1.0) + '@babel/runtime': 7.27.0 + '@rc-component/color-picker': 2.0.1(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + '@rc-component/mutate-observer': 1.1.0(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + '@rc-component/qrcode': 1.0.0(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + '@rc-component/tour': 1.15.1(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + '@rc-component/trigger': 2.2.6(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + classnames: 2.5.1 + copy-to-clipboard: 3.3.3 + dayjs: 1.11.13 + rc-cascader: 3.33.1(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + rc-checkbox: 3.5.0(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + rc-collapse: 3.9.0(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + rc-dialog: 9.6.0(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + rc-drawer: 7.2.0(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + rc-dropdown: 4.2.1(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + rc-field-form: 2.7.0(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + rc-image: 7.11.1(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + rc-input: 1.8.0(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + rc-input-number: 9.5.0(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + rc-mentions: 2.20.0(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + rc-menu: 9.16.1(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + rc-motion: 2.9.5(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + rc-notification: 5.6.4(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + rc-pagination: 5.1.0(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + rc-picker: 4.11.3(dayjs@1.11.13)(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + rc-progress: 4.0.0(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + rc-rate: 2.13.1(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + rc-resize-observer: 1.4.3(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + rc-segmented: 2.7.0(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + rc-select: 14.16.6(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + rc-slider: 11.1.8(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + rc-steps: 6.0.1(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + rc-switch: 4.1.0(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + rc-table: 7.50.4(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + rc-tabs: 15.6.0(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + rc-textarea: 1.10.0(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + rc-tooltip: 6.4.0(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + rc-tree: 5.13.1(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + rc-tree-select: 5.27.0(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + rc-upload: 4.8.1(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + rc-util: 5.44.4(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + react: 19.1.0 + react-dom: 19.1.0(react@19.1.0) + scroll-into-view-if-needed: 3.1.0 + throttle-debounce: 5.0.2 + transitivePeerDependencies: + - date-fns + - luxon + - moment + + anymatch@3.1.3: + dependencies: + normalize-path: 3.0.0 + picomatch: 2.3.1 + + argparse@2.0.1: {} + + array-buffer-byte-length@1.0.2: + dependencies: + call-bound: 1.0.4 + is-array-buffer: 3.0.5 + + array-includes@3.1.8: + dependencies: + call-bind: 1.0.8 + define-properties: 1.2.1 + es-abstract: 1.23.9 + es-object-atoms: 1.1.1 + get-intrinsic: 1.3.0 + is-string: 1.1.1 + + array.prototype.findlast@1.2.5: + dependencies: + call-bind: 1.0.8 + define-properties: 1.2.1 + es-abstract: 1.23.9 + es-errors: 1.3.0 + es-object-atoms: 1.1.1 + es-shim-unscopables: 1.1.0 + + array.prototype.flat@1.3.3: + dependencies: + call-bind: 1.0.8 + define-properties: 1.2.1 + es-abstract: 1.23.9 + es-shim-unscopables: 1.1.0 + + array.prototype.flatmap@1.3.3: + dependencies: + call-bind: 1.0.8 + define-properties: 1.2.1 + es-abstract: 1.23.9 + es-shim-unscopables: 1.1.0 + + array.prototype.tosorted@1.1.4: + dependencies: + call-bind: 1.0.8 + define-properties: 1.2.1 + es-abstract: 1.23.9 + es-errors: 1.3.0 + es-shim-unscopables: 1.1.0 + + arraybuffer.prototype.slice@1.0.4: + dependencies: + array-buffer-byte-length: 1.0.2 + call-bind: 1.0.8 + define-properties: 1.2.1 + es-abstract: 1.23.9 + es-errors: 1.3.0 + get-intrinsic: 1.3.0 + is-array-buffer: 3.0.5 + + async-function@1.0.0: {} + + asynckit@0.4.0: {} + + available-typed-arrays@1.0.7: + dependencies: + possible-typed-array-names: 1.1.0 + + axios@1.8.4: + dependencies: + follow-redirects: 1.15.9 + form-data: 4.0.2 + proxy-from-env: 1.1.0 + transitivePeerDependencies: + - debug + + babel-dead-code-elimination@1.0.10: + dependencies: + '@babel/core': 7.26.10 + '@babel/parser': 7.27.0 + '@babel/traverse': 7.27.0 + '@babel/types': 7.27.0 + transitivePeerDependencies: + - supports-color + + babel-plugin-macros@3.1.0: + dependencies: + '@babel/runtime': 7.27.0 + cosmiconfig: 7.1.0 + resolve: 1.22.10 + + balanced-match@1.0.2: {} + + binary-extensions@2.3.0: {} + + brace-expansion@1.1.11: + dependencies: + balanced-match: 1.0.2 + concat-map: 0.0.1 + + brace-expansion@2.0.1: + dependencies: + balanced-match: 1.0.2 + + braces@3.0.3: + dependencies: + fill-range: 7.1.1 + + browserslist@4.24.4: + dependencies: + caniuse-lite: 1.0.30001715 + electron-to-chromium: 1.5.140 + node-releases: 2.0.19 + update-browserslist-db: 1.1.3(browserslist@4.24.4) + + call-bind-apply-helpers@1.0.2: + dependencies: + es-errors: 1.3.0 + function-bind: 1.1.2 + + call-bind@1.0.8: + dependencies: + call-bind-apply-helpers: 1.0.2 + es-define-property: 1.0.1 + get-intrinsic: 1.3.0 + set-function-length: 1.2.2 + + call-bound@1.0.4: + dependencies: + call-bind-apply-helpers: 1.0.2 + get-intrinsic: 1.3.0 + + callsites@3.1.0: {} + + camelcase-css@2.0.1: {} + + camelcase@6.3.0: {} + + caniuse-lite@1.0.30001715: {} + + chalk@4.1.2: + dependencies: + ansi-styles: 4.3.0 + supports-color: 7.2.0 + + chalk@5.4.1: {} + + chokidar@3.6.0: + dependencies: + anymatch: 3.1.3 + braces: 3.0.3 + glob-parent: 5.1.2 + is-binary-path: 2.1.0 + is-glob: 4.0.3 + normalize-path: 3.0.0 + readdirp: 3.6.0 + optionalDependencies: + fsevents: 2.3.3 + + classnames@2.5.1: {} + + cli-cursor@5.0.0: + dependencies: + restore-cursor: 5.1.0 + + cli-truncate@4.0.0: + dependencies: + slice-ansi: 5.0.0 + string-width: 7.2.0 + + clsx@2.1.1: {} + + color-convert@2.0.1: + dependencies: + color-name: 1.1.4 + + color-name@1.1.4: {} + + colorette@2.0.20: {} + + combined-stream@1.0.8: + dependencies: + delayed-stream: 1.0.0 + + commander@13.1.0: {} + + compute-scroll-into-view@3.1.1: {} + + concat-map@0.0.1: {} + + confbox@0.1.8: {} + + confbox@0.2.2: {} + + convert-source-map@1.9.0: {} + + convert-source-map@2.0.0: {} + + copy-to-clipboard@3.3.3: + dependencies: + toggle-selection: 1.0.6 + + cosmiconfig@7.1.0: + dependencies: + '@types/parse-json': 4.0.2 + import-fresh: 3.3.1 + parse-json: 5.2.0 + path-type: 4.0.0 + yaml: 1.10.2 + + cosmiconfig@8.3.6(typescript@5.7.3): + dependencies: + import-fresh: 3.3.1 + js-yaml: 4.1.0 + parse-json: 5.2.0 + path-type: 4.0.0 + optionalDependencies: + typescript: 5.7.3 + + cross-spawn@7.0.6: + dependencies: + path-key: 3.1.1 + shebang-command: 2.0.0 + which: 2.0.2 + + css-in-js-utils@3.1.0: + dependencies: + hyphenate-style-name: 1.1.0 + + css-tree@1.1.3: + dependencies: + mdn-data: 2.0.14 + source-map: 0.6.1 + + cssesc@3.0.0: {} + + csstype@3.1.3: {} + + data-view-buffer@1.0.2: + dependencies: + call-bound: 1.0.4 + es-errors: 1.3.0 + is-data-view: 1.0.2 + + data-view-byte-length@1.0.2: + dependencies: + call-bound: 1.0.4 + es-errors: 1.3.0 + is-data-view: 1.0.2 + + data-view-byte-offset@1.0.1: + dependencies: + call-bound: 1.0.4 + es-errors: 1.3.0 + is-data-view: 1.0.2 + + dayjs@1.11.13: {} + + debug@4.4.0: + dependencies: + ms: 2.1.3 + + deep-is@0.1.4: {} + + define-data-property@1.1.4: + dependencies: + es-define-property: 1.0.1 + es-errors: 1.3.0 + gopd: 1.2.0 + + define-properties@1.2.1: + dependencies: + define-data-property: 1.1.4 + has-property-descriptors: 1.0.2 + object-keys: 1.1.1 + + delayed-stream@1.0.0: {} + + dequal@2.0.3: {} + + detect-node-es@1.1.0: {} + + diff@7.0.0: {} + + doctrine@2.1.0: + dependencies: + esutils: 2.0.3 + + dom-helpers@5.2.1: + dependencies: + '@babel/runtime': 7.27.0 + csstype: 3.1.3 + + dot-case@3.0.4: + dependencies: + no-case: 3.0.4 + tslib: 2.8.1 + + dunder-proto@1.0.1: + dependencies: + call-bind-apply-helpers: 1.0.2 + es-errors: 1.3.0 + gopd: 1.2.0 + + electron-to-chromium@1.5.140: {} + + emoji-regex@10.4.0: {} + + entities@4.5.0: {} + + environment@1.1.0: {} + + error-ex@1.3.2: + dependencies: + is-arrayish: 0.2.1 + + error-stack-parser@2.1.4: + dependencies: + stackframe: 1.3.4 + + es-abstract@1.23.9: + dependencies: + array-buffer-byte-length: 1.0.2 + arraybuffer.prototype.slice: 1.0.4 + available-typed-arrays: 1.0.7 + call-bind: 1.0.8 + call-bound: 1.0.4 + data-view-buffer: 1.0.2 + data-view-byte-length: 1.0.2 + data-view-byte-offset: 1.0.1 + es-define-property: 1.0.1 + es-errors: 1.3.0 + es-object-atoms: 1.1.1 + es-set-tostringtag: 2.1.0 + es-to-primitive: 1.3.0 + function.prototype.name: 1.1.8 + get-intrinsic: 1.3.0 + get-proto: 1.0.1 + get-symbol-description: 1.1.0 + globalthis: 1.0.4 + gopd: 1.2.0 + has-property-descriptors: 1.0.2 + has-proto: 1.2.0 + has-symbols: 1.1.0 + hasown: 2.0.2 + internal-slot: 1.1.0 + is-array-buffer: 3.0.5 + is-callable: 1.2.7 + is-data-view: 1.0.2 + is-regex: 1.2.1 + is-shared-array-buffer: 1.0.4 + is-string: 1.1.1 + is-typed-array: 1.1.15 + is-weakref: 1.1.1 + math-intrinsics: 1.1.0 + object-inspect: 1.13.4 + object-keys: 1.1.1 + object.assign: 4.1.7 + own-keys: 1.0.1 + regexp.prototype.flags: 1.5.4 + safe-array-concat: 1.1.3 + safe-push-apply: 1.0.0 + safe-regex-test: 1.1.0 + set-proto: 1.0.0 + string.prototype.trim: 1.2.10 + string.prototype.trimend: 1.0.9 + string.prototype.trimstart: 1.0.8 + typed-array-buffer: 1.0.3 + typed-array-byte-length: 1.0.3 + typed-array-byte-offset: 1.0.4 + typed-array-length: 1.0.7 + unbox-primitive: 1.1.0 + which-typed-array: 1.1.19 + + es-define-property@1.0.1: {} + + es-errors@1.3.0: {} + + es-iterator-helpers@1.2.1: + dependencies: + call-bind: 1.0.8 + call-bound: 1.0.4 + define-properties: 1.2.1 + es-abstract: 1.23.9 + es-errors: 1.3.0 + es-set-tostringtag: 2.1.0 + function-bind: 1.1.2 + get-intrinsic: 1.3.0 + globalthis: 1.0.4 + gopd: 1.2.0 + has-property-descriptors: 1.0.2 + has-proto: 1.2.0 + has-symbols: 1.1.0 + internal-slot: 1.1.0 + iterator.prototype: 1.1.5 + safe-array-concat: 1.1.3 + + es-object-atoms@1.1.1: + dependencies: + es-errors: 1.3.0 + + es-set-tostringtag@2.1.0: + dependencies: + es-errors: 1.3.0 + get-intrinsic: 1.3.0 + has-tostringtag: 1.0.2 + hasown: 2.0.2 + + es-shim-unscopables@1.1.0: + dependencies: + hasown: 2.0.2 + + es-to-primitive@1.3.0: + dependencies: + is-callable: 1.2.7 + is-date-object: 1.1.0 + is-symbol: 1.1.1 + + esbuild@0.25.2: + optionalDependencies: + '@esbuild/aix-ppc64': 0.25.2 + '@esbuild/android-arm': 0.25.2 + '@esbuild/android-arm64': 0.25.2 + '@esbuild/android-x64': 0.25.2 + '@esbuild/darwin-arm64': 0.25.2 + '@esbuild/darwin-x64': 0.25.2 + '@esbuild/freebsd-arm64': 0.25.2 + '@esbuild/freebsd-x64': 0.25.2 + '@esbuild/linux-arm': 0.25.2 + '@esbuild/linux-arm64': 0.25.2 + '@esbuild/linux-ia32': 0.25.2 + '@esbuild/linux-loong64': 0.25.2 + '@esbuild/linux-mips64el': 0.25.2 + '@esbuild/linux-ppc64': 0.25.2 + '@esbuild/linux-riscv64': 0.25.2 + '@esbuild/linux-s390x': 0.25.2 + '@esbuild/linux-x64': 0.25.2 + '@esbuild/netbsd-arm64': 0.25.2 + '@esbuild/netbsd-x64': 0.25.2 + '@esbuild/openbsd-arm64': 0.25.2 + '@esbuild/openbsd-x64': 0.25.2 + '@esbuild/sunos-x64': 0.25.2 + '@esbuild/win32-arm64': 0.25.2 + '@esbuild/win32-ia32': 0.25.2 + '@esbuild/win32-x64': 0.25.2 + + escalade@3.2.0: {} + + escape-string-regexp@4.0.0: {} + + eslint-plugin-react-hooks@5.2.0(eslint@9.25.1): + dependencies: + eslint: 9.25.1 + + eslint-plugin-react-refresh@0.4.20(eslint@9.25.1): + dependencies: + eslint: 9.25.1 + + eslint-plugin-react@7.37.5(eslint@9.25.1): + dependencies: + array-includes: 3.1.8 + array.prototype.findlast: 1.2.5 + array.prototype.flatmap: 1.3.3 + array.prototype.tosorted: 1.1.4 + doctrine: 2.1.0 + es-iterator-helpers: 1.2.1 + eslint: 9.25.1 + estraverse: 5.3.0 + hasown: 2.0.2 + jsx-ast-utils: 3.3.5 + minimatch: 3.1.2 + object.entries: 1.1.9 + object.fromentries: 2.0.8 + object.values: 1.2.1 + prop-types: 15.8.1 + resolve: 2.0.0-next.5 + semver: 6.3.1 + string.prototype.matchall: 4.0.12 + string.prototype.repeat: 1.0.0 + + eslint-plugin-unused-imports@4.1.4(@typescript-eslint/eslint-plugin@8.31.0(@typescript-eslint/parser@8.31.0(eslint@9.25.1)(typescript@5.7.3))(eslint@9.25.1)(typescript@5.7.3))(eslint@9.25.1): + dependencies: + eslint: 9.25.1 + optionalDependencies: + '@typescript-eslint/eslint-plugin': 8.31.0(@typescript-eslint/parser@8.31.0(eslint@9.25.1)(typescript@5.7.3))(eslint@9.25.1)(typescript@5.7.3) + + eslint-scope@8.3.0: + dependencies: + esrecurse: 4.3.0 + estraverse: 5.3.0 + + eslint-visitor-keys@3.4.3: {} + + eslint-visitor-keys@4.2.0: {} + + eslint@9.25.1: + dependencies: + '@eslint-community/eslint-utils': 4.6.1(eslint@9.25.1) + '@eslint-community/regexpp': 4.12.1 + '@eslint/config-array': 0.20.0 + '@eslint/config-helpers': 0.2.1 + '@eslint/core': 0.13.0 + '@eslint/eslintrc': 3.3.1 + '@eslint/js': 9.25.1 + '@eslint/plugin-kit': 0.2.8 + '@humanfs/node': 0.16.6 + '@humanwhocodes/module-importer': 1.0.1 + '@humanwhocodes/retry': 0.4.2 + '@types/estree': 1.0.7 + '@types/json-schema': 7.0.15 + ajv: 6.12.6 + chalk: 4.1.2 + cross-spawn: 7.0.6 + debug: 4.4.0 + escape-string-regexp: 4.0.0 + eslint-scope: 8.3.0 + eslint-visitor-keys: 4.2.0 + espree: 10.3.0 + esquery: 1.6.0 + esutils: 2.0.3 + fast-deep-equal: 3.1.3 + file-entry-cache: 8.0.0 + find-up: 5.0.0 + glob-parent: 6.0.2 + ignore: 5.3.2 + imurmurhash: 0.1.4 + is-glob: 4.0.3 + json-stable-stringify-without-jsonify: 1.0.1 + lodash.merge: 4.6.2 + minimatch: 3.1.2 + natural-compare: 1.4.0 + optionator: 0.9.4 + transitivePeerDependencies: + - supports-color + + espree@10.3.0: + dependencies: + acorn: 8.14.1 + acorn-jsx: 5.3.2(acorn@8.14.1) + eslint-visitor-keys: 4.2.0 + + esquery@1.6.0: + dependencies: + estraverse: 5.3.0 + + esrecurse@4.3.0: + dependencies: + estraverse: 5.3.0 + + estraverse@5.3.0: {} + + esutils@2.0.3: {} + + eventemitter3@5.0.1: {} + + execa@8.0.1: + dependencies: + cross-spawn: 7.0.6 + get-stream: 8.0.1 + human-signals: 5.0.0 + is-stream: 3.0.0 + merge-stream: 2.0.0 + npm-run-path: 5.3.0 + onetime: 6.0.0 + signal-exit: 4.1.0 + strip-final-newline: 3.0.0 + + exsolve@1.0.5: {} + + fast-clean@1.4.0: {} + + fast-deep-equal@3.1.3: {} + + fast-glob@3.3.3: + dependencies: + '@nodelib/fs.stat': 2.0.5 + '@nodelib/fs.walk': 1.2.8 + glob-parent: 5.1.2 + merge2: 1.4.1 + micromatch: 4.0.8 + + fast-json-stable-stringify@2.1.0: {} + + fast-levenshtein@2.0.6: {} + + fast-shallow-equal@1.0.0: {} + + fastest-stable-stringify@2.0.2: {} + + fastq@1.19.1: + dependencies: + reusify: 1.1.0 + + fdir@6.4.4(picomatch@4.0.2): + optionalDependencies: + picomatch: 4.0.2 + + file-entry-cache@8.0.0: + dependencies: + flat-cache: 4.0.1 + + fill-range@7.1.1: + dependencies: + to-regex-range: 5.0.1 + + find-root@1.1.0: {} + + find-up@5.0.0: + dependencies: + locate-path: 6.0.0 + path-exists: 4.0.0 + + flat-cache@4.0.1: + dependencies: + flatted: 3.3.3 + keyv: 4.5.4 + + flatted@3.3.3: {} + + follow-redirects@1.15.9: {} + + for-each@0.3.5: + dependencies: + is-callable: 1.2.7 + + form-data@4.0.2: + dependencies: + asynckit: 0.4.0 + combined-stream: 1.0.8 + es-set-tostringtag: 2.1.0 + mime-types: 2.1.35 + + fsevents@2.3.3: + optional: true + + function-bind@1.1.2: {} + + function.prototype.name@1.1.8: + dependencies: + call-bind: 1.0.8 + call-bound: 1.0.4 + define-properties: 1.2.1 + functions-have-names: 1.2.3 + hasown: 2.0.2 + is-callable: 1.2.7 + + functions-have-names@1.2.3: {} + + gensync@1.0.0-beta.2: {} + + get-east-asian-width@1.3.0: {} + + get-intrinsic@1.3.0: + dependencies: + call-bind-apply-helpers: 1.0.2 + es-define-property: 1.0.1 + es-errors: 1.3.0 + es-object-atoms: 1.1.1 + function-bind: 1.1.2 + get-proto: 1.0.1 + gopd: 1.2.0 + has-symbols: 1.1.0 + hasown: 2.0.2 + math-intrinsics: 1.1.0 + + get-nonce@1.0.1: {} + + get-proto@1.0.1: + dependencies: + dunder-proto: 1.0.1 + es-object-atoms: 1.1.1 + + get-stream@8.0.1: {} + + get-symbol-description@1.1.0: + dependencies: + call-bound: 1.0.4 + es-errors: 1.3.0 + get-intrinsic: 1.3.0 + + get-tsconfig@4.10.0: + dependencies: + resolve-pkg-maps: 1.0.0 + + glob-parent@5.1.2: + dependencies: + is-glob: 4.0.3 + + glob-parent@6.0.2: + dependencies: + is-glob: 4.0.3 + + globals@11.12.0: {} + + globals@14.0.0: {} + + globals@15.15.0: {} + + globals@16.0.0: {} + + globalthis@1.0.4: + dependencies: + define-properties: 1.2.1 + gopd: 1.2.0 + + globrex@0.1.2: {} + + goober@2.1.16(csstype@3.1.3): + dependencies: + csstype: 3.1.3 + + gopd@1.2.0: {} + + graphemer@1.4.0: {} + + has-bigints@1.1.0: {} + + has-flag@4.0.0: {} + + has-property-descriptors@1.0.2: + dependencies: + es-define-property: 1.0.1 + + has-proto@1.2.0: + dependencies: + dunder-proto: 1.0.1 + + has-symbols@1.1.0: {} + + has-tostringtag@1.0.2: + dependencies: + has-symbols: 1.1.0 + + hasown@2.0.2: + dependencies: + function-bind: 1.1.2 + + hoist-non-react-statics@3.3.2: + dependencies: + react-is: 16.13.1 + + html-parse-stringify@3.0.1: + dependencies: + void-elements: 3.1.0 + + human-signals@5.0.0: {} + + husky@9.1.7: {} + + hyphenate-style-name@1.1.0: {} + + i18next@25.0.1(typescript@5.7.3): + dependencies: + '@babel/runtime': 7.27.0 + optionalDependencies: + typescript: 5.7.3 + + ignore@5.3.2: {} + + immer@10.1.1: {} + + import-fresh@3.3.1: + dependencies: + parent-module: 1.0.1 + resolve-from: 4.0.0 + + imurmurhash@0.1.4: {} + + inline-style-prefixer@7.0.1: + dependencies: + css-in-js-utils: 3.1.0 + + internal-slot@1.1.0: + dependencies: + es-errors: 1.3.0 + hasown: 2.0.2 + side-channel: 1.1.0 + + is-array-buffer@3.0.5: + dependencies: + call-bind: 1.0.8 + call-bound: 1.0.4 + get-intrinsic: 1.3.0 + + is-arrayish@0.2.1: {} + + is-async-function@2.1.1: + dependencies: + async-function: 1.0.0 + call-bound: 1.0.4 + get-proto: 1.0.1 + has-tostringtag: 1.0.2 + safe-regex-test: 1.1.0 + + is-bigint@1.1.0: + dependencies: + has-bigints: 1.1.0 + + is-binary-path@2.1.0: + dependencies: + binary-extensions: 2.3.0 + + is-boolean-object@1.2.2: + dependencies: + call-bound: 1.0.4 + has-tostringtag: 1.0.2 + + is-callable@1.2.7: {} + + is-core-module@2.16.1: + dependencies: + hasown: 2.0.2 + + is-data-view@1.0.2: + dependencies: + call-bound: 1.0.4 + get-intrinsic: 1.3.0 + is-typed-array: 1.1.15 + + is-date-object@1.1.0: + dependencies: + call-bound: 1.0.4 + has-tostringtag: 1.0.2 + + is-extglob@2.1.1: {} + + is-finalizationregistry@1.1.1: + dependencies: + call-bound: 1.0.4 + + is-fullwidth-code-point@4.0.0: {} + + is-fullwidth-code-point@5.0.0: + dependencies: + get-east-asian-width: 1.3.0 + + is-generator-function@1.1.0: + dependencies: + call-bound: 1.0.4 + get-proto: 1.0.1 + has-tostringtag: 1.0.2 + safe-regex-test: 1.1.0 + + is-glob@4.0.3: + dependencies: + is-extglob: 2.1.1 + + is-map@2.0.3: {} + + is-number-object@1.1.1: + dependencies: + call-bound: 1.0.4 + has-tostringtag: 1.0.2 + + is-number@7.0.0: {} + + is-regex@1.2.1: + dependencies: + call-bound: 1.0.4 + gopd: 1.2.0 + has-tostringtag: 1.0.2 + hasown: 2.0.2 + + is-set@2.0.3: {} + + is-shared-array-buffer@1.0.4: + dependencies: + call-bound: 1.0.4 + + is-stream@3.0.0: {} + + is-string@1.1.1: + dependencies: + call-bound: 1.0.4 + has-tostringtag: 1.0.2 + + is-symbol@1.1.1: + dependencies: + call-bound: 1.0.4 + has-symbols: 1.1.0 + safe-regex-test: 1.1.0 + + is-typed-array@1.1.15: + dependencies: + which-typed-array: 1.1.19 + + is-weakmap@2.0.2: {} + + is-weakref@1.1.1: + dependencies: + call-bound: 1.0.4 + + is-weakset@2.0.4: + dependencies: + call-bound: 1.0.4 + get-intrinsic: 1.3.0 + + isarray@2.0.5: {} + + isexe@2.0.0: {} + + iterator.prototype@1.1.5: + dependencies: + define-data-property: 1.1.4 + es-object-atoms: 1.1.1 + get-intrinsic: 1.3.0 + get-proto: 1.0.1 + has-symbols: 1.1.0 + set-function-name: 2.0.2 + + js-cookie@2.2.1: {} + + js-tokens@4.0.0: {} + + js-yaml@4.1.0: + dependencies: + argparse: 2.0.1 + + jsesc@3.1.0: {} + + json-buffer@3.0.1: {} + + json-parse-even-better-errors@2.3.1: {} + + json-schema-traverse@0.4.1: {} + + json-stable-stringify-without-jsonify@1.0.1: {} + + json2mq@0.2.0: + dependencies: + string-convert: 0.2.1 + + json5@2.2.3: {} + + jsx-ast-utils@3.3.5: + dependencies: + array-includes: 3.1.8 + array.prototype.flat: 1.3.3 + object.assign: 4.1.7 + object.values: 1.2.1 + + just-clone@6.2.0: {} + + keyv@4.5.4: + dependencies: + json-buffer: 3.0.1 + + kolorist@1.8.0: {} + + levn@0.4.1: + dependencies: + prelude-ls: 1.2.1 + type-check: 0.4.0 + + lilconfig@3.1.3: {} + + lines-and-columns@1.2.4: {} + + lint-staged@15.5.1: + dependencies: + chalk: 5.4.1 + commander: 13.1.0 + debug: 4.4.0 + execa: 8.0.1 + lilconfig: 3.1.3 + listr2: 8.3.2 + micromatch: 4.0.8 + pidtree: 0.6.0 + string-argv: 0.3.2 + yaml: 2.7.1 + transitivePeerDependencies: + - supports-color + + listr2@8.3.2: + dependencies: + cli-truncate: 4.0.0 + colorette: 2.0.20 + eventemitter3: 5.0.1 + log-update: 6.1.0 + rfdc: 1.4.1 + wrap-ansi: 9.0.0 + + little-state-machine@4.8.1(react@19.1.0): + dependencies: + react: 19.1.0 + + local-pkg@1.1.1: + dependencies: + mlly: 1.7.4 + pkg-types: 2.1.0 + quansync: 0.2.10 + + locate-path@6.0.0: + dependencies: + p-locate: 5.0.0 + + lodash-es@4.17.21: {} + + lodash.merge@4.6.2: {} + + lodash@4.17.21: {} + + log-update@6.1.0: + dependencies: + ansi-escapes: 7.0.0 + cli-cursor: 5.0.0 + slice-ansi: 7.1.0 + strip-ansi: 7.1.0 + wrap-ansi: 9.0.0 + + loose-envify@1.4.0: + dependencies: + js-tokens: 4.0.0 + + lower-case@2.0.2: + dependencies: + tslib: 2.8.1 + + lru-cache@5.1.1: + dependencies: + yallist: 3.1.1 + + math-intrinsics@1.1.0: {} + + mdn-data@2.0.14: {} + + merge-stream@2.0.0: {} + + merge2@1.4.1: {} + + micromatch@4.0.8: + dependencies: + braces: 3.0.3 + picomatch: 2.3.1 + + mime-db@1.52.0: {} + + mime-types@2.1.35: + dependencies: + mime-db: 1.52.0 + + mimic-fn@4.0.0: {} + + mimic-function@5.0.1: {} + + minimatch@3.1.2: + dependencies: + brace-expansion: 1.1.11 + + minimatch@9.0.5: + dependencies: + brace-expansion: 2.0.1 + + mlly@1.7.4: + dependencies: + acorn: 8.14.1 + pathe: 2.0.3 + pkg-types: 1.3.1 + ufo: 1.6.1 + + mobx-react-lite@4.1.0(mobx@6.13.7)(react-dom@19.1.0(react@19.1.0))(react@19.1.0): + dependencies: + mobx: 6.13.7 + react: 19.1.0 + use-sync-external-store: 1.5.0(react@19.1.0) + optionalDependencies: + react-dom: 19.1.0(react@19.1.0) + + mobx@6.13.7: {} + + monaco-editor@0.52.2: {} + + ms@2.1.3: {} + + nano-css@5.6.2(react-dom@19.1.0(react@19.1.0))(react@19.1.0): + dependencies: + '@jridgewell/sourcemap-codec': 1.5.0 + css-tree: 1.1.3 + csstype: 3.1.3 + fastest-stable-stringify: 2.0.2 + inline-style-prefixer: 7.0.1 + react: 19.1.0 + react-dom: 19.1.0(react@19.1.0) + rtl-css-js: 1.16.1 + stacktrace-js: 2.0.2 + stylis: 4.3.6 + + nanoid@3.3.11: {} + + nanoid@5.1.5: {} + + natural-compare@1.4.0: {} + + no-case@3.0.4: + dependencies: + lower-case: 2.0.2 + tslib: 2.8.1 + + node-releases@2.0.19: {} + + normalize-path@3.0.0: {} + + npm-run-path@5.3.0: + dependencies: + path-key: 4.0.0 + + object-assign@4.1.1: {} + + object-inspect@1.13.4: {} + + object-keys@1.1.1: {} + + object.assign@4.1.7: + dependencies: + call-bind: 1.0.8 + call-bound: 1.0.4 + define-properties: 1.2.1 + es-object-atoms: 1.1.1 + has-symbols: 1.1.0 + object-keys: 1.1.1 + + object.entries@1.1.9: + dependencies: + call-bind: 1.0.8 + call-bound: 1.0.4 + define-properties: 1.2.1 + es-object-atoms: 1.1.1 + + object.fromentries@2.0.8: + dependencies: + call-bind: 1.0.8 + define-properties: 1.2.1 + es-abstract: 1.23.9 + es-object-atoms: 1.1.1 + + object.values@1.2.1: + dependencies: + call-bind: 1.0.8 + call-bound: 1.0.4 + define-properties: 1.2.1 + es-object-atoms: 1.1.1 + + onetime@6.0.0: + dependencies: + mimic-fn: 4.0.0 + + onetime@7.0.0: + dependencies: + mimic-function: 5.0.1 + + optionator@0.9.4: + dependencies: + deep-is: 0.1.4 + fast-levenshtein: 2.0.6 + levn: 0.4.1 + prelude-ls: 1.2.1 + type-check: 0.4.0 + word-wrap: 1.2.5 + + own-keys@1.0.1: + dependencies: + get-intrinsic: 1.3.0 + object-keys: 1.1.1 + safe-push-apply: 1.0.0 + + p-limit@3.1.0: + dependencies: + yocto-queue: 0.1.0 + + p-locate@5.0.0: + dependencies: + p-limit: 3.1.0 + + package-manager-detector@0.2.11: + dependencies: + quansync: 0.2.10 + + parent-module@1.0.1: + dependencies: + callsites: 3.1.0 + + parse-json@5.2.0: + dependencies: + '@babel/code-frame': 7.26.2 + error-ex: 1.3.2 + json-parse-even-better-errors: 2.3.1 + lines-and-columns: 1.2.4 + + path-exists@4.0.0: {} + + path-key@3.1.1: {} + + path-key@4.0.0: {} + + path-parse@1.0.7: {} + + path-to-regexp@8.2.0: {} + + path-type@4.0.0: {} + + pathe@2.0.3: {} + + picocolors@1.1.1: {} + + picomatch@2.3.1: {} + + picomatch@4.0.2: {} + + pidtree@0.6.0: {} + + pkg-types@1.3.1: + dependencies: + confbox: 0.1.8 + mlly: 1.7.4 + pathe: 2.0.3 + + pkg-types@2.1.0: + dependencies: + confbox: 0.2.2 + exsolve: 1.0.5 + pathe: 2.0.3 + + possible-typed-array-names@1.1.0: {} + + postcss-js@4.0.1(postcss@8.5.3): + dependencies: + camelcase-css: 2.0.1 + postcss: 8.5.3 + + postcss-mixins@9.0.4(postcss@8.5.3): + dependencies: + fast-glob: 3.3.3 + postcss: 8.5.3 + postcss-js: 4.0.1(postcss@8.5.3) + postcss-simple-vars: 7.0.1(postcss@8.5.3) + sugarss: 4.0.1(postcss@8.5.3) + + postcss-nested@6.2.0(postcss@8.5.3): + dependencies: + postcss: 8.5.3 + postcss-selector-parser: 6.1.2 + + postcss-preset-mantine@1.17.0(postcss@8.5.3): + dependencies: + postcss: 8.5.3 + postcss-mixins: 9.0.4(postcss@8.5.3) + postcss-nested: 6.2.0(postcss@8.5.3) + + postcss-selector-parser@6.1.2: + dependencies: + cssesc: 3.0.0 + util-deprecate: 1.0.2 + + postcss-simple-vars@7.0.1(postcss@8.5.3): + dependencies: + postcss: 8.5.3 + + postcss@8.5.3: + dependencies: + nanoid: 3.3.11 + picocolors: 1.1.1 + source-map-js: 1.2.1 + + prelude-ls@1.2.1: {} + + prettier@3.5.3: {} + + prop-types@15.8.1: + dependencies: + loose-envify: 1.4.0 + object-assign: 4.1.1 + react-is: 16.13.1 + + proxy-from-env@1.1.0: {} + + punycode@2.3.1: {} + + qs@6.14.0: + dependencies: + side-channel: 1.1.0 + + quansync@0.2.10: {} + + queue-microtask@1.2.3: {} + + rambdax@11.3.1: {} + + rc-cascader@3.33.1(react-dom@19.1.0(react@19.1.0))(react@19.1.0): + dependencies: + '@babel/runtime': 7.27.0 + classnames: 2.5.1 + rc-select: 14.16.6(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + rc-tree: 5.13.1(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + rc-util: 5.44.4(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + react: 19.1.0 + react-dom: 19.1.0(react@19.1.0) + + rc-checkbox@3.5.0(react-dom@19.1.0(react@19.1.0))(react@19.1.0): + dependencies: + '@babel/runtime': 7.27.0 + classnames: 2.5.1 + rc-util: 5.44.4(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + react: 19.1.0 + react-dom: 19.1.0(react@19.1.0) + + rc-collapse@3.9.0(react-dom@19.1.0(react@19.1.0))(react@19.1.0): + dependencies: + '@babel/runtime': 7.27.0 + classnames: 2.5.1 + rc-motion: 2.9.5(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + rc-util: 5.44.4(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + react: 19.1.0 + react-dom: 19.1.0(react@19.1.0) + + rc-dialog@9.6.0(react-dom@19.1.0(react@19.1.0))(react@19.1.0): + dependencies: + '@babel/runtime': 7.27.0 + '@rc-component/portal': 1.1.2(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + classnames: 2.5.1 + rc-motion: 2.9.5(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + rc-util: 5.44.4(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + react: 19.1.0 + react-dom: 19.1.0(react@19.1.0) + + rc-drawer@7.2.0(react-dom@19.1.0(react@19.1.0))(react@19.1.0): + dependencies: + '@babel/runtime': 7.27.0 + '@rc-component/portal': 1.1.2(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + classnames: 2.5.1 + rc-motion: 2.9.5(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + rc-util: 5.44.4(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + react: 19.1.0 + react-dom: 19.1.0(react@19.1.0) + + rc-dropdown@4.2.1(react-dom@19.1.0(react@19.1.0))(react@19.1.0): + dependencies: + '@babel/runtime': 7.27.0 + '@rc-component/trigger': 2.2.6(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + classnames: 2.5.1 + rc-util: 5.44.4(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + react: 19.1.0 + react-dom: 19.1.0(react@19.1.0) + + rc-field-form@2.7.0(react-dom@19.1.0(react@19.1.0))(react@19.1.0): + dependencies: + '@babel/runtime': 7.27.0 + '@rc-component/async-validator': 5.0.4 + rc-util: 5.44.4(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + react: 19.1.0 + react-dom: 19.1.0(react@19.1.0) + + rc-image@7.11.1(react-dom@19.1.0(react@19.1.0))(react@19.1.0): + dependencies: + '@babel/runtime': 7.27.0 + '@rc-component/portal': 1.1.2(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + classnames: 2.5.1 + rc-dialog: 9.6.0(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + rc-motion: 2.9.5(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + rc-util: 5.44.4(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + react: 19.1.0 + react-dom: 19.1.0(react@19.1.0) + + rc-input-number@9.5.0(react-dom@19.1.0(react@19.1.0))(react@19.1.0): + dependencies: + '@babel/runtime': 7.27.0 + '@rc-component/mini-decimal': 1.1.0 + classnames: 2.5.1 + rc-input: 1.8.0(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + rc-util: 5.44.4(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + react: 19.1.0 + react-dom: 19.1.0(react@19.1.0) + + rc-input@1.8.0(react-dom@19.1.0(react@19.1.0))(react@19.1.0): + dependencies: + '@babel/runtime': 7.27.0 + classnames: 2.5.1 + rc-util: 5.44.4(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + react: 19.1.0 + react-dom: 19.1.0(react@19.1.0) + + rc-mentions@2.20.0(react-dom@19.1.0(react@19.1.0))(react@19.1.0): + dependencies: + '@babel/runtime': 7.27.0 + '@rc-component/trigger': 2.2.6(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + classnames: 2.5.1 + rc-input: 1.8.0(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + rc-menu: 9.16.1(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + rc-textarea: 1.10.0(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + rc-util: 5.44.4(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + react: 19.1.0 + react-dom: 19.1.0(react@19.1.0) + + rc-menu@9.16.1(react-dom@19.1.0(react@19.1.0))(react@19.1.0): + dependencies: + '@babel/runtime': 7.27.0 + '@rc-component/trigger': 2.2.6(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + classnames: 2.5.1 + rc-motion: 2.9.5(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + rc-overflow: 1.4.1(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + rc-util: 5.44.4(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + react: 19.1.0 + react-dom: 19.1.0(react@19.1.0) + + rc-motion@2.9.5(react-dom@19.1.0(react@19.1.0))(react@19.1.0): + dependencies: + '@babel/runtime': 7.27.0 + classnames: 2.5.1 + rc-util: 5.44.4(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + react: 19.1.0 + react-dom: 19.1.0(react@19.1.0) + + rc-notification@5.6.4(react-dom@19.1.0(react@19.1.0))(react@19.1.0): + dependencies: + '@babel/runtime': 7.27.0 + classnames: 2.5.1 + rc-motion: 2.9.5(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + rc-util: 5.44.4(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + react: 19.1.0 + react-dom: 19.1.0(react@19.1.0) + + rc-overflow@1.4.1(react-dom@19.1.0(react@19.1.0))(react@19.1.0): + dependencies: + '@babel/runtime': 7.27.0 + classnames: 2.5.1 + rc-resize-observer: 1.4.3(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + rc-util: 5.44.4(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + react: 19.1.0 + react-dom: 19.1.0(react@19.1.0) + + rc-pagination@5.1.0(react-dom@19.1.0(react@19.1.0))(react@19.1.0): + dependencies: + '@babel/runtime': 7.27.0 + classnames: 2.5.1 + rc-util: 5.44.4(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + react: 19.1.0 + react-dom: 19.1.0(react@19.1.0) + + rc-picker@4.11.3(dayjs@1.11.13)(react-dom@19.1.0(react@19.1.0))(react@19.1.0): + dependencies: + '@babel/runtime': 7.27.0 + '@rc-component/trigger': 2.2.6(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + classnames: 2.5.1 + rc-overflow: 1.4.1(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + rc-resize-observer: 1.4.3(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + rc-util: 5.44.4(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + react: 19.1.0 + react-dom: 19.1.0(react@19.1.0) + optionalDependencies: + dayjs: 1.11.13 + + rc-progress@4.0.0(react-dom@19.1.0(react@19.1.0))(react@19.1.0): + dependencies: + '@babel/runtime': 7.27.0 + classnames: 2.5.1 + rc-util: 5.44.4(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + react: 19.1.0 + react-dom: 19.1.0(react@19.1.0) + + rc-rate@2.13.1(react-dom@19.1.0(react@19.1.0))(react@19.1.0): + dependencies: + '@babel/runtime': 7.27.0 + classnames: 2.5.1 + rc-util: 5.44.4(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + react: 19.1.0 + react-dom: 19.1.0(react@19.1.0) + + rc-resize-observer@0.2.6(react-dom@19.1.0(react@19.1.0))(react@19.1.0): + dependencies: + '@babel/runtime': 7.27.0 + classnames: 2.5.1 + rc-util: 5.44.4(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + react: 19.1.0 + react-dom: 19.1.0(react@19.1.0) + resize-observer-polyfill: 1.5.1 + + rc-resize-observer@1.4.3(react-dom@19.1.0(react@19.1.0))(react@19.1.0): + dependencies: + '@babel/runtime': 7.27.0 + classnames: 2.5.1 + rc-util: 5.44.4(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + react: 19.1.0 + react-dom: 19.1.0(react@19.1.0) + resize-observer-polyfill: 1.5.1 + + rc-segmented@2.7.0(react-dom@19.1.0(react@19.1.0))(react@19.1.0): + dependencies: + '@babel/runtime': 7.27.0 + classnames: 2.5.1 + rc-motion: 2.9.5(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + rc-util: 5.44.4(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + react: 19.1.0 + react-dom: 19.1.0(react@19.1.0) + + rc-select@14.16.6(react-dom@19.1.0(react@19.1.0))(react@19.1.0): + dependencies: + '@babel/runtime': 7.27.0 + '@rc-component/trigger': 2.2.6(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + classnames: 2.5.1 + rc-motion: 2.9.5(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + rc-overflow: 1.4.1(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + rc-util: 5.44.4(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + rc-virtual-list: 3.18.5(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + react: 19.1.0 + react-dom: 19.1.0(react@19.1.0) + + rc-slider@11.1.8(react-dom@19.1.0(react@19.1.0))(react@19.1.0): + dependencies: + '@babel/runtime': 7.27.0 + classnames: 2.5.1 + rc-util: 5.44.4(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + react: 19.1.0 + react-dom: 19.1.0(react@19.1.0) + + rc-steps@6.0.1(react-dom@19.1.0(react@19.1.0))(react@19.1.0): + dependencies: + '@babel/runtime': 7.27.0 + classnames: 2.5.1 + rc-util: 5.44.4(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + react: 19.1.0 + react-dom: 19.1.0(react@19.1.0) + + rc-switch@4.1.0(react-dom@19.1.0(react@19.1.0))(react@19.1.0): + dependencies: + '@babel/runtime': 7.27.0 + classnames: 2.5.1 + rc-util: 5.44.4(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + react: 19.1.0 + react-dom: 19.1.0(react@19.1.0) + + rc-table@7.50.4(react-dom@19.1.0(react@19.1.0))(react@19.1.0): + dependencies: + '@babel/runtime': 7.27.0 + '@rc-component/context': 1.4.0(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + classnames: 2.5.1 + rc-resize-observer: 1.4.3(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + rc-util: 5.44.4(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + rc-virtual-list: 3.18.5(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + react: 19.1.0 + react-dom: 19.1.0(react@19.1.0) + + rc-tabs@15.6.0(react-dom@19.1.0(react@19.1.0))(react@19.1.0): + dependencies: + '@babel/runtime': 7.27.0 + classnames: 2.5.1 + rc-dropdown: 4.2.1(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + rc-menu: 9.16.1(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + rc-motion: 2.9.5(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + rc-resize-observer: 1.4.3(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + rc-util: 5.44.4(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + react: 19.1.0 + react-dom: 19.1.0(react@19.1.0) + + rc-textarea@1.10.0(react-dom@19.1.0(react@19.1.0))(react@19.1.0): + dependencies: + '@babel/runtime': 7.27.0 + classnames: 2.5.1 + rc-input: 1.8.0(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + rc-resize-observer: 1.4.3(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + rc-util: 5.44.4(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + react: 19.1.0 + react-dom: 19.1.0(react@19.1.0) + + rc-tooltip@6.4.0(react-dom@19.1.0(react@19.1.0))(react@19.1.0): + dependencies: + '@babel/runtime': 7.27.0 + '@rc-component/trigger': 2.2.6(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + classnames: 2.5.1 + rc-util: 5.44.4(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + react: 19.1.0 + react-dom: 19.1.0(react@19.1.0) + + rc-tree-select@5.27.0(react-dom@19.1.0(react@19.1.0))(react@19.1.0): + dependencies: + '@babel/runtime': 7.27.0 + classnames: 2.5.1 + rc-select: 14.16.6(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + rc-tree: 5.13.1(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + rc-util: 5.44.4(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + react: 19.1.0 + react-dom: 19.1.0(react@19.1.0) + + rc-tree@5.13.1(react-dom@19.1.0(react@19.1.0))(react@19.1.0): + dependencies: + '@babel/runtime': 7.27.0 + classnames: 2.5.1 + rc-motion: 2.9.5(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + rc-util: 5.44.4(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + rc-virtual-list: 3.18.5(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + react: 19.1.0 + react-dom: 19.1.0(react@19.1.0) + + rc-upload@4.8.1(react-dom@19.1.0(react@19.1.0))(react@19.1.0): + dependencies: + '@babel/runtime': 7.27.0 + classnames: 2.5.1 + rc-util: 5.44.4(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + react: 19.1.0 + react-dom: 19.1.0(react@19.1.0) + + rc-util@4.21.1: + dependencies: + add-dom-event-listener: 1.1.0 + prop-types: 15.8.1 + react-is: 16.13.1 + react-lifecycles-compat: 3.0.4 + shallowequal: 1.1.0 + + rc-util@5.44.4(react-dom@19.1.0(react@19.1.0))(react@19.1.0): + dependencies: + '@babel/runtime': 7.27.0 + react: 19.1.0 + react-dom: 19.1.0(react@19.1.0) + react-is: 18.3.1 + + rc-virtual-list@3.18.5(react-dom@19.1.0(react@19.1.0))(react@19.1.0): + dependencies: + '@babel/runtime': 7.27.0 + classnames: 2.5.1 + rc-resize-observer: 1.4.3(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + rc-util: 5.44.4(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + react: 19.1.0 + react-dom: 19.1.0(react@19.1.0) + + react-dom@19.1.0(react@19.1.0): + dependencies: + react: 19.1.0 + scheduler: 0.26.0 + + react-hook-form@7.56.1(react@19.1.0): + dependencies: + react: 19.1.0 + + react-i18next@15.4.1(i18next@25.0.1(typescript@5.7.3))(react-dom@19.1.0(react@19.1.0))(react@19.1.0): + dependencies: + '@babel/runtime': 7.27.0 + html-parse-stringify: 3.0.1 + i18next: 25.0.1(typescript@5.7.3) + react: 19.1.0 + optionalDependencies: + react-dom: 19.1.0(react@19.1.0) + + react-is@16.13.1: {} + + react-is@18.3.1: {} + + react-lifecycles-compat@3.0.4: {} + + react-number-format@5.4.4(react-dom@19.1.0(react@19.1.0))(react@19.1.0): + dependencies: + react: 19.1.0 + react-dom: 19.1.0(react@19.1.0) + + react-remove-scroll-bar@2.3.8(@types/react@19.1.2)(react@19.1.0): + dependencies: + react: 19.1.0 + react-style-singleton: 2.2.3(@types/react@19.1.2)(react@19.1.0) + tslib: 2.8.1 + optionalDependencies: + '@types/react': 19.1.2 + + react-remove-scroll@2.6.3(@types/react@19.1.2)(react@19.1.0): + dependencies: + react: 19.1.0 + react-remove-scroll-bar: 2.3.8(@types/react@19.1.2)(react@19.1.0) + react-style-singleton: 2.2.3(@types/react@19.1.2)(react@19.1.0) + tslib: 2.8.1 + use-callback-ref: 1.3.3(@types/react@19.1.2)(react@19.1.0) + use-sidecar: 1.1.3(@types/react@19.1.2)(react@19.1.0) + optionalDependencies: + '@types/react': 19.1.2 + + react-simple-animate@3.5.3(react-dom@19.1.0(react@19.1.0)): + dependencies: + react-dom: 19.1.0(react@19.1.0) + + react-style-singleton@2.2.3(@types/react@19.1.2)(react@19.1.0): + dependencies: + get-nonce: 1.0.1 + react: 19.1.0 + tslib: 2.8.1 + optionalDependencies: + '@types/react': 19.1.2 + + react-textarea-autosize@8.5.9(@types/react@19.1.2)(react@19.1.0): + dependencies: + '@babel/runtime': 7.27.0 + react: 19.1.0 + use-composed-ref: 1.4.0(@types/react@19.1.2)(react@19.1.0) + use-latest: 1.3.0(@types/react@19.1.2)(react@19.1.0) + transitivePeerDependencies: + - '@types/react' + + react-transition-group@4.4.5(react-dom@19.1.0(react@19.1.0))(react@19.1.0): + dependencies: + '@babel/runtime': 7.27.0 + dom-helpers: 5.2.1 + loose-envify: 1.4.0 + prop-types: 15.8.1 + react: 19.1.0 + react-dom: 19.1.0(react@19.1.0) + + react-universal-interface@0.6.2(react@19.1.0)(tslib@2.8.1): + dependencies: + react: 19.1.0 + tslib: 2.8.1 + + react-use@17.6.0(react-dom@19.1.0(react@19.1.0))(react@19.1.0): + dependencies: + '@types/js-cookie': 2.2.7 + '@xobotyi/scrollbar-width': 1.9.5 + copy-to-clipboard: 3.3.3 + fast-deep-equal: 3.1.3 + fast-shallow-equal: 1.0.0 + js-cookie: 2.2.1 + nano-css: 5.6.2(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + react: 19.1.0 + react-dom: 19.1.0(react@19.1.0) + react-universal-interface: 0.6.2(react@19.1.0)(tslib@2.8.1) + resize-observer-polyfill: 1.5.1 + screenfull: 5.2.0 + set-harmonic-interval: 1.0.1 + throttle-debounce: 3.0.1 + ts-easing: 0.2.0 + tslib: 2.8.1 + + react@19.1.0: {} + + reactcss@1.2.3(react@19.1.0): + dependencies: + lodash: 4.17.21 + react: 19.1.0 + + readdirp@3.6.0: + dependencies: + picomatch: 2.3.1 + + reflect.getprototypeof@1.0.10: + dependencies: + call-bind: 1.0.8 + define-properties: 1.2.1 + es-abstract: 1.23.9 + es-errors: 1.3.0 + es-object-atoms: 1.1.1 + get-intrinsic: 1.3.0 + get-proto: 1.0.1 + which-builtin-type: 1.2.1 + + regenerator-runtime@0.14.1: {} + + regexp.prototype.flags@1.5.4: + dependencies: + call-bind: 1.0.8 + define-properties: 1.2.1 + es-errors: 1.3.0 + get-proto: 1.0.1 + gopd: 1.2.0 + set-function-name: 2.0.2 + + resize-observer-polyfill@1.5.1: {} + + resolve-from@4.0.0: {} + + resolve-pkg-maps@1.0.0: {} + + resolve@1.22.10: + dependencies: + is-core-module: 2.16.1 + path-parse: 1.0.7 + supports-preserve-symlinks-flag: 1.0.0 + + resolve@2.0.0-next.5: + dependencies: + is-core-module: 2.16.1 + path-parse: 1.0.7 + supports-preserve-symlinks-flag: 1.0.0 + + restore-cursor@5.1.0: + dependencies: + onetime: 7.0.0 + signal-exit: 4.1.0 + + reusify@1.1.0: {} + + rfdc@1.4.1: {} + + rollup@4.40.0: + dependencies: + '@types/estree': 1.0.7 + optionalDependencies: + '@rollup/rollup-android-arm-eabi': 4.40.0 + '@rollup/rollup-android-arm64': 4.40.0 + '@rollup/rollup-darwin-arm64': 4.40.0 + '@rollup/rollup-darwin-x64': 4.40.0 + '@rollup/rollup-freebsd-arm64': 4.40.0 + '@rollup/rollup-freebsd-x64': 4.40.0 + '@rollup/rollup-linux-arm-gnueabihf': 4.40.0 + '@rollup/rollup-linux-arm-musleabihf': 4.40.0 + '@rollup/rollup-linux-arm64-gnu': 4.40.0 + '@rollup/rollup-linux-arm64-musl': 4.40.0 + '@rollup/rollup-linux-loongarch64-gnu': 4.40.0 + '@rollup/rollup-linux-powerpc64le-gnu': 4.40.0 + '@rollup/rollup-linux-riscv64-gnu': 4.40.0 + '@rollup/rollup-linux-riscv64-musl': 4.40.0 + '@rollup/rollup-linux-s390x-gnu': 4.40.0 + '@rollup/rollup-linux-x64-gnu': 4.40.0 + '@rollup/rollup-linux-x64-musl': 4.40.0 + '@rollup/rollup-win32-arm64-msvc': 4.40.0 + '@rollup/rollup-win32-ia32-msvc': 4.40.0 + '@rollup/rollup-win32-x64-msvc': 4.40.0 + fsevents: 2.3.3 + + rtl-css-js@1.16.1: + dependencies: + '@babel/runtime': 7.27.0 + + run-parallel@1.2.0: + dependencies: + queue-microtask: 1.2.3 + + safe-array-concat@1.1.3: + dependencies: + call-bind: 1.0.8 + call-bound: 1.0.4 + get-intrinsic: 1.3.0 + has-symbols: 1.1.0 + isarray: 2.0.5 + + safe-push-apply@1.0.0: + dependencies: + es-errors: 1.3.0 + isarray: 2.0.5 + + safe-regex-test@1.1.0: + dependencies: + call-bound: 1.0.4 + es-errors: 1.3.0 + is-regex: 1.2.1 + + safe-stable-stringify@2.5.0: {} + + scheduler@0.26.0: {} + + screenfull@5.2.0: {} + + scroll-into-view-if-needed@3.1.0: + dependencies: + compute-scroll-into-view: 3.1.1 + + semver@6.3.1: {} + + semver@7.7.1: {} + + seroval-plugins@1.2.1(seroval@1.2.1): + dependencies: + seroval: 1.2.1 + + seroval@1.2.1: {} + + set-function-length@1.2.2: + dependencies: + define-data-property: 1.1.4 + es-errors: 1.3.0 + function-bind: 1.1.2 + get-intrinsic: 1.3.0 + gopd: 1.2.0 + has-property-descriptors: 1.0.2 + + set-function-name@2.0.2: + dependencies: + define-data-property: 1.1.4 + es-errors: 1.3.0 + functions-have-names: 1.2.3 + has-property-descriptors: 1.0.2 + + set-harmonic-interval@1.0.1: {} + + set-proto@1.0.0: + dependencies: + dunder-proto: 1.0.1 + es-errors: 1.3.0 + es-object-atoms: 1.1.1 + + shallowequal@1.1.0: {} + + shebang-command@2.0.0: + dependencies: + shebang-regex: 3.0.0 + + shebang-regex@3.0.0: {} + + side-channel-list@1.0.0: + dependencies: + es-errors: 1.3.0 + object-inspect: 1.13.4 + + side-channel-map@1.0.1: + dependencies: + call-bound: 1.0.4 + es-errors: 1.3.0 + get-intrinsic: 1.3.0 + object-inspect: 1.13.4 + + side-channel-weakmap@1.0.2: + dependencies: + call-bound: 1.0.4 + es-errors: 1.3.0 + get-intrinsic: 1.3.0 + object-inspect: 1.13.4 + side-channel-map: 1.0.1 + + side-channel@1.1.0: + dependencies: + es-errors: 1.3.0 + object-inspect: 1.13.4 + side-channel-list: 1.0.0 + side-channel-map: 1.0.1 + side-channel-weakmap: 1.0.2 + + signal-exit@4.1.0: {} + + slice-ansi@5.0.0: + dependencies: + ansi-styles: 6.2.1 + is-fullwidth-code-point: 4.0.0 + + slice-ansi@7.1.0: + dependencies: + ansi-styles: 6.2.1 + is-fullwidth-code-point: 5.0.0 + + snake-case@3.0.4: + dependencies: + dot-case: 3.0.4 + tslib: 2.8.1 + + solid-js@1.9.5: + dependencies: + csstype: 3.1.3 + seroval: 1.2.1 + seroval-plugins: 1.2.1(seroval@1.2.1) + + source-map-js@1.2.1: {} + + source-map@0.5.6: {} + + source-map@0.5.7: {} + + source-map@0.6.1: {} + + stack-generator@2.0.10: + dependencies: + stackframe: 1.3.4 + + stackframe@1.3.4: {} + + stacktrace-gps@3.1.2: + dependencies: + source-map: 0.5.6 + stackframe: 1.3.4 + + stacktrace-js@2.0.2: + dependencies: + error-stack-parser: 2.1.4 + stack-generator: 2.0.10 + stacktrace-gps: 3.1.2 + + state-local@1.0.7: {} + + string-argv@0.3.2: {} + + string-convert@0.2.1: {} + + string-width@7.2.0: + dependencies: + emoji-regex: 10.4.0 + get-east-asian-width: 1.3.0 + strip-ansi: 7.1.0 + + string.prototype.matchall@4.0.12: + dependencies: + call-bind: 1.0.8 + call-bound: 1.0.4 + define-properties: 1.2.1 + es-abstract: 1.23.9 + es-errors: 1.3.0 + es-object-atoms: 1.1.1 + get-intrinsic: 1.3.0 + gopd: 1.2.0 + has-symbols: 1.1.0 + internal-slot: 1.1.0 + regexp.prototype.flags: 1.5.4 + set-function-name: 2.0.2 + side-channel: 1.1.0 + + string.prototype.repeat@1.0.0: + dependencies: + define-properties: 1.2.1 + es-abstract: 1.23.9 + + string.prototype.trim@1.2.10: + dependencies: + call-bind: 1.0.8 + call-bound: 1.0.4 + define-data-property: 1.1.4 + define-properties: 1.2.1 + es-abstract: 1.23.9 + es-object-atoms: 1.1.1 + has-property-descriptors: 1.0.2 + + string.prototype.trimend@1.0.9: + dependencies: + call-bind: 1.0.8 + call-bound: 1.0.4 + define-properties: 1.2.1 + es-object-atoms: 1.1.1 + + string.prototype.trimstart@1.0.8: + dependencies: + call-bind: 1.0.8 + define-properties: 1.2.1 + es-object-atoms: 1.1.1 + + strip-ansi@7.1.0: + dependencies: + ansi-regex: 6.1.0 + + strip-final-newline@3.0.0: {} + + strip-json-comments@3.1.1: {} + + stylis@4.2.0: {} + + stylis@4.3.6: {} + + sugarss@4.0.1(postcss@8.5.3): + dependencies: + postcss: 8.5.3 + + supports-color@7.2.0: + dependencies: + has-flag: 4.0.0 + + supports-preserve-symlinks-flag@1.0.0: {} + + svg-parser@2.0.4: {} + + swr@2.3.3(react@19.1.0): + dependencies: + dequal: 2.0.3 + react: 19.1.0 + use-sync-external-store: 1.5.0(react@19.1.0) + + tabbable@6.2.0: {} + + throttle-debounce@3.0.1: {} + + throttle-debounce@5.0.2: {} + + tiny-invariant@1.3.3: {} + + tiny-warning@1.0.3: {} + + tinycolor2@1.6.0: {} + + tinyexec@0.3.2: {} + + tinyglobby@0.2.13: + dependencies: + fdir: 6.4.4(picomatch@4.0.2) + picomatch: 4.0.2 + + to-regex-range@5.0.1: + dependencies: + is-number: 7.0.0 + + toggle-selection@1.0.6: {} + + ts-api-utils@2.1.0(typescript@5.7.3): + dependencies: + typescript: 5.7.3 + + ts-easing@0.2.0: {} + + tsconfck@3.1.5(typescript@5.7.3): + optionalDependencies: + typescript: 5.7.3 + + tslib@2.8.1: {} + + tsx@4.19.3: + dependencies: + esbuild: 0.25.2 + get-tsconfig: 4.10.0 + optionalDependencies: + fsevents: 2.3.3 + + type-check@0.4.0: + dependencies: + prelude-ls: 1.2.1 + + type-fest@4.40.0: {} + + typed-array-buffer@1.0.3: + dependencies: + call-bound: 1.0.4 + es-errors: 1.3.0 + is-typed-array: 1.1.15 + + typed-array-byte-length@1.0.3: + dependencies: + call-bind: 1.0.8 + for-each: 0.3.5 + gopd: 1.2.0 + has-proto: 1.2.0 + is-typed-array: 1.1.15 + + typed-array-byte-offset@1.0.4: + dependencies: + available-typed-arrays: 1.0.7 + call-bind: 1.0.8 + for-each: 0.3.5 + gopd: 1.2.0 + has-proto: 1.2.0 + is-typed-array: 1.1.15 + reflect.getprototypeof: 1.0.10 + + typed-array-length@1.0.7: + dependencies: + call-bind: 1.0.8 + for-each: 0.3.5 + gopd: 1.2.0 + is-typed-array: 1.1.15 + possible-typed-array-names: 1.1.0 + reflect.getprototypeof: 1.0.10 + + typescript-eslint@8.31.0(eslint@9.25.1)(typescript@5.7.3): + dependencies: + '@typescript-eslint/eslint-plugin': 8.31.0(@typescript-eslint/parser@8.31.0(eslint@9.25.1)(typescript@5.7.3))(eslint@9.25.1)(typescript@5.7.3) + '@typescript-eslint/parser': 8.31.0(eslint@9.25.1)(typescript@5.7.3) + '@typescript-eslint/utils': 8.31.0(eslint@9.25.1)(typescript@5.7.3) + eslint: 9.25.1 + typescript: 5.7.3 + transitivePeerDependencies: + - supports-color + + typescript@5.7.3: {} + + ufo@1.6.1: {} + + unbox-primitive@1.1.0: + dependencies: + call-bound: 1.0.4 + has-bigints: 1.1.0 + has-symbols: 1.1.0 + which-boxed-primitive: 1.1.1 + + undici-types@6.21.0: {} + + unplugin-icons@22.1.0(@svgr/core@8.1.0(typescript@5.7.3)): + dependencies: + '@antfu/install-pkg': 1.0.0 + '@iconify/utils': 2.3.0 + debug: 4.4.0 + local-pkg: 1.1.1 + unplugin: 2.3.2 + optionalDependencies: + '@svgr/core': 8.1.0(typescript@5.7.3) + transitivePeerDependencies: + - supports-color + + unplugin@2.3.2: + dependencies: + acorn: 8.14.1 + picomatch: 4.0.2 + webpack-virtual-modules: 0.6.2 + + update-browserslist-db@1.1.3(browserslist@4.24.4): + dependencies: + browserslist: 4.24.4 + escalade: 3.2.0 + picocolors: 1.1.1 + + uri-js@4.4.1: + dependencies: + punycode: 2.3.1 + + use-callback-ref@1.3.3(@types/react@19.1.2)(react@19.1.0): + dependencies: + react: 19.1.0 + tslib: 2.8.1 + optionalDependencies: + '@types/react': 19.1.2 + + use-composed-ref@1.4.0(@types/react@19.1.2)(react@19.1.0): + dependencies: + react: 19.1.0 + optionalDependencies: + '@types/react': 19.1.2 + + use-deep-compare-effect@1.8.1(react@19.1.0): + dependencies: + '@babel/runtime': 7.27.0 + dequal: 2.0.3 + react: 19.1.0 + + use-isomorphic-layout-effect@1.2.0(@types/react@19.1.2)(react@19.1.0): + dependencies: + react: 19.1.0 + optionalDependencies: + '@types/react': 19.1.2 + + use-latest@1.3.0(@types/react@19.1.2)(react@19.1.0): + dependencies: + react: 19.1.0 + use-isomorphic-layout-effect: 1.2.0(@types/react@19.1.2)(react@19.1.0) + optionalDependencies: + '@types/react': 19.1.2 + + use-sidecar@1.1.3(@types/react@19.1.2)(react@19.1.0): + dependencies: + detect-node-es: 1.1.0 + react: 19.1.0 + tslib: 2.8.1 + optionalDependencies: + '@types/react': 19.1.2 + + use-sync-external-store@1.5.0(react@19.1.0): + dependencies: + react: 19.1.0 + + util-deprecate@1.0.2: {} + + uuid@8.3.2: {} + + vite-tsconfig-paths@5.1.4(typescript@5.7.3)(vite@6.3.2(@types/node@22.14.1)(sugarss@4.0.1(postcss@8.5.3))(tsx@4.19.3)(yaml@2.7.1)): + dependencies: + debug: 4.4.0 + globrex: 0.1.2 + tsconfck: 3.1.5(typescript@5.7.3) + optionalDependencies: + vite: 6.3.2(@types/node@22.14.1)(sugarss@4.0.1(postcss@8.5.3))(tsx@4.19.3)(yaml@2.7.1) + transitivePeerDependencies: + - supports-color + - typescript + + vite@6.3.2(@types/node@22.14.1)(sugarss@4.0.1(postcss@8.5.3))(tsx@4.19.3)(yaml@2.7.1): + dependencies: + esbuild: 0.25.2 + fdir: 6.4.4(picomatch@4.0.2) + picomatch: 4.0.2 + postcss: 8.5.3 + rollup: 4.40.0 + tinyglobby: 0.2.13 + optionalDependencies: + '@types/node': 22.14.1 + fsevents: 2.3.3 + sugarss: 4.0.1(postcss@8.5.3) + tsx: 4.19.3 + yaml: 2.7.1 + + void-elements@3.1.0: {} + + warning@4.0.3: + dependencies: + loose-envify: 1.4.0 + + webpack-virtual-modules@0.6.2: {} + + which-boxed-primitive@1.1.1: + dependencies: + is-bigint: 1.1.0 + is-boolean-object: 1.2.2 + is-number-object: 1.1.1 + is-string: 1.1.1 + is-symbol: 1.1.1 + + which-builtin-type@1.2.1: + dependencies: + call-bound: 1.0.4 + function.prototype.name: 1.1.8 + has-tostringtag: 1.0.2 + is-async-function: 2.1.1 + is-date-object: 1.1.0 + is-finalizationregistry: 1.1.1 + is-generator-function: 1.1.0 + is-regex: 1.2.1 + is-weakref: 1.1.1 + isarray: 2.0.5 + which-boxed-primitive: 1.1.1 + which-collection: 1.0.2 + which-typed-array: 1.1.19 + + which-collection@1.0.2: + dependencies: + is-map: 2.0.3 + is-set: 2.0.3 + is-weakmap: 2.0.2 + is-weakset: 2.0.4 + + which-typed-array@1.1.19: + dependencies: + available-typed-arrays: 1.0.7 + call-bind: 1.0.8 + call-bound: 1.0.4 + for-each: 0.3.5 + get-proto: 1.0.1 + gopd: 1.2.0 + has-tostringtag: 1.0.2 + + which@2.0.2: + dependencies: + isexe: 2.0.0 + + word-wrap@1.2.5: {} + + wrap-ansi@9.0.0: + dependencies: + ansi-styles: 6.2.1 + string-width: 7.2.0 + strip-ansi: 7.1.0 + + yallist@3.1.1: {} + + yaml@1.10.2: {} + + yaml@2.7.1: {} + + yocto-queue@0.1.0: {} + + zod-empty@1.4.4(typescript@5.7.3)(zod@3.24.3): + dependencies: + just-clone: 6.2.0 + typescript: 5.7.3 + zod: 3.24.3 + + zod@3.24.3: {} diff --git a/src/apis/consumers.ts b/src/apis/consumers.ts new file mode 100644 index 0000000000..849b544a16 --- /dev/null +++ b/src/apis/consumers.ts @@ -0,0 +1,34 @@ +import { API_CONSUMERS } from '@/config/constant'; +import { req } from '@/config/req'; +import type { APISIXType } from '@/types/schema/apisix'; +import type { PageSearchType } from '@/types/schema/pageSearch'; +import { queryOptions } from '@tanstack/react-query'; + +export const getConsumerListQueryOptions = (props: PageSearchType) => { + const { page, pageSize } = props; + return queryOptions({ + queryKey: ['consumers', page, pageSize], + queryFn: () => + req + .get(API_CONSUMERS, { + params: { page, page_size: pageSize }, + }) + .then((v) => v.data), + }); +}; + +export const getConsumerQueryOptions = (username: string) => + queryOptions({ + queryKey: ['consumer', username], + queryFn: () => + req + .get(`${API_CONSUMERS}/${username}`) + .then((v) => v.data), + }); + +export const putConsumerReq = (data: APISIXType['ConsumerPut']) => { + return req.put( + API_CONSUMERS, + data + ); +}; diff --git a/src/apis/credentials.ts b/src/apis/credentials.ts new file mode 100644 index 0000000000..7627c2853d --- /dev/null +++ b/src/apis/credentials.ts @@ -0,0 +1,56 @@ +import { API_CREDENTIALS, SKIP_INTERCEPTOR_HEADER } from '@/config/constant'; +import { req } from '@/config/req'; +import type { APISIXType } from '@/types/schema/apisix'; +import type { APISIXListResponse } from '@/types/schema/apisix/type'; +import { queryOptions } from '@tanstack/react-query'; + +type WithUsername = Pick; +export const getCredentialListQueryOptions = (props: WithUsername) => { + const { username } = props; + return queryOptions({ + queryKey: ['credentials', username], + queryFn: () => + req + .get( + API_CREDENTIALS(username), + { + headers: { + [SKIP_INTERCEPTOR_HEADER]: ['404'], + }, + } + ) + .then((v) => v.data) + .catch((e) => { + // 404 means credentials is empty + if (e.response.status === 404) { + const res: APISIXListResponse = { + total: 0, + list: [], + }; + return res; + } + throw e; + }), + }); +}; + +export const getCredentialQueryOptions = (username: string, id: string) => + queryOptions({ + queryKey: ['credential', username, id], + queryFn: () => + req + .get( + `${API_CREDENTIALS(username)}/${id}` + ) + .then((v) => v.data), + }); + +export const putCredentialReq = ( + data: APISIXType['CredentialPut'] & WithUsername +) => { + const { username, id, ...rest } = data; + return req.put< + APISIXType['CredentialPut'], + APISIXType['RespCredentialDetail'] + >(`${API_CREDENTIALS(username)}/${id}`, rest); +}; diff --git a/src/apis/plugins.ts b/src/apis/plugins.ts new file mode 100644 index 0000000000..210c0123ac --- /dev/null +++ b/src/apis/plugins.ts @@ -0,0 +1,115 @@ +import type { PluginConfig } from '@/components/form-slice/FormItemPlugins/PluginEditorDrawer'; +import { + API_GLOBAL_RULES, + API_PLUGINS, + API_PLUGINS_LIST, + API_PLUGIN_METADATA, +} from '@/config/constant'; +import { req } from '@/config/req'; +import type { APISIXType } from '@/types/schema/apisix'; +import { queryOptions, skipToken } from '@tanstack/react-query'; +import type { AxiosRequestConfig } from 'axios'; + +export const putGlobalRuleReq = (data: APISIXType['GlobalRulePut']) => { + const { id, ...rest } = data; + return req.put< + APISIXType['GlobalRulePut'], + APISIXType['RespGlobalRuleDetail'] + >(`${API_GLOBAL_RULES}/${id}`, rest); +}; + +export const getGlobalRuleQueryOptions = (id: string) => + queryOptions({ + queryKey: ['global_rule', id], + queryFn: () => + req + .get( + `${API_GLOBAL_RULES}/${id}` + ) + .then((v) => v.data), + }); + +export type NeedPluginSchema = { + schema: APISIXType['PluginSchemaKeys']; +}; + +export const getPluginsListQueryOptions = () => { + return queryOptions({ + queryKey: ['plugins-list'], + queryFn: () => + req + .get(API_PLUGINS_LIST) + .then((v) => v.data), + }); +}; + +export const getPluginsListWithSchemaQueryOptions = ( + props: APISIXType['PluginsQuery'] & NeedPluginSchema = { schema: 'schema' } +) => { + const { subsystem, schema } = props; + return queryOptions({ + queryKey: ['plugins-list-with-schema', subsystem, schema], + queryFn: () => + req + .get(API_PLUGINS, { + params: { subsystem, all: true }, + }) + .then((v) => { + const data = Object.entries(v.data); + const names = []; + for (const [name, config] of data) { + if (config[schema]) { + names.push(name); + } + } + return { names, originObj: v.data }; + }), + }); +}; + +export const getPluginSchemaQueryOptions = ( + name: string, + enabled: boolean = true +) => { + return queryOptions({ + queryKey: ['plugin-schema', name], + queryFn: name + ? () => + req + .get( + `${API_PLUGINS}/${name}` + ) + .then((v) => v.data) + : skipToken, + enabled, + }); +}; + +export const putPluginMetadataReq = (props: PluginConfig) => { + const { name, config } = props; + return req.put< + APISIXType['PluginMetadataPut'], + APISIXType['RespPluginMetadataDetail'] + >(`${API_PLUGIN_METADATA}/${name}`, config); +}; + +export const deletePluginMetadataReq = (name: string) => { + return req.delete( + `${API_PLUGIN_METADATA}/${name}` + ); +}; + +export const getPluginMetadataQueryOptions = ( + plugin_name: string, + headers?: AxiosRequestConfig['headers'] +) => + queryOptions({ + queryKey: ['plugin_metadata', plugin_name], + queryFn: () => + req + .get( + `${API_PLUGIN_METADATA}/${plugin_name}`, + { headers } + ) + .then((v) => v.data), + }); diff --git a/src/apis/protos.ts b/src/apis/protos.ts new file mode 100644 index 0000000000..0f2c3b1ec5 --- /dev/null +++ b/src/apis/protos.ts @@ -0,0 +1,45 @@ +import { API_PROTOS } from '@/config/constant'; +import { req } from '@/config/req'; +import type { APISIXType } from '@/types/schema/apisix'; +import { queryOptions } from '@tanstack/react-query'; +import type { PageSearchType } from '@/types/schema/pageSearch'; + +export const getProtoListQueryOptions = (props: PageSearchType) => { + const { page, pageSize } = props; + return queryOptions({ + queryKey: ['protos', page, pageSize], + queryFn: () => + req + .get(API_PROTOS, { + params: { + page, + page_size: pageSize, + }, + }) + .then((v) => v.data), + }); +}; + +export const getProtoQueryOptions = (id: string) => + queryOptions({ + queryKey: ['proto', id], + queryFn: () => + req + .get(`${API_PROTOS}/${id}`) + .then((v) => v.data), + }); + +export const putProtoReq = (data: APISIXType['Proto']) => { + const { id, ...rest } = data; + return req.put( + `${API_PROTOS}/${id}`, + rest + ); +}; + +export const postProtoReq = (data: APISIXType['ProtoPost']) => { + return req.post( + API_PROTOS, + data + ); +}; diff --git a/src/apis/routes.ts b/src/apis/routes.ts new file mode 100644 index 0000000000..4cfd5ee58a --- /dev/null +++ b/src/apis/routes.ts @@ -0,0 +1,39 @@ +import type { RoutePostType } from '@/components/form-slice/FormPartRoute/schema'; +import { API_ROUTES } from '@/config/constant'; +import { req } from '@/config/req'; +import type { APISIXType } from '@/types/schema/apisix'; +import type { PageSearchType } from '@/types/schema/pageSearch'; +import { queryOptions } from '@tanstack/react-query'; + +export const getRouteListQueryOptions = (props: PageSearchType) => { + const { page, pageSize } = props; + return queryOptions({ + queryKey: ['routes', page, pageSize], + queryFn: () => + req + .get(API_ROUTES, { + params: { page, page_size: pageSize }, + }) + .then((v) => v.data), + }); +}; + +export const getRouteQueryOptions = (id: string) => + queryOptions({ + queryKey: ['route', id], + queryFn: () => + req + .get(`${API_ROUTES}/${id}`) + .then((v) => v.data), + }); + +export const putRouteReq = (data: APISIXType['Route']) => { + const { id, ...rest } = data; + return req.put( + `${API_ROUTES}/${id}`, + rest + ); +}; + +export const postRouteReq = (data: RoutePostType) => + req.post(API_ROUTES, data); diff --git a/src/apis/services.ts b/src/apis/services.ts new file mode 100644 index 0000000000..e3527cae0f --- /dev/null +++ b/src/apis/services.ts @@ -0,0 +1,43 @@ +import { API_SERVICES } from '@/config/constant'; +import { req } from '@/config/req'; +import type { APISIXType } from '@/types/schema/apisix'; +import type { PageSearchType } from '@/types/schema/pageSearch'; +import { queryOptions } from '@tanstack/react-query'; + +export type ServicePostType = APISIXType['ServicePost']; + +export const getServiceListQueryOptions = (props: PageSearchType) => { + const { page, pageSize } = props; + return queryOptions({ + queryKey: ['services', page, pageSize], + queryFn: () => + req + .get(API_SERVICES, { + params: { page, page_size: pageSize }, + }) + .then((v) => v.data), + }); +}; + +export const getServiceQueryOptions = (id: string) => + queryOptions({ + queryKey: ['service', id], + queryFn: () => + req + .get(`${API_SERVICES}/${id}`) + .then((v) => v.data), + }); + +export const putServiceReq = (data: APISIXType['Service']) => { + const { id, ...rest } = data; + return req.put( + `${API_SERVICES}/${id}`, + rest + ); +}; + +export const postServiceReq = (data: ServicePostType) => + req.post( + API_SERVICES, + data + ); diff --git a/src/apis/ssls.ts b/src/apis/ssls.ts new file mode 100644 index 0000000000..41aa0dcce8 --- /dev/null +++ b/src/apis/ssls.ts @@ -0,0 +1,38 @@ +import { API_SSLS } from '@/config/constant'; +import { req } from '@/config/req'; +import type { APISIXType } from '@/types/schema/apisix'; +import type { PageSearchType } from '@/types/schema/pageSearch'; +import { queryOptions } from '@tanstack/react-query'; + +export const getSSLListQueryOptions = (props: PageSearchType) => { + const { page, pageSize } = props; + return queryOptions({ + queryKey: ['ssls', page, pageSize], + queryFn: () => + req + .get(API_SSLS, { + params: { + page, + page_size: pageSize, + }, + }) + .then((v) => v.data), + }); +}; + +export const getSSLDetailQueryOptions = (id: string) => + queryOptions({ + queryKey: ['ssl', id], + queryFn: () => + req + .get(`${API_SSLS}/${id}`) + .then((v) => v.data), + }); + +export const putSSLReq = (data: APISIXType['SSL']) => { + const { id, ...rest } = data; + return req.put( + `${API_SSLS}/${id}`, + rest + ); +}; diff --git a/src/apis/stream_routes.ts b/src/apis/stream_routes.ts new file mode 100644 index 0000000000..e6d9b255a0 --- /dev/null +++ b/src/apis/stream_routes.ts @@ -0,0 +1,44 @@ +import type { StreamRoutePostType } from '@/components/form-slice/FormPartStreamRoute/schema'; +import { API_STREAM_ROUTES } from '@/config/constant'; +import { req } from '@/config/req'; +import type { APISIXType } from '@/types/schema/apisix'; +import type { PageSearchType } from '@/types/schema/pageSearch'; +import { queryOptions } from '@tanstack/react-query'; + +export const getStreamRouteListQueryOptions = (props: PageSearchType) => { + const { page, pageSize } = props; + return queryOptions({ + queryKey: ['stream_routes', page, pageSize], + queryFn: () => + req + .get(API_STREAM_ROUTES, { + params: { page, page_size: pageSize }, + }) + .then((v) => v.data), + }); +}; + +export const getStreamRouteQueryOptions = (id: string) => + queryOptions({ + queryKey: ['stream_route', id], + queryFn: () => + req + .get( + `${API_STREAM_ROUTES}/${id}` + ) + .then((v) => v.data), + }); + +export const putStreamRouteReq = (data: APISIXType['StreamRoute']) => { + const { id, ...rest } = data; + return req.put< + APISIXType['StreamRoute'], + APISIXType['RespStreamRouteDetail'] + >(`${API_STREAM_ROUTES}/${id}`, rest); +}; + +export const postStreamRouteReq = (data: StreamRoutePostType) => + req.post( + API_STREAM_ROUTES, + data + ); diff --git a/src/apis/upstreams.ts b/src/apis/upstreams.ts new file mode 100644 index 0000000000..14f07fd731 --- /dev/null +++ b/src/apis/upstreams.ts @@ -0,0 +1,23 @@ +import { API_UPSTREAMS } from '@/config/constant'; +import { req } from '@/config/req'; +import type { APISIXType } from '@/types/schema/apisix'; +import { queryOptions } from '@tanstack/react-query'; + +export const getUpstreamReq = (id: string) => + queryOptions({ + queryKey: ['upstream', id], + queryFn: () => + req + .get( + `${API_UPSTREAMS}/${id}` + ) + .then((v) => v.data), + }); + +export const putUpstreamReq = (data: APISIXType['Upstream']) => { + const { id, ...rest } = data; + return req.put( + `${API_UPSTREAMS}/${id}`, + rest + ); +}; diff --git a/src/assets/apisix-logo.svg b/src/assets/apisix-logo.svg new file mode 100644 index 0000000000..8e99cc539b --- /dev/null +++ b/src/assets/apisix-logo.svg @@ -0,0 +1,15 @@ + + + + + + + + + + + + + + + diff --git a/src/components/Btn.tsx b/src/components/Btn.tsx new file mode 100644 index 0000000000..3f48756c45 --- /dev/null +++ b/src/components/Btn.tsx @@ -0,0 +1,12 @@ +import { Button, type ButtonProps } from '@mantine/core'; +import { createLink } from '@tanstack/react-router'; +import { forwardRef } from 'react'; + +const MantineBtnLinkComponent = forwardRef( + (props, ref) => { + return + )} + {mode === 'view' && ( + + )} + {mode === 'edit' && ( + <> + + + + )} + + + ); +}; diff --git a/src/components/form-slice/FormItemPlugins/PluginCardList.tsx b/src/components/form-slice/FormItemPlugins/PluginCardList.tsx new file mode 100644 index 0000000000..d574b267b3 --- /dev/null +++ b/src/components/form-slice/FormItemPlugins/PluginCardList.tsx @@ -0,0 +1,142 @@ +import { + CloseButton, + Combobox, + ScrollArea, + SimpleGrid, + TextInput, + useVirtualizedCombobox, + type TextInputProps, +} from '@mantine/core'; +import { PluginCard, type PluginCardProps } from './PluginCard'; +import { useTranslation } from 'react-i18next'; +import { observer, useLocalObservable } from 'mobx-react-lite'; +import { useEffect } from 'react'; + +type PluginCardListSearchProps = Pick & { + search: string; + setSearch: (search: string) => void; +}; +export const PluginCardListSearch = (props: PluginCardListSearchProps) => { + const { placeholder, search, setSearch } = props; + const { t } = useTranslation(); + return ( + { + event.preventDefault(); + event.stopPropagation(); + setSearch(event.currentTarget.value); + }} + rightSectionPointerEvents="all" + rightSection={ + { + event.preventDefault(); + event.stopPropagation(); + setSearch(''); + }} + /> + } + /> + ); +}; + +type OptionProps = Pick< + PluginCardProps, + 'onAdd' | 'onEdit' | 'onDelete' | 'onView' | 'mode' +> & { + name: string; +}; +const Option = observer((props: OptionProps) => { + const { mode, name, onAdd, onEdit, onDelete, onView } = props; + return ( + + onAdd?.(name)} + onEdit={() => onEdit?.(name)} + onDelete={() => onDelete?.(name)} + onView={() => onView?.(name)} + /> + + ); +}); + +const Options = observer((props: { list: OptionProps[] }) => { + const { list } = props; + return ( + <> + {list.map((option) => ( +