feat: remove openim config

Signed-off-by: Xinwei Xiong(cubxxw) <3293172751nss@gmail.com>
pull/1055/head
Xinwei Xiong(cubxxw) 2 years ago
parent 78912cf455
commit 355223f494

@ -48,7 +48,7 @@ function openim::crontask::start()
openim::log::info "Start OpenIM Cron, binary root: ${SERVER_NAME}" openim::log::info "Start OpenIM Cron, binary root: ${SERVER_NAME}"
openim::log::status "Start OpenIM Cron, path: ${OPENIM_CRONTASK_BINARY}" openim::log::status "Start OpenIM Cron, path: ${OPENIM_CRONTASK_BINARY}"
openim::util::stop_services_with_name ${SERVER_NAME} openim::util::stop_services_with_name ${OPENIM_CRONTASK_BINARY}
openim::log::status "start cron_task process, path: ${OPENIM_CRONTASK_BINARY}" openim::log::status "start cron_task process, path: ${OPENIM_CRONTASK_BINARY}"
nohup ${OPENIM_CRONTASK_BINARY} >> ${LOG_FILE} 2>&1 & nohup ${OPENIM_CRONTASK_BINARY} >> ${LOG_FILE} 2>&1 &

@ -1,25 +0,0 @@
# Notes about go workspace
As openim is using go1.18's [workspace feature](https://go.dev/doc/tutorial/workspaces), once you add a new module, you need to run `go work use -r .` at root directory to update the workspace synced.
### Create a new extensions
1. Create your tools_name directory in pkg `/tools` first and cd into it.
2. Init the project.
3. Then `go work use -r .` at current directory to update the workspace.
4. Create your tools
You can execute the following commands to do things above:
```bash
# edit the CRD_NAME and CRD_GROUP to your own
export OPENIM_TOOLS_NAME=<Changeme>
# copy and paste to create a new CRD and Controller
mkdir tools/${OPENIM_TOOLS_NAME}
cd tools/${OPENIM_TOOLS_NAME}
go mod init github.com/openimsdk/open-im-server/tools/${OPENIM_TOOLS_NAME}
go mod tidy
go work use -r .
cd ../..
```

@ -1,308 +0,0 @@
// 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"
"log"
"os"
"os/exec"
"regexp"
"sort"
"strings"
)
var (
mergeRequest = regexp.MustCompile(`Merge pull request #([\d]+)`)
webconsoleBump = regexp.MustCompile(regexp.QuoteMeta("bump(github.com/openshift/origin-web-console): ") + `([\w]+)`)
upstreamKube = regexp.MustCompile(`^UPSTREAM: (\d+)+:(.+)`)
upstreamRepo = regexp.MustCompile(`^UPSTREAM: ([\w/-]+): (\d+)+:(.+)`)
prefix = regexp.MustCompile(`^[\w-]: `)
assignments = []prefixAssignment{
{"cluster up", "cluster"},
{" pv ", "storage"},
{"haproxy", "router"},
{"router", "router"},
{"route", "route"},
{"authoriz", "auth"},
{"rbac", "auth"},
{"authent", "auth"},
{"reconcil", "auth"},
{"auth", "auth"},
{"role", "auth"},
{" dc ", "deploy"},
{"deployment", "deploy"},
{"rolling", "deploy"},
{"security context constr", "security"},
{"scc", "security"},
{"pipeline", "build"},
{"build", "build"},
{"registry", "registry"},
{"registries", "image"},
{"image", "image"},
{" arp ", "network"},
{" cni ", "network"},
{"egress", "network"},
{"network", "network"},
{"oc ", "cli"},
{"template", "template"},
{"etcd", "server"},
{"pod", "node"},
{"hack/", "hack"},
{"e2e", "test"},
{"integration", "test"},
{"cluster", "cluster"},
{"master", "server"},
{"packages", "hack"},
{"api", "server"},
}
)
type prefixAssignment struct {
term string
prefix string
}
type commit struct {
short string
parents []string
message string
}
func contains(arr []string, value string) bool {
for _, s := range arr {
if s == value {
return true
}
}
return false
}
func main() {
log.SetFlags(0)
if len(os.Args) != 3 {
log.Fatalf("Must specify two arguments, FROM and TO")
}
from := os.Args[1]
to := os.Args[2]
out, err := exec.Command("git", "log", "--topo-order", "--pretty=tformat:%h %p|%s", "--reverse", fmt.Sprintf("%s..%s", from, to)).CombinedOutput()
if err != nil {
log.Fatal(err)
}
hide := make(map[string]struct{})
var apiChanges []string
var webconsole []string
var commits []commit
var upstreams []commit
var bumps []commit
for _, line := range strings.Split(string(out), "\n") {
if len(strings.TrimSpace(line)) == 0 {
continue
}
parts := strings.SplitN(line, "|", 2)
hashes := strings.Split(parts[0], " ")
c := commit{short: hashes[0], parents: hashes[1:], message: parts[1]}
if strings.HasPrefix(c.message, "UPSTREAM: ") {
hide[c.short] = struct{}{}
upstreams = append(upstreams, c)
}
if strings.HasPrefix(c.message, "bump(") {
hide[c.short] = struct{}{}
bumps = append(bumps, c)
}
if len(c.parents) == 1 {
commits = append(commits, c)
continue
}
matches := mergeRequest.FindStringSubmatch(line)
if len(matches) == 0 {
// this may have been a human pressing the merge button, we'll just record this as a direct push
continue
}
// split the accumulated commits into any that are force merges (assumed to be the initial set due
// to --topo-order) from the PR commits as soon as we see any of our merge parents. Then print
// any of the force merges
var first int
for i := range commits {
first = i
if contains(c.parents, commits[i].short) {
first++
break
}
}
individual := commits[:first]
merged := commits[first:]
for _, commit := range individual {
if len(commit.parents) > 1 {
continue
}
if _, ok := hide[commit.short]; ok {
continue
}
fmt.Printf("force-merge: %s %s\n", commit.message, commit.short)
}
// try to find either the PR title or the first commit title from the merge commit
out, err := exec.Command("git", "show", "--pretty=tformat:%b", c.short).CombinedOutput()
if err != nil {
log.Fatal(err)
}
var message string
para := strings.Split(string(out), "\n\n")
if len(para) > 0 && strings.HasPrefix(para[0], "Automatic merge from submit-queue") {
para = para[1:]
}
// this is no longer necessary with the submit queue in place
if len(para) > 0 && strings.HasPrefix(para[0], "Merged by ") {
para = para[1:]
}
// post submit-queue, the merge bot will add the PR title, which is usually pretty good
if len(para) > 0 {
message = strings.Split(para[0], "\n")[0]
}
if len(message) == 0 && len(merged) > 0 {
message = merged[0].message
}
if len(message) > 0 && len(merged) == 1 && message == merged[0].message {
merged = nil
}
// try to calculate a prefix based on the diff
if len(message) > 0 && !prefix.MatchString(message) {
prefix, ok := findPrefixFor(message, merged)
if ok {
message = prefix + ": " + message
}
}
// github merge
// has api changes
display := fmt.Sprintf("%s [\\#%s](https://github.com/openimsdk/Open-IM-Server/pull/%s)", message, matches[1], matches[1])
if hasFileChanges(c.short, "pkg/apistruct/") {
apiChanges = append(apiChanges, display)
}
var filtered []commit
for _, commit := range merged {
if _, ok := hide[commit.short]; ok {
continue
}
filtered = append(filtered, commit)
}
if len(filtered) > 0 {
fmt.Printf("- %s\n", display)
for _, commit := range filtered {
fmt.Printf(" - %s (%s)\n", commit.message, commit.short)
}
}
// stick the merge commit in at the beginning of the next list so we can anchor the previous parent
commits = []commit{c}
}
// chunk the bumps
var lines []string
for _, commit := range bumps {
if m := webconsoleBump.FindStringSubmatch(commit.message); len(m) > 0 {
webconsole = append(webconsole, m[1])
continue
}
lines = append(lines, commit.message)
}
lines = sortAndUniq(lines)
for _, line := range lines {
fmt.Printf("- %s\n", line)
}
// chunk the upstreams
lines = nil
for _, commit := range upstreams {
lines = append(lines, commit.message)
}
lines = sortAndUniq(lines)
for _, line := range lines {
fmt.Printf("- %s\n", upstreamLinkify(line))
}
if len(webconsole) > 0 {
fmt.Printf("- web: from %s^..%s\n", webconsole[0], webconsole[len(webconsole)-1])
}
for _, apiChange := range apiChanges {
fmt.Printf(" - %s\n", apiChange)
}
}
func findPrefixFor(message string, commits []commit) (string, bool) {
message = strings.ToLower(message)
for _, m := range assignments {
if strings.Contains(message, m.term) {
return m.prefix, true
}
}
for _, c := range commits {
if prefix, ok := findPrefixFor(c.message, nil); ok {
return prefix, ok
}
}
return "", false
}
func hasFileChanges(commit string, prefixes ...string) bool {
out, err := exec.Command("git", "diff", "--name-only", fmt.Sprintf("%s^..%s", commit, commit)).CombinedOutput()
if err != nil {
log.Fatal(err)
}
for _, file := range strings.Split(string(out), "\n") {
for _, prefix := range prefixes {
if strings.HasPrefix(file, prefix) {
return true
}
}
}
return false
}
func sortAndUniq(lines []string) []string {
sort.Strings(lines)
out := make([]string, 0, len(lines))
last := ""
for _, s := range lines {
if last == s {
continue
}
last = s
out = append(out, s)
}
return out
}
func upstreamLinkify(line string) string {
if m := upstreamKube.FindStringSubmatch(line); len(m) > 0 {
return fmt.Sprintf("UPSTREAM: [#%s](https://github.com/openimsdk/open-im-server/pull/%s):%s", m[1], m[1], m[2])
}
if m := upstreamRepo.FindStringSubmatch(line); len(m) > 0 {
return fmt.Sprintf("UPSTREAM: [%s#%s](https://github.com/%s/pull/%s):%s", m[1], m[2], m[1], m[2], m[3])
}
return line
}

@ -1,3 +0,0 @@
module github.com/openimsdk/open-im-server/v3/tools/changelog
go 1.18

@ -1,318 +0,0 @@
// 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 (
"context"
"database/sql"
"flag"
"fmt"
"net"
"net/url"
"os"
"time"
"github.com/minio/minio-go/v7"
"github.com/redis/go-redis/v9"
"gopkg.in/yaml.v3"
"github.com/IBM/sarama"
"github.com/OpenIMSDK/tools/errs"
"github.com/OpenIMSDK/tools/utils"
"github.com/go-zookeeper/zk"
"go.mongodb.org/mongo-driver/mongo"
"go.mongodb.org/mongo-driver/mongo/options"
"go.mongodb.org/mongo-driver/mongo/readpref"
"gorm.io/driver/mysql"
"gorm.io/gorm"
"github.com/openimsdk/open-im-server/v3/pkg/common/config"
"github.com/openimsdk/open-im-server/v3/pkg/common/kafka"
"github.com/minio/minio-go/v7/pkg/credentials"
)
const (
// defaultCfgPath is the default path of the configuration file
defaultCfgPath = "../../../../../config/config.yaml"
minioHealthCheckDuration = 1
maxRetry = 100
componentStartErrCode = 6000
configErrCode = 6001
)
var (
cfgPath = flag.String("c", defaultCfgPath, "Path to the configuration file")
ErrComponentStart = errs.NewCodeError(componentStartErrCode, "ComponentStartErr")
ErrConfig = errs.NewCodeError(configErrCode, "Config file is incorrect")
)
func initCfg() error {
data, err := os.ReadFile(*cfgPath)
if err != nil {
return err
}
return yaml.Unmarshal(data, &config.Config)
}
type checkFunc struct {
name string
function func() error
}
func main() {
flag.Parse()
if err := initCfg(); err != nil {
fmt.Printf("Read config failed: %v\n", err)
return
}
checks := []checkFunc{
{name: "Mysql", function: checkMysql},
{name: "Mongo", function: checkMongo},
{name: "Minio", function: checkMinio},
{name: "Redis", function: checkRedis},
{name: "Zookeeper", function: checkZookeeper},
{name: "Kafka", function: checkKafka},
}
for i := 0; i < maxRetry; i++ {
if i != 0 {
time.Sleep(3 * time.Second)
}
fmt.Printf("Checking components Round %v...\n", i+1)
allSuccess := true
for _, check := range checks {
err := check.function()
if err != nil {
errorPrint(fmt.Sprintf("Starting %s failed: %v", check.name, err))
allSuccess = false
break
} else {
successPrint(fmt.Sprintf("%s starts successfully", check.name))
}
}
if allSuccess {
successPrint("All components started successfully!")
return
}
}
os.Exit(1)
}
func checkMinioIP() error {
for _, i := range []string{config.Config.Object.ApiURL, config.Config.Object.Minio.SignEndpoint} {
u, err := url.Parse(i)
if err != nil {
return utils.Wrap(err, "api format error,please check config file apiURL or Minio SignEndpoint")
}
if u.Scheme == "https" {
continue
}
host, _, err := net.SplitHostPort(u.Host)
if err != nil {
host = u.Host
}
if host == "127.0.0.1" {
return ErrConfig.Wrap("apiURL or Minio SignEndpoint endpoint contain 127.0.0.1,please modify it")
}
}
return nil
}
func checkMysql() error {
var sqlDB *sql.DB
defer func() {
if sqlDB != nil {
sqlDB.Close()
}
}()
dsn := fmt.Sprintf("%s:%s@tcp(%s)/%s?charset=utf8mb4&parseTime=true&loc=Local",
config.Config.Mysql.Username, config.Config.Mysql.Password, config.Config.Mysql.Address[0], "mysql")
db, err := gorm.Open(mysql.Open(dsn), nil)
if err != nil {
return errs.Wrap(err)
} else {
sqlDB, err = db.DB()
err = sqlDB.Ping()
if err != nil {
return errs.Wrap(err)
}
}
return nil
}
func checkMongo() error {
var client *mongo.Client
defer func() {
if client != nil {
client.Disconnect(context.TODO())
}
}()
mongodbHosts := ""
for i, v := range config.Config.Mongo.Address {
if i == len(config.Config.Mongo.Address)-1 {
mongodbHosts += v
} else {
mongodbHosts += v + ","
}
}
client, err := mongo.Connect(context.TODO(), options.Client().ApplyURI(
fmt.Sprintf("mongodb://%v:%v@%v/?authSource=admin",
config.Config.Mongo.Username, config.Config.Mongo.Password, mongodbHosts)))
if err != nil {
return errs.Wrap(err)
} else {
err = client.Ping(context.TODO(), &readpref.ReadPref{})
if err != nil {
return errs.Wrap(err)
}
}
return nil
}
func checkMinio() error {
if config.Config.Object.Enable == "minio" {
conf := config.Config.Object.Minio
u, _ := url.Parse(conf.Endpoint)
minioClient, err := minio.New(u.Host, &minio.Options{
Creds: credentials.NewStaticV4(conf.AccessKeyID, conf.SecretAccessKey, ""),
Secure: u.Scheme == "https",
})
if err != nil {
return errs.Wrap(err)
}
cancel, err := minioClient.HealthCheck(time.Duration(minioHealthCheckDuration) * time.Second)
defer func() {
if cancel != nil {
cancel()
}
}()
if err != nil {
return errs.Wrap(err)
} else {
if minioClient.IsOffline() {
return ErrComponentStart.Wrap("Minio server is offline")
}
}
if checkMinioIP() != nil {
return checkMinioIP()
}
}
return nil
}
func checkRedis() error {
var redisClient redis.UniversalClient
defer func() {
if redisClient != nil {
redisClient.Close()
}
}()
if len(config.Config.Redis.Address) > 1 {
redisClient = redis.NewClusterClient(&redis.ClusterOptions{
Addrs: config.Config.Redis.Address,
Username: config.Config.Redis.Username,
Password: config.Config.Redis.Password,
})
} else {
redisClient = redis.NewClient(&redis.Options{
Addr: config.Config.Redis.Address[0],
Username: config.Config.Redis.Username,
Password: config.Config.Redis.Password,
})
}
_, err := redisClient.Ping(context.Background()).Result()
if err != nil {
return errs.Wrap(err)
}
return nil
}
func checkZookeeper() error {
var c *zk.Conn
defer func() {
if c != nil {
c.Close()
}
}()
c, _, err := zk.Connect(config.Config.Zookeeper.ZkAddr, time.Second)
if err != nil {
return errs.Wrap(err)
} else {
if config.Config.Zookeeper.Username != "" && config.Config.Zookeeper.Password != "" {
if err := c.AddAuth("digest", []byte(config.Config.Zookeeper.Username+":"+config.Config.Zookeeper.Password)); err != nil {
return errs.Wrap(err)
}
}
_, _, err = c.Get("/")
if err != nil {
return errs.Wrap(err)
}
}
return nil
}
func checkKafka() error {
var kafkaClient sarama.Client
defer func() {
if kafkaClient != nil {
kafkaClient.Close()
}
}()
cfg := sarama.NewConfig()
if config.Config.Kafka.Username != "" && config.Config.Kafka.Password != "" {
cfg.Net.SASL.Enable = true
cfg.Net.SASL.User = config.Config.Kafka.Username
cfg.Net.SASL.Password = config.Config.Kafka.Password
}
kafka.SetupTLSConfig(cfg)
kafkaClient, err := sarama.NewClient(config.Config.Kafka.Addr, cfg)
if err != nil {
return errs.Wrap(err)
} else {
topics, err := kafkaClient.Topics()
if err != nil {
return err
}
if !utils.IsContain(config.Config.Kafka.MsgToMongo.Topic, topics) {
return ErrComponentStart.Wrap(fmt.Sprintf("kafka doesn't contain topic:%v", config.Config.Kafka.MsgToMongo.Topic))
}
if !utils.IsContain(config.Config.Kafka.MsgToPush.Topic, topics) {
return ErrComponentStart.Wrap(fmt.Sprintf("kafka doesn't contain topic:%v", config.Config.Kafka.MsgToPush.Topic))
}
if !utils.IsContain(config.Config.Kafka.LatestMsgToRedis.Topic, topics) {
return ErrComponentStart.Wrap(fmt.Sprintf("kafka doesn't contain topic:%v", config.Config.Kafka.LatestMsgToRedis.Topic))
}
}
return nil
}
func errorPrint(s string) {
fmt.Printf("\x1b[%dm%v\x1b[0m\n", 31, s)
}
func successPrint(s string) {
fmt.Printf("\x1b[%dm%v\x1b[0m\n", 32, s)
}
func warningPrint(s string) {
fmt.Printf("\x1b[%dmWarning: But %v\x1b[0m\n", 33, s)
}

