migrated from logrus to zerolog, improved error formatting, fixed concurrent map write, fixed crash on rapid page refresh for idle containers, fixed infinite recursion on gotfiy error, fixed websocket connection problem when using idlewatcher

This commit is contained in:
yusing
2024-10-29 11:34:58 +08:00
parent cfa74d69ae
commit e5bbb18414
137 changed files with 2640 additions and 2348 deletions

View File

@@ -1,14 +1,15 @@
package docker
import (
"errors"
"net/http"
"sync"
"github.com/docker/cli/cli/connhelper"
"github.com/docker/docker/client"
"github.com/sirupsen/logrus"
"github.com/rs/zerolog"
"github.com/yusing/go-proxy/internal/common"
E "github.com/yusing/go-proxy/internal/error"
"github.com/yusing/go-proxy/internal/logging"
"github.com/yusing/go-proxy/internal/task"
U "github.com/yusing/go-proxy/internal/utils"
F "github.com/yusing/go-proxy/internal/utils/functional"
@@ -22,7 +23,7 @@ type (
key string
refCount *U.RefCount
l logrus.FieldLogger
l zerolog.Logger
}
)
@@ -70,7 +71,7 @@ func (c *SharedClient) Close() error {
// Returns:
// - Client: the Docker client connection.
// - error: an error if the connection failed.
func ConnectClient(host string) (Client, E.Error) {
func ConnectClient(host string) (Client, error) {
clientMapMu.Lock()
defer clientMapMu.Unlock()
@@ -85,13 +86,13 @@ func ConnectClient(host string) (Client, E.Error) {
switch host {
case "":
return nil, E.Invalid("docker host", "empty")
return nil, errors.New("empty docker host")
case common.DockerHostFromEnv:
opt = clientOptEnvHost
default:
helper, err := E.Check(connhelper.GetConnectionHelper(host))
if err.HasError() {
return nil, E.UnexpectedError(err.Error())
helper, err := connhelper.GetConnectionHelper(host)
if err != nil {
logging.Panic().Err(err).Msg("failed to get connection helper")
}
if helper != nil {
httpClient := &http.Client{
@@ -113,9 +114,9 @@ func ConnectClient(host string) (Client, E.Error) {
}
}
client, err := E.Check(client.NewClientWithOpts(opt...))
client, err := client.NewClientWithOpts(opt...)
if err.HasError() {
if err != nil {
return nil, err
}
@@ -123,9 +124,9 @@ func ConnectClient(host string) (Client, E.Error) {
Client: client,
key: host,
refCount: U.NewRefCounter(),
l: logger.WithField("docker_client", client.DaemonHost()),
l: logger.With().Str("address", client.DaemonHost()).Logger(),
}
c.l.Debugf("client connected")
c.l.Trace().Msg("client connected")
clientMap.Store(host, c)
@@ -135,7 +136,7 @@ func ConnectClient(host string) (Client, E.Error) {
if c.Connected() {
c.Client.Close()
c.l.Debugf("client closed")
c.l.Trace().Msg("client closed")
}
}()
return c, nil

View File

@@ -6,8 +6,8 @@ import (
"strings"
"github.com/docker/docker/api/types"
"github.com/sirupsen/logrus"
U "github.com/yusing/go-proxy/internal/utils"
"github.com/yusing/go-proxy/internal/utils/strutils"
)
type (
@@ -59,7 +59,7 @@ func FromDocker(c *types.Container, dockerHost string) (res *Container) {
NetworkMode: c.HostConfig.NetworkMode,
Aliases: helper.getAliases(),
IsExcluded: U.ParseBool(helper.getDeleteLabel(LabelExclude)),
IsExcluded: strutils.ParseBool(helper.getDeleteLabel(LabelExclude)),
IsExplicit: isExplicit,
IsDatabase: helper.isDatabase(),
IdleTimeout: helper.getDeleteLabel(LabelIdleTimeout),
@@ -120,7 +120,7 @@ func (c *Container) setPublicIP() {
}
url, err := url.Parse(c.DockerHost)
if err != nil {
logrus.Errorf("invalid docker host %q: %v\nfalling back to 127.0.0.1", c.DockerHost, err)
logger.Err(err).Msgf("invalid docker host %q, falling back to 127.0.0.1", c.DockerHost)
c.PublicIP = "127.0.0.1"
return
}

View File

@@ -4,7 +4,7 @@ import (
"strings"
"github.com/docker/docker/api/types"
U "github.com/yusing/go-proxy/internal/utils"
"github.com/yusing/go-proxy/internal/utils/strutils"
)
type containerHelper struct {
@@ -23,7 +23,7 @@ func (c containerHelper) getDeleteLabel(label string) string {
func (c containerHelper) getAliases() []string {
if l := c.getDeleteLabel(LabelAliases); l != "" {
return U.CommaSeperatedList(l)
return strutils.CommaSeperatedList(l)
}
return []string{c.getName()}
}
@@ -44,7 +44,7 @@ func (c containerHelper) getPublicPortMapping() PortMapping {
if v.PublicPort == 0 {
continue
}
res[U.PortString(v.PublicPort)] = v
res[strutils.PortString(v.PublicPort)] = v
}
return res
}
@@ -52,7 +52,7 @@ func (c containerHelper) getPublicPortMapping() PortMapping {
func (c containerHelper) getPrivatePortMapping() PortMapping {
res := make(PortMapping)
for _, v := range c.Ports {
res[U.PortString(v.PrivatePort)] = v
res[strutils.PortString(v.PrivatePort)] = v
}
return res
}

View File

@@ -1,6 +1,7 @@
package types
import (
"errors"
"time"
"github.com/yusing/go-proxy/internal/docker"
@@ -30,7 +31,7 @@ const (
StopMethodKill StopMethod = "kill"
)
func ValidateConfig(cont *docker.Container) (cfg *Config, res E.Error) {
func ValidateConfig(cont *docker.Container) (*Config, E.Error) {
if cont == nil {
return nil, nil
}
@@ -44,26 +45,16 @@ func ValidateConfig(cont *docker.Container) (cfg *Config, res E.Error) {
}, nil
}
b := E.NewBuilder("invalid idlewatcher config")
defer b.To(&res)
errs := E.NewBuilder("invalid idlewatcher config")
idleTimeout, err := validateDurationPostitive(cont.IdleTimeout)
b.Add(err.Subjectf("%s", "idle_timeout"))
idleTimeout := E.Collect(errs, validateDurationPostitive, cont.IdleTimeout)
wakeTimeout := E.Collect(errs, validateDurationPostitive, cont.WakeTimeout)
stopTimeout := E.Collect(errs, validateDurationPostitive, cont.StopTimeout)
stopMethod := E.Collect(errs, validateStopMethod, cont.StopMethod)
signal := E.Collect(errs, validateSignal, cont.StopSignal)
wakeTimeout, err := validateDurationPostitive(cont.WakeTimeout)
b.Add(err.Subjectf("%s", "wake_timeout"))
stopTimeout, err := validateDurationPostitive(cont.StopTimeout)
b.Add(err.Subjectf("%s", "stop_timeout"))
stopMethod, err := validateStopMethod(cont.StopMethod)
b.Add(err)
signal, err := validateSignal(cont.StopSignal)
b.Add(err)
if err := b.Build(); err != nil {
return
if errs.HasError() {
return nil, errs.Error()
}
return &Config{
@@ -80,33 +71,33 @@ func ValidateConfig(cont *docker.Container) (cfg *Config, res E.Error) {
}, nil
}
func validateDurationPostitive(value string) (time.Duration, E.Error) {
func validateDurationPostitive(value string) (time.Duration, error) {
d, err := time.ParseDuration(value)
if err != nil {
return 0, E.Invalid("duration", value).With(err)
return 0, err
}
if d < 0 {
return 0, E.Invalid("duration", "negative value")
return 0, errors.New("duration must be positive")
}
return d, nil
}
func validateSignal(s string) (Signal, E.Error) {
func validateSignal(s string) (Signal, error) {
switch s {
case "", "SIGINT", "SIGTERM", "SIGHUP", "SIGQUIT",
"INT", "TERM", "HUP", "QUIT":
return Signal(s), nil
}
return "", E.Invalid("signal", s)
return "", errors.New("invalid signal " + s)
}
func validateStopMethod(s string) (StopMethod, E.Error) {
func validateStopMethod(s string) (StopMethod, error) {
sm := StopMethod(s)
switch sm {
case StopMethodPause, StopMethodStop, StopMethodKill:
return sm, nil
default:
return "", E.Invalid("stop_method", sm)
return "", errors.New("invalid stop method " + s)
}
}

View File

@@ -42,7 +42,7 @@ func newWaker(providerSubTask task.Task, entry entry.Entry, rp *gphttp.ReversePr
watcher, err := registerWatcher(providerSubTask, entry, waker)
if err != nil {
return nil, err
return nil, E.Errorf("register watcher: %w", err)
}
if rp != nil {
@@ -75,6 +75,9 @@ func (w *Watcher) Start(routeSubTask task.Task) E.Error {
// Finish implements health.HealthMonitor.
func (w *Watcher) Finish(reason any) {
if w.stream != nil {
w.stream.Close()
}
}
// Name implements health.HealthMonitor.

View File

@@ -7,9 +7,7 @@ import (
"strconv"
"time"
"github.com/sirupsen/logrus"
"github.com/yusing/go-proxy/internal/common"
E "github.com/yusing/go-proxy/internal/error"
gphttp "github.com/yusing/go-proxy/internal/net/http"
"github.com/yusing/go-proxy/internal/watcher/health"
)
@@ -20,21 +18,26 @@ func (w *Watcher) ServeHTTP(rw http.ResponseWriter, r *http.Request) {
if !shouldNext {
return
}
w.rp.ServeHTTP(rw, r)
select {
case <-r.Context().Done():
return
default:
w.rp.ServeHTTP(rw, r)
}
}
func (w *Watcher) wakeFromHTTP(rw http.ResponseWriter, r *http.Request) (shouldNext bool) {
w.resetIdleTimer()
if r.Body != nil {
defer r.Body.Close()
}
// pass through if container is already ready
if w.ready.Load() {
return true
}
if r.Body != nil {
defer r.Body.Close()
}
accept := gphttp.GetAccept(r.Header)
acceptHTML := (r.Method == http.MethodGet && accept.AcceptHTML() || r.RequestURI == "/" && accept.IsEmpty())
@@ -49,23 +52,22 @@ func (w *Watcher) wakeFromHTTP(rw http.ResponseWriter, r *http.Request) (shouldN
rw.Header().Add("Cache-Control", "must-revalidate")
rw.Header().Add("Connection", "close")
if _, err := rw.Write(body); err != nil {
w.l.Errorf("error writing http response: %s", err)
w.Err(err).Msg("error writing http response")
}
return
return false
}
ctx, cancel := context.WithTimeoutCause(r.Context(), w.WakeTimeout, errors.New("wake timeout"))
defer cancel()
checkCanceled := func() bool {
checkCanceled := func() (canceled bool) {
select {
case <-w.task.Context().Done():
w.l.Debugf("wake canceled: %s", context.Cause(w.task.Context()))
http.Error(rw, "Service unavailable", http.StatusServiceUnavailable)
return true
case <-ctx.Done():
w.l.Debugf("wake canceled: %s", context.Cause(ctx))
http.Error(rw, "Waking timed out", http.StatusGatewayTimeout)
w.WakeDebug().Str("cause", context.Cause(ctx).Error()).Msg("canceled")
return true
case <-w.task.Context().Done():
w.WakeDebug().Str("cause", w.task.FinishCause().Error()).Msg("canceled")
http.Error(rw, "Service unavailable", http.StatusServiceUnavailable)
return true
default:
return false
@@ -76,12 +78,12 @@ func (w *Watcher) wakeFromHTTP(rw http.ResponseWriter, r *http.Request) (shouldN
return false
}
w.l.Debug("wake signal received")
w.WakeTrace().Msg("signal received")
err := w.wakeIfStopped()
if err != nil {
w.l.Error(E.FailWith("wake", err))
w.WakeError(err).Send()
http.Error(rw, "Error waking container", http.StatusInternalServerError)
return
return false
}
for {
@@ -92,11 +94,11 @@ func (w *Watcher) wakeFromHTTP(rw http.ResponseWriter, r *http.Request) (shouldN
if w.Status() == health.StatusHealthy {
w.resetIdleTimer()
if isCheckRedirect {
logrus.Debugf("container %s is ready, redirecting to %s ...", w.String(), w.hc.URL())
w.Debug().Msgf("redirecting to %s ...", w.hc.URL())
rw.WriteHeader(http.StatusOK)
return
return false
}
logrus.Infof("container %s is ready, passing through to %s", w.String(), w.hc.URL())
w.Debug().Msgf("passing through to %s ...", w.hc.URL())
return true
}

View File

@@ -7,7 +7,7 @@ import (
"net"
"time"
"github.com/sirupsen/logrus"
"github.com/yusing/go-proxy/internal/net/types"
"github.com/yusing/go-proxy/internal/watcher/health"
)
@@ -16,25 +16,25 @@ func (w *Watcher) Addr() net.Addr {
return w.stream.Addr()
}
// Setup implements types.Stream.
func (w *Watcher) Setup() error {
return w.stream.Setup()
}
// Accept implements types.Stream.
func (w *Watcher) Accept() (conn net.Conn, err error) {
func (w *Watcher) Accept() (conn types.StreamConn, err error) {
conn, err = w.stream.Accept()
if err != nil {
logrus.Errorf("accept failed with error: %s", err)
return
}
if err := w.wakeFromStream(); err != nil {
w.l.Error(err)
if wakeErr := w.wakeFromStream(); wakeErr != nil {
w.WakeError(wakeErr).Msg("error waking from stream")
}
return
}
// Handle implements types.Stream.
func (w *Watcher) Handle(conn net.Conn) error {
func (w *Watcher) Handle(conn types.StreamConn) error {
if err := w.wakeFromStream(); err != nil {
return err
}
@@ -54,11 +54,11 @@ func (w *Watcher) wakeFromStream() error {
return nil
}
w.l.Debug("wake signal received")
w.WakeDebug().Msg("wake signal received")
wakeErr := w.wakeIfStopped()
if wakeErr != nil {
wakeErr = fmt.Errorf("wake failed with error: %w", wakeErr)
w.l.Error(wakeErr)
wakeErr = fmt.Errorf("%s failed: %w", w.String(), wakeErr)
w.WakeError(wakeErr).Msg("wake failed")
return wakeErr
}
@@ -69,18 +69,18 @@ func (w *Watcher) wakeFromStream() error {
select {
case <-w.task.Context().Done():
cause := w.task.FinishCause()
w.l.Debugf("wake canceled: %s", cause)
w.WakeDebug().Str("cause", cause.Error()).Msg("canceled")
return cause
case <-ctx.Done():
cause := context.Cause(ctx)
w.l.Debugf("wake canceled: %s", cause)
w.WakeDebug().Str("cause", cause.Error()).Msg("timeout")
return cause
default:
}
if w.Status() == health.StatusHealthy {
w.resetIdleTimer()
logrus.Infof("container %s is ready, passing through to %s", w.String(), w.hc.URL())
w.Debug().Msg("container is ready, passing through to " + w.hc.URL().String())
return nil
}

View File

@@ -3,15 +3,15 @@ package idlewatcher
import (
"context"
"errors"
"fmt"
"sync"
"time"
"github.com/docker/docker/api/types/container"
"github.com/sirupsen/logrus"
"github.com/rs/zerolog"
D "github.com/yusing/go-proxy/internal/docker"
idlewatcher "github.com/yusing/go-proxy/internal/docker/idlewatcher/types"
E "github.com/yusing/go-proxy/internal/error"
"github.com/yusing/go-proxy/internal/logging"
"github.com/yusing/go-proxy/internal/proxy/entry"
"github.com/yusing/go-proxy/internal/task"
U "github.com/yusing/go-proxy/internal/utils"
@@ -25,6 +25,8 @@ type (
Watcher struct {
_ U.NoCopy
zerolog.Logger
*idlewatcher.Config
*waker
@@ -32,7 +34,6 @@ type (
stopByMethod StopCallback // send a docker command w.r.t. `stop_method`
ticker *time.Ticker
task task.Task
l *logrus.Entry
}
WakeDone <-chan error
@@ -44,13 +45,12 @@ var (
watcherMap = F.NewMapOf[string, *Watcher]()
watcherMapMu sync.Mutex
logger = logrus.WithField("module", "idle_watcher")
logger = logging.With().Str("module", "idle_watcher").Logger()
)
const dockerReqTimeout = 3 * time.Second
func registerWatcher(providerSubtask task.Task, entry entry.Entry, waker *waker) (*Watcher, E.Error) {
failure := E.Failure("idle_watcher register")
func registerWatcher(providerSubtask task.Task, entry entry.Entry, waker *waker) (*Watcher, error) {
cfg := entry.IdlewatcherConfig()
if cfg.IdleTimeout == 0 {
@@ -71,17 +71,17 @@ func registerWatcher(providerSubtask task.Task, entry entry.Entry, waker *waker)
}
client, err := D.ConnectClient(cfg.DockerHost)
if err.HasError() {
return nil, failure.With(err)
if err != nil {
return nil, err
}
w := &Watcher{
Logger: logger.With().Str("name", cfg.ContainerName).Logger(),
Config: cfg,
waker: waker,
client: client,
task: providerSubtask,
ticker: time.NewTicker(cfg.IdleTimeout),
l: logger.WithField("container", cfg.ContainerName),
}
w.stopByMethod = w.getStopCallback()
watcherMap.Store(key, w)
@@ -99,6 +99,23 @@ func registerWatcher(providerSubtask task.Task, entry entry.Entry, waker *waker)
return w, nil
}
// WakeDebug logs a debug message related to waking the container.
func (w *Watcher) WakeDebug() *zerolog.Event {
return w.Debug().Str("action", "wake")
}
func (w *Watcher) WakeTrace() *zerolog.Event {
return w.Trace().Str("action", "wake")
}
func (w *Watcher) WakeError(err error) *zerolog.Event {
return w.Err(err).Str("action", "wake")
}
func (w *Watcher) LogReason(action, reason string) {
w.Info().Str("reason", reason).Msg(action)
}
func (w *Watcher) containerStop(ctx context.Context) error {
return w.client.ContainerStop(ctx, w.ContainerID, container.StopOptions{
Signal: string(w.StopSignal),
@@ -130,7 +147,7 @@ func (w *Watcher) containerStatus() (string, error) {
defer cancel()
json, err := w.client.ContainerInspect(ctx, w.ContainerID)
if err != nil {
return "", fmt.Errorf("failed to inspect container: %w", err)
return "", err
}
return json.State.Status, nil
}
@@ -181,7 +198,7 @@ func (w *Watcher) getStopCallback() StopCallback {
}
func (w *Watcher) resetIdleTimer() {
w.l.Trace("reset idle timer")
w.Trace().Msg("reset idle timer")
w.ticker.Reset(w.IdleTimeout)
}
@@ -190,7 +207,7 @@ func (w *Watcher) getEventCh(dockerWatcher watcher.DockerWatcher) (eventTask tas
eventCh, errCh = dockerWatcher.EventsWithOptions(eventTask.Context(), W.DockerListOptions{
Filters: W.NewDockerFilter(
W.DockerFilterContainer,
W.DockerrFilterContainer(w.ContainerID),
W.DockerFilterContainerNameID(w.ContainerID),
W.DockerFilterStart,
W.DockerFilterStop,
W.DockerFilterDie,
@@ -214,7 +231,7 @@ func (w *Watcher) getEventCh(dockerWatcher watcher.DockerWatcher) (eventTask tas
//
// it exits only if the context is canceled, the container is destroyed,
// errors occured on docker client, or route provider died (mainly caused by config reload).
func (w *Watcher) watchUntilDestroy() error {
func (w *Watcher) watchUntilDestroy() (returnCause error) {
dockerWatcher := W.NewDockerWatcherWithClient(w.client)
eventTask, dockerEventCh, dockerEventErrCh := w.getEventCh(dockerWatcher)
defer eventTask.Finish("stopped")
@@ -224,36 +241,36 @@ func (w *Watcher) watchUntilDestroy() error {
case <-w.task.Context().Done():
return w.task.FinishCause()
case err := <-dockerEventErrCh:
if err != nil && err.IsNot(context.Canceled) {
w.l.Error(E.FailWith("docker watcher", err))
return err.Error()
if !err.Is(context.Canceled) {
E.LogError("idlewatcher error", err, &w.Logger)
}
return err
case e := <-dockerEventCh:
switch {
case e.Action == events.ActionContainerDestroy:
w.ContainerRunning = false
w.ready.Store(false)
w.l.Info("watcher stopped by container destruction")
w.LogReason("watcher stopped", "container destroyed")
return errors.New("container destroyed")
// create / start / unpause
case e.Action.IsContainerWake():
w.ContainerRunning = true
w.resetIdleTimer()
w.l.Info("container awaken")
w.Info().Msg("awaken")
case e.Action.IsContainerSleep(): // stop / pause / kil
w.ContainerRunning = false
w.ready.Store(false)
w.ticker.Stop()
default:
w.l.Errorf("unexpected docker event: %s", e)
w.Error().Msg("unexpected docker event: " + e.String())
}
// container name changed should also change the container id
if w.ContainerName != e.ActorName {
w.l.Debugf("container renamed %s -> %s", w.ContainerName, e.ActorName)
w.Debug().Msgf("renamed %s -> %s", w.ContainerName, e.ActorName)
w.ContainerName = e.ActorName
}
if w.ContainerID != e.ActorID {
w.l.Debugf("container id changed %s -> %s", w.ContainerID, e.ActorID)
w.Debug().Msgf("id changed %s -> %s", w.ContainerID, e.ActorID)
w.ContainerID = e.ActorID
// recreate event stream
eventTask.Finish("recreate event stream")
@@ -263,9 +280,9 @@ func (w *Watcher) watchUntilDestroy() error {
w.ticker.Stop()
if w.ContainerRunning {
if err := w.stopByMethod(); err != nil && !errors.Is(err, context.Canceled) {
w.l.Errorf("container stop with method %q failed with error: %v", w.StopMethod, err)
w.Err(err).Msgf("container stop with method %q failed", w.StopMethod)
} else {
w.l.Info("container stopped by idle timeout")
w.LogReason("container stopped", "idle timeout")
}
}
}

View File

@@ -4,28 +4,26 @@ import (
"context"
"errors"
"time"
E "github.com/yusing/go-proxy/internal/error"
)
func Inspect(dockerHost string, containerID string) (*Container, E.Error) {
func Inspect(dockerHost string, containerID string) (*Container, error) {
client, err := ConnectClient(dockerHost)
defer client.Close()
if err.HasError() {
return nil, E.FailWith("connect to docker", err)
if err != nil {
return nil, err
}
return client.Inspect(containerID)
}
func (c Client) Inspect(containerID string) (*Container, E.Error) {
func (c Client) Inspect(containerID string) (*Container, error) {
ctx, cancel := context.WithTimeoutCause(context.Background(), 3*time.Second, errors.New("docker container inspect timeout"))
defer cancel()
json, err := c.ContainerInspect(ctx, containerID)
if err != nil {
return nil, E.From(err)
return nil, err
}
return FromJSON(json, c.key), nil
}

View File

@@ -24,6 +24,11 @@ type (
NestedLabelMap map[string]U.SerializedObject
)
var (
ErrApplyToNil = E.New("label value is nil")
ErrFieldNotExist = E.New("field does not exist")
)
func (l *Label) String() string {
if l.Attribute == "" {
return l.Namespace + "." + l.Target
@@ -41,7 +46,7 @@ func (l *Label) String() string {
// - error: an error if the field does not exist.
func ApplyLabel[T any](obj *T, l *Label) E.Error {
if obj == nil {
return E.Invalid("nil object", l)
return ErrApplyToNil.Subject(l.String())
}
switch nestedLabel := l.Value.(type) {
case *Label:
@@ -54,7 +59,7 @@ func ApplyLabel[T any](obj *T, l *Label) E.Error {
}
}
if !field.IsValid() {
return E.NotExist("field", l.Attribute)
return ErrFieldNotExist.Subject(l.Attribute).Subject(l.String())
}
dst, ok := field.Interface().(NestedLabelMap)
if !ok {
@@ -65,7 +70,11 @@ func ApplyLabel[T any](obj *T, l *Label) E.Error {
} else {
field = field.Addr()
}
return U.Deserialize(U.SerializedObject{nestedLabel.Namespace: nestedLabel.Value}, field.Interface())
err := U.Deserialize(U.SerializedObject{nestedLabel.Namespace: nestedLabel.Value}, field.Interface())
if err != nil {
return err.Subject(l.String())
}
return nil
}
if dst == nil {
field.Set(reflect.MakeMap(reflect.TypeFor[NestedLabelMap]()))
@@ -77,18 +86,22 @@ func ApplyLabel[T any](obj *T, l *Label) E.Error {
dst[nestedLabel.Namespace][nestedLabel.Attribute] = nestedLabel.Value
return nil
default:
return U.Deserialize(U.SerializedObject{l.Attribute: l.Value}, obj)
err := U.Deserialize(U.SerializedObject{l.Attribute: l.Value}, obj)
if err != nil {
return err.Subject(l.String())
}
return nil
}
}
func ParseLabel(label string, value string) (*Label, E.Error) {
func ParseLabel(label string, value string) *Label {
parts := strings.Split(label, ".")
if len(parts) < 2 {
return &Label{
Namespace: label,
Value: value,
}, nil
}
}
l := &Label{
@@ -104,12 +117,9 @@ func ParseLabel(label string, value string) (*Label, E.Error) {
l.Attribute = parts[2]
default:
l.Attribute = parts[2]
nestedLabel, err := ParseLabel(strings.Join(parts[3:], "."), value)
if err != nil {
return nil, err
}
nestedLabel := ParseLabel(strings.Join(parts[3:], "."), value)
l.Value = nestedLabel
}
return l, nil
return l
}

View File

@@ -20,9 +20,8 @@ func makeLabel(ns, name, attr string) string {
func TestNestedLabel(t *testing.T) {
mAttr := "prop1"
pl, err := ParseLabel(makeLabel(NSProxy, "foo", makeLabel("middlewares", mName, mAttr)), v)
ExpectNoError(t, err.Error())
sGot := ExpectType[*Label](t, pl.Value)
lbl := ParseLabel(makeLabel(NSProxy, "foo", makeLabel("middlewares", mName, mAttr)), v)
sGot := ExpectType[*Label](t, lbl.Value)
ExpectFalse(t, sGot == nil)
ExpectEqual(t, sGot.Namespace, mName)
ExpectEqual(t, sGot.Attribute, mAttr)
@@ -32,10 +31,9 @@ func TestApplyNestedLabel(t *testing.T) {
entry := new(struct {
Middlewares NestedLabelMap `yaml:"middlewares"`
})
pl, err := ParseLabel(makeLabel(NSProxy, "foo", makeLabel("middlewares", mName, mAttr)), v)
ExpectNoError(t, err.Error())
err = ApplyLabel(entry, pl)
ExpectNoError(t, err.Error())
lbl := ParseLabel(makeLabel(NSProxy, "foo", makeLabel("middlewares", mName, mAttr)), v)
err := ApplyLabel(entry, lbl)
ExpectNoError(t, err)
middleware1, ok := entry.Middlewares[mName]
ExpectTrue(t, ok)
got := ExpectType[string](t, middleware1[mAttr])
@@ -52,10 +50,9 @@ func TestApplyNestedLabelExisting(t *testing.T) {
entry.Middlewares[mName] = make(U.SerializedObject)
entry.Middlewares[mName][checkAttr] = checkV
pl, err := ParseLabel(makeLabel(NSProxy, "foo", makeLabel("middlewares", mName, mAttr)), v)
ExpectNoError(t, err.Error())
err = ApplyLabel(entry, pl)
ExpectNoError(t, err.Error())
lbl := ParseLabel(makeLabel(NSProxy, "foo", makeLabel("middlewares", mName, mAttr)), v)
err := ApplyLabel(entry, lbl)
ExpectNoError(t, err)
middleware1, ok := entry.Middlewares[mName]
ExpectTrue(t, ok)
got := ExpectType[string](t, middleware1[mAttr])
@@ -74,10 +71,9 @@ func TestApplyNestedLabelNoAttr(t *testing.T) {
entry.Middlewares = make(NestedLabelMap)
entry.Middlewares[mName] = make(U.SerializedObject)
pl, err := ParseLabel(makeLabel(NSProxy, "foo", fmt.Sprintf("%s.%s", "middlewares", mName)), v)
ExpectNoError(t, err.Error())
err = ApplyLabel(entry, pl)
ExpectNoError(t, err.Error())
lbl := ParseLabel(makeLabel(NSProxy, "foo", fmt.Sprintf("%s.%s", "middlewares", mName)), v)
err := ApplyLabel(entry, lbl)
ExpectNoError(t, err)
_, ok := entry.Middlewares[mName]
ExpectTrue(t, ok)
}

View File

@@ -8,7 +8,6 @@ import (
"github.com/docker/docker/api/types"
"github.com/docker/docker/api/types/container"
"github.com/docker/docker/client"
E "github.com/yusing/go-proxy/internal/error"
)
var listOptions = container.ListOptions{
@@ -23,19 +22,19 @@ var listOptions = container.ListOptions{
All: true,
}
func ListContainers(clientHost string) ([]types.Container, E.Error) {
func ListContainers(clientHost string) ([]types.Container, error) {
dockerClient, err := ConnectClient(clientHost)
if err.HasError() {
return nil, E.FailWith("connect to docker", err)
if err != nil {
return nil, err
}
defer dockerClient.Close()
ctx, cancel := context.WithTimeoutCause(context.Background(), 3*time.Second, errors.New("list containers timeout"))
defer cancel()
containers, err := E.Check(dockerClient.ContainerList(ctx, listOptions))
if err.HasError() {
return nil, E.FailWith("list containers", err)
containers, err := dockerClient.ContainerList(ctx, listOptions)
if err != nil {
return nil, err
}
return containers, nil
}

View File

@@ -1,5 +1,7 @@
package docker
import "github.com/sirupsen/logrus"
import (
"github.com/yusing/go-proxy/internal/logging"
)
var logger = logrus.WithField("module", "docker")
var logger = logging.With().Str("module", "docker").Logger()