Large refactoring projects: OpenIM automation, scripting, and openimctl refactoring (#825)

* fix: fix bin tools path

Signed-off-by: Xinwei Xiong(cubxxw-openim) <3293172751nss@gmail.com>

* fix: fix golang release file path

Signed-off-by: Xinwei Xiong(cubxxw-openim) <3293172751nss@gmail.com>

* fix: fix golang release file path

Signed-off-by: Xinwei Xiong(cubxxw-openim) <3293172751nss@gmail.com>

* fix: fix scripts and optimize

Signed-off-by: Xinwei Xiong(cubxxw-openim) <3293172751nss@gmail.com>

* fix: fix scripts path module

Signed-off-by: Xinwei Xiong(cubxxw-openim) <3293172751nss@gmail.com>

* feat: sync script code

Signed-off-by: Xinwei Xiong(cubxxw-openim) <3293172751nss@gmail.com>

* feat: add lib and start scripts

Signed-off-by: Xinwei Xiong(cubxxw-openim) <3293172751nss@gmail.com>

* ci: add copyright scripts

Signed-off-by: Xinwei Xiong(cubxxw-openim) <3293172751nss@gmail.com>

* ci: add go-docs file and  copyright scripts

Signed-off-by: Xinwei Xiong(cubxxw-openim) <3293172751nss@gmail.com>

* feat: add scripts cross ower

Signed-off-by: Xinwei Xiong(cubxxw-openim) <3293172751nss@gmail.com>

* style: Formatting code make lint path

Signed-off-by: Xinwei Xiong(cubxxw-openim) <3293172751nss@gmail.com>

* style: Formatting code make lint path

Signed-off-by: Xinwei Xiong(cubxxw-openim) <3293172751nss@gmail.com>

* style: Formatting code make lint path

Signed-off-by: Xinwei Xiong(cubxxw-openim) <3293172751nss@gmail.com>

* style: Formatting code make lint path

Signed-off-by: Xinwei Xiong(cubxxw-openim) <3293172751nss@gmail.com>

* fix: chat scripts path bug

Signed-off-by: Xinwei Xiong(cubxxw-openim) <3293172751nss@gmail.com>

* fix: channge smail images

Signed-off-by: Xinwei Xiong(cubxxw-openim) <3293172751nss@gmail.com>

* feat: add makefile feature

Signed-off-by: Xinwei Xiong(cubxxw-openim) <3293172751nss@gmail.com>

* feat: add config and images log

Signed-off-by: Xinwei Xiong(cubxxw-openim) <3293172751nss@gmail.com>

* style: Migrate directory to remove docker to images

Signed-off-by: Xinwei Xiong(cubxxw-openim) <3293172751nss@gmail.com>

* style: formatting style Code

Signed-off-by: Xinwei Xiong(cubxxw-openim) <3293172751nss@gmail.com>

* feat: set opneim's bash logs

Signed-off-by: Xinwei Xiong(cubxxw-openim) <3293172751nss@gmail.com>

* feat: option scripts and docs

Signed-off-by: Xinwei Xiong(cubxxw-openim) <3293172751nss@gmail.com>

* feat: add git cherry

Signed-off-by: Xinwei Xiong(cubxxw-openim) <3293172751nss@gmail.com>

* feat: add git cherry

Signed-off-by: Xinwei Xiong(cubxxw-openim) <3293172751nss@gmail.com>

* feat: save all bash and docs labels

Signed-off-by: Xinwei Xiong(cubxxw-openim) <3293172751nss@gmail.com>

* feat: scripts feature extend

Signed-off-by: Xinwei Xiong(cubxxw-openim) <3293172751nss@gmail.com>

* feat: add config path config

Signed-off-by: Xinwei Xiong(cubxxw-openim) <3293172751nss@gmail.com>

* feat: add config path config

Signed-off-by: Xinwei Xiong(cubxxw-openim) <3293172751nss@gmail.com>

* feat: add feat scripts

Signed-off-by: Xinwei Xiong(cubxxw-openim) <3293172751nss@gmail.com>

* feat: add save scripts

Signed-off-by: Xinwei Xiong(cubxxw-openim) <3293172751nss@gmail.com>

* feat: add save scripts

Signed-off-by: Xinwei Xiong(cubxxw-openim) <3293172751nss@gmail.com>

* feat: add sctips help

Signed-off-by: Xinwei Xiong(cubxxw-openim) <3293172751nss@gmail.com>

* feat: add start sctips help

Signed-off-by: Xinwei Xiong(cubxxw-openim) <3293172751nss@gmail.com>

* feat: save scripts file

Signed-off-by: Xinwei Xiong(cubxxw-openim) <3293172751nss@gmail.com>

* feat: save all file

Signed-off-by: Xinwei Xiong(cubxxw-openim) <3293172751nss@gmail.com>

* feat: add openim server template file

Signed-off-by: Xinwei Xiong(cubxxw-openim) <3293172751nss@gmail.com>

* feat: add alot of system design

Signed-off-by: Xinwei Xiong(cubxxw-openim) <3293172751nss@gmail.com>

* feat: save all file

Signed-off-by: Xinwei Xiong(cubxxw-openim) <3293172751nss@gmail.com>

* feat: save all file

Signed-off-by: Xinwei Xiong(cubxxw-openim) <3293172751nss@gmail.com>

* feat: add env config options

Signed-off-by: Xinwei Xiong(cubxxw-openim) <3293172751nss@gmail.com>

* feat: add more robot details

Signed-off-by: Xinwei Xiong(cubxxw-openim) <3293172751nss@gmail.com>

* feat: add more module explain

Signed-off-by: Xinwei Xiong(cubxxw-openim) <3293172751nss@gmail.com>

* feat: add scripts environment details design

Signed-off-by: Xinwei Xiong(cubxxw-openim) <3293172751nss@gmail.com>

* feat: add openim msgtransfer scripts

Signed-off-by: Xinwei Xiong(cubxxw-openim) <3293172751nss@gmail.com>

* feat: add openim msgtransfer scripts

Signed-off-by: Xinwei Xiong(cubxxw-openim) <3293172751nss@gmail.com>

* feat: add more design scripts

Signed-off-by: Xinwei Xiong(cubxxw-openim) <3293172751nss@gmail.com>

* feat: add file save

Signed-off-by: Xinwei Xiong(cubxxw-openim) <3293172751nss@gmail.com>

* feat: add file save

Signed-off-by: Xinwei Xiong(cubxxw-openim) <3293172751nss@gmail.com>

* feat: add rpc build and start

Signed-off-by: Xinwei Xiong(cubxxw-openim) <3293172751nss@gmail.com>

* feat: add rpc build and start

Signed-off-by: Xinwei Xiong(cubxxw-openim) <3293172751nss@gmail.com>

* feat: add rpc build and start

Signed-off-by: Xinwei Xiong(cubxxw-openim) <3293172751nss@gmail.com>

* feat: save all images file

Signed-off-by: Xinwei Xiong(cubxxw-openim) <3293172751nss@gmail.com>

* feat: add scripts set

Signed-off-by: Xinwei Xiong(cubxxw-openim) <3293172751nss@gmail.com>

* feat: add test options

Signed-off-by: Xinwei Xiong(cubxxw-openim) <3293172751nss@gmail.com>

* fix: fix config path file

Signed-off-by: Xinwei Xiong(cubxxw-openim) <3293172751nss@gmail.com>

* fix: update config file

Signed-off-by: Xinwei Xiong(cubxxw-openim) <3293172751nss@gmail.com>

* fix: update config file

Signed-off-by: Xinwei Xiong(cubxxw-openim) <3293172751nss@gmail.com>

* fix: update config file

Signed-off-by: Xinwei Xiong(cubxxw-openim) <3293172751nss@gmail.com>

* fix: update config file

Signed-off-by: Xinwei Xiong(cubxxw-openim) <3293172751nss@gmail.com>

* feat: add readme docs

Signed-off-by: Xinwei Xiong(cubxxw-openim) <3293172751nss@gmail.com>

* feat: save build scripts

Signed-off-by: Xinwei Xiong(cubxxw-openim) <3293172751nss@gmail.com>

* feat: add all actions file

Signed-off-by: Xinwei Xiong(cubxxw-openim) <3293172751nss@gmail.com>

* feat: add chat scripts name

Signed-off-by: Xinwei Xiong(cubxxw-openim) <3293172751nss@gmail.com>

* feat: add all compose

Signed-off-by: Xinwei Xiong(cubxxw-openim) <3293172751nss@gmail.com>

* feat: commit tag

Signed-off-by: Xinwei Xiong(cubxxw-openim) <3293172751nss@gmail.com>

* feat: save server code

Signed-off-by: Xinwei Xiong(cubxxw-openim) <3293172751nss@gmail.com>

* feat: add docker compose fix

Signed-off-by: Xinwei Xiong(cubxxw-openim) <3293172751nss@gmail.com>

* feat: add docker compose fix

Signed-off-by: Xinwei Xiong(cubxxw-openim) <3293172751nss@gmail.com>

* feat: add docker compose fix

Signed-off-by: Xinwei Xiong(cubxxw-openim) <3293172751nss@gmail.com>

* feat: save server code

Signed-off-by: Xinwei Xiong(cubxxw-openim) <3293172751nss@gmail.com>

* feat: optimize dockerfile option

Signed-off-by: Xinwei Xiong(cubxxw-openim) <3293172751nss@gmail.com>

* feat: optimize dockerfile option

Signed-off-by: Xinwei Xiong(cubxxw-openim) <3293172751nss@gmail.com>

* feat: optimize dockerfile option

Signed-off-by: Xinwei Xiong(cubxxw-openim) <3293172751nss@gmail.com>

* feat: add all options

Signed-off-by: Xinwei Xiong(cubxxw-openim) <3293172751nss@gmail.com>

* feat: add all options

Signed-off-by: Xinwei Xiong(cubxxw-openim) <3293172751nss@gmail.com>

* feat: add more scrips

Signed-off-by: Xinwei Xiong(cubxxw-openim) <3293172751nss@gmail.com>

* feat: add more options

Signed-off-by: Xinwei Xiong(cubxxw-openim) <3293172751nss@gmail.com>

* feat: add more options

Signed-off-by: Xinwei Xiong(cubxxw-openim) <3293172751nss@gmail.com>

* feat: add config path

Signed-off-by: Xinwei Xiong(cubxxw-openim) <3293172751nss@gmail.com>

* fix: Add some optimizations

Signed-off-by: Xinwei Xiong(cubxxw-openim) <3293172751nss@gmail.com>

* fix: Add some optimizations

Signed-off-by: Xinwei Xiong(cubxxw-openim) <3293172751nss@gmail.com>

* feat: delele go work sum

Signed-off-by: Xinwei Xiong(cubxxw-openim) <3293172751nss@gmail.com>

* feat: delele go work sum

Signed-off-by: Xinwei Xiong(cubxxw-openim) <3293172751nss@gmail.com>

* Delete go.work.sum

* feat: delele go work sum

Signed-off-by: Xinwei Xiong(cubxxw-openim) <3293172751nss@gmail.com>

* Update .env

* feat: delele go work sum

Signed-off-by: Xinwei Xiong(cubxxw-openim) <3293172751nss@gmail.com>

* feat: delele go work sum

Signed-off-by: Xinwei Xiong(cubxxw-openim) <3293172751nss@gmail.com>

* feat: delele go work sum

Signed-off-by: Xinwei Xiong(cubxxw-openim) <3293172751nss@gmail.com>

* feat: delele go work sum

Signed-off-by: Xinwei Xiong(cubxxw-openim) <3293172751nss@gmail.com>

* feat: add docker compose fix

Signed-off-by: Xinwei Xiong(cubxxw-openim) <3293172751nss@gmail.com>

* feat: add docker compose fix

Signed-off-by: Xinwei Xiong(cubxxw-openim) <3293172751nss@gmail.com>

* feat: delele go work sum

Signed-off-by: Xinwei Xiong(cubxxw-openim) <3293172751nss@gmail.com>

---------

Signed-off-by: Xinwei Xiong(cubxxw-openim) <3293172751nss@gmail.com>
This commit is contained in:
Xinwei Xiong
2023-08-23 09:09:51 +08:00
committed by GitHub
parent ff9b258229
commit 58f591e5f6
255 changed files with 12457 additions and 4111 deletions
-20
View File
@@ -12,26 +12,6 @@ As openim is using go1.18's [workspace feature](https://go.dev/doc/tutorial/work
You can execute the following commands to do things above:
```bash
# 4dd91a700d3f:/openim# tree
# .
# ├── LICENSE
# ├── README.md
# ├── openim-chat
# │ ├── bin
# │ ├── config
# │ ├── logs
# │ └── scripts
# ├── openim-server
# │ ├── bin
# │ ├── config
# │ ├── logs
# │ └── scripts
# ├── openkf
# │ ├── bin
# │ ├── config
# │ ├── logs
# │ └── scripts
# cd tools_code_dir
# edit the CRD_NAME and CRD_GROUP to your own
export OPENIM_TOOLS_NAME=<Changeme>
+308
View File
@@ -0,0 +1,308 @@
// 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
}
+3
View File
@@ -0,0 +1,3 @@
module github.com/OpenIMSDK/Open-IM-Server/tools/changelog
go 1.20
@@ -1,8 +1,23 @@
// 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"
@@ -29,7 +44,8 @@ import (
)
const (
cfgPath = "../../../../../config/config.yaml"
// defaultCfgPath is the default path of the configuration file
defaultCfgPath = "../../../../../config/config.yaml"
minioHealthCheckDuration = 1
maxRetry = 100
componentStartErrCode = 6000
@@ -37,84 +53,64 @@ const (
)
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)
data, err := os.ReadFile(*cfgPath)
if err != nil {
return err
}
if err = yaml.Unmarshal(data, &config.Config); err != nil {
return err
}
return nil
return yaml.Unmarshal(data, &config.Config)
}
type checkFunc struct {
name string
function func() error
}
func main() {
err := initCfg()
if err != nil {
fmt.Printf("Read config failed: %v", err.Error())
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)
// Check MySQL
if err := checkMysql(); err != nil {
errorPrint(fmt.Sprintf("Starting Mysql failed: %v. Please make sure your mysql service has started", err.Error()))
continue
} else {
successPrint(fmt.Sprint("Mysql starts successfully"))
}
fmt.Printf("Checking components Round %v...\n", i+1)
// Check MongoDB
if err := checkMongo(); err != nil {
errorPrint(fmt.Sprintf("Starting Mongo failed: %v. Please make sure your monngo service has started", err.Error()))
continue
} else {
successPrint(fmt.Sprint("Mongo starts successfully"))
}
// Check Minio
if err := checkMinio(); err != nil {
if index := strings.Index(err.Error(), utils.IntToString(configErrCode)); index != -1 {
successPrint(fmt.Sprint("Minio starts successfully"))
warningPrint(fmt.Sprintf("%v. Please modify your config file", err.Error()))
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 {
errorPrint(fmt.Sprintf("Starting Minio failed: %v. Please make sure your Minio service has started", err.Error()))
continue
successPrint(fmt.Sprintf("%s starts successfully", check.name))
}
} else {
successPrint(fmt.Sprint("Minio starts successfully"))
}
// Check Redis
if err := checkRedis(); err != nil {
errorPrint(fmt.Sprintf("Starting Redis failed: %v.Please make sure your Redis service has started", err.Error()))
continue
} else {
successPrint(fmt.Sprint("Redis starts successfully"))
}
// Check Zookeeper
if err := checkZookeeper(); err != nil {
errorPrint(fmt.Sprintf("Starting Zookeeper failed: %v.Please make sure your Zookeeper service has started", err.Error()))
continue
} else {
successPrint(fmt.Sprint("Zookeeper starts successfully"))
if allSuccess {
successPrint("All components started successfully!")
return
}
// Check Kafka
if err := checkKafka(); err != nil {
errorPrint(fmt.Sprintf("Starting Kafka failed: %v.Please make sure your Kafka service has started", err.Error()))
continue
} else {
successPrint(fmt.Sprint("Kafka starts successfully"))
}
successPrint(fmt.Sprint("All components starts successfully"))
os.Exit(0)
}
os.Exit(1)
}
@@ -207,8 +203,8 @@ func checkMinio() error {
return ErrComponentStart.Wrap("Minio server is offline")
}
}
if exactIP(config.Config.Object.ApiURL) == "127.0.0.1" || exactIP(config.Config.Object.Minio.Endpoint) == "127.0.0.1" {
return ErrConfig.Wrap("apiURL or Minio endpoint contain 127.0.0.1.")
if exactIP(config.Config.Object.ApiURL) == "127.0.0.1" || exactIP(config.Config.Object.Minio.SignEndpoint) == "127.0.0.1" {
return ErrConfig.Wrap("apiURL or Minio SignEndpoint endpoint contain 127.0.0.1.")
}
}
return nil
+47
View File
@@ -0,0 +1,47 @@
# OpenIM CTL 模块
## 为什么设计这个模块
OpenIM 后期功能扩展,不能总依赖一些单独的模块,而是整合到 Imctl 中。
测试同学做自动化测试或者是 e2e 测试,接口测试等等,每一次调用 API 很麻烦,用 imctl 的方式为 api 的调用提供了方便
和 scripts 深度交互,同样减少了 IM 本身的耦合度,提高了 IM 的可扩展性。
## 功能设计
+ 用户管理:例如,添加、删除或禁用用户账户。
+ 系统监控:查看在线用户数量、消息传送速率等关键性能指标。
+ 调试:如查看日志、调整日志级别、查看系统状态等。
+ 配置管理:更新系统设置、管理插件或模块等。
+ 数据管理:备份和恢复数据、导入和导出数据等。
+ 系统维护:例如,执行更新、重启服务、进行维护模式等。
## 设计思路
参考 kubectl, 方便 开发、运维、测试同学使用系统功能,并且实现自动化功能。
**自动化设计思路:**
1. 为后面的扩展子模块或者子命令提供自动化的功能,提供子命令 `imctl new` 自动创建新的子命令
2. 以通过 imctl 对用户、密钥和策略进行CURD操作
3. 设置 imctl 自动补全脚本
4. 版本管理,问题:https://github.com/OpenIMSDK/Open-IM-Server/issues/574,做 IM 自动化的版本管理,查看 IM 系统版本
一些简单的 IM 解决方案可能不需要这样的工具,而复杂、高度定制的系统可能会从中受益。
所以暂时将这个模块放在 tools/imctl 中,后面迁移到 cmd/imctl 中
## 目录结构设计
为了方便迁移,将 imctl 工程化设计,命令工具放入到 tools/imctl/cmd 中,其他的模块放入到 tools/imctl/pkg 中
```
+49
View File
@@ -0,0 +1,49 @@
# OpenIM `man` Module README
Welcome to the `man` module of OpenIM, the comprehensive guide for using OpenIM's range of powerful commands. Here, you'll find in-depth details for each command, its options, and examples to help you harness the full power of the OpenIM suite.
## Overview
OpenIM is a robust instant messaging solution. To ensure users can effectively harness its capabilities, OpenIM provides a suite of commands that serve different functionalities, from the API level to RPC calls and utilities.
The `man` module ensures that users, both new and experienced, have a reliable source of information and documentation to use these commands effectively.
## Available Commands
The OpenIM commands are divided into core services and tools. Below is a brief overview of each:
### Core Services
- **openim-api**: Interface to the main functionalities of OpenIM.
- **openim-cmdutils**: Utilities for executing common tasks.
- **openim-crontask**: Schedule and manage routine tasks within OpenIM.
- **openim-msggateway**: Gateway for managing messages within the OpenIM system.
- **openim-msgtransfer**: Handle message transfers across different parts of OpenIM.
- **openim-push**: Service for pushing notifications and updates.
- **openim-rpc-auth**: RPC interface for authentication tasks.
- **openim-rpc-conversation**: RPC service for handling conversations.
- **openim-rpc-friend**: Manage friend lists and related functionalities through RPC.
- **openim-rpc-group**: Group management via RPC.
- **openim-rpc-msg**: Message handling at the RPC level.
- **openim-rpc-third**: Third-party integrations and related tasks through RPC.
- **openim-rpc-user**: User management and tasks via RPC.
### Tools
- **changelog**: Track and manage changes in OpenIM.
- **component**: Utilities related to different components within OpenIM.
- **infra**: Infrastructure and backend management tools.
- **ncpu**: Monitor and manage CPU usage and related tasks.
- **yamlfmt**: A tool for formatting and linting YAML files within the OpenIM configuration.
## How to Use
To view the manual page for any of the OpenIM commands, use the `man` command followed by the command name. For example:
```
man openim-api
```
## Contributions
We welcome contributions to enhance the `man` pages. If you discover inconsistencies, errors, or areas where further details are required, feel free to raise an issue or submit a pull request.
+63
View File
@@ -0,0 +1,63 @@
// 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":
}
+29
View File
@@ -0,0 +1,29 @@
// Copyright © 2023 OpenIM. All rights reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
// iamctl is the command line tool for iam platform.
package main
import (
"os"
"github.com/OpenIMSDK/Open-IM-Server/tools/imctl/internal/imctl/cmd"
)
func main() {
command := cmd.NewDefaultIMCtlCommand()
if err := command.Execute(); err != nil {
os.Exit(1)
}
}
+19
View File
@@ -0,0 +1,19 @@
module github.com/OpenIMSDK/Open-IM-Server/tools/imctl
go 1.20
require (
github.com/MakeNowJust/heredoc/v2 v2.0.1
github.com/marmotedu/iam v1.7.0
github.com/mitchellh/go-wordwrap v1.0.1
github.com/moby/term v0.5.0
github.com/russross/blackfriday v1.6.0
github.com/spf13/cobra v1.7.0
github.com/spf13/pflag v1.0.5
)
require (
github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1 // indirect
github.com/inconshreveable/mousetrap v1.1.0 // indirect
golang.org/x/sys v0.1.0 // indirect
)
+26
View File
@@ -0,0 +1,26 @@
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/marmotedu/iam v1.7.0 h1:9aWg5Enx+npHU9kxQ0eYsdXvbiGeUsuuzxaV49BQa0I=
github.com/marmotedu/iam v1.7.0/go.mod h1:kjQ1Tzr+M6/B49DSC3Zky+2Ai9vAr6PbhuHV8mWUS48=
github.com/mitchellh/go-wordwrap v1.0.1 h1:TLuKupo69TCn6TQSyGxwI1EblZZEsQ0vMlAFQflz0v0=
github.com/mitchellh/go-wordwrap v1.0.1/go.mod h1:R62XHJLzvMFRBbcrT7m7WgmE1eOyTSsCt+hzestvNj0=
github.com/moby/term v0.5.0 h1:xt8Q1nalod/v7BqbG21f8mQPqH+xAaC9C3N3wfWbVP0=
github.com/moby/term v0.5.0/go.mod h1:8FzsFHVUBGZdbDsJw/ot+X+d5HLUbvklYLJ9uGfcI3Y=
github.com/russross/blackfriday v1.6.0 h1:KqfZb0pUVN2lYqZUYRddxF4OR8ZMURnJIG5Y3VRLtww=
github.com/russross/blackfriday v1.6.0/go.mod h1:ti0ldHuxg49ri4ksnFxlkCfN+hvslNlmVHqNRXXJNAY=
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
github.com/spf13/cobra v1.7.0 h1:hyqWnYt1ZQShIddO5kBpj3vu05/++x6tJ6dg8EC572I=
github.com/spf13/cobra v1.7.0/go.mod h1:uLxZILRyS/50WlhOIKD7W6V5bgeIt+4sICxh6uRMrb0=
github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA=
github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
golang.org/x/sys v0.0.0-20210616094352-59db8d763f22/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.1.0 h1:kunALQeHf1/185U1i0GOB/fy1IPRDDpuoOOqRReG57U=
golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
+133
View File
@@ -0,0 +1,133 @@
// 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"
"github.com/marmotedu/iam/pkg/cli/genericclioptions"
"github.com/spf13/cobra"
"github.com/spf13/viper"
"k8s.io/kubectl/pkg/cmd/completion"
"k8s.io/kubectl/pkg/cmd/options"
"k8s.io/kubectl/pkg/cmd/set"
"k8s.io/kubectl/pkg/cmd/version"
"github.com/OpenIMSDK/Open-IM-Server/tools/imctl/internal/util/templates"
)
// 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 NewIAMCtlCommand(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()
}
@@ -0,0 +1,95 @@
// Copyright © 2023 OpenIM. All rights reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package cmd
import (
"errors"
"fmt"
"os"
"runtime"
"runtime/pprof"
"github.com/spf13/pflag"
)
// profiling configuration variables
var (
profileName string = "none" // Name of the profile to capture.
profileOutput string = "profile.pprof" // File to write the profile data.
)
// addProfilingFlags registers profiling related flags to the given FlagSet.
func addProfilingFlags(flags *pflag.FlagSet) {
flags.StringVar(
&profileName,
"profile",
"none",
"Type of profile to capture. Options: none, cpu, heap, goroutine, threadcreate, block, mutex",
)
flags.StringVar(&profileOutput, "profile-output", "profile.pprof", "File to write the profile data")
}
// initProfiling sets up profiling based on the user's choice.
// If 'cpu' is selected, it starts the CPU profile. For block and mutex profiles,
// sampling rates are set up.
func initProfiling() error {
switch profileName {
case "none":
return nil
case "cpu":
f, err := os.Create(profileOutput)
if err != nil {
return err
}
return pprof.StartCPUProfile(f)
case "block":
runtime.SetBlockProfileRate(1) // Sampling every block event
return nil
case "mutex":
runtime.SetMutexProfileFraction(1) // Sampling every mutex event
return nil
default:
if profile := pprof.Lookup(profileName); profile == nil {
return fmt.Errorf("unknown profile type: '%s'", profileName)
}
return nil
}
}
// flushProfiling writes the profiling data to the specified file.
// For heap profiles, it runs the GC before capturing the data.
// It stops the CPU profile if it was started.
func flushProfiling() error {
switch profileName {
case "none":
return nil
case "cpu":
pprof.StopCPUProfile()
return nil
case "heap":
runtime.GC() // Run garbage collection before writing heap profile
fallthrough
default:
profile := pprof.Lookup(profileName)
if profile == nil {
return errors.New("invalid profile type")
}
f, err := os.Create(profileOutput)
if err != nil {
return err
}
return profile.WriteTo(f, 0)
}
}
@@ -0,0 +1,103 @@
// 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 interrupt deal with signals.
package interrupt
import (
"os"
"os/signal"
"sync"
"syscall"
)
// terminationSignals are signals that cause the program to exit in the
// supported platforms (linux, darwin, windows).
var terminationSignals = []os.Signal{syscall.SIGHUP, syscall.SIGINT, syscall.SIGTERM, syscall.SIGQUIT}
// Handler guarantees execution of notifications after a critical section (the function passed
// to a Run method), even in the presence of process termination. It guarantees exactly once
// invocation of the provided notify functions.
type Handler struct {
notify []func()
final func(os.Signal)
once sync.Once
}
// Chain creates a new handler that invokes all notify functions when the critical section exits
// and then invokes the optional handler's notifications. This allows critical sections to be
// nested without losing exactly once invocations. Notify functions can invoke any cleanup needed
// but should not exit (which is the responsibility of the parent handler).
func Chain(handler *Handler, notify ...func()) *Handler {
if handler == nil {
return New(nil, notify...)
}
return New(handler.Signal, append(notify, handler.Close)...)
}
// New creates a new handler that guarantees all notify functions are run after the critical
// section exits (or is interrupted by the OS), then invokes the final handler. If no final
// handler is specified, the default final is `os.Exit(1)`. A handler can only be used for
// one critical section.
func New(final func(os.Signal), notify ...func()) *Handler {
return &Handler{
final: final,
notify: notify,
}
}
// Close executes all the notification handlers if they have not yet been executed.
func (h *Handler) Close() {
h.once.Do(func() {
for _, fn := range h.notify {
fn()
}
})
}
// Signal is called when an os.Signal is received, and guarantees that all notifications
// are executed, then the final handler is executed. This function should only be called once
// per Handler instance.
func (h *Handler) Signal(s os.Signal) {
h.once.Do(func() {
for _, fn := range h.notify {
fn()
}
if h.final == nil {
os.Exit(1)
}
h.final(s)
})
}
// Run ensures that any notifications are invoked after the provided fn exits (even if the
// process is interrupted by an OS termination signal). Notifications are only invoked once
// per Handler instance, so calling Run more than once will not behave as the user expects.
func (h *Handler) Run(fn func() error) error {
ch := make(chan os.Signal, 1)
signal.Notify(ch, terminationSignals...)
defer func() {
signal.Stop(ch)
close(ch)
}()
go func() {
sig, ok := <-ch
if !ok {
return
}
h.Signal(sig)
}()
defer h.Close()
return fn()
}
@@ -0,0 +1,57 @@
// 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 templates
import (
"github.com/spf13/cobra"
)
type CommandGroup struct {
Message string
Commands []*cobra.Command
}
type CommandGroups []CommandGroup
func (g CommandGroups) Add(c *cobra.Command) {
for _, group := range g {
c.AddCommand(group.Commands...)
}
}
func (g CommandGroups) Has(c *cobra.Command) bool {
for _, group := range g {
for _, command := range group.Commands {
if command == c {
return true
}
}
}
return false
}
func AddAdditionalCommands(g CommandGroups, message string, cmds []*cobra.Command) CommandGroups {
group := CommandGroup{Message: message}
for _, c := range cmds {
// Don't show commands that have no short description
if !g.Has(c) && len(c.Short) != 0 {
group.Commands = append(group.Commands, c)
}
}
if len(group.Commands) == 0 {
return g
}
return append(g, group)
}
@@ -0,0 +1,149 @@
// 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 templates
import (
"bytes"
"fmt"
"io"
"strings"
"github.com/russross/blackfriday"
)
const linebreak = "\n"
// ASCIIRenderer implements blackfriday.Renderer.
var _ blackfriday.Renderer = &ASCIIRenderer{}
// ASCIIRenderer is a blackfriday.Renderer intended for rendering markdown
// documents as plain text, well suited for human reading on terminals.
type ASCIIRenderer struct {
Indentation string
listItemCount uint
listLevel uint
}
// NormalText gets a text chunk *after* the markdown syntax was already
// processed and does a final cleanup on things we don't expect here, like
// removing linebreaks on things that are not a paragraph break (auto unwrap).
func (r *ASCIIRenderer) NormalText(out *bytes.Buffer, text []byte) {
raw := string(text)
lines := strings.Split(raw, linebreak)
for _, line := range lines {
trimmed := strings.Trim(line, " \n\t")
if len(trimmed) > 0 && trimmed[0] != '_' {
out.WriteString(" ")
}
out.WriteString(trimmed)
}
}
// List renders the start and end of a list.
func (r *ASCIIRenderer) List(out *bytes.Buffer, text func() bool, flags int) {
r.listLevel++
out.WriteString(linebreak)
text()
r.listLevel--
}
// ListItem renders list items and supports both ordered and unordered lists.
func (r *ASCIIRenderer) ListItem(out *bytes.Buffer, text []byte, flags int) {
if flags&blackfriday.LIST_ITEM_BEGINNING_OF_LIST != 0 {
r.listItemCount = 1
} else {
r.listItemCount++
}
indent := strings.Repeat(r.Indentation, int(r.listLevel))
var bullet string
if flags&blackfriday.LIST_TYPE_ORDERED != 0 {
bullet += fmt.Sprintf("%d.", r.listItemCount)
} else {
bullet += "*"
}
out.WriteString(indent + bullet + " ")
r.fw(out, text)
out.WriteString(linebreak)
}
// Paragraph renders the start and end of a paragraph.
func (r *ASCIIRenderer) Paragraph(out *bytes.Buffer, text func() bool) {
out.WriteString(linebreak)
text()
out.WriteString(linebreak)
}
// BlockCode renders a chunk of text that represents source code.
func (r *ASCIIRenderer) BlockCode(out *bytes.Buffer, text []byte, lang string) {
out.WriteString(linebreak)
lines := []string{}
for _, line := range strings.Split(string(text), linebreak) {
indented := r.Indentation + line
lines = append(lines, indented)
}
out.WriteString(strings.Join(lines, linebreak))
}
func (r *ASCIIRenderer) GetFlags() int { return 0 }
func (r *ASCIIRenderer) HRule(out *bytes.Buffer) {
out.WriteString(linebreak + "----------" + linebreak)
}
func (r *ASCIIRenderer) LineBreak(out *bytes.Buffer) { out.WriteString(linebreak) }
func (r *ASCIIRenderer) TitleBlock(out *bytes.Buffer, text []byte) { r.fw(out, text) }
func (r *ASCIIRenderer) Header(out *bytes.Buffer, text func() bool, level int, id string) { text() }
func (r *ASCIIRenderer) BlockHtml(out *bytes.Buffer, text []byte) { r.fw(out, text) }
func (r *ASCIIRenderer) BlockQuote(out *bytes.Buffer, text []byte) { r.fw(out, text) }
func (r *ASCIIRenderer) TableRow(out *bytes.Buffer, text []byte) { r.fw(out, text) }
func (r *ASCIIRenderer) TableHeaderCell(out *bytes.Buffer, text []byte, align int) { r.fw(out, text) }
func (r *ASCIIRenderer) TableCell(out *bytes.Buffer, text []byte, align int) { r.fw(out, text) }
func (r *ASCIIRenderer) Footnotes(out *bytes.Buffer, text func() bool) { text() }
func (r *ASCIIRenderer) FootnoteItem(out *bytes.Buffer, name, text []byte, flags int) {
r.fw(out, text)
}
func (r *ASCIIRenderer) AutoLink(out *bytes.Buffer, link []byte, kind int) { r.fw(out, link) }
func (r *ASCIIRenderer) CodeSpan(out *bytes.Buffer, text []byte) { r.fw(out, text) }
func (r *ASCIIRenderer) DoubleEmphasis(out *bytes.Buffer, text []byte) { r.fw(out, text) }
func (r *ASCIIRenderer) Emphasis(out *bytes.Buffer, text []byte) { r.fw(out, text) }
func (r *ASCIIRenderer) RawHtmlTag(out *bytes.Buffer, text []byte) { r.fw(out, text) }
func (r *ASCIIRenderer) TripleEmphasis(out *bytes.Buffer, text []byte) { r.fw(out, text) }
func (r *ASCIIRenderer) StrikeThrough(out *bytes.Buffer, text []byte) { r.fw(out, text) }
func (r *ASCIIRenderer) FootnoteRef(out *bytes.Buffer, ref []byte, id int) { r.fw(out, ref) }
func (r *ASCIIRenderer) Entity(out *bytes.Buffer, entity []byte) { r.fw(out, entity) }
func (r *ASCIIRenderer) Smartypants(out *bytes.Buffer, text []byte) { r.fw(out, text) }
func (r *ASCIIRenderer) DocumentHeader(out *bytes.Buffer) {}
func (r *ASCIIRenderer) DocumentFooter(out *bytes.Buffer) {}
func (r *ASCIIRenderer) TocHeaderWithAnchor(text []byte, level int, anchor string) {}
func (r *ASCIIRenderer) TocHeader(text []byte, level int) {}
func (r *ASCIIRenderer) TocFinalize() {}
func (r *ASCIIRenderer) Table(out *bytes.Buffer, header []byte, body []byte, columnData []int) {
r.fw(out, header, body)
}
func (r *ASCIIRenderer) Link(out *bytes.Buffer, link []byte, title []byte, content []byte) {
out.WriteString(" ")
r.fw(out, link)
}
func (r *ASCIIRenderer) Image(out *bytes.Buffer, link []byte, title []byte, alt []byte) {
r.fw(out, link)
}
func (r *ASCIIRenderer) fw(out io.Writer, text ...[]byte) {
for _, t := range text {
out.Write(t)
}
}
@@ -0,0 +1,99 @@
// 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 templates
import (
"strings"
"github.com/MakeNowJust/heredoc/v2"
"github.com/russross/blackfriday"
"github.com/spf13/cobra"
)
const Indentation = ` `
// LongDesc normalizes a command's long description to follow the conventions.
func LongDesc(s string) string {
if len(s) == 0 {
return s
}
return normalizer{s}.heredoc().markdown().trim().string
}
// Examples normalizes a command's examples to follow the conventions.
func Examples(s string) string {
if len(s) == 0 {
return s
}
return normalizer{s}.trim().indent().string
}
// Normalize perform all required normalizations on a given command.
func Normalize(cmd *cobra.Command) *cobra.Command {
if len(cmd.Long) > 0 {
cmd.Long = LongDesc(cmd.Long)
}
if len(cmd.Example) > 0 {
cmd.Example = Examples(cmd.Example)
}
return cmd
}
// NormalizeAll perform all required normalizations in the entire command tree.
func NormalizeAll(cmd *cobra.Command) *cobra.Command {
if cmd.HasSubCommands() {
for _, subCmd := range cmd.Commands() {
NormalizeAll(subCmd)
}
}
Normalize(cmd)
return cmd
}
type normalizer struct {
string
}
func (s normalizer) markdown() normalizer {
bytes := []byte(s.string)
formatted := blackfriday.Markdown(
bytes,
&ASCIIRenderer{Indentation: Indentation},
blackfriday.EXTENSION_NO_INTRA_EMPHASIS,
)
s.string = string(formatted)
return s
}
func (s normalizer) heredoc() normalizer {
s.string = heredoc.Doc(s.string)
return s
}
func (s normalizer) trim() normalizer {
s.string = strings.TrimSpace(s.string)
return s
}
func (s normalizer) indent() normalizer {
indentedLines := []string{}
for _, line := range strings.Split(s.string, "\n") {
trimmed := strings.TrimSpace(line)
indented := Indentation + trimmed
indentedLines = append(indentedLines, indented)
}
s.string = strings.Join(indentedLines, "\n")
return s
}
@@ -0,0 +1,296 @@
// 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 templates
import (
"bytes"
"fmt"
"strings"
"text/template"
"unicode"
"github.com/spf13/cobra"
flag "github.com/spf13/pflag"
"github.com/OpenIMSDK/Open-IM-Server/tools/imctl/internal/util/term"
)
type FlagExposer interface {
ExposeFlags(cmd *cobra.Command, flags ...string) FlagExposer
}
func ActsAsRootCommand(cmd *cobra.Command, filters []string, groups ...CommandGroup) FlagExposer {
if cmd == nil {
panic("nil root command")
}
templater := &templater{
RootCmd: cmd,
UsageTemplate: MainUsageTemplate(),
HelpTemplate: MainHelpTemplate(),
CommandGroups: groups,
Filtered: filters,
}
cmd.SetFlagErrorFunc(templater.FlagErrorFunc())
cmd.SilenceUsage = true
cmd.SetUsageFunc(templater.UsageFunc())
cmd.SetHelpFunc(templater.HelpFunc())
return templater
}
func UseOptionsTemplates(cmd *cobra.Command) {
templater := &templater{
UsageTemplate: OptionsUsageTemplate(),
HelpTemplate: OptionsHelpTemplate(),
}
cmd.SetUsageFunc(templater.UsageFunc())
cmd.SetHelpFunc(templater.HelpFunc())
}
type templater struct {
UsageTemplate string
HelpTemplate string
RootCmd *cobra.Command
CommandGroups
Filtered []string
}
func (t *templater) FlagErrorFunc(exposedFlags ...string) func(*cobra.Command, error) error {
return func(c *cobra.Command, err error) error {
c.SilenceUsage = true
switch c.CalledAs() {
case "options":
return fmt.Errorf("%s\nrun '%s' without flags", err, c.CommandPath())
default:
return fmt.Errorf("%s\nsee '%s --help' for usage", err, c.CommandPath())
}
}
}
func (t *templater) ExposeFlags(cmd *cobra.Command, flags ...string) FlagExposer {
cmd.SetUsageFunc(t.UsageFunc(flags...))
return t
}
func (t *templater) HelpFunc() func(*cobra.Command, []string) {
return func(c *cobra.Command, s []string) {
tt := template.New("help")
tt.Funcs(t.templateFuncs())
template.Must(tt.Parse(t.HelpTemplate))
out := term.NewResponsiveWriter(c.OutOrStdout())
err := tt.Execute(out, c)
if err != nil {
c.Println(err)
}
}
}
func (t *templater) UsageFunc(exposedFlags ...string) func(*cobra.Command) error {
return func(c *cobra.Command) error {
tt := template.New("usage")
tt.Funcs(t.templateFuncs(exposedFlags...))
template.Must(tt.Parse(t.UsageTemplate))
out := term.NewResponsiveWriter(c.OutOrStderr())
return tt.Execute(out, c)
}
}
func (t *templater) templateFuncs(exposedFlags ...string) template.FuncMap {
return template.FuncMap{
"trim": strings.TrimSpace,
"trimRight": func(s string) string { return strings.TrimRightFunc(s, unicode.IsSpace) },
"trimLeft": func(s string) string { return strings.TrimLeftFunc(s, unicode.IsSpace) },
"gt": cobra.Gt,
"eq": cobra.Eq,
"rpad": rpad,
"appendIfNotPresent": appendIfNotPresent,
"flagsNotIntersected": flagsNotIntersected,
"visibleFlags": visibleFlags,
"flagsUsages": flagsUsages,
"cmdGroups": t.cmdGroups,
"cmdGroupsString": t.cmdGroupsString,
"rootCmd": t.rootCmdName,
"isRootCmd": t.isRootCmd,
"optionsCmdFor": t.optionsCmdFor,
"usageLine": t.usageLine,
"exposed": func(c *cobra.Command) *flag.FlagSet {
exposed := flag.NewFlagSet("exposed", flag.ContinueOnError)
if len(exposedFlags) > 0 {
for _, name := range exposedFlags {
if flag := c.Flags().Lookup(name); flag != nil {
exposed.AddFlag(flag)
}
}
}
return exposed
},
}
}
func (t *templater) cmdGroups(c *cobra.Command, all []*cobra.Command) []CommandGroup {
if len(t.CommandGroups) > 0 && c == t.RootCmd {
all = filter(all, t.Filtered...)
return AddAdditionalCommands(t.CommandGroups, "Other Commands:", all)
}
all = filter(all, "options")
return []CommandGroup{
{
Message: "Available Commands:",
Commands: all,
},
}
}
func (t *templater) cmdGroupsString(c *cobra.Command) string {
groups := []string{}
for _, cmdGroup := range t.cmdGroups(c, c.Commands()) {
cmds := []string{cmdGroup.Message}
for _, cmd := range cmdGroup.Commands {
if cmd.IsAvailableCommand() {
cmds = append(cmds, " "+rpad(cmd.Name(), cmd.NamePadding())+" "+cmd.Short)
}
}
groups = append(groups, strings.Join(cmds, "\n"))
}
return strings.Join(groups, "\n\n")
}
func (t *templater) rootCmdName(c *cobra.Command) string {
return t.rootCmd(c).CommandPath()
}
func (t *templater) isRootCmd(c *cobra.Command) bool {
return t.rootCmd(c) == c
}
func (t *templater) parents(c *cobra.Command) []*cobra.Command {
parents := []*cobra.Command{c}
for current := c; !t.isRootCmd(current) && current.HasParent(); {
current = current.Parent()
parents = append(parents, current)
}
return parents
}
func (t *templater) rootCmd(c *cobra.Command) *cobra.Command {
if c != nil && !c.HasParent() {
return c
}
if t.RootCmd == nil {
panic("nil root cmd")
}
return t.RootCmd
}
func (t *templater) optionsCmdFor(c *cobra.Command) string {
if !c.Runnable() {
return ""
}
rootCmdStructure := t.parents(c)
for i := len(rootCmdStructure) - 1; i >= 0; i-- {
cmd := rootCmdStructure[i]
if _, _, err := cmd.Find([]string{"options"}); err == nil {
return cmd.CommandPath() + " options"
}
}
return ""
}
func (t *templater) usageLine(c *cobra.Command) string {
usage := c.UseLine()
suffix := "[options]"
if c.HasFlags() && !strings.Contains(usage, suffix) {
usage += " " + suffix
}
return usage
}
func flagsUsages(f *flag.FlagSet) string {
x := new(bytes.Buffer)
f.VisitAll(func(flag *flag.Flag) {
if flag.Hidden {
return
}
format := "--%s=%s: %s\n"
if flag.Value.Type() == "string" {
format = "--%s='%s': %s\n"
}
if len(flag.Shorthand) > 0 {
format = " -%s, " + format
} else {
format = " %s " + format
}
fmt.Fprintf(x, format, flag.Shorthand, flag.Name, flag.DefValue, flag.Usage)
})
return x.String()
}
func rpad(s string, padding int) string {
template := fmt.Sprintf("%%-%ds", padding)
return fmt.Sprintf(template, s)
}
func appendIfNotPresent(s, stringToAppend string) string {
if strings.Contains(s, stringToAppend) {
return s
}
return s + " " + stringToAppend
}
func flagsNotIntersected(l *flag.FlagSet, r *flag.FlagSet) *flag.FlagSet {
f := flag.NewFlagSet("notIntersected", flag.ContinueOnError)
l.VisitAll(func(flag *flag.Flag) {
if r.Lookup(flag.Name) == nil {
f.AddFlag(flag)
}
})
return f
}
func visibleFlags(l *flag.FlagSet) *flag.FlagSet {
hidden := "help"
f := flag.NewFlagSet("visible", flag.ContinueOnError)
l.VisitAll(func(flag *flag.Flag) {
if flag.Name != hidden {
f.AddFlag(flag)
}
})
return f
}
func filter(cmds []*cobra.Command, names ...string) []*cobra.Command {
out := []*cobra.Command{}
for _, c := range cmds {
if c.Hidden {
continue
}
skip := false
for _, name := range names {
if name == c.Name() {
skip = true
break
}
}
if skip {
continue
}
out = append(out, c)
}
return out
}
@@ -0,0 +1,103 @@
// 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 templates provides template functions for working with templates.
package templates
import (
"strings"
"unicode"
)
const (
// SectionVars is the help template section that declares variables to be used in the template.
SectionVars = `{{$isRootCmd := isRootCmd .}}` +
`{{$rootCmd := rootCmd .}}` +
`{{$visibleFlags := visibleFlags (flagsNotIntersected .LocalFlags .PersistentFlags)}}` +
`{{$explicitlyExposedFlags := exposed .}}` +
`{{$optionsCmdFor := optionsCmdFor .}}` +
`{{$usageLine := usageLine .}}`
// SectionAliases is the help template section that displays command aliases.
SectionAliases = `{{if gt .Aliases 0}}Aliases:
{{.NameAndAliases}}
{{end}}`
// SectionExamples is the help template section that displays command examples.
SectionExamples = `{{if .HasExample}}Examples:
{{trimRight .Example}}
{{end}}`
// SectionSubcommands is the help template section that displays the command's subcommands.
SectionSubcommands = `{{if .HasAvailableSubCommands}}{{cmdGroupsString .}}
{{end}}`
// SectionFlags is the help template section that displays the command's flags.
SectionFlags = `{{ if or $visibleFlags.HasFlags $explicitlyExposedFlags.HasFlags}}Options:
{{ if $visibleFlags.HasFlags}}{{trimRight (flagsUsages $visibleFlags)}}{{end}}{{ if $explicitlyExposedFlags.HasFlags}}{{ if $visibleFlags.HasFlags}}
{{end}}{{trimRight (flagsUsages $explicitlyExposedFlags)}}{{end}}
{{end}}`
// SectionUsage is the help template section that displays the command's usage.
SectionUsage = `{{if and .Runnable (ne .UseLine "") (ne .UseLine $rootCmd)}}Usage:
{{$usageLine}}
{{end}}`
// SectionTipsHelp is the help template section that displays the '--help' hint.
SectionTipsHelp = `{{if .HasSubCommands}}Use "{{$rootCmd}} <command> --help" for more information about a given command.
{{end}}`
// SectionTipsGlobalOptions is the help template section that displays the 'options' hint for displaying global
// flags.
SectionTipsGlobalOptions = `{{if $optionsCmdFor}}Use "{{$optionsCmdFor}}" for a list of global command-line options (applies to all commands).
{{end}}`
)
// MainHelpTemplate if the template for 'help' used by most commands.
func MainHelpTemplate() string {
return `{{with or .Long .Short }}{{. | trim}}{{end}}{{if or .Runnable .HasSubCommands}}{{.UsageString}}{{end}}`
}
// MainUsageTemplate if the template for 'usage' used by most commands.
func MainUsageTemplate() string {
sections := []string{
"\n\n",
SectionVars,
SectionAliases,
SectionExamples,
SectionSubcommands,
SectionFlags,
SectionUsage,
SectionTipsHelp,
SectionTipsGlobalOptions,
}
return strings.TrimRightFunc(strings.Join(sections, ""), unicode.IsSpace)
}
// OptionsHelpTemplate if the template for 'help' used by the 'options' command.
func OptionsHelpTemplate() string {
return ""
}
// OptionsUsageTemplate if the template for 'usage' used by the 'options' command.
func OptionsUsageTemplate() string {
return `{{ if .HasInheritedFlags}}The following options can be passed to any command:
{{flagsUsages .InheritedFlags}}{{end}}`
}
@@ -0,0 +1,53 @@
// 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 term
import (
"github.com/moby/term"
)
// TerminalSize represents the width and height of a terminal.
type TerminalSize struct {
Width uint16
Height uint16
}
// TerminalSizeQueue is capable of returning terminal resize events as they occur.
type TerminalSizeQueue interface {
// Next returns the new terminal size after the terminal has been resized. It returns nil when
// monitoring has been stopped.
Next() *TerminalSize
}
// GetSize returns the current size of the user's terminal. If it isn't a terminal,
// nil is returned.
func (t TTY) GetSize() *TerminalSize {
outFd, isTerminal := term.GetFdInfo(t.Out)
if !isTerminal {
return nil
}
return GetSize(outFd)
}
// GetSize returns the current size of the terminal associated with fd.
func GetSize(fd uintptr) *TerminalSize {
winsize, err := term.GetWinsize(fd)
if err != nil {
// runtime.HandleError(fmt.Errorf("unable to get terminal size: %v", err))
return nil
}
return &TerminalSize{Width: winsize.Width, Height: winsize.Height}
}
@@ -0,0 +1,37 @@
// 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 term provides structures and helper functions to work with
// terminal (state, sizes).
package term
import (
"io"
)
// TTY helps invoke a function and preserve the state of the terminal, even if the process is
// terminated during execution. It also provides support for terminal resizing for remote command
// execution/attachment.
type TTY struct {
// In is a reader representing stdin. It is a required field.
In io.Reader
// Out is a writer representing stdout. It must be set to support terminal resizing. It is an
// optional field.
Out io.Writer
// Raw is true if the terminal should be set raw.
Raw bool
// TryDev indicates the TTY should try to open /dev/tty if the provided input
// is not a file descriptor.
TryDev bool
}
@@ -0,0 +1,124 @@
// 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 term
import (
"io"
"os"
wordwrap "github.com/mitchellh/go-wordwrap"
"github.com/moby/term"
)
type wordWrapWriter struct {
limit uint
writer io.Writer
}
// NewResponsiveWriter creates a Writer that detects the column width of the
// terminal we are in, and adjusts every line width to fit and use recommended
// terminal sizes for better readability. Does proper word wrapping automatically.
//
// if terminal width >= 120 columns use 120 columns
// if terminal width >= 100 columns use 100 columns
// if terminal width >= 80 columns use 80 columns
//
// In case we're not in a terminal or if it's smaller than 80 columns width,
// doesn't do any wrapping.
func NewResponsiveWriter(w io.Writer) io.Writer {
file, ok := w.(*os.File)
if !ok {
return w
}
fd := file.Fd()
if !term.IsTerminal(fd) {
return w
}
terminalSize := GetSize(fd)
if terminalSize == nil {
return w
}
var limit uint
switch {
case terminalSize.Width >= 120:
limit = 120
case terminalSize.Width >= 100:
limit = 100
case terminalSize.Width >= 80:
limit = 80
}
return NewWordWrapWriter(w, limit)
}
// NewWordWrapWriter is a Writer that supports a limit of characters on every line
// and does auto word wrapping that respects that limit.
func NewWordWrapWriter(w io.Writer, limit uint) io.Writer {
return &wordWrapWriter{
limit: limit,
writer: w,
}
}
func (w wordWrapWriter) Write(p []byte) (nn int, err error) {
if w.limit == 0 {
return w.writer.Write(p)
}
original := string(p)
wrapped := wordwrap.WrapString(original, w.limit)
return w.writer.Write([]byte(wrapped))
}
// NewPunchCardWriter is a NewWordWrapWriter that limits the line width to 80 columns.
func NewPunchCardWriter(w io.Writer) io.Writer {
return NewWordWrapWriter(w, 80)
}
type maxWidthWriter struct {
maxWidth uint
currentWidth uint
written uint
writer io.Writer
}
// NewMaxWidthWriter is a Writer that supports a limit of characters on every
// line, but doesn't do any word wrapping automatically.
func NewMaxWidthWriter(w io.Writer, maxWidth uint) io.Writer {
return &maxWidthWriter{
maxWidth: maxWidth,
writer: w,
}
}
func (m maxWidthWriter) Write(p []byte) (nn int, err error) {
for _, b := range p {
if m.currentWidth == m.maxWidth {
m.writer.Write([]byte{'\n'})
m.currentWidth = 0
}
if b == '\n' {
m.currentWidth = 0
}
_, err := m.writer.Write([]byte{b})
if err != nil {
return int(m.written), err
}
m.written++
m.currentWidth++
}
return len(p), nil
}
@@ -0,0 +1,114 @@
// 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 term
import (
"bytes"
"strings"
"testing"
)
const test = "Iam Iam Iam Iam Iam Iam Iam Iam Iam Iam Iam Iam Iam Iam Iam Iam Iam Iam Iam Iam Iam Iam Iam Iam Iam Iam Iam Iam Iam Iam Iam Iam Iam Iam Iam Iam Iam Iam Iam Iam Iam Iam Iam Iam Iam Iam Iam Iam Iam Iam Iam Iam Iam Iam Iam Iam Iam Iam Iam Iam Iam Iam Iam Iam Iam Iam Iam Iam"
func TestWordWrapWriter(t *testing.T) {
testcases := map[string]struct {
input string
maxWidth uint
}{
"max 10": {input: test, maxWidth: 10},
"max 80": {input: test, maxWidth: 80},
"max 120": {input: test, maxWidth: 120},
"max 5000": {input: test, maxWidth: 5000},
}
for k, tc := range testcases {
b := bytes.NewBufferString("")
w := NewWordWrapWriter(b, tc.maxWidth)
_, err := w.Write([]byte(tc.input))
if err != nil {
t.Errorf("%s: Unexpected error: %v", k, err)
}
result := b.String()
if !strings.Contains(result, "Iam") {
t.Errorf("%s: Expected to contain \"Iam\"", k)
}
if len(result) < len(tc.input) {
t.Errorf(
"%s: Unexpectedly short string, got %d wanted at least %d chars: %q",
k,
len(result),
len(tc.input),
result,
)
}
for _, line := range strings.Split(result, "\n") {
if len(line) > int(tc.maxWidth) {
t.Errorf("%s: Every line must be at most %d chars long, got %d: %q", k, tc.maxWidth, len(line), line)
}
}
for _, word := range strings.Split(result, " ") {
if !strings.Contains(word, "Iam") {
t.Errorf("%s: Unexpected broken word: %q", k, word)
}
}
}
}
func TestMaxWidthWriter(t *testing.T) {
testcases := map[string]struct {
input string
maxWidth uint
}{
"max 10": {input: test, maxWidth: 10},
"max 80": {input: test, maxWidth: 80},
"max 120": {input: test, maxWidth: 120},
"max 5000": {input: test, maxWidth: 5000},
}
for k, tc := range testcases {
b := bytes.NewBufferString("")
w := NewMaxWidthWriter(b, tc.maxWidth)
_, err := w.Write([]byte(tc.input))
if err != nil {
t.Errorf("%s: Unexpected error: %v", k, err)
}
result := b.String()
if !strings.Contains(result, "Iam") {
t.Errorf("%s: Expected to contain \"Iam\"", k)
}
if len(result) < len(tc.input) {
t.Errorf(
"%s: Unexpectedly short string, got %d wanted at least %d chars: %q",
k,
len(result),
len(tc.input),
result,
)
}
lines := strings.Split(result, "\n")
for i, line := range lines {
if len(line) > int(tc.maxWidth) {
t.Errorf("%s: Every line must be at most %d chars long, got %d: %q", k, tc.maxWidth, len(line), line)
}
if i < len(lines)-1 && len(line) != int(tc.maxWidth) {
t.Errorf(
"%s: Lines except the last one are expected to be exactly %d chars long, got %d: %q",
k,
tc.maxWidth,
len(line),
line,
)
}
}
}
}
+40
View File
@@ -0,0 +1,40 @@
// 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)
}
-26
View File
@@ -1,26 +0,0 @@
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)
}
+47
View File
@@ -0,0 +1,47 @@
# 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.)
## Contributing
If you have any suggestions, bug reports, or wish to contribute to the development of `ncpu`, please refer to our contribution guidelines (link to guidelines).
## License
`ncpu` is released under the [LICENSE_NAME](LINK_TO_LICENSE). Please refer to the license file for more details.
-13
View File
@@ -1,13 +0,0 @@
package main
import (
"fmt"
"runtime"
"go.uber.org/automaxprocs/maxprocs"
)
func main() {
maxprocs.Set()
fmt.Print(runtime.GOMAXPROCS(0))
}
+27
View File
@@ -0,0 +1,27 @@
// 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))
}
+35
View File
@@ -0,0 +1,35 @@
// 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()
})
}
}
+10
View File
@@ -0,0 +1,10 @@
# See the OWNERS docs at https://go.k8s.io/owners
reviewers:
- cubxxw
- kubbot
approvers:
- cubxxw
labels:
- sig/testing
- sig/contributor-experience
+8
View File
@@ -0,0 +1,8 @@
module github.com/OpenIMSDK/Open-IM-Server/tools/yamlfmt
go 1.20
require (
github.com/likexian/gokit v0.25.13
gopkg.in/yaml.v3 v3.0.1
)
+6
View File
@@ -0,0 +1,6 @@
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=
+72
View File
@@ -0,0 +1,72 @@
// 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()
}
+158
View File
@@ -0,0 +1,158 @@
// 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{
&yaml.Node{
Kind: yaml.ScalarNode,
Tag: "!!str",
Value: "key",
},
&yaml.Node{
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{
&yaml.Node{
Kind: yaml.ScalarNode,
Tag: "!!str",
Value: "key",
},
&yaml.Node{
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)
}
})
}
}