@ -1,42 +0,0 @@
// 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 (
"testing"
"github.com/stretchr/testify/assert"
"github.com/openimsdk/open-im-server/v3/pkg/common/config"
)
func TestCheckMysql(t *testing.T) {
err := mockInitCfg()
assert.NoError(t, err, "Initialization should not produce errors")
err = checkMysql()
if err != nil {
// You might expect an error if MySQL isn't running locally with the mock credentials.
t.Logf("Expected error due to mock configuration: %v", err)
}
}
// Mock for initCfg for testing purpose
func mockInitCfg() error {
config.Config.Mysql.Username = "root"
config.Config.Mysql.Password = "openIM123"
config.Config.Mysql.Address = []string{"127.0.0.1:13306"}
return nil
}

@ -1,3 +0,0 @@
module github.com/openimsdk/open-im-server/v3/tools/component
go 1.18

@ -1,89 +0,0 @@
# [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.

@ -1,49 +0,0 @@
# 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.

@ -1,62 +0,0 @@
// 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":
}
}

@ -1,243 +0,0 @@
// 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,
}
}

@ -1,29 +0,0 @@
// 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/v3/tools/imctl/internal/imctl/cmd"
)
func main() {
command := cmd.NewDefaultIMCtlCommand()
if err := command.Execute(); err != nil {
os.Exit(1)
}
}

@ -1,19 +0,0 @@
module github.com/openimsdk/open-im-server/v3/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.10.0 // indirect
k8s.io/kubernetes v1.28.2
)

@ -1,27 +0,0 @@
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=
golang.org/x/sys v0.10.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=
k8s.io/kubernetes v1.28.2 h1:GhcnYeNTukeaC0dD5BC+UWBvzQsFEpWj7XBVMQptfYc=
k8s.io/kubernetes v1.28.2/go.mod h1:FmB1Mlp9ua0ezuwQCTGs/y6wj/fVisN2sVxhzjj0WDk=

@ -1,146 +0,0 @@
// 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()
}

@ -1,352 +0,0 @@
// 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"
cmdutil "github.com/openimsdk/open-im-server/tools/imctl/internal/imctl/cmd/util"
"github.com/openimsdk/open-im-server/tools/imctl/pkg/cli/genericclioptions"
"github.com/openimsdk/open-im-server/tools/imctl/pkg/util/templates"
)
// 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
}

@ -1,283 +0,0 @@
// 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"
cmdutil "github.com/openimsdk/open-im-server/tools/imctl/internal/imctl/cmd/util"
"github.com/openimsdk/open-im-server/tools/imctl/pkg/util/templates"
)
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
}

@ -1,119 +0,0 @@
// 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"
cmdutil "github.com/openimsdk/open-im-server/tools/imctl/internal/imctl/cmd/util"
"github.com/openimsdk/open-im-server/tools/imctl/pkg/cli/genericclioptions"
"github.com/openimsdk/open-im-server/tools/imctl/pkg/util/templates"
)
// 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
}

@ -1,619 +0,0 @@
// 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
}

@ -1,50 +0,0 @@
// 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
}

@ -1,95 +0,0 @@
// 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)
}
}

@ -1,165 +0,0 @@
// 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
}

@ -1,3 +0,0 @@
module github.com/openimsdk/open-im-server/v3/tools/infra
go 1.18

@ -1,40 +0,0 @@
// 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"
"log"
)
func main() {
log.Println("Current module is still under development.")
message := `
Current module is still under development.
____ _____ __ __
/ __ \ |_ _|| \/ |
| | | | _ __ ___ _ __ | | | \ / |
| | | || '_ \ / _ \| '_ \ | | | |\/| |
| |__| || |_) || __/| | | | _| |_ | | | |
\____/ | .__/ \___||_| |_||_____||_| |_|
| |
|_|
Keep checking for updates!
`
fmt.Println(message)
}

@ -1,39 +0,0 @@
# ncpu
**ncpu** is a simple utility to fetch the number of CPU cores across different operating systems.
## Introduction
In various scenarios, especially while compiling code, it's beneficial to know the number of available CPU cores to optimize the build process. However, the command to fetch the CPU core count differs between operating systems. For example, on Linux, we use `nproc`, while on macOS, it's `sysctl -n hw.ncpu`. The `ncpu` utility provides a unified way to obtain this number, regardless of the platform.
## Usage
To retrieve the number of CPU cores, simply use the `ncpu` command:
```bash
$ ncpu
```
This will return an integer representing the number of available CPU cores.
### Example:
Let's say you're compiling a project using `make`. To utilize all the CPU cores for the compilation process, you can use:
```bash
$ make -j $(ncpu) build # or any other build command
```
The above command will ensure the build process takes advantage of all the available CPU cores, thereby potentially speeding up the compilation.
## Why use `ncpu`?
- **Cross-platform compatibility**: No need to remember or detect which OS-specific command to use. Just use `ncpu`!
- **Ease of use**: A simple and intuitive command that's easy to incorporate into scripts or command-line operations.
- **Consistency**: Ensures consistent behavior and output across different systems and environments.
## Installation
(Include installation steps here, e.g., how to clone the repo, build the tool, or install via package manager.)

@ -1,5 +0,0 @@
module github.com/openimsdk/open-im-server/v3/tools/ncpu
go 1.18
require go.uber.org/automaxprocs v1.5.3

