service/logs: move to cli/internal/logdetails
This package is only used by cli/command/service, and has no external consumers. Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
This commit is contained in:
@@ -13,7 +13,7 @@ import (
|
||||
"github.com/docker/cli/cli/command"
|
||||
"github.com/docker/cli/cli/command/completion"
|
||||
"github.com/docker/cli/cli/command/idresolver"
|
||||
"github.com/docker/cli/service/logs"
|
||||
"github.com/docker/cli/cli/internal/logdetails"
|
||||
"github.com/docker/docker/api/types"
|
||||
"github.com/docker/docker/api/types/container"
|
||||
"github.com/docker/docker/api/types/swarm"
|
||||
@@ -267,7 +267,7 @@ func (lw *logWriter) Write(buf []byte) (int, error) {
|
||||
return 0, errors.Errorf("invalid context in log message: %v", string(buf))
|
||||
}
|
||||
// parse the details out
|
||||
details, err := logs.ParseLogDetails(string(parts[detailsIndex]))
|
||||
details, err := logdetails.Parse(string(parts[detailsIndex]))
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
@@ -0,0 +1,38 @@
|
||||
// Package logdetails contains tools for parsing docker log lines.
|
||||
package logdetails
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"net/url"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// Parse parses a string of key value pairs in the form
|
||||
// "k=v,l=w", where the keys and values are url query escaped, and each pair
|
||||
// is separated by a comma. Returns a map of the key value pairs on success,
|
||||
// and an error if the details string is not in a valid format.
|
||||
//
|
||||
// The details string encoding is implemented in
|
||||
// github.com/moby/moby/api/server/httputils/write_log_stream.go
|
||||
func Parse(details string) (map[string]string, error) {
|
||||
pairs := strings.Split(details, ",")
|
||||
detailsMap := make(map[string]string, len(pairs))
|
||||
for _, pair := range pairs {
|
||||
k, v, ok := strings.Cut(pair, "=")
|
||||
if !ok || k == "" {
|
||||
// missing equal sign, or no key.
|
||||
return nil, errors.New("invalid details format")
|
||||
}
|
||||
var err error
|
||||
k, err = url.QueryUnescape(k)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
v, err = url.QueryUnescape(v)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
detailsMap[k] = v
|
||||
}
|
||||
return detailsMap, nil
|
||||
}
|
||||
@@ -0,0 +1,60 @@
|
||||
package logdetails
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"gotest.tools/v3/assert"
|
||||
is "gotest.tools/v3/assert/cmp"
|
||||
)
|
||||
|
||||
func TestParse(t *testing.T) {
|
||||
testCases := []struct {
|
||||
line string
|
||||
expected map[string]string
|
||||
expectedErr string
|
||||
}{
|
||||
{
|
||||
line: "key=value",
|
||||
expected: map[string]string{"key": "value"},
|
||||
},
|
||||
{
|
||||
line: "key1=value1,key2=value2",
|
||||
expected: map[string]string{"key1": "value1", "key2": "value2"},
|
||||
},
|
||||
{
|
||||
line: "key+with+spaces=value%3Dequals,asdf%2C=",
|
||||
expected: map[string]string{"key with spaces": "value=equals", "asdf,": ""},
|
||||
},
|
||||
{
|
||||
line: "key=,key2=",
|
||||
expected: map[string]string{"key": "", "key2": ""},
|
||||
},
|
||||
{
|
||||
line: "key=,=nothing",
|
||||
expectedErr: "invalid details format",
|
||||
},
|
||||
{
|
||||
line: "=nothing",
|
||||
expectedErr: "invalid details format",
|
||||
},
|
||||
{
|
||||
line: "=",
|
||||
expectedErr: "invalid details format",
|
||||
},
|
||||
{
|
||||
line: "errors",
|
||||
expectedErr: "invalid details format",
|
||||
},
|
||||
}
|
||||
for _, tc := range testCases {
|
||||
t.Run(tc.line, func(t *testing.T) {
|
||||
actual, err := Parse(tc.line)
|
||||
if tc.expectedErr != "" {
|
||||
assert.Check(t, is.Error(err, tc.expectedErr))
|
||||
} else {
|
||||
assert.Check(t, err)
|
||||
}
|
||||
assert.Check(t, is.DeepEqual(tc.expected, actual))
|
||||
})
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user