diff --git a/internal/docker/container.go b/internal/docker/container.go index 9faf72a..219719f 100644 --- a/internal/docker/container.go +++ b/internal/docker/container.go @@ -2,6 +2,8 @@ package docker import ( "context" + "errors" + "fmt" "net" "net/url" "strconv" @@ -9,10 +11,8 @@ import ( "github.com/docker/docker/api/types/container" "github.com/docker/go-connections/nat" - "github.com/rs/zerolog/log" "github.com/yusing/go-proxy/agent/pkg/agent" config "github.com/yusing/go-proxy/internal/config/types" - "github.com/yusing/go-proxy/internal/gperr" idlewatcher "github.com/yusing/go-proxy/internal/idlewatcher/types" "github.com/yusing/go-proxy/internal/serialization" "github.com/yusing/go-proxy/internal/utils" @@ -46,6 +46,8 @@ type ( IsExplicit bool `json:"is_explicit"` IsHostNetworkMode bool `json:"is_host_network_mode"` Running bool `json:"running"` + + Errors *containerError `json:"errors"` } ContainerImage struct { Author string `json:"author,omitempty"` @@ -56,6 +58,11 @@ type ( var DummyContainer = new(Container) +var ( + ErrNetworkNotFound = errors.New("network not found") + ErrNoNetwork = errors.New("no network found") +) + func FromDocker(c *container.SummaryTrimmed, dockerHost string) (res *Container) { _, isExplicit := c.Labels[LabelAliases] helper := containerHelper{c} @@ -68,6 +75,7 @@ func FromDocker(c *container.SummaryTrimmed, dockerHost string) (res *Container) } } } + network := helper.getDeleteLabel(LabelNetwork) isExcluded, _ := strconv.ParseBool(helper.getDeleteLabel(LabelExclude)) res = &Container{ @@ -80,6 +88,7 @@ func FromDocker(c *container.SummaryTrimmed, dockerHost string) (res *Container) Mounts: helper.getMounts(), + Network: network, PublicPortMapping: helper.getPublicPortMapping(), PrivatePortMapping: helper.getPrivatePortMapping(), @@ -94,13 +103,17 @@ func FromDocker(c *container.SummaryTrimmed, dockerHost string) (res *Container) var ok bool res.Agent, ok = config.GetInstance().GetAgent(dockerHost) if !ok { - log.Error().Msgf("agent %q not found", dockerHost) + res.addError(fmt.Errorf("agent %q not found", dockerHost)) } } res.setPrivateHostname(helper) res.setPublicHostname() res.loadDeleteIdlewatcherLabels(helper) + + if res.PrivateHostname == "" && res.PublicHostname == "" && res.Running { + res.addError(ErrNoNetwork) + } return } @@ -203,7 +216,6 @@ func (c *Container) setPublicHostname() { } url, err := url.Parse(c.DockerHost) if err != nil { - log.Err(err).Msgf("invalid docker host %q, falling back to 127.0.0.1", c.DockerHost) c.PublicHostname = "127.0.0.1" return } @@ -217,6 +229,28 @@ func (c *Container) setPrivateHostname(helper containerHelper) { if helper.NetworkSettings == nil { return } + if c.Network != "" { + v, ok := helper.NetworkSettings.Networks[c.Network] + if ok { + c.PrivateHostname = v.IPAddress + return + } + // try {project_name}_{network_name} + if proj := c.DockerComposeProject(); proj != "" { + oldNetwork, newNetwork := c.Network, fmt.Sprintf("%s_%s", proj, c.Network) + if newNetwork != oldNetwork { + v, ok = helper.NetworkSettings.Networks[newNetwork] + if ok { + c.Network = newNetwork // update network to the new one + c.PrivateHostname = v.IPAddress + return + } + } + } + c.addError(fmt.Errorf("%w: %s", ErrNetworkNotFound, c.Network)) + return + } + // fallback to first network if no network is specified for k, v := range helper.NetworkSettings.Networks { if v.IPAddress != "" { c.Network = k // update network to the first network @@ -252,9 +286,16 @@ func (c *Container) loadDeleteIdlewatcherLabels(helper containerHelper) { err := serialization.MapUnmarshalValidate(cfg, idwCfg) if err != nil { - gperr.LogWarn("invalid idlewatcher config", gperr.PrependSubject(c.ContainerName, err)) + c.addError(err) } else { c.IdlewatcherConfig = idwCfg } } } + +func (c *Container) addError(err error) { + if c.Errors == nil { + c.Errors = new(containerError) + } + c.Errors.Add(err) +} diff --git a/internal/docker/errors.go b/internal/docker/errors.go new file mode 100644 index 0000000..a168847 --- /dev/null +++ b/internal/docker/errors.go @@ -0,0 +1,34 @@ +package docker + +import ( + "encoding/json" + + "github.com/yusing/go-proxy/internal/gperr" +) + +type containerError struct { + errs *gperr.Builder +} + +func (e *containerError) Add(err error) { + if e.errs == nil { + e.errs = gperr.NewBuilder() + } + e.errs.Add(err) +} + +func (e *containerError) Error() string { + if e.errs == nil { + return "" + } + return e.errs.String() +} + +func (e *containerError) Unwrap() error { + return e.errs.Error() +} + +func (e *containerError) MarshalJSON() ([]byte, error) { + err := e.errs.Error().(interface{ Plain() []byte }) + return json.Marshal(string(err.Plain())) +} diff --git a/internal/docker/labels.go b/internal/docker/labels.go index 7489f02..bb8e917 100644 --- a/internal/docker/labels.go +++ b/internal/docker/labels.go @@ -14,4 +14,5 @@ const ( LabelStopSignal = NSProxy + ".stop_signal" LabelStartEndpoint = NSProxy + ".start_endpoint" LabelDependsOn = NSProxy + ".depends_on" + LabelNetwork = NSProxy + ".network" ) diff --git a/internal/route/provider/docker.go b/internal/route/provider/docker.go index c36d3d1..77836a5 100755 --- a/internal/route/provider/docker.go +++ b/internal/route/provider/docker.go @@ -72,6 +72,11 @@ func (p *DockerProvider) loadRoutesImpl() (route.Routes, gperr.Error) { for _, c := range containers { container := docker.FromDocker(&c, p.dockerHost) + if container.Errors != nil { + errs.Add(container.Errors) + continue + } + if container.IsHostNetworkMode { err := container.UpdatePorts() if err != nil {