@ -1,7 +0,0 @@
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/prashantv/gostub v1.1.0 h1:BTyx3RfQjRHnUWaGF9oQos79AlQ5k8WNktv7VGvVH4g=
github.com/stretchr/testify v1.7.1 h1:5TQK59W5E3v0r2duFAb7P95B6hEeOyEnHRa8MjYSMTY=
go.uber.org/automaxprocs v1.5.3 h1:kWazyxZUrS3Gs4qUpbwo5kEIMGe/DAvi5Z4tl2NW4j8=
go.uber.org/automaxprocs v1.5.3/go.mod h1:eRbA25aqJrxAbsLO0xy5jVwPt7FQnRgjW+efnwa1WM0=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=

@ -1,27 +0,0 @@
// 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"
"runtime"
"go.uber.org/automaxprocs/maxprocs"
)
func main() {
maxprocs.Set()
fmt.Print(runtime.GOMAXPROCS(0))
}

@ -1,35 +0,0 @@
// 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 "testing"
func Test_main(t *testing.T) {
tests := []struct {
name string
}{
{
name: "Test_main",
},
{
name: "Test_main2",
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
main()
})
}
}

@ -1,47 +0,0 @@
# 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.
# 使用官方Go镜像作为基础镜像
FROM golang:1.21 AS build-env
ENV CGO_ENABLED=0
# 设置工作目录
WORKDIR /app
# 安装curl和unzip工具
#RUN apt-get update && apt-get install -y curl unzip
# 从GitHub下载并解压dist.zip
#RUN curl -LO https://github.com/OpenIMSDK/dist.zip \
# && unzip dist.zip -d ./ \
# && rm dist.zip
# 复制Go代码到容器
COPY . .
# 编译Go代码
RUN go build -o openim-web
# 使用轻量级的基础镜像
FROM debian:buster-slim
# 将编译好的二进制文件和dist资源复制到新的容器
WORKDIR /app
COPY --from=build-env /app/openim-web /app/openim-web
COPY --from=build-env /app/dist /app/dist
# 开放容器的20001端口
EXPOSE 20001
# 指定容器启动命令
ENTRYPOINT ["/app/openim-web"]

