mirror of
https://github.com/yusing/godoxy.git
synced 2025-07-23 20:54:02 +02:00
fixing idlewatcher
This commit is contained in:
parent
e48b9bbb0a
commit
d7eab2ebcd
16 changed files with 352 additions and 242 deletions
3
Makefile
3
Makefile
|
@ -42,3 +42,6 @@ rapid-crash:
|
||||||
sudo docker run --restart=always --name test_crash debian:bookworm-slim /bin/cat &&\
|
sudo docker run --restart=always --name test_crash debian:bookworm-slim /bin/cat &&\
|
||||||
sleep 3 &&\
|
sleep 3 &&\
|
||||||
sudo docker rm -f test_crash
|
sudo docker rm -f test_crash
|
||||||
|
|
||||||
|
debug-list-containers:
|
||||||
|
bash -c 'echo -e "GET /containers/json HTTP/1.0\r\n" | sudo netcat -U /var/run/docker.sock | tail -n +9 | jq'
|
|
@ -16,6 +16,8 @@ type Client struct {
|
||||||
key string
|
key string
|
||||||
refCount *atomic.Int32
|
refCount *atomic.Int32
|
||||||
*client.Client
|
*client.Client
|
||||||
|
|
||||||
|
l logrus.FieldLogger
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c Client) DaemonHostname() string {
|
func (c Client) DaemonHostname() string {
|
||||||
|
@ -23,10 +25,13 @@ func (c Client) DaemonHostname() string {
|
||||||
return url.Hostname()
|
return url.Hostname()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (c Client) Connected() bool {
|
||||||
|
return c.Client != nil
|
||||||
|
}
|
||||||
|
|
||||||
// if the client is still referenced, this is no-op
|
// if the client is still referenced, this is no-op
|
||||||
func (c Client) Close() error {
|
func (c *Client) Close() error {
|
||||||
if c.refCount.Load() > 0 {
|
if c.refCount.Add(-1) > 0 {
|
||||||
c.refCount.Add(-1)
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -34,7 +39,15 @@ func (c Client) Close() error {
|
||||||
defer clientMapMu.Unlock()
|
defer clientMapMu.Unlock()
|
||||||
delete(clientMap, c.key)
|
delete(clientMap, c.key)
|
||||||
|
|
||||||
return c.Client.Close()
|
client := c.Client
|
||||||
|
c.Client = nil
|
||||||
|
|
||||||
|
c.l.Debugf("client closed")
|
||||||
|
|
||||||
|
if client != nil {
|
||||||
|
return client.Close()
|
||||||
|
}
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// ConnectClient creates a new Docker client connection to the specified host.
|
// ConnectClient creates a new Docker client connection to the specified host.
|
||||||
|
@ -94,12 +107,16 @@ func ConnectClient(host string) (Client, E.NestedError) {
|
||||||
return Client{}, err
|
return Client{}, err
|
||||||
}
|
}
|
||||||
|
|
||||||
clientMap[host] = Client{
|
c := Client{
|
||||||
Client: client,
|
Client: client,
|
||||||
key: host,
|
key: host,
|
||||||
refCount: &atomic.Int32{},
|
refCount: &atomic.Int32{},
|
||||||
|
l: logger.WithField("docker_client", client.DaemonHost()),
|
||||||
}
|
}
|
||||||
clientMap[host].refCount.Add(1)
|
c.refCount.Add(1)
|
||||||
|
c.l.Debugf("client connected")
|
||||||
|
|
||||||
|
clientMap[host] = c
|
||||||
return clientMap[host], nil
|
return clientMap[host], nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -10,17 +10,18 @@ import (
|
||||||
)
|
)
|
||||||
|
|
||||||
type ProxyProperties struct {
|
type ProxyProperties struct {
|
||||||
DockerHost string `yaml:"docker_host" json:"docker_host"`
|
DockerHost string `yaml:"-" json:"docker_host"`
|
||||||
ContainerName string `yaml:"container_name" json:"container_name"`
|
ContainerName string `yaml:"-" json:"container_name"`
|
||||||
ImageName string `yaml:"image_name" json:"image_name"`
|
ImageName string `yaml:"-" json:"image_name"`
|
||||||
Aliases []string `yaml:"aliases" json:"aliases"`
|
Aliases []string `yaml:"-" json:"aliases"`
|
||||||
IsExcluded bool `yaml:"is_excluded" json:"is_excluded"`
|
IsExcluded bool `yaml:"-" json:"is_excluded"`
|
||||||
FirstPort string `yaml:"first_port" json:"first_port"`
|
FirstPort string `yaml:"-" json:"first_port"`
|
||||||
IdleTimeout string `yaml:"idle_timeout" json:"idle_timeout"`
|
IdleTimeout string `yaml:"-" json:"idle_timeout"`
|
||||||
WakeTimeout string `yaml:"wake_timeout" json:"wake_timeout"`
|
WakeTimeout string `yaml:"-" json:"wake_timeout"`
|
||||||
StopMethod string `yaml:"stop_method" json:"stop_method"`
|
StopMethod string `yaml:"-" json:"stop_method"`
|
||||||
StopTimeout string `yaml:"stop_timeout" json:"stop_timeout"` // stop_method = "stop" only
|
StopTimeout string `yaml:"-" json:"stop_timeout"` // stop_method = "stop" only
|
||||||
StopSignal string `yaml:"stop_signal" json:"stop_signal"` // stop_method = "stop" | "kill" only
|
StopSignal string `yaml:"-" json:"stop_signal"` // stop_method = "stop" | "kill" only
|
||||||
|
Running bool `yaml:"-" json:"running"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type Container struct {
|
type Container struct {
|
||||||
|
@ -42,6 +43,7 @@ func FromDocker(c *types.Container, dockerHost string) (res Container) {
|
||||||
StopMethod: res.getDeleteLabel(LabelStopMethod),
|
StopMethod: res.getDeleteLabel(LabelStopMethod),
|
||||||
StopTimeout: res.getDeleteLabel(LabelStopTimeout),
|
StopTimeout: res.getDeleteLabel(LabelStopTimeout),
|
||||||
StopSignal: res.getDeleteLabel(LabelStopSignal),
|
StopSignal: res.getDeleteLabel(LabelStopSignal),
|
||||||
|
Running: c.Status == "running",
|
||||||
}
|
}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
|
@ -15,10 +15,13 @@ import (
|
||||||
E "github.com/yusing/go-proxy/error"
|
E "github.com/yusing/go-proxy/error"
|
||||||
P "github.com/yusing/go-proxy/proxy"
|
P "github.com/yusing/go-proxy/proxy"
|
||||||
PT "github.com/yusing/go-proxy/proxy/fields"
|
PT "github.com/yusing/go-proxy/proxy/fields"
|
||||||
|
W "github.com/yusing/go-proxy/watcher"
|
||||||
|
event "github.com/yusing/go-proxy/watcher/events"
|
||||||
)
|
)
|
||||||
|
|
||||||
type watcher struct {
|
type watcher struct {
|
||||||
*P.ReverseProxyEntry
|
*P.ReverseProxyEntry
|
||||||
|
|
||||||
client D.Client
|
client D.Client
|
||||||
|
|
||||||
refCount atomic.Int32
|
refCount atomic.Int32
|
||||||
|
@ -26,6 +29,7 @@ type watcher struct {
|
||||||
stopByMethod StopCallback
|
stopByMethod StopCallback
|
||||||
wakeCh chan struct{}
|
wakeCh chan struct{}
|
||||||
wakeDone chan E.NestedError
|
wakeDone chan E.NestedError
|
||||||
|
running atomic.Bool
|
||||||
|
|
||||||
ctx context.Context
|
ctx context.Context
|
||||||
cancel context.CancelFunc
|
cancel context.CancelFunc
|
||||||
|
@ -36,7 +40,7 @@ type watcher struct {
|
||||||
type (
|
type (
|
||||||
WakeDone <-chan error
|
WakeDone <-chan error
|
||||||
WakeFunc func() WakeDone
|
WakeFunc func() WakeDone
|
||||||
StopCallback func() (bool, E.NestedError)
|
StopCallback func() E.NestedError
|
||||||
)
|
)
|
||||||
|
|
||||||
func Register(entry *P.ReverseProxyEntry) (*watcher, E.NestedError) {
|
func Register(entry *P.ReverseProxyEntry) (*watcher, E.NestedError) {
|
||||||
|
@ -51,6 +55,7 @@ func Register(entry *P.ReverseProxyEntry) (*watcher, E.NestedError) {
|
||||||
|
|
||||||
if w, ok := watcherMap[entry.ContainerName]; ok {
|
if w, ok := watcherMap[entry.ContainerName]; ok {
|
||||||
w.refCount.Add(1)
|
w.refCount.Add(1)
|
||||||
|
w.ReverseProxyEntry = entry
|
||||||
return w, nil
|
return w, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -67,8 +72,9 @@ func Register(entry *P.ReverseProxyEntry) (*watcher, E.NestedError) {
|
||||||
l: logger.WithField("container", entry.ContainerName),
|
l: logger.WithField("container", entry.ContainerName),
|
||||||
}
|
}
|
||||||
w.refCount.Add(1)
|
w.refCount.Add(1)
|
||||||
|
w.running.Store(entry.ContainerRunning)
|
||||||
w.stopByMethod = w.getStopCallback()
|
w.stopByMethod = w.getStopCallback()
|
||||||
|
|
||||||
watcherMap[w.ContainerName] = w
|
watcherMap[w.ContainerName] = w
|
||||||
|
|
||||||
go func() {
|
go func() {
|
||||||
|
@ -84,13 +90,14 @@ func Unregister(containerName string) {
|
||||||
defer watcherMapMu.Unlock()
|
defer watcherMapMu.Unlock()
|
||||||
|
|
||||||
if w, ok := watcherMap[containerName]; ok {
|
if w, ok := watcherMap[containerName]; ok {
|
||||||
if w.refCount.Load() == 0 {
|
if w.refCount.Add(-1) > 0 {
|
||||||
w.cancel()
|
return
|
||||||
close(w.wakeCh)
|
|
||||||
delete(watcherMap, containerName)
|
|
||||||
} else {
|
|
||||||
w.refCount.Add(-1)
|
|
||||||
}
|
}
|
||||||
|
if w.cancel != nil {
|
||||||
|
w.cancel()
|
||||||
|
}
|
||||||
|
w.client.Close()
|
||||||
|
delete(watcherMap, containerName)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -131,19 +138,26 @@ func (w *watcher) PatchRoundTripper(rtp http.RoundTripper) roundTripper {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (w *watcher) roundTrip(origRoundTrip roundTripFunc, req *http.Request) (*http.Response, error) {
|
func (w *watcher) roundTrip(origRoundTrip roundTripFunc, req *http.Request) (*http.Response, error) {
|
||||||
timeout := time.After(w.WakeTimeout)
|
|
||||||
w.wakeCh <- struct{}{}
|
w.wakeCh <- struct{}{}
|
||||||
|
|
||||||
|
if w.running.Load() {
|
||||||
|
return origRoundTrip(req)
|
||||||
|
}
|
||||||
|
timeout := time.After(w.WakeTimeout)
|
||||||
|
|
||||||
for {
|
for {
|
||||||
|
if w.running.Load() {
|
||||||
|
return origRoundTrip(req)
|
||||||
|
}
|
||||||
select {
|
select {
|
||||||
|
case <-req.Context().Done():
|
||||||
|
return nil, req.Context().Err()
|
||||||
case err := <-w.wakeDone:
|
case err := <-w.wakeDone:
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err.Error()
|
return nil, err.Error()
|
||||||
}
|
}
|
||||||
return origRoundTrip(req)
|
|
||||||
case <-timeout:
|
case <-timeout:
|
||||||
resp := loadingResponse
|
return getLoadingResponse(), nil
|
||||||
resp.TLS = req.TLS
|
|
||||||
return &resp, nil
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -178,36 +192,23 @@ func (w *watcher) containerStatus() (string, E.NestedError) {
|
||||||
return json.State.Status, nil
|
return json.State.Status, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (w *watcher) wakeIfStopped() (bool, E.NestedError) {
|
func (w *watcher) wakeIfStopped() E.NestedError {
|
||||||
failure := E.Failure("wake")
|
|
||||||
status, err := w.containerStatus()
|
status, err := w.containerStatus()
|
||||||
|
|
||||||
if err.HasError() {
|
if err.HasError() {
|
||||||
return false, failure.With(err)
|
return err
|
||||||
}
|
}
|
||||||
// "created", "running", "paused", "restarting", "removing", "exited", or "dead"
|
// "created", "running", "paused", "restarting", "removing", "exited", or "dead"
|
||||||
switch status {
|
switch status {
|
||||||
case "exited", "dead":
|
case "exited", "dead":
|
||||||
err = E.From(w.containerStart())
|
return E.From(w.containerStart())
|
||||||
case "paused":
|
case "paused":
|
||||||
err = E.From(w.containerUnpause())
|
return E.From(w.containerUnpause())
|
||||||
case "running":
|
case "running":
|
||||||
return false, nil
|
w.running.Store(true)
|
||||||
|
return nil
|
||||||
default:
|
default:
|
||||||
return false, failure.With(E.Unexpected("container state", status))
|
return E.Unexpected("container state", status)
|
||||||
}
|
|
||||||
|
|
||||||
if err.HasError() {
|
|
||||||
return false, failure.With(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
status, err = w.containerStatus()
|
|
||||||
if err.HasError() {
|
|
||||||
return false, failure.With(err)
|
|
||||||
} else if status != "running" {
|
|
||||||
return false, failure.With(E.Unexpected("container state", status))
|
|
||||||
} else {
|
|
||||||
return true, nil
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -223,19 +224,15 @@ func (w *watcher) getStopCallback() StopCallback {
|
||||||
default:
|
default:
|
||||||
panic("should not reach here")
|
panic("should not reach here")
|
||||||
}
|
}
|
||||||
return func() (bool, E.NestedError) {
|
return func() E.NestedError {
|
||||||
status, err := w.containerStatus()
|
status, err := w.containerStatus()
|
||||||
if err.HasError() {
|
if err.HasError() {
|
||||||
return false, E.FailWith("stop", err)
|
return err
|
||||||
}
|
}
|
||||||
if status != "running" {
|
if status != "running" {
|
||||||
return false, nil
|
return nil
|
||||||
}
|
}
|
||||||
err = E.From(cb())
|
return E.From(cb())
|
||||||
if err.HasError() {
|
|
||||||
return false, E.FailWith("stop", err)
|
|
||||||
}
|
|
||||||
return true, nil
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -244,42 +241,83 @@ func (w *watcher) watch() {
|
||||||
w.ctx = watcherCtx
|
w.ctx = watcherCtx
|
||||||
w.cancel = watcherCancel
|
w.cancel = watcherCancel
|
||||||
|
|
||||||
|
dockerWatcher := W.NewDockerWatcherWithClient(w.client)
|
||||||
|
|
||||||
|
defer close(w.wakeCh)
|
||||||
|
|
||||||
|
dockerEventCh, dockerEventErrCh := dockerWatcher.EventsWithOptions(w.ctx, W.DockerListOptions{
|
||||||
|
Filters: W.NewDockerFilter(
|
||||||
|
W.DockerFilterContainer,
|
||||||
|
W.DockerrFilterContainerName(w.ContainerName),
|
||||||
|
W.DockerFilterStart,
|
||||||
|
W.DockerFilterStop,
|
||||||
|
W.DockerFilterDie,
|
||||||
|
W.DockerFilterKill,
|
||||||
|
W.DockerFilterPause,
|
||||||
|
W.DockerFilterUnpause,
|
||||||
|
),
|
||||||
|
})
|
||||||
|
|
||||||
ticker := time.NewTicker(w.IdleTimeout)
|
ticker := time.NewTicker(w.IdleTimeout)
|
||||||
defer ticker.Stop()
|
defer ticker.Stop()
|
||||||
|
|
||||||
for {
|
for {
|
||||||
select {
|
select {
|
||||||
case <-mainLoopCtx.Done():
|
case <-mainLoopCtx.Done():
|
||||||
watcherCancel()
|
w.cancel()
|
||||||
case <-watcherCtx.Done():
|
case <-watcherCtx.Done():
|
||||||
w.l.Debug("stopped")
|
w.l.Debug("stopped")
|
||||||
return
|
return
|
||||||
|
case err := <-dockerEventErrCh:
|
||||||
|
if err != nil && err.IsNot(context.Canceled) {
|
||||||
|
w.l.Error(E.FailWith("docker watcher", err))
|
||||||
|
}
|
||||||
|
case e := <-dockerEventCh:
|
||||||
|
switch e.Action {
|
||||||
|
case event.ActionDockerStartUnpause:
|
||||||
|
w.running.Store(true)
|
||||||
|
w.l.Infof("%s %s", e.ActorName, e.Action)
|
||||||
|
case event.ActionDockerStopPause:
|
||||||
|
w.running.Store(false)
|
||||||
|
w.l.Infof("%s %s", e.ActorName, e.Action)
|
||||||
|
}
|
||||||
case <-ticker.C:
|
case <-ticker.C:
|
||||||
w.l.Debug("timeout")
|
w.l.Debug("timeout")
|
||||||
stopped, err := w.stopByMethod()
|
ticker.Stop()
|
||||||
if err.HasError() {
|
if err := w.stopByMethod(); err != nil && err.IsNot(context.Canceled) {
|
||||||
w.l.Error(err.Extraf("stop method: %s", w.StopMethod))
|
w.l.Error(E.FailWith("stop", err).Extraf("stop method: %s", w.StopMethod))
|
||||||
} else if stopped {
|
|
||||||
w.l.Infof("%s: ok", w.StopMethod)
|
|
||||||
} else {
|
|
||||||
ticker.Stop()
|
|
||||||
}
|
}
|
||||||
case <-w.wakeCh:
|
case <-w.wakeCh:
|
||||||
w.l.Debug("wake received")
|
w.l.Debug("wake signal received")
|
||||||
go func() {
|
ticker.Reset(w.IdleTimeout)
|
||||||
started, err := w.wakeIfStopped()
|
err := w.wakeIfStopped()
|
||||||
if err != nil {
|
if err != nil && err.IsNot(context.Canceled) {
|
||||||
w.l.Error(err)
|
w.l.Error(E.FailWith("wake", err))
|
||||||
} else if started {
|
}
|
||||||
w.l.Infof("awaken")
|
select {
|
||||||
ticker.Reset(w.IdleTimeout)
|
case w.wakeDone <- err: // this is passed to roundtrip
|
||||||
}
|
default:
|
||||||
w.wakeDone <- err // this is passed to roundtrip
|
}
|
||||||
}()
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func getLoadingResponse() *http.Response {
|
||||||
|
return &http.Response{
|
||||||
|
StatusCode: http.StatusAccepted,
|
||||||
|
Header: http.Header{
|
||||||
|
"Content-Type": {"text/html"},
|
||||||
|
"Cache-Control": {
|
||||||
|
"no-cache",
|
||||||
|
"no-store",
|
||||||
|
"must-revalidate",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Body: io.NopCloser(bytes.NewReader((loadingPage))),
|
||||||
|
ContentLength: int64(len(loadingPage)),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
var (
|
var (
|
||||||
mainLoopCtx context.Context
|
mainLoopCtx context.Context
|
||||||
mainLoopCancel context.CancelFunc
|
mainLoopCancel context.CancelFunc
|
||||||
|
@ -292,20 +330,6 @@ var (
|
||||||
|
|
||||||
logger = logrus.WithField("module", "idle_watcher")
|
logger = logrus.WithField("module", "idle_watcher")
|
||||||
|
|
||||||
loadingResponse = http.Response{
|
|
||||||
StatusCode: http.StatusAccepted,
|
|
||||||
Header: http.Header{
|
|
||||||
"Content-Type": {"text/html"},
|
|
||||||
"Cache-Control": {
|
|
||||||
"no-cache",
|
|
||||||
"no-store",
|
|
||||||
"must-revalidate",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
Body: io.NopCloser(bytes.NewReader((loadingPage))),
|
|
||||||
ContentLength: int64(len(loadingPage)),
|
|
||||||
}
|
|
||||||
|
|
||||||
loadingPage = []byte(`
|
loadingPage = []byte(`
|
||||||
<!DOCTYPE html>
|
<!DOCTYPE html>
|
||||||
<html>
|
<html>
|
||||||
|
@ -317,12 +341,16 @@ var (
|
||||||
<body>
|
<body>
|
||||||
<script>
|
<script>
|
||||||
window.onload = function() {
|
window.onload = function() {
|
||||||
setTimeout(function() {
|
setTimeout(function() {
|
||||||
location.reload();
|
window.location.reload()
|
||||||
}, 1000); // 1000 milliseconds = 1 second
|
}, 1000)
|
||||||
|
// fetch(window.location.href)
|
||||||
|
// .then(resp => resp.text())
|
||||||
|
// .then(data => { document.body.innerHTML = data; })
|
||||||
|
// .catch(err => { document.body.innerHTML = 'Error: ' + err; });
|
||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
<p>Container is starting... Please wait</p>
|
<h1>Container is starting... Please wait</h1>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
`[1:])
|
`[1:])
|
||||||
|
|
|
@ -3,8 +3,8 @@ module github.com/yusing/go-proxy
|
||||||
go 1.22.0
|
go 1.22.0
|
||||||
|
|
||||||
require (
|
require (
|
||||||
github.com/docker/cli v27.2.1+incompatible
|
github.com/docker/cli v27.3.1+incompatible
|
||||||
github.com/docker/docker v27.2.1+incompatible
|
github.com/docker/docker v27.3.1+incompatible
|
||||||
github.com/fsnotify/fsnotify v1.7.0
|
github.com/fsnotify/fsnotify v1.7.0
|
||||||
github.com/go-acme/lego/v4 v4.18.0
|
github.com/go-acme/lego/v4 v4.18.0
|
||||||
github.com/puzpuzpuz/xsync/v3 v3.4.0
|
github.com/puzpuzpuz/xsync/v3 v3.4.0
|
||||||
|
|
|
@ -13,10 +13,10 @@ 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.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||||
github.com/distribution/reference v0.6.0 h1:0IXCQ5g4/QMHHkarYzh5l+u8T3t73zM5QvfrDyIgxBk=
|
github.com/distribution/reference v0.6.0 h1:0IXCQ5g4/QMHHkarYzh5l+u8T3t73zM5QvfrDyIgxBk=
|
||||||
github.com/distribution/reference v0.6.0/go.mod h1:BbU0aIcezP1/5jX/8MP0YiH4SdvB5Y4f/wlDRiLyi3E=
|
github.com/distribution/reference v0.6.0/go.mod h1:BbU0aIcezP1/5jX/8MP0YiH4SdvB5Y4f/wlDRiLyi3E=
|
||||||
github.com/docker/cli v27.2.1+incompatible h1:U5BPtiD0viUzjGAjV1p0MGB8eVA3L3cbIrnyWmSJI70=
|
github.com/docker/cli v27.3.1+incompatible h1:qEGdFBF3Xu6SCvCYhc7CzaQTlBmqDuzxPDpigSyeKQQ=
|
||||||
github.com/docker/cli v27.2.1+incompatible/go.mod h1:JLrzqnKDaYBop7H2jaqPtU4hHvMKP+vjCwu2uszcLI8=
|
github.com/docker/cli v27.3.1+incompatible/go.mod h1:JLrzqnKDaYBop7H2jaqPtU4hHvMKP+vjCwu2uszcLI8=
|
||||||
github.com/docker/docker v27.2.1+incompatible h1:fQdiLfW7VLscyoeYEBz7/J8soYFDZV1u6VW6gJEjNMI=
|
github.com/docker/docker v27.3.1+incompatible h1:KttF0XoteNTicmUtBO0L2tP+J7FGRFTjaEF4k6WdhfI=
|
||||||
github.com/docker/docker v27.2.1+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk=
|
github.com/docker/docker v27.3.1+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk=
|
||||||
github.com/docker/go-connections v0.5.0 h1:USnMq7hx7gwdVZq1L49hLXaFtUdTADjXGp+uj1Br63c=
|
github.com/docker/go-connections v0.5.0 h1:USnMq7hx7gwdVZq1L49hLXaFtUdTADjXGp+uj1Br63c=
|
||||||
github.com/docker/go-connections v0.5.0/go.mod h1:ov60Kzw0kKElRwhNs9UlUHAE/F9Fe6GLaXnqyDdmEXc=
|
github.com/docker/go-connections v0.5.0/go.mod h1:ov60Kzw0kKElRwhNs9UlUHAE/F9Fe6GLaXnqyDdmEXc=
|
||||||
github.com/docker/go-units v0.5.0 h1:69rxXcBk27SvSaaxTtLh/8llcHD8vYHT7WSdRZ/jvr4=
|
github.com/docker/go-units v0.5.0 h1:69rxXcBk27SvSaaxTtLh/8llcHD8vYHT7WSdRZ/jvr4=
|
||||||
|
|
|
@ -22,13 +22,14 @@ type (
|
||||||
HideHeaders []string
|
HideHeaders []string
|
||||||
|
|
||||||
/* Docker only */
|
/* Docker only */
|
||||||
IdleTimeout time.Duration
|
IdleTimeout time.Duration
|
||||||
WakeTimeout time.Duration
|
WakeTimeout time.Duration
|
||||||
StopMethod T.StopMethod
|
StopMethod T.StopMethod
|
||||||
StopTimeout int
|
StopTimeout int
|
||||||
StopSignal T.Signal
|
StopSignal T.Signal
|
||||||
DockerHost string
|
DockerHost string
|
||||||
ContainerName string
|
ContainerName string
|
||||||
|
ContainerRunning bool
|
||||||
}
|
}
|
||||||
StreamEntry struct {
|
StreamEntry struct {
|
||||||
Alias T.Alias `json:"alias"`
|
Alias T.Alias `json:"alias"`
|
||||||
|
@ -102,20 +103,21 @@ func validateRPEntry(m *M.ProxyEntry, s T.Scheme, b E.Builder) *ReverseProxyEntr
|
||||||
}
|
}
|
||||||
|
|
||||||
return &ReverseProxyEntry{
|
return &ReverseProxyEntry{
|
||||||
Alias: T.NewAlias(m.Alias),
|
Alias: T.NewAlias(m.Alias),
|
||||||
Scheme: s,
|
Scheme: s,
|
||||||
URL: url,
|
URL: url,
|
||||||
NoTLSVerify: m.NoTLSVerify,
|
NoTLSVerify: m.NoTLSVerify,
|
||||||
PathPatterns: pathPatterns,
|
PathPatterns: pathPatterns,
|
||||||
SetHeaders: setHeaders,
|
SetHeaders: setHeaders,
|
||||||
HideHeaders: m.HideHeaders,
|
HideHeaders: m.HideHeaders,
|
||||||
IdleTimeout: idleTimeout,
|
IdleTimeout: idleTimeout,
|
||||||
WakeTimeout: wakeTimeout,
|
WakeTimeout: wakeTimeout,
|
||||||
StopMethod: stopMethod,
|
StopMethod: stopMethod,
|
||||||
StopTimeout: int(stopTimeOut.Seconds()), // docker api takes integer seconds for timeout argument
|
StopTimeout: int(stopTimeOut.Seconds()), // docker api takes integer seconds for timeout argument
|
||||||
StopSignal: stopSignal,
|
StopSignal: stopSignal,
|
||||||
DockerHost: m.DockerHost,
|
DockerHost: m.DockerHost,
|
||||||
ContainerName: m.ContainerName,
|
ContainerName: m.ContainerName,
|
||||||
|
ContainerRunning: m.Running,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,12 +1,13 @@
|
||||||
package provider
|
package provider
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"strconv"
|
||||||
|
|
||||||
D "github.com/yusing/go-proxy/docker"
|
D "github.com/yusing/go-proxy/docker"
|
||||||
E "github.com/yusing/go-proxy/error"
|
E "github.com/yusing/go-proxy/error"
|
||||||
M "github.com/yusing/go-proxy/models"
|
M "github.com/yusing/go-proxy/models"
|
||||||
R "github.com/yusing/go-proxy/route"
|
R "github.com/yusing/go-proxy/route"
|
||||||
W "github.com/yusing/go-proxy/watcher"
|
W "github.com/yusing/go-proxy/watcher"
|
||||||
. "github.com/yusing/go-proxy/watcher/event"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
type DockerProvider struct {
|
type DockerProvider struct {
|
||||||
|
@ -60,7 +61,7 @@ func (p *DockerProvider) LoadRoutesImpl() (routes R.Routes, err E.NestedError) {
|
||||||
return routes, errors.Build()
|
return routes, errors.Build()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *DockerProvider) OnEvent(event Event, routes R.Routes) (res EventResult) {
|
func (p *DockerProvider) OnEvent(event W.Event, routes R.Routes) (res EventResult) {
|
||||||
b := E.NewBuilder("event %s error", event)
|
b := E.NewBuilder("event %s error", event)
|
||||||
defer b.To(&res.err)
|
defer b.To(&res.err)
|
||||||
|
|
||||||
|
@ -72,36 +73,33 @@ func (p *DockerProvider) OnEvent(event Event, routes R.Routes) (res EventResult)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
switch event.Action {
|
client, err := D.ConnectClient(p.dockerHost)
|
||||||
case ActionStarted, ActionCreated, ActionModified:
|
if err.HasError() {
|
||||||
client, err := D.ConnectClient(p.dockerHost)
|
b.Add(E.FailWith("connect to docker", err))
|
||||||
if err.HasError() {
|
return
|
||||||
b.Add(E.FailWith("connect to docker", err))
|
|
||||||
return
|
|
||||||
}
|
|
||||||
defer client.Close()
|
|
||||||
cont, err := client.Inspect(event.ActorID)
|
|
||||||
if err.HasError() {
|
|
||||||
b.Add(E.FailWith("inspect container", err))
|
|
||||||
return
|
|
||||||
}
|
|
||||||
entries, err := p.entriesFromContainerLabels(cont)
|
|
||||||
b.Add(err)
|
|
||||||
|
|
||||||
entries.RangeAll(func(alias string, entry *M.ProxyEntry) {
|
|
||||||
if routes.Has(alias) {
|
|
||||||
b.Add(E.AlreadyExist("alias", alias))
|
|
||||||
} else {
|
|
||||||
if route, err := R.NewRoute(entry); err.HasError() {
|
|
||||||
b.Add(err)
|
|
||||||
} else {
|
|
||||||
routes.Store(alias, route)
|
|
||||||
b.Add(route.Start())
|
|
||||||
res.nAdded++
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
defer client.Close()
|
||||||
|
cont, err := client.Inspect(event.ActorID)
|
||||||
|
if err.HasError() {
|
||||||
|
b.Add(E.FailWith("inspect container", err))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
entries, err := p.entriesFromContainerLabels(cont)
|
||||||
|
b.Add(err)
|
||||||
|
|
||||||
|
entries.RangeAll(func(alias string, entry *M.ProxyEntry) {
|
||||||
|
if routes.Has(alias) {
|
||||||
|
b.Add(E.AlreadyExist("alias", alias))
|
||||||
|
} else {
|
||||||
|
if route, err := R.NewRoute(entry); err.HasError() {
|
||||||
|
b.Add(err)
|
||||||
|
} else {
|
||||||
|
routes.Store(alias, route)
|
||||||
|
b.Add(route.Start())
|
||||||
|
res.nAdded++
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
@ -125,6 +123,22 @@ func (p *DockerProvider) entriesFromContainerLabels(container D.Container) (M.Pr
|
||||||
errors.Add(p.applyLabel(entries, key, val))
|
errors.Add(p.applyLabel(entries, key, val))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// selecting correct host port
|
||||||
|
if container.HostConfig.NetworkMode != "host" {
|
||||||
|
for _, a := range container.Aliases {
|
||||||
|
entry, ok := entries.Load(a)
|
||||||
|
if !ok {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
for _, p := range container.Ports {
|
||||||
|
containerPort := strconv.Itoa(int(p.PrivatePort))
|
||||||
|
if containerPort == entry.Port {
|
||||||
|
entry.Port = strconv.Itoa(int(p.PublicPort))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return entries, errors.Build().Subject(container.ContainerName)
|
return entries, errors.Build().Subject(container.ContainerName)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -10,7 +10,6 @@ import (
|
||||||
R "github.com/yusing/go-proxy/route"
|
R "github.com/yusing/go-proxy/route"
|
||||||
U "github.com/yusing/go-proxy/utils"
|
U "github.com/yusing/go-proxy/utils"
|
||||||
W "github.com/yusing/go-proxy/watcher"
|
W "github.com/yusing/go-proxy/watcher"
|
||||||
. "github.com/yusing/go-proxy/watcher/event"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
type FileProvider struct {
|
type FileProvider struct {
|
||||||
|
@ -29,7 +28,7 @@ func Validate(data []byte) E.NestedError {
|
||||||
return U.ValidateYaml(U.GetSchema(common.ProvidersSchemaPath), data)
|
return U.ValidateYaml(U.GetSchema(common.ProvidersSchemaPath), data)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p FileProvider) OnEvent(event Event, routes R.Routes) (res EventResult) {
|
func (p FileProvider) OnEvent(event W.Event, routes R.Routes) (res EventResult) {
|
||||||
b := E.NewBuilder("event %s error", event)
|
b := E.NewBuilder("event %s error", event)
|
||||||
defer b.To(&res.err)
|
defer b.To(&res.err)
|
||||||
|
|
||||||
|
|
|
@ -9,7 +9,6 @@ import (
|
||||||
E "github.com/yusing/go-proxy/error"
|
E "github.com/yusing/go-proxy/error"
|
||||||
R "github.com/yusing/go-proxy/route"
|
R "github.com/yusing/go-proxy/route"
|
||||||
W "github.com/yusing/go-proxy/watcher"
|
W "github.com/yusing/go-proxy/watcher"
|
||||||
. "github.com/yusing/go-proxy/watcher/event"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
type (
|
type (
|
||||||
|
@ -29,7 +28,7 @@ type (
|
||||||
ProviderImpl interface {
|
ProviderImpl interface {
|
||||||
NewWatcher() W.Watcher
|
NewWatcher() W.Watcher
|
||||||
LoadRoutesImpl() (R.Routes, E.NestedError)
|
LoadRoutesImpl() (R.Routes, E.NestedError)
|
||||||
OnEvent(event Event, routes R.Routes) EventResult
|
OnEvent(event W.Event, routes R.Routes) EventResult
|
||||||
}
|
}
|
||||||
ProviderType string
|
ProviderType string
|
||||||
EventResult struct {
|
EventResult struct {
|
||||||
|
|
|
@ -4,72 +4,101 @@ import (
|
||||||
"context"
|
"context"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/docker/docker/api/types/events"
|
docker_events "github.com/docker/docker/api/types/events"
|
||||||
"github.com/docker/docker/api/types/filters"
|
"github.com/docker/docker/api/types/filters"
|
||||||
|
"github.com/sirupsen/logrus"
|
||||||
D "github.com/yusing/go-proxy/docker"
|
D "github.com/yusing/go-proxy/docker"
|
||||||
E "github.com/yusing/go-proxy/error"
|
E "github.com/yusing/go-proxy/error"
|
||||||
. "github.com/yusing/go-proxy/watcher/event"
|
"github.com/yusing/go-proxy/watcher/events"
|
||||||
)
|
)
|
||||||
|
|
||||||
type DockerWatcher struct {
|
type (
|
||||||
host string
|
DockerWatcher struct {
|
||||||
|
host string
|
||||||
|
client D.Client
|
||||||
|
logrus.FieldLogger
|
||||||
|
}
|
||||||
|
DockerListOptions = docker_events.ListOptions
|
||||||
|
)
|
||||||
|
|
||||||
|
// https://docs.docker.com/reference/api/engine/version/v1.47/#tag/System/operation/SystemPingHead
|
||||||
|
var (
|
||||||
|
DockerFilterContainer = filters.Arg("type", string(docker_events.ContainerEventType))
|
||||||
|
DockerFilterStart = filters.Arg("event", string(docker_events.ActionStart))
|
||||||
|
DockerFilterStop = filters.Arg("event", string(docker_events.ActionStop))
|
||||||
|
DockerFilterDie = filters.Arg("event", string(docker_events.ActionDie))
|
||||||
|
DockerFilterKill = filters.Arg("event", string(docker_events.ActionKill))
|
||||||
|
DockerFilterPause = filters.Arg("event", string(docker_events.ActionPause))
|
||||||
|
DockerFilterUnpause = filters.Arg("event", string(docker_events.ActionUnPause))
|
||||||
|
|
||||||
|
NewDockerFilter = filters.NewArgs
|
||||||
|
)
|
||||||
|
|
||||||
|
func DockerrFilterContainerName(name string) filters.KeyValuePair {
|
||||||
|
return filters.Arg("container", name)
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewDockerWatcher(host string) *DockerWatcher {
|
func NewDockerWatcher(host string) DockerWatcher {
|
||||||
return &DockerWatcher{host: host}
|
return DockerWatcher{host: host, FieldLogger: logrus.WithField("module", "docker_watcher")}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (w *DockerWatcher) Events(ctx context.Context) (<-chan Event, <-chan E.NestedError) {
|
func NewDockerWatcherWithClient(client D.Client) DockerWatcher {
|
||||||
|
return DockerWatcher{client: client, FieldLogger: logrus.WithField("module", "docker_watcher")}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w DockerWatcher) Events(ctx context.Context) (<-chan Event, <-chan E.NestedError) {
|
||||||
|
return w.EventsWithOptions(ctx, optionsWatchAll)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w DockerWatcher) EventsWithOptions(ctx context.Context, options DockerListOptions) (<-chan Event, <-chan E.NestedError) {
|
||||||
eventCh := make(chan Event)
|
eventCh := make(chan Event)
|
||||||
errCh := make(chan E.NestedError)
|
errCh := make(chan E.NestedError)
|
||||||
started := make(chan struct{})
|
started := make(chan struct{})
|
||||||
|
|
||||||
go func() {
|
go func() {
|
||||||
|
defer close(eventCh)
|
||||||
defer close(errCh)
|
defer close(errCh)
|
||||||
|
|
||||||
var cl D.Client
|
if !w.client.Connected() {
|
||||||
var err E.NestedError
|
var err E.NestedError
|
||||||
for range 3 {
|
for range 3 {
|
||||||
cl, err = D.ConnectClient(w.host)
|
w.client, err = D.ConnectClient(w.host)
|
||||||
if err.NoError() {
|
if err != nil {
|
||||||
break
|
defer w.client.Close()
|
||||||
|
break
|
||||||
|
}
|
||||||
|
time.Sleep(1 * time.Second)
|
||||||
|
}
|
||||||
|
if err.HasError() {
|
||||||
|
errCh <- E.FailWith("docker connection", err)
|
||||||
|
return
|
||||||
}
|
}
|
||||||
errCh <- err
|
|
||||||
time.Sleep(1 * time.Second)
|
|
||||||
}
|
}
|
||||||
if err.HasError() {
|
|
||||||
errCh <- E.Failure("connecting to docker")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
defer cl.Close()
|
|
||||||
|
|
||||||
cEventCh, cErrCh := cl.Events(ctx, dwOptions)
|
cEventCh, cErrCh := w.client.Events(ctx, options)
|
||||||
started <- struct{}{}
|
started <- struct{}{}
|
||||||
|
|
||||||
for {
|
for {
|
||||||
select {
|
select {
|
||||||
case <-ctx.Done():
|
case <-ctx.Done():
|
||||||
if err := <-cErrCh; err != nil {
|
if err := E.From(ctx.Err()); err != nil && err.IsNot(context.Canceled) {
|
||||||
errCh <- E.From(err)
|
errCh <- err
|
||||||
}
|
}
|
||||||
return
|
return
|
||||||
case msg := <-cEventCh:
|
case msg := <-cEventCh:
|
||||||
var Action Action
|
action, ok := events.DockerEventMap[msg.Action]
|
||||||
switch msg.Action {
|
if !ok {
|
||||||
case events.ActionStart:
|
w.Debugf("ignored unknown docker event: %s for container %s", msg.Action, msg.Actor.Attributes["name"])
|
||||||
Action = ActionCreated
|
continue
|
||||||
case events.ActionDie:
|
|
||||||
Action = ActionStopped
|
|
||||||
default: // NOTE: should not happen
|
|
||||||
Action = ActionModified
|
|
||||||
}
|
}
|
||||||
eventCh <- Event{
|
event := Event{
|
||||||
Type: EventTypeDocker,
|
Type: events.EventTypeDocker,
|
||||||
ActorID: msg.Actor.ID,
|
ActorID: msg.Actor.ID,
|
||||||
ActorAttributes: msg.Actor.Attributes, // labels
|
ActorAttributes: msg.Actor.Attributes, // labels
|
||||||
ActorName: msg.Actor.Attributes["name"],
|
ActorName: msg.Actor.Attributes["name"],
|
||||||
Action: Action,
|
Action: action,
|
||||||
}
|
}
|
||||||
|
eventCh <- event
|
||||||
case err := <-cErrCh:
|
case err := <-cErrCh:
|
||||||
if err == nil {
|
if err == nil {
|
||||||
continue
|
continue
|
||||||
|
@ -81,7 +110,7 @@ func (w *DockerWatcher) Events(ctx context.Context) (<-chan Event, <-chan E.Nest
|
||||||
default:
|
default:
|
||||||
if D.IsErrConnectionFailed(err) {
|
if D.IsErrConnectionFailed(err) {
|
||||||
time.Sleep(100 * time.Millisecond)
|
time.Sleep(100 * time.Millisecond)
|
||||||
cEventCh, cErrCh = cl.Events(ctx, dwOptions)
|
cEventCh, cErrCh = w.client.Events(ctx, options)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -92,8 +121,9 @@ func (w *DockerWatcher) Events(ctx context.Context) (<-chan Event, <-chan E.Nest
|
||||||
return eventCh, errCh
|
return eventCh, errCh
|
||||||
}
|
}
|
||||||
|
|
||||||
var dwOptions = events.ListOptions{Filters: filters.NewArgs(
|
var optionsWatchAll = DockerListOptions{Filters: NewDockerFilter(
|
||||||
filters.Arg("type", string(events.ContainerEventType)),
|
DockerFilterContainer,
|
||||||
filters.Arg("event", string(events.ActionStart)),
|
DockerFilterStart,
|
||||||
filters.Arg("event", string(events.ActionDie)), // 'stop' already triggering 'die'
|
DockerFilterStop,
|
||||||
|
DockerFilterDie,
|
||||||
)}
|
)}
|
||||||
|
|
|
@ -1,34 +0,0 @@
|
||||||
package event
|
|
||||||
|
|
||||||
import "fmt"
|
|
||||||
|
|
||||||
type (
|
|
||||||
Event struct {
|
|
||||||
Type EventType
|
|
||||||
ActorName string
|
|
||||||
ActorID string
|
|
||||||
ActorAttributes map[string]string
|
|
||||||
Action Action
|
|
||||||
}
|
|
||||||
Action string
|
|
||||||
EventType string
|
|
||||||
)
|
|
||||||
|
|
||||||
const (
|
|
||||||
ActionModified Action = "modified"
|
|
||||||
ActionCreated Action = "created"
|
|
||||||
ActionStarted Action = "started"
|
|
||||||
ActionDeleted Action = "deleted"
|
|
||||||
ActionStopped Action = "stopped"
|
|
||||||
|
|
||||||
EventTypeDocker EventType = "docker"
|
|
||||||
EventTypeFile EventType = "file"
|
|
||||||
)
|
|
||||||
|
|
||||||
func (e Event) String() string {
|
|
||||||
return fmt.Sprintf("%s %s", e.ActorName, e.Action)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (a Action) IsDelete() bool {
|
|
||||||
return a == ActionDeleted
|
|
||||||
}
|
|
49
src/watcher/events/events.go
Normal file
49
src/watcher/events/events.go
Normal file
|
@ -0,0 +1,49 @@
|
||||||
|
package events
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
dockerEvents "github.com/docker/docker/api/types/events"
|
||||||
|
)
|
||||||
|
|
||||||
|
type (
|
||||||
|
Event struct {
|
||||||
|
Type EventType
|
||||||
|
ActorName string
|
||||||
|
ActorID string
|
||||||
|
ActorAttributes map[string]string
|
||||||
|
Action Action
|
||||||
|
}
|
||||||
|
Action string
|
||||||
|
EventType string
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
ActionFileModified Action = "modified"
|
||||||
|
ActionFileCreated Action = "created"
|
||||||
|
ActionFileDeleted Action = "deleted"
|
||||||
|
|
||||||
|
ActionDockerStartUnpause Action = "start"
|
||||||
|
ActionDockerStopPause Action = "stop"
|
||||||
|
|
||||||
|
EventTypeDocker EventType = "docker"
|
||||||
|
EventTypeFile EventType = "file"
|
||||||
|
)
|
||||||
|
|
||||||
|
var DockerEventMap = map[dockerEvents.Action]Action{
|
||||||
|
dockerEvents.ActionCreate: ActionDockerStartUnpause,
|
||||||
|
dockerEvents.ActionStart: ActionDockerStartUnpause,
|
||||||
|
dockerEvents.ActionPause: ActionDockerStartUnpause,
|
||||||
|
dockerEvents.ActionDie: ActionDockerStopPause,
|
||||||
|
dockerEvents.ActionStop: ActionDockerStopPause,
|
||||||
|
dockerEvents.ActionUnPause: ActionDockerStopPause,
|
||||||
|
dockerEvents.ActionKill: ActionDockerStopPause,
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e Event) String() string {
|
||||||
|
return fmt.Sprintf("%s %s", e.ActorName, e.Action)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a Action) IsDelete() bool {
|
||||||
|
return a == ActionFileDeleted
|
||||||
|
}
|
|
@ -6,7 +6,6 @@ import (
|
||||||
|
|
||||||
"github.com/yusing/go-proxy/common"
|
"github.com/yusing/go-proxy/common"
|
||||||
E "github.com/yusing/go-proxy/error"
|
E "github.com/yusing/go-proxy/error"
|
||||||
. "github.com/yusing/go-proxy/watcher/event"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
type fileWatcher struct {
|
type fileWatcher struct {
|
||||||
|
|
|
@ -9,7 +9,7 @@ import (
|
||||||
"github.com/fsnotify/fsnotify"
|
"github.com/fsnotify/fsnotify"
|
||||||
"github.com/sirupsen/logrus"
|
"github.com/sirupsen/logrus"
|
||||||
E "github.com/yusing/go-proxy/error"
|
E "github.com/yusing/go-proxy/error"
|
||||||
. "github.com/yusing/go-proxy/watcher/event"
|
"github.com/yusing/go-proxy/watcher/events"
|
||||||
)
|
)
|
||||||
|
|
||||||
type fileWatcherHelper struct {
|
type fileWatcherHelper struct {
|
||||||
|
@ -81,30 +81,30 @@ func (h *fileWatcherHelper) start() {
|
||||||
|
|
||||||
for {
|
for {
|
||||||
select {
|
select {
|
||||||
case event, ok := <-h.w.Events:
|
case fsEvent, ok := <-h.w.Events:
|
||||||
if !ok {
|
if !ok {
|
||||||
// closed manually?
|
// closed manually?
|
||||||
fsLogger.Error("channel closed")
|
fsLogger.Error("channel closed")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
// retrieve the watcher
|
// retrieve the watcher
|
||||||
w, ok := h.m[path.Base(event.Name)]
|
w, ok := h.m[path.Base(fsEvent.Name)]
|
||||||
if !ok {
|
if !ok {
|
||||||
// watcher for this file does not exist
|
// watcher for this file does not exist
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
msg := Event{
|
msg := Event{
|
||||||
Type: EventTypeFile,
|
Type: events.EventTypeFile,
|
||||||
ActorName: w.filename,
|
ActorName: w.filename,
|
||||||
}
|
}
|
||||||
switch {
|
switch {
|
||||||
case event.Has(fsnotify.Create):
|
case fsEvent.Has(fsnotify.Create):
|
||||||
msg.Action = ActionCreated
|
msg.Action = events.ActionFileCreated
|
||||||
case event.Has(fsnotify.Write):
|
case fsEvent.Has(fsnotify.Write):
|
||||||
msg.Action = ActionModified
|
msg.Action = events.ActionFileModified
|
||||||
case event.Has(fsnotify.Remove), event.Has(fsnotify.Rename):
|
case fsEvent.Has(fsnotify.Remove), fsEvent.Has(fsnotify.Rename):
|
||||||
msg.Action = ActionDeleted
|
msg.Action = events.ActionFileDeleted
|
||||||
default: // ignore other events
|
default: // ignore other events
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,9 +4,11 @@ import (
|
||||||
"context"
|
"context"
|
||||||
|
|
||||||
E "github.com/yusing/go-proxy/error"
|
E "github.com/yusing/go-proxy/error"
|
||||||
. "github.com/yusing/go-proxy/watcher/event"
|
"github.com/yusing/go-proxy/watcher/events"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
type Event = events.Event
|
||||||
|
|
||||||
type Watcher interface {
|
type Watcher interface {
|
||||||
Events(ctx context.Context) (<-chan Event, <-chan E.NestedError)
|
Events(ctx context.Context) (<-chan Event, <-chan E.NestedError)
|
||||||
}
|
}
|
||||||
|
|
Loading…
Add table
Reference in a new issue