From da8912121fa6c34c6bc6e89f62c27af0be7fc786 Mon Sep 17 00:00:00 2001 From: Xinwei Xiong <3293172751@qq.com> Date: Thu, 7 Sep 2023 17:38:09 +0800 Subject: [PATCH] fix: add openim server deploy (#1036) * fix: add openim server deploy Signed-off-by: Xinwei Xiong(cubxxw-openim) <3293172751nss@gmail.com> * feat: add openim im ctl Signed-off-by: Xinwei Xiong(cubxxw-openim) <3293172751nss@gmail.com> * feat: add more deployment docs Signed-off-by: Xinwei Xiong(cubxxw-openim) <3293172751nss@gmail.com> * feat: add openim save images and release Signed-off-by: Xinwei Xiong(cubxxw-openim) <3293172751nss@gmail.com> * build: set IBM sarama and set k8s deployment helm Signed-off-by: Xinwei Xiong(cubxxw-openim) <3293172751nss@gmail.com> * fix: add openim code fix Signed-off-by: Xinwei Xiong(cubxxw-openim) <3293172751nss@gmail.com> * fix: add openim code fix Signed-off-by: Xinwei Xiong(cubxxw-openim) <3293172751nss@gmail.com> --------- Signed-off-by: Xinwei Xiong(cubxxw-openim) <3293172751nss@gmail.com> --- .github/workflows/build-openim-web-image.yml | 274 ++++++++ .github/workflows/docker-buildx.yml | 64 -- .github/workflows/release.yml | 2 +- README-zh_CN.md | 26 +- README.md | 117 +--- deployments/README.md | 57 ++ docker-compose.yml | 68 +- docs/contrib/environment.md | 138 ++++ docs/contrib/linux_development.md | 18 +- docs/contrib/protoc_tools.md | 2 +- docs/conversions/version.md | 3 +- go.mod | 16 +- go.sum | 54 +- go.work | 3 +- internal/msggateway/client.go | 1 + .../msgtransfer/online_history_msg_handler.go | 2 +- .../online_msg_to_mongo_handler.go | 2 +- .../msgtransfer/persistent_msg_handler.go | 2 +- internal/push/push_handler.go | 2 +- pkg/common/kafka/consumer.go | 2 +- pkg/common/kafka/consumer_group.go | 2 +- pkg/common/kafka/producer.go | 2 +- pkg/common/kafka/util.go | 2 +- .../githooks/{pre-commit => pre-commit.sh} | 0 scripts/githooks/{pre-push => pre-push.sh} | 0 scripts/init-githooks.sh | 45 +- scripts/install/environment.sh | 1 + test/data-conversion/kafka-conversation.go | 4 +- tools/component/component.go | 2 +- tools/imctl/README.md | 89 +++ tools/imctl/cmd/genman/README.md | 49 ++ tools/imctl/cmd/genman/genman.go | 62 ++ tools/imctl/cmd/genopenimdocs/config_flags.go | 243 +++++++ tools/imctl/cmd/imctl/imctl.go | 29 + tools/imctl/go.mod | 18 + tools/imctl/go.sum | 24 + tools/imctl/internal/imctl/cmd/cmd.go | 146 +++++ tools/imctl/internal/imctl/cmd/color/color.go | 352 ++++++++++ .../imctl/cmd/completion/completion.go | 283 ++++++++ tools/imctl/internal/imctl/cmd/info/info.go | 119 ++++ tools/imctl/internal/imctl/cmd/new/new.go | 619 ++++++++++++++++++ .../internal/imctl/cmd/options/options.go | 50 ++ tools/imctl/internal/imctl/cmd/profiling.go | 95 +++ .../internal/imctl/cmd/version/version.go | 165 +++++ 44 files changed, 2971 insertions(+), 283 deletions(-) create mode 100644 .github/workflows/build-openim-web-image.yml create mode 100644 docs/contrib/environment.md rename scripts/githooks/{pre-commit => pre-commit.sh} (100%) rename scripts/githooks/{pre-push => pre-push.sh} (100%) create mode 100644 tools/imctl/README.md create mode 100644 tools/imctl/cmd/genman/README.md create mode 100644 tools/imctl/cmd/genman/genman.go create mode 100644 tools/imctl/cmd/genopenimdocs/config_flags.go create mode 100644 tools/imctl/cmd/imctl/imctl.go create mode 100644 tools/imctl/go.mod create mode 100644 tools/imctl/go.sum create mode 100644 tools/imctl/internal/imctl/cmd/cmd.go create mode 100644 tools/imctl/internal/imctl/cmd/color/color.go create mode 100644 tools/imctl/internal/imctl/cmd/completion/completion.go create mode 100644 tools/imctl/internal/imctl/cmd/info/info.go create mode 100644 tools/imctl/internal/imctl/cmd/new/new.go create mode 100644 tools/imctl/internal/imctl/cmd/options/options.go create mode 100644 tools/imctl/internal/imctl/cmd/profiling.go create mode 100644 tools/imctl/internal/imctl/cmd/version/version.go diff --git a/.github/workflows/build-openim-web-image.yml b/.github/workflows/build-openim-web-image.yml new file mode 100644 index 000000000..471d7688e --- /dev/null +++ b/.github/workflows/build-openim-web-image.yml @@ -0,0 +1,274 @@ +# Copyright © 2023 OpenIM open source community. All rights reserved. +# +# Licensed 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. + +name: Build OpenIM Web Docker image + +on: + push: + branches: + - main + - release-* + tags: + - v* + workflow_dispatch: + +env: + # Common versions + GO_VERSION: "1.20" + +jobs: + build-dockerhub: + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v3 + - name: Set up QEMU + uses: docker/setup-qemu-action@v2 + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v2 + +# docker.io/openim/openim-server:latest + - name: Extract metadata (tags, labels) for Docker + id: meta + uses: docker/metadata-action@v4.6.0 + with: + images: openim/openim-server + # generate Docker tags based on the following events/attributes + tags: | + type=schedule + type=ref,event=branch + type=ref,event=pr + type=semver,pattern={{version}} + type=semver,pattern={{major}}.{{minor}} + type=semver,pattern={{major}} + type=sha + + - name: Log in to Docker Hub + uses: docker/login-action@v2 + with: + username: ${{ secrets.DOCKER_USERNAME }} + password: ${{ secrets.DOCKER_PASSWORD }} + + - name: Build and push Docker image + uses: docker/build-push-action@v4 + with: + context: . + # linux/ppc64le,linux/s390x + platforms: linux/amd64,linux/arm64 + push: ${{ github.event_name != 'pull_request' }} + tags: ${{ steps.meta.outputs.tags }} + labels: ${{ steps.meta.outputs.labels }} + + build-aliyun: + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v3 + - name: Set up QEMU + uses: docker/setup-qemu-action@v2 + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v2 +# registry.cn-hangzhou.aliyuncs.com/openimsdk/openim-server:latest + - name: Extract metadata (tags, labels) for Docker + id: meta2 + uses: docker/metadata-action@v4.6.0 + with: + images: registry.cn-hangzhou.aliyuncs.com/openimsdk/openim-server + + - name: Log in to AliYun Docker Hub + uses: docker/login-action@v2 + with: + registry: registry.cn-hangzhou.aliyuncs.com + username: ${{ secrets.ALIREGISTRY_USERNAME }} + password: ${{ secrets.ALIREGISTRY_TOKEN }} + + - name: Build and push Docker image + uses: docker/build-push-action@v4 + with: + context: . + # linux/ppc64le,linux/s390x + platforms: linux/amd64,linux/arm64 + push: ${{ github.event_name != 'pull_request' }} + tags: ${{ steps.meta2.outputs.tags }} + labels: ${{ steps.meta2.outputs.labels }} + + build-ghcr: + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v3 + - name: Set up QEMU + uses: docker/setup-qemu-action@v2 + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v2 +# ghcr.io/openimsdk/openim-server:latest + - name: Extract metadata (tags, labels) for Docker + id: meta3 + uses: docker/metadata-action@v4.6.0 + with: + images: ghcr.io/openimsdk/openim-server + + - name: Log in to GitHub Container Registry + uses: docker/login-action@v2 + with: + registry: ghcr.io + username: ${{ github.repository_owner }} + password: ${{ secrets.GITHUB_TOKEN }} + + - name: Build and push Docker image + uses: docker/build-push-action@v4 + with: + context: . + # linux/ppc64le,linux/s390x + platforms: linux/amd64,linux/arm64 + push: ${{ github.event_name != 'pull_request' }} + tags: ${{ steps.meta3.outputs.tags }} + labels: ${{ steps.meta3.outputs.labels }} + + build-openim-web-dockerhub: + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v3 + - name: Set up QEMU + uses: docker/setup-qemu-action@v2 + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v2 + +# docker.io/openim/openim-server:latest + - name: Extract metadata (tags, labels) for Docker + id: meta + uses: docker/metadata-action@v4.6.0 + with: + images: openim/openim-server + # generate Docker tags based on the following events/attributes + tags: | + type=schedule + type=ref,event=branch + type=ref,event=pr + type=semver,pattern={{version}} + type=semver,pattern={{major}}.{{minor}} + type=semver,pattern={{major}} + type=sha + + - name: Log in to Docker Hub + uses: docker/login-action@v2 + with: + username: ${{ secrets.DOCKER_USERNAME }} + password: ${{ secrets.DOCKER_PASSWORD }} + + - name: Build and push Docker image + uses: docker/build-push-action@v4 + with: + context: . + file: ./build/images/openim-tools/openim-web/Dockerfile + platforms: linux/amd64,linux/arm64 + push: ${{ github.event_name != 'pull_request' }} + tags: ${{ steps.meta.outputs.tags }} + labels: ${{ steps.meta.outputs.labels }} + + build-openim-web-aliyun: + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v3 + - name: Set up QEMU + uses: docker/setup-qemu-action@v2 + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v2 +# registry.cn-hangzhou.aliyuncs.com/openimsdk/openim-server:latest + - name: Extract metadata (tags, labels) for Docker + id: meta2 + uses: docker/metadata-action@v4.6.0 + with: + images: registry.cn-hangzhou.aliyuncs.com/openimsdk/openim-server + + - name: Log in to AliYun Docker Hub + uses: docker/login-action@v2 + with: + registry: registry.cn-hangzhou.aliyuncs.com + username: ${{ secrets.ALIREGISTRY_USERNAME }} + password: ${{ secrets.ALIREGISTRY_TOKEN }} + + - name: Build and push Docker image + uses: docker/build-push-action@v4 + with: + context: . + file: ./build/images/openim-tools/openim-web/Dockerfile + platforms: linux/amd64,linux/arm64 + push: ${{ github.event_name != 'pull_request' }} + tags: ${{ steps.meta2.outputs.tags }} + labels: ${{ steps.meta2.outputs.labels }} + + build-openim-web-ghcr: + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v3 + - name: Set up QEMU + uses: docker/setup-qemu-action@v2 + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v2 + with: + install: true + - name: Cache Docker layers + uses: actions/cache@v3 + with: + path: /tmp/.buildx-cache + key: ${{ runner.os }}-buildx-${{ github.sha }} + restore-keys: | + ${{ runner.os }}-buildx- + + - name: Log in to GitHub Container Registry + uses: docker/login-action@v2 + with: + registry: ghcr.io + username: ${{ github.repository_owner }} + password: ${{ secrets.GITHUB_TOKEN }} + + - name: Extract metadata (tags, labels) for Docker openim-web + id: meta1 + uses: docker/metadata-action@v4.6.0 + with: + images: ghcr.io/openimsdk/openim-web + + - name: Build and push Docker image for openim-web + uses: docker/build-push-action@v4 + with: + context: . + file: ./build/images/openim-tools/openim-web/Dockerfile + platforms: linux/amd64,linux/arm64 + push: ${{ github.event_name != 'pull_request' }} + tags: ${{ steps.meta1.outputs.tags }} + labels: ${{ steps.meta1.outputs.labels }} + cache-from: type=local,src=/tmp/.buildx-cache + cache-to: type=local,dest=/tmp/.buildx-cache + + - name: Extract metadata (tags, labels) for Docker openim-web + id: meta2 + uses: docker/metadata-action@v4.6.0 + with: + images: ghcr.io/openimsdk/component + + - name: Build and push Docker image for component + uses: docker/build-push-action@v4 + with: + context: . + file: ./build/images/openim-tools/component/Dockerfile + platforms: linux/amd64,linux/arm64 + push: ${{ github.event_name != 'pull_request' }} + tags: ${{ steps.meta2.outputs.tags }} + labels: ${{ steps.meta2.outputs.labels }} + cache-from: type=local,src=/tmp/.buildx-cache + cache-to: type=local,dest=/tmp/.buildx-cache diff --git a/.github/workflows/docker-buildx.yml b/.github/workflows/docker-buildx.yml index 9734e05e2..ae52fa242 100644 --- a/.github/workflows/docker-buildx.yml +++ b/.github/workflows/docker-buildx.yml @@ -319,67 +319,3 @@ jobs: cache-from: type=local,src=/tmp/.buildx-cache cache-to: type=local,dest=/tmp/.buildx-cache - build-tools-ghcr: - runs-on: ubuntu-latest - steps: - - name: Checkout - uses: actions/checkout@v3 - - - name: Set up QEMU - uses: docker/setup-qemu-action@v2 - - - name: Set up Docker Buildx - uses: docker/setup-buildx-action@v2 - with: - install: true - - - name: Cache Docker layers - uses: actions/cache@v3 - with: - path: /tmp/.buildx-cache - key: ${{ runner.os }}-buildx-${{ github.sha }} - restore-keys: | - ${{ runner.os }}-buildx- - - - name: Log in to GitHub Container Registry - uses: docker/login-action@v2 - with: - registry: ghcr.io - username: ${{ github.repository_owner }} - password: ${{ secrets.GITHUB_TOKEN }} - - - name: Extract metadata (tags, labels) for Docker openim-web - id: meta1 - uses: docker/metadata-action@v4.6.0 - with: - images: ghcr.io/openimsdk/openim-web - - - name: Build and push Docker image for openim-web - uses: docker/build-push-action@v4 - with: - context: . - file: ./build/images/openim-tools/openim-web/Dockerfile - platforms: linux/amd64,linux/arm64 - push: ${{ github.event_name != 'pull_request' }} - tags: ${{ steps.meta1.outputs.tags }} - labels: ${{ steps.meta1.outputs.labels }} - cache-from: type=local,src=/tmp/.buildx-cache - cache-to: type=local,dest=/tmp/.buildx-cache - - - name: Extract metadata (tags, labels) for Docker openim-web - id: meta2 - uses: docker/metadata-action@v4.6.0 - with: - images: ghcr.io/openimsdk/component - - - name: Build and push Docker image for component - uses: docker/build-push-action@v4 - with: - context: . - file: ./build/images/openim-tools/component/Dockerfile - platforms: linux/amd64,linux/arm64 - push: ${{ github.event_name != 'pull_request' }} - tags: ${{ steps.meta2.outputs.tags }} - labels: ${{ steps.meta2.outputs.labels }} - cache-from: type=local,src=/tmp/.buildx-cache - cache-to: type=local,dest=/tmp/.buildx-cache \ No newline at end of file diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 62b89e837..c14db3699 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -44,7 +44,7 @@ jobs: distribution: goreleaser version: latest workdir: . - args: release -f ./build/goreleaser.yaml --rm-dist --clean --release-footer-tmpl=scripts/template/footer.md.tmpl --release-header-tmpl=scripts/template/head.md.tmpl + args: release -f ./build/goreleaser.yaml --clean --release-footer-tmpl=scripts/template/footer.md.tmpl --release-header-tmpl=scripts/template/head.md.tmpl env: USERNAME: ${{ github.repository_owner }} GITHUB_TOKEN: ${{ secrets.BOT_GITHUB_TOKEN }} diff --git a/README-zh_CN.md b/README-zh_CN.md index 951112ae4..3d9eea319 100644 --- a/README-zh_CN.md +++ b/README-zh_CN.md @@ -116,7 +116,7 @@ Open-IM-Server 不仅仅是一个即时消息服务器;它是将实时网络 1. 克隆项目 ``` -bashCopy code# 选择您需要的 +# 选择您需要的 BRANCH=release-v3.1 git clone -b $BRANCH https://github.com/OpenIMSDK/Open-IM-Server openim && export openim=$(pwd)/openim && cd $openim && make build ``` @@ -126,7 +126,7 @@ git clone -b $BRANCH https://github.com/OpenIMSDK/Open-IM-Server openim && expor 1. 修改 `.env` ``` -bashCopy codeUSER=root #无需修改 +USER=root #无需修改 PASSWORD=openIM123 #8位或更多数字和字母的组合,此密码适用于redis、mysql、mongo,以及config/config.yaml中的accessSecret ENDPOINT=http://127.0.0.1:10005 #minio的外部服务IP和端口,或使用域名storage.xx.xx,应用程序必须能够访问此IP和端口或域名, API_URL=http://127.0.0.1:10002/object/ #应用程序必须能够访问此IP和端口或域名, @@ -138,14 +138,14 @@ DATA_DIR=./ #指定大磁盘目录 > **注意** 此命令只能执行一次。它会基于 `.env` 中的 `PASSWORD` 变量修改 docker-compose 中的组件密码,并修改 `config/config.yaml` 中的组件密码。如果 `.env` 中的密码发生变化,您需要首先执行 `docker-compose down`;`rm components -rf` 然后执行此命令。 ``` -bashCopy code + make install ``` 1. 检查服务 ``` -bashCopy code + make check ``` @@ -158,7 +158,7 @@ make check 版本详情:https://github.com/OpenIMSDK/Open-IM-Server/blob/main/docs/conversions/version.md ``` -bashCopy code# 选择您需要的 +# 选择您需要的 BRANCH=release-v3.1 git clone -b $BRANCH https://github.com/OpenIMSDK/Open-IM-Server openim && export openim=$(pwd)/openim && cd $openim && make build ``` @@ -180,7 +180,7 @@ config/config.yaml 文件为存储组件提供了详细的配置说明。 - 用于 RPC 服务发现和注册,支持集群。 ``` - bashCopy codezookeeper: + zookeeper: schema: openim #不建议修改 address: [ 127.0.0.1:2181 ] #地址 username: #用户名 @@ -192,7 +192,7 @@ config/config.yaml 文件为存储组件提供了详细的配置说明。 - 用于存储用户、关系和群组,支持主从数据库。 ``` - bashCopy codemysql: + mysql: address: [ 127.0.0.1:13306 ] #地址 username: root #用户名 password: openIM123 #密码 @@ -209,7 +209,7 @@ config/config.yaml 文件为存储组件提供了详细的配置说明。 - 用于存储离线消息,支持 mongo 分片集群。 ``` - bashCopy codemongo: + mongo: uri: #如果不为空,则直接使用此值 address: [ 127.0.0.1:37017 ] #地址 database: openIM #默认 mongo 数据库 @@ -223,7 +223,7 @@ config/config.yaml 文件为存储组件提供了详细的配置说明。 - 用于存储消息序列号、最新消息、用户令牌和 mysql 缓存,支持集群部署。 ``` - bashCopy coderedis: + redis: address: [ 127.0.0.1:16379 ] #地址 username: #用户名 password: openIM123 #密码 @@ -234,7 +234,7 @@ config/config.yaml 文件为存储组件提供了详细的配置说明。 - 用于消息队列,用于消息解耦,支持集群部署。 ``` - bashCopy codekafka: + kafka: username: #用户名 password: #密码 addr: [ 127.0.0.1:9092 ] #地址 @@ -259,21 +259,21 @@ config/config.yaml 文件为存储组件提供了详细的配置说明。 启动服务 ``` -bashCopy code + ./scripts/start-all.sh; ``` 检查服务 ``` -bashCopy code + ./scripts/check-all.sh ``` 停止服务 ``` -bashCopy code + ./scripts/stop-all.sh ``` diff --git a/README.md b/README.md index da496c23a..8e02b1a5d 100644 --- a/README.md +++ b/README.md @@ -128,14 +128,13 @@ $ make demo ```bash -git clone -b feat/test https://github.com/openim-sigs/openim-docker openim/openim-docker && export openim=$(pwd)/openim && cd $openim/openim-docker && ./scripts/init-config.sh && docker-compose up -d +git clone -b main https://github.com/openim-sigs/openim-docker openim/openim-docker && export openim=$(pwd)/openim && cd $openim/openim-docker && ./scripts/init-config.sh && docker-compose up -d ``` > **Note** > If you don't know OpenIM's versioning policy, 📚Read our release policy: https://github.com/OpenIMSDK/Open-IM-Server/blob/main/docs/conversions/version.md - **2. Configure the config file** If you tried to get started quickly with `make demo`, then you know that our config file is generated by automation. @@ -161,10 +160,15 @@ export SERVER_BRANCH="main" # MONGO_PASSWORD: Set the MongoDB password # MONGO_DATABASE: Sets the MongoDB database name # MINIO_ENDPOINT: set the MinIO service address +# DOCKER_BRIDGE_SUBNET: set the docker bridge network address +export DOCKER_BRIDGE_SUBNET="172.28.0.0/16" # API_URL: under network environment, set OpenIM Server API address export API_URL="http://127.0.0.1:10002" ``` +If you wish to use more custom features, read our [config documentation](https://github.com/OpenIMSDK/Open-IM-Server/blob/main/docs/contrib/environment.md). + + Next, update the configuration using make init: ```bash @@ -187,11 +191,26 @@ go version && make --version || echo "Error: One of the commands failed." Version Details: https://github.com/OpenIMSDK/Open-IM-Server/blob/main/docs/conversions/version.md +You can get the version number from the command below or from [github releases](https://github.com/OpenIMSDK/Open-IM-Server/tags). + +```bash +$ curl --silent "https://api.github.com/repos/OpenIMSDK/Open-IM-Server/releases" | jq -r '.[].tag_name' +``` + +We have our own version management policy, if you are interested in our version management, I recommend reading [📚 OpenIM Version](https://github.com/OpenIMSDK/Open-IM-Server/blob/main/docs/conversions/version.md), We recommend using stable versions such as `v3.3.0` and `v3.2.0` whenever possible. `v3.1.1-alpha.3` as well as `v3.3.0-beta.0` and `v3.2.0-rc.0` are pre-release or beta versions and are not recommended. + +Set `OPENIM_VERSION` environment variables for the latest `OPENIM_VERSION` number, or replace the `OPENIM_VERSION` for you to install the OpenIM-Server `OPENIM_VERSION`: + +```bash +$ OPENIM_VERSION=`curl -s https://api.github.com/repos/OpenIMSDK/Open-IM-Server/releases/latest | grep -oE '"tag_name": "[^"]+"' | head -n1 | cut -d'"' -f4` +# OPENIM_VERSION=v3.3.0 +``` + +Deploy basic components at the click of a command: + ```bash -# choose what you need -$ BRANCH=release-v3.2 # install openim dependency -$ git clone -b $BRANCH https://github.com/OpenIMSDK/Open-IM-Server openim/openim-server && export openim=$(pwd)/openim/openim-server && cd $openim/openim-server +$ git clone https://github.com/OpenIMSDK/Open-IM-Server openim/openim-server && export openim=$(pwd)/openim/openim-server && cd $openim/openim-server && git checkout $OPENIM_VERSION $ curl https://raw.githubusercontent.com/OpenIMSDK/openim-docker/main/example/basic-openim-server-dependency.yml -o basic-openim-server-dependency.yml && make init && docker compose -f basic-openim-server-dependency.yml up -d && make start ``` @@ -208,96 +227,16 @@ You can use the `make help-all` see OpenIM in action.
Component Configuration Instructions -The `config/config.yaml` file has detailed configuration instructions for the storage components. - - -The config file is available via [environment.sh](https://github.com/OpenIMSDK/Open-IM-Server/blob/main/scripts/install/environment.sh) configuration [openim.yaml](https://github.com/OpenIMSDK/Open-IM-Server/blob/main/deployments/templates/openim.yaml) template, and then through the `make init` to automatically generate a new configuration. - -- Zookeeper - - - Used for RPC service discovery and registration, cluster support. - - ```bash - zookeeper: - schema: openim #Not recommended to modify - address: [ 127.0.0.1:2181 ] #address - username: #username - password: #password - ``` - -- MySQL - - - Used for storing users, relationships, and groups, supports master-slave database. - - ```bash - mysql: - address: [ 127.0.0.1:13306 ] #address - username: root #username - password: openIM123 #password - database: openIM_v2 #Not recommended to modify - maxOpenConn: 1000 #maximum connection - maxIdleConn: 100 #maximum idle connection - maxLifeTime: 60 #maximum time a connection can be reused (seconds) - logLevel: 4 #log level 1=slient 2=error 3=warn 4=info - slowThreshold: 500 #slow statement threshold (milliseconds) - ``` - -- Mongo - - - Used for storing offline messages, supports mongo sharded clusters. - - ```bash - mongo: - uri: #Use this value directly if not empty - address: [ 127.0.0.1:37017 ] #address - database: openIM #default mongo db - username: root #username - password: openIM123 #password - maxPoolSize: 100 #maximum connections - ``` - -- Redis - - - Used for storing message sequence numbers, latest messages, user tokens, and mysql cache, supports cluster deployment. - - ```bash - redis: - address: [ 127.0.0.1:16379 ] #address - username: #username - password: openIM123 #password - ``` - -- Kafka - - - Used for message queues, for message decoupling, supports cluster deployment. - - ```bash - kafka: - username: #username - password: #password - addr: [ 127.0.0.1:9092 ] #address - latestMsgToRedis: - topic: "latestMsgToRedis" - offlineMsgToMongo: - topic: "offlineMsgToMongoMysql" - msgToPush: - topic: "msqToPush" - msgToModify: - topic: "msgToModify" - consumerGroupID: - msgToRedis: redis - msgToMongo: mongo - msgToMySql: mysql - msgToPush: push - msgToModify: modify - ``` + +Read: Configuration center document:https://github.com/OpenIMSDK/Open-IM-Server/blob/main/docs/contrib/environment.md
+
Deployed with kubernetes -read: https://github.com/OpenIMSDK/Open-IM-Server/blob/main/deployments/README.md +Read: https://github.com/OpenIMSDK/Open-IM-Server/blob/main/deployments/README.md
diff --git a/deployments/README.md b/deployments/README.md index 4e4beda9f..7e3d47854 100644 --- a/deployments/README.md +++ b/deployments/README.md @@ -41,6 +41,63 @@ $ make init ``` 此时会帮你在 `deployments/openim/config` 目录下生成配置文件,你可以根据自己的需求进行修改。 + +## 集群搭建 + +如果你已经有了一个 `kubernetes` 集群,或者是你希望自己从头开始搭建一个 `kubernetes` 那么你可以直接跳过这一步。 + +为了快速开始,我使用 [sealos](https://github.com/labring/sealos) 来快速搭建集群,sealos 底层也是对 kubeadm 的封装: + +```bash +$ SEALOS_VERSION=`curl -s https://api.github.com/repos/labring/sealos/releases/latest | grep -oE '"tag_name": "[^"]+"' | head -n1 | cut -d'"' -f4` && \ + curl -sfL https://raw.githubusercontent.com/labring/sealos/${SEALOS_VERSION}/scripts/install.sh | + sh -s ${SEALOS_VERSION} labring/sealos +``` + +**支持的版本:** + ++ docker: `labring/kubernetes-docker`:(v1.24.0~v1.27.0) ++ containerd: `labring/kubernetes`:(v1.24.0~v1.27.0) + + +#### 安装集群: + +集群的信息如下: + +| 机器名 | IP地址 | 系统信息 | +|---------|-----------------|------------------------------------------------------------------------------------------------------------| +| master01| 10.0.0.9 | Linux VM-0-9-ubuntu 5.15.0-76-generic #83-Ubuntu SMP Thu Jun 15 19:16:32 UTC 2023 x86_64 x86_64 x86_64 GNU/Linux | +| node01 | 10.0.0.4 | Linux VM-0-9-ubuntu 5.15.0-76-generic #83-Ubuntu SMP Thu Jun 15 19:16:32 UTC 2023 x86_64 x86_64 x86_64 GNU/Linux | +| node02 | 10.0.0.10 | Linux VM-0-9-ubuntu 5.15.0-76-generic #83-Ubuntu SMP Thu Jun 15 19:16:32 UTC 2023 x86_64 x86_64 x86_64 GNU/Linux | + +```bash +$ export CLUSTER_USERNAME=ubuntu +$ export CLUSTER_PASSWORD=123456 +$ sealos run labring/kubernetes:v1.25.0 labring/helm:v3.8.2 labring/calico:v3.24.1 \ + --masters 10.0.0.9 \ + --nodes 10.0.0.4,10.0.0.10 \ + -u "$CLUSTER_USERNAME" \ + -p "$CLUSTER_PASSWORD" +``` + +### 安装 helm + +helm通过打包的方式,支持发布的版本管理和控制,很大程度上简化了Kubernetes应用的部署和管理。 + + +**使用脚本:** + +```bash +$ curl https://raw.githubusercontent.com/helm/helm/main/scripts/get-helm-3 | bash +``` + +**添加仓库:** + +```bash +$ helm repo add brigade https://openimsdk.github.io/openim-charts +``` + + ### 容器化安装 具体安装步骤如下: diff --git a/docker-compose.yml b/docker-compose.yml index 4e9aa02b9..b08c12097 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -164,40 +164,40 @@ services: server: ipv4_address: ${OPENIM_WEB_NETWORK_ADDRESS} - openim-server: - # image: ghcr.io/openimsdk/openim-server:main - image: registry.cn-hangzhou.aliyuncs.com/openimsdk/openim-server:main - # image: openim/openim-server:main -# build: . - container_name: openim-server - ports: - - ${OPENIM_WS_PORT}:10001 - - ${API_OPENIM_PORT}:10002 - healthcheck: - test: ["CMD", "/openim/openim-server/scripts/check-all.sh"] - interval: 300s - timeout: 10s - retries: 5 - volumes: - - ./logs:/openim/openim-server/logs - - ./_output:/openim/openim-server/_output - - ./config:/openim/openim-server/config - - ./scripts:/openim/openim-server/scripts - restart: always - depends_on: - - kafka - - mysql - - mongodb - - redis - - minio - logging: - driver: json-file - options: - max-size: "1g" - max-file: "2" - networks: - server: - ipv4_address: ${OPENIM_SERVER_NETWORK_ADDRESS} +# openim-server: +# # image: ghcr.io/openimsdk/openim-server:main +# image: registry.cn-hangzhou.aliyuncs.com/openimsdk/openim-server:main +# # image: openim/openim-server:main +# # build: . +# container_name: openim-server +# ports: +# - ${OPENIM_WS_PORT}:10001 +# - ${API_OPENIM_PORT}:10002 +# healthcheck: +# test: ["CMD", "/openim/openim-server/scripts/check-all.sh"] +# interval: 300s +# timeout: 10s +# retries: 5 +# volumes: +# - ./logs:/openim/openim-server/logs +# - ./_output:/openim/openim-server/_output +# - ./config:/openim/openim-server/config +# - ./scripts:/openim/openim-server/scripts +# restart: always +# depends_on: +# - kafka +# - mysql +# - mongodb +# - redis +# - minio +# logging: +# driver: json-file +# options: +# max-size: "1g" +# max-file: "2" +# networks: +# server: +# ipv4_address: ${OPENIM_SERVER_NETWORK_ADDRESS} # openim-chat: # # image: ghcr.io/openimsdk/openim-chat:main diff --git a/docs/contrib/environment.md b/docs/contrib/environment.md new file mode 100644 index 000000000..51baa5ae0 --- /dev/null +++ b/docs/contrib/environment.md @@ -0,0 +1,138 @@ +# OpenIM enviroment + + +## How to change the configuration + + +**Modify the configuration files:** + +Three ways to modify the configuration: + +#### **1. Recommended using environment variables:** + +```bash +export PASSWORD="openIM123" # Set password +export USER="root" # Set username +# Choose chat version and server version https://github.com/OpenIMSDK/Open-IM-Server/blob/main/docs/conversions/images.md, eg: main, release-v*.* +export CHAT_BRANCH="main" +export SERVER_BRANCH="main" +#... Other environment variables +# MONGO_USERNAME: This sets the MongoDB username +# MONGO_PASSWORD: Set the MongoDB password +# MONGO_DATABASE: Sets the MongoDB database name +# MINIO_ENDPOINT: set the MinIO service address +# API_URL: under network environment, set OpenIM Server API address +export API_URL="http://127.0.0.1:10002" +``` + +Next, update the configuration using `make init`: + +```bash +make init +``` + +#### **2. Modify the automation script:** + +```bash +scripts/install/environment.sh +``` + +Next, update the configuration using `make init`: + +```bash +make init +``` + +#### 3. Modify `config.yaml` and `.env` files (but will be overwritten when using `make init` again). + +The `config/config.yaml` file has detailed configuration instructions for the storage components. + + +The config file is available via [environment.sh](https://github.com/OpenIMSDK/Open-IM-Server/blob/main/scripts/install/environment.sh) configuration [openim.yaml](https://github.com/OpenIMSDK/Open-IM-Server/blob/main/deployments/templates/openim.yaml) template, and then through the `make init` to automatically generate a new configuration. + + + + +## Configuration Details + +###### Zookeeper + +- **Purpose**: Used for RPC service discovery and registration, cluster support. + + ```bash + zookeeper: + schema: openim # Not recommended to modify + address: [ 127.0.0.1:2181 ] # Address + username: # Username + password: # Password + ``` + +###### MySQL + +- **Purpose**: Used for storing users, relationships, and groups. Supports master-slave database. + + ```bash + mysql: + address: [ 127.0.0.1:13306 ] # Address + username: root # Username + password: openIM123 # Password + database: openIM_v2 # Not recommended to modify + maxOpenConn: 1000 # Maximum connection + maxIdleConn: 100 # Maximum idle connection + maxLifeTime: 60 # Max time a connection can be reused (seconds) + logLevel: 4 # Log level (1=silent, 2=error, 3=warn, 4=info) + slowThreshold: 500 # Slow statement threshold (milliseconds) + ``` + +###### Mongo + +- **Purpose**: Used for storing offline messages. Supports mongo sharded clusters. + + ```bash + mongo: + uri: # Use this value directly if not empty + address: [ 127.0.0.1:37017 ] # Address + database: openIM # Default mongo db + username: root # Username + password: openIM123 # Password + maxPoolSize: 100 # Maximum connections + ``` + +###### Redis + +- **Purpose**: Used for storing message sequence numbers, latest messages, user tokens, and MySQL cache. Supports cluster deployment. + + ```bash + redis: + address: [ 127.0.0.1:16379 ] # Address + username: # Username + password: openIM123 # Password + ``` + +###### Kafka + +- **Purpose**: Used for message queues for decoupling. Supports cluster deployment. + + ```bash + kafka: + username: # Username + password: # Password + addr: [ 127.0.0.1:9092 ] # Address + topics: + latestMsgToRedis: "latestMsgToRedis" + offlineMsgToMongo: "offlineMsgToMongoMysql" + msgToPush: "msgToPush" + msgToModify: "msgToModify" + consumerGroupID: + msgToRedis: redis + msgToMongo: mongo + msgToMySql: mysql + msgToPush: push + msgToModify: modify + ``` + + + +## Config options + +... \ No newline at end of file diff --git a/docs/contrib/linux_development.md b/docs/contrib/linux_development.md index e5569a069..4a0cb9f18 100644 --- a/docs/contrib/linux_development.md +++ b/docs/contrib/linux_development.md @@ -19,7 +19,7 @@ You can use tools like PuTTY or other SSH clients to log in to your Ubuntu serve Generally, a project will involve multiple developers. Instead of provisioning a server for every developer, many organizations share a single development machine among developers. To simulate this real-world scenario, we'll use a standard user for development. To create the `openim` user: ``` -bashCopy code# adduser openim # Create the openim user, which developers will use for login and development. +# adduser openim # Create the openim user, which developers will use for login and development. # passwd openim # Set the login password for openim. ``` @@ -30,7 +30,7 @@ Working with a non-root user ensures the system's safety and is a good practice. Often, even standard users need root privileges. Instead of frequently asking the system administrator for the root password, you can add the standard user to the sudoers. This allows them to temporarily gain root access using the sudo command. To add the `openim` user to sudoers: ``` -bashCopy code + # sed -i '/^root.*ALL=(ALL:ALL).*ALL/a\openim\tALL=(ALL) \tALL' /etc/sudoers ``` @@ -45,7 +45,7 @@ Assuming we're using the **openim** user, log in using PuTTY or other SSH client The first step after logging into a new server is to configure the `$HOME/.bashrc` file. It makes the Linux shell more user-friendly by setting environment variables like `LANG` and `PS1`. Here's how the configuration would look: ``` -bashCopy code# .bashrc +# .bashrc # User specific aliases and functions @@ -82,7 +82,7 @@ The OpenIM project on Ubuntu may have various dependencies. Some are direct, and You can use the `apt` command to install the required tools on Ubuntu: ``` -bashCopy code$ sudo apt-get update +$ sudo apt-get update $ sudo apt-get install build-essential autoconf automake cmake perl libcurl4-gnutls-dev libtool gcc g++ glibc-doc-reference zlib1g-dev git-lfs telnet lrzsz jq libexpat1-dev libssl-dev $ sudo apt install libcurl4-openssl-dev ``` @@ -92,7 +92,7 @@ $ sudo apt install libcurl4-openssl-dev A higher version of Git ensures compatibility with certain commands like `git fetch --unshallow`. To install a recent version: ``` -bashCopy code$ cd /tmp +$ cd /tmp $ wget --no-check-certificate https://mirrors.edge.kernel.org/pub/software/scm/git/git-2.36.1.tar.gz $ tar -xvzf git-2.36.1.tar.gz $ cd git-2.36.1/ @@ -105,7 +105,7 @@ $ git --version Then, add Git's binary directory to the `PATH`: ``` -bashCopy code + $ echo 'export PATH=/usr/local/libexec/git-core:$PATH' >> $HOME/.bashrc ``` @@ -114,7 +114,7 @@ $ echo 'export PATH=/usr/local/libexec/git-core:$PATH' >> $HOME/.bashrc To set up Git: ``` -bashCopy code$ git config --global user.name "Your Name" +$ git config --global user.name "Your Name" $ git config --global user.email "your_email@example.com" $ git config --global credential.helper store $ git config --global core.longpaths true @@ -123,14 +123,14 @@ $ git config --global core.longpaths true Other Git configurations include: ``` -bashCopy code + $ git config --global core.quotepath off ``` And for handling larger files: ``` -bashCopy code + $ git lfs install --skip-repo ``` diff --git a/docs/contrib/protoc_tools.md b/docs/contrib/protoc_tools.md index 66f2e9dec..b164728d3 100644 --- a/docs/contrib/protoc_tools.md +++ b/docs/contrib/protoc_tools.md @@ -44,7 +44,7 @@ For Linux: The OpenIM Protoc tool provides a multitude of options for parsing `.proto` files and generating output: ``` -bashCopy code + ./protoc [OPTION] PROTO_FILES ``` diff --git a/docs/conversions/version.md b/docs/conversions/version.md index 490c8e4a7..0e6a26988 100644 --- a/docs/conversions/version.md +++ b/docs/conversions/version.md @@ -82,7 +82,7 @@ We reinforce our approach to branch management and versioning with stringent tes The workflow to address a bug fix might follow these steps: ```bash -bashCopy codebashCopy code# Checkout the branch for the version that needs the bug fix +# Checkout the branch for the version that needs the bug fix git checkout release-v3.1 # Create a new branch for the bug fix @@ -121,4 +121,3 @@ Throughout this process, active communication within the team is pivotal to main ## Docker Images Version Management For more details on managing Docker image versions, visit [OpenIM Docker Images Administration](https://github.com/OpenIMSDK/Open-IM-Server/blob/main/docs/conversions/images.md). - diff --git a/go.mod b/go.mod index 21dbce086..8e146b7cc 100644 --- a/go.mod +++ b/go.mod @@ -4,7 +4,6 @@ go 1.18 require ( firebase.google.com/go v3.13.0+incompatible - github.com/Shopify/sarama v1.29.0 github.com/bwmarrin/snowflake v0.3.0 // indirect github.com/dtm-labs/rockscache v0.1.1 github.com/gin-gonic/gin v1.9.1 @@ -37,6 +36,7 @@ require ( require github.com/google/uuid v1.3.1 require ( + github.com/IBM/sarama v1.41.1 github.com/OpenIMSDK/protocol v0.0.18 github.com/OpenIMSDK/tools v0.0.14 github.com/aliyun/aliyun-oss-go-sdk v2.2.9+incompatible @@ -62,8 +62,8 @@ require ( github.com/davecgh/go-spew v1.1.1 // indirect github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect github.com/dustin/go-humanize v1.0.1 // indirect - github.com/eapache/go-resiliency v1.2.0 // indirect - github.com/eapache/go-xerial-snappy v0.0.0-20180814174437-776d5712da21 // indirect + github.com/eapache/go-resiliency v1.4.0 // indirect + github.com/eapache/go-xerial-snappy v0.0.0-20230731223053-c322873962e3 // indirect github.com/eapache/queue v1.1.0 // indirect github.com/gabriel-vasile/mimetype v1.4.2 // indirect github.com/gin-contrib/sse v0.1.0 // indirect @@ -76,12 +76,14 @@ require ( github.com/google/s2a-go v0.1.5 // indirect github.com/googleapis/enterprise-certificate-proxy v0.2.5 // indirect github.com/googleapis/gax-go/v2 v2.12.0 // indirect - github.com/hashicorp/go-uuid v1.0.2 // indirect + github.com/hashicorp/errwrap v1.0.0 // indirect + github.com/hashicorp/go-multierror v1.1.1 // indirect + github.com/hashicorp/go-uuid v1.0.3 // indirect github.com/inconshreveable/mousetrap v1.1.0 // indirect github.com/jcmturner/aescts/v2 v2.0.0 // indirect github.com/jcmturner/dnsutils/v2 v2.0.0 // indirect - github.com/jcmturner/gofork v1.0.0 // indirect - github.com/jcmturner/gokrb5/v8 v8.4.2 // indirect + github.com/jcmturner/gofork v1.7.6 // indirect + github.com/jcmturner/gokrb5/v8 v8.4.4 // indirect github.com/jcmturner/rpc/v2 v2.0.3 // indirect github.com/jinzhu/inflection v1.0.0 // indirect github.com/jinzhu/now v1.1.5 // indirect @@ -100,7 +102,7 @@ require ( github.com/onsi/ginkgo v1.16.5 // indirect github.com/onsi/gomega v1.18.1 // indirect github.com/pelletier/go-toml/v2 v2.0.8 // indirect - github.com/pierrec/lz4 v2.6.0+incompatible // indirect + github.com/pierrec/lz4/v4 v4.1.18 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect github.com/prometheus/client_model v0.3.0 // indirect github.com/prometheus/common v0.42.0 // indirect diff --git a/go.sum b/go.sum index 45165e608..e94b507ae 100644 --- a/go.sum +++ b/go.sum @@ -17,15 +17,13 @@ cloud.google.com/go/storage v1.30.1/go.mod h1:NfxhC0UJE1aXSx7CIIbCf7y9HKT7Biccwk firebase.google.com/go v3.13.0+incompatible h1:3TdYC3DDi6aHn20qoRkxwGqNgdjtblwVAyRLQwGn/+4= firebase.google.com/go v3.13.0+incompatible/go.mod h1:xlah6XbEyW6tbfSklcfe5FHJIwjt8toICdV5Wh9ptHs= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= +github.com/IBM/sarama v1.41.1 h1:B4/TdHce/8Ipza+qrLIeNJ9D1AOxZVp/3uDv6H/dp2M= +github.com/IBM/sarama v1.41.1/go.mod h1:JFCPURVskaipJdKRFkiE/OZqQHw7jqliaJmRwXCmSSw= github.com/OpenIMSDK/protocol v0.0.18 h1:hXukFiDMLZx7s+hDCQePIK9ABiHyNlobNL4MppvOuMY= github.com/OpenIMSDK/protocol v0.0.18/go.mod h1:F25dFrwrIx3lkNoiuf6FkCfxuwf8L4Z8UIsdTHP/r0Y= github.com/OpenIMSDK/tools v0.0.14 h1:WLof/+WxyPyRST+QkoTKubYCiV73uCLiL8pgnpH/yKQ= github.com/OpenIMSDK/tools v0.0.14/go.mod h1:eg+q4A34Qmu73xkY0mt37FHGMCMfC6CtmOnm0kFEGFI= github.com/QcloudApi/qcloud_sign_golang v0.0.0-20141224014652-e4130a326409/go.mod h1:1pk82RBxDY/JZnPQrtqHlUFfCctgdorsd9M06fMynOM= -github.com/Shopify/sarama v1.29.0 h1:ARid8o8oieau9XrHI55f/L3EoRAhm9px6sonbD7yuUE= -github.com/Shopify/sarama v1.29.0/go.mod h1:2QpgD79wpdAESqNQMxNc0KYMkycd4slxGdV3TWSVqrU= -github.com/Shopify/toxiproxy v2.1.4+incompatible h1:TKdv8HiTLgE5wdJuEML90aBgNWsokNbMijUGhmcoBJc= -github.com/Shopify/toxiproxy v2.1.4+incompatible/go.mod h1:OXgGpZ6Cli1/URJOF1DMxUHB2q5Ap20/P/eIdh4G0pI= github.com/aliyun/aliyun-oss-go-sdk v2.2.9+incompatible h1:Sg/2xHwDrioHpxTN6WMiwbXTpUEinBpHsN7mG21Rc2k= github.com/aliyun/aliyun-oss-go-sdk v2.2.9+incompatible/go.mod h1:T/Aws4fEfogEE9v+HPhhw+CntffsBHJ8nXQCwKr0/g8= github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY= @@ -59,7 +57,6 @@ github.com/cncf/xds/go v0.0.0-20210805033703-aa0b78936158/go.mod h1:eXthEFrGJvWH github.com/cncf/xds/go v0.0.0-20210922020428-25de7278fc84/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= github.com/cncf/xds/go v0.0.0-20211011173535-cb28da3451f1/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= github.com/cpuguy83/go-md2man/v2 v2.0.2/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= -github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= @@ -69,10 +66,10 @@ github.com/dtm-labs/rockscache v0.1.1 h1:6S1vgaHvGqrLd8Ka4hRTKeKPV7v+tT0MSkTIX81 github.com/dtm-labs/rockscache v0.1.1/go.mod h1:c76WX0kyIibmQ2ACxUXvDvaLykoPakivMqIxt+UzE7A= github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY= github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto= -github.com/eapache/go-resiliency v1.2.0 h1:v7g92e/KSN71Rq7vSThKaWIq68fL4YHvWyiUKorFR1Q= -github.com/eapache/go-resiliency v1.2.0/go.mod h1:kFI+JgMyC7bLPUVY133qvEBtVayf5mFgVsvEsIPBvNs= -github.com/eapache/go-xerial-snappy v0.0.0-20180814174437-776d5712da21 h1:YEetp8/yCZMuEPMUDHG0CW/brkkEp8mzqk2+ODEitlw= -github.com/eapache/go-xerial-snappy v0.0.0-20180814174437-776d5712da21/go.mod h1:+020luEh2TKB4/GOp8oxxtq0Daoen/Cii55CzbTV6DU= +github.com/eapache/go-resiliency v1.4.0 h1:3OK9bWpPk5q6pbFAaYSEwD9CLUSHG8bnZuqX2yMt3B0= +github.com/eapache/go-resiliency v1.4.0/go.mod h1:5yPzW0MIvSe0JDsv0v+DvcjEv2FyD6iZYSs1ZI+iQho= +github.com/eapache/go-xerial-snappy v0.0.0-20230731223053-c322873962e3 h1:Oy0F4ALJ04o5Qqpdz8XLIpNA3WM/iSIXqxtqo7UGVws= +github.com/eapache/go-xerial-snappy v0.0.0-20230731223053-c322873962e3/go.mod h1:YvSRo5mw33fLEx1+DlK6L2VV43tJt5Eyel9n9XBcR+0= github.com/eapache/queue v1.1.0 h1:YOEu7KNc61ntiQlcEeUIoDTJ2o8mQznoNvUhiigpIqc= github.com/eapache/queue v1.1.0/go.mod h1:6eCeP0CKFpHLu8blIFXhExK/dRa7WDZfr6jVFPTqq+I= github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= @@ -82,9 +79,6 @@ github.com/envoyproxy/go-control-plane v0.9.9-0.20201210154907-fd9021fe5dad/go.m github.com/envoyproxy/go-control-plane v0.9.10-0.20210907150352-cf90f659a021/go.mod h1:AFq3mo9L8Lqqiid3OhADV3RfLJnjiw63cSpi+fDTRC0= github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= github.com/fortytw2/leaktest v1.3.0 h1:u8491cBMTQ8ft8aeV+adlcytMZylmA5nnwwkRZjI8vw= -github.com/fortytw2/leaktest v1.3.0/go.mod h1:jDsjWgpAGjm2CA7WthBh/CdZYEPF31XHquHwclZch5g= -github.com/frankban/quicktest v1.11.3 h1:8sXhOn0uLys67V8EsXLc6eszDs8VXWxL3iRvebPhedY= -github.com/frankban/quicktest v1.11.3/go.mod h1:wRf/ReqHper53s+kmmSZizM8NamnL3IM0I9ntUbOk+k= github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= github.com/fsnotify/fsnotify v1.4.9 h1:hsms1Qyu0jgnwNXIxa+/V/PDsU6CfLf6CNO8H7IWoS4= github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ= @@ -139,7 +133,6 @@ github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiu github.com/golang/protobuf v1.5.3 h1:KhyjKVUg7Usr/dYsdSqoFveMYd5ko72D+zANwlG1mmg= github.com/golang/protobuf v1.5.3/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= github.com/golang/snappy v0.0.1/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= -github.com/golang/snappy v0.0.3/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= github.com/golang/snappy v0.0.4 h1:yAGX7huGHXlcLOEtBnF4w7FQwA26wojNCwOYAEhLjQM= github.com/golang/snappy v0.0.4/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= @@ -149,7 +142,6 @@ github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/ github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.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.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38= github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= @@ -176,8 +168,13 @@ github.com/gorilla/websocket v1.5.0/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/ad github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0 h1:Ovs26xHkKqVztRpIrF/92BcuyuQ/YW4NSIpoGtfXNho= 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/go-uuid v1.0.2 h1:cfejS+Tpcp13yd5nYHWDI6qVCny6wyX2Mt5SGur2IGE= +github.com/hashicorp/errwrap v1.0.0 h1:hLrqtEDnRye3+sgx6z4qVLNuviH3MR5aQ0ykNJa/UYA= +github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= +github.com/hashicorp/go-multierror v1.1.1 h1:H5DkEtf6CXdFp0N0Em5UCwQpXMWke8IA0+lD48awMYo= +github.com/hashicorp/go-multierror v1.1.1/go.mod h1:iw975J/qwKPdAO1clOe2L8331t/9/fmwbPZ6JB6eMoM= github.com/hashicorp/go-uuid v1.0.2/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= +github.com/hashicorp/go-uuid v1.0.3 h1:2gKiV6YVmrJ1i2CKKa9obLvRieoRGviZFL26PcT/Co8= +github.com/hashicorp/go-uuid v1.0.3/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= 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/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8= @@ -186,12 +183,12 @@ github.com/jcmturner/aescts/v2 v2.0.0 h1:9YKLH6ey7H4eDBXW8khjYslgyqG2xZikXP0EQFK github.com/jcmturner/aescts/v2 v2.0.0/go.mod h1:AiaICIRyfYg35RUkr8yESTqvSy7csK90qZ5xfvvsoNs= github.com/jcmturner/dnsutils/v2 v2.0.0 h1:lltnkeZGL0wILNvrNiVCR6Ro5PGU/SeBvVO/8c/iPbo= github.com/jcmturner/dnsutils/v2 v2.0.0/go.mod h1:b0TnjGOvI/n42bZa+hmXL+kFJZsFT7G4t3HTlQ184QM= -github.com/jcmturner/gofork v1.0.0 h1:J7uCkflzTEhUZ64xqKnkDxq3kzc96ajM1Gli5ktUem8= -github.com/jcmturner/gofork v1.0.0/go.mod h1:MK8+TM0La+2rjBD4jE12Kj1pCCxK7d2LK/UM3ncEo0o= +github.com/jcmturner/gofork v1.7.6 h1:QH0l3hzAU1tfT3rZCnW5zXl+orbkNMMRGJfdJjHVETg= +github.com/jcmturner/gofork v1.7.6/go.mod h1:1622LH6i/EZqLloHfE7IeZ0uEJwMSUyQ/nDd82IeqRo= github.com/jcmturner/goidentity/v6 v6.0.1 h1:VKnZd2oEIMorCTsFBnJWbExfNN7yZr3EhJAxwOkZg6o= github.com/jcmturner/goidentity/v6 v6.0.1/go.mod h1:X1YW3bgtvwAXju7V3LCIMpY0Gbxyjn/mY9zx4tFonSg= -github.com/jcmturner/gokrb5/v8 v8.4.2 h1:6ZIM6b/JJN0X8UM43ZOM6Z4SJzla+a/u7scXFJzodkA= -github.com/jcmturner/gokrb5/v8 v8.4.2/go.mod h1:sb+Xq/fTY5yktf/VxLsE3wlfPqQjp0aWNYyvBVK62bc= +github.com/jcmturner/gokrb5/v8 v8.4.4 h1:x1Sv4HaTpepFkXbt2IkL29DXRf8sOfZXo8eRKh687T8= +github.com/jcmturner/gokrb5/v8 v8.4.4/go.mod h1:1btQEpgT6k+unzCwX1KdWMEwPPkkgBtP+F6aCACiMrs= github.com/jcmturner/rpc/v2 v2.0.3 h1:7FXXj8Ti1IaVFpSAziCZWNzbNuZmnvw/i6CqLNdWfZY= github.com/jcmturner/rpc/v2 v2.0.3/go.mod h1:VUJYCIDm3PVOEHw8sgt091/20OJjskO/YJki3ELg/Hc= github.com/jinzhu/copier v0.4.0 h1:w3ciUoD19shMCRargcpm0cm91ytaBhDvuRpz1ODO/U8= @@ -205,7 +202,6 @@ github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnr github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8= github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= -github.com/klauspost/compress v1.12.2/go.mod h1:8dP1Hq4DHOhN9w426knH3Rhby4rFm6D8eO+e+Dq5Gzg= github.com/klauspost/compress v1.13.6/go.mod h1:/3/Vjq9QcHkK5uEr5lBEmyoZ1iFhe47etQ6QUkpK6sk= github.com/klauspost/compress v1.16.7 h1:2mk3MPGNzKyxErAw8YaohYh69+pa4sIQSC0fPGCFR9I= github.com/klauspost/compress v1.16.7/go.mod h1:ntbaceVETuRiXiv4DpjP66DpAtAGkEQskQzEyD//IeE= @@ -213,12 +209,8 @@ github.com/klauspost/cpuid/v2 v2.0.1/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa02 github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg= github.com/klauspost/cpuid/v2 v2.2.5 h1:0E5MSMDEoAulmXNFquVs//DdoomxaoTY1kUhbc/qbZg= github.com/klauspost/cpuid/v2 v2.2.5/go.mod h1:Lcz8mBdAVJIBVzewtcLocK12l3Y+JytZYpaMropDUws= -github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= -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/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= github.com/leodido/go-urn v1.2.4 h1:XlAE/cm/ms7TE/VMVoduSpNBoyc2dOxHs5MZSwAN63Q= github.com/leodido/go-urn v1.2.4/go.mod h1:7ZrI8mTSeBSHl/UaRyKQW1qZeMgak41ANeCNaVckg+4= github.com/lestrrat-go/envload v0.0.0-20180220234015-a3eb8ddeffcc h1:RKf14vYWi2ttpEmkA4aQ3j4u9dStX2t4M8UM6qqNsG8= @@ -270,8 +262,8 @@ github.com/onsi/gomega v1.18.1 h1:M1GfJqGRrBrrGGsbxzV5dqM2U2ApXefZCQpkukxYRLE= github.com/onsi/gomega v1.18.1/go.mod h1:0q+aL8jAiMXy9hbwj2mr5GziHiwhAIQpFmmtT5hitRs= github.com/pelletier/go-toml/v2 v2.0.8 h1:0ctb6s9mE31h0/lhu+J6OPmVeDxJn+kYnJc2jZR9tGQ= github.com/pelletier/go-toml/v2 v2.0.8/go.mod h1:vuYfssBdrU2XDZ9bYydBu6t+6a6PYNcZljzZR9VXg+4= -github.com/pierrec/lz4 v2.6.0+incompatible h1:Ix9yFKn1nSPBLFl/yZknTp8TU5G4Ps0JDmguYK6iH1A= -github.com/pierrec/lz4 v2.6.0+incompatible/go.mod h1:pdkljMzZIN41W+lC3N2tnIh5sFi+IEE17M5jbnwPHcY= +github.com/pierrec/lz4/v4 v4.1.18 h1:xaKrnTkyoqfh1YItXl56+6KJNVYWlEEPuAQW9xsplYQ= +github.com/pierrec/lz4/v4 v4.1.18/go.mod h1:gZWDp/Ze/IJXGXf23ltt2EXimqmTUXEy0GFuRQyBid4= github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= @@ -308,7 +300,6 @@ github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpE 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= @@ -331,8 +322,6 @@ github.com/xdg-go/scram v1.1.2 h1:FHX5I5B4i4hKRVRBCFRxq1iQRej7WO3hhBuJf+UUySY= github.com/xdg-go/scram v1.1.2/go.mod h1:RT/sEzTbU5y00aCK8UOx6R7YryM0iF1N2MOmC3kKLN4= github.com/xdg-go/stringprep v1.0.4 h1:XLI/Ng3O1Atzq0oBs3TWm+5ZVgkq2aqdlvP9JtoZ6c8= github.com/xdg-go/stringprep v1.0.4/go.mod h1:mPGuuIYwz7CmR2bT9j4GbQqutWS1zV24gijq1dTyGkM= -github.com/xdg/scram v1.0.3/go.mod h1:lB8K/P019DLNhemzwFU4jHLhdvlE6uDZjXFejJXr49I= -github.com/xdg/stringprep v1.0.3/go.mod h1:Jhud4/sHMO4oL310DaZAKk9ZaJ08SJfe+sJh0HrGL1Y= github.com/youmark/pkcs8 v0.0.0-20181117223130-1be2e3e5546d h1:splanxYIlg+5LfHAM6xpdFEAYOk8iySO56hMFq6uLyA= github.com/youmark/pkcs8 v0.0.0-20181117223130-1be2e3e5546d/go.mod h1:rHwXgn7JulP+udvsHwJoVG1YGAP6VLg4y9I5dyZdqmA= github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= @@ -356,11 +345,10 @@ golang.org/x/arch v0.3.0/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8= 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-20201112155050-0c6587e931a9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= -golang.org/x/crypto v0.0.0-20210421170649-83a5a9bb288b/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4= golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/crypto v0.0.0-20220314234659-1baeb1ce4c0b/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= +golang.org/x/crypto v0.6.0/go.mod h1:OFC/31mSvZgRz0V1QTNCzfAI1aIRzbiufJtkMIlEp58= golang.org/x/crypto v0.12.0 h1:tFM/ta59kqch6LlvYnPa0yx5a83cL2nHflFhYKvv9Yk= golang.org/x/crypto v0.12.0/go.mod h1:NF0Gs7EO5K4qLn+Ylc+fih8BSTeIjAP05siRnAh98yw= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= @@ -389,11 +377,11 @@ golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81R golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= golang.org/x/net v0.0.0-20201110031124-69a78807bb2b/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= -golang.org/x/net v0.0.0-20210427231257-85d9c07bbe3a/go.mod h1:OJAsFXCWl8Ukc7SiCT/9KSuxbyM7479/AVlXFRxuMCk= golang.org/x/net v0.0.0-20210428140749-89ef3d95e781/go.mod h1:OJAsFXCWl8Ukc7SiCT/9KSuxbyM7479/AVlXFRxuMCk= golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= +golang.org/x/net v0.7.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= golang.org/x/net v0.14.0 h1:BONx9s002vGdD9umnlX1Po8vOZmrgH34qlHcD1MfK14= golang.org/x/net v0.14.0/go.mod h1:PpSgVXXLK0OxS0F31C1/tv6XNguvCrnXIDrFMspZIUI= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= @@ -504,7 +492,6 @@ google.golang.org/protobuf v1.31.0 h1:g0LDEJHgrBl9N9r17Ru3sqWhkIx2NB67okBHPwC7hs google.golang.org/protobuf v1.31.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= -gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys= gopkg.in/ini.v1 v1.67.0 h1:Dgnx+6+nfE+IfzjUEISNeydPJh9AXNNsWbGP9KzCsOA= gopkg.in/ini.v1 v1.67.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= @@ -517,7 +504,6 @@ 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= gorm.io/driver/mysql v1.5.1 h1:WUEH5VF9obL/lTtzjmML/5e6VfFR/788coz2uaVCAZw= diff --git a/go.work b/go.work index 853ffd5a3..c95cb7e93 100644 --- a/go.work +++ b/go.work @@ -4,9 +4,10 @@ use ( . ./test/typecheck ./tools/changelog + ./tools/imctl ./tools/infra ./tools/ncpu - ./tools/versionchecker ./tools/openim-web + ./tools/versionchecker ./tools/yamlfmt ) diff --git a/internal/msggateway/client.go b/internal/msggateway/client.go index e6fdf7a39..9f612e021 100644 --- a/internal/msggateway/client.go +++ b/internal/msggateway/client.go @@ -136,6 +136,7 @@ func (c *Client) readMessage() { } switch messageType { case MessageBinary: + _ = c.conn.SetReadDeadline(pongWait) parseDataErr := c.handleMessage(message) if parseDataErr != nil { c.closedErr = parseDataErr diff --git a/internal/msgtransfer/online_history_msg_handler.go b/internal/msgtransfer/online_history_msg_handler.go index efaf28cc3..1fc7ca9a1 100644 --- a/internal/msgtransfer/online_history_msg_handler.go +++ b/internal/msgtransfer/online_history_msg_handler.go @@ -25,7 +25,7 @@ import ( "github.com/OpenIMSDK/tools/errs" - "github.com/Shopify/sarama" + "github.com/IBM/sarama" "github.com/go-redis/redis" "google.golang.org/protobuf/proto" diff --git a/internal/msgtransfer/online_msg_to_mongo_handler.go b/internal/msgtransfer/online_msg_to_mongo_handler.go index 90c54d917..ac7dc6662 100644 --- a/internal/msgtransfer/online_msg_to_mongo_handler.go +++ b/internal/msgtransfer/online_msg_to_mongo_handler.go @@ -17,7 +17,7 @@ package msgtransfer import ( "context" - "github.com/Shopify/sarama" + "github.com/IBM/sarama" "google.golang.org/protobuf/proto" pbmsg "github.com/OpenIMSDK/protocol/msg" diff --git a/internal/msgtransfer/persistent_msg_handler.go b/internal/msgtransfer/persistent_msg_handler.go index 02cd49767..088960e5c 100644 --- a/internal/msgtransfer/persistent_msg_handler.go +++ b/internal/msgtransfer/persistent_msg_handler.go @@ -26,7 +26,7 @@ import ( "github.com/OpenIMSDK/Open-IM-Server/pkg/common/db/controller" kfk "github.com/OpenIMSDK/Open-IM-Server/pkg/common/kafka" - "github.com/Shopify/sarama" + "github.com/IBM/sarama" "google.golang.org/protobuf/proto" ) diff --git a/internal/push/push_handler.go b/internal/push/push_handler.go index 736bfc7f9..2ddb17800 100644 --- a/internal/push/push_handler.go +++ b/internal/push/push_handler.go @@ -17,7 +17,7 @@ package push import ( "context" - "github.com/Shopify/sarama" + "github.com/IBM/sarama" "google.golang.org/protobuf/proto" "github.com/OpenIMSDK/protocol/constant" diff --git a/pkg/common/kafka/consumer.go b/pkg/common/kafka/consumer.go index e253ec5e0..5aca865e7 100644 --- a/pkg/common/kafka/consumer.go +++ b/pkg/common/kafka/consumer.go @@ -19,7 +19,7 @@ import ( "github.com/OpenIMSDK/Open-IM-Server/pkg/common/config" - "github.com/Shopify/sarama" + "github.com/IBM/sarama" ) type Consumer struct { diff --git a/pkg/common/kafka/consumer_group.go b/pkg/common/kafka/consumer_group.go index da62fbe65..2b669fe85 100644 --- a/pkg/common/kafka/consumer_group.go +++ b/pkg/common/kafka/consumer_group.go @@ -21,7 +21,7 @@ import ( "github.com/OpenIMSDK/Open-IM-Server/pkg/common/config" - "github.com/Shopify/sarama" + "github.com/IBM/sarama" ) type MConsumerGroup struct { diff --git a/pkg/common/kafka/producer.go b/pkg/common/kafka/producer.go index b7ec32714..44b88e602 100644 --- a/pkg/common/kafka/producer.go +++ b/pkg/common/kafka/producer.go @@ -26,7 +26,7 @@ import ( "github.com/OpenIMSDK/Open-IM-Server/pkg/common/config" - "github.com/Shopify/sarama" + "github.com/IBM/sarama" "google.golang.org/protobuf/proto" prome "github.com/OpenIMSDK/Open-IM-Server/pkg/common/prome" diff --git a/pkg/common/kafka/util.go b/pkg/common/kafka/util.go index cc3dc64c6..4495f983a 100644 --- a/pkg/common/kafka/util.go +++ b/pkg/common/kafka/util.go @@ -15,7 +15,7 @@ package kafka import ( - "github.com/Shopify/sarama" + "github.com/IBM/sarama" "github.com/OpenIMSDK/Open-IM-Server/pkg/common/config" "github.com/OpenIMSDK/Open-IM-Server/pkg/common/tls" diff --git a/scripts/githooks/pre-commit b/scripts/githooks/pre-commit.sh similarity index 100% rename from scripts/githooks/pre-commit rename to scripts/githooks/pre-commit.sh diff --git a/scripts/githooks/pre-push b/scripts/githooks/pre-push.sh similarity index 100% rename from scripts/githooks/pre-push rename to scripts/githooks/pre-push.sh diff --git a/scripts/init-githooks.sh b/scripts/init-githooks.sh index 9d1675c8e..399054bb8 100755 --- a/scripts/init-githooks.sh +++ b/scripts/init-githooks.sh @@ -36,7 +36,7 @@ # ----------------------------------------------------------------------------- OPENIM_ROOT=$(dirname "${BASH_SOURCE[0]}")/.. -HOOKS_DIR=".git/hooks" +HOOKS_DIR="${OPENIM_ROOT}/.git/hooks" help_info() { echo "Usage: $0 [options]" @@ -50,32 +50,43 @@ help_info() { } delete_hooks() { - for file in scripts/githooks/*.sh; do - hook_name=$(basename "$file" .sh) + for file in ${OPENIM_ROOT}/scripts/githooks/*.sh; do + hook_name=$(basename "$file" .sh) # This removes the .sh extension rm -f "$HOOKS_DIR/$hook_name" done echo "Git hooks have been deleted." } enable_hooks() { - echo "Would you like to enable git hooks mode? [y/n]" + echo "Would you like to:" + echo "1) Enable git hooks mode" + echo "2) Delete existing git hooks" + echo "Please select a number (or any other key to exit):" read -r choice - if [[ $choice == "y" || $choice == "Y" ]]; then - for file in scripts/githooks/*.sh; do - cp -f "$file" "$HOOKS_DIR/$(basename "$file" .sh)" - done + case "$choice" in + 1) + for file in ${OPENIM_ROOT}/scripts/githooks/*.sh; do + hook_name=$(basename "$file" .sh) # This removes the .sh extension + cp -f "$file" "$HOOKS_DIR/$hook_name" + done - chmod +x $HOOKS_DIR/* - - echo "Git hooks mode has been enabled." - echo "With git hooks enabled, every time you perform a git action (e.g. git commit), the corresponding hooks script will be triggered automatically." - echo "This means that if the size of the file you're committing exceeds the set limit (e.g. 42MB), the commit will be rejected." - else - echo "Git hooks mode remains disabled." - fi + chmod +x $HOOKS_DIR/* + + echo "Git hooks mode has been enabled." + echo "With git hooks enabled, every time you perform a git action (e.g. git commit), the corresponding hooks script will be triggered automatically." + echo "This means that if the size of the file you're committing exceeds the set limit (e.g. 42MB), the commit will be rejected." + ;; + 2) + delete_hooks + ;; + *) + echo "Exiting without making changes." + ;; + esac } + case "$1" in -h|--help) help_info @@ -86,4 +97,4 @@ case "$1" in *) enable_hooks ;; -esac \ No newline at end of file +esac diff --git a/scripts/install/environment.sh b/scripts/install/environment.sh index 2967d825c..57a8d0400 100755 --- a/scripts/install/environment.sh +++ b/scripts/install/environment.sh @@ -17,6 +17,7 @@ # You need to supplement the script according to the specification. # Read: https://github.com/OpenIMSDK/Open-IM-Server/blob/main/docs/contrib/init_config.md # 格式化 bash 注释:https://tool.lu/shell/ +# 配置中心文档:https://github.com/OpenIMSDK/Open-IM-Server/blob/main/docs/contrib/environment.md OPENIM_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd -P)" diff --git a/test/data-conversion/kafka-conversation.go b/test/data-conversion/kafka-conversation.go index a3fd0dc87..cb6976d67 100644 --- a/test/data-conversion/kafka-conversation.go +++ b/test/data-conversion/kafka-conversation.go @@ -17,7 +17,7 @@ package data_conversion import ( "fmt" - "github.com/Shopify/sarama" + "github.com/IBM/sarama" ) var ( @@ -31,7 +31,7 @@ var ( ) func init() { - //Producer + // Producer config := sarama.NewConfig() // Instantiate a sarama Config config.Producer.Return.Successes = true // Whether to enable the successes channel to be notified after the message is sent successfully config.Producer.Return.Errors = true diff --git a/tools/component/component.go b/tools/component/component.go index be454f900..1cd611c73 100644 --- a/tools/component/component.go +++ b/tools/component/component.go @@ -25,9 +25,9 @@ import ( "strings" "time" + "github.com/IBM/sarama" "github.com/OpenIMSDK/tools/errs" "github.com/OpenIMSDK/tools/utils" - "github.com/Shopify/sarama" "github.com/go-zookeeper/zk" "github.com/minio/minio-go/v7" "github.com/redis/go-redis/v9" diff --git a/tools/imctl/README.md b/tools/imctl/README.md new file mode 100644 index 000000000..520df6421 --- /dev/null +++ b/tools/imctl/README.md @@ -0,0 +1,89 @@ +# [RFC #0005] OpenIM CTL Module Proposal + +## Meta + +- Name: OpenIM CTL Module Enhancement +- Start Date: 2023-08-23 +- Author(s): @cubxxw +- Status: Draft +- RFC Pull Request: (leave blank) +- OpenIMSDK Pull Request: (leave blank) +- OpenIMSDK Issue: https://github.com/OpenIMSDK/Open-IM-Server/issues/924 +- Supersedes: N/A + +## 📇Topics + +- RFC #0000 OpenIMSDK CTL Module Proposal + - [Meta](#meta) + - [Summary](#summary) + - [Definitions](#definitions) + - [Motivation](#motivation) + - [What it is](#what-it-is) + - [How it Works](#how-it-works) + - [Migration](#migration) + - [Drawbacks](#drawbacks) + - [Alternatives](#alternatives) + - [Prior Art](#prior-art) + - [Unresolved Questions](#unresolved-questions) + - [Spec. Changes (OPTIONAL)](#spec-changes-optional) + - [History](#history) + +## Summary + +The OpenIM CTL module proposal aims to provide an integrated tool for the OpenIM system, offering utilities for user management, system monitoring, debugging, configuration, and more. This tool will enhance the extensibility of the OpenIM system and reduce dependencies on individual modules. + +## Definitions + +- **OpenIM**: An Instant Messaging system. +- **`imctl`**: The control command-line tool for OpenIM. +- **E2E Testing**: End-to-End Testing. +- **API**: Application Programming Interface. + +## Motivation + +- Improve the OpenIM system's extensibility and reduce dependencies on individual modules. +- Simplify the process for testers to perform automated tests. +- Enhance interaction with scripts and reduce the system's coupling. +- Implement a consistent tool similar to kubectl for a streamlined user experience. + +## What it is + +`imctl` is a command-line utility designed for OpenIM to provide functionalities including: + +- User Management: Add, delete, or disable user accounts. +- System Monitoring: View metrics like online users, message transfer rate. +- Debugging: View logs, adjust log levels, check system states. +- Configuration Management: Update system settings, manage plugins/modules. +- Data Management: Backup, restore, import, or export data. +- System Maintenance: Update, restart services, or maintenance mode. + +## How it Works + +`imctl`, inspired by kubectl, will have sub-commands and options for the functionalities mentioned. Developers, operations, and testers can invoke these commands to manage and monitor the OpenIM system. + +## Migration + +Currently, the `imctl` will be housed in `tools/imctl`, and later on, the plan is to move it to `cmd/imctl`. Migration guidelines will be provided to ensure smooth transitions. + +## Drawbacks + +- Overhead in learning and adapting to a new tool for existing users. +- Potential complexities in implementing some of the advanced functionalities. + +## Alternatives + +- Continue using individual modules for OpenIM management. +- Utilize third-party tools or platforms with similar functionalities, customizing them for OpenIM. + +## Prior Art + +Kubectl from Kubernetes is a significant inspiration for `imctl`, offering a comprehensive command-line tool for managing clusters. + +## Unresolved Questions + +- What other functionalities might be required in future versions of `imctl`? +- What's the expected timeline for transitioning from `tools/imctl` to `cmd/imctl`? + +## Spec. Changes (OPTIONAL) + +As of now, there are no proposed changes to the core specifications or extensions. Future changes based on community feedback might necessitate spec changes, which will be documented accordingly. \ No newline at end of file diff --git a/tools/imctl/cmd/genman/README.md b/tools/imctl/cmd/genman/README.md new file mode 100644 index 000000000..7b46cbefb --- /dev/null +++ b/tools/imctl/cmd/genman/README.md @@ -0,0 +1,49 @@ +# OpenIM `man` Module README + +Welcome to the `man` module of OpenIM, the comprehensive guide for using OpenIM's range of powerful commands. Here, you'll find in-depth details for each command, its options, and examples to help you harness the full power of the OpenIM suite. + +## Overview + +OpenIM is a robust instant messaging solution. To ensure users can effectively harness its capabilities, OpenIM provides a suite of commands that serve different functionalities, from the API level to RPC calls and utilities. + +The `man` module ensures that users, both new and experienced, have a reliable source of information and documentation to use these commands effectively. + +## Available Commands + +The OpenIM commands are divided into core services and tools. Below is a brief overview of each: + +### Core Services + +- **openim-api**: Interface to the main functionalities of OpenIM. +- **openim-cmdutils**: Utilities for executing common tasks. +- **openim-crontask**: Schedule and manage routine tasks within OpenIM. +- **openim-msggateway**: Gateway for managing messages within the OpenIM system. +- **openim-msgtransfer**: Handle message transfers across different parts of OpenIM. +- **openim-push**: Service for pushing notifications and updates. +- **openim-rpc-auth**: RPC interface for authentication tasks. +- **openim-rpc-conversation**: RPC service for handling conversations. +- **openim-rpc-friend**: Manage friend lists and related functionalities through RPC. +- **openim-rpc-group**: Group management via RPC. +- **openim-rpc-msg**: Message handling at the RPC level. +- **openim-rpc-third**: Third-party integrations and related tasks through RPC. +- **openim-rpc-user**: User management and tasks via RPC. + +### Tools + +- **changelog**: Track and manage changes in OpenIM. +- **component**: Utilities related to different components within OpenIM. +- **infra**: Infrastructure and backend management tools. +- **ncpu**: Monitor and manage CPU usage and related tasks. +- **yamlfmt**: A tool for formatting and linting YAML files within the OpenIM configuration. + +## How to Use + +To view the manual page for any of the OpenIM commands, use the `man` command followed by the command name. For example: + +``` +man openim-api +``` + +## Contributions + +We welcome contributions to enhance the `man` pages. If you discover inconsistencies, errors, or areas where further details are required, feel free to raise an issue or submit a pull request. \ No newline at end of file diff --git a/tools/imctl/cmd/genman/genman.go b/tools/imctl/cmd/genman/genman.go new file mode 100644 index 000000000..1de5c68cd --- /dev/null +++ b/tools/imctl/cmd/genman/genman.go @@ -0,0 +1,62 @@ +// Copyright © 2023 OpenIM. All rights reserved. +// +// Licensed 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 ( + "fmt" + "os" + + "k8s.io/kubernetes/cmd/genutils" +) + +func main() { + // TODO use os.Args instead of "flags" because "flags" will mess up the man pages! + path := "docs/man/man1" + module := "" + if len(os.Args) == 3 { + path = os.Args[1] + module = os.Args[2] + } else { + fmt.Fprintf(os.Stderr, "usage: %s [output directory] [module] \n", os.Args[0]) + os.Exit(1) + } + + outDir, err := genutils.OutDir(path) + if err != nil { + fmt.Fprintf(os.Stderr, "failed to get output directory: %v\n", err) + os.Exit(1) + } + + // Set environment variables used by command so the output is consistent, + // regardless of where we run. + os.Setenv("HOME", "/home/username") + + // openim-api + // openim-cmdutils + // openim-crontask + // openim-msggateway + // openim-msgtransfer + // openim-push + // openim-rpc-auth + // openim-rpc-conversation + // openim-rpc-friend + // openim-rpc-group + // openim-rpc-msg + // openim-rpc-third + // openim-rpc-user + switch module { + case "openim-api": + } +} diff --git a/tools/imctl/cmd/genopenimdocs/config_flags.go b/tools/imctl/cmd/genopenimdocs/config_flags.go new file mode 100644 index 000000000..efd5297b8 --- /dev/null +++ b/tools/imctl/cmd/genopenimdocs/config_flags.go @@ -0,0 +1,243 @@ +// Copyright © 2023 OpenIM. All rights reserved. +// +// Licensed 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 genericclioptions + +import ( + "flag" + "fmt" + "sync" + "time" + + "github.com/AlekSi/pointer" + "github.com/marmotedu/marmotedu-sdk-go/rest" + "github.com/marmotedu/marmotedu-sdk-go/tools/clientcmd" + "github.com/spf13/pflag" + "github.com/spf13/viper" +) + +// Defines flag for imctl. +const ( + FlagIMConfig = "imconfig" + FlagBearerToken = "user.token" + FlagUsername = "user.username" + FlagPassword = "user.password" + FlagSecretID = "user.secret-id" + FlagSecretKey = "user.secret-key" + FlagCertFile = "user.client-certificate" + FlagKeyFile = "user.client-key" + FlagTLSServerName = "server.tls-server-name" + FlagInsecure = "server.insecure-skip-tls-verify" + FlagCAFile = "server.certificate-authority" + FlagAPIServer = "server.address" + FlagTimeout = "server.timeout" + FlagMaxRetries = "server.max-retries" + FlagRetryInterval = "server.retry-interval" +) + +// RESTClientGetter is an interface that the ConfigFlags describe to provide an easier way to mock for commands +// and eliminate the direct coupling to a struct type. Users may wish to duplicate this type in their own packages +// as per the golang type overlapping. +type RESTClientGetter interface { + // ToRESTConfig returns restconfig + ToRESTConfig() (*rest.Config, error) + // ToRawIMConfigLoader return imconfig loader as-is + ToRawIMConfigLoader() clientcmd.ClientConfig +} + +var _ RESTClientGetter = &ConfigFlags{} + +// ConfigFlags composes the set of values necessary +// for obtaining a REST client config. +type ConfigFlags struct { + IMConfig *string + + BearerToken *string + Username *string + Password *string + SecretID *string + SecretKey *string + + Insecure *bool + TLSServerName *string + CertFile *string + KeyFile *string + CAFile *string + + APIServer *string + Timeout *time.Duration + MaxRetries *int + RetryInterval *time.Duration + + clientConfig clientcmd.ClientConfig + lock sync.Mutex + // If set to true, will use persistent client config and + // propagate the config to the places that need it, rather than + // loading the config multiple times + usePersistentConfig bool +} + +// ToRESTConfig implements RESTClientGetter. +// Returns a REST client configuration based on a provided path +// to a .imconfig file, loading rules, and config flag overrides. +// Expects the AddFlags method to have been called. +func (f *ConfigFlags) ToRESTConfig() (*rest.Config, error) { + return f.ToRawIMConfigLoader().ClientConfig() +} + +// ToRawIMConfigLoader binds config flag values to config overrides +// Returns an interactive clientConfig if the password flag is enabled, +// or a non-interactive clientConfig otherwise. +func (f *ConfigFlags) ToRawIMConfigLoader() clientcmd.ClientConfig { + if f.usePersistentConfig { + return f.toRawIMPersistentConfigLoader() + } + + return f.toRawIMConfigLoader() +} + +func (f *ConfigFlags) toRawIMConfigLoader() clientcmd.ClientConfig { + config := clientcmd.NewConfig() + if err := viper.Unmarshal(&config); err != nil { + panic(err) + } + + return clientcmd.NewClientConfigFromConfig(config) +} + +// toRawIMPersistentConfigLoader binds config flag values to config overrides +// Returns a persistent clientConfig for propagation. +func (f *ConfigFlags) toRawIMPersistentConfigLoader() clientcmd.ClientConfig { + f.lock.Lock() + defer f.lock.Unlock() + + if f.clientConfig == nil { + f.clientConfig = f.toRawIMConfigLoader() + } + + return f.clientConfig +} + +// AddFlags binds client configuration flags to a given flagset. +func (f *ConfigFlags) AddFlags(flags *pflag.FlagSet) { + if f.IMConfig != nil { + flags.StringVar(f.IMConfig, FlagIMConfig, *f.IMConfig, + fmt.Sprintf("Path to the %s file to use for CLI requests", FlagIMConfig)) + } + + if f.BearerToken != nil { + flags.StringVar( + f.BearerToken, + FlagBearerToken, + *f.BearerToken, + "Bearer token for authentication to the API server", + ) + } + + if f.Username != nil { + flags.StringVar(f.Username, FlagUsername, *f.Username, "Username for basic authentication to the API server") + } + + if f.Password != nil { + flags.StringVar(f.Password, FlagPassword, *f.Password, "Password for basic authentication to the API server") + } + + if f.SecretID != nil { + flags.StringVar(f.SecretID, FlagSecretID, *f.SecretID, "SecretID for JWT authentication to the API server") + } + + if f.SecretKey != nil { + flags.StringVar(f.SecretKey, FlagSecretKey, *f.SecretKey, "SecretKey for jwt authentication to the API server") + } + + if f.CertFile != nil { + flags.StringVar(f.CertFile, FlagCertFile, *f.CertFile, "Path to a client certificate file for TLS") + } + if f.KeyFile != nil { + flags.StringVar(f.KeyFile, FlagKeyFile, *f.KeyFile, "Path to a client key file for TLS") + } + if f.TLSServerName != nil { + flags.StringVar(f.TLSServerName, FlagTLSServerName, *f.TLSServerName, ""+ + "Server name to use for server certificate validation. If it is not provided, the hostname used to contact the server is used") + } + if f.Insecure != nil { + flags.BoolVar(f.Insecure, FlagInsecure, *f.Insecure, ""+ + "If true, the server's certificate will not be checked for validity. This will make your HTTPS connections insecure") + } + if f.CAFile != nil { + flags.StringVar(f.CAFile, FlagCAFile, *f.CAFile, "Path to a cert file for the certificate authority") + } + + if f.APIServer != nil { + flags.StringVarP(f.APIServer, FlagAPIServer, "s", *f.APIServer, "The address and port of the IM API server") + } + + if f.Timeout != nil { + flags.DurationVar( + f.Timeout, + FlagTimeout, + *f.Timeout, + "The length of time to wait before giving up on a single server request. Non-zero values should contain a corresponding time unit (e.g. 1s, 2m, 3h). A value of zero means don't timeout requests.", + ) + } + + if f.MaxRetries != nil { + flag.IntVar(f.MaxRetries, FlagMaxRetries, *f.MaxRetries, "Maximum number of retries.") + } + + if f.RetryInterval != nil { + flags.DurationVar( + f.RetryInterval, + FlagRetryInterval, + *f.RetryInterval, + "The interval time between each attempt.", + ) + } +} + +// WithDeprecatedPasswordFlag enables the username and password config flags. +func (f *ConfigFlags) WithDeprecatedPasswordFlag() *ConfigFlags { + f.Username = pointer.ToString("") + f.Password = pointer.ToString("") + + return f +} + +// WithDeprecatedSecretFlag enables the secretID and secretKey config flags. +func (f *ConfigFlags) WithDeprecatedSecretFlag() *ConfigFlags { + f.SecretID = pointer.ToString("") + f.SecretKey = pointer.ToString("") + + return f +} + +// NewConfigFlags returns ConfigFlags with default values set. +func NewConfigFlags(usePersistentConfig bool) *ConfigFlags { + return &ConfigFlags{ + IMConfig: pointer.ToString(""), + + BearerToken: pointer.ToString(""), + Insecure: pointer.ToBool(false), + TLSServerName: pointer.ToString(""), + CertFile: pointer.ToString(""), + KeyFile: pointer.ToString(""), + CAFile: pointer.ToString(""), + + APIServer: pointer.ToString(""), + Timeout: pointer.ToDuration(30 * time.Second), + MaxRetries: pointer.ToInt(0), + RetryInterval: pointer.ToDuration(1 * time.Second), + usePersistentConfig: usePersistentConfig, + } +} diff --git a/tools/imctl/cmd/imctl/imctl.go b/tools/imctl/cmd/imctl/imctl.go new file mode 100644 index 000000000..d72fee927 --- /dev/null +++ b/tools/imctl/cmd/imctl/imctl.go @@ -0,0 +1,29 @@ +// Copyright © 2023 OpenIM. All rights reserved. +// +// Licensed 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. + +// iactl is the command line tool for openim platform. +package main + +import ( + "os" + + "github.com/OpenIMSDK/Open-IM-Server/tools/imctl/internal/imctl/cmd" +) + +func main() { + command := cmd.NewDefaultIMCtlCommand() + if err := command.Execute(); err != nil { + os.Exit(1) + } +} diff --git a/tools/imctl/go.mod b/tools/imctl/go.mod new file mode 100644 index 000000000..5b14e0168 --- /dev/null +++ b/tools/imctl/go.mod @@ -0,0 +1,18 @@ +module github.com/OpenIMSDK/Open-IM-Server/tools/imctl + +go 1.18 + +require ( + github.com/MakeNowJust/heredoc/v2 v2.0.1 + github.com/mitchellh/go-wordwrap v1.0.1 + github.com/moby/term v0.5.0 + github.com/russross/blackfriday v1.6.0 + github.com/spf13/cobra v1.7.0 + github.com/spf13/pflag v1.0.5 +) + +require ( + github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1 // indirect + github.com/inconshreveable/mousetrap v1.1.0 // indirect + golang.org/x/sys v0.1.0 // indirect +) diff --git a/tools/imctl/go.sum b/tools/imctl/go.sum new file mode 100644 index 000000000..3d4c61343 --- /dev/null +++ b/tools/imctl/go.sum @@ -0,0 +1,24 @@ +github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1 h1:UQHMgLO+TxOElx5B5HZ4hJQsoJ/PvUvKRhJHDQXO8P8= +github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1/go.mod h1:xomTg63KZ2rFqZQzSB4Vz2SUXa1BpHTVz9L5PTmPC4E= +github.com/MakeNowJust/heredoc/v2 v2.0.1 h1:rlCHh70XXXv7toz95ajQWOWQnN4WNLt0TdpZYIR/J6A= +github.com/MakeNowJust/heredoc/v2 v2.0.1/go.mod h1:6/2Abh5s+hc3g9nbWLe9ObDIOhaRrqsyY9MWy+4JdRM= +github.com/cpuguy83/go-md2man/v2 v2.0.2/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= +github.com/creack/pty v1.1.18 h1:n56/Zwd5o6whRC5PMGretI4IdRLlmBXYNjScPaBgsbY= +github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8= +github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= +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/moby/term v0.5.0 h1:xt8Q1nalod/v7BqbG21f8mQPqH+xAaC9C3N3wfWbVP0= +github.com/moby/term v0.5.0/go.mod h1:8FzsFHVUBGZdbDsJw/ot+X+d5HLUbvklYLJ9uGfcI3Y= +github.com/russross/blackfriday v1.6.0 h1:KqfZb0pUVN2lYqZUYRddxF4OR8ZMURnJIG5Y3VRLtww= +github.com/russross/blackfriday v1.6.0/go.mod h1:ti0ldHuxg49ri4ksnFxlkCfN+hvslNlmVHqNRXXJNAY= +github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= +github.com/spf13/cobra v1.7.0 h1:hyqWnYt1ZQShIddO5kBpj3vu05/++x6tJ6dg8EC572I= +github.com/spf13/cobra v1.7.0/go.mod h1:uLxZILRyS/50WlhOIKD7W6V5bgeIt+4sICxh6uRMrb0= +github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= +github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= +golang.org/x/sys v0.0.0-20210616094352-59db8d763f22/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.1.0 h1:kunALQeHf1/185U1i0GOB/fy1IPRDDpuoOOqRReG57U= +golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/tools/imctl/internal/imctl/cmd/cmd.go b/tools/imctl/internal/imctl/cmd/cmd.go new file mode 100644 index 000000000..24b8a2c8e --- /dev/null +++ b/tools/imctl/internal/imctl/cmd/cmd.go @@ -0,0 +1,146 @@ +// Copyright © 2023 OpenIM. All rights reserved. +// +// Licensed 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 ( + "flag" + "io" + "os" + + cliflag "github.com/openimsdk/component-base/pkg/cli/flag" + "github.com/spf13/cobra" + "github.com/spf13/viper" + + "github.com/OpenIMSDK/Open-IM-Server/tools/imctl/pkg/cli/genericclioptions" + + cmdutil "github.com/OpenIMSDK/Open-IM-Server/tools/imctl/inernal/iamctl/cmd/util" + "github.com/OpenIMSDK/Open-IM-Server/tools/imctl/internal/imctl/cmd/color" + "github.com/OpenIMSDK/Open-IM-Server/tools/imctl/internal/imctl/cmd/completion" + "github.com/OpenIMSDK/Open-IM-Server/tools/imctl/internal/imctl/cmd/info" + "github.com/OpenIMSDK/Open-IM-Server/tools/imctl/internal/imctl/cmd/jwt" + "github.com/OpenIMSDK/Open-IM-Server/tools/imctl/internal/imctl/cmd/new" + "github.com/OpenIMSDK/Open-IM-Server/tools/imctl/internal/imctl/cmd/options" + "github.com/OpenIMSDK/Open-IM-Server/tools/imctl/internal/imctl/cmd/policy" + "github.com/OpenIMSDK/Open-IM-Server/tools/imctl/internal/imctl/cmd/secret" + "github.com/OpenIMSDK/Open-IM-Server/tools/imctl/internal/imctl/cmd/set" + "github.com/OpenIMSDK/Open-IM-Server/tools/imctl/internal/imctl/cmd/user" + "github.com/OpenIMSDK/Open-IM-Server/tools/imctl/internal/imctl/cmd/validate" + "github.com/OpenIMSDK/Open-IM-Server/tools/imctl/internal/imctl/cmd/version" + "github.com/OpenIMSDK/Open-IM-Server/tools/imctl/internal/imctl/util/templates" + genericapiserver "github.com/OpenIMSDK/Open-IM-Server/tools/imctl/internal/pkg/server" + "github.com/OpenIMSDK/Open-IM-Server/tools/imctl/pkg/cli/genericclioptions" +) + +// NewDefaultIAMCtlCommand creates the `imctl` command with default arguments. +func NewDefaultIMCtlCommand() *cobra.Command { + return NewIMCtlCommand(os.Stdin, os.Stdout, os.Stderr) +} + +// NewIAMCtlCommand returns new initialized instance of 'imctl' root command. +func NewIMCtlCommand(in io.Reader, out, err io.Writer) *cobra.Command { + // Parent command to which all subcommands are added. + cmds := &cobra.Command{ + Use: "imctl", + Short: "imctl controls the IM platform", + Long: templates.LongDesc(` + imctl controls the IM platform, is the client side tool for IM platform. + + Find more information at: + // TODO: add link to docs, from auto scripts and gendocs + https://github.com/OpenIMSDK/Open-IM-Server/tree/main/docs`), + Run: runHelp, + // Hook before and after Run initialize and write profiles to disk, + // respectively. + PersistentPreRunE: func(*cobra.Command, []string) error { + return initProfiling() + }, + PersistentPostRunE: func(*cobra.Command, []string) error { + return flushProfiling() + }, + } + + flags := cmds.PersistentFlags() + flags.SetNormalizeFunc(cliflag.WarnWordSepNormalizeFunc) // Warn for "_" flags + + // Normalize all flags that are coming from other packages or pre-configurations + // a.k.a. change all "_" to "-". e.g. glog package + flags.SetNormalizeFunc(cliflag.WordSepNormalizeFunc) + + addProfilingFlags(flags) + + iamConfigFlags := genericclioptions.NewConfigFlags(true).WithDeprecatedPasswordFlag().WithDeprecatedSecretFlag() + iamConfigFlags.AddFlags(flags) + matchVersionIAMConfigFlags := cmdutil.NewMatchVersionFlags(iamConfigFlags) + matchVersionIAMConfigFlags.AddFlags(cmds.PersistentFlags()) + + _ = viper.BindPFlags(cmds.PersistentFlags()) + cobra.OnInitialize(func() { + genericapiserver.LoadConfig(viper.GetString(genericclioptions.FlagIAMConfig), "iamctl") + }) + cmds.PersistentFlags().AddGoFlagSet(flag.CommandLine) + + f := cmdutil.NewFactory(matchVersionIAMConfigFlags) + + // From this point and forward we get warnings on flags that contain "_" separators + cmds.SetGlobalNormalizationFunc(cliflag.WarnWordSepNormalizeFunc) + + ioStreams := genericclioptions.IOStreams{In: in, Out: out, ErrOut: err} + + groups := templates.CommandGroups{ + { + Message: "Basic Commands:", + Commands: []*cobra.Command{ + info.NewCmdInfo(f, ioStreams), + color.NewCmdColor(f, ioStreams), + new.NewCmdNew(f, ioStreams), + jwt.NewCmdJWT(f, ioStreams), + }, + }, + { + Message: "Identity and Access Management Commands:", + Commands: []*cobra.Command{ + user.NewCmdUser(f, ioStreams), + secret.NewCmdSecret(f, ioStreams), + policy.NewCmdPolicy(f, ioStreams), + }, + }, + { + Message: "Troubleshooting and Debugging Commands:", + Commands: []*cobra.Command{ + validate.NewCmdValidate(f, ioStreams), + }, + }, + { + Message: "Settings Commands:", + Commands: []*cobra.Command{ + set.NewCmdSet(f, ioStreams), + completion.NewCmdCompletion(ioStreams.Out, ""), + }, + }, + } + groups.Add(cmds) + + filters := []string{"options"} + templates.ActsAsRootCommand(cmds, filters, groups...) + + cmds.AddCommand(version.NewCmdVersion(f, ioStreams)) + cmds.AddCommand(options.NewCmdOptions(ioStreams.Out)) + + return cmds +} + +func runHelp(cmd *cobra.Command, args []string) { + _ = cmd.Help() +} diff --git a/tools/imctl/internal/imctl/cmd/color/color.go b/tools/imctl/internal/imctl/cmd/color/color.go new file mode 100644 index 000000000..bbdd439cc --- /dev/null +++ b/tools/imctl/internal/imctl/cmd/color/color.go @@ -0,0 +1,352 @@ +// Copyright © 2023 OpenIM. All rights reserved. +// +// Licensed 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 color print colors supported by the current terminal. +package color + +import ( + "fmt" + "strings" + + "github.com/fatih/color" + "github.com/olekukonko/tablewriter" + "github.com/openim-sigs/component-base/util/stringutil" + "github.com/spf13/cobra" + + "github.com/OpenIMSDK/Open-IM-Server/tools/imctl/pkg/util/templates" + "github.com/OpenIMSDK/Open-IM-Server/tools/imctl/pkg/cli/genericclioptions" + cmdutil "github.com/OpenIMSDK/Open-IM-Server/tools/imctl/internal/imctl/cmd/util" +) + +// ColorOptions is an options struct to support color subcommands. +type ColorOptions struct { + Type []string + Example bool + + genericclioptions.IOStreams +} + +var ( + colorLong = templates.LongDesc(`Print the colors supported by the current terminal. + +Color lets you use colorized outputs in terms of ANSI Escape Codes in Go (Golang). +It has support for Windows too! The API can be used in several ways, pick one that suits you. + +Find more information at: + https://github.com/fatih/color`) + + colorExample = templates.Examples(` + # Print supported foreground and background colors + imctl color + + # Print supported colors by type + imctl color -t fg-hi + + # Print all supported colors + imctl color -t all`) + + availableTypes = []string{"fg", "fg-hi", "bg", "bg-hi", "base", "all"} + + colorCodeExample = templates.Examples(` +### 1. Standard colors + +// Print with default helper functions +color.Cyan("Prints text in cyan.") + +// A newline will be appended automatically +color.Blue("Prints %s in blue.", "text") + +// These are using the default foreground colors +color.Red("We have red") +color.Magenta("And many others ..") + +### 2. Mix and reuse colors + +// Create a new color object +c := color.New(color.FgCyan).Add(color.Underline) +c.Println("Prints cyan text with an underline.") + +// Or just add them to New() +d := color.New(color.FgCyan, color.Bold) +d.Printf("This prints bold cyan %s\n", "too!.") + +// Mix up foreground and background colors, create new mixes! +red := color.New(color.FgRed) + +boldRed := red.Add(color.Bold) +boldRed.Println("This will print text in bold red.") + +whiteBackground := red.Add(color.BgWhite) +whiteBackground.Println("Red text with white background.") + +### 3. Use your own output (io.Writer) + +// Use your own io.Writer output +color.New(color.FgBlue).Fprintln(myWriter, "blue color!") + +blue := color.New(color.FgBlue) +blue.Fprint(writer, "This will print text in blue.") + +### 4. Custom print functions (PrintFunc) + +// Create a custom print function for convenience +red := color.New(color.FgRed).PrintfFunc() +red("Warning") +red("Error: %s", err) + +// Mix up multiple attributes +notice := color.New(color.Bold, color.FgGreen).PrintlnFunc() +notice("Don't forget this...") + +### 5. Custom fprint functions (FprintFunc) + +blue := color.New(FgBlue).FprintfFunc() +blue(myWriter, "important notice: %s", stars) + +// Mix up with multiple attributes +success := color.New(color.Bold, color.FgGreen).FprintlnFunc() +success(myWriter, "Don't forget this...") + +### 6. Insert into noncolor strings (SprintFunc) + +// Create SprintXxx functions to mix strings with other non-colorized strings: +yellow := color.New(color.FgYellow).SprintFunc() +red := color.New(color.FgRed).SprintFunc() +fmt.Printf("This is a %s and this is %s.\n", yellow("warning"), red("error")) + +info := color.New(color.FgWhite, color.BgGreen).SprintFunc() +fmt.Printf("This %s rocks!\n", info("package")) + +// Use helper functions +fmt.Println("This", color.RedString("warning"), "should be not neglected.") +fmt.Printf("%v %v\n", color.GreenString("Info:"), "an important message.") + +// Windows supported too! Just don't forget to change the output to color.Output +fmt.Fprintf(color.Output, "Windows support: %s", color.GreenString("PASS")) + +### 7. Plug into existing code + +// Use handy standard colors +color.Set(color.FgYellow) + +fmt.Println("Existing text will now be in yellow") +fmt.Printf("This one %s\n", "too") + +color.Unset() // Don't forget to unset + +// You can mix up parameters +color.Set(color.FgMagenta, color.Bold) +defer color.Unset() // Use it in your function + +fmt.Println("All text will now be bold magenta.") + +### 8. Disable/Enable color + +There might be a case where you want to explicitly disable/enable color output. the +go-isatty package will automatically disable color output for non-tty output streams +(for example if the output were piped directly to less) + +Color has support to disable/enable colors both globally and for single color +definitions. For example suppose you have a CLI app and a --no-color bool flag. You +can easily disable the color output with: + + +var flagNoColor = flag.Bool("no-color", false, "Disable color output") + +if *flagNoColor { + color.NoColor = true // disables colorized output +} + +It also has support for single color definitions (local). You can +disable/enable color output on the fly: + +c := color.New(color.FgCyan) +c.Println("Prints cyan text") + +c.DisableColor() +c.Println("This is printed without any color") + +c.EnableColor() +c.Println("This prints again cyan...")`) +) + +// NewColorOptions returns an initialized ColorOptions instance. +func NewColorOptions(ioStreams genericclioptions.IOStreams) *ColorOptions { + return &ColorOptions{ + Type: []string{}, + Example: false, + IOStreams: ioStreams, + } +} + +// NewCmdColor returns new initialized instance of color sub command. +func NewCmdColor(f cmdutil.Factory, ioStreams genericclioptions.IOStreams) *cobra.Command { + o := NewColorOptions(ioStreams) + + cmd := &cobra.Command{ + Use: "color", + DisableFlagsInUseLine: true, + Aliases: []string{}, + Short: "Print colors supported by the current terminal", + TraverseChildren: true, + Long: colorLong, + Example: colorExample, + // NoArgs, ArbitraryArgs, OnlyValidArgs, MinimumArgs, MaximumArgs, ExactArgs, ExactArgs + ValidArgs: []string{}, + Run: func(cmd *cobra.Command, args []string) { + cmdutil.CheckErr(o.Complete(f, cmd, args)) + cmdutil.CheckErr(o.Validate(cmd, args)) + cmdutil.CheckErr(o.Run(args)) + }, + SuggestFor: []string{}, + } + + cmd.Flags().StringSliceVarP(&o.Type, "type", "t", o.Type, + fmt.Sprintf("Specify the color type, available types: `%s`.", strings.Join(availableTypes, ","))) + cmd.Flags().BoolVar(&o.Example, "example", o.Example, "Print code to show how to use color package.") + + return cmd +} + +// Complete completes all the required options. +func (o *ColorOptions) Complete(f cmdutil.Factory, cmd *cobra.Command, args []string) error { + if len(o.Type) == 0 { + o.Type = []string{"fg", "bg"} + + return nil + } + + if stringutil.StringIn("all", o.Type) { + o.Type = []string{"fg", "fg-hi", "bg", "bg-hi", "base"} + } + + return nil +} + +// Validate makes sure there is no discrepency in command options. +func (o *ColorOptions) Validate(cmd *cobra.Command, args []string) error { + for _, t := range o.Type { + if !stringutil.StringIn(t, availableTypes) { + return cmdutil.UsageErrorf(cmd, "--type must be in: %s", strings.Join(availableTypes, ",")) + } + } + + return nil +} + +// Run executes a color subcommand using the specified options. +func (o *ColorOptions) Run(args []string) error { + if o.Example { + fmt.Fprint(o.Out, colorCodeExample+"\n") + + return nil + } + + var data [][]string + + // print table to stdout + table := tablewriter.NewWriter(o.Out) + + // set table attributes + table.SetAutoMergeCells(true) + table.SetRowLine(false) + table.SetBorder(false) + table.SetAutoWrapText(false) + table.SetAutoFormatHeaders(true) + table.SetHeaderAlignment(tablewriter.ALIGN_CENTER) + table.SetAlignment(tablewriter.ALIGN_LEFT) + table.SetHeader([]string{"Category", "Color Name", "Effect"}) + table.SetHeaderColor(tablewriter.Colors{tablewriter.FgGreenColor}, + tablewriter.Colors{tablewriter.FgRedColor}, + tablewriter.Colors{tablewriter.FgCyanColor}) + + for _, t := range o.Type { + switch t { + // Foreground text colors + case "fg": + fg := [][]string{ + {"fg", "black", color.BlackString("color.BlackString")}, + {"fg", "red", color.RedString("color.RedString")}, + {"fg", "green", color.GreenString("color.GreenString")}, + {"fg", "yellow", color.YellowString("color.YellowString")}, + {"fg", "blue", color.BlueString("color.BlueString")}, + {"fg", "magenta", color.MagentaString("color.MagentaString")}, + {"fg", "Cyan", color.CyanString("color.CyanString")}, + {"fg", "white", color.WhiteString("color.WhiteString")}, + } + data = append(data, fg...) + // Foreground Hi-Intensity text colors + case "fg-hi": + fg := [][]string{ + {"fg-hi", "black", color.HiBlackString("color.HiBlackString")}, + {"fg-hi", "red", color.HiRedString("color.HiRedString")}, + {"fg-hi", "green", color.HiGreenString("color.HiGreenString")}, + {"fg-hi", "yellow", color.HiYellowString("color.HiYellowString")}, + {"fg-hi", "blue", color.HiBlueString("color.HiBlueString")}, + {"fg-hi", "magenta", color.HiMagentaString("color.HiMagentaString")}, + {"fg-hi", "Cyan", color.HiCyanString("color.HiCyanString")}, + {"fg-hi", "white", color.HiWhiteString("color.HiWhiteString")}, + } + data = append(data, fg...) + + // Background text colors + case "bg": + bg := [][]string{ + {"bg", "black", color.New(color.FgWhite, color.BgBlack).SprintFunc()("color.BgBlack")}, + {"bg", "red", color.New(color.FgBlack, color.BgRed).SprintFunc()("color.BgRed")}, + {"bg", "greep", color.New(color.FgBlack, color.BgGreen).SprintFunc()("color.BgGreen")}, + {"bg", "yellow", color.New(color.FgWhite, color.BgYellow).SprintFunc()("color.BgYellow")}, + {"bg", "blue", color.New(color.FgWhite, color.BgBlue).SprintFunc()("color.BgBlue")}, + {"bg", "magenta", color.New(color.FgWhite, color.BgMagenta).SprintFunc()("color.BgMagenta")}, + {"bg", "cyan", color.New(color.FgWhite, color.BgCyan).SprintFunc()("color.BgCyan")}, + {"bg", "white", color.New(color.FgBlack, color.BgWhite).SprintFunc()("color.BgWhite")}, + } + data = append(data, bg...) + // Background Hi-Intensity text colors + case "bg-hi": + bghi := [][]string{ + {"bg-hi", "black", color.New(color.FgWhite, color.BgHiBlack).SprintFunc()("color.BgHiBlack")}, + {"bg-hi", "red", color.New(color.FgBlack, color.BgHiRed).SprintFunc()("color.BgHiRed")}, + {"bg-hi", "greep", color.New(color.FgBlack, color.BgHiGreen).SprintFunc()("color.BgHiGreen")}, + {"bg-hi", "yellow", color.New(color.FgWhite, color.BgHiYellow).SprintFunc()("color.BgHiYellow")}, + {"bg-hi", "blue", color.New(color.FgWhite, color.BgHiBlue).SprintFunc()("color.BgHiBlue")}, + {"bg-hi", "magenta", color.New(color.FgWhite, color.BgHiMagenta).SprintFunc()("color.BgHiMagenta")}, + {"bg-hi", "cyan", color.New(color.FgWhite, color.BgHiCyan).SprintFunc()("color.BgHiCyan")}, + {"bg-hi", "white", color.New(color.FgBlack, color.BgHiWhite).SprintFunc()("color.BgHiWhite")}, + } + data = append(data, bghi...) + // Base attributes + case "base": + base := [][]string{ + {"base", "black", color.New(color.FgGreen, color.Reset).SprintFunc()("color.Reset")}, + {"base", "red", color.New(color.FgGreen, color.Bold).SprintFunc()("color.Bold")}, + {"base", "greep", color.New(color.FgGreen, color.Faint).SprintFunc()("color.Faint")}, + {"base", "yellow", color.New(color.FgGreen, color.Italic).SprintFunc()("color.Italic")}, + {"base", "blue", color.New(color.FgGreen, color.Underline).SprintFunc()("color.Underline")}, + {"base", "magenta", color.New(color.FgGreen, color.BlinkSlow).SprintFunc()("color.BlinkSlow")}, + {"base", "cyan", color.New(color.FgGreen, color.BlinkRapid).SprintFunc()("color.BlinkRapid")}, + {"base", "white", color.New(color.FgGreen, color.ReverseVideo).SprintFunc()("color.ReverseVideo")}, + {"base", "white", color.New(color.FgGreen, color.Concealed).SprintFunc()("color.Concealed")}, + {"base", "white", color.New(color.FgGreen, color.CrossedOut).SprintFunc()("color.CrossedOut")}, + } + data = append(data, base...) + default: + } + } + + table.AppendBulk(data) + table.Render() + + return nil +} diff --git a/tools/imctl/internal/imctl/cmd/completion/completion.go b/tools/imctl/internal/imctl/cmd/completion/completion.go new file mode 100644 index 000000000..f0bfd5bd9 --- /dev/null +++ b/tools/imctl/internal/imctl/cmd/completion/completion.go @@ -0,0 +1,283 @@ +// Copyright © 2023 OpenIM. All rights reserved. +// +// Licensed 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 completion output shell completion code for the specified shell (bash or zsh). +package completion + +import ( + "bytes" + "io" + + "github.com/spf13/cobra" + + "github.com/OpenIMSDK/Open-IM-Server/tools/imctl/pkg/util/templates" + cmdutil "github.com/OpenIMSDK/Open-IM-Server/tools/imctl/internal/imctl/cmd/util" +) + +const defaultBoilerPlate = ` +# Copyright © 2023 OpenIM. All rights reserved. +# +# Licensed 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. +` + +var ( + completionLong = templates.LongDesc(` + Output shell completion code for the specified shell (bash or zsh). + The shell code must be evaluated to provide interactive + completion of imctl commands. This can be done by sourcing it from + the .bash_profile. + + Detailed instructions on how to do this are available here: + http://github.com/marmotedu/iam/docs/installation/imctl.md#enabling-shell-autocompletion + + Note for zsh users: [1] zsh completions are only supported in versions of zsh >= 5.2`) + + completionExample = templates.Examples(` + # Installing bash completion on macOS using homebrew + ## If running Bash 3.2 included with macOS + brew install bash-completion + ## or, if running Bash 4.1+ + brew install bash-completion@2 + ## If imctl is installed via homebrew, this should start working immediately. + ## If you've installed via other means, you may need add the completion to your completion directory + imctl completion bash > $(brew --prefix)/etc/bash_completion.d/imctl + + + # Installing bash completion on Linux + ## If bash-completion is not installed on Linux, please install the 'bash-completion' package + ## via your distribution's package manager. + ## Load the imctl completion code for bash into the current shell + source <(imctl completion bash) + ## Write bash completion code to a file and source if from .bash_profile + imctl completion bash > ~/.iam/completion.bash.inc + printf " + # IAM shell completion + source '$HOME/.iam/completion.bash.inc' + " >> $HOME/.bash_profile + source $HOME/.bash_profile + + # Load the imctl completion code for zsh[1] into the current shell + source <(imctl completion zsh) + # Set the imctl completion code for zsh[1] to autoload on startup + imctl completion zsh > "${fpath[1]}/_imctl"`) +) + +var completionShells = map[string]func(out io.Writer, boilerPlate string, cmd *cobra.Command) error{ + "bash": runCompletionBash, + "zsh": runCompletionZsh, +} + +// NewCmdCompletion creates the `completion` command. +func NewCmdCompletion(out io.Writer, boilerPlate string) *cobra.Command { + shells := []string{} + for s := range completionShells { + shells = append(shells, s) + } + + cmd := &cobra.Command{ + Use: "completion SHELL", + DisableFlagsInUseLine: true, + Short: "Output shell completion code for the specified shell (bash or zsh)", + Long: completionLong, + Example: completionExample, + Run: func(cmd *cobra.Command, args []string) { + err := RunCompletion(out, boilerPlate, cmd, args) + cmdutil.CheckErr(err) + }, + ValidArgs: shells, + } + + return cmd +} + +// RunCompletion checks given arguments and executes command. +func RunCompletion(out io.Writer, boilerPlate string, cmd *cobra.Command, args []string) error { + if len(args) == 0 { + return cmdutil.UsageErrorf(cmd, "Shell not specified.") + } + + if len(args) > 1 { + return cmdutil.UsageErrorf(cmd, "Too many arguments. Expected only the shell type.") + } + + run, found := completionShells[args[0]] + if !found { + return cmdutil.UsageErrorf(cmd, "Unsupported shell type %q.", args[0]) + } + + return run(out, boilerPlate, cmd.Parent()) +} + +func runCompletionBash(out io.Writer, boilerPlate string, imctl *cobra.Command) error { + if len(boilerPlate) == 0 { + boilerPlate = defaultBoilerPlate + } + + if _, err := out.Write([]byte(boilerPlate)); err != nil { + return err + } + + return imctl.GenBashCompletion(out) +} + +func runCompletionZsh(out io.Writer, boilerPlate string, imctl *cobra.Command) error { + zshHead := "#compdef imctl\n" + + if _, err := out.Write([]byte(zshHead)); err != nil { + return err + } + + if len(boilerPlate) == 0 { + boilerPlate = defaultBoilerPlate + } + + if _, err := out.Write([]byte(boilerPlate)); err != nil { + return err + } + + zshInitialization := ` +__imctl_bash_source() { + alias shopt=':' + emulate -L sh + setopt kshglob noshglob braceexpand + + source "$@" +} + +__imctl_type() { + # -t is not supported by zsh + if [ "$1" == "-t" ]; then + shift + + # fake Bash 4 to disable "complete -o nospace". Instead + # "compopt +-o nospace" is used in the code to toggle trailing + # spaces. We don't support that, but leave trailing spaces on + # all the time + if [ "$1" = "__imctl_compopt" ]; then + echo builtin + return 0 + fi + fi + type "$@" +} + +__imctl_compgen() { + local completions w + completions=( $(compgen "$@") ) || return $? + + # filter by given word as prefix + while [[ "$1" = -* && "$1" != -- ]]; do + shift + shift + done + if [[ "$1" == -- ]]; then + shift + fi + for w in "${completions[@]}"; do + if [[ "${w}" = "$1"* ]]; then + echo "${w}" + fi + done +} + +__imctl_compopt() { + true # don't do anything. Not supported by bashcompinit in zsh +} + +__imctl_ltrim_colon_completions() +{ + if [[ "$1" == *:* && "$COMP_WORDBREAKS" == *:* ]]; then + # Remove colon-word prefix from COMPREPLY items + local colon_word=${1%${1##*:}} + local i=${#COMPREPLY[*]} + while [[ $((--i)) -ge 0 ]]; do + COMPREPLY[$i]=${COMPREPLY[$i]#"$colon_word"} + done + fi +} + +__imctl_get_comp_words_by_ref() { + cur="${COMP_WORDS[COMP_CWORD]}" + prev="${COMP_WORDS[${COMP_CWORD}-1]}" + words=("${COMP_WORDS[@]}") + cword=("${COMP_CWORD[@]}") +} + +__imctl_filedir() { + # Don't need to do anything here. + # Otherwise we will get trailing space without "compopt -o nospace" + true +} + +autoload -U +X bashcompinit && bashcompinit + +# use word boundary patterns for BSD or GNU sed +LWORD='[[:<:]]' +RWORD='[[:>:]]' +if sed --help 2>&1 | grep -q 'GNU\|BusyBox'; then + LWORD='\<' + RWORD='\>' +fi + +__imctl_convert_bash_to_zsh() { + sed \ + -e 's/declare -F/whence -w/' \ + -e 's/_get_comp_words_by_ref "\$@"/_get_comp_words_by_ref "\$*"/' \ + -e 's/local \([a-zA-Z0-9_]*\)=/local \1; \1=/' \ + -e 's/flags+=("\(--.*\)=")/flags+=("\1"); two_word_flags+=("\1")/' \ + -e 's/must_have_one_flag+=("\(--.*\)=")/must_have_one_flag+=("\1")/' \ + -e "s/${LWORD}_filedir${RWORD}/__imctl_filedir/g" \ + -e "s/${LWORD}_get_comp_words_by_ref${RWORD}/__imctl_get_comp_words_by_ref/g" \ + -e "s/${LWORD}__ltrim_colon_completions${RWORD}/__imctl_ltrim_colon_completions/g" \ + -e "s/${LWORD}compgen${RWORD}/__imctl_compgen/g" \ + -e "s/${LWORD}compopt${RWORD}/__imctl_compopt/g" \ + -e "s/${LWORD}declare${RWORD}/builtin declare/g" \ + -e "s/\\\$(type${RWORD}/\$(__imctl_type/g" \ + <<'BASH_COMPLETION_EOF' +` + if _, err := out.Write([]byte(zshInitialization)); err != nil { + return err + } + + buf := new(bytes.Buffer) + if err := imctl.GenBashCompletion(buf); err != nil { + return err + } + + if _, err := out.Write(buf.Bytes()); err != nil { + return err + } + + zshTail := ` +BASH_COMPLETION_EOF +} + +__imctl_bash_source <(__imctl_convert_bash_to_zsh) +` + if _, err := out.Write([]byte(zshTail)); err != nil { + return err + } + + return nil +} diff --git a/tools/imctl/internal/imctl/cmd/info/info.go b/tools/imctl/internal/imctl/cmd/info/info.go new file mode 100644 index 000000000..928d12bbb --- /dev/null +++ b/tools/imctl/internal/imctl/cmd/info/info.go @@ -0,0 +1,119 @@ +// Copyright © 2023 OpenIM. All rights reserved. +// +// Licensed 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 info print the host information. +package info + +import ( + "fmt" + "reflect" + "strconv" + + hoststat "github.com/likexian/host-stat-go" + "github.com/openim-sigs/component-base/util/iputil" + "github.com/spf13/cobra" + + "github.com/OpenIMSDK/Open-IM-Server/tools/imctl/pkg/util/templates" + "github.com/OpenIMSDK/Open-IM-Server/tools/imctl/pkg/cli/genericclioptions" + cmdutil "github.com/OpenIMSDK/Open-IM-Server/tools/imctl/internal/imctl/cmd/util" +) + +// Info defines the host information struct. +type Info struct { + HostName string + IPAddress string + OSRelease string + CPUCore uint64 + MemTotal string + MemFree string +} + +// InfoOptions is an options struct to support 'info' sub command. +type InfoOptions struct { + genericclioptions.IOStreams +} + +var infoExample = templates.Examples(` + # Print the host information + imctl info`) + +// NewInfoOptions returns an initialized InfoOptions instance. +func NewInfoOptions(ioStreams genericclioptions.IOStreams) *InfoOptions { + return &InfoOptions{ + IOStreams: ioStreams, + } +} + +// NewCmdInfo returns new initialized instance of 'info' sub command. +func NewCmdInfo(f cmdutil.Factory, ioStreams genericclioptions.IOStreams) *cobra.Command { + o := NewInfoOptions(ioStreams) + + cmd := &cobra.Command{ + Use: "info", + DisableFlagsInUseLine: true, + Aliases: []string{}, + Short: "Print the host information", + Long: "Print the host information.", + Example: infoExample, + Run: func(cmd *cobra.Command, args []string) { + cmdutil.CheckErr(o.Run(args)) + }, + SuggestFor: []string{}, + } + + return cmd +} + +// Run executes an info sub command using the specified options. +func (o *InfoOptions) Run(args []string) error { + var info Info + + hostInfo, err := hoststat.GetHostInfo() + if err != nil { + return fmt.Errorf("get host info failed!error:%w", err) + } + + info.HostName = hostInfo.HostName + info.OSRelease = hostInfo.Release + " " + hostInfo.OSBit + + memStat, err := hoststat.GetMemStat() + if err != nil { + return fmt.Errorf("get mem stat failed!error:%w", err) + } + + info.MemTotal = strconv.FormatUint(memStat.MemTotal, 10) + "M" + info.MemFree = strconv.FormatUint(memStat.MemFree, 10) + "M" + info.IPAddress = iputil.GetLocalIP() + + cpuStat, err := hoststat.GetCPUInfo() + if err != nil { + return fmt.Errorf("get cpu stat failed!error:%w", err) + } + + info.CPUCore = cpuStat.CoreCount + + s := reflect.ValueOf(&info).Elem() + typeOfInfo := s.Type() + + for i := 0; i < s.NumField(); i++ { + f := s.Field(i) + + v := fmt.Sprintf("%v", f.Interface()) + if v != "" { + fmt.Fprintf(o.Out, "%12s %v\n", typeOfInfo.Field(i).Name+":", f.Interface()) + } + } + + return nil +} diff --git a/tools/imctl/internal/imctl/cmd/new/new.go b/tools/imctl/internal/imctl/cmd/new/new.go new file mode 100644 index 000000000..6ac0667fc --- /dev/null +++ b/tools/imctl/internal/imctl/cmd/new/new.go @@ -0,0 +1,619 @@ +// Copyright © 2023 OpenIM. All rights reserved. +// +// Licensed 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 new used to generate demo command code. +// nolint: predeclared +package new + +import ( + "fmt" + "os" + "path/filepath" + "strings" + "text/template" + + "github.com/openim-sigs/component-base/pkg/util/fileutil" + "github.com/spf13/cobra" + "golang.org/x/text/cases" + "golang.org/x/text/language" + + "github.com/OpenIMSDK/Open-IM-Server/tools/imctl/internal/imctl/util/templates" + "github.com/OpenIMSDK/Open-IM-Server/tools/imctl/pkg/cli/genericclioptions" + cmdutil "github.com/OpenIMSDK/Open-IM-Server/tools/imctl/pkg/util" +) + +const ( + newUsageStr = "new CMD_NAME | CMD_NAME CMD_DESCRIPTION" +) + +var ( + newLong = templates.LongDesc(`Used to generate demo command source code file. + +Can use this command generate a command template file, and do some modify based on your needs. +This can improve your R&D efficiency.`) + + newExample = templates.Examples(` + # Create a default 'test' command file without a description + imctl new test + + # Create a default 'test' command file in /tmp/ + imctl new test -d /tmp/ + + # Create a default 'test' command file with a description + imctl new test "This is a test command" + + # Create command 'test' with two subcommands + imctl new -g test "This is a test command with two subcommands"`) + + newUsageErrStr = fmt.Sprintf( + "expected '%s'.\nat least CMD_NAME is a required argument for the new command", + newUsageStr, + ) + + cmdTemplate = `package {{.CommandName}} + +import ( + "fmt" + + "github.com/spf13/cobra" + + cmdutil "github.com/OpenIMSDK/Open-IM-Server/tools/imctl/pkg/util" + "github.com/OpenIMSDK/Open-IM-Server/tools/imctl/internal/imctl/util/templates" + "github.com/OpenIMSDK/Open-IM-Server/tools/imctl/pkg/cli/genericclioptions" +) + +const ( + {{.CommandName}}UsageStr = "{{.CommandName}} USERNAME PASSWORD" + maxStringLength = 17 +) + +// {{.CommandFunctionName}}Options is an options struct to support '{{.CommandName}}' sub command. +type {{.CommandFunctionName}}Options struct { + // options + StringOption string + StringSliceOption []string + IntOption int + BoolOption bool + + // args + Username string + Password string + + genericclioptions.IOStreams +} + +var ( + {{.CommandName}}Long = templates.LongDesc({{.Dot}}A longer description that spans multiple lines and likely contains examples +and usage of using your command. For example: + +Cobra is a CLI library for Go that empowers applications. +This application is a tool to generate the needed files +to quickly create a Cobra application.{{.Dot}}) + + {{.CommandName}}Example = templates.Examples({{.Dot}} + # Print all option values for {{.CommandName}} + imctl {{.CommandName}} marmotedu marmotedupass{{.Dot}}) + + {{.CommandName}}UsageErrStr = fmt.Sprintf("expected '%s'.\nUSERNAME and PASSWORD are required arguments for the {{.CommandName}} command", {{.CommandName}}UsageStr) +) + +// New{{.CommandFunctionName}}Options returns an initialized {{.CommandFunctionName}}Options instance. +func New{{.CommandFunctionName}}Options(ioStreams genericclioptions.IOStreams) *{{.CommandFunctionName}}Options { + return &{{.CommandFunctionName}}Options{ + StringOption: "default", + IOStreams: ioStreams, + } +} + +// NewCmd{{.CommandFunctionName}} returns new initialized instance of '{{.CommandName}}' sub command. +func NewCmd{{.CommandFunctionName}}(f cmdutil.Factory, ioStreams genericclioptions.IOStreams) *cobra.Command { + o := New{{.CommandFunctionName}}Options(ioStreams) + + cmd := &cobra.Command{ + Use: {{.CommandName}}UsageStr, + DisableFlagsInUseLine: true, + Aliases: []string{}, + Short: "{{.CommandDescription}}", + TraverseChildren: true, + Long: {{.CommandName}}Long, + Example: {{.CommandName}}Example, + Run: func(cmd *cobra.Command, args []string) { + cmdutil.CheckErr(o.Complete(f, cmd, args)) + cmdutil.CheckErr(o.Validate(cmd, args)) + cmdutil.CheckErr(o.Run(args)) + }, + SuggestFor: []string{}, + Args: func(cmd *cobra.Command, args []string) error { + // nolint: gomnd // no need + if len(args) < 2 { + return cmdutil.UsageErrorf(cmd, {{.CommandName}}UsageErrStr) + } + + // if need args equal to zero, uncomment the following code + /* + if len(args) != 0 { + return cmdutil.UsageErrorf(cmd, "Unexpected args: %v", args) + } + */ + + return nil + }, + } + + // mark flag as deprecated + _ = cmd.Flags().MarkDeprecated("deprecated-opt", "This flag is deprecated and will be removed in future.") + cmd.Flags().StringVarP(&o.StringOption, "string", "", o.StringOption, "String option.") + cmd.Flags().StringSliceVar(&o.StringSliceOption, "slice", o.StringSliceOption, "String slice option.") + cmd.Flags().IntVarP(&o.IntOption, "int", "i", o.IntOption, "Int option.") + cmd.Flags().BoolVarP(&o.BoolOption, "bool", "b", o.BoolOption, "Bool option.") + + return cmd +} + +// Complete completes all the required options. +func (o *{{.CommandFunctionName}}Options) Complete(f cmdutil.Factory, cmd *cobra.Command, args []string) error { + if o.StringOption != "" { + o.StringOption += "(complete)" + } + + o.Username = args[0] + o.Password = args[1] + + return nil +} + +// Validate makes sure there is no discrepency in command options. +func (o *{{.CommandFunctionName}}Options) Validate(cmd *cobra.Command, args []string) error { + if len(o.StringOption) > maxStringLength { + return cmdutil.UsageErrorf(cmd, "--string length must less than 18") + } + + if o.IntOption < 0 { + return cmdutil.UsageErrorf(cmd, "--int must be a positive integer: %v", o.IntOption) + } + + return nil +} + +// Run executes a {{.CommandName}} sub command using the specified options. +func (o *{{.CommandFunctionName}}Options) Run(args []string) error { + fmt.Fprintf(o.Out, "The following is option values:\n") + fmt.Fprintf(o.Out, "==> --string: %v\n==> --slice: %v\n==> --int: %v\n==> --bool: %v\n", + o.StringOption, o.StringSliceOption, o.IntOption, o.BoolOption) + + fmt.Fprintf(o.Out, "\nThe following is args values:\n") + fmt.Fprintf(o.Out, "==> username: %v\n==> password: %v\n", o.Username, o.Password) + + return nil +} +` + + maincmdTemplate = `package {{.CommandName}} + +import ( + "github.com/spf13/cobra" + "github.com/spf13/viper" + + cmdutil "github.com/OpenIMSDK/Open-IM-Server/tools/imctl/pkg/util" + "github.com/OpenIMSDK/Open-IM-Server/tools/imctl/internal/imctl/util/templates" + "github.com/OpenIMSDK/Open-IM-Server/tools/imctl/pkg/cli/genericclioptions" +) + +const maxStringLength = 17 + +var ( + {{.CommandName}}Long = templates.LongDesc({{.Dot}} + Demo command. + + This commands show you how to implement a command with two sub commands.{{.Dot}}) +) + +// NewCmd{{.CommandFunctionName}} returns new initialized instance of '{{.CommandName}}' sub command. +func NewCmd{{.CommandFunctionName}}(f cmdutil.Factory, ioStreams genericclioptions.IOStreams) *cobra.Command { + cmd := &cobra.Command{ + Use: "{{.CommandName}} SUBCOMMAND", + DisableFlagsInUseLine: true, + Short: "{{.CommandDescription}}", + Long: {{.CommandName}}Long, + Run: cmdutil.DefaultSubCommandRun(ioStreams.ErrOut), + } + + // add subcommands + cmd.AddCommand(NewCmdSubCmd1(f, ioStreams)) + cmd.AddCommand(NewCmdSubCmd2(f, ioStreams)) + + // add persistent flags for '{{.CommandName}}' + cmdutil.AddCleanFlags(cmd) + + // persistent flag, we can get the value in subcommand via {{.Dot}}viper.Get{{.Dot}} + cmd.PersistentFlags().StringP("persistent", "p", "this is a persistent option", "Cobra persistent option.") + + // bind flags with viper + viper.BindPFlag("persistent", cmd.PersistentFlags().Lookup("persistent")) + + return cmd +} +` + subcmd1Template = `package {{.CommandName}} + +import ( + "fmt" + + "github.com/spf13/cobra" + "github.com/spf13/viper" + + cmdutil "github.com/OpenIMSDK/Open-IM-Server/tools/imctl/pkg/util" + "github.com/OpenIMSDK/Open-IM-Server/tools/imctl/internal/imctl/util/templates" + "github.com/OpenIMSDK/Open-IM-Server/tools/imctl/pkg/cli/genericclioptions" +) + +const ( + subcmd1UsageStr = "subcmd1 USERNAME PASSWORD" +) + +// SubCmd1Options is an options struct to support subcmd1 subcommands. +type SubCmd1Options struct { + // options + StringOption string + StringSliceOption []string + IntOption int + BoolOption bool + PersistentOption string + + // args + Username string + Password string + + genericclioptions.IOStreams +} + +var ( + subcmd1Long = templates.LongDesc({{.Dot}}A longer description that spans multiple lines and likely contains examples +and usage of using your command. For example: + +Cobra is a CLI library for Go that empowers applications. +This application is a tool to generate the needed files +to quickly create a Cobra application.{{.Dot}}) + + subcmd1Example = templates.Examples({{.Dot}} + # Print all option values for subcmd1 + imctl {{.CommandName}} subcmd1 marmotedu marmotedupass + + # Print all option values for subcmd1 with --persistent specified + imctl {{.CommandName}} subcmd1 marmotedu marmotedupass --persistent="specified persistent option in command line"{{.Dot}}) + + subcmd1UsageErrStr = fmt.Sprintf("expected '%s'.\nUSERNAME and PASSWORD are required arguments for the subcmd1 command", subcmd1UsageStr) +) + +// NewSubCmd1Options returns an initialized SubCmd1Options instance. +func NewSubCmd1Options(ioStreams genericclioptions.IOStreams) *SubCmd1Options { + return &SubCmd1Options{ + StringOption: "default", + IOStreams: ioStreams, + } +} + +// NewCmdSubCmd1 returns new initialized instance of subcmd1 sub command. +func NewCmdSubCmd1(f cmdutil.Factory, ioStreams genericclioptions.IOStreams) *cobra.Command { + o := NewSubCmd1Options(ioStreams) + + cmd := &cobra.Command{ + Use: "subcmd1 USERNAME PASSWORD", + DisableFlagsInUseLine: true, + Aliases: []string{"sub1"}, + Short: "A brief description of your command", + TraverseChildren: true, + Long: subcmd1Long, + Example: subcmd1Example, + Run: func(cmd *cobra.Command, args []string) { + cmdutil.CheckErr(o.Complete(f, cmd, args)) + cmdutil.CheckErr(o.Validate(cmd, args)) + cmdutil.CheckErr(o.Run(args)) + }, + SuggestFor: []string{}, + Args: func(cmd *cobra.Command, args []string) error { + // nolint: gomnd // no need + if len(args) < 2 { + return cmdutil.UsageErrorf(cmd, subcmd1UsageErrStr) + } + + return nil + }, + } + + // mark flag as deprecated + _ = cmd.Flags().MarkDeprecated("deprecated-opt", "This flag is deprecated and will be removed in future.") + cmd.Flags().StringVarP(&o.StringOption, "string", "", o.StringOption, "String option.") + cmd.Flags().StringSliceVar(&o.StringSliceOption, "slice", o.StringSliceOption, "String slice option.") + cmd.Flags().IntVarP(&o.IntOption, "int", "i", o.IntOption, "Int option.") + cmd.Flags().BoolVarP(&o.BoolOption, "bool", "b", o.BoolOption, "Bool option.") + + return cmd +} + +// Complete completes all the required options. +func (o *SubCmd1Options) Complete(f cmdutil.Factory, cmd *cobra.Command, args []string) error { + if o.StringOption != "" { + o.StringOption += "(complete)" + } + + o.PersistentOption = viper.GetString("persistent") + o.Username = args[0] + o.Password = args[1] + + return nil +} + +// Validate makes sure there is no discrepency in command options. +func (o *SubCmd1Options) Validate(cmd *cobra.Command, args []string) error { + if len(o.StringOption) > maxStringLength { + return cmdutil.UsageErrorf(cmd, "--string length must less than 18") + } + + if o.IntOption < 0 { + return cmdutil.UsageErrorf(cmd, "--int must be a positive integer: %v", o.IntOption) + } + + return nil +} + +// Run executes a subcmd1 subcommand using the specified options. +func (o *SubCmd1Options) Run(args []string) error { + fmt.Fprintf(o.Out, "The following is option values:\n") + fmt.Fprintf(o.Out, "==> --string: %v\n==> --slice: %v\n==> --int: %v\n==> --bool: %v\n==> --persistent: %v\n", + o.StringOption, o.StringSliceOption, o.IntOption, o.BoolOption, o.PersistentOption) + + fmt.Fprintf(o.Out, "\nThe following is args values:\n") + fmt.Fprintf(o.Out, "==> username: %v\n==> password: %v\n", o.Username, o.Password) + return nil +} +` + subcmd2Template = `package {{.CommandName}} + +import ( + "fmt" + + "github.com/spf13/cobra" + + cmdutil "github.com/OpenIMSDK/Open-IM-Server/tools/imctl/pkg/util" + "github.com/OpenIMSDK/Open-IM-Server/tools/imctl/internal/imctl/util/templates" + "github.com/OpenIMSDK/Open-IM-Server/tools/imctl/pkg/cli/genericclioptions" +) + +// SubCmd2Options is an options struct to support subcmd2 subcommands. +type SubCmd2Options struct { + StringOption string + StringSliceOption []string + IntOption int + BoolOption bool + + genericclioptions.IOStreams +} + +var ( + subcmd2Long = templates.LongDesc({{.Dot}}A longer description that spans multiple lines and likely contains examples +and usage of using your command. For example: + +Cobra is a CLI library for Go that empowers applications. +This application is a tool to generate the needed files +to quickly create a Cobra application.{{.Dot}}) + + subcmd2Example = templates.Examples({{.Dot}} + # Print all option values for subcmd2 + imctl {{.CommandName}} subcmd2{{.Dot}}) +) + +// NewSubCmd2Options returns an initialized SubCmd2Options instance. +func NewSubCmd2Options(ioStreams genericclioptions.IOStreams) *SubCmd2Options { + return &SubCmd2Options{ + StringOption: "default", + IOStreams: ioStreams, + } +} + +// NewCmdSubCmd2 returns new initialized instance of subcmd2 sub command. +func NewCmdSubCmd2(f cmdutil.Factory, ioStreams genericclioptions.IOStreams) *cobra.Command { + o := NewSubCmd2Options(ioStreams) + + cmd := &cobra.Command{ + Use: "subcmd2", + DisableFlagsInUseLine: true, + Aliases: []string{"sub2"}, + Short: "A brief description of your command", + TraverseChildren: true, + Long: subcmd2Long, + Example: subcmd2Example, + Run: func(cmd *cobra.Command, args []string) { + cmdutil.CheckErr(o.Complete(f, cmd, args)) + cmdutil.CheckErr(o.Validate(cmd, args)) + cmdutil.CheckErr(o.Run(args)) + }, + SuggestFor: []string{}, + } + + // mark flag as deprecated + cmd.Flags().StringVarP(&o.StringOption, "string", "", o.StringOption, "String option.") + cmd.Flags().StringSliceVar(&o.StringSliceOption, "slice", o.StringSliceOption, "String slice option.") + cmd.Flags().IntVarP(&o.IntOption, "int", "i", o.IntOption, "Int option.") + cmd.Flags().BoolVarP(&o.BoolOption, "bool", "b", o.BoolOption, "Bool option.") + + return cmd +} + +// Complete completes all the required options. +func (o *SubCmd2Options) Complete(f cmdutil.Factory, cmd *cobra.Command, args []string) error { + if len(args) != 0 { + return cmdutil.UsageErrorf(cmd, "Unexpected args: %v", args) + } + + if o.StringOption != "" { + o.StringOption += "(complete)" + } + + return nil +} + +// Validate makes sure there is no discrepency in command options. +func (o *SubCmd2Options) Validate(cmd *cobra.Command, args []string) error { + if len(o.StringOption) > maxStringLength { + return cmdutil.UsageErrorf(cmd, "--string length must less than 18") + } + + if o.IntOption < 0 { + return cmdutil.UsageErrorf(cmd, "--int must be a positive integer: %v", o.IntOption) + } + + return nil +} + +// Run executes a subcmd2 subcommand using the specified options. +func (o *SubCmd2Options) Run(args []string) error { + fmt.Fprintf(o.Out, "The following is option values:\n") + fmt.Fprintf(o.Out, "==> --string: %v\n==> --slice: %v\n==> --int: %v\n==> --bool: %v\n", + o.StringOption, o.StringSliceOption, o.IntOption, o.BoolOption) + return nil +} +` +) + +// NewOptions is an options struct to support 'new' sub command. +type NewOptions struct { + Group bool + Outdir string + + // command template options, will render to command template + CommandName string + CommandDescription string + CommandFunctionName string + Dot string + + genericclioptions.IOStreams +} + +// NewNewOptions returns an initialized NewOptions instance. +func NewNewOptions(ioStreams genericclioptions.IOStreams) *NewOptions { + return &NewOptions{ + Group: false, + Outdir: ".", + CommandDescription: "A brief description of your command", + Dot: "`", + IOStreams: ioStreams, + } +} + +// NewCmdNew returns new initialized instance of 'new' sub command. +func NewCmdNew(f cmdutil.Factory, ioStreams genericclioptions.IOStreams) *cobra.Command { + o := NewNewOptions(ioStreams) + + cmd := &cobra.Command{ + Use: newUsageStr, + DisableFlagsInUseLine: true, + Short: "Generate demo command code", + Long: newLong, + Example: newExample, + Run: func(cmd *cobra.Command, args []string) { + cmdutil.CheckErr(o.Complete(cmd, args)) + cmdutil.CheckErr(o.Validate(cmd)) + cmdutil.CheckErr(o.Run(args)) + }, + Aliases: []string{}, + SuggestFor: []string{}, + } + + cmd.Flags().BoolVarP(&o.Group, "group", "g", o.Group, "Generate two subcommands.") + cmd.Flags().StringVarP(&o.Outdir, "outdir", "d", o.Outdir, "Where to create demo command files.") + + return cmd +} + +// Complete completes all the required options. +func (o *NewOptions) Complete(cmd *cobra.Command, args []string) error { + if len(args) < 1 { + return cmdutil.UsageErrorf(cmd, newUsageErrStr) + } + + o.CommandName = strings.ToLower(args[0]) + if len(args) > 1 { + o.CommandDescription = args[1] + } + + o.CommandFunctionName = cases.Title(language.English).String(o.CommandName) + + return nil +} + +// Validate makes sure there is no discrepency in command options. +func (o *NewOptions) Validate(cmd *cobra.Command) error { + return nil +} + +// Run executes a new sub command using the specified options. +func (o *NewOptions) Run(args []string) error { + if o.Group { + return o.CreateCommandWithSubCommands() + } + + return o.CreateCommand() +} + +// CreateCommand create the command with options. +func (o *NewOptions) CreateCommand() error { + return o.GenerateGoCode(o.CommandName+".go", cmdTemplate) +} + +// CreateCommandWithSubCommands create sub commands with options. +func (o *NewOptions) CreateCommandWithSubCommands() error { + if err := o.GenerateGoCode(o.CommandName+".go", maincmdTemplate); err != nil { + return err + } + + if err := o.GenerateGoCode(o.CommandName+"_subcmd1.go", subcmd1Template); err != nil { + return err + } + + if err := o.GenerateGoCode(o.CommandName+"_subcmd2.go", subcmd2Template); err != nil { + return err + } + + return nil +} + +// GenerateGoCode generate go source file. +func (o *NewOptions) GenerateGoCode(name, codeTemplate string) error { + tmpl, err := template.New("cmd").Parse(codeTemplate) + if err != nil { + return err + } + + err = fileutil.EnsureDirAll(o.Outdir) + if err != nil { + return err + } + + filename := filepath.Join(o.Outdir, name) + fd, err := os.Create(filename) + if err != nil { + return err + } + defer fd.Close() + + err = tmpl.Execute(fd, o) + if err != nil { + return err + } + + fmt.Printf("Command file generated: %s\n", filename) + + return nil +} diff --git a/tools/imctl/internal/imctl/cmd/options/options.go b/tools/imctl/internal/imctl/cmd/options/options.go new file mode 100644 index 000000000..9dfee06c9 --- /dev/null +++ b/tools/imctl/internal/imctl/cmd/options/options.go @@ -0,0 +1,50 @@ +// Copyright © 2023 OpenIM. All rights reserved. +// +// Licensed 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 options print a list of global command-line options (applies to all commands). +package options + +import ( + "io" + + "github.com/spf13/cobra" + + "github.com/OpenIMSDK/Open-IM-Server/tools/imctl/pkg/util/templates" +) + +var optionsExample = templates.Examples(` + # Print flags inherited by all commands + iamctl options`) + +// NewCmdOptions implements the options command. +func NewCmdOptions(out io.Writer) *cobra.Command { + cmd := &cobra.Command{ + Use: "options", + Short: "Print the list of flags inherited by all commands", + Long: "Print the list of flags inherited by all commands", + Example: optionsExample, + Run: func(cmd *cobra.Command, args []string) { + _ = cmd.Usage() + }, + } + + // The `options` command needs write its output to the `out` stream + // (typically stdout). Without calling SetOutput here, the Usage() + // function call will fall back to stderr. + cmd.SetOutput(out) + + templates.UseOptionsTemplates(cmd) + + return cmd +} diff --git a/tools/imctl/internal/imctl/cmd/profiling.go b/tools/imctl/internal/imctl/cmd/profiling.go new file mode 100644 index 000000000..f0be6e38c --- /dev/null +++ b/tools/imctl/internal/imctl/cmd/profiling.go @@ -0,0 +1,95 @@ +// Copyright © 2023 OpenIM. All rights reserved. +// +// Licensed 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 ( + "errors" + "fmt" + "os" + "runtime" + "runtime/pprof" + + "github.com/spf13/pflag" +) + +// profiling configuration variables +var ( + profileName string = "none" // Name of the profile to capture. + profileOutput string = "profile.pprof" // File to write the profile data. +) + +// addProfilingFlags registers profiling related flags to the given FlagSet. +func addProfilingFlags(flags *pflag.FlagSet) { + flags.StringVar( + &profileName, + "profile", + "none", + "Type of profile to capture. Options: none, cpu, heap, goroutine, threadcreate, block, mutex", + ) + flags.StringVar(&profileOutput, "profile-output", "profile.pprof", "File to write the profile data") +} + +// initProfiling sets up profiling based on the user's choice. +// If 'cpu' is selected, it starts the CPU profile. For block and mutex profiles, +// sampling rates are set up. +func initProfiling() error { + switch profileName { + case "none": + return nil + case "cpu": + f, err := os.Create(profileOutput) + if err != nil { + return err + } + return pprof.StartCPUProfile(f) + case "block": + runtime.SetBlockProfileRate(1) // Sampling every block event + return nil + case "mutex": + runtime.SetMutexProfileFraction(1) // Sampling every mutex event + return nil + default: + if profile := pprof.Lookup(profileName); profile == nil { + return fmt.Errorf("unknown profile type: '%s'", profileName) + } + return nil + } +} + +// flushProfiling writes the profiling data to the specified file. +// For heap profiles, it runs the GC before capturing the data. +// It stops the CPU profile if it was started. +func flushProfiling() error { + switch profileName { + case "none": + return nil + case "cpu": + pprof.StopCPUProfile() + return nil + case "heap": + runtime.GC() // Run garbage collection before writing heap profile + fallthrough + default: + profile := pprof.Lookup(profileName) + if profile == nil { + return errors.New("invalid profile type") + } + f, err := os.Create(profileOutput) + if err != nil { + return err + } + return profile.WriteTo(f, 0) + } +} diff --git a/tools/imctl/internal/imctl/cmd/version/version.go b/tools/imctl/internal/imctl/cmd/version/version.go new file mode 100644 index 000000000..851f9b28d --- /dev/null +++ b/tools/imctl/internal/imctl/cmd/version/version.go @@ -0,0 +1,165 @@ +// Copyright © 2023 OpenIM. All rights reserved. +// +// Licensed 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 print the client and server version information. +package version + +import ( + "context" + "errors" + "fmt" + + "github.com/ghodss/yaml" + "github.com/openim-sigs/component-base/pkg/json" + "github.com/openim-sigs/component-base/pkg/version" + "github.com/spf13/cobra" + + "github.com/OpenIMSDK/Open-IM-Server/tools/imctl/internal/imctl/util/templates" + "github.com/OpenIMSDK/Open-IM-Server/tools/imctl/pkg/cli/genericclioptions" + cmdutil "github.com/OpenIMSDK/Open-IM-Server/tools/imctl/pkg/util" +) + +// Version is a struct for version information. +type Version struct { + ClientVersion *version.Info `json:"clientVersion,omitempty" yaml:"clientVersion,omitempty"` + ServerVersion *version.Info `json:"serverVersion,omitempty" yaml:"serverVersion,omitempty"` +} + +var versionExample = templates.Examples(` + # Print the client and server versions for the current context + imctl version`) + +// Options is a struct to support version command. +type Options struct { + ClientOnly bool + Short bool + Output string + + client *restclient.RESTClient + genericclioptions.IOStreams +} + +// NewOptions returns initialized Options. +func NewOptions(ioStreams genericclioptions.IOStreams) *Options { + return &Options{ + IOStreams: ioStreams, + } +} + +// NewCmdVersion returns a cobra command for fetching versions. +func NewCmdVersion(f cmdutil.Factory, ioStreams genericclioptions.IOStreams) *cobra.Command { + o := NewOptions(ioStreams) + cmd := &cobra.Command{ + Use: "version", + Short: "Print the client and server version information", + Long: "Print the client and server version information for the current context", + Example: versionExample, + Run: func(cmd *cobra.Command, args []string) { + cmdutil.CheckErr(o.Complete(f, cmd)) + cmdutil.CheckErr(o.Validate()) + cmdutil.CheckErr(o.Run()) + }, + } + + cmd.Flags().BoolVar( + &o.ClientOnly, + "client", + o.ClientOnly, + "If true, shows client version only (no server required).", + ) + cmd.Flags().BoolVar(&o.Short, "short", o.Short, "If true, print just the version number.") + cmd.Flags().StringVarP(&o.Output, "output", "o", o.Output, "One of 'yaml' or 'json'.") + + return cmd +} + +// Complete completes all the required options. +func (o *Options) Complete(f cmdutil.Factory, cmd *cobra.Command) error { + var err error + if o.ClientOnly { + return nil + } + + o.client, err = f.RESTClient() + if err != nil { + return err + } + + return nil +} + +// Validate validates the provided options. +func (o *Options) Validate() error { + if o.Output != "" && o.Output != "yaml" && o.Output != "json" { + return errors.New(`--output must be 'yaml' or 'json'`) + } + + return nil +} + +// Run executes version command. +func (o *Options) Run() error { + var ( + serverVersion *version.Info + serverErr error + versionInfo Version + ) + + clientVersion := version.Get() + versionInfo.ClientVersion = &clientVersion + + if !o.ClientOnly && o.client != nil { + // Always request fresh data from the server + if err := o.client.Get().AbsPath("/version").Do(context.TODO()).Into(&serverVersion); err != nil { + return err + } + versionInfo.ServerVersion = serverVersion + } + + switch o.Output { + case "": + if o.Short { + fmt.Fprintf(o.Out, "Client Version: %s\n", clientVersion.GitVersion) + + if serverVersion != nil { + fmt.Fprintf(o.Out, "Server Version: %s\n", serverVersion.GitVersion) + } + } else { + fmt.Fprintf(o.Out, "Client Version: %s\n", fmt.Sprintf("%#v", clientVersion)) + if serverVersion != nil { + fmt.Fprintf(o.Out, "Server Version: %s\n", fmt.Sprintf("%#v", *serverVersion)) + } + } + case "yaml": + marshaled, err := yaml.Marshal(&versionInfo) + if err != nil { + return err + } + + fmt.Fprintln(o.Out, string(marshaled)) + case "json": + marshaled, err := json.MarshalIndent(&versionInfo, "", " ") + if err != nil { + return err + } + + fmt.Fprintln(o.Out, string(marshaled)) + default: + // There is a bug in the program if we hit this case. + // However, we follow a policy of never panicking. + return fmt.Errorf("VersionOptions were not validated: --output=%q should have been rejected", o.Output) + } + + return serverErr +}