@ -1,79 +0,0 @@
# OpenIM Web Service
- [OpenIM Web Service](#openim-web-service)
- [Overview](#overview)
- [User](#user)
- [Docker Deployment](#docker-deployment)
- [Build the Docker Image](#build-the-docker-image)
- [Run the Docker Container](#run-the-docker-container)
- [Configuration](#configuration)
- [Contributions](#contributions)
OpenIM Web Service is a lightweight containerized service built with Go. The service serves static files and allows customization via environment variables.
## Overview
- Built using Go.
- Deployed as a Docker container.
- Serves static files from a directory which can be set via an environment variable.
- The default port for the service is `20001`, but it can be customized using an environment variable.
## User
example
```bash
$ ./openim-web -h
Usage of ./openim-web:
-distPath string
Path to the distribution (default "/app/dist")
-port string
Port to run the server on (default "20001")
```
Variables can be set as above, Environment variables can also be set
example:
```bash
$ export OPENIM_WEB_DIST_PATH="/app/dist"
$ export OPENIM_WEB_PPRT="11001"
```
Initialize the env configuration file:
```bash
$ make init
```
## Docker Deployment
### Build the Docker Image
Even though we've implemented automation, it's to make the developer experience easier:
To build the Docker image for OpenIM Web Service:
```bash
$ docker build -t openim-web .
```
### Run the Docker Container
To run the service:
```bash
$ docker run -e DIST_PATH=/app/dist -e PORT=20001 -p 20001:20001 openim-web
```
## Configuration
You can configure the OpenIM Web Service using the following environment variables:
- **DIST_PATH**: The path to the directory containing the static files. Default: `/app/dist`.
- **PORT**: The port on which the service listens. Default: `11001`.
## Contributions
We welcome contributions from the community. If you find any bugs or have feature suggestions, please create an issue or send a pull request.

@ -1,7 +0,0 @@
module github.com/openimsdk/open-im-server/v3/tools/openim-web
go 1.18
require gopkg.in/yaml.v2 v2.4.0
require github.com/NYTimes/gziphandler v1.1.1 // indirect

@ -1,10 +0,0 @@
github.com/NYTimes/gziphandler v1.1.1 h1:ZUDjpQae29j0ryrS0u/B8HZfJBtBQHjqw2rQ2cqUQ3I=
github.com/NYTimes/gziphandler v1.1.1/go.mod h1:n/CVRwUEOgIxrgPvAQhUUr9oeUtvrhMomdKFjzJNB0c=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=

@ -1,63 +0,0 @@
// 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 (
"flag"
"log"
"net/http"
"os"
"github.com/NYTimes/gziphandler"
)
var (
distPathFlag string
portFlag string
)
func init() {
flag.StringVar(&distPathFlag, "distPath", "/app/dist", "Path to the distribution")
flag.StringVar(&portFlag, "port", "11001", "Port to run the server on")
}
func main() {
flag.Parse()
distPath := getConfigValue("DIST_PATH", distPathFlag, "/app/dist")
fs := http.FileServer(http.Dir(distPath))
withGzip := gziphandler.GzipHandler(fs)
http.Handle("/", withGzip)
port := getConfigValue("PORT", portFlag, "11001")
log.Printf("Server listening on port %s in %s...", port, distPath)
err := http.ListenAndServe(":"+port, nil)
if err != nil {
log.Fatal(err)
}
}
func getConfigValue(envKey, flagValue, fallback string) string {
envVal := os.Getenv(envKey)
if envVal != "" {
return envVal
}
if flagValue != "" {
return flagValue
}
return fallback
}

@ -1,71 +0,0 @@
// 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 (
"os"
"testing"
)
func TestGetConfigValue(t *testing.T) {
tests := []struct {
name string
envKey string
envValue string
flagValue string
fallback string
wantResult string
}{
{
name: "environment variable set",
envKey: "TEST_KEY",
envValue: "envValue",
flagValue: "",
fallback: "default",
wantResult: "envValue",
},
{
name: "flag set and environment variable not set",
envKey: "TEST_KEY",
envValue: "",
flagValue: "flagValue",
fallback: "default",
wantResult: "flagValue",
},
{
name: "nothing set, use fallback",
envKey: "TEST_KEY",
envValue: "",
flagValue: "",
fallback: "default",
wantResult: "default",
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
if tt.envValue != "" {
os.Setenv(tt.envKey, tt.envValue)
defer os.Unsetenv(tt.envKey)
}
got := getConfigValue(tt.envKey, tt.flagValue, tt.fallback)
if got != tt.wantResult {
t.Errorf("getConfigValue(%s, %s, %s) = %s; want %s", tt.envKey, tt.flagValue, tt.fallback, got, tt.wantResult)
}
})
}
}

@ -1,20 +0,0 @@
module github.com/openimsdk/open-im-server/v3/tools/url2im
go 1.20
require (
github.com/OpenIMSDK/protocol v0.0.21
github.com/kelindar/bitmap v1.5.1
)
require (
github.com/golang/protobuf v1.5.3 // indirect
github.com/kelindar/simd v1.1.2 // indirect
github.com/klauspost/cpuid/v2 v2.2.4 // indirect
golang.org/x/net v0.9.0 // indirect
golang.org/x/sys v0.7.0 // indirect
golang.org/x/text v0.9.0 // indirect
google.golang.org/genproto v0.0.0-20230410155749-daa745c078e1 // indirect
google.golang.org/grpc v1.56.2 // indirect
google.golang.org/protobuf v1.31.0 // indirect
)

@ -1,33 +0,0 @@
github.com/OpenIMSDK/protocol v0.0.21 h1:5H6H+hJ9d/VgRqttvxD/zfK9Asd+4M8Eknk5swSbUVY=
github.com/OpenIMSDK/protocol v0.0.21/go.mod h1:F25dFrwrIx3lkNoiuf6FkCfxuwf8L4Z8UIsdTHP/r0Y=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=
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/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/kelindar/bitmap v1.5.1 h1:+ZmZdwHbJ+CGE+q/aAJ74KJSnp0vOlGD7KY5x51mVzk=
github.com/kelindar/bitmap v1.5.1/go.mod h1:j3qZjxH9s4OtvsnFTP2bmPkjqil9Y2xQlxPYHexasEA=
github.com/kelindar/simd v1.1.2 h1:KduKb+M9cMY2HIH8S/cdJyD+5n5EGgq+Aeeleos55To=
github.com/kelindar/simd v1.1.2/go.mod h1:inq4DFudC7W8L5fhxoeZflLRNpWSs0GNx6MlWFvuvr0=
github.com/klauspost/cpuid/v2 v2.2.4 h1:acbojRNwl3o09bUq+yDCtZFc1aiwaAAxtcn8YkZXnvk=
github.com/klauspost/cpuid/v2 v2.2.4/go.mod h1:RVVoqg1df56z8g3pUjL/3lE5UfnlrJX8tyFgg4nqhuY=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/stretchr/testify v1.8.2 h1:+h33VjcLVPDHtOdpUCuF+7gSuG3yGIftsP1YvFihtJ8=
golang.org/x/net v0.9.0 h1:aWJ/m6xSmxWBx+V0XRHTlrYrPG56jKsLdTFmsSsCzOM=
golang.org/x/net v0.9.0/go.mod h1:d48xBJpPfHeWQsugry2m+kC02ZBRGRgulfHnEXEuWns=
golang.org/x/sys v0.0.0-20220704084225-05e143d24a9e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.7.0 h1:3jlCCIQZPdOYu1h8BkNvLz8Kgwtae2cagcG/VamtZRU=
golang.org/x/sys v0.7.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/text v0.9.0 h1:2sjJmO8cDvYveuX97RDLsxlyUxLl+GHoLxBiRdHllBE=
golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
google.golang.org/genproto v0.0.0-20230410155749-daa745c078e1 h1:KpwkzHKEF7B9Zxg18WzOa7djJ+Ha5DzthMyZYQfEn2A=
google.golang.org/genproto v0.0.0-20230410155749-daa745c078e1/go.mod h1:nKE/iIaLqn2bQwXBg8f1g2Ylh6r5MN5CmZvuzZCgsCU=
google.golang.org/grpc v1.56.2 h1:fVRFRnXvU+x6C4IlHZewvJOVHoOv1TUuQyoRsYnB4bI=
google.golang.org/grpc v1.56.2/go.mod h1:I9bI3vqKfayGqPUAwGdOSu7kt6oIJLixfffKrpXqQ9s=
google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
google.golang.org/protobuf v1.31.0 h1:g0LDEJHgrBl9N9r17Ru3sqWhkIx2NB67okBHPwC7hs8=
google.golang.org/protobuf v1.31.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=

@ -1,84 +0,0 @@
package main
import (
"flag"
"log"
"os"
"path/filepath"
"time"
"github.com/openimsdk/open-im-server/v3/tools/url2im/pkg"
)
/*take.txt
{"url":"http://xxx/xxxx","name":"xxxx","contentType":"image/jpeg"}
{"url":"http://xxx/xxxx","name":"xxxx","contentType":"image/jpeg"}
{"url":"http://xxx/xxxx","name":"xxxx","contentType":"image/jpeg"}
*/
func main() {
var conf pkg.Config // 后面带*的为必填项
flag.StringVar(&conf.TaskPath, "task", "take.txt", "task path") // 任务日志文件*
flag.StringVar(&conf.ProgressPath, "progress", "", "progress path") // 进度日志文件
flag.IntVar(&conf.Concurrency, "concurrency", 1, "concurrency num") // 并发数
flag.IntVar(&conf.Retry, "retry", 1, "retry num") // 重试次数
flag.StringVar(&conf.TempDir, "temp", "", "temp dir") // 临时文件夹
flag.Int64Var(&conf.CacheSize, "cache", 1024*1024*100, "cache size") // 缓存大小(超过时,下载到磁盘)
flag.Int64Var((*int64)(&conf.Timeout), "timeout", 5000, "timeout") // 请求超时时间(毫秒)
flag.StringVar(&conf.Api, "api", "http://127.0.0.1:10002", "api") // im地址*
flag.StringVar(&conf.UserID, "userID", "openIM123456", "userID") // im管理员
flag.StringVar(&conf.Secret, "secret", "openIM123", "secret") // im config secret
flag.Parse()
if !filepath.IsAbs(conf.TaskPath) {
var err error
conf.TaskPath, err = filepath.Abs(conf.TaskPath)
if err != nil {
log.Println("get abs path err:", err)
return
}
}
if conf.ProgressPath == "" {
conf.ProgressPath = conf.TaskPath + ".progress.txt"
} else if !filepath.IsAbs(conf.ProgressPath) {
var err error
conf.ProgressPath, err = filepath.Abs(conf.ProgressPath)
if err != nil {
log.Println("get abs path err:", err)
return
}
}
if conf.TempDir == "" {
conf.TempDir = conf.TaskPath + ".temp"
}
if info, err := os.Stat(conf.TempDir); err == nil {
if !info.IsDir() {
log.Printf("temp dir %s is not dir\n", err)
return
}
} else if os.IsNotExist(err) {
if err := os.MkdirAll(conf.TempDir, os.ModePerm); err != nil {
log.Printf("mkdir temp dir %s err %+v\n", conf.TempDir, err)
return
}
defer os.RemoveAll(conf.TempDir)
} else {
log.Println("get temp dir err:", err)
return
}
if conf.Concurrency <= 0 {
conf.Concurrency = 1
}
if conf.Retry <= 0 {
conf.Retry = 1
}
if conf.CacheSize <= 0 {
conf.CacheSize = 1024 * 1024 * 100 // 100M
}
if conf.Timeout <= 0 {
conf.Timeout = 5000
}
conf.Timeout = conf.Timeout * time.Millisecond
if err := pkg.Run(conf); err != nil {
log.Println("main err:", err)
}
}

@ -1,112 +0,0 @@
package pkg
import (
"bytes"
"context"
"encoding/json"
"errors"
"fmt"
"io"
"net/http"
"github.com/OpenIMSDK/protocol/auth"
"github.com/OpenIMSDK/protocol/constant"
"github.com/OpenIMSDK/protocol/third"
)
type Api struct {
Api string
UserID string
Secret string
Token string
Client *http.Client
}
func (a *Api) apiPost(ctx context.Context, path string, req any, resp any) error {
operationID, _ := ctx.Value("operationID").(string)
if operationID == "" {
return errors.New("call api operationID is empty")
}
reqBody, err := json.Marshal(req)
if err != nil {
return err
}
request, err := http.NewRequestWithContext(ctx, http.MethodPost, a.Api+path, bytes.NewReader(reqBody))
if err != nil {
return err
}
DefaultRequestHeader(request.Header)
request.ContentLength = int64(len(reqBody))
request.Header.Set("Content-Type", "application/json")
request.Header.Set("operationID", operationID)
if a.Token != "" {
request.Header.Set("token", a.Token)
}
response, err := a.Client.Do(request)
if err != nil {
return err
}
defer response.Body.Close()
body, err := io.ReadAll(response.Body)
if err != nil {
return err
}
if response.StatusCode != http.StatusOK {
return fmt.Errorf("api %s status %s body %s", path, response.Status, body)
}
var baseResponse struct {
ErrCode int `json:"errCode"`
ErrMsg string `json:"errMsg"`
ErrDlt string `json:"errDlt"`
Data json.RawMessage `json:"data"`
}
if err := json.Unmarshal(body, &baseResponse); err != nil {
return err
}
if baseResponse.ErrCode != 0 {
return fmt.Errorf("api %s errCode %d errMsg %s errDlt %s", path, baseResponse.ErrCode, baseResponse.ErrMsg, baseResponse.ErrDlt)
}
if resp != nil {
if err := json.Unmarshal(baseResponse.Data, resp); err != nil {
return err
}
}
return nil
}
func (a *Api) GetToken(ctx context.Context) (string, error) {
req := auth.UserTokenReq{
UserID: a.UserID,
Secret: a.Secret,
PlatformID: constant.AdminPlatformID,
}
var resp auth.UserTokenResp
if err := a.apiPost(ctx, "/auth/user_token", &req, &resp); err != nil {
return "", err
}
return resp.Token, nil
}
func (a *Api) GetPartLimit(ctx context.Context) (*third.PartLimitResp, error) {
var resp third.PartLimitResp
if err := a.apiPost(ctx, "/object/part_limit", &third.PartLimitReq{}, &resp); err != nil {
return nil, err
}
return &resp, nil
}
func (a *Api) InitiateMultipartUpload(ctx context.Context, req *third.InitiateMultipartUploadReq) (*third.InitiateMultipartUploadResp, error) {
var resp third.InitiateMultipartUploadResp
if err := a.apiPost(ctx, "/object/initiate_multipart_upload", req, &resp); err != nil {
return nil, err
}
return &resp, nil
}
func (a *Api) CompleteMultipartUpload(ctx context.Context, req *third.CompleteMultipartUploadReq) (string, error) {
var resp third.CompleteMultipartUploadResp
if err := a.apiPost(ctx, "/object/complete_multipart_upload", req, &resp); err != nil {
return "", err
}
return resp.Url, nil
}

@ -1,96 +0,0 @@
package pkg
import (
"bytes"
"io"
"os"
)
type ReadSeekSizeCloser interface {
io.ReadSeekCloser
Size() int64
}
func NewReader(r io.Reader, max int64, path string) (ReadSeekSizeCloser, error) {
buf := make([]byte, max+1)
n, err := io.ReadFull(r, buf)
if err == nil {
f, err := os.OpenFile(path, os.O_CREATE|os.O_RDWR, 0666)
if err != nil {
return nil, err
}
var ok bool
defer func() {
if !ok {
_ = f.Close()
_ = os.Remove(path)
}
}()
if _, err := f.Write(buf[:n]); err != nil {
return nil, err
}
cn, err := io.Copy(f, r)
if err != nil {
return nil, err
}
if _, err := f.Seek(0, io.SeekStart); err != nil {
return nil, err
}
ok = true
return &fileBuffer{
f: f,
n: cn + int64(n),
}, nil
} else if err == io.EOF || err == io.ErrUnexpectedEOF {
return &memoryBuffer{
r: bytes.NewReader(buf[:n]),
}, nil
} else {
return nil, err
}
}
type fileBuffer struct {
n int64
f *os.File
}
func (r *fileBuffer) Read(p []byte) (n int, err error) {
return r.f.Read(p)
}
func (r *fileBuffer) Seek(offset int64, whence int) (int64, error) {
return r.f.Seek(offset, whence)
}
func (r *fileBuffer) Size() int64 {
return r.n
}
func (r *fileBuffer) Close() error {
name := r.f.Name()
if err := r.f.Close(); err != nil {
return err
}
return os.Remove(name)
}
type memoryBuffer struct {
r *bytes.Reader
}
func (r *memoryBuffer) Read(p []byte) (n int, err error) {
return r.r.Read(p)
}
func (r *memoryBuffer) Seek(offset int64, whence int) (int64, error) {
return r.r.Seek(offset, whence)
}
func (r *memoryBuffer) Close() error {
return nil
}
func (r *memoryBuffer) Size() int64 {
return r.r.Size()
}

@ -1,16 +0,0 @@
package pkg
import "time"
type Config struct {
TaskPath string
ProgressPath string
Concurrency int
Retry int
Timeout time.Duration
Api string
UserID string
Secret string
TempDir string
CacheSize int64
}

@ -1,7 +0,0 @@
package pkg
import "net/http"
func DefaultRequestHeader(header http.Header) {
header.Set("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/116.0.0.0 Safari/537.36")
}

@ -1,385 +0,0 @@
package pkg
import (
"bufio"
"context"
"crypto/md5"
"encoding/hex"
"encoding/json"
"errors"
"fmt"
"io"
"log"
"net/http"
"net/url"
"os"
"path/filepath"
"strconv"
"strings"
"sync"
"sync/atomic"
"time"
"github.com/OpenIMSDK/protocol/third"
)
type Upload struct {
URL string `json:"url"`
Name string `json:"name"`
ContentType string `json:"contentType"`
}
type Task struct {
Index int
Upload Upload
}
type PartInfo struct {
ContentType string
PartSize int64
PartNum int
FileMd5 string
PartMd5 string
PartSizes []int64
PartMd5s []string
}
func Run(conf Config) error {
m := &Manage{
prefix: time.Now().Format("20060102150405"),
conf: &conf,
ctx: context.Background(),
}
return m.Run()
}
type Manage struct {
conf *Config
ctx context.Context
api *Api
partLimit *third.PartLimitResp
prefix string
tasks chan Task
id uint64
success int64
failed int64
}
func (m *Manage) tempFilePath() string {
return filepath.Join(m.conf.TempDir, fmt.Sprintf("%s_%d", m.prefix, atomic.AddUint64(&m.id, 1)))
}
func (m *Manage) Run() error {
defer func(start time.Time) {
log.Printf("run time %s\n", time.Since(start))
}(time.Now())
m.api = &Api{
Api: m.conf.Api,
UserID: m.conf.UserID,
Secret: m.conf.Secret,
Client: &http.Client{Timeout: m.conf.Timeout},
}
var err error
ctx := context.WithValue(m.ctx, "operationID", fmt.Sprintf("%s_init", m.prefix))
m.api.Token, err = m.api.GetToken(ctx)
if err != nil {
return err
}
m.partLimit, err = m.api.GetPartLimit(ctx)
if err != nil {
return err
}
progress, err := ReadProgress(m.conf.ProgressPath)
if err != nil {
return err
}
progressFile, err := os.OpenFile(m.conf.ProgressPath, os.O_CREATE|os.O_RDWR|os.O_APPEND, 0666)
if err != nil {
return err
}
var mutex sync.Mutex
writeSuccessIndex := func(index int) {
mutex.Lock()
defer mutex.Unlock()
if _, err := progressFile.Write([]byte(strconv.Itoa(index) + "\n")); err != nil {
log.Printf("write progress err: %v\n", err)
}
}
file, err := os.Open(m.conf.TaskPath)
if err != nil {
return err
}
m.tasks = make(chan Task, m.conf.Concurrency*2)
go func() {
defer file.Close()
defer close(m.tasks)
scanner := bufio.NewScanner(file)
var (
index int
num int
)
for scanner.Scan() {
line := strings.TrimSpace(scanner.Text())
if line == "" {
continue
}
index++
if progress.IsUploaded(index) {
log.Printf("index: %d already uploaded %s\n", index, line)
continue
}
var upload Upload
if err := json.Unmarshal([]byte(line), &upload); err != nil {
log.Printf("index: %d json.Unmarshal(%s) err: %v", index, line, err)
continue
}
num++
m.tasks <- Task{
Index: index,
Upload: upload,
}
}
if num == 0 {
log.Println("mark all completed")
}
}()
var wg sync.WaitGroup
wg.Add(m.conf.Concurrency)
for i := 0; i < m.conf.Concurrency; i++ {
go func(tid int) {
defer wg.Done()
for task := range m.tasks {
var success bool
for n := 0; n < m.conf.Retry; n++ {
ctx := context.WithValue(m.ctx, "operationID", fmt.Sprintf("%s_%d_%d_%d", m.prefix, tid, task.Index, n+1))
if urlRaw, err := m.RunTask(ctx, task); err == nil {
writeSuccessIndex(task.Index)
log.Println("index:", task.Index, "upload success", "urlRaw", urlRaw)
success = true
break
} else {
log.Printf("index: %d upload: %+v err: %v", task.Index, task.Upload, err)
}
}
if success {
atomic.AddInt64(&m.success, 1)
} else {
atomic.AddInt64(&m.failed, 1)
log.Printf("index: %d upload: %+v failed", task.Index, task.Upload)
}
}
}(i + 1)
}
wg.Wait()
log.Printf("execution completed success %d failed %d\n", m.success, m.failed)
return nil
}
func (m *Manage) RunTask(ctx context.Context, task Task) (string, error) {
resp, err := m.HttpGet(ctx, task.Upload.URL)
if err != nil {
return "", err
}
defer resp.Body.Close()
reader, err := NewReader(resp.Body, m.conf.CacheSize, m.tempFilePath())
if err != nil {
return "", err
}
defer reader.Close()
part, err := m.getPartInfo(ctx, reader, reader.Size())
if err != nil {
return "", err
}
var contentType string
if task.Upload.ContentType == "" {
contentType = part.ContentType
} else {
contentType = task.Upload.ContentType
}
initiateMultipartUploadResp, err := m.api.InitiateMultipartUpload(ctx, &third.InitiateMultipartUploadReq{
Hash: part.PartMd5,
Size: reader.Size(),
PartSize: part.PartSize,
MaxParts: -1,
Cause: "batch-import",
Name: task.Upload.Name,
ContentType: contentType,
})
if err != nil {
return "", err
}
if initiateMultipartUploadResp.Upload == nil {
return initiateMultipartUploadResp.Url, nil
}
if _, err := reader.Seek(0, io.SeekStart); err != nil {
return "", err
}
uploadParts := make([]*third.SignPart, part.PartNum)
for _, part := range initiateMultipartUploadResp.Upload.Sign.Parts {
uploadParts[part.PartNumber-1] = part
}
for i, currentPartSize := range part.PartSizes {
md5Reader := NewMd5Reader(io.LimitReader(reader, currentPartSize))
if m.doPut(ctx, m.api.Client, initiateMultipartUploadResp.Upload.Sign, uploadParts[i], md5Reader, currentPartSize); err != nil {
return "", err
}
if md5val := md5Reader.Md5(); md5val != part.PartMd5s[i] {
return "", fmt.Errorf("upload part %d failed, md5 not match, expect %s, got %s", i, part.PartMd5s[i], md5val)
}
}
urlRaw, err := m.api.CompleteMultipartUpload(ctx, &third.CompleteMultipartUploadReq{
UploadID: initiateMultipartUploadResp.Upload.UploadID,
Parts: part.PartMd5s,
Name: task.Upload.Name,
ContentType: contentType,
Cause: "batch-import",
})
if err != nil {
return "", err
}
return urlRaw, nil
}
func (m *Manage) partSize(size int64) (int64, error) {
if size <= 0 {
return 0, errors.New("size must be greater than 0")
}
if size > m.partLimit.MaxPartSize*int64(m.partLimit.MaxNumSize) {
return 0, fmt.Errorf("size must be less than %db", m.partLimit.MaxPartSize*int64(m.partLimit.MaxNumSize))
}
if size <= m.partLimit.MinPartSize*int64(m.partLimit.MaxNumSize) {
return m.partLimit.MinPartSize, nil
}
partSize := size / int64(m.partLimit.MaxNumSize)
if size%int64(m.partLimit.MaxNumSize) != 0 {
partSize++
}
return partSize, nil
}
func (m *Manage) partMD5(parts []string) string {
s := strings.Join(parts, ",")
md5Sum := md5.Sum([]byte(s))
return hex.EncodeToString(md5Sum[:])
}
func (m *Manage) getPartInfo(ctx context.Context, r io.Reader, fileSize int64) (*PartInfo, error) {
partSize, err := m.partSize(fileSize)
if err != nil {
return nil, err
}
partNum := int(fileSize / partSize)
if fileSize%partSize != 0 {
partNum++
}
partSizes := make([]int64, partNum)
for i := 0; i < partNum; i++ {
partSizes[i] = partSize
}
partSizes[partNum-1] = fileSize - partSize*(int64(partNum)-1)
partMd5s := make([]string, partNum)
buf := make([]byte, 1024*8)
fileMd5 := md5.New()
var contentType string
for i := 0; i < partNum; i++ {
h := md5.New()
r := io.LimitReader(r, partSize)
for {
if n, err := r.Read(buf); err == nil {
if contentType == "" {
contentType = http.DetectContentType(buf[:n])
}
h.Write(buf[:n])
fileMd5.Write(buf[:n])
} else if err == io.EOF {
break
} else {
return nil, err
}
}
partMd5s[i] = hex.EncodeToString(h.Sum(nil))
}
partMd5Val := m.partMD5(partMd5s)
fileMd5val := hex.EncodeToString(fileMd5.Sum(nil))
return &PartInfo{
ContentType: contentType,
PartSize: partSize,
PartNum: partNum,
FileMd5: fileMd5val,
PartMd5: partMd5Val,
PartSizes: partSizes,
PartMd5s: partMd5s,
}, nil
}
func (m *Manage) doPut(ctx context.Context, client *http.Client, sign *third.AuthSignParts, part *third.SignPart, reader io.Reader, size int64) error {
rawURL := part.Url
if rawURL == "" {
rawURL = sign.Url
}
if len(sign.Query)+len(part.Query) > 0 {
u, err := url.Parse(rawURL)
if err != nil {
return err
}
query := u.Query()
for i := range sign.Query {
v := sign.Query[i]
query[v.Key] = v.Values
}
for i := range part.Query {
v := part.Query[i]
query[v.Key] = v.Values
}
u.RawQuery = query.Encode()
rawURL = u.String()
}
req, err := http.NewRequestWithContext(ctx, http.MethodPut, rawURL, reader)
if err != nil {
return err
}
for i := range sign.Header {
v := sign.Header[i]
req.Header[v.Key] = v.Values
}
for i := range part.Header {
v := part.Header[i]
req.Header[v.Key] = v.Values
}
req.ContentLength = size
resp, err := client.Do(req)
if err != nil {
return err
}
defer func() {
_ = resp.Body.Close()
}()
body, err := io.ReadAll(resp.Body)
if err != nil {
return err
}
if resp.StatusCode/200 != 1 {
return fmt.Errorf("PUT %s part %d failed, status code %d, body %s", rawURL, part.PartNumber, resp.StatusCode, string(body))
}
return nil
}
func (m *Manage) HttpGet(ctx context.Context, url string) (*http.Response, error) {
reqUrl := url
for {
request, err := http.NewRequestWithContext(ctx, http.MethodGet, reqUrl, nil)
if err != nil {
return nil, err
}
DefaultRequestHeader(request.Header)
response, err := m.api.Client.Do(request)
if err != nil {
return nil, err
}
if response.StatusCode != http.StatusOK {
_ = response.Body.Close()
return nil, fmt.Errorf("http get %s status %s", url, response.Status)
}
return response, nil
}
}

@ -1,29 +0,0 @@
package pkg
import (
"crypto/md5"
"encoding/hex"
"hash"
"io"
)
func NewMd5Reader(r io.Reader) *Md5Reader {
return &Md5Reader{h: md5.New(), r: r}
}
type Md5Reader struct {
h hash.Hash
r io.Reader
}
func (r *Md5Reader) Read(p []byte) (n int, err error) {
n, err = r.r.Read(p)
if err == nil && n > 0 {
r.h.Write(p[:n])
}
return
}
func (r *Md5Reader) Md5() string {
return hex.EncodeToString(r.h.Sum(nil))
}

@ -1,41 +0,0 @@
package pkg
import (
"bufio"
"os"
"strconv"
"github.com/kelindar/bitmap"
)
func ReadProgress(path string) (*Progress, error) {
file, err := os.Open(path)
if err != nil {
if os.IsNotExist(err) {
return &Progress{}, nil
}
return nil, err
}
defer file.Close()
scanner := bufio.NewScanner(file)
var upload bitmap.Bitmap
for scanner.Scan() {
index, err := strconv.Atoi(scanner.Text())
if err != nil || index < 0 {
continue
}
upload.Set(uint32(index))
}
return &Progress{upload: upload}, nil
}
type Progress struct {
upload bitmap.Bitmap
}
func (p *Progress) IsUploaded(index int) bool {
if p == nil {
return false
}
return p.upload.Contains(uint32(index))
}

@ -1,3 +0,0 @@
module github.com/openimsdk/open-im-server/v3/tools/versionchecker
go 1.18

@ -1,115 +0,0 @@
// 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 (
"bytes"
"fmt"
"os/exec"
"runtime"
"time"
)
func executeCommand(cmdName string, args ...string) (string, error) {
cmd := exec.Command(cmdName, args...)
var out bytes.Buffer
var stderr bytes.Buffer
cmd.Stdout = &out
cmd.Stderr = &stderr
err := cmd.Run()
if err != nil {
return "", fmt.Errorf("Error executing %s: %v", cmdName, err)
}
return out.String(), nil
}
func printTime() string {
currentTime := time.Now()
// 使用 Format 函数以优雅的方式格式化日期和时间
// 2006-01-02 15:04:05 是 Go 中的标准时间格式
formattedTime := currentTime.Format("2006-01-02 15:04:05")
return fmt.Sprintf("Current Date & Time:", formattedTime)
}
func getGoVersion() string {
version := runtime.Version()
goos := runtime.GOOS
goarch := runtime.GOARCH
return fmt.Sprintf("Go Version: %s\nOS: %s\nArchitecture: %s", version, goos, goarch)
}
func getDockerVersion() string {
version, err := executeCommand("docker", "--version")
if err != nil {
return "Docker is not installed. Please install it to get the version."
}
return version
}
func getDockerComposeVersion() string {
version, err := executeCommand("docker-compose", "--version")
if err != nil {
return "Docker Compose is not installed. Please install it to get the version."
}
return version
}
func getKubernetesVersion() string {
version, err := executeCommand("kubectl", "version", "--client", "--short")
if err != nil {
return "Kubernetes is not installed. Please install it to get the version."
}
return version
}
func getGitVersion() string {
version, err := executeCommand("git", "branch", "--show-current")
if err != nil {
return "Git is not installed. Please install it to get the version."
}
return version
}
// NOTE: You'll need to provide appropriate commands for OpenIM versions.
func getOpenIMServerVersion() string {
// Placeholder
return "OpenIM Server: v3.2"
}
func getOpenIMClientVersion() string {
// Placeholder
return "OpenIM Client: v3.2"
}
func main() {
fmt.Println(printTime())
fmt.Println("# Diagnostic Tool Result\n")
fmt.Println("## Go Version")
fmt.Println(getGoVersion())
fmt.Println("## Branch Type")
fmt.Println(getGitVersion())
fmt.Println("## Docker Version")
fmt.Println(getDockerVersion())
fmt.Println("## Docker Compose Version")
fmt.Println(getDockerComposeVersion())
fmt.Println("## Kubernetes Version")
fmt.Println(getKubernetesVersion())
fmt.Println("## OpenIM Versions")
fmt.Println(getOpenIMServerVersion())
fmt.Println(getOpenIMClientVersion())
}

@ -1,10 +0,0 @@
# See the OWNERS docs at https://go.k8s.io/owners
reviewers:
- cubxxw
- kubbot
approvers:
- cubxxw
labels:
- sig/testing
- sig/contributor-experience

@ -1,8 +0,0 @@
module github.com/openimsdk/open-im-server/v3/tools/yamlfmt
go 1.18
require (
github.com/likexian/gokit v0.25.13
gopkg.in/yaml.v3 v3.0.1
)

@ -1,6 +0,0 @@
github.com/likexian/gokit v0.25.13 h1:p2Uw3+6fGG53CwdU2Dz0T6bOycdb2+bAFAa3ymwWVkM=
github.com/likexian/gokit v0.25.13/go.mod h1:qQhEWFBEfqLCO3/vOEo2EDKd+EycekVtUK4tex+l2H4=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=

@ -1,72 +0,0 @@
// 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.
// OPENIM plan on prow tools
package main
import (
"flag"
"fmt"
"io"
"os"
"gopkg.in/yaml.v3"
)
func main() {
// Prow OWNERs file defines the default indent as 2 spaces.
indent := flag.Int("indent", 2, "default indent")
flag.Parse()
for _, path := range flag.Args() {
sourceYaml, err := os.ReadFile(path)
if err != nil {
fmt.Fprintf(os.Stderr, "%s: %v\n", path, err)
continue
}
rootNode, err := fetchYaml(sourceYaml)
if err != nil {
fmt.Fprintf(os.Stderr, "%s: %v\n", path, err)
continue
}
writer, err := os.OpenFile(path, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0666)
if err != nil {
fmt.Fprintf(os.Stderr, "%s: %v\n", path, err)
continue
}
err = streamYaml(writer, indent, rootNode)
if err != nil {
fmt.Fprintf(os.Stderr, "%s: %v\n", path, err)
continue
}
}
}
func fetchYaml(sourceYaml []byte) (*yaml.Node, error) {
rootNode := yaml.Node{}
err := yaml.Unmarshal(sourceYaml, &rootNode)
if err != nil {
return nil, err
}
return &rootNode, nil
}
func streamYaml(writer io.Writer, indent *int, in *yaml.Node) error {
encoder := yaml.NewEncoder(writer)
encoder.SetIndent(*indent)
err := encoder.Encode(in)
if err != nil {
return err
}
return encoder.Close()
}

@ -1,158 +0,0 @@
// 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 (
"bufio"
"bytes"
"reflect"
"testing"
"github.com/likexian/gokit/assert"
"gopkg.in/yaml.v3"
)
func Test_main(t *testing.T) {
sourceYaml := ` # See the OWNERS docs at https://go.k8s.io/owners
approvers:
- dep-approvers
- thockin # Network
- liggitt
labels:
- sig/architecture
`
outputYaml := `# See the OWNERS docs at https://go.k8s.io/owners
approvers:
- dep-approvers
- thockin # Network
- liggitt
labels:
- sig/architecture
`
node, _ := fetchYaml([]byte(sourceYaml))
var output bytes.Buffer
indent := 2
writer := bufio.NewWriter(&output)
_ = streamYaml(writer, &indent, node)
_ = writer.Flush()
assert.Equal(t, outputYaml, string(output.Bytes()), "yaml was not formatted correctly")
}
func Test_fetchYaml(t *testing.T) {
type args struct {
sourceYaml []byte
}
tests := []struct {
name string
args args
want *yaml.Node
wantErr bool
}{
{
name: "Valid YAML",
args: args{sourceYaml: []byte("key: value")},
want: &yaml.Node{
Kind: yaml.MappingNode,
Tag: "!!map",
Value: "",
Content: []*yaml.Node{
{
Kind: yaml.ScalarNode,
Tag: "!!str",
Value: "key",
},
{
Kind: yaml.ScalarNode,
Tag: "!!str",
Value: "value",
},
},
},
wantErr: false,
},
{
name: "Invalid YAML",
args: args{sourceYaml: []byte("key:")},
want: nil,
wantErr: true,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got, err := fetchYaml(tt.args.sourceYaml)
if (err != nil) != tt.wantErr {
t.Errorf("fetchYaml() error = %v, wantErr %v", err, tt.wantErr)
return
}
if !reflect.DeepEqual(got, tt.want) {
t.Errorf("fetchYaml() = %v, want %v", got, tt.want)
}
})
}
}
func Test_streamYaml(t *testing.T) {
type args struct {
indent *int
in *yaml.Node
}
defaultIndent := 2
tests := []struct {
name string
args args
wantWriter string
wantErr bool
}{
{
name: "Valid YAML node with default indent",
args: args{
indent: &defaultIndent,
in: &yaml.Node{
Kind: yaml.MappingNode,
Tag: "!!map",
Value: "",
Content: []*yaml.Node{
{
Kind: yaml.ScalarNode,
Tag: "!!str",
Value: "key",
},
{
Kind: yaml.ScalarNode,
Tag: "!!str",
Value: "value",
},
},
},
},
wantWriter: "key: value\n",
wantErr: false,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
writer := &bytes.Buffer{}
if err := streamYaml(writer, tt.args.indent, tt.args.in); (err != nil) != tt.wantErr {
t.Errorf("streamYaml() error = %v, wantErr %v", err, tt.wantErr)
return
}
if gotWriter := writer.String(); gotWriter != tt.wantWriter {
t.Errorf("streamYaml() = %v, want %v", gotWriter, tt.wantWriter)
}
})
}
}
Loading…
Cancel
Save