mirror of https://github.com/argoproj/pkg.git
Compare commits
No commits in common. "master" and "v0.13.5" have entirely different histories.
|
@ -13,7 +13,7 @@ jobs:
|
|||
steps:
|
||||
- name: Dependabot metadata
|
||||
id: metadata
|
||||
uses: dependabot/fetch-metadata@v2.4.0
|
||||
uses: dependabot/fetch-metadata@v1.3.1
|
||||
with:
|
||||
github-token: "${{ secrets.GITHUB_TOKEN }}"
|
||||
- name: Approve PR
|
||||
|
|
|
@ -11,12 +11,12 @@ jobs:
|
|||
build:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/checkout@v3
|
||||
|
||||
- name: Set up Go
|
||||
uses: actions/setup-go@v5
|
||||
uses: actions/setup-go@v3
|
||||
with:
|
||||
go-version: 1.23
|
||||
go-version: 1.15
|
||||
|
||||
- name: Build
|
||||
run: |
|
||||
|
|
|
@ -12,15 +12,12 @@ jobs:
|
|||
name: lint
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/setup-go@v5
|
||||
with:
|
||||
go-version: 1.23
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/checkout@v3
|
||||
- name: golangci-lint
|
||||
uses: golangci/golangci-lint-action@v7
|
||||
uses: golangci/golangci-lint-action@v3
|
||||
with:
|
||||
# Optional: version of golangci-lint to use in form of v1.2 or v1.2.3 or `latest` to use the latest version
|
||||
version: v2.0.2
|
||||
version: v1.36.0
|
||||
|
||||
# Optional: working directory, useful for monorepos
|
||||
# working-directory: somedir
|
||||
|
@ -38,4 +35,4 @@ jobs:
|
|||
# skip-pkg-cache: true
|
||||
|
||||
# Optional: if set to true then the action don't cache or restore ~/.cache/go-build.
|
||||
# skip-build-cache: true
|
||||
# skip-build-cache: true
|
|
@ -1,32 +1,9 @@
|
|||
version: "2"
|
||||
# https://golangci-lint.run/usage/quick-start/
|
||||
run:
|
||||
concurrency: 4
|
||||
linters:
|
||||
enable:
|
||||
- testifylint
|
||||
settings:
|
||||
testifylint:
|
||||
enable-all: true
|
||||
disable:
|
||||
- float-compare
|
||||
exclusions:
|
||||
presets:
|
||||
- comments
|
||||
- common-false-positives
|
||||
- legacy
|
||||
- std-error-handling
|
||||
paths:
|
||||
- vendor
|
||||
- third_party$
|
||||
- builtin$
|
||||
- examples$
|
||||
formatters:
|
||||
settings:
|
||||
goimports:
|
||||
local-prefixes:
|
||||
- github.com/argoproj/pkg/v2
|
||||
exclusions:
|
||||
paths:
|
||||
- third_party$
|
||||
- builtin$
|
||||
- examples$
|
||||
timeout: 5m
|
||||
skip-dirs:
|
||||
- vendor
|
||||
linters-settings:
|
||||
goimports:
|
||||
local-prefixes: github.com/argoproj/pkg
|
||||
|
|
2
Makefile
2
Makefile
|
@ -26,7 +26,7 @@ darwin-build:
|
|||
.PHONY: lint
|
||||
lint:
|
||||
go mod tidy
|
||||
GOGC=$(LINT_GOGC) golangci-lint run --fix --verbose --concurrency $(LINT_CONCURRENCY) --timeout $(LINT_DEADLINE)
|
||||
GOGC=$(LINT_GOGC) golangci-lint run --fix --verbose --concurrency $(LINT_CONCURRENCY) --deadline $(LINT_DEADLINE)
|
||||
|
||||
.PHONY: test
|
||||
test: lint
|
||||
|
|
|
@ -1,6 +0,0 @@
|
|||
# argoproj/pkg
|
||||
|
||||
This repository contains shared libraries and utilities for the Argo project. The packages are primarily maintained for
|
||||
use by the Argo projects, but they are open for use by anyone.
|
||||
|
||||
If your project uses `argoproj/pkg`, please add it to the [USERS.md](USERS.md) file.
|
9
USERS.md
9
USERS.md
|
@ -1,9 +0,0 @@
|
|||
## Who uses argoproj/pkg?
|
||||
|
||||
These are the projects that use `argoproj/pkg`. If you use one or more of these packages, add your project here!
|
||||
|
||||
| Project | grpc/http | kubeclientmetrics | stats | sync | time |
|
||||
|--------------------------------------------------------------|--------------------|--------------------|--------------------|--------------------|--------------------|
|
||||
| [Argo CD](https://github.com/argoproj/argo-cd) | :white_check_mark: | :white_check_mark: | :white_check_mark: | :white_check_mark: | :white_check_mark: |
|
||||
| [Argo Rollouts](https://github.com/argoproj/argo-rollouts) | :white_check_mark: | :white_check_mark: | | | |
|
||||
| [Argo Workflows](https://github.com/argoproj/argo-workflows) | :white_check_mark: | | :white_check_mark: | :white_check_mark: | :white_check_mark: |
|
|
@ -0,0 +1,25 @@
|
|||
package cli
|
||||
|
||||
import (
|
||||
"flag"
|
||||
"strconv"
|
||||
|
||||
log "github.com/sirupsen/logrus"
|
||||
"k8s.io/klog/v2"
|
||||
|
||||
"github.com/argoproj/pkg/errors"
|
||||
)
|
||||
|
||||
// SetLogLevel parses and sets a logrus log level
|
||||
func SetLogLevel(logLevel string) {
|
||||
level, err := log.ParseLevel(logLevel)
|
||||
errors.CheckError(err)
|
||||
log.SetLevel(level)
|
||||
}
|
||||
|
||||
// SetGLogLevel set the glog level for the k8s go-client
|
||||
func SetGLogLevel(glogLevel int) {
|
||||
klog.InitFlags(nil)
|
||||
_ = flag.Set("logtostderr", "true")
|
||||
_ = flag.Set("v", strconv.Itoa(glogLevel))
|
||||
}
|
|
@ -0,0 +1,55 @@
|
|||
package env
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"strconv"
|
||||
"time"
|
||||
)
|
||||
|
||||
func LookupEnvStringOr(key string, o string) string {
|
||||
v, found := os.LookupEnv(key)
|
||||
if found {
|
||||
return v
|
||||
}
|
||||
return o
|
||||
}
|
||||
|
||||
func LookupEnvDurationOr(key string, o time.Duration) time.Duration {
|
||||
v, found := os.LookupEnv(key)
|
||||
if found {
|
||||
d, err := time.ParseDuration(v)
|
||||
if err != nil {
|
||||
panic(fmt.Errorf("%s=%s: failed to convert to duration", key, v))
|
||||
} else {
|
||||
return d
|
||||
}
|
||||
}
|
||||
return o
|
||||
}
|
||||
|
||||
func LookupEnvIntOr(key string, o int) int {
|
||||
v, found := os.LookupEnv(key)
|
||||
if found {
|
||||
d, err := strconv.Atoi(v)
|
||||
if err != nil {
|
||||
panic(fmt.Errorf("%s=%s: failed to convert to int", key, v))
|
||||
} else {
|
||||
return d
|
||||
}
|
||||
}
|
||||
return o
|
||||
}
|
||||
|
||||
func LookupEnvFloatOr(key string, o float64) float64 {
|
||||
v, found := os.LookupEnv(key)
|
||||
if found {
|
||||
d, err := strconv.ParseFloat(v, 64)
|
||||
if err != nil {
|
||||
panic(fmt.Errorf("%s=%s: failed to convert to float", key, v))
|
||||
} else {
|
||||
return d
|
||||
}
|
||||
}
|
||||
return o
|
||||
}
|
|
@ -0,0 +1,43 @@
|
|||
package env
|
||||
|
||||
import (
|
||||
"os"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestLookupEnvStringOr(t *testing.T) {
|
||||
defer func() { _ = os.Unsetenv("FOO") }()
|
||||
assert.Equal(t, "bar", LookupEnvStringOr("", "bar"), "default value")
|
||||
_ = os.Setenv("FOO", "baz")
|
||||
assert.Equal(t, "baz", LookupEnvStringOr("FOO", "bar"), "env var value")
|
||||
}
|
||||
|
||||
func TestLookupEnvDurationOr(t *testing.T) {
|
||||
defer func() { _ = os.Unsetenv("FOO") }()
|
||||
assert.Equal(t, time.Second, LookupEnvDurationOr("", time.Second), "default value")
|
||||
_ = os.Setenv("FOO", "bar")
|
||||
assert.Panics(t, func() { LookupEnvDurationOr("FOO", time.Second) }, "bad value")
|
||||
_ = os.Setenv("FOO", "1h")
|
||||
assert.Equal(t, time.Hour, LookupEnvDurationOr("FOO", time.Second), "env var value")
|
||||
}
|
||||
|
||||
func TestLookupEnvIntOr(t *testing.T) {
|
||||
defer func() { _ = os.Unsetenv("FOO") }()
|
||||
assert.Equal(t, 1, LookupEnvIntOr("", 1), "default value")
|
||||
_ = os.Setenv("FOO", "not-int")
|
||||
assert.Panics(t, func() { LookupEnvIntOr("FOO", 1) }, "bad value")
|
||||
_ = os.Setenv("FOO", "2")
|
||||
assert.Equal(t, 2, LookupEnvIntOr("FOO", 1), "env var value")
|
||||
}
|
||||
|
||||
func TestLookupEnvFloatOr(t *testing.T) {
|
||||
defer func() { _ = os.Unsetenv("FOO") }()
|
||||
assert.Equal(t, 1., LookupEnvFloatOr("", 1.), "default value")
|
||||
_ = os.Setenv("FOO", "not-float")
|
||||
assert.Panics(t, func() { LookupEnvFloatOr("FOO", 1.) }, "bad value")
|
||||
_ = os.Setenv("FOO", "2.0")
|
||||
assert.Equal(t, 2., LookupEnvFloatOr("FOO", 1.), "env var value")
|
||||
}
|
|
@ -0,0 +1,12 @@
|
|||
package errors
|
||||
|
||||
import (
|
||||
log "github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
// CheckError is a convenience function to fatally log an exit if the supplied error is non-nil
|
||||
func CheckError(err error) {
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
}
|
|
@ -0,0 +1,180 @@
|
|||
package exec
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"os"
|
||||
"os/exec"
|
||||
"runtime"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
log "github.com/sirupsen/logrus"
|
||||
|
||||
"github.com/argoproj/pkg/rand"
|
||||
)
|
||||
|
||||
var (
|
||||
ErrWaitPIDTimeout = fmt.Errorf("Timed out waiting for PID to complete")
|
||||
Unredacted = Redact(nil)
|
||||
)
|
||||
|
||||
type CmdError struct {
|
||||
Args string
|
||||
Stderr string
|
||||
Cause error
|
||||
}
|
||||
|
||||
func (ce *CmdError) Error() string {
|
||||
res := fmt.Sprintf("`%v` failed %v", ce.Args, ce.Cause)
|
||||
if ce.Stderr != "" {
|
||||
res = fmt.Sprintf("%s: %s", res, ce.Stderr)
|
||||
}
|
||||
return res
|
||||
}
|
||||
|
||||
func (ce *CmdError) String() string {
|
||||
return ce.Error()
|
||||
}
|
||||
|
||||
func newCmdError(args string, cause error, stderr string) *CmdError {
|
||||
return &CmdError{Args: args, Stderr: stderr, Cause: cause}
|
||||
}
|
||||
|
||||
type CmdOpts struct {
|
||||
Timeout time.Duration
|
||||
Redactor func(text string) string
|
||||
}
|
||||
|
||||
var DefaultCmdOpts = CmdOpts{
|
||||
Timeout: time.Duration(0),
|
||||
Redactor: Unredacted,
|
||||
}
|
||||
|
||||
func Redact(items []string) func(text string) string {
|
||||
return func(text string) string {
|
||||
for _, item := range items {
|
||||
text = strings.Replace(text, item, "******", -1)
|
||||
}
|
||||
return text
|
||||
}
|
||||
}
|
||||
|
||||
// RunCommandExt is a convenience function to run/log a command and return/log stderr in an error upon
|
||||
// failure.
|
||||
func RunCommandExt(cmd *exec.Cmd, opts CmdOpts) (string, error) {
|
||||
execId, err := rand.RandString(5)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
logCtx := log.WithFields(log.Fields{"execID": execId})
|
||||
|
||||
redactor := DefaultCmdOpts.Redactor
|
||||
if opts.Redactor != nil {
|
||||
redactor = opts.Redactor
|
||||
}
|
||||
|
||||
// log in a way we can copy-and-paste into a terminal
|
||||
args := strings.Join(cmd.Args, " ")
|
||||
logCtx.WithFields(log.Fields{"dir": cmd.Dir}).Info(redactor(args))
|
||||
|
||||
var stdout bytes.Buffer
|
||||
var stderr bytes.Buffer
|
||||
cmd.Stdout = &stdout
|
||||
cmd.Stderr = &stderr
|
||||
|
||||
start := time.Now()
|
||||
err = cmd.Start()
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
done := make(chan error)
|
||||
go func() { done <- cmd.Wait() }()
|
||||
|
||||
// Start a timer
|
||||
timeout := DefaultCmdOpts.Timeout
|
||||
|
||||
if opts.Timeout != time.Duration(0) {
|
||||
timeout = opts.Timeout
|
||||
}
|
||||
|
||||
var timoutCh <-chan time.Time
|
||||
if timeout != 0 {
|
||||
timoutCh = time.NewTimer(timeout).C
|
||||
}
|
||||
|
||||
select {
|
||||
// noinspection ALL
|
||||
case <-timoutCh:
|
||||
_ = cmd.Process.Kill()
|
||||
output := stdout.String()
|
||||
logCtx.WithFields(log.Fields{"duration": time.Since(start)}).Debug(redactor(output))
|
||||
err = newCmdError(redactor(args), fmt.Errorf("timeout after %v", timeout), "")
|
||||
logCtx.Error(err.Error())
|
||||
return strings.TrimSuffix(output, "\n"), err
|
||||
case err := <-done:
|
||||
if err != nil {
|
||||
output := stdout.String()
|
||||
logCtx.WithFields(log.Fields{"duration": time.Since(start)}).Debug(redactor(output))
|
||||
err := newCmdError(redactor(args), errors.New(redactor(err.Error())), strings.TrimSpace(redactor(stderr.String())))
|
||||
logCtx.Error(err.Error())
|
||||
return strings.TrimSuffix(output, "\n"), err
|
||||
}
|
||||
}
|
||||
|
||||
output := stdout.String()
|
||||
logCtx.WithFields(log.Fields{"duration": time.Since(start)}).Debug(redactor(output))
|
||||
|
||||
return strings.TrimSuffix(output, "\n"), nil
|
||||
}
|
||||
|
||||
func RunCommand(name string, opts CmdOpts, arg ...string) (string, error) {
|
||||
return RunCommandExt(exec.Command(name, arg...), opts)
|
||||
}
|
||||
|
||||
// WaitPIDOpts are options to WaitPID
|
||||
type WaitPIDOpts struct {
|
||||
PollInterval time.Duration
|
||||
Timeout time.Duration
|
||||
}
|
||||
|
||||
// WaitPID waits for a non-child process id to exit
|
||||
func WaitPID(pid int, opts ...WaitPIDOpts) error {
|
||||
if runtime.GOOS != "linux" {
|
||||
return errors.Errorf("Platform '%s' unsupported", runtime.GOOS)
|
||||
}
|
||||
var timeout time.Duration
|
||||
pollInterval := time.Second
|
||||
if len(opts) > 0 {
|
||||
if opts[0].PollInterval != 0 {
|
||||
pollInterval = opts[0].PollInterval
|
||||
}
|
||||
if opts[0].Timeout != 0 {
|
||||
timeout = opts[0].Timeout
|
||||
}
|
||||
}
|
||||
path := fmt.Sprintf("/proc/%d", pid)
|
||||
|
||||
ticker := time.NewTicker(pollInterval)
|
||||
defer ticker.Stop()
|
||||
var timoutCh <-chan time.Time
|
||||
if timeout != 0 {
|
||||
timoutCh = time.NewTimer(timeout).C
|
||||
}
|
||||
for {
|
||||
select {
|
||||
case <-ticker.C:
|
||||
_, err := os.Stat(path)
|
||||
if err != nil {
|
||||
if os.IsNotExist(err) {
|
||||
return nil
|
||||
}
|
||||
return errors.WithStack(err)
|
||||
}
|
||||
case <-timoutCh:
|
||||
return ErrWaitPIDTimeout
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,128 @@
|
|||
package exec
|
||||
|
||||
import (
|
||||
"os/exec"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
log "github.com/sirupsen/logrus"
|
||||
"github.com/sirupsen/logrus/hooks/test"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestRunCommand(t *testing.T) {
|
||||
hook := test.NewGlobal()
|
||||
log.SetLevel(log.DebugLevel)
|
||||
defer log.SetLevel(log.InfoLevel)
|
||||
|
||||
message, err := RunCommand("echo", CmdOpts{Redactor: Redact([]string{"world"})}, "hello world")
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, "hello world", message)
|
||||
|
||||
assert.Len(t, hook.Entries, 2)
|
||||
|
||||
entry := hook.Entries[0]
|
||||
assert.Equal(t, log.InfoLevel, entry.Level)
|
||||
assert.Equal(t, "echo hello ******", entry.Message)
|
||||
assert.Contains(t, entry.Data, "dir")
|
||||
assert.Contains(t, entry.Data, "execID")
|
||||
|
||||
entry = hook.Entries[1]
|
||||
assert.Equal(t, log.DebugLevel, entry.Level)
|
||||
assert.Equal(t, "hello ******\n", entry.Message)
|
||||
assert.Contains(t, entry.Data, "duration")
|
||||
assert.Contains(t, entry.Data, "execID")
|
||||
}
|
||||
|
||||
func TestRunCommandTimeout(t *testing.T) {
|
||||
hook := test.NewGlobal()
|
||||
log.SetLevel(log.DebugLevel)
|
||||
defer log.SetLevel(log.InfoLevel)
|
||||
|
||||
output, err := RunCommand("sh", CmdOpts{Timeout: 500 * time.Millisecond}, "-c", "echo hello world && echo my-error >&2 && sleep 2")
|
||||
assert.Equal(t, output, "hello world")
|
||||
assert.EqualError(t, err, "`sh -c echo hello world && echo my-error >&2 && sleep 2` failed timeout after 500ms")
|
||||
|
||||
assert.Len(t, hook.Entries, 3)
|
||||
|
||||
entry := hook.Entries[0]
|
||||
assert.Equal(t, log.InfoLevel, entry.Level)
|
||||
assert.Equal(t, "sh -c echo hello world && echo my-error >&2 && sleep 2", entry.Message)
|
||||
assert.Contains(t, entry.Data, "dir")
|
||||
assert.Contains(t, entry.Data, "execID")
|
||||
|
||||
entry = hook.Entries[1]
|
||||
assert.Equal(t, log.DebugLevel, entry.Level)
|
||||
assert.Equal(t, "hello world\n", entry.Message)
|
||||
assert.Contains(t, entry.Data, "duration")
|
||||
assert.Contains(t, entry.Data, "execID")
|
||||
|
||||
entry = hook.Entries[2]
|
||||
assert.Equal(t, log.ErrorLevel, entry.Level)
|
||||
assert.Equal(t, "`sh -c echo hello world && echo my-error >&2 && sleep 2` failed timeout after 500ms", entry.Message)
|
||||
assert.Contains(t, entry.Data, "execID")
|
||||
}
|
||||
|
||||
func TestTrimmedOutput(t *testing.T) {
|
||||
message, err := RunCommand("printf", CmdOpts{}, "hello world")
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, "hello world", message)
|
||||
}
|
||||
|
||||
func TestRunCommandExitErr(t *testing.T) {
|
||||
hook := test.NewGlobal()
|
||||
log.SetLevel(log.DebugLevel)
|
||||
defer log.SetLevel(log.InfoLevel)
|
||||
|
||||
output, err := RunCommand("sh", CmdOpts{Redactor: Redact([]string{"world"})}, "-c", "echo hello world && echo my-error >&2 && exit 1")
|
||||
assert.Equal(t, "hello world", output)
|
||||
assert.EqualError(t, err, "`sh -c echo hello ****** && echo my-error >&2 && exit 1` failed exit status 1: my-error")
|
||||
|
||||
assert.Len(t, hook.Entries, 3)
|
||||
|
||||
entry := hook.Entries[0]
|
||||
assert.Equal(t, log.InfoLevel, entry.Level)
|
||||
assert.Equal(t, "sh -c echo hello ****** && echo my-error >&2 && exit 1", entry.Message)
|
||||
assert.Contains(t, entry.Data, "dir")
|
||||
assert.Contains(t, entry.Data, "execID")
|
||||
|
||||
entry = hook.Entries[1]
|
||||
assert.Equal(t, log.DebugLevel, entry.Level)
|
||||
assert.Equal(t, "hello ******\n", entry.Message)
|
||||
assert.Contains(t, entry.Data, "duration")
|
||||
assert.Contains(t, entry.Data, "execID")
|
||||
|
||||
entry = hook.Entries[2]
|
||||
assert.Equal(t, log.ErrorLevel, entry.Level)
|
||||
assert.Equal(t, "`sh -c echo hello ****** && echo my-error >&2 && exit 1` failed exit status 1: my-error", entry.Message)
|
||||
assert.Contains(t, entry.Data, "execID")
|
||||
}
|
||||
|
||||
func TestRunCommandErr(t *testing.T) {
|
||||
//_ := test.NewGlobal()
|
||||
log.SetLevel(log.DebugLevel)
|
||||
defer log.SetLevel(log.InfoLevel)
|
||||
|
||||
// this would create a panic
|
||||
output, err := RunCommand("", CmdOpts{Redactor: Redact([]string{"world"})})
|
||||
assert.Empty(t, output)
|
||||
assert.EqualError(t, err, "fork/exec : no such file or directory")
|
||||
}
|
||||
|
||||
func TestRunInDir(t *testing.T) {
|
||||
cmd := exec.Command("pwd")
|
||||
cmd.Dir = "/"
|
||||
message, err := RunCommandExt(cmd, CmdOpts{})
|
||||
assert.Nil(t, err)
|
||||
assert.Equal(t, "/", message)
|
||||
}
|
||||
|
||||
func TestRedact(t *testing.T) {
|
||||
assert.Equal(t, "", Redact(nil)(""))
|
||||
assert.Equal(t, "", Redact([]string{})(""))
|
||||
assert.Equal(t, "", Redact([]string{"foo"})(""))
|
||||
assert.Equal(t, "foo", Redact([]string{})("foo"))
|
||||
assert.Equal(t, "******", Redact([]string{"foo"})("foo"))
|
||||
assert.Equal(t, "****** ******", Redact([]string{"foo", "bar"})("foo bar"))
|
||||
assert.Equal(t, "****** ******", Redact([]string{"foo"})("foo foo"))
|
||||
}
|
|
@ -0,0 +1,108 @@
|
|||
package expr
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"reflect"
|
||||
"strconv"
|
||||
|
||||
"github.com/oliveagle/jsonpath"
|
||||
)
|
||||
|
||||
func GetExprEnvFunctionMap() map[string]interface{} {
|
||||
return map[string]interface{}{
|
||||
"asInt": AsInt,
|
||||
"asFloat": AsFloat,
|
||||
"string": AsStr,
|
||||
"jsonpath": JsonPath,
|
||||
}
|
||||
}
|
||||
|
||||
func AsStr(val interface{}) interface{} {
|
||||
return fmt.Sprintf("%v", val)
|
||||
}
|
||||
|
||||
func JsonPath(jsonStr string, path string) interface{} {
|
||||
var jsonMap interface{}
|
||||
err := json.Unmarshal([]byte(jsonStr), &jsonMap)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
value, err := jsonpath.JsonPathLookup(jsonMap, path)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return value
|
||||
}
|
||||
|
||||
func AsInt(in interface{}) int64 {
|
||||
switch i := in.(type) {
|
||||
case float64:
|
||||
return int64(i)
|
||||
case float32:
|
||||
return int64(i)
|
||||
case int64:
|
||||
return i
|
||||
case int32:
|
||||
return int64(i)
|
||||
case int16:
|
||||
return int64(i)
|
||||
case int8:
|
||||
return int64(i)
|
||||
case int:
|
||||
return int64(i)
|
||||
case uint64:
|
||||
return int64(i)
|
||||
case uint32:
|
||||
return int64(i)
|
||||
case uint16:
|
||||
return int64(i)
|
||||
case uint8:
|
||||
return int64(i)
|
||||
case uint:
|
||||
return int64(i)
|
||||
case string:
|
||||
inAsInt, err := strconv.ParseInt(i, 10, 64)
|
||||
if err == nil {
|
||||
return inAsInt
|
||||
}
|
||||
panic(err)
|
||||
}
|
||||
panic(fmt.Sprintf("asInt() not supported on %v %v", reflect.TypeOf(in), in))
|
||||
}
|
||||
|
||||
func AsFloat(in interface{}) float64 {
|
||||
switch i := in.(type) {
|
||||
case float64:
|
||||
return i
|
||||
case float32:
|
||||
return float64(i)
|
||||
case int64:
|
||||
return float64(i)
|
||||
case int32:
|
||||
return float64(i)
|
||||
case int16:
|
||||
return float64(i)
|
||||
case int8:
|
||||
return float64(i)
|
||||
case int:
|
||||
return float64(i)
|
||||
case uint64:
|
||||
return float64(i)
|
||||
case uint32:
|
||||
return float64(i)
|
||||
case uint16:
|
||||
return float64(i)
|
||||
case uint8:
|
||||
return float64(i)
|
||||
case uint:
|
||||
return float64(i)
|
||||
case string:
|
||||
inAsFloat, err := strconv.ParseFloat(i, 64)
|
||||
if err == nil {
|
||||
return inAsFloat
|
||||
}
|
||||
panic(err)
|
||||
}
|
||||
panic(fmt.Sprintf("asFloat() not supported on %v %v", reflect.TypeOf(in), in))
|
||||
}
|
|
@ -0,0 +1,32 @@
|
|||
package expr
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestAsFloat(t *testing.T) {
|
||||
assert.Equal(t, int64(1), AsInt(int32(1)))
|
||||
assert.Equal(t, int64(1), AsInt(int64(1)))
|
||||
assert.Equal(t, int64(1), AsInt("1"))
|
||||
assert.Panics(t, func() { AsInt("1.56") })
|
||||
}
|
||||
|
||||
func TestAsFloat2(t *testing.T) {
|
||||
assert.Equal(t, 1.24, AsFloat(1.24))
|
||||
assert.Equal(t, 1.65, AsFloat("1.65"))
|
||||
}
|
||||
|
||||
func TestAsStr(t *testing.T) {
|
||||
assert.Equal(t, "1.24", AsStr(1.24))
|
||||
assert.Equal(t, "1", AsStr(1))
|
||||
}
|
||||
|
||||
func TestJsonPath(t *testing.T) {
|
||||
simpleJson := "{\"employee\":{\"name\":\"sonoo\",\"salary\":56000,\"married\":true}}"
|
||||
arrayJson := "{\"employees\":[{\"name\":\"Shyam\",\"email\":\"shyamjaiswal@gmail.com\"},{\"name\":\"Bob\",\"email\":\"bob32@gmail.com\"}," +
|
||||
"{\"name\":\"Jai\",\"email\":\"jai87@gmail.com\"}]}"
|
||||
assert.Equal(t, "sonoo", JsonPath(simpleJson, "$.employee.name"))
|
||||
assert.Equal(t, "Bob", JsonPath(arrayJson, "$.employees[1].name"))
|
||||
}
|
|
@ -0,0 +1,27 @@
|
|||
package file
|
||||
|
||||
import (
|
||||
"os"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
// IsDirectory returns whether or not the given file is a directory
|
||||
func IsDirectory(path string) (bool, error) {
|
||||
fileOrDir, err := os.Open(path)
|
||||
if err != nil {
|
||||
return false, errors.WithStack(err)
|
||||
}
|
||||
defer func() { _ = fileOrDir.Close() }()
|
||||
stat, err := fileOrDir.Stat()
|
||||
if err != nil {
|
||||
return false, errors.WithStack(err)
|
||||
}
|
||||
return stat.IsDir(), nil
|
||||
}
|
||||
|
||||
// Exists returns whether or not a path exists
|
||||
func Exists(path string) bool {
|
||||
_, err := os.Stat(path)
|
||||
return !os.IsNotExist(err)
|
||||
}
|
|
@ -0,0 +1,41 @@
|
|||
package file
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"path/filepath"
|
||||
"runtime"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
|
||||
"github.com/argoproj/pkg/rand"
|
||||
)
|
||||
|
||||
// TestIsDirectory tests if a path is a directory. Errors if directory doesn't exist
|
||||
func TestIsDirectory(t *testing.T) {
|
||||
_, filename, _, ok := runtime.Caller(0)
|
||||
if !ok {
|
||||
panic("could not determine test directory")
|
||||
}
|
||||
testDir := filepath.Dir(filename)
|
||||
|
||||
isDir, err := IsDirectory(testDir)
|
||||
assert.Nil(t, err)
|
||||
assert.True(t, isDir)
|
||||
|
||||
isDir, err = IsDirectory(filename)
|
||||
assert.Nil(t, err)
|
||||
assert.False(t, isDir)
|
||||
|
||||
isDir, err = IsDirectory("doesnt-exist")
|
||||
assert.NotNil(t, err)
|
||||
assert.False(t, isDir)
|
||||
}
|
||||
|
||||
func TestExists(t *testing.T) {
|
||||
assert.True(t, Exists("/"))
|
||||
path, err := rand.RandString(10)
|
||||
assert.NoError(t, err)
|
||||
randFilePath := fmt.Sprintf("/%s", path)
|
||||
assert.False(t, Exists(randFilePath))
|
||||
}
|
115
go.mod
115
go.mod
|
@ -1,83 +1,48 @@
|
|||
module github.com/argoproj/pkg/v2
|
||||
module github.com/argoproj/pkg
|
||||
|
||||
go 1.23.5
|
||||
|
||||
toolchain go1.24.1
|
||||
go 1.14
|
||||
|
||||
require (
|
||||
github.com/felixge/httpsnoop v1.0.4
|
||||
github.com/golang/protobuf v1.5.4
|
||||
github.com/aws/aws-sdk-go v1.44.39
|
||||
github.com/dustin/go-humanize v1.0.0
|
||||
github.com/felixge/httpsnoop v1.0.3
|
||||
github.com/golang/groupcache v0.0.0-20190129154638-5b532d6fd5ef // indirect
|
||||
github.com/golang/protobuf v1.3.3
|
||||
github.com/grpc-ecosystem/grpc-gateway v1.16.0
|
||||
github.com/sirupsen/logrus v1.9.3
|
||||
github.com/stretchr/testify v1.10.0
|
||||
golang.org/x/net v0.42.0
|
||||
k8s.io/api v0.32.2
|
||||
k8s.io/apimachinery v0.32.2
|
||||
k8s.io/client-go v0.32.2
|
||||
)
|
||||
|
||||
require (
|
||||
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect
|
||||
github.com/emicklei/go-restful/v3 v3.11.0 // indirect
|
||||
github.com/fxamacker/cbor/v2 v2.7.0 // indirect
|
||||
github.com/go-logr/logr v1.4.2 // indirect
|
||||
github.com/go-openapi/jsonpointer v0.21.0 // indirect
|
||||
github.com/go-openapi/jsonreference v0.20.2 // indirect
|
||||
github.com/go-openapi/swag v0.23.0 // indirect
|
||||
github.com/gogo/protobuf v1.3.2 // indirect
|
||||
github.com/google/gnostic-models v0.6.8 // indirect
|
||||
github.com/google/go-cmp v0.6.0 // indirect
|
||||
github.com/google/gofuzz v1.2.0 // indirect
|
||||
github.com/google/uuid v1.6.0 // indirect
|
||||
github.com/josharian/intern v1.0.0 // indirect
|
||||
github.com/json-iterator/go v1.1.12 // indirect
|
||||
github.com/mailru/easyjson v0.7.7 // indirect
|
||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
|
||||
github.com/modern-go/reflect2 v1.0.2 // indirect
|
||||
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect
|
||||
github.com/pkg/errors v0.9.1 // indirect
|
||||
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect
|
||||
github.com/spf13/pflag v1.0.6 // indirect
|
||||
github.com/x448/float16 v0.8.4 // indirect
|
||||
golang.org/x/oauth2 v0.23.0 // indirect
|
||||
golang.org/x/sys v0.34.0 // indirect
|
||||
golang.org/x/term v0.33.0 // indirect
|
||||
golang.org/x/text v0.27.0 // indirect
|
||||
golang.org/x/time v0.7.0 // indirect
|
||||
google.golang.org/genproto v0.0.0-20230410155749-daa745c078e1 // indirect
|
||||
google.golang.org/grpc v1.56.3 // indirect
|
||||
google.golang.org/protobuf v1.35.1 // indirect
|
||||
gopkg.in/evanphx/json-patch.v4 v4.12.0 // indirect
|
||||
gopkg.in/inf.v0 v0.9.1 // indirect
|
||||
gopkg.in/yaml.v3 v3.0.1 // indirect
|
||||
k8s.io/klog/v2 v2.130.1 // indirect
|
||||
k8s.io/kube-openapi v0.0.0-20241105132330-32ad38e42d3f // indirect
|
||||
k8s.io/utils v0.0.0-20241104100929-3ea5e8cea738 // indirect
|
||||
sigs.k8s.io/json v0.0.0-20241010143419-9aa6b5e7a4b3 // indirect
|
||||
sigs.k8s.io/structured-merge-diff/v4 v4.4.2 // indirect
|
||||
sigs.k8s.io/yaml v1.4.0 // indirect
|
||||
github.com/minio/minio-go/v7 v7.0.29
|
||||
github.com/oliveagle/jsonpath v0.0.0-20180606110733-2e52cf6e6852
|
||||
github.com/pkg/errors v0.9.1
|
||||
github.com/sirupsen/logrus v1.8.1
|
||||
github.com/spf13/cobra v1.5.0
|
||||
github.com/stretchr/testify v1.7.3
|
||||
golang.org/x/net v0.0.0-20220127200216-cd36cc0744dd
|
||||
k8s.io/api v0.17.8
|
||||
k8s.io/apimachinery v0.17.8
|
||||
k8s.io/client-go v0.17.8
|
||||
k8s.io/klog/v2 v2.5.0
|
||||
)
|
||||
|
||||
replace (
|
||||
k8s.io/api => k8s.io/api v0.32.2
|
||||
k8s.io/apiextensions-apiserver => k8s.io/apiextensions-apiserver v0.32.2
|
||||
k8s.io/apimachinery => k8s.io/apimachinery v0.32.2
|
||||
k8s.io/apiserver => k8s.io/apiserver v0.32.2
|
||||
k8s.io/cli-runtime => k8s.io/cli-runtime v0.32.2
|
||||
k8s.io/client-go => k8s.io/client-go v0.32.2
|
||||
k8s.io/cloud-provider => k8s.io/cloud-provider v0.32.2
|
||||
k8s.io/cluster-bootstrap => k8s.io/cluster-bootstrap v0.32.2
|
||||
k8s.io/code-generator => k8s.io/code-generator v0.32.2
|
||||
k8s.io/component-base => k8s.io/component-base v0.32.2
|
||||
k8s.io/cri-api => k8s.io/cri-api v0.32.2
|
||||
k8s.io/csi-translation-lib => k8s.io/csi-translation-lib v0.32.2
|
||||
k8s.io/kube-aggregator => k8s.io/kube-aggregator v0.32.2
|
||||
k8s.io/kube-controller-manager => k8s.io/kube-controller-manager v0.32.2
|
||||
k8s.io/kube-proxy => k8s.io/kube-proxy v0.32.2
|
||||
k8s.io/kube-scheduler => k8s.io/kube-scheduler v0.32.2
|
||||
k8s.io/kubectl => k8s.io/kubectl v0.32.2
|
||||
k8s.io/kubelet => k8s.io/kubelet v0.32.2
|
||||
k8s.io/legacy-cloud-providers => k8s.io/legacy-cloud-providers v0.32.2
|
||||
k8s.io/metrics => k8s.io/metrics v0.32.2
|
||||
k8s.io/sample-apiserver => k8s.io/sample-apiserver v0.32.2
|
||||
// https://github.com/kubernetes/kubernetes/issues/79384#issuecomment-505627280
|
||||
k8s.io/api => k8s.io/api v0.17.8
|
||||
k8s.io/apiextensions-apiserver => k8s.io/apiextensions-apiserver v0.17.8 // indirect
|
||||
k8s.io/apimachinery => k8s.io/apimachinery v0.17.8 // indirect
|
||||
k8s.io/apiserver => k8s.io/apiserver v0.17.8
|
||||
k8s.io/cli-runtime => k8s.io/cli-runtime v0.17.8
|
||||
k8s.io/client-go => k8s.io/client-go v0.17.8
|
||||
k8s.io/cloud-provider => k8s.io/cloud-provider v0.17.8
|
||||
k8s.io/cluster-bootstrap => k8s.io/cluster-bootstrap v0.17.8
|
||||
k8s.io/code-generator => k8s.io/code-generator v0.17.8
|
||||
k8s.io/component-base => k8s.io/component-base v0.17.8
|
||||
k8s.io/cri-api => k8s.io/cri-api v0.17.8
|
||||
k8s.io/csi-translation-lib => k8s.io/csi-translation-lib v0.17.8
|
||||
k8s.io/kube-aggregator => k8s.io/kube-aggregator v0.17.8
|
||||
k8s.io/kube-controller-manager => k8s.io/kube-controller-manager v0.17.8
|
||||
k8s.io/kube-proxy => k8s.io/kube-proxy v0.17.8
|
||||
k8s.io/kube-scheduler => k8s.io/kube-scheduler v0.17.8
|
||||
k8s.io/kubectl => k8s.io/kubectl v0.17.8
|
||||
k8s.io/kubelet => k8s.io/kubelet v0.17.8
|
||||
k8s.io/legacy-cloud-providers => k8s.io/legacy-cloud-providers v0.17.8
|
||||
k8s.io/metrics => k8s.io/metrics v0.17.8
|
||||
k8s.io/sample-apiserver => k8s.io/sample-apiserver v0.17.8
|
||||
)
|
||||
|
|
330
go.sum
330
go.sum
|
@ -1,222 +1,298 @@
|
|||
cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
|
||||
cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
|
||||
cloud.google.com/go v0.38.0/go.mod h1:990N+gfupTy94rShfmMCWGDn0LpTmnzTp2qbd1dvSRU=
|
||||
github.com/Azure/go-autorest/autorest v0.9.0/go.mod h1:xyHB1BMZT0cuDHU7I0+g046+BFDTQ8rEZB0s4Yfa6bI=
|
||||
github.com/Azure/go-autorest/autorest/adal v0.5.0/go.mod h1:8Z9fGy2MpX0PvDjB1pEgQTmVqjGhiHBW7RJJEciWzS0=
|
||||
github.com/Azure/go-autorest/autorest/date v0.1.0/go.mod h1:plvfp3oPSKwf2DNjlBjWF/7vwR+cUD/ELuzDCXwHUVA=
|
||||
github.com/Azure/go-autorest/autorest/mocks v0.1.0/go.mod h1:OTyCOPRA2IgIlWxVYxBee2F5Gr4kF2zd2J5cFRaIDN0=
|
||||
github.com/Azure/go-autorest/autorest/mocks v0.2.0/go.mod h1:OTyCOPRA2IgIlWxVYxBee2F5Gr4kF2zd2J5cFRaIDN0=
|
||||
github.com/Azure/go-autorest/logger v0.1.0/go.mod h1:oExouG+K6PryycPJfVSxi/koC6LSNgds39diKLz7Vrc=
|
||||
github.com/Azure/go-autorest/tracing v0.5.0/go.mod h1:r/s2XiOKccPW3HrqB+W0TQzfbtp2fGCgRFtBroKn4Dk=
|
||||
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
|
||||
github.com/NYTimes/gziphandler v0.0.0-20170623195520-56545f4a5d46/go.mod h1:3wb06e3pkSAbeQ52E9H9iFoQsEEwGN64994WTCIhntQ=
|
||||
github.com/PuerkitoBio/purell v1.0.0/go.mod h1:c11w/QuzBsJSee3cPx9rAFu61PvFxuPbtSwDGJws/X0=
|
||||
github.com/PuerkitoBio/urlesc v0.0.0-20160726150825-5bd2802263f2/go.mod h1:uGdkoq3SwY9Y+13GIhn11/XLaGBb4BfwItxLd5jeuXE=
|
||||
github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY=
|
||||
github.com/aws/aws-sdk-go v1.44.39 h1:pMxYLqnuDidT0ZTDAhYC66fb3W3Yc+oShmfzEL4fTDI=
|
||||
github.com/aws/aws-sdk-go v1.44.39/go.mod h1:y4AeaBuwd2Lk+GepC1E9v0qOiTws0MIWAX4oIKwKHZo=
|
||||
github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
|
||||
github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
|
||||
github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc=
|
||||
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
|
||||
github.com/cpuguy83/go-md2man/v2 v2.0.2/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
|
||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM=
|
||||
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/emicklei/go-restful/v3 v3.11.0 h1:rAQeMHw1c7zTmncogyy8VvRZwtkmkZ4FxERmMY4rD+g=
|
||||
github.com/emicklei/go-restful/v3 v3.11.0/go.mod h1:6n3XBCmQQb25CM2LCACGz8ukIrRry+4bhvbpWn3mrbc=
|
||||
github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ=
|
||||
github.com/docker/spdystream v0.0.0-20160310174837-449fdfce4d96/go.mod h1:Qh8CwZgvJUkLughtfhJv5dyTYa91l1fOUCrgjqmcifM=
|
||||
github.com/dustin/go-humanize v1.0.0 h1:VSnTsYCnlFHaM2/igO1h6X3HA71jcobQuxemgkq4zYo=
|
||||
github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk=
|
||||
github.com/elazarl/goproxy v0.0.0-20170405201442-c4fc26588b6e/go.mod h1:/Zj4wYkgs4iZTTu3o/KG3Itv/qCCa8VVMlb3i9OVuzc=
|
||||
github.com/emicklei/go-restful v0.0.0-20170410110728-ff4f55a20633/go.mod h1:otzb+WCGbkyDHkqmQmT5YD2WR4BBwUdeQoFo8l/7tVs=
|
||||
github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
|
||||
github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
|
||||
github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98=
|
||||
github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
|
||||
github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg=
|
||||
github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U=
|
||||
github.com/fxamacker/cbor/v2 v2.7.0 h1:iM5WgngdRBanHcxugY4JySA0nk1wZorNOpTgCMedv5E=
|
||||
github.com/fxamacker/cbor/v2 v2.7.0/go.mod h1:pxXPTn3joSm21Gbwsv0w9OSA2y1HFR9qXEeXQVeNoDQ=
|
||||
github.com/evanphx/json-patch v4.2.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk=
|
||||
github.com/felixge/httpsnoop v1.0.3 h1:s/nj+GCswXYzN5v2DpNMuMQYe+0DDwt5WVCU6CWBdXk=
|
||||
github.com/felixge/httpsnoop v1.0.3/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U=
|
||||
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
|
||||
github.com/ghodss/yaml v0.0.0-20150909031657-73d445a93680/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
|
||||
github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
|
||||
github.com/go-logr/logr v1.4.2 h1:6pFjapn8bFcIbiKo3XT4j/BhANplGihG6tvd+8rYgrY=
|
||||
github.com/go-logr/logr v1.4.2/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
|
||||
github.com/go-openapi/jsonpointer v0.19.6/go.mod h1:osyAmYz/mB/C3I+WsTTSgw1ONzaLJoLCyoi6/zppojs=
|
||||
github.com/go-openapi/jsonpointer v0.21.0 h1:YgdVicSA9vH5RiHs9TZW5oyafXZFc6+2Vc1rr/O9oNQ=
|
||||
github.com/go-openapi/jsonpointer v0.21.0/go.mod h1:IUyH9l/+uyhIYQ/PXVA41Rexl+kOkAPDdXEYns6fzUY=
|
||||
github.com/go-openapi/jsonreference v0.20.2 h1:3sVjiK66+uXK/6oQ8xgcRKcFgQ5KXa2KvnJRumpMGbE=
|
||||
github.com/go-openapi/jsonreference v0.20.2/go.mod h1:Bl1zwGIM8/wsvqjsOQLJ/SH+En5Ap4rVB5KVcIDZG2k=
|
||||
github.com/go-openapi/swag v0.22.3/go.mod h1:UzaqsxGiab7freDnrUUra0MwWfN/q7tE4j+VcZ0yl14=
|
||||
github.com/go-openapi/swag v0.23.0 h1:vsEVJDUo2hPJ2tu0/Xc+4noaxyEffXNIs3cOULZ+GrE=
|
||||
github.com/go-openapi/swag v0.23.0/go.mod h1:esZ8ITTYEsH1V2trKHjAN8Ai7xHb8RV+YSZ577vPjgQ=
|
||||
github.com/go-task/slim-sprig/v3 v3.0.0 h1:sUs3vkvUymDpBKi3qH1YSqBQk9+9D/8M2mN1vB6EwHI=
|
||||
github.com/go-task/slim-sprig/v3 v3.0.0/go.mod h1:W848ghGpv3Qj3dhTPRyJypKRiqCdHZiAzKg9hl15HA8=
|
||||
github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q=
|
||||
github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q=
|
||||
github.com/go-logr/logr v0.1.0/go.mod h1:ixOQHD9gLJUVQQ2ZOR7zLEifBX6tGkNJF4QyIY7sIas=
|
||||
github.com/go-logr/logr v0.4.0 h1:K7/B1jt6fIBQVd4Owv2MqGQClcgf0R266+7C/QjRcLc=
|
||||
github.com/go-logr/logr v0.4.0/go.mod h1:z6/tIYblkpsD+a4lm/fGIIU9mZ+XfAiaFtq7xTgseGU=
|
||||
github.com/go-openapi/jsonpointer v0.0.0-20160704185906-46af16f9f7b1/go.mod h1:+35s3my2LFTysnkMfxsJBAMHj/DoqoB9knIWoYG/Vk0=
|
||||
github.com/go-openapi/jsonreference v0.0.0-20160704190145-13c6e3589ad9/go.mod h1:W3Z9FmVs9qj+KR4zFKmDPGiLdk1D9Rlm7cyMvf57TTg=
|
||||
github.com/go-openapi/spec v0.0.0-20160808142527-6aced65f8501/go.mod h1:J8+jY1nAiCcj+friV/PDoE1/3eeccG9LYBs0tYvLOWc=
|
||||
github.com/go-openapi/swag v0.0.0-20160704191624-1d0bd113de87/go.mod h1:DXUve3Dpr1UfpPtxFw+EFuQ41HhCWZfha5jSVRG7C7I=
|
||||
github.com/gogo/protobuf v1.2.2-0.20190723190241-65acae22fc9d h1:3PaI8p3seN09VjbTYC/QWlUZdZ1qS1zGjy7LH2Wt07I=
|
||||
github.com/gogo/protobuf v1.2.2-0.20190723190241-65acae22fc9d/go.mod h1:SlYgWuQ5SjCEi6WLHjHCa1yvBfUnHcTbrrZtXPKa29o=
|
||||
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
|
||||
github.com/golang/groupcache v0.0.0-20160516000752-02826c3e7903/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
|
||||
github.com/golang/groupcache v0.0.0-20190129154638-5b532d6fd5ef h1:veQD95Isof8w9/WXiA+pa3tz3fJXkt5B7QaRBrM62gk=
|
||||
github.com/golang/groupcache v0.0.0-20190129154638-5b532d6fd5ef/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
|
||||
github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
|
||||
github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
|
||||
github.com/golang/protobuf v0.0.0-20161109072736-4bd1920723d7/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
||||
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
||||
github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
||||
github.com/golang/protobuf v1.3.3 h1:gyjaxf+svBWX08ZjK86iN9geUJF0H6gp2IRKX6Nf6/I=
|
||||
github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw=
|
||||
github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek=
|
||||
github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps=
|
||||
github.com/google/gnostic-models v0.6.8 h1:yo/ABAfM5IMRsS1VnXjTBvUb61tFIHozhlYvRgGre9I=
|
||||
github.com/google/gnostic-models v0.6.8/go.mod h1:5n7qKqH0f5wFt+aWF8CW6pZLLNOfYuF5OpfBSENuI8U=
|
||||
github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
|
||||
github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
|
||||
github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
|
||||
github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
|
||||
github.com/google/go-cmp v0.4.0 h1:xsAVV57WRhGj6kEIi8ReJzQlHHqcBYCElAvkovg3B/4=
|
||||
github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
|
||||
github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
|
||||
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
|
||||
github.com/google/gofuzz v1.0.0 h1:A8PeW59pxE9IoFRqBp37U+mSNaQoZ46F1f0f863XSXw=
|
||||
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
|
||||
github.com/google/gofuzz v1.2.0 h1:xRy4A+RhZaiKjJ1bPfwQ8sedCA+YS2YcCHW6ec7JMi0=
|
||||
github.com/google/gofuzz v1.2.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
|
||||
github.com/google/pprof v0.0.0-20241029153458-d1b30febd7db h1:097atOisP2aRj7vFgYQBbFN4U4JNXUNYpxael3UzMyo=
|
||||
github.com/google/pprof v0.0.0-20241029153458-d1b30febd7db/go.mod h1:vavhavw2zAxS5dIdcRluK6cSGGPlZynqzFM8NdvU144=
|
||||
github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs=
|
||||
github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc=
|
||||
github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||
github.com/google/uuid v1.1.2 h1:EVhdT+1Kseyi1/pUmXKaFxYsDNy9RQYkMWRH68J/W7Y=
|
||||
github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
|
||||
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||
github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg=
|
||||
github.com/googleapis/gnostic v0.0.0-20170729233727-0c5108395e2d h1:7XGaL1e6bYS1yIonGp9761ExpPPV1ui0SAC59Yube9k=
|
||||
github.com/googleapis/gnostic v0.0.0-20170729233727-0c5108395e2d/go.mod h1:sJBsCZ4ayReDTBIg8b9dl28c5xFWyhBTVRp3pOg5EKY=
|
||||
github.com/gophercloud/gophercloud v0.1.0/go.mod h1:vxM41WHh5uqHVBMZHzuwNOHh8XEoIEcSTewFxm1c5g8=
|
||||
github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1 h1:EGx4pi6eqNxGaHF6qqu48+N2wcFQ5qg5FXgOdqsJ5d8=
|
||||
github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=
|
||||
github.com/gregjones/httpcache v0.0.0-20180305231024-9cad4c3443a7/go.mod h1:FecbI9+v66THATjSRHfNgh1IVFe/9kFxbXtjV0ctIMA=
|
||||
github.com/grpc-ecosystem/grpc-gateway v1.16.0 h1:gmcG1KaJ57LophUzW0Hy8NmPhnMZb4M0+kPpLofRdBo=
|
||||
github.com/grpc-ecosystem/grpc-gateway v1.16.0/go.mod h1:BDjrQk3hbvj6Nolgz8mAMFbcEtjT1g+wF4CSlocrBnw=
|
||||
github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY=
|
||||
github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y=
|
||||
github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
|
||||
github.com/hashicorp/golang-lru v0.5.1 h1:0hERBMJE1eitiLkihrMvRVBYAkpHzc/J3QdDN+dAcgU=
|
||||
github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
|
||||
github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=
|
||||
github.com/imdario/mergo v0.3.5 h1:JboBksRwiiAJWvIYJVo46AfV+IAIKZpfrSzVKj42R4Q=
|
||||
github.com/imdario/mergo v0.3.5/go.mod h1:2EnlNZ0deacrJVfApfmtdGgDfMuh/nq6Ok1EcJh5FfA=
|
||||
github.com/inconshreveable/mousetrap v1.0.0 h1:Z8tu5sraLXCXIcARxBp/8cbvlwVa7Z1NHg9XEKhtSvM=
|
||||
github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8=
|
||||
github.com/jmespath/go-jmespath v0.4.0 h1:BEgLn5cpjn8UN1mAw4NjwDrS35OdebyEtFe+9YPoQUg=
|
||||
github.com/jmespath/go-jmespath v0.4.0/go.mod h1:T8mJZnbsbmF+m6zOOFylbeCJqk5+pHWvzYPziyZiYoo=
|
||||
github.com/jmespath/go-jmespath/internal/testify v1.5.1 h1:shLQSRRSCCPj3f2gpwzGwWFoC7ycTf1rcQZHOlsJ6N8=
|
||||
github.com/jmespath/go-jmespath/internal/testify v1.5.1/go.mod h1:L3OGu8Wl2/fWfCI6z80xFu9LTZmf1ZRjMHUOPmWr69U=
|
||||
github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU=
|
||||
github.com/json-iterator/go v1.1.8/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
|
||||
github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM=
|
||||
github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo=
|
||||
github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8=
|
||||
github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU=
|
||||
github.com/jtolds/gls v4.20.0+incompatible h1:xdiiI2gbIgH/gLH7ADydsJ1uDOEzR8yvV7C0MuV77Wo=
|
||||
github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU=
|
||||
github.com/kisielk/errcheck v1.2.0/go.mod h1:/BMXB+zMLi60iA8Vv6Ksmxu/1UDYcXs4uQLJ+jE2L00=
|
||||
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
|
||||
github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
|
||||
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
|
||||
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
|
||||
github.com/klauspost/compress v1.13.5 h1:9O69jUPDcsT9fEm74W92rZL9FQY7rCdaXVneq+yyzl4=
|
||||
github.com/klauspost/compress v1.13.5/go.mod h1:/3/Vjq9QcHkK5uEr5lBEmyoZ1iFhe47etQ6QUkpK6sk=
|
||||
github.com/klauspost/cpuid v1.2.3/go.mod h1:Pj4uuM528wm8OyEC2QMXAi2YiTZ96dNQPGgoMS4s3ek=
|
||||
github.com/klauspost/cpuid v1.3.1 h1:5JNjFYYQrZeKRJ0734q51WCEEn2huer72Dc7K+R/b6s=
|
||||
github.com/klauspost/cpuid v1.3.1/go.mod h1:bYW4mA6ZgKPob1/Dlai2LviZJO7KGI3uoWLd42rAQw4=
|
||||
github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI=
|
||||
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
|
||||
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
|
||||
github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE=
|
||||
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
|
||||
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
|
||||
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
|
||||
github.com/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0=
|
||||
github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc=
|
||||
github.com/mailru/easyjson v0.0.0-20160728113105-d5b7844b561a/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc=
|
||||
github.com/minio/md5-simd v1.1.0 h1:QPfiOqlZH+Cj9teu0t9b1nTBfPbyTl16Of5MeuShdK4=
|
||||
github.com/minio/md5-simd v1.1.0/go.mod h1:XpBqgZULrMYD3R+M28PcmP0CkI7PEMzB3U77ZrKZ0Gw=
|
||||
github.com/minio/minio-go/v7 v7.0.29 h1:7md6lIq1s6zPzUiDRX1BVLHolA4pDM8RMQqIszaJbY0=
|
||||
github.com/minio/minio-go/v7 v7.0.29/go.mod h1:x81+AX5gHSfCSqw7jxRKHvxUXMlE5uKX0Vb75Xk5yYg=
|
||||
github.com/minio/sha256-simd v0.1.1 h1:5QHSlgo3nt5yKOJrC7W8w7X+NFl8cMPZm96iu8kKUJU=
|
||||
github.com/minio/sha256-simd v0.1.1/go.mod h1:B5e1o+1/KgNmWrSQK08Y6Z1Vb5pwIktudl0J58iy0KM=
|
||||
github.com/mitchellh/go-homedir v1.1.0 h1:lukF9ziXFxDFPkA1vsr5zpc1XuPDn/wFntq5mG+4E0Y=
|
||||
github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0=
|
||||
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
|
||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg=
|
||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
|
||||
github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
|
||||
github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
|
||||
github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M=
|
||||
github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
|
||||
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA=
|
||||
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ=
|
||||
github.com/onsi/ginkgo/v2 v2.21.0 h1:7rg/4f3rB88pb5obDgNZrNHrQ4e6WpjonchcpuBRnZM=
|
||||
github.com/onsi/ginkgo/v2 v2.21.0/go.mod h1:7Du3c42kxCUegi0IImZ1wUQzMBVecgIHjR1C+NkhLQo=
|
||||
github.com/onsi/gomega v1.35.1 h1:Cwbd75ZBPxFSuZ6T+rN/WCb/gOc6YgFBXLlZLhC7Ds4=
|
||||
github.com/onsi/gomega v1.35.1/go.mod h1:PvZbdDc8J6XJEpDK4HCuRBm8a6Fzp9/DmhC9C7yFlog=
|
||||
github.com/munnerz/goautoneg v0.0.0-20120707110453-a547fc61f48d/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ=
|
||||
github.com/mxk/go-flowrate v0.0.0-20140419014527-cca7078d478f/go.mod h1:ZdcZmHo+o7JKHSa8/e818NopupXU1YMK5fe1lsApnBw=
|
||||
github.com/oliveagle/jsonpath v0.0.0-20180606110733-2e52cf6e6852 h1:Yl0tPBa8QPjGmesFh1D0rDy+q1Twx6FyU7VWHi8wZbI=
|
||||
github.com/oliveagle/jsonpath v0.0.0-20180606110733-2e52cf6e6852/go.mod h1:eqOVx5Vwu4gd2mmMZvVZsgIqNSaW3xxRThUJ0k/TPk4=
|
||||
github.com/onsi/ginkgo v0.0.0-20170829012221-11459a886d9c/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
|
||||
github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
|
||||
github.com/onsi/ginkgo v1.10.1/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
|
||||
github.com/onsi/gomega v0.0.0-20170829124025-dcabb60a477c/go.mod h1:C1qb7wdrVGGVU+Z6iS04AVkA3Q65CEZX59MT0QO5uiA=
|
||||
github.com/onsi/gomega v1.7.0/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY=
|
||||
github.com/peterbourgon/diskv v2.0.1+incompatible/go.mod h1:uqqh8zWWbv1HBMNONnaR/tNboyR3/BZd58JJSHlUSCU=
|
||||
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
|
||||
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U=
|
||||
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
|
||||
github.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6LYCDYWNEvQ=
|
||||
github.com/rogpeppe/go-internal v1.12.0 h1:exVL4IDcn6na9z1rAb56Vxr+CgyK3nn3O+epU5NdKM8=
|
||||
github.com/rogpeppe/go-internal v1.12.0/go.mod h1:E+RYuTGaKKdloAfM02xzb0FW3Paa99yedzYV+kq4uf4=
|
||||
github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ=
|
||||
github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ=
|
||||
github.com/spf13/pflag v1.0.6 h1:jFzHGLGAlb3ruxLB8MhbI6A8+AQX/2eW4qeyNZXNp2o=
|
||||
github.com/spf13/pflag v1.0.6/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
|
||||
github.com/rs/xid v1.2.1 h1:mhH9Nq+C1fY2l1XIpgxIiUOfNpRBYH1kKcr+qfKgjRc=
|
||||
github.com/rs/xid v1.2.1/go.mod h1:+uKXf+4Djp6Md1KODXJxgGQPKngRmWyn10oCKFzNHOQ=
|
||||
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
|
||||
github.com/sirupsen/logrus v1.8.1 h1:dJKuHgqk1NNQlqoA6BTlM1Wf9DOH3NBjQyu0h9+AZZE=
|
||||
github.com/sirupsen/logrus v1.8.1/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0=
|
||||
github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d h1:zE9ykElWQ6/NYmHa3jpm/yHnI4xSofP+UP6SpjHcSeM=
|
||||
github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc=
|
||||
github.com/smartystreets/goconvey v1.6.4 h1:fv0U8FUIMPNf1L9lnHLvLhgicrIVChEkdzIKYqbNC9s=
|
||||
github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA=
|
||||
github.com/spf13/afero v1.2.2/go.mod h1:9ZxEEn6pIJ8Rxe320qSDBk6AsU0r9pR7Q4OcevTdifk=
|
||||
github.com/spf13/cobra v1.5.0 h1:X+jTBEBqF0bHN+9cSMgmfuvv2VHJ9ezmFNf9Y/XstYU=
|
||||
github.com/spf13/cobra v1.5.0/go.mod h1:dWXEIy2H428czQCjInthrTRUg7yKbok+2Qi/yBIJoUM=
|
||||
github.com/spf13/pflag v0.0.0-20170130214245-9ff6c6923cff/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4=
|
||||
github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA=
|
||||
github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
|
||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
|
||||
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
|
||||
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
|
||||
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
|
||||
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
|
||||
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
|
||||
github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
|
||||
github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA=
|
||||
github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
|
||||
github.com/x448/float16 v0.8.4 h1:qLwI1I70+NjRFUR3zs1JPUCgaCXSh3SW62uAKT1mSBM=
|
||||
github.com/x448/float16 v0.8.4/go.mod h1:14CWIYCyZA/cWjXOioeEpHeN/83MdbZDRQHoFcYsOfg=
|
||||
github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
||||
github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
||||
github.com/stretchr/testify v1.7.3 h1:dAm0YRdRQlWojc3CrCRgPBzG5f941d0zvAKu7qY4e+I=
|
||||
github.com/stretchr/testify v1.7.3/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
|
||||
go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU=
|
||||
golang.org/x/crypto v0.0.0-20190211182817-74369b46fc67/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
|
||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||
golang.org/x/crypto v0.0.0-20200220183623-bac4c82f6975/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||
golang.org/x/crypto v0.0.0-20201216223049-8b5274cf687f h1:aZp0e2vLN4MToVqnjNEYEtrEA8RH8U8FN1CU7JgqsPU=
|
||||
golang.org/x/crypto v0.0.0-20201216223049-8b5274cf687f/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I=
|
||||
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
||||
golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
|
||||
golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU=
|
||||
golang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
|
||||
golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
|
||||
golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||
golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||
golang.org/x/net v0.0.0-20170114055629-f2499483f923/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20191004110552-13f9640d40b9/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20200707034311-ab3426394381/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
|
||||
golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
|
||||
golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
|
||||
golang.org/x/net v0.42.0 h1:jzkYrhi3YQWD6MLBJcsklgQsoAcw89EcZbJw8Z614hs=
|
||||
golang.org/x/net v0.42.0/go.mod h1:FF1RA5d3u7nAYA4z2TkclSCKh68eSXtiFwcWQpPXdt8=
|
||||
golang.org/x/net v0.0.0-20220127200216-cd36cc0744dd h1:O7DYs+zxREGLKzKoMQrtrEacpb0ZVXA5rIwylE2Xchk=
|
||||
golang.org/x/net v0.0.0-20220127200216-cd36cc0744dd/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk=
|
||||
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
|
||||
golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
||||
golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
||||
golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d h1:TzXSXBo42m9gQenoE3b9BGiEpg5IG2JkU5FkPIawgtw=
|
||||
golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
||||
golang.org/x/oauth2 v0.23.0 h1:PbgcYx2W7i4LvjJWEbf0ngHV6qJYr86PkAV3bXdLEbs=
|
||||
golang.org/x/oauth2 v0.23.0/go.mod h1:XYTD2NtWslqkgxebSiOHnXEap4TF09sJSc7H1sXbhtI=
|
||||
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sys v0.0.0-20170830134202-bb24a47a89ea/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20190209173611-3b5209105503/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190826190057-c7b8b68b1456/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.34.0 h1:H5Y5sJ2L2JRdyv7ROF1he/lPdvFsd0mJHFw2ThKHxLA=
|
||||
golang.org/x/sys v0.34.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
|
||||
golang.org/x/term v0.33.0 h1:NuFncQrRcaRvVmgRkvM3j/F00gWIAlcmlB8ACEKmGIg=
|
||||
golang.org/x/term v0.33.0/go.mod h1:s18+ql9tYWp1IfpV9DmCtQDDSRBUjKaw9M1eAv5UeF0=
|
||||
golang.org/x/sys v0.0.0-20200625212154-ddb9806d33ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e h1:fLOSk5Q00efkSvAm+4xcoXD+RRmLmmulPn5I3Y9F2EM=
|
||||
golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw=
|
||||
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211 h1:JGgROgKl9N8DuW20oFS5gxc+lE67/N3FcwmBPMe7ArY=
|
||||
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
||||
golang.org/x/text v0.0.0-20160726164857-2910a502d2bf/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
|
||||
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/text v0.27.0 h1:4fGWRpyh641NLlecmyl4LOe6yDdfaYNrGb2zdfo4JV4=
|
||||
golang.org/x/text v0.27.0/go.mod h1:1D28KMCvyooCX9hBiosv5Tz/+YLxj0j7XhWjpSUF7CU=
|
||||
golang.org/x/time v0.7.0 h1:ntUhktv3OPE6TgYxXWv9vKvUSJyIFJlyohwbkEwPrKQ=
|
||||
golang.org/x/time v0.7.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM=
|
||||
golang.org/x/text v0.3.7 h1:olpwvP2KacW1ZWvsR7uQhoyTYvKAupfQrRGBFM352Gk=
|
||||
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
|
||||
golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||
golang.org/x/time v0.0.0-20190308202827-9d24e82272b4 h1:SvFZT6jyqRaOeXpc5h/JSfZenJ2O330aBsf7JfSUXmQ=
|
||||
golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
golang.org/x/tools v0.0.0-20181011042414-1f849cf54d09/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
golang.org/x/tools v0.0.0-20181030221726-6c7e314b6563/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY=
|
||||
golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
|
||||
golang.org/x/tools v0.0.0-20190312170243-e65039ee4138/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
|
||||
golang.org/x/tools v0.0.0-20190328211700-ab21143f2384/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
|
||||
golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
|
||||
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
|
||||
golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
|
||||
golang.org/x/tools v0.34.0 h1:qIpSLOxeCYGg9TrcJokLBG4KFA6d795g0xkBkiESGlo=
|
||||
golang.org/x/tools v0.34.0/go.mod h1:pAP9OwEaY1CAW3HOmg3hLZC5Z0CCmzjAF2UQMSqNARg=
|
||||
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE=
|
||||
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE=
|
||||
google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
|
||||
google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
|
||||
google.golang.org/appengine v1.5.0 h1:KxkO13IPW4Lslp2bz+KHP2E3gtFlrIGNThxkZQ3g+4c=
|
||||
google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
|
||||
google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
|
||||
google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
|
||||
google.golang.org/genproto v0.0.0-20190418145605-e7d98fc518a7/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
|
||||
google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=
|
||||
google.golang.org/genproto v0.0.0-20200513103714-09dca8ec2884 h1:fiNLklpBwWK1mth30Hlwk+fcdBmIALlgF5iy77O37Ig=
|
||||
google.golang.org/genproto v0.0.0-20200513103714-09dca8ec2884/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
|
||||
google.golang.org/genproto v0.0.0-20230410155749-daa745c078e1 h1:KpwkzHKEF7B9Zxg18WzOa7djJ+Ha5DzthMyZYQfEn2A=
|
||||
google.golang.org/genproto v0.0.0-20230410155749-daa745c078e1/go.mod h1:nKE/iIaLqn2bQwXBg8f1g2Ylh6r5MN5CmZvuzZCgsCU=
|
||||
google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
|
||||
google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg=
|
||||
google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY=
|
||||
google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
|
||||
google.golang.org/grpc v1.33.1 h1:DGeFlSan2f+WEtCERJ4J9GJWk15TxUi8QGagfI87Xyc=
|
||||
google.golang.org/grpc v1.33.1/go.mod h1:fr5YgcSWrqhRRxogOsw7RzIpsmvOZ6IcH4kBYTpR3n0=
|
||||
google.golang.org/grpc v1.56.3 h1:8I4C0Yq1EjstUzUJzpcRVbuYA2mODtEmpWiQoN/b2nc=
|
||||
google.golang.org/grpc v1.56.3/go.mod h1:I9bI3vqKfayGqPUAwGdOSu7kt6oIJLixfffKrpXqQ9s=
|
||||
google.golang.org/protobuf v1.35.1 h1:m3LfL6/Ca+fqnjnlqQXNpFPABW1UD7mjh8KO2mKFytA=
|
||||
google.golang.org/protobuf v1.35.1/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
|
||||
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
|
||||
gopkg.in/evanphx/json-patch.v4 v4.12.0 h1:n6jtcsulIzXPJaxegRbvFNNrZDjbij7ny3gmSPG+6V4=
|
||||
gopkg.in/evanphx/json-patch.v4 v4.12.0/go.mod h1:p8EYWUEYMpynmqDbY58zCKCFZw8pRWMG4EsWvDvM72M=
|
||||
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY=
|
||||
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys=
|
||||
gopkg.in/inf.v0 v0.9.1 h1:73M5CoZyi3ZLMOyDlQh031Cx6N9NDJ2Vvfl76EDAgDc=
|
||||
gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw=
|
||||
gopkg.in/ini.v1 v1.57.0 h1:9unxIsFcTt4I55uWluz+UmL95q4kdJ0buvQ1ZIqVQww=
|
||||
gopkg.in/ini.v1 v1.57.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
|
||||
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw=
|
||||
gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v2 v2.2.3/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
|
||||
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
|
||||
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
||||
honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
||||
honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
||||
k8s.io/api v0.32.2 h1:bZrMLEkgizC24G9eViHGOPbW+aRo9duEISRIJKfdJuw=
|
||||
k8s.io/api v0.32.2/go.mod h1:hKlhk4x1sJyYnHENsrdCWw31FEmCijNGPJO5WzHiJ6Y=
|
||||
k8s.io/apimachinery v0.32.2 h1:yoQBR9ZGkA6Rgmhbp/yuT9/g+4lxtsGYwW6dR6BDPLQ=
|
||||
k8s.io/apimachinery v0.32.2/go.mod h1:GpHVgxoKlTxClKcteaeuF1Ul/lDVb74KpZcxcmLDElE=
|
||||
k8s.io/client-go v0.32.2 h1:4dYCD4Nz+9RApM2b/3BtVvBHw54QjMFUl1OLcJG5yOA=
|
||||
k8s.io/client-go v0.32.2/go.mod h1:fpZ4oJXclZ3r2nDOv+Ux3XcJutfrwjKTCHz2H3sww94=
|
||||
k8s.io/klog/v2 v2.130.1 h1:n9Xl7H1Xvksem4KFG4PYbdQCQxqc/tTUyrgXaOhHSzk=
|
||||
k8s.io/klog/v2 v2.130.1/go.mod h1:3Jpz1GvMt720eyJH1ckRHK1EDfpxISzJ7I9OYgaDtPE=
|
||||
k8s.io/kube-openapi v0.0.0-20241105132330-32ad38e42d3f h1:GA7//TjRY9yWGy1poLzYYJJ4JRdzg3+O6e8I+e+8T5Y=
|
||||
k8s.io/kube-openapi v0.0.0-20241105132330-32ad38e42d3f/go.mod h1:R/HEjbvWI0qdfb8viZUeVZm0X6IZnxAydC7YU42CMw4=
|
||||
k8s.io/utils v0.0.0-20241104100929-3ea5e8cea738 h1:M3sRQVHv7vB20Xc2ybTt7ODCeFj6JSWYFzOFnYeS6Ro=
|
||||
k8s.io/utils v0.0.0-20241104100929-3ea5e8cea738/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0=
|
||||
sigs.k8s.io/json v0.0.0-20241010143419-9aa6b5e7a4b3 h1:/Rv+M11QRah1itp8VhT6HoVx1Ray9eB4DBr+K+/sCJ8=
|
||||
sigs.k8s.io/json v0.0.0-20241010143419-9aa6b5e7a4b3/go.mod h1:18nIHnGi6636UCz6m8i4DhaJ65T6EruyzmoQqI2BVDo=
|
||||
sigs.k8s.io/structured-merge-diff/v4 v4.4.2 h1:MdmvkGuXi/8io6ixD5wud3vOLwc1rj0aNqRlpuvjmwA=
|
||||
sigs.k8s.io/structured-merge-diff/v4 v4.4.2/go.mod h1:N8f93tFZh9U6vpxwRArLiikrE5/2tiu1w1AGfACIGE4=
|
||||
sigs.k8s.io/yaml v1.4.0 h1:Mk1wCc2gy/F0THH0TAp1QYyJNzRm2KCLy3o5ASXVI5E=
|
||||
sigs.k8s.io/yaml v1.4.0/go.mod h1:Ejl7/uTz7PSA4eKMyQCUTnhZYNmLIl+5c2lQPGR2BPY=
|
||||
k8s.io/api v0.17.8 h1:8JHlbqJ3A6sGhoacXfu/sASSD+HWWqVq67qt9lyB0kU=
|
||||
k8s.io/api v0.17.8/go.mod h1:N++Llhs8kCixMUoCaXXAyMMPbo8dDVnh+IQ36xZV2/0=
|
||||
k8s.io/apimachinery v0.17.8 h1:zXvd8rYMAjRJXpILP9tdAiUnFIENM9EmHuE81apIoms=
|
||||
k8s.io/apimachinery v0.17.8/go.mod h1:Lg8zZ5iC/O8UjCqW6DNhcQG2m4TdjF9kwG3891OWbbA=
|
||||
k8s.io/client-go v0.17.8 h1:cuZSfjqVrNjoZ3wViQHljFPyWMOcgxUjjmQs5Rifbxk=
|
||||
k8s.io/client-go v0.17.8/go.mod h1:SJsDS64AAtt9VZyeaQMb4Ck5etCitZ/FwajWdzua5eY=
|
||||
k8s.io/gengo v0.0.0-20190128074634-0689ccc1d7d6/go.mod h1:ezvh/TsK7cY6rbqRK0oQQ8IAqLxYwwyPxAX1Pzy0ii0=
|
||||
k8s.io/klog v0.0.0-20181102134211-b9b56d5dfc92/go.mod h1:Gq+BEi5rUBO/HRz0bTSXDUcqjScdoY3a9IHpCEIOOfk=
|
||||
k8s.io/klog v0.3.0/go.mod h1:Gq+BEi5rUBO/HRz0bTSXDUcqjScdoY3a9IHpCEIOOfk=
|
||||
k8s.io/klog v1.0.0 h1:Pt+yjF5aB1xDSVbau4VsWe+dQNzA0qv1LlXdC2dF6Q8=
|
||||
k8s.io/klog v1.0.0/go.mod h1:4Bi6QPql/J/LkTDqv7R/cd3hPo4k2DG6Ptcz060Ez5I=
|
||||
k8s.io/klog/v2 v2.5.0 h1:8mOnjf1RmUPW6KRqQCfYSZq/K20Unmp3IhuZUhxl8KI=
|
||||
k8s.io/klog/v2 v2.5.0/go.mod h1:hy9LJ/NvuK+iVyP4Ehqva4HxZG/oXyIS3n3Jmire4Ec=
|
||||
k8s.io/kube-openapi v0.0.0-20200410145947-bcb3869e6f29/go.mod h1:F+5wygcW0wmRTnM3cOgIqGivxkwSWIWT5YdsDbeAOaU=
|
||||
k8s.io/utils v0.0.0-20191114184206-e782cd3c129f h1:GiPwtSzdP43eI1hpPCbROQCCIgCuiMMNF8YUVLF3vJo=
|
||||
k8s.io/utils v0.0.0-20191114184206-e782cd3c129f/go.mod h1:sZAwmy6armz5eXlNoLmJcl4F1QuKu7sr+mFQ0byX7Ew=
|
||||
sigs.k8s.io/structured-merge-diff/v2 v2.0.1/go.mod h1:Wb7vfKAodbKgf6tn1Kl0VvGj7mRH6DGaRcixXEJXTsE=
|
||||
sigs.k8s.io/yaml v1.1.0 h1:4A07+ZFc2wgJwo8YNlQpr1rVlgUDlxXHhPJciaPY5gs=
|
||||
sigs.k8s.io/yaml v1.1.0/go.mod h1:UJmg0vDUVViEyp3mgSv9WPwZCDxu4rQW1olrI1uml+o=
|
||||
|
|
|
@ -10,7 +10,6 @@ import (
|
|||
"time"
|
||||
|
||||
"github.com/felixge/httpsnoop"
|
||||
//nolint:staticcheck // SA1019 Can't migrate due to grpc gateway using old package and major version bump needed
|
||||
"github.com/golang/protobuf/proto"
|
||||
"github.com/grpc-ecosystem/grpc-gateway/runtime"
|
||||
log "github.com/sirupsen/logrus"
|
||||
|
@ -18,10 +17,9 @@ import (
|
|||
)
|
||||
|
||||
type messageMarshaler struct {
|
||||
fields map[string]interface{}
|
||||
exclude bool
|
||||
isSSE bool
|
||||
fieldProcessor FieldProcessor
|
||||
fields map[string]interface{}
|
||||
exclude bool
|
||||
isSSE bool
|
||||
}
|
||||
|
||||
func (m *messageMarshaler) Unmarshal(data []byte, v interface{}) error {
|
||||
|
@ -44,32 +42,12 @@ func (m *messageMarshaler) ContentType() string {
|
|||
}
|
||||
}
|
||||
|
||||
// FieldProcessor is a function that handles included/excluded fields
|
||||
type FieldProcessor func(val interface{}, fields map[string]interface{}, exclude bool) (interface{}, error)
|
||||
|
||||
func (m *messageMarshaler) Marshal(v interface{}) ([]byte, error) {
|
||||
var dataBytes []byte
|
||||
var err error
|
||||
|
||||
if len(m.fields) == 0 {
|
||||
dataBytes, err = json.Marshal(v)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
} else if m.fieldProcessor != nil {
|
||||
res, err := m.fieldProcessor(v, m.fields, m.exclude)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
dataBytes, err = json.Marshal(res)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
} else {
|
||||
dataBytes, err = json.Marshal(v)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
dataBytes, err := json.Marshal(v)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if len(m.fields) > 0 {
|
||||
if _, ok := v.([]interface{}); ok {
|
||||
data := make([]interface{}, 0)
|
||||
err = json.Unmarshal(dataBytes, &data)
|
||||
|
@ -133,7 +111,7 @@ func (m *messageMarshaler) processItem(path []string, item interface{}) {
|
|||
}
|
||||
}
|
||||
|
||||
func newMarshaler(req *http.Request, isSSE bool) *messageMarshaler {
|
||||
func newMarshaler(req *http.Request, isSSE bool) runtime.Marshaler {
|
||||
fieldsQuery := req.URL.Query().Get("fields")
|
||||
fields := make(map[string]interface{})
|
||||
exclude := false
|
||||
|
@ -267,21 +245,6 @@ func NewStreamForwarder(messageKey func(proto.Message) (string, error)) StreamFo
|
|||
runtime.ForwardResponseStream(ctx, mux, m, w, req, recv, opts...)
|
||||
}
|
||||
}
|
||||
func UnaryForwarderWithFieldProcessor(fieldProcessor FieldProcessor) func(
|
||||
ctx context.Context,
|
||||
mux *runtime.ServeMux,
|
||||
marshaler runtime.Marshaler,
|
||||
w http.ResponseWriter,
|
||||
req *http.Request,
|
||||
resp proto.Message,
|
||||
opts ...func(context.Context, http.ResponseWriter, proto.Message) error,
|
||||
) {
|
||||
return func(ctx context.Context, mux *runtime.ServeMux, marshaler runtime.Marshaler, w http.ResponseWriter, req *http.Request, resp proto.Message, opts ...func(context.Context, http.ResponseWriter, proto.Message) error) {
|
||||
m := newMarshaler(req, false)
|
||||
m.fieldProcessor = fieldProcessor
|
||||
runtime.ForwardResponseMessage(ctx, mux, m, w, req, resp, opts...)
|
||||
}
|
||||
}
|
||||
|
||||
var (
|
||||
// UnaryForwarder serializes protobuf message to JSON and removes fields using query parameter `fields`.
|
||||
|
|
|
@ -7,7 +7,6 @@ import (
|
|||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
type testStruct struct {
|
||||
|
@ -41,8 +40,8 @@ func TestMarshalerIncludeFields(t *testing.T) {
|
|||
|
||||
out, err := m.Marshal(testVal)
|
||||
|
||||
require.NoError(t, err)
|
||||
assert.JSONEq(t, `{"metadata":{"name":"test"},"spec":{"source":{"path":"test_path"}}}`, string(out))
|
||||
assert.Nil(t, err)
|
||||
assert.Equal(t, `{"metadata":{"name":"test"},"spec":{"source":{"path":"test_path"}}}`, string(out))
|
||||
}
|
||||
|
||||
func TestMarshalerExcludeFields(t *testing.T) {
|
||||
|
@ -52,8 +51,8 @@ func TestMarshalerExcludeFields(t *testing.T) {
|
|||
|
||||
out, err := m.Marshal(testVal)
|
||||
|
||||
require.NoError(t, err)
|
||||
assert.JSONEq(t, `{"metadata":{},"spec":{"source":{"path":"test_path"}},"status":{"message":"Failed"}}`, string(out))
|
||||
assert.Nil(t, err)
|
||||
assert.Equal(t, `{"metadata":{},"spec":{"source":{"path":"test_path"}},"status":{"message":"Failed"}}`, string(out))
|
||||
}
|
||||
|
||||
func TestMarshalerSSE(t *testing.T) {
|
||||
|
@ -61,7 +60,7 @@ func TestMarshalerSSE(t *testing.T) {
|
|||
|
||||
out, err := m.Marshal(testVal)
|
||||
|
||||
require.NoError(t, err)
|
||||
assert.Nil(t, err)
|
||||
assert.Equal(t, `data: {"metadata":{"name":"test"},"spec":{"source":{"path":"test_path"}},"status":{"message":"Failed"}}
|
||||
|
||||
`, string(out))
|
||||
|
@ -90,7 +89,7 @@ func TestFlushSuccess(t *testing.T) {
|
|||
f := flusher{w: bufio.NewWriter(&buf)}
|
||||
flush(&f)
|
||||
|
||||
assert.True(t, flushed)
|
||||
assert.Equal(t, true, flushed)
|
||||
}
|
||||
|
||||
func TestFlushFailed(t *testing.T) {
|
||||
|
@ -99,5 +98,5 @@ func TestFlushFailed(t *testing.T) {
|
|||
f := flusher{}
|
||||
flush(&f)
|
||||
|
||||
assert.False(t, flushed)
|
||||
assert.Equal(t, false, flushed)
|
||||
}
|
||||
|
|
|
@ -0,0 +1,86 @@
|
|||
package humanize
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"math"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
gohumanize "github.com/dustin/go-humanize"
|
||||
)
|
||||
|
||||
func Timestamp(ts time.Time) string {
|
||||
return fmt.Sprintf("%s (%s)", ts.Format("Mon Jan 02 15:04:05 -0700"), gohumanize.Time(ts))
|
||||
}
|
||||
|
||||
var relativeMagnitudes = []gohumanize.RelTimeMagnitude{
|
||||
{D: time.Second, Format: "0 seconds", DivBy: time.Second},
|
||||
{D: 2 * time.Second, Format: "1 second %s", DivBy: 1},
|
||||
{D: time.Minute, Format: "%d seconds %s", DivBy: time.Second},
|
||||
{D: 2 * time.Minute, Format: "1 minute %s", DivBy: 1},
|
||||
{D: time.Hour, Format: "%d minutes %s", DivBy: time.Minute},
|
||||
{D: 2 * time.Hour, Format: "1 hour %s", DivBy: 1},
|
||||
{D: gohumanize.Day, Format: "%d hours %s", DivBy: time.Hour},
|
||||
{D: 2 * gohumanize.Day, Format: "1 day %s", DivBy: 1},
|
||||
{D: gohumanize.Week, Format: "%d days %s", DivBy: gohumanize.Day},
|
||||
{D: 2 * gohumanize.Week, Format: "1 week %s", DivBy: 1},
|
||||
{D: gohumanize.Month, Format: "%d weeks %s", DivBy: gohumanize.Week},
|
||||
{D: 2 * gohumanize.Month, Format: "1 month %s", DivBy: 1},
|
||||
{D: gohumanize.Year, Format: "%d months %s", DivBy: gohumanize.Month},
|
||||
{D: 18 * gohumanize.Month, Format: "1 year %s", DivBy: 1},
|
||||
{D: 2 * gohumanize.Year, Format: "2 years %s", DivBy: 1},
|
||||
{D: gohumanize.LongTime, Format: "%d years %s", DivBy: gohumanize.Year},
|
||||
{D: math.MaxInt64, Format: "a long while %s", DivBy: 1},
|
||||
}
|
||||
|
||||
// TruncatedDuration returns a duration truncated to a single unit
|
||||
func TruncatedDuration(d time.Duration) string {
|
||||
start := time.Time{}
|
||||
finish := start.Add(d)
|
||||
return strings.TrimSpace(gohumanize.CustomRelTime(start, finish, "", "", relativeMagnitudes))
|
||||
}
|
||||
|
||||
// Duration humanizes time.Duration output to a meaningful value with up to two units
|
||||
func Duration(d time.Duration) string {
|
||||
if d.Seconds() < 60.0 {
|
||||
return TruncatedDuration(d)
|
||||
}
|
||||
if d.Minutes() < 60.0 {
|
||||
remainingSeconds := int64(math.Mod(d.Seconds(), 60))
|
||||
return fmt.Sprintf("%s %d seconds", TruncatedDuration(d), remainingSeconds)
|
||||
}
|
||||
if d.Hours() < 24.0 {
|
||||
remainingMinutes := int64(math.Mod(d.Minutes(), 60))
|
||||
return fmt.Sprintf("%s %d minutes", TruncatedDuration(d), remainingMinutes)
|
||||
}
|
||||
remainingHours := int64(math.Mod(d.Hours(), 24))
|
||||
return fmt.Sprintf("%s %d hours", TruncatedDuration(d), remainingHours)
|
||||
}
|
||||
|
||||
// RelativeDuration returns a formatted duration from the relative times
|
||||
func RelativeDuration(start, finish time.Time) string {
|
||||
if finish.IsZero() && !start.IsZero() {
|
||||
finish = time.Now().UTC()
|
||||
}
|
||||
return Duration(finish.Sub(start))
|
||||
}
|
||||
|
||||
var shortTimeMagnitudes = []gohumanize.RelTimeMagnitude{
|
||||
{D: time.Second, Format: "0s", DivBy: time.Second},
|
||||
{D: 2 * time.Second, Format: "1s %s", DivBy: 1},
|
||||
{D: time.Minute, Format: "%ds %s", DivBy: time.Second},
|
||||
{D: 2 * time.Minute, Format: "1m %s", DivBy: 1},
|
||||
{D: time.Hour, Format: "%dm %s", DivBy: time.Minute},
|
||||
{D: 2 * time.Hour, Format: "1h %s", DivBy: 1},
|
||||
{D: gohumanize.Day, Format: "%dh %s", DivBy: time.Hour},
|
||||
{D: 2 * gohumanize.Day, Format: "1d %s", DivBy: 1},
|
||||
{D: gohumanize.Week, Format: "%dd %s", DivBy: gohumanize.Day},
|
||||
}
|
||||
|
||||
// RelativeDurationShort returns a relative duration in short format
|
||||
func RelativeDurationShort(start, finish time.Time) string {
|
||||
if finish.IsZero() && !start.IsZero() {
|
||||
finish = time.Now().UTC()
|
||||
}
|
||||
return strings.TrimSpace(gohumanize.CustomRelTime(start, finish, "", "", shortTimeMagnitudes))
|
||||
}
|
|
@ -0,0 +1,68 @@
|
|||
package humanize
|
||||
|
||||
import (
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
// TestRelativeDurationShort tests RelativeDurationShort
|
||||
func TestRelativeDurationShort(t *testing.T) {
|
||||
start := time.Now()
|
||||
end := start
|
||||
assert.Equal(t, "0s", RelativeDurationShort(start, end))
|
||||
|
||||
start = time.Now().Add(-1 * time.Hour)
|
||||
end = time.Time{}
|
||||
assert.Equal(t, "1h", RelativeDurationShort(start, end))
|
||||
|
||||
start = time.Now().Add(-1 * (time.Hour + 30*time.Minute))
|
||||
end = time.Time{}
|
||||
assert.Equal(t, "1h", RelativeDurationShort(start, end))
|
||||
|
||||
start = time.Time{}
|
||||
end = time.Time{}
|
||||
assert.Equal(t, "0s", RelativeDurationShort(start, end))
|
||||
}
|
||||
|
||||
// TestDuration tests Duration
|
||||
func TestDuration(t *testing.T) {
|
||||
assert.Equal(t, "1 second", Duration(time.Second))
|
||||
assert.Equal(t, "1 minute 0 seconds", Duration(time.Minute))
|
||||
assert.Equal(t, "1 hour 0 minutes", Duration(time.Hour))
|
||||
assert.Equal(t, "2 hours 0 minutes", Duration(2*time.Hour))
|
||||
assert.Equal(t, "10 hours 0 minutes", Duration(10*time.Hour))
|
||||
assert.Equal(t, "1 day 0 hours", Duration(24*time.Hour))
|
||||
}
|
||||
|
||||
// TestDuration tests Duration
|
||||
func TestRelativeDuration(t *testing.T) {
|
||||
start := time.Now()
|
||||
end := start
|
||||
assert.Equal(t, "0 seconds", RelativeDuration(start, end))
|
||||
|
||||
start = time.Now()
|
||||
end = start.Add(-1 * time.Second)
|
||||
assert.Equal(t, "1 second", RelativeDuration(start, end))
|
||||
|
||||
start = time.Now()
|
||||
end = start.Add(-59 * time.Second)
|
||||
assert.Equal(t, "59 seconds", RelativeDuration(start, end))
|
||||
|
||||
start = time.Now().Add(-90 * time.Second)
|
||||
end = time.Time{}
|
||||
assert.Equal(t, "1 minute 30 seconds", RelativeDuration(start, end))
|
||||
|
||||
start = time.Now().Add(-1 * time.Hour)
|
||||
end = time.Time{}
|
||||
assert.Equal(t, "1 hour 0 minutes", RelativeDuration(start, end))
|
||||
|
||||
start = time.Now().Add(-1 * (time.Hour + 30*time.Minute))
|
||||
end = time.Time{}
|
||||
assert.Equal(t, "1 hour 30 minutes", RelativeDuration(start, end))
|
||||
|
||||
start = time.Time{}
|
||||
end = time.Time{}
|
||||
assert.Equal(t, "0 seconds", RelativeDuration(start, end))
|
||||
}
|
|
@ -0,0 +1,36 @@
|
|||
package json
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
)
|
||||
|
||||
// DisallowUnknownFields configures the JSON decoder to error out if unknown
|
||||
// fields come along, instead of dropping them by default.
|
||||
func DisallowUnknownFields(d *json.Decoder) *json.Decoder {
|
||||
d.DisallowUnknownFields()
|
||||
return d
|
||||
}
|
||||
|
||||
// JSONOpt is a decoding option for decoding from JSON format.
|
||||
type JSONOpt func(*json.Decoder) *json.Decoder
|
||||
|
||||
// Unmarshal is a convenience wrapper around json.Unmarshal to support json decode options
|
||||
func Unmarshal(j []byte, o interface{}, opts ...JSONOpt) error {
|
||||
d := json.NewDecoder(bytes.NewReader(j))
|
||||
for _, opt := range opts {
|
||||
d = opt(d)
|
||||
}
|
||||
return d.Decode(&o)
|
||||
}
|
||||
|
||||
// UnmarshalStrict is a convenience wrapper around json.Unmarshal with strict unmarshal options
|
||||
func UnmarshalStrict(j []byte, o interface{}) error {
|
||||
return Unmarshal(j, o, DisallowUnknownFields)
|
||||
}
|
||||
|
||||
// IsJSON tests whether or not the suppied byte array is valid JSON
|
||||
func IsJSON(j []byte) bool {
|
||||
var js json.RawMessage
|
||||
return json.Unmarshal(j, &js) == nil
|
||||
}
|
|
@ -0,0 +1,43 @@
|
|||
package json
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
// TestDisallowUnknownFields tests ability to disallow unknown fields
|
||||
func TestDisallowUnknownFields(t *testing.T) {
|
||||
type mystruct struct {
|
||||
MyField string `json:"myField"`
|
||||
}
|
||||
jsonWithUnknownField := []byte(`
|
||||
{
|
||||
"myField": "foo",
|
||||
"unknown": "bar"
|
||||
}
|
||||
`)
|
||||
var obj mystruct
|
||||
|
||||
err := Unmarshal(jsonWithUnknownField, &obj)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, "foo", obj.MyField)
|
||||
|
||||
obj = mystruct{}
|
||||
err = Unmarshal(jsonWithUnknownField, &obj, DisallowUnknownFields)
|
||||
assert.Error(t, err)
|
||||
assert.Equal(t, "foo", obj.MyField)
|
||||
|
||||
obj = mystruct{}
|
||||
err = UnmarshalStrict(jsonWithUnknownField, &obj)
|
||||
assert.Error(t, err)
|
||||
assert.Equal(t, "foo", obj.MyField)
|
||||
}
|
||||
|
||||
func TestIsJSON(t *testing.T) {
|
||||
assert.True(t, IsJSON([]byte(`"foo"`)))
|
||||
assert.True(t, IsJSON([]byte(`{"a": "b"}`)))
|
||||
assert.True(t, IsJSON([]byte(`[{"a": "b"}]`)))
|
||||
assert.False(t, IsJSON([]byte(`foo`)))
|
||||
assert.False(t, IsJSON([]byte(`foo: bar`)))
|
||||
}
|
|
@ -0,0 +1,110 @@
|
|||
// The package provides a way to create compact JWTs, "zJWT".
|
||||
//
|
||||
// zJWTs for JWT with a large payload can be 1/3 the size of the original.
|
||||
// This is achieved by gzipping the payload before base 64 encoding it.
|
||||
//
|
||||
// zJWTs are only compact if they are smaller than the original JWTs.
|
||||
// as long as they are smaller than the zJWT representation.
|
||||
//
|
||||
// To help differentiate, zJWTs start with "zJWT/v1:"
|
||||
package zjwt
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"compress/gzip"
|
||||
"encoding/base64"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"strings"
|
||||
)
|
||||
|
||||
var encoding = base64.RawStdEncoding
|
||||
|
||||
// some magic text that is easy to search the Internet for and find your way to docs
|
||||
var magicNumber = "zJWT/v1"
|
||||
|
||||
// when to use ZJWT
|
||||
// - "never" - never use it - good for it goes wrong
|
||||
// - "" - when better
|
||||
// - "always" - always use it - good for forcing this on for testing
|
||||
var featureFlag = os.Getenv("ARGO_ZJWT_FEATURE_FLAG")
|
||||
|
||||
// the smallest size JWT we'll compress, 3k chosen as cookies max out at 4k
|
||||
var minSize = 3000
|
||||
|
||||
// ZJWT turns a JWT into either a zJWT or return the original JWT, whichever is smaller.
|
||||
func ZJWT(text string) (string, error) {
|
||||
if featureFlag == "never" || featureFlag != "always" && len(text) < minSize {
|
||||
return text, nil
|
||||
}
|
||||
parts := strings.SplitN(text, ".", 3)
|
||||
if len(parts) != 3 {
|
||||
return "", fmt.Errorf("JWT '%s' should have 3 dot-delimited parts", text)
|
||||
}
|
||||
header := parts[0]
|
||||
payload := parts[1]
|
||||
signature := parts[2]
|
||||
decodedPayload, err := encoding.DecodeString(payload)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
var buf bytes.Buffer
|
||||
w := gzip.NewWriter(&buf)
|
||||
_, err = w.Write(decodedPayload)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
err = w.Flush()
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
err = w.Close()
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
compressedPayload := encoding.EncodeToString(buf.Bytes())
|
||||
zJWT := fmt.Sprintf("%s.%s.%s.%s", magicNumber, header, compressedPayload, signature)
|
||||
if featureFlag == "always" || len(zJWT) < len(text) {
|
||||
return zJWT, nil
|
||||
} else {
|
||||
return text, nil
|
||||
}
|
||||
}
|
||||
|
||||
// JWT expands either a zJWT or a JWT to a JWT.
|
||||
func JWT(text string) (string, error) {
|
||||
parts := strings.SplitN(text, ".", 4)
|
||||
if len(parts) == 3 {
|
||||
return text, nil
|
||||
}
|
||||
if len(parts) != 4 {
|
||||
return "", fmt.Errorf("zJWT '%s' should have 4 dot-delimited parts", text)
|
||||
}
|
||||
part0 := parts[0]
|
||||
if part0 != magicNumber {
|
||||
return "", fmt.Errorf("the first part of the zJWT '%s' does not equal '%s'", part0, magicNumber)
|
||||
}
|
||||
header := parts[1]
|
||||
payload := parts[2]
|
||||
signature := parts[3]
|
||||
decodedPayload, err := encoding.DecodeString(payload)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
r, err := gzip.NewReader(bytes.NewReader(decodedPayload))
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
uncompressedPayload, err := ioutil.ReadAll(r)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
err = r.Close()
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
encodedPayload := encoding.EncodeToString(uncompressedPayload)
|
||||
jwt := fmt.Sprintf("%s.%s.%s", header, encodedPayload, signature)
|
||||
return jwt, nil
|
||||
}
|
|
@ -0,0 +1,49 @@
|
|||
package zjwt
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func init() {
|
||||
minSize = 0
|
||||
}
|
||||
|
||||
func TestCompactor(t *testing.T) {
|
||||
t.Run("Small", func(t *testing.T) {
|
||||
jwt := `eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.EpM5XBzTJZ4J8AfoJEcJrjth8pfH28LWdjLo90sYb9g`
|
||||
compactJWT, err := ZJWT(jwt)
|
||||
assert.NoError(t, err)
|
||||
expandedJWT, err := JWT(compactJWT)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, jwt, expandedJWT)
|
||||
assert.Equal(t, jwt, compactJWT)
|
||||
assert.Equal(t, 100, 100*len(compactJWT)/len(jwt))
|
||||
})
|
||||
t.Run("Large", func(t *testing.T) {
|
||||
jwt := `eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyLCJncm91cHMiOlsiU0JHIiwiU0JHOk1lbWJlcnMiLCJTQkc6dWkiLCJTQkc6emlvbiIsIlNCRzpoYXJtb255LXVpLWNvbXBvbmVudHMtZXh0ZW5kZWQiLCJTQkc6bmV4Z2VuLXNlcnZpY2VzLXRlYW0iLCJTQkc6c2JnLXFhLXdyaXRlIiwiU0JHOmFkbWluLXFiYS11aS1hdXRvbWF0aW9uIiwiU0JHOnNiZy9xYm8tYXV0b21hdGlvbi10ZWFtLWFkbWluIiwiU0JHOmdpdGxhYi10b29scy13cml0ZSIsIlNCRzpTQkctT2ZmZXJpbmdzLVFFIiwiU0JHOnFiby1iaWxsaW5nLXRlc3QtcWUtdGVhbSIsIlNCRzpxYm8tYmlsbGluZy10ZXN0LWFkbWluIiwiU0JHOnFiYS1xdWFsaXR5LXRlYW0iLCJTQkc6cWJvLWZ0dS1xdWFsaXR5LWFkbWluLXRlYW0iLCJTQkc6cWJhLW1pZ3JhdGlvbi10b29scy10ZWFtIiwiU0JHOlhjZWxsZW5jZUZvcnVtSlMiLCJTQkc6dHJpbml0eWpzLW1vYmlsZSIsIm1vbmV5LWFuZC1tYWdpYyIsIm1vbmV5LWFuZC1tYWdpYzpPd25lcnMiLCJtb25leS1hbmQtbWFnaWM6TSAmIE0gU2NydW0gVGVhbSIsInBheXJvbGwiLCJwYXlyb2xsOkRFUFJFQ0FURURfT3duZXJzIiwicGF5cm9sbDpQYXlyb2xsQWxsIiwiU0JHLVBDUy1DSUNEIiwiU0JHLVBDUy1DSUNEOk93bmVycyIsIlNCLVFCTy1RRS1PZmZlcmluZ3MiLCJTQi1RQk8tUUUtT2ZmZXJpbmdzOk93bmVycyIsIlNCLVFCTy1RRS1PZmZlcmluZ3M6cWJvLWxxYS1hZG1pbiIsIlNCLVFCTy1RRS1PZmZlcmluZ3M6UUUtR3JvdXAiLCJTQi1RQk8tUUUtUGF5bWVudHMiLCJTQi1RQk8tUUUtUGF5bWVudHM6T3duZXJzIiwiU0ItUUJPLVFFLVBheW1lbnRzOnFiby1wYXltZW50cy1xZS10ZWFtIiwiU0ItUUJPLVFFLVBheXJvbGwiLCJTQi1RQk8tUUUtUGF5cm9sbDpPd25lcnMiLCJTQi1RQk8tUUUtUGF5cm9sbDpTQi1RQk8tUUUtUGF5cm9sbCIsIlNCLVFCTy1RRS1QYXlyb2xsOnBheXJvbGwtcWUtdGVhbSIsIml0YWciLCJpdGFnOml0YWctdWkiLCJpdGFnOml0YWctd3MiLCJpdGFnOm1hcnRpbmktanMiLCJnaXRsYWItbWlncmF0aW9uLTAwMDEiLCJnaXRsYWItbWlncmF0aW9uLTAwMDE6T3duZXJzIiwiZ2l0bGFiLW1pZ3JhdGlvbi0wMDAyIiwiZ2l0bGFiLW1pZ3JhdGlvbi0wMDAyOk93bmVycyIsImdpdGxhYi1taWdyYXRpb24tMDA0NSIsImdpdGxhYi1taWdyYXRpb24tMDA0NTpPd25lcnMiLCJnaXRsYWItbWlncmF0aW9uLTAwNDYiLCJnaXRsYWItbWlncmF0aW9uLTAwNDY6T3duZXJzIiwiU0JHLVBsdWdpbnMiLCJTQkctUGx1Z2luczpDb3JlIiwiU0JHLVBsdWdpbnM6aW1wcm92ZWQtaW52ZW50b3J5IiwiU0JHLVBsdWdpbnM6d2ludm9pY2UtZ21haWwiLCJBbHRpbWV0cmlrIiwiQWx0aW1ldHJpazpBbHRpbWV0cmlrIiwiU0ItUUJPLVFFLU9mZmVyaW5ncy1Nb2JpbGUiLCJTQi1RQk8tUUUtT2ZmZXJpbmdzLU1vYmlsZTpPd25lcnMiLCJRQk8tQ29yZSIsIlFCTy1Db3JlOnFiby1qYXZhLW1vbm9saXRoLXdyaXRlLWFjY2VzcyIsIlFCTy1JbnZlbnRvcnkiLCJRQk8tSW52ZW50b3J5OkFkbWlucyIsIlFCTy1JbnZlbnRvcnk6Q29udHJpYnV0b3JzIiwiUUJPLUludmVudG9yeTpDSUNEIiwiUUJPLUJpbGxpbmciLCJRQk8tQmlsbGluZzpCaWxsaW5nLVVJLUFkbWluIiwiU0JHLVFFIiwiZmFicmljLWFwcC1zaGVsbHMiLCJmYWJyaWMtYXBwLXNoZWxsczpNb2JpbGUgU2hlbGwiLCJmYWJyaWMtYXBwLXVpLWNvbXBvbmVudHMiLCJmYWJyaWMtYXBwLXVpLWNvbXBvbmVudHM6ZmFicmljLWFwcC11aS1jb21wb25lbnRzLW1lbWJlcnMiLCJmYWJyaWMtYXBwLXVpLWNvbXBvbmVudHM6SURTIEV4dGVuZGVkIENvcmUgVGVhbSIsImRldi10ZXN0IiwiZGV2LXRlc3Q6VEVQIiwidGVzdC1wbGF0Zm9ybSIsInRlc3QtcGxhdGZvcm06dGVzdC1wbGF0Zm9ybS1vcmctYWRtaW5zIiwidGVzdC1wbGF0Zm9ybTpvdmVyd2F0Y2gtY29yZSIsIm9wZW4tdWktY29tcG9uZW50cyIsIlNCU0VHLUVQSUMiLCJhY2NvdW50aW5nLWNvcmUiLCJhY2NvdW50aW5nLWNvcmU6YWNjb3VudGluZy1jb3JlLXFiby1jYXNlY2VudGVyLXVpIiwia3ViZXJuZXRlcyIsImt1YmVybmV0ZXM6c2Jnc2VnLWNkcC10ZWFtIiwic3ZjLXNic2VnLWNkcCIsIlNVRFMiLCJTVURTOlNVRFMiLCJhcHAtc2hlbGwiLCJhcHAtc2hlbGw6YXJnb2NkLWFkbWlucyIsIlNCRy1UcmlhZ2VCb3QiLCJzYW5kYm94LXNhbmRib3giLCJzYW5kYm94LXNhbmRib3g6dXgtd29ya3Nob3AtcmFqIiwic2FuZGJveC1zYW5kYm94OnV4LXdzLXJhaiIsInNhbmRib3gtc2FuZGJveDpyYWotdGVzdC13cyIsInBheXJvbGwtcGF5Y2hlY2siLCJwYXlyb2xsLXBheWNoZWNrOlNCR1NDVEVQIiwiYXBwLXVpY29tcG9uZW50cyIsImFwcC11aWNvbXBvbmVudHM6UGxheWdyb3VuZC1ydmFzaWthcmxhIiwiYXBwLXVpY29tcG9uZW50czphcHAtdWljb21wb25lbnRzLWlkcy1kYXNoYm9hcmQtdWkiLCJhcHAtdWljb21wb25lbnRzOmlkcy1wbGF5Z3JvdW5kLXVpIiwiVVgtSW5mcmEiLCJVWC1JbmZyYTpDb3JlIiwiZGVzaWduLXN5c3RlbXMiXX0.XDozAEiz9AIVkA3DFbPOKG6msM43gT5zup3oxsHg_4Q`
|
||||
compactJWT, err := ZJWT(jwt)
|
||||
assert.NoError(t, err)
|
||||
expandedJWT, err := JWT(compactJWT)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, jwt, expandedJWT)
|
||||
assert.Equal(t, 37, 100*len(compactJWT)/len(jwt))
|
||||
})
|
||||
}
|
||||
|
||||
func TestCompact(t *testing.T) {
|
||||
_, err := ZJWT(".")
|
||||
assert.Error(t, err)
|
||||
}
|
||||
|
||||
func TestExpand(t *testing.T) {
|
||||
_, err := JWT(".")
|
||||
assert.Error(t, err)
|
||||
_, err = JWT("...")
|
||||
assert.Error(t, err)
|
||||
_, err = JWT("zJWT/v1:...")
|
||||
assert.Error(t, err)
|
||||
_, err = JWT("zJWT/v1:..!.")
|
||||
assert.Error(t, err)
|
||||
}
|
|
@ -0,0 +1,20 @@
|
|||
package cli
|
||||
|
||||
import (
|
||||
"os"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
"k8s.io/client-go/tools/clientcmd"
|
||||
)
|
||||
|
||||
// AddKubectlFlagsToCmd adds kubectl like flags to a command and returns the ClientConfig interface
|
||||
// for retrieving the values.
|
||||
func AddKubectlFlagsToCmd(cmd *cobra.Command) clientcmd.ClientConfig {
|
||||
loadingRules := clientcmd.NewDefaultClientConfigLoadingRules()
|
||||
loadingRules.DefaultClientConfig = &clientcmd.DefaultClientConfig
|
||||
overrides := clientcmd.ConfigOverrides{}
|
||||
kflags := clientcmd.RecommendedConfigOverrideFlags("")
|
||||
cmd.PersistentFlags().StringVar(&loadingRules.ExplicitPath, "kubeconfig", "", "Path to a kube config. Only required if out-of-cluster")
|
||||
clientcmd.BindOverrideFlags(&overrides, cmd.PersistentFlags(), kflags)
|
||||
return clientcmd.NewInteractiveDeferredLoadingClientConfig(loadingRules, &overrides, os.Stdin)
|
||||
}
|
|
@ -0,0 +1,27 @@
|
|||
package errors
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"strings"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
apierr "k8s.io/apimachinery/pkg/api/errors"
|
||||
)
|
||||
|
||||
// IsRequestEntityTooLargeErr determines if err is an error which indicates the size of the request
|
||||
// was too large for the server to handle.
|
||||
func IsRequestEntityTooLargeErr(err error) bool {
|
||||
err = errors.Cause(err)
|
||||
switch t := err.(type) {
|
||||
case apierr.APIStatus:
|
||||
if t.Status().Code == http.StatusRequestEntityTooLarge {
|
||||
return true
|
||||
}
|
||||
// This also manifest with a 500 error with the message:
|
||||
// etcdserver: request is too large
|
||||
if strings.Contains(t.Status().Message, "request is too large") {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
|
@ -0,0 +1,34 @@
|
|||
package errors
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
apierr "k8s.io/apimachinery/pkg/api/errors"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
)
|
||||
|
||||
// TestIsRequestEntityTooLargeErr test
|
||||
func TestIsRequestEntityTooLargeErr(t *testing.T) {
|
||||
assert.False(t, IsRequestEntityTooLargeErr(nil))
|
||||
|
||||
err := &apierr.StatusError{ErrStatus: metav1.Status{
|
||||
Status: metav1.StatusFailure,
|
||||
Code: http.StatusRequestEntityTooLarge,
|
||||
}}
|
||||
assert.True(t, IsRequestEntityTooLargeErr(err))
|
||||
|
||||
err = &apierr.StatusError{ErrStatus: metav1.Status{
|
||||
Status: metav1.StatusFailure,
|
||||
Code: http.StatusInternalServerError,
|
||||
Message: "etcdserver: request is too large",
|
||||
}}
|
||||
assert.True(t, IsRequestEntityTooLargeErr(err))
|
||||
|
||||
err = &apierr.StatusError{ErrStatus: metav1.Status{
|
||||
Status: metav1.StatusFailure,
|
||||
Code: http.StatusInternalServerError,
|
||||
}}
|
||||
assert.False(t, IsRequestEntityTooLargeErr(err))
|
||||
}
|
|
@ -0,0 +1,46 @@
|
|||
package unstructured
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
"k8s.io/apimachinery/pkg/runtime/schema"
|
||||
"k8s.io/apimachinery/pkg/watch"
|
||||
"k8s.io/client-go/dynamic"
|
||||
"k8s.io/client-go/informers/internalinterfaces"
|
||||
"k8s.io/client-go/tools/cache"
|
||||
)
|
||||
|
||||
// NewUnstructuredInformer constructs a new informer for Unstructured type.
|
||||
// Always prefer using an informer factory to get a shared informer instead of getting an independent
|
||||
// one. This reduces memory footprint and number of connections to the server.
|
||||
func NewUnstructuredInformer(resource schema.GroupVersionResource, client dynamic.Interface, namespace string, resyncPeriod time.Duration, indexers cache.Indexers) cache.SharedIndexInformer {
|
||||
return NewFilteredUnstructuredInformer(resource, client, namespace, resyncPeriod, indexers, nil)
|
||||
}
|
||||
|
||||
// NewFilteredUnstructuredInformer constructs a new informer for Unstructured type.
|
||||
// Always prefer using an informer factory to get a shared informer instead of getting an independent
|
||||
// one. This reduces memory footprint and number of connections to the server.
|
||||
func NewFilteredUnstructuredInformer(resource schema.GroupVersionResource, client dynamic.Interface, namespace string, resyncPeriod time.Duration, indexers cache.Indexers, tweakListOptions internalinterfaces.TweakListOptionsFunc) cache.SharedIndexInformer {
|
||||
return cache.NewSharedIndexInformer(
|
||||
&cache.ListWatch{
|
||||
ListFunc: func(options metav1.ListOptions) (runtime.Object, error) {
|
||||
if tweakListOptions != nil {
|
||||
tweakListOptions(&options)
|
||||
}
|
||||
return client.Resource(resource).Namespace(namespace).List(options)
|
||||
},
|
||||
WatchFunc: func(options metav1.ListOptions) (watch.Interface, error) {
|
||||
if tweakListOptions != nil {
|
||||
tweakListOptions(&options)
|
||||
}
|
||||
return client.Resource(resource).Namespace(namespace).Watch(options)
|
||||
},
|
||||
},
|
||||
&unstructured.Unstructured{},
|
||||
resyncPeriod,
|
||||
indexers,
|
||||
)
|
||||
}
|
|
@ -2,7 +2,7 @@ package kubeclientmetrics
|
|||
|
||||
import (
|
||||
"encoding/json"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"path"
|
||||
"regexp"
|
||||
|
@ -103,7 +103,7 @@ func handleCreate(r *http.Request) ResourceInfo {
|
|||
log.WithField("Kind", kind).Warnf("Unable to Process Create request: %v", err)
|
||||
return ResourceInfo{}
|
||||
}
|
||||
body, err := io.ReadAll(bodyIO)
|
||||
body, err := ioutil.ReadAll(bodyIO)
|
||||
if err != nil {
|
||||
log.WithField("Kind", kind).Warnf("Unable to Process Create request: %v", err)
|
||||
return ResourceInfo{}
|
||||
|
|
|
@ -1,7 +1,6 @@
|
|||
package kubeclientmetrics
|
||||
|
||||
import (
|
||||
"context"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"net/url"
|
||||
|
@ -24,19 +23,10 @@ type fakeWrapper struct {
|
|||
func (f fakeWrapper) RoundTrip(r *http.Request) (*http.Response, error) {
|
||||
resp := httptest.NewRecorder()
|
||||
resp.Code = 201
|
||||
assert.Equal(f.t, f.expectedCount, f.currentCount)
|
||||
assert.Equal(f.t, f.currentCount, f.expectedCount)
|
||||
return resp.Result(), nil
|
||||
}
|
||||
|
||||
func NewConfig(url string) *rest.Config {
|
||||
return &rest.Config{
|
||||
Host: url,
|
||||
ContentConfig: rest.ContentConfig{
|
||||
ContentType: "application/json",
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// TestWrappingTwice Ensures that the config doesn't lose any previous wrappers and the previous wrapper
|
||||
// gets executed first
|
||||
func TestAddMetricsTransportWrapperWrapTwice(t *testing.T) {
|
||||
|
@ -58,7 +48,7 @@ func TestAddMetricsTransportWrapperWrapTwice(t *testing.T) {
|
|||
})
|
||||
|
||||
client := kubernetes.NewForConfigOrDie(newConfig)
|
||||
_, _ = client.AppsV1().ReplicaSets(metav1.NamespaceDefault).Get(context.Background(), "test", metav1.GetOptions{})
|
||||
_, _ = client.AppsV1().ReplicaSets(metav1.NamespaceDefault).Get("test", metav1.GetOptions{})
|
||||
// Ensures second wrapper added by AddMetricsTransportWrapper is executed
|
||||
assert.Equal(t, 1, currentCount)
|
||||
}
|
||||
|
@ -296,7 +286,9 @@ func TestGetRequest(t *testing.T) {
|
|||
}))
|
||||
defer ts.Close()
|
||||
executed := false
|
||||
config := NewConfig(ts.URL)
|
||||
config := &rest.Config{
|
||||
Host: ts.URL,
|
||||
}
|
||||
newConfig := AddMetricsTransportWrapper(config, func(info ResourceInfo) error {
|
||||
assert.Equal(t, expectedStatusCode, info.StatusCode)
|
||||
assert.Equal(t, "replicasets", info.Kind)
|
||||
|
@ -307,7 +299,7 @@ func TestGetRequest(t *testing.T) {
|
|||
return nil
|
||||
})
|
||||
client := kubernetes.NewForConfigOrDie(newConfig)
|
||||
_, _ = client.AppsV1().ReplicaSets(metav1.NamespaceDefault).Get(context.Background(), "test", metav1.GetOptions{})
|
||||
_, _ = client.AppsV1().ReplicaSets(metav1.NamespaceDefault).Get("test", metav1.GetOptions{})
|
||||
assert.True(t, executed)
|
||||
}
|
||||
|
||||
|
@ -318,18 +310,20 @@ func TestListRequest(t *testing.T) {
|
|||
}))
|
||||
defer ts.Close()
|
||||
executed := false
|
||||
config := NewConfig(ts.URL)
|
||||
config := &rest.Config{
|
||||
Host: ts.URL,
|
||||
}
|
||||
newConfig := AddMetricsTransportWrapper(config, func(info ResourceInfo) error {
|
||||
assert.Equal(t, expectedStatusCode, info.StatusCode)
|
||||
assert.Equal(t, "replicasets", info.Kind)
|
||||
assert.Equal(t, metav1.NamespaceDefault, info.Namespace)
|
||||
assert.Empty(t, info.Name)
|
||||
assert.Equal(t, "", info.Name)
|
||||
assert.Equal(t, List, info.Verb)
|
||||
executed = true
|
||||
return nil
|
||||
})
|
||||
client := kubernetes.NewForConfigOrDie(newConfig)
|
||||
_, _ = client.AppsV1().ReplicaSets(metav1.NamespaceDefault).List(context.Background(), metav1.ListOptions{})
|
||||
_, _ = client.AppsV1().ReplicaSets(metav1.NamespaceDefault).List(metav1.ListOptions{})
|
||||
assert.True(t, executed)
|
||||
}
|
||||
|
||||
|
@ -340,7 +334,9 @@ func TestCreateRequest(t *testing.T) {
|
|||
}))
|
||||
defer ts.Close()
|
||||
executed := false
|
||||
config := NewConfig(ts.URL)
|
||||
config := &rest.Config{
|
||||
Host: ts.URL,
|
||||
}
|
||||
newConfig := AddMetricsTransportWrapper(config, func(info ResourceInfo) error {
|
||||
assert.Equal(t, expectedStatusCode, info.StatusCode)
|
||||
assert.Equal(t, "replicasets", info.Kind)
|
||||
|
@ -357,7 +353,7 @@ func TestCreateRequest(t *testing.T) {
|
|||
Namespace: metav1.NamespaceDefault,
|
||||
},
|
||||
}
|
||||
_, _ = client.AppsV1().ReplicaSets(metav1.NamespaceDefault).Create(context.Background(), rs, metav1.CreateOptions{})
|
||||
_, _ = client.AppsV1().ReplicaSets(metav1.NamespaceDefault).Create(rs)
|
||||
assert.True(t, executed)
|
||||
}
|
||||
|
||||
|
@ -368,7 +364,9 @@ func TestDeleteRequest(t *testing.T) {
|
|||
}))
|
||||
defer ts.Close()
|
||||
executed := false
|
||||
config := NewConfig(ts.URL)
|
||||
config := &rest.Config{
|
||||
Host: ts.URL,
|
||||
}
|
||||
newConfig := AddMetricsTransportWrapper(config, func(info ResourceInfo) error {
|
||||
assert.Equal(t, expectedStatusCode, info.StatusCode)
|
||||
assert.Equal(t, "replicasets", info.Kind)
|
||||
|
@ -379,7 +377,7 @@ func TestDeleteRequest(t *testing.T) {
|
|||
return nil
|
||||
})
|
||||
client := kubernetes.NewForConfigOrDie(newConfig)
|
||||
_ = client.AppsV1().ReplicaSets(metav1.NamespaceDefault).Delete(context.Background(), "test", metav1.DeleteOptions{})
|
||||
_ = client.AppsV1().ReplicaSets(metav1.NamespaceDefault).Delete("test", &metav1.DeleteOptions{})
|
||||
assert.True(t, executed)
|
||||
}
|
||||
|
||||
|
@ -390,7 +388,9 @@ func TestPatchRequest(t *testing.T) {
|
|||
}))
|
||||
defer ts.Close()
|
||||
executed := false
|
||||
config := NewConfig(ts.URL)
|
||||
config := &rest.Config{
|
||||
Host: ts.URL,
|
||||
}
|
||||
newConfig := AddMetricsTransportWrapper(config, func(info ResourceInfo) error {
|
||||
assert.Equal(t, expectedStatusCode, info.StatusCode)
|
||||
assert.Equal(t, "replicasets", info.Kind)
|
||||
|
@ -401,7 +401,7 @@ func TestPatchRequest(t *testing.T) {
|
|||
return nil
|
||||
})
|
||||
client := kubernetes.NewForConfigOrDie(newConfig)
|
||||
_, _ = client.AppsV1().ReplicaSets(metav1.NamespaceDefault).Patch(context.Background(), "test", types.MergePatchType, []byte("{}"), metav1.PatchOptions{})
|
||||
_, _ = client.AppsV1().ReplicaSets(metav1.NamespaceDefault).Patch("test", types.MergePatchType, []byte("{}"))
|
||||
assert.True(t, executed)
|
||||
}
|
||||
|
||||
|
@ -412,7 +412,9 @@ func TestUpdateRequest(t *testing.T) {
|
|||
}))
|
||||
defer ts.Close()
|
||||
executed := false
|
||||
config := NewConfig(ts.URL)
|
||||
config := &rest.Config{
|
||||
Host: ts.URL,
|
||||
}
|
||||
newConfig := AddMetricsTransportWrapper(config, func(info ResourceInfo) error {
|
||||
assert.Equal(t, expectedStatusCode, info.StatusCode)
|
||||
assert.Equal(t, "replicasets", info.Kind)
|
||||
|
@ -428,7 +430,7 @@ func TestUpdateRequest(t *testing.T) {
|
|||
Name: "test",
|
||||
},
|
||||
}
|
||||
_, _ = client.AppsV1().ReplicaSets(metav1.NamespaceDefault).Update(context.Background(), rs, metav1.UpdateOptions{})
|
||||
_, _ = client.AppsV1().ReplicaSets(metav1.NamespaceDefault).Update(rs)
|
||||
assert.True(t, executed)
|
||||
}
|
||||
|
||||
|
@ -439,7 +441,9 @@ func TestUnknownRequest(t *testing.T) {
|
|||
}))
|
||||
defer ts.Close()
|
||||
executed := false
|
||||
config := NewConfig(ts.URL)
|
||||
config := &rest.Config{
|
||||
Host: ts.URL,
|
||||
}
|
||||
newConfig := AddMetricsTransportWrapper(config, func(info ResourceInfo) error {
|
||||
assert.Equal(t, expectedStatusCode, info.StatusCode)
|
||||
assert.Equal(t, Unknown, info.Verb)
|
||||
|
@ -447,6 +451,6 @@ func TestUnknownRequest(t *testing.T) {
|
|||
return nil
|
||||
})
|
||||
client := kubernetes.NewForConfigOrDie(newConfig)
|
||||
client.Discovery().RESTClient().Verb("invalid-verb").Do(context.Background())
|
||||
client.Discovery().RESTClient().Verb("invalid-verb").Do()
|
||||
assert.True(t, executed)
|
||||
}
|
||||
|
|
|
@ -0,0 +1,15 @@
|
|||
package rand
|
||||
|
||||
import (
|
||||
"crypto/rand"
|
||||
"encoding/hex"
|
||||
)
|
||||
|
||||
// RandString returns a cryptographically-secure pseudo-random alpha-numeric string of a given length
|
||||
func RandString(n int) (string, error) {
|
||||
bytes := make([]byte, n/2+1) // we need one extra letter to discard
|
||||
if _, err := rand.Read(bytes); err != nil {
|
||||
return "", err
|
||||
}
|
||||
return hex.EncodeToString(bytes)[0:n], nil
|
||||
}
|
|
@ -0,0 +1,15 @@
|
|||
package rand
|
||||
|
||||
import (
|
||||
"github.com/stretchr/testify/assert"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestRandString(t *testing.T) {
|
||||
ss, err := RandString(10)
|
||||
assert.NoError(t, err)
|
||||
assert.Len(t, ss, 10)
|
||||
ss, err = RandString(5)
|
||||
assert.NoError(t, err)
|
||||
assert.Len(t, ss, 5)
|
||||
}
|
|
@ -0,0 +1,477 @@
|
|||
package s3
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/base64"
|
||||
"encoding/json"
|
||||
"io"
|
||||
"os"
|
||||
"path"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
"github.com/aws/aws-sdk-go/aws"
|
||||
"github.com/aws/aws-sdk-go/aws/credentials/stscreds"
|
||||
"github.com/aws/aws-sdk-go/aws/session"
|
||||
"github.com/minio/minio-go/v7"
|
||||
"github.com/minio/minio-go/v7/pkg/credentials"
|
||||
"github.com/minio/minio-go/v7/pkg/encrypt"
|
||||
"github.com/minio/minio-go/v7/pkg/sse"
|
||||
"github.com/pkg/errors"
|
||||
log "github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
const nullIAMEndpoint = ""
|
||||
|
||||
type S3Client interface {
|
||||
// PutFile puts a single file to a bucket at the specified key
|
||||
PutFile(bucket, key, path string) error
|
||||
|
||||
// PutDirectory puts a complete directory into a bucket key prefix, with each file in the directory
|
||||
// a separate key in the bucket.
|
||||
PutDirectory(bucket, key, path string) error
|
||||
|
||||
// GetFile downloads a file to a local file path
|
||||
GetFile(bucket, key, path string) error
|
||||
|
||||
// OpenFile opens a file for much lower disk and memory usage that GetFile
|
||||
OpenFile(bucket, key string) (io.ReadCloser, error)
|
||||
|
||||
// KeyExists checks if object exists (and if we have permission to access)
|
||||
KeyExists(bucket, key string) (bool, error)
|
||||
|
||||
// Delete deletes the key from the bucket
|
||||
Delete(bucket, key string) error
|
||||
|
||||
// GetDirectory downloads a directory to a local file path
|
||||
GetDirectory(bucket, key, path string) error
|
||||
|
||||
// ListDirectory list the contents of a directory/bucket
|
||||
ListDirectory(bucket, keyPrefix string) ([]string, error)
|
||||
|
||||
// IsDirectory tests if the key is acting like an s3 directory
|
||||
IsDirectory(bucket, key string) (bool, error)
|
||||
|
||||
// BucketExists returns whether a bucket exists
|
||||
BucketExists(bucket string) (bool, error)
|
||||
|
||||
// MakeBucket creates a bucket with name bucketName and options opts
|
||||
MakeBucket(bucketName string, opts minio.MakeBucketOptions) error
|
||||
}
|
||||
|
||||
type EncryptOpts struct {
|
||||
KmsKeyId string
|
||||
KmsEncryptionContext string
|
||||
Enabled bool
|
||||
ServerSideCustomerKey string
|
||||
}
|
||||
|
||||
type S3ClientOpts struct {
|
||||
Endpoint string
|
||||
Region string
|
||||
Secure bool
|
||||
AccessKey string
|
||||
SecretKey string
|
||||
Trace bool
|
||||
RoleARN string
|
||||
RoleSessionName string
|
||||
UseSDKCreds bool
|
||||
EncryptOpts EncryptOpts
|
||||
}
|
||||
|
||||
type s3client struct {
|
||||
S3ClientOpts
|
||||
minioClient *minio.Client
|
||||
ctx context.Context
|
||||
}
|
||||
|
||||
var _ S3Client = &s3client{}
|
||||
|
||||
// Get AWS credentials based on default order from aws SDK
|
||||
func GetAWSCredentials(opts S3ClientOpts) (*credentials.Credentials, error) {
|
||||
sess := session.Must(session.NewSessionWithOptions(session.Options{
|
||||
Config: aws.Config{Region: aws.String(opts.Region)},
|
||||
SharedConfigState: session.SharedConfigEnable,
|
||||
}))
|
||||
|
||||
value, err := sess.Config.Credentials.Get()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return credentials.NewStaticV4(value.AccessKeyID, value.SecretAccessKey, value.SessionToken), nil
|
||||
}
|
||||
|
||||
// GetAssumeRoleCredentials gets Assumed role credentials
|
||||
func GetAssumeRoleCredentials(opts S3ClientOpts) (*credentials.Credentials, error) {
|
||||
sess := session.Must(session.NewSession())
|
||||
|
||||
// Create the credentials from AssumeRoleProvider to assume the role
|
||||
// referenced by the "myRoleARN" ARN. Prompt for MFA token from stdin.
|
||||
|
||||
creds := stscreds.NewCredentials(sess, opts.RoleARN)
|
||||
value, err := creds.Get()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return credentials.NewStaticV4(value.AccessKeyID, value.SecretAccessKey, value.SessionToken), nil
|
||||
}
|
||||
|
||||
func GetCredentials(opts S3ClientOpts) (*credentials.Credentials, error) {
|
||||
if opts.AccessKey != "" && opts.SecretKey != "" {
|
||||
log.WithField("endpoint", opts.Endpoint).Info("Creating minio client using static credentials")
|
||||
return credentials.NewStaticV4(opts.AccessKey, opts.SecretKey, ""), nil
|
||||
} else if opts.RoleARN != "" {
|
||||
log.WithField("roleArn", opts.RoleARN).Info("Creating minio client using assumed-role credentials")
|
||||
return GetAssumeRoleCredentials(opts)
|
||||
} else if opts.UseSDKCreds {
|
||||
log.Info("Creating minio client using AWS SDK credentials")
|
||||
return GetAWSCredentials(opts)
|
||||
} else {
|
||||
log.Info("Creating minio client using IAM role")
|
||||
return credentials.NewIAM(nullIAMEndpoint), nil
|
||||
}
|
||||
}
|
||||
|
||||
// NewS3Client instantiates a new S3 client object backed
|
||||
func NewS3Client(ctx context.Context, opts S3ClientOpts) (S3Client, error) {
|
||||
s3cli := s3client{
|
||||
S3ClientOpts: opts,
|
||||
}
|
||||
s3cli.AccessKey = strings.TrimSpace(s3cli.AccessKey)
|
||||
s3cli.SecretKey = strings.TrimSpace(s3cli.SecretKey)
|
||||
var minioClient *minio.Client
|
||||
var err error
|
||||
|
||||
credentials, err := GetCredentials(opts)
|
||||
if err != nil {
|
||||
return nil, errors.WithStack(err)
|
||||
}
|
||||
|
||||
minioOpts := &minio.Options{Creds: credentials, Secure: s3cli.Secure, Region: s3cli.Region}
|
||||
minioClient, err = minio.New(s3cli.Endpoint, minioOpts)
|
||||
if err != nil {
|
||||
return nil, errors.WithStack(err)
|
||||
}
|
||||
if opts.Trace {
|
||||
minioClient.TraceOn(log.StandardLogger().Out)
|
||||
}
|
||||
|
||||
if opts.EncryptOpts.KmsKeyId != "" && opts.EncryptOpts.ServerSideCustomerKey != "" {
|
||||
return nil, errors.New("EncryptOpts.KmsKeyId and EncryptOpts.SSECPassword cannot be set together")
|
||||
}
|
||||
|
||||
if opts.EncryptOpts.ServerSideCustomerKey != "" && !opts.Secure {
|
||||
return nil, errors.New("Secure must be set if EncryptOpts.SSECPassword is set")
|
||||
}
|
||||
|
||||
s3cli.ctx = ctx
|
||||
s3cli.minioClient = minioClient
|
||||
|
||||
return &s3cli, nil
|
||||
}
|
||||
|
||||
// PutFile puts a single file to a bucket at the specified key
|
||||
func (s *s3client) PutFile(bucket, key, path string) error {
|
||||
log.WithFields(log.Fields{"endpoint": s.Endpoint, "bucket": bucket, "key": key, "path": path}).Info("Saving file to s3")
|
||||
// NOTE: minio will detect proper mime-type based on file extension
|
||||
|
||||
encOpts, err := s.EncryptOpts.buildServerSideEnc(bucket, key)
|
||||
|
||||
if err != nil {
|
||||
return errors.WithStack(err)
|
||||
}
|
||||
|
||||
_, err = s.minioClient.FPutObject(s.ctx, bucket, key, path, minio.PutObjectOptions{ServerSideEncryption: encOpts})
|
||||
if err != nil {
|
||||
return errors.WithStack(err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *s3client) BucketExists(bucketName string) (bool, error) {
|
||||
log.WithField("bucket", bucketName).Info("Checking if bucket exists")
|
||||
result, err := s.minioClient.BucketExists(s.ctx, bucketName)
|
||||
return result, errors.WithStack(err)
|
||||
}
|
||||
|
||||
func (s *s3client) MakeBucket(bucketName string, opts minio.MakeBucketOptions) error {
|
||||
log.WithFields(log.Fields{"bucket": bucketName, "region": opts.Region, "objectLocking": opts.ObjectLocking}).Info("Creating bucket")
|
||||
err := s.minioClient.MakeBucket(s.ctx, bucketName, opts)
|
||||
|
||||
if err != nil {
|
||||
return errors.WithStack(err)
|
||||
}
|
||||
|
||||
err = s.setBucketEnc(bucketName)
|
||||
return errors.WithStack(err)
|
||||
}
|
||||
|
||||
type uploadTask struct {
|
||||
key string
|
||||
path string
|
||||
}
|
||||
|
||||
func generatePutTasks(keyPrefix, rootPath string) chan uploadTask {
|
||||
rootPath = filepath.Clean(rootPath) + string(os.PathSeparator)
|
||||
uploadTasks := make(chan uploadTask)
|
||||
go func() {
|
||||
_ = filepath.Walk(rootPath, func(localPath string, fi os.FileInfo, _ error) error {
|
||||
relPath := strings.TrimPrefix(localPath, rootPath)
|
||||
if fi.IsDir() {
|
||||
return nil
|
||||
}
|
||||
if fi.Mode()&os.ModeSymlink != 0 {
|
||||
return nil
|
||||
}
|
||||
t := uploadTask{
|
||||
key: path.Join(keyPrefix, relPath),
|
||||
path: localPath,
|
||||
}
|
||||
uploadTasks <- t
|
||||
return nil
|
||||
})
|
||||
close(uploadTasks)
|
||||
}()
|
||||
return uploadTasks
|
||||
}
|
||||
|
||||
// PutDirectory puts a complete directory into a bucket key prefix, with each file in the directory
|
||||
// a separate key in the bucket.
|
||||
func (s *s3client) PutDirectory(bucket, key, path string) error {
|
||||
for putTask := range generatePutTasks(key, path) {
|
||||
err := s.PutFile(bucket, putTask.key, putTask.path)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// GetFile downloads a file to a local file path
|
||||
func (s *s3client) GetFile(bucket, key, path string) error {
|
||||
log.WithFields(log.Fields{"endpoint": s.Endpoint, "bucket": bucket, "key": key, "path": path}).Info("Getting file from s3")
|
||||
|
||||
encOpts, err := s.EncryptOpts.buildServerSideEnc(bucket, key)
|
||||
if err != nil {
|
||||
return errors.WithStack(err)
|
||||
}
|
||||
|
||||
err = s.minioClient.FGetObject(s.ctx, bucket, key, path, minio.GetObjectOptions{ServerSideEncryption: encOpts})
|
||||
if err != nil {
|
||||
return errors.WithStack(err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// OpenFile opens a file for reading
|
||||
func (s *s3client) OpenFile(bucket, key string) (io.ReadCloser, error) {
|
||||
log.WithFields(log.Fields{"endpoint": s.Endpoint, "bucket": bucket, "key": key}).Info("Opening file from s3")
|
||||
|
||||
encOpts, err := s.EncryptOpts.buildServerSideEnc(bucket, key)
|
||||
if err != nil {
|
||||
return nil, errors.WithStack(err)
|
||||
}
|
||||
f, err := s.minioClient.GetObject(s.ctx, bucket, key, minio.GetObjectOptions{ServerSideEncryption: encOpts})
|
||||
if err != nil {
|
||||
return nil, errors.WithStack(err)
|
||||
}
|
||||
// the call above doesn't return an error in the case that the key doesn't exist, but by calling Stat() it will
|
||||
_, err = f.Stat()
|
||||
if err != nil {
|
||||
return nil, errors.WithStack(err)
|
||||
}
|
||||
return f, nil
|
||||
}
|
||||
|
||||
// checks if object exists (and if we have permission to access)
|
||||
func (s *s3client) KeyExists(bucket, key string) (bool, error) {
|
||||
log.WithFields(log.Fields{"endpoint": s.Endpoint, "bucket": bucket, "key": key}).Info("Checking key exists from s3")
|
||||
|
||||
encOpts, err := s.EncryptOpts.buildServerSideEnc(bucket, key)
|
||||
if err != nil {
|
||||
return false, errors.WithStack(err)
|
||||
}
|
||||
|
||||
_, err = s.minioClient.StatObject(s.ctx, bucket, key, minio.StatObjectOptions{ServerSideEncryption: encOpts})
|
||||
if err == nil {
|
||||
return true, nil
|
||||
}
|
||||
if IsS3ErrCode(err, "NoSuchKey") {
|
||||
return false, nil
|
||||
}
|
||||
|
||||
return false, errors.WithStack(err)
|
||||
}
|
||||
|
||||
func (s *s3client) Delete(bucket, key string) error {
|
||||
log.WithFields(log.Fields{"endpoint": s.Endpoint, "bucket": bucket, "key": key}).Info("Deleting object from s3")
|
||||
return s.minioClient.RemoveObject(s.ctx, bucket, key, minio.RemoveObjectOptions{})
|
||||
}
|
||||
|
||||
// GetDirectory downloads a s3 directory to a local path
|
||||
func (s *s3client) GetDirectory(bucket, keyPrefix, path string) error {
|
||||
log.WithFields(log.Fields{"endpoint": s.Endpoint, "bucket": bucket, "key": keyPrefix, "path": path}).Info("Getting directory from s3")
|
||||
keys, err := s.ListDirectory(bucket, keyPrefix)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
for _, objKey := range keys {
|
||||
relKeyPath := strings.TrimPrefix(objKey, keyPrefix)
|
||||
localPath := filepath.Join(path, relKeyPath)
|
||||
err := s.minioClient.FGetObject(s.ctx, bucket, objKey, localPath, minio.GetObjectOptions{})
|
||||
if err != nil {
|
||||
return errors.WithStack(err)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// IsDirectory tests if the key is acting like a s3 directory. This just means it has at least one
|
||||
// object which is prefixed with the given key
|
||||
func (s *s3client) IsDirectory(bucket, keyPrefix string) (bool, error) {
|
||||
doneCh := make(chan struct{})
|
||||
defer close(doneCh)
|
||||
|
||||
if keyPrefix != "" {
|
||||
keyPrefix = filepath.Clean(keyPrefix) + "/"
|
||||
if os.PathSeparator == '\\' {
|
||||
keyPrefix = strings.ReplaceAll(keyPrefix, "\\", "/")
|
||||
}
|
||||
}
|
||||
|
||||
listOpts := minio.ListObjectsOptions{
|
||||
Prefix: keyPrefix,
|
||||
Recursive: false,
|
||||
}
|
||||
objCh := s.minioClient.ListObjects(s.ctx, bucket, listOpts)
|
||||
for obj := range objCh {
|
||||
if obj.Err != nil {
|
||||
return false, errors.WithStack(obj.Err)
|
||||
} else {
|
||||
return true, nil
|
||||
}
|
||||
}
|
||||
return false, nil
|
||||
}
|
||||
|
||||
func (s *s3client) ListDirectory(bucket, keyPrefix string) ([]string, error) {
|
||||
log.WithFields(log.Fields{"endpoint": s.Endpoint, "bucket": bucket, "key": keyPrefix}).Info("Listing directory from s3")
|
||||
|
||||
if keyPrefix != "" {
|
||||
keyPrefix = filepath.Clean(keyPrefix) + "/"
|
||||
if os.PathSeparator == '\\' {
|
||||
keyPrefix = strings.ReplaceAll(keyPrefix, "\\", "/")
|
||||
}
|
||||
}
|
||||
|
||||
doneCh := make(chan struct{})
|
||||
defer close(doneCh)
|
||||
listOpts := minio.ListObjectsOptions{
|
||||
Prefix: keyPrefix,
|
||||
Recursive: true,
|
||||
}
|
||||
var out []string
|
||||
objCh := s.minioClient.ListObjects(s.ctx, bucket, listOpts)
|
||||
for obj := range objCh {
|
||||
if obj.Err != nil {
|
||||
return nil, errors.WithStack(obj.Err)
|
||||
}
|
||||
if strings.HasSuffix(obj.Key, "/") {
|
||||
// When a dir is created through AWS S3 console, a nameless obj will be created
|
||||
// automatically, its key will be {dir_name} + "/". This obj does not display in the
|
||||
// console, but you can see it when using aws cli.
|
||||
// If obj.Key ends with "/" means it's a dir obj, we need to skip it, otherwise it
|
||||
// will be downloaded as a regular file with the same name as the dir, and it will
|
||||
// creates error when downloading the files under the dir.
|
||||
continue
|
||||
}
|
||||
out = append(out, obj.Key)
|
||||
}
|
||||
return out, nil
|
||||
}
|
||||
|
||||
// IsS3ErrCode returns if the supplied error is of a specific S3 error code
|
||||
func IsS3ErrCode(err error, code string) bool {
|
||||
err = errors.Cause(err)
|
||||
if minioErr, ok := err.(minio.ErrorResponse); ok {
|
||||
return minioErr.Code == code
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// setBucketEnc sets the encryption options on a bucket
|
||||
func (s *s3client) setBucketEnc(bucketName string) error {
|
||||
if !s.EncryptOpts.Enabled {
|
||||
return nil
|
||||
}
|
||||
|
||||
var config *sse.Configuration
|
||||
if s.EncryptOpts.KmsKeyId != "" {
|
||||
config = sse.NewConfigurationSSEKMS(s.EncryptOpts.KmsKeyId)
|
||||
} else {
|
||||
config = sse.NewConfigurationSSES3()
|
||||
}
|
||||
|
||||
log.WithFields(log.Fields{"KmsKeyId": s.EncryptOpts.KmsKeyId, "bucketName": bucketName}).Info("Setting Bucket Encryption")
|
||||
err := s.minioClient.SetBucketEncryption(s.ctx, bucketName, config)
|
||||
return err
|
||||
}
|
||||
|
||||
// buildServerSideEnc creates the minio encryption options when putting encrypted items in a bucket
|
||||
func (e *EncryptOpts) buildServerSideEnc(bucket, key string) (encrypt.ServerSide, error) {
|
||||
if e == nil || !e.Enabled {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
if e.ServerSideCustomerKey != "" {
|
||||
encryption := encrypt.DefaultPBKDF([]byte(e.ServerSideCustomerKey), []byte(bucket+key))
|
||||
|
||||
return encryption, nil
|
||||
}
|
||||
|
||||
if e.KmsKeyId != "" {
|
||||
encryptionCtx, err := parseKMSEncCntx(e.KmsEncryptionContext)
|
||||
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "failed to parse KMS encryption context")
|
||||
}
|
||||
|
||||
if encryptionCtx == nil {
|
||||
// To overcome a limitation in Minio which checks interface{} == nil.
|
||||
kms, err := encrypt.NewSSEKMS(e.KmsKeyId, nil)
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return kms, nil
|
||||
}
|
||||
|
||||
kms, err := encrypt.NewSSEKMS(e.KmsKeyId, encryptionCtx)
|
||||
|
||||
if err != nil {
|
||||
return nil, errors.WithStack(err)
|
||||
}
|
||||
|
||||
return kms, nil
|
||||
}
|
||||
|
||||
return encrypt.NewSSE(), nil
|
||||
}
|
||||
|
||||
// parseKMSEncCntx validates if kmsEncCntx is a valid JSON
|
||||
func parseKMSEncCntx(kmsEncCntx string) (*string, error) {
|
||||
if kmsEncCntx == "" {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
jsonKMSEncryptionContext, err := json.Marshal(json.RawMessage(kmsEncCntx))
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "failed to marshal KMS encryption context")
|
||||
}
|
||||
|
||||
parsedKMSEncryptionContext := base64.StdEncoding.EncodeToString([]byte(jsonKMSEncryptionContext))
|
||||
|
||||
return &parsedKMSEncryptionContext, nil
|
||||
}
|
|
@ -0,0 +1,97 @@
|
|||
package s3
|
||||
|
||||
import (
|
||||
"context"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
// TestNewS3Client tests the s3 construtor
|
||||
func TestNewS3Client(t *testing.T) {
|
||||
opts := S3ClientOpts{
|
||||
Endpoint: "foo.com",
|
||||
Region: "us-south-3",
|
||||
Secure: false,
|
||||
AccessKey: "key",
|
||||
SecretKey: "secret",
|
||||
Trace: true,
|
||||
RoleARN: "",
|
||||
RoleSessionName: "",
|
||||
UseSDKCreds: false,
|
||||
EncryptOpts: EncryptOpts{Enabled: true, ServerSideCustomerKey: "", KmsKeyId: "", KmsEncryptionContext: ""},
|
||||
}
|
||||
s3If, err := NewS3Client(context.Background(), opts)
|
||||
assert.NoError(t, err)
|
||||
s3cli := s3If.(*s3client)
|
||||
assert.Equal(t, opts.Endpoint, s3cli.Endpoint)
|
||||
assert.Equal(t, opts.Region, s3cli.Region)
|
||||
assert.Equal(t, opts.Secure, s3cli.Secure)
|
||||
assert.Equal(t, opts.AccessKey, s3cli.AccessKey)
|
||||
assert.Equal(t, opts.Trace, s3cli.Trace)
|
||||
assert.Equal(t, opts.EncryptOpts, s3cli.EncryptOpts)
|
||||
// s3cli.minioClient.
|
||||
// s3client.minioClient
|
||||
}
|
||||
|
||||
// TestNewS3Client tests the s3 construtor
|
||||
func TestNewS3ClientWithDiff(t *testing.T) {
|
||||
t.Run("IAMRole", func(t *testing.T) {
|
||||
opts := S3ClientOpts{
|
||||
Endpoint: "foo.com",
|
||||
Region: "us-south-3",
|
||||
Secure: false,
|
||||
Trace: true,
|
||||
}
|
||||
s3If, err := NewS3Client(context.Background(), opts)
|
||||
assert.NoError(t, err)
|
||||
s3cli := s3If.(*s3client)
|
||||
assert.Equal(t, opts.Endpoint, s3cli.Endpoint)
|
||||
assert.Equal(t, opts.Region, s3cli.Region)
|
||||
assert.Equal(t, opts.Trace, s3cli.Trace)
|
||||
assert.Equal(t, opts.Endpoint, s3cli.minioClient.EndpointURL().Host)
|
||||
})
|
||||
t.Run("AssumeIAMRole", func(t *testing.T) {
|
||||
t.SkipNow()
|
||||
opts := S3ClientOpts{
|
||||
Endpoint: "foo.com",
|
||||
Region: "us-south-3",
|
||||
Secure: false,
|
||||
Trace: true,
|
||||
RoleARN: "01234567890123456789",
|
||||
}
|
||||
s3If, err := NewS3Client(context.Background(), opts)
|
||||
assert.NoError(t, err)
|
||||
s3cli := s3If.(*s3client)
|
||||
assert.Equal(t, opts.Endpoint, s3cli.Endpoint)
|
||||
assert.Equal(t, opts.Region, s3cli.Region)
|
||||
assert.Equal(t, opts.Trace, s3cli.Trace)
|
||||
assert.Equal(t, opts.Endpoint, s3cli.minioClient.EndpointURL().Host)
|
||||
})
|
||||
}
|
||||
|
||||
func TestDisallowedComboOptions(t *testing.T) {
|
||||
t.Run("KMS and SSEC", func(t *testing.T) {
|
||||
opts := S3ClientOpts{
|
||||
Endpoint: "foo.com",
|
||||
Region: "us-south-3",
|
||||
Secure: true,
|
||||
Trace: true,
|
||||
EncryptOpts: EncryptOpts{Enabled: true, ServerSideCustomerKey: "PASSWORD", KmsKeyId: "00000000-0000-0000-0000-000000000000", KmsEncryptionContext: ""},
|
||||
}
|
||||
_, err := NewS3Client(context.Background(), opts)
|
||||
assert.Error(t, err)
|
||||
})
|
||||
|
||||
t.Run("SSEC and InSecure", func(t *testing.T) {
|
||||
opts := S3ClientOpts{
|
||||
Endpoint: "foo.com",
|
||||
Region: "us-south-3",
|
||||
Secure: false,
|
||||
Trace: true,
|
||||
EncryptOpts: EncryptOpts{Enabled: true, ServerSideCustomerKey: "PASSWORD", KmsKeyId: "", KmsEncryptionContext: ""},
|
||||
}
|
||||
_, err := NewS3Client(context.Background(), opts)
|
||||
assert.Error(t, err)
|
||||
})
|
||||
}
|
|
@ -0,0 +1,60 @@
|
|||
package strftime
|
||||
|
||||
// Adapted from: github.com/jehiah/go-strftime to expose available format characters
|
||||
|
||||
import (
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
// FormatChars are the supported characters for strftime. It is a subset of all strftime characters
|
||||
// See also, time/format.go
|
||||
var FormatChars = map[rune]string{
|
||||
'B': "January",
|
||||
'b': "Jan",
|
||||
'm': "01",
|
||||
'A': "Monday",
|
||||
'a': "Mon",
|
||||
'd': "02",
|
||||
'H': "15",
|
||||
'I': "03",
|
||||
'M': "04",
|
||||
'S': "05",
|
||||
'Y': "2006",
|
||||
'y': "06",
|
||||
'p': "PM",
|
||||
'Z': "MST",
|
||||
'z': "-0700",
|
||||
'L': ".000",
|
||||
}
|
||||
|
||||
// Format formats a time object using strftime syntax
|
||||
func Format(format string, t time.Time) string {
|
||||
retval := make([]byte, 0, len(format))
|
||||
for i, ni := 0, 0; i < len(format); i = ni + 2 {
|
||||
ni = strings.IndexByte(format[i:], '%')
|
||||
if ni < 0 {
|
||||
ni = len(format)
|
||||
} else {
|
||||
ni += i
|
||||
}
|
||||
retval = append(retval, []byte(format[i:ni])...)
|
||||
if ni+1 < len(format) {
|
||||
c := format[ni+1]
|
||||
if c == '%' {
|
||||
retval = append(retval, '%')
|
||||
} else {
|
||||
if layoutCmd, ok := FormatChars[rune(c)]; ok {
|
||||
retval = append(retval, []byte(t.Format(layoutCmd))...)
|
||||
} else {
|
||||
retval = append(retval, '%', c)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if ni < len(format) {
|
||||
retval = append(retval, '%')
|
||||
}
|
||||
}
|
||||
}
|
||||
return string(retval)
|
||||
}
|
|
@ -0,0 +1,49 @@
|
|||
package strftime
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
|
||||
func ExampleFormat() {
|
||||
t := time.Unix(1340244776, 0)
|
||||
utc, _ := time.LoadLocation("UTC")
|
||||
t = t.In(utc)
|
||||
fmt.Println(Format("%Y-%m-%d %H:%M:%S", t))
|
||||
// Output:
|
||||
// 2012-06-21 02:12:56
|
||||
}
|
||||
|
||||
func TestNoLeadingPercentSign(t *testing.T) {
|
||||
tm := time.Unix(1340244776, 0)
|
||||
utc, _ := time.LoadLocation("UTC")
|
||||
tm = tm.In(utc)
|
||||
result := Format("aaabbb0123456789%Y", tm)
|
||||
if result != "aaabbb01234567892012" {
|
||||
t.Logf("%s != %s", result, "aaabbb01234567892012")
|
||||
t.Fail()
|
||||
}
|
||||
}
|
||||
|
||||
func TestUnsupported(t *testing.T) {
|
||||
tm := time.Unix(1340244776, 0)
|
||||
utc, _ := time.LoadLocation("UTC")
|
||||
tm = tm.In(utc)
|
||||
result := Format("%0%1%%%2", tm)
|
||||
if result != "%0%1%%2" {
|
||||
t.Logf("%s != %s", result, "%0%1%%2")
|
||||
t.Fail()
|
||||
}
|
||||
}
|
||||
|
||||
func TestRubyStrftime(t *testing.T) {
|
||||
tm := time.Unix(1340244776, 0)
|
||||
utc, _ := time.LoadLocation("UTC")
|
||||
tm = tm.In(utc)
|
||||
result := Format("%H:%M:%S%L", tm)
|
||||
if result != "02:12:56.000" {
|
||||
t.Logf("%s != %s", result, "02:12:56.000")
|
||||
t.Fail()
|
||||
}
|
||||
}
|
|
@ -1,11 +1,12 @@
|
|||
package time
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"log"
|
||||
"regexp"
|
||||
"strconv"
|
||||
"time"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
var durationRegex = regexp.MustCompile(`^(\d+)([smhd])$`)
|
||||
|
@ -14,7 +15,7 @@ var durationRegex = regexp.MustCompile(`^(\d+)([smhd])$`)
|
|||
func ParseDuration(duration string) (*time.Duration, error) {
|
||||
matches := durationRegex.FindStringSubmatch(duration)
|
||||
if len(matches) != 3 {
|
||||
return nil, fmt.Errorf("invalid since format '%s', expected format <duration><unit> (e.g. 3h)", duration)
|
||||
return nil, errors.Errorf("Invalid since format '%s'. Expected format <duration><unit> (e.g. 3h)\n", duration)
|
||||
}
|
||||
amount, err := strconv.ParseInt(matches[1], 10, 64)
|
||||
if err != nil {
|
||||
|
|
|
@ -5,7 +5,6 @@ import (
|
|||
"time"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
// TestParseDuration tests TestParseDuration
|
||||
|
@ -25,17 +24,17 @@ func TestParseDuration(t *testing.T) {
|
|||
}
|
||||
for _, data := range testdata {
|
||||
dur, err := ParseDuration(data.duration)
|
||||
require.NoError(t, err)
|
||||
assert.Nil(t, err)
|
||||
assert.Equal(t, dur.Nanoseconds(), data.xVal.Nanoseconds())
|
||||
}
|
||||
_, err := ParseDuration("1z")
|
||||
assert.Error(t, err)
|
||||
assert.NotNil(t, err)
|
||||
}
|
||||
|
||||
// TestParseSince tests parsing of since strings
|
||||
func TestParseSince(t *testing.T) {
|
||||
oneDayAgo, err := ParseSince("1d")
|
||||
require.NoError(t, err)
|
||||
assert.Nil(t, err)
|
||||
yesterday := time.Now().UTC().Add(-24 * time.Hour)
|
||||
assert.Equal(t, yesterday.Minute(), oneDayAgo.Minute())
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue