mirror of
https://github.com/TECHNOFAB11/powerproto.git
synced 2026-02-02 09:25:07 +01:00
init
Signed-off-by: storyicon <yuanchao@bilibili.com>
This commit is contained in:
commit
9aac714c32
47 changed files with 5480 additions and 0 deletions
109
pkg/util/command/command.go
Normal file
109
pkg/util/command/command.go
Normal file
|
|
@ -0,0 +1,109 @@
|
|||
// Copyright 2021 storyicon@foxmail.com
|
||||
//
|
||||
// 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 command
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"fmt"
|
||||
"os"
|
||||
"os/exec"
|
||||
|
||||
"github.com/storyicon/powerproto/pkg/util"
|
||||
"github.com/storyicon/powerproto/pkg/util/logger"
|
||||
)
|
||||
|
||||
type dryRun struct{}
|
||||
type ignoreDryRun struct{}
|
||||
type disableAction struct{}
|
||||
|
||||
// WithIgnoreDryRun is used to inject ignore dryRun flag into context
|
||||
func WithIgnoreDryRun(ctx context.Context) context.Context {
|
||||
return context.WithValue(ctx, ignoreDryRun{}, "true")
|
||||
}
|
||||
|
||||
// WithDisableAction is used to disable post action/shell
|
||||
func WithDisableAction(ctx context.Context) context.Context {
|
||||
return context.WithValue(ctx, disableAction{}, "true")
|
||||
}
|
||||
|
||||
// WithDryRun is used to inject dryRun flag into context
|
||||
func WithDryRun(ctx context.Context) context.Context {
|
||||
return context.WithValue(ctx, dryRun{}, "true")
|
||||
}
|
||||
|
||||
// IsDisableAction is used to decide whether to disable PostAction and PostShell
|
||||
func IsDisableAction(ctx context.Context) bool {
|
||||
return ctx.Value(disableAction{}) != nil
|
||||
}
|
||||
|
||||
// IsIgnoreDryRun is used to determine whether it is currently in ignore DryRun mode
|
||||
func IsIgnoreDryRun(ctx context.Context) bool {
|
||||
return ctx.Value(ignoreDryRun{}) != nil
|
||||
}
|
||||
|
||||
// IsDryRun is used to determine whether it is currently in DryRun mode
|
||||
func IsDryRun(ctx context.Context) bool {
|
||||
return ctx.Value(dryRun{}) != nil
|
||||
}
|
||||
|
||||
// Execute is used to execute commands, return stdout and execute errors
|
||||
func Execute(ctx context.Context,
|
||||
log logger.Logger,
|
||||
dir string, name string, arguments []string, env []string) ([]byte, *ErrCommandExec) {
|
||||
cmd := exec.CommandContext(ctx, name, arguments...)
|
||||
cmd.Env = append(os.Environ(), env...)
|
||||
cmd.Dir = dir
|
||||
|
||||
if IsDryRun(ctx) && !IsIgnoreDryRun(ctx) {
|
||||
log.LogInfo(map[string]interface{}{
|
||||
"command": cmd.String(),
|
||||
"dir": cmd.Dir,
|
||||
}, "DryRun")
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
var stdout, stderr bytes.Buffer
|
||||
cmd.Stdout = &stdout
|
||||
cmd.Stderr = &stderr
|
||||
if err := cmd.Run(); err != nil {
|
||||
return nil, &ErrCommandExec{
|
||||
Err: err,
|
||||
Dir: cmd.Dir,
|
||||
Command: cmd.String(),
|
||||
ExitCode: util.GetExitCode(err),
|
||||
Stdout: stdout.String(),
|
||||
Stderr: stderr.String(),
|
||||
}
|
||||
}
|
||||
return stdout.Bytes(), nil
|
||||
}
|
||||
|
||||
// ErrCommandExec defines the command error
|
||||
type ErrCommandExec struct {
|
||||
Err error
|
||||
Dir string
|
||||
Command string
|
||||
ExitCode int
|
||||
Stdout string
|
||||
Stderr string
|
||||
}
|
||||
|
||||
// Error implements the error interface
|
||||
func (err *ErrCommandExec) Error() string {
|
||||
return fmt.Sprintf("failed to execute %s in %s, stderr: %s, exit code %d, %s",
|
||||
err.Command, err.Dir, err.Stderr, err.ExitCode, err.Err,
|
||||
)
|
||||
}
|
||||
27
pkg/util/command/command_suite_test.go
Normal file
27
pkg/util/command/command_suite_test.go
Normal file
|
|
@ -0,0 +1,27 @@
|
|||
// Copyright 2021 storyicon@foxmail.com
|
||||
//
|
||||
// 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 command_test
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
. "github.com/onsi/ginkgo"
|
||||
. "github.com/onsi/gomega"
|
||||
)
|
||||
|
||||
func TestCommand(t *testing.T) {
|
||||
RegisterFailHandler(Fail)
|
||||
RunSpecs(t, "Command Suite")
|
||||
}
|
||||
38
pkg/util/command/command_test.go
Normal file
38
pkg/util/command/command_test.go
Normal file
|
|
@ -0,0 +1,38 @@
|
|||
// Copyright 2021 storyicon@foxmail.com
|
||||
//
|
||||
// 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 command_test
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
. "github.com/onsi/ginkgo"
|
||||
. "github.com/onsi/gomega"
|
||||
|
||||
"github.com/storyicon/powerproto/pkg/util/command"
|
||||
)
|
||||
|
||||
var _ = Describe("Command", func() {
|
||||
ctx := context.Background()
|
||||
It("should able to dryRun", func() {
|
||||
Expect(command.IsDryRun(ctx)).To(BeFalse())
|
||||
ctx = command.WithDryRun(ctx)
|
||||
Expect(command.IsDryRun(ctx)).To(BeTrue())
|
||||
})
|
||||
It("should able to ignore dryRun", func() {
|
||||
Expect(command.IsIgnoreDryRun(ctx)).To(BeFalse())
|
||||
ctx = command.WithIgnoreDryRun(ctx)
|
||||
Expect(command.IsIgnoreDryRun(ctx)).To(BeTrue())
|
||||
})
|
||||
})
|
||||
38
pkg/util/concurrent/buffer.go
Normal file
38
pkg/util/concurrent/buffer.go
Normal file
|
|
@ -0,0 +1,38 @@
|
|||
// Copyright 2021 storyicon@foxmail.com
|
||||
//
|
||||
// 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 concurrent
|
||||
|
||||
import (
|
||||
"io"
|
||||
"sync"
|
||||
)
|
||||
|
||||
// Buffer is goroutine safety buffer
|
||||
type Buffer struct {
|
||||
writer io.Writer
|
||||
mutex sync.Mutex
|
||||
}
|
||||
|
||||
// Write implements the io.Writer
|
||||
func (s *Buffer) Write(p []byte) (n int, err error) {
|
||||
s.mutex.Lock()
|
||||
defer s.mutex.Unlock()
|
||||
return s.writer.Write(p)
|
||||
}
|
||||
|
||||
// NewBuffer is used to create a new buffer
|
||||
func NewBuffer(writer io.Writer) io.Writer {
|
||||
return &Buffer{writer: writer}
|
||||
}
|
||||
72
pkg/util/concurrent/errgroup.go
Normal file
72
pkg/util/concurrent/errgroup.go
Normal file
|
|
@ -0,0 +1,72 @@
|
|||
// Copyright 2021 storyicon@foxmail.com
|
||||
//
|
||||
// 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 concurrent
|
||||
|
||||
import (
|
||||
"context"
|
||||
"sync"
|
||||
)
|
||||
|
||||
// ErrGroup is another ErrGroup implement
|
||||
type ErrGroup struct {
|
||||
ctx context.Context
|
||||
cancel context.CancelFunc
|
||||
limit chan struct{}
|
||||
|
||||
errOnce sync.Once
|
||||
err error
|
||||
|
||||
wg sync.WaitGroup
|
||||
}
|
||||
|
||||
// NewErrGroup is used to create a new ErrGroup
|
||||
func NewErrGroup(ctx context.Context, concurrency int) *ErrGroup {
|
||||
ctx, cancel := context.WithCancel(ctx)
|
||||
return &ErrGroup{
|
||||
ctx: ctx,
|
||||
cancel: cancel,
|
||||
limit: make(chan struct{}, concurrency),
|
||||
}
|
||||
}
|
||||
|
||||
// Wait is used to wait ErrGroup finish
|
||||
func (g *ErrGroup) Wait() error {
|
||||
g.wg.Wait()
|
||||
g.cancel()
|
||||
return g.err
|
||||
}
|
||||
|
||||
// Go is used to start a new goroutine
|
||||
func (g *ErrGroup) Go(f func(ctx context.Context) error) {
|
||||
if g.err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
g.limit <- struct{}{}
|
||||
g.wg.Add(1)
|
||||
go func() {
|
||||
defer func() {
|
||||
<-g.limit
|
||||
g.wg.Done()
|
||||
}()
|
||||
if err := f(g.ctx); err != nil {
|
||||
g.cancel()
|
||||
g.errOnce.Do(func() {
|
||||
g.err = err
|
||||
g.cancel()
|
||||
})
|
||||
}
|
||||
}()
|
||||
}
|
||||
131
pkg/util/file.go
Normal file
131
pkg/util/file.go
Normal file
|
|
@ -0,0 +1,131 @@
|
|||
// Copyright 2021 storyicon@foxmail.com
|
||||
//
|
||||
// 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 util
|
||||
|
||||
import (
|
||||
"io"
|
||||
"io/fs"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"path/filepath"
|
||||
|
||||
"github.com/bmatcuk/doublestar"
|
||||
filecopy "github.com/otiai10/copy"
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
// MatchPath is used to match path with specified pattern
|
||||
func MatchPath(pattern string, path string) (bool, error) {
|
||||
return doublestar.PathMatch(pattern, path)
|
||||
}
|
||||
|
||||
// CopyDirectory is used to copy directory
|
||||
// If dst already exists, it will be merged
|
||||
func CopyDirectory(src, dst string) error {
|
||||
return filecopy.Copy(src, dst)
|
||||
}
|
||||
|
||||
// CopyFile is used to copy file from src to dst
|
||||
func CopyFile(src, dst string) error {
|
||||
sourceFileStat, err := os.Stat(src)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if !sourceFileStat.Mode().IsRegular() {
|
||||
return errors.Errorf("%s is not a regular file", src)
|
||||
}
|
||||
source, err := os.Open(src)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer source.Close()
|
||||
if err := os.MkdirAll(filepath.Dir(dst), fs.ModePerm); err != nil {
|
||||
return err
|
||||
}
|
||||
destination, err := os.Create(dst)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer destination.Close()
|
||||
_, err = io.Copy(destination, source)
|
||||
return err
|
||||
}
|
||||
|
||||
// IsFileExists is used to check whether the file exists
|
||||
func IsFileExists(path string) (bool, error) {
|
||||
info, err := os.Stat(path)
|
||||
if err != nil {
|
||||
if os.IsNotExist(err) {
|
||||
return false, nil
|
||||
}
|
||||
return false, err
|
||||
}
|
||||
if info.IsDir() {
|
||||
return false, errors.Errorf("%s is not a file", path)
|
||||
}
|
||||
return true, nil
|
||||
}
|
||||
|
||||
// IsDirExists is used to check whether the dir exists
|
||||
func IsDirExists(path string) (bool, error) {
|
||||
info, err := os.Stat(path)
|
||||
if err != nil {
|
||||
if os.IsNotExist(err) {
|
||||
return false, nil
|
||||
}
|
||||
return false, err
|
||||
}
|
||||
if !info.IsDir() {
|
||||
return false, errors.Errorf("%s is not a directory", path)
|
||||
}
|
||||
return true, nil
|
||||
}
|
||||
|
||||
// GetFilesWithExtRecursively is used to recursively list files with a specific suffix
|
||||
// expectExt should contain the prefix '.'
|
||||
func GetFilesWithExtRecursively(target string, targetExt string) ([]string, error) {
|
||||
var data []string
|
||||
err := filepath.Walk(target, func(path string, info fs.FileInfo, err error) error {
|
||||
if info.IsDir() {
|
||||
return nil
|
||||
}
|
||||
ext := filepath.Ext(path)
|
||||
if ext == targetExt {
|
||||
data = append(data, path)
|
||||
}
|
||||
return nil
|
||||
})
|
||||
return data, err
|
||||
}
|
||||
|
||||
// GetFilesWithExtRecursively is used to list files with a specific suffix
|
||||
// expectExt should contain the prefix '.'
|
||||
func GetFilesWithExt(dir string, targetExt string) ([]string, error) {
|
||||
children, err := ioutil.ReadDir(dir)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
var data []string
|
||||
for _, child := range children {
|
||||
if child.IsDir() {
|
||||
continue
|
||||
}
|
||||
if ext := filepath.Ext(child.Name()); ext != targetExt {
|
||||
continue
|
||||
}
|
||||
data = append(data, filepath.Join(dir, child.Name()))
|
||||
}
|
||||
return data, nil
|
||||
}
|
||||
217
pkg/util/logger/logger.go
Normal file
217
pkg/util/logger/logger.go
Normal file
|
|
@ -0,0 +1,217 @@
|
|||
// Copyright 2021 storyicon@foxmail.com
|
||||
//
|
||||
// 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 logger
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
"sort"
|
||||
"strings"
|
||||
|
||||
"github.com/fatih/color"
|
||||
jsoniter "github.com/json-iterator/go"
|
||||
"github.com/prometheus/client_golang/prometheus"
|
||||
)
|
||||
|
||||
// Logger defines the basic log library implementation
|
||||
type Logger interface {
|
||||
// LogDebug print a message with debug level.
|
||||
LogDebug(fields map[string]interface{}, format string, args ...interface{})
|
||||
// LogInfo print a message with info level.
|
||||
LogInfo(fields map[string]interface{}, format string, args ...interface{})
|
||||
// LogWarn print a message with warn level.
|
||||
LogWarn(fields map[string]interface{}, format string, args ...interface{})
|
||||
// LogError print a message with error level.
|
||||
LogError(fields map[string]interface{}, format string, args ...interface{})
|
||||
// LogFatal print a message with fatal level.
|
||||
LogFatal(fields map[string]interface{}, format string, args ...interface{})
|
||||
// NewLogger is used to derive a new child Logger
|
||||
NewLogger(component string) Logger
|
||||
// SetLogLevel is used to set log level
|
||||
SetLogLevel(level Level) Logger
|
||||
}
|
||||
|
||||
// BasicLogger simply implements Logger
|
||||
type BasicLogger struct {
|
||||
cfg *Config
|
||||
|
||||
component string
|
||||
registerer prometheus.Registerer
|
||||
}
|
||||
|
||||
// Config defines the config structure
|
||||
type Config struct {
|
||||
Pretty bool
|
||||
Level Level
|
||||
}
|
||||
|
||||
type Level struct {
|
||||
Name string
|
||||
Color color.Attribute
|
||||
Index int
|
||||
Writer io.Writer
|
||||
}
|
||||
|
||||
var (
|
||||
LevelDebug = Level{Name: "debug", Color: color.FgWhite, Index: 0, Writer: os.Stdout}
|
||||
LevelInfo = Level{Name: "info", Color: color.FgWhite, Index: 1, Writer: os.Stdout}
|
||||
LevelWarn = Level{Name: "warn", Color: color.FgYellow, Index: 2, Writer: os.Stderr}
|
||||
LevelError = Level{Name: "error", Color: color.FgHiRed, Index: 3, Writer: os.Stderr}
|
||||
LevelFatal = Level{Name: "fatal", Color: color.FgRed, Index: 4, Writer: os.Stderr}
|
||||
)
|
||||
|
||||
// NewConfig is used to init config with default values
|
||||
func NewConfig() *Config {
|
||||
return &Config{
|
||||
Pretty: false,
|
||||
Level: LevelDebug,
|
||||
}
|
||||
}
|
||||
|
||||
// NewDefault is used to initialize a simple Logger
|
||||
func NewDefault(component string) Logger {
|
||||
logger, err := New(NewConfig(), component, prometheus.DefaultRegisterer)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return logger
|
||||
}
|
||||
|
||||
// New is used to init service
|
||||
func New(cfg *Config, component string, registerer prometheus.Registerer) (Logger, error) {
|
||||
if cfg == nil {
|
||||
cfg = NewConfig()
|
||||
}
|
||||
service := &BasicLogger{
|
||||
cfg: cfg,
|
||||
component: component,
|
||||
registerer: registerer,
|
||||
}
|
||||
return service, nil
|
||||
}
|
||||
|
||||
// LogDebug print a message with debug level.
|
||||
func (b *BasicLogger) LogDebug(fields map[string]interface{}, format string, args ...interface{}) {
|
||||
b.log(LevelDebug, fields, format, args...)
|
||||
}
|
||||
|
||||
// LogInfo print a message with info level.
|
||||
func (b *BasicLogger) LogInfo(fields map[string]interface{}, format string, args ...interface{}) {
|
||||
b.log(LevelInfo, fields, format, args...)
|
||||
}
|
||||
|
||||
// LogWarn print a message with warn level.
|
||||
func (b *BasicLogger) LogWarn(fields map[string]interface{}, format string, args ...interface{}) {
|
||||
b.log(LevelWarn, fields, format, args...)
|
||||
}
|
||||
|
||||
// LogError print a message with error level.
|
||||
func (b *BasicLogger) LogError(fields map[string]interface{}, format string, args ...interface{}) {
|
||||
b.log(LevelError, fields, format, args...)
|
||||
}
|
||||
|
||||
// LogFatal print a message with fatal level.
|
||||
func (b *BasicLogger) LogFatal(fields map[string]interface{}, format string, args ...interface{}) {
|
||||
b.log(LevelFatal, fields, format, args...)
|
||||
}
|
||||
|
||||
// NewLogger is used to derive a new child Logger
|
||||
func (b *BasicLogger) NewLogger(component string) Logger {
|
||||
name := strings.Join([]string{b.component, component}, ".")
|
||||
logger, err := New(b.cfg, name, b.registerer)
|
||||
if err != nil {
|
||||
b.LogWarn(map[string]interface{}{
|
||||
"name": name,
|
||||
}, "failed to extend logger: %s", err)
|
||||
return b
|
||||
}
|
||||
return logger
|
||||
}
|
||||
|
||||
// SetLogLevel is used to set log level
|
||||
func (b *BasicLogger) SetLogLevel(level Level) Logger {
|
||||
b.cfg.Level = level
|
||||
return b
|
||||
}
|
||||
|
||||
func (b *BasicLogger) log(level Level, fields map[string]interface{}, format string, args ...interface{}) {
|
||||
if b.cfg.Level.Index > level.Index {
|
||||
return
|
||||
}
|
||||
if fields == nil {
|
||||
fields = map[string]interface{}{}
|
||||
}
|
||||
// if b.cfg.Level == LevelDebug {
|
||||
// if _, file, line, ok := runtime.Caller(4); ok {
|
||||
// fields["file"] = fmt.Sprintf("%s:%d", path.Base(file), line)
|
||||
// }
|
||||
// if b.component != "" {
|
||||
// fields["component"] = b.component
|
||||
// }
|
||||
// }
|
||||
if b.cfg.Pretty {
|
||||
dict := map[string]interface{}{}
|
||||
for key, val := range fields {
|
||||
dict[key] = val
|
||||
}
|
||||
dict["level"] = level.Name
|
||||
dict["message"] = fmt.Sprintf(format, args...)
|
||||
_ = jsoniter.NewEncoder(level.Writer).Encode(dict)
|
||||
return
|
||||
}
|
||||
var buf []byte
|
||||
buf = appendString(buf, fmt.Sprintf(format, args...))
|
||||
if len(fields) > 0 {
|
||||
buf = appendTab(buf)
|
||||
buf = appendFields(buf, fields)
|
||||
}
|
||||
buf = appendLF(buf)
|
||||
fmt.Fprintf(level.Writer, color.New(level.Color).Sprint(string(buf)))
|
||||
if level == LevelFatal {
|
||||
os.Exit(1)
|
||||
}
|
||||
}
|
||||
|
||||
func appendFields(dst []byte, fields map[string]interface{}) []byte {
|
||||
keys := make([]string, 0, len(fields))
|
||||
for key := range fields {
|
||||
keys = append(keys, key)
|
||||
}
|
||||
sort.Strings(keys)
|
||||
for _, key := range keys {
|
||||
dst = appendField(dst, key, fields[key])
|
||||
dst = appendTab(dst)
|
||||
}
|
||||
return dst
|
||||
}
|
||||
|
||||
func appendTab(dst []byte) []byte {
|
||||
return append(dst, ' ')
|
||||
}
|
||||
|
||||
func appendField(dst []byte, key string, val interface{}) []byte {
|
||||
dst = appendString(dst, fmt.Sprintf("%s=%v", key, val))
|
||||
return dst
|
||||
}
|
||||
|
||||
func appendString(dst []byte, s string) []byte {
|
||||
dst = append(dst, []byte(s)...)
|
||||
return dst
|
||||
}
|
||||
|
||||
func appendLF(dst []byte) []byte {
|
||||
return append(dst, '\n')
|
||||
}
|
||||
123
pkg/util/progressbar/progressbar.go
Normal file
123
pkg/util/progressbar/progressbar.go
Normal file
|
|
@ -0,0 +1,123 @@
|
|||
// Copyright 2021 storyicon@foxmail.com
|
||||
//
|
||||
// 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 progressbar
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/fatih/color"
|
||||
"github.com/vbauerster/mpb/v7"
|
||||
"github.com/vbauerster/mpb/v7/decor"
|
||||
)
|
||||
|
||||
// ProgressBar implements a customizable progress bar
|
||||
type ProgressBar interface {
|
||||
// Incr is used to increase progress
|
||||
Incr()
|
||||
// Wait is used to wait for the rendering of the progress bar to complete
|
||||
Wait()
|
||||
// SetPrefix is used to set the prefix of progress bar
|
||||
SetPrefix(format string, args ...interface{})
|
||||
// SetSuffix is used to set the suffix of progress bar
|
||||
SetSuffix(format string, args ...interface{})
|
||||
}
|
||||
|
||||
type progressBar struct {
|
||||
container *mpb.Progress
|
||||
bar *mpb.Bar
|
||||
prefix string
|
||||
suffix string
|
||||
}
|
||||
|
||||
// SetPrefix is used to set the prefix of progress bar
|
||||
func (s *progressBar) SetPrefix(format string, args ...interface{}) {
|
||||
s.prefix = fmt.Sprintf(format, args...)
|
||||
}
|
||||
|
||||
// SetSuffix is used to set the suffix of progress bar
|
||||
func (s *progressBar) SetSuffix(format string, args ...interface{}) {
|
||||
s.suffix = fmt.Sprintf(format, args...)
|
||||
}
|
||||
|
||||
func newEmbedProgressBar(container *mpb.Progress, bar *mpb.Bar) *progressBar {
|
||||
return &progressBar{
|
||||
container: container,
|
||||
bar: bar,
|
||||
}
|
||||
}
|
||||
|
||||
func (s *progressBar) Incr() {
|
||||
s.bar.Increment()
|
||||
}
|
||||
|
||||
func (s *progressBar) Wait() {
|
||||
s.container.Wait()
|
||||
}
|
||||
|
||||
func getSpinner() []string {
|
||||
activeState := "[ " + color.GreenString("●") + " ] "
|
||||
defaultState := "[ ] "
|
||||
return []string{
|
||||
activeState,
|
||||
activeState,
|
||||
activeState,
|
||||
defaultState,
|
||||
defaultState,
|
||||
defaultState,
|
||||
}
|
||||
}
|
||||
|
||||
// GetProgressBar is used to get progress bar
|
||||
func GetProgressBar(count int) ProgressBar {
|
||||
var progressBar *progressBar
|
||||
container := mpb.New()
|
||||
bar := container.Add(int64(count),
|
||||
mpb.NewBarFiller(mpb.BarStyle().Lbound("[").
|
||||
Filler(color.GreenString("=")).
|
||||
Tip(color.GreenString(">")).Padding(" ").Rbound("]")),
|
||||
mpb.PrependDecorators(
|
||||
func() decor.Decorator {
|
||||
frames := getSpinner()
|
||||
var count uint
|
||||
return decor.Any(func(statistics decor.Statistics) string {
|
||||
if statistics.Completed {
|
||||
return frames[0]
|
||||
}
|
||||
frame := frames[count%uint(len(frames))]
|
||||
count++
|
||||
return frame
|
||||
})
|
||||
}(),
|
||||
decor.Any(func(statistics decor.Statistics) string {
|
||||
if progressBar != nil {
|
||||
return progressBar.prefix
|
||||
}
|
||||
return ""
|
||||
}),
|
||||
),
|
||||
mpb.AppendDecorators(
|
||||
decor.NewPercentage("%d "),
|
||||
decor.Any(func(statistics decor.Statistics) string {
|
||||
if progressBar != nil {
|
||||
return fmt.Sprintf("(%d/%d) %s", statistics.Current, count, progressBar.suffix)
|
||||
}
|
||||
return ""
|
||||
}),
|
||||
),
|
||||
mpb.BarWidth(15),
|
||||
)
|
||||
progressBar = newEmbedProgressBar(container, bar)
|
||||
return progressBar
|
||||
}
|
||||
106
pkg/util/util.go
Normal file
106
pkg/util/util.go
Normal file
|
|
@ -0,0 +1,106 @@
|
|||
// Copyright 2021 storyicon@foxmail.com
|
||||
//
|
||||
// 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 util
|
||||
|
||||
import (
|
||||
"os"
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
"regexp"
|
||||
"runtime"
|
||||
"strings"
|
||||
"syscall"
|
||||
)
|
||||
|
||||
// ContainsEmpty is used to check whether items contains empty string
|
||||
func ContainsEmpty(items ...string) bool {
|
||||
for _, item := range items {
|
||||
if item == "" {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// SetToSlice is used to convert set<string> to slice<string>
|
||||
func SetToSlice(set map[string]struct{}) []string {
|
||||
data := make([]string, 0, len(set))
|
||||
for key := range set {
|
||||
data = append(data, key)
|
||||
}
|
||||
return data
|
||||
}
|
||||
|
||||
// GetMapKeys is used to get the keys of map
|
||||
func GetMapKeys(dict map[string]string) []string {
|
||||
data := make([]string, 0, len(dict))
|
||||
for key := range dict {
|
||||
data = append(data, key)
|
||||
}
|
||||
return data
|
||||
}
|
||||
|
||||
// GetExitCode is used to parse exit code from cmd error
|
||||
func GetExitCode(err error) int {
|
||||
if exitErr, ok := err.(*exec.ExitError); ok {
|
||||
// The program has exited with an exit code != 0
|
||||
// This works on both Unix and Windows. Although package
|
||||
// syscall is generally platform dependent, WaitStatus is
|
||||
// defined for both Unix and Windows and in both cases has
|
||||
// an ExitStatus() method with the same signature.
|
||||
if status, ok := exitErr.Sys().(syscall.WaitStatus); ok {
|
||||
return status.ExitStatus()
|
||||
}
|
||||
}
|
||||
return 1
|
||||
}
|
||||
|
||||
var regexpEnvironmentVar = regexp.MustCompile(`\$[A-Za-z_]+`)
|
||||
|
||||
// RenderPathWithEnv is used to render path with environment
|
||||
func RenderPathWithEnv(path string) string {
|
||||
matches := regexpEnvironmentVar.FindAllString(path, -1)
|
||||
for _, match := range matches {
|
||||
path = strings.ReplaceAll(path, match, os.Getenv(match[1:]))
|
||||
}
|
||||
return filepath.Clean(path)
|
||||
}
|
||||
|
||||
// SplitGoPackageVersion is used to split go package version
|
||||
func SplitGoPackageVersion(pkg string) (path string, version string, ok bool) {
|
||||
i := strings.Index(pkg, "@")
|
||||
if i == -1 {
|
||||
return "", "", false
|
||||
}
|
||||
return pkg[:i], pkg[i+1:], true
|
||||
}
|
||||
|
||||
// JoinGoPackageVersion is used to join go path and versions
|
||||
func JoinGoPackageVersion(path, version string) string {
|
||||
return strings.Join([]string{
|
||||
path, version,
|
||||
}, "@")
|
||||
}
|
||||
|
||||
// GetBinaryFileName is used to get os based binary file name
|
||||
func GetBinaryFileName(name string) string {
|
||||
if runtime.GOOS == "windows" {
|
||||
if !strings.HasSuffix(name, ".exe") {
|
||||
return name + ".exe"
|
||||
}
|
||||
return name
|
||||
}
|
||||
return name
|
||||
}
|
||||
74
pkg/util/yaml.go
Normal file
74
pkg/util/yaml.go
Normal file
|
|
@ -0,0 +1,74 @@
|
|||
// Copyright 2021 storyicon@foxmail.com
|
||||
//
|
||||
// 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 util
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
|
||||
"github.com/hashicorp/go-multierror"
|
||||
"gopkg.in/yaml.v2"
|
||||
)
|
||||
|
||||
// SplitYAML is used to split yaml
|
||||
func SplitYAML(data []byte) ([][]byte, error) {
|
||||
decoder := yaml.NewDecoder(bytes.NewReader(data))
|
||||
var parts [][]byte
|
||||
for {
|
||||
var value interface{}
|
||||
err := decoder.Decode(&value)
|
||||
if err == io.EOF {
|
||||
break
|
||||
}
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
part, err := yaml.Marshal(value)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
parts = append(parts, part)
|
||||
}
|
||||
return parts, nil
|
||||
}
|
||||
|
||||
// DumpYaml is used to dump yaml into stdout
|
||||
func DumpYaml(cfg interface{}) {
|
||||
out, err := yaml.Marshal(cfg)
|
||||
if err != nil {
|
||||
fmt.Fprintln(os.Stderr, err)
|
||||
} else {
|
||||
fmt.Printf("%s\n", out)
|
||||
}
|
||||
}
|
||||
|
||||
// LoadConfig read YAML-formatted config from filename into cfg.
|
||||
func LoadConfig(filename string, pointer interface{}) error {
|
||||
buf, err := ioutil.ReadFile(filename)
|
||||
if err != nil {
|
||||
return multierror.Prefix(err, "Error reading config file")
|
||||
}
|
||||
|
||||
err = yaml.UnmarshalStrict(buf, pointer)
|
||||
if err != nil {
|
||||
return multierror.Prefix(err, "Error parsing config file")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
Loading…
Add table
Add a link
Reference in a new issue