mirror of
https://github.com/yusing/godoxy.git
synced 2026-03-20 00:03:53 +01:00
preparing for v0.5
This commit is contained in:
86
src/watcher/docker_watcher.go
Normal file
86
src/watcher/docker_watcher.go
Normal file
@@ -0,0 +1,86 @@
|
||||
package watcher
|
||||
|
||||
import (
|
||||
"context"
|
||||
"time"
|
||||
|
||||
"github.com/docker/docker/api/types"
|
||||
"github.com/docker/docker/api/types/filters"
|
||||
D "github.com/yusing/go-proxy/docker"
|
||||
E "github.com/yusing/go-proxy/error"
|
||||
)
|
||||
|
||||
type DockerWatcher struct {
|
||||
host string
|
||||
}
|
||||
|
||||
func NewDockerWatcher(host string) *DockerWatcher {
|
||||
return &DockerWatcher{host: host}
|
||||
}
|
||||
|
||||
func (w *DockerWatcher) Events(ctx context.Context) (<-chan Event, <-chan E.NestedError) {
|
||||
eventCh := make(chan Event)
|
||||
errCh := make(chan E.NestedError)
|
||||
started := make(chan struct{})
|
||||
|
||||
go func() {
|
||||
defer close(errCh)
|
||||
|
||||
var cl D.Client
|
||||
var err E.NestedError
|
||||
for range 3 {
|
||||
cl, err = D.ConnectClient(w.host)
|
||||
if err.IsNotNil() {
|
||||
break
|
||||
}
|
||||
errCh <- E.From(err)
|
||||
time.Sleep(1 * time.Second)
|
||||
}
|
||||
if err.IsNotNil() {
|
||||
errCh <- E.Failure("connect to docker")
|
||||
return
|
||||
}
|
||||
|
||||
cEventCh, cErrCh := cl.Events(ctx, dwOptions)
|
||||
started <- struct{}{}
|
||||
|
||||
for {
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
errCh <- E.From(<-cErrCh)
|
||||
return
|
||||
case msg := <-cEventCh:
|
||||
containerName, ok := msg.Actor.Attributes["name"]
|
||||
if !ok {
|
||||
// NOTE: should not happen
|
||||
// but if it happens, just ignore it
|
||||
continue
|
||||
}
|
||||
eventCh <- Event{
|
||||
ActorName: containerName,
|
||||
Action: ActionModified,
|
||||
}
|
||||
case err := <-cErrCh:
|
||||
errCh <- E.From(err)
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
return
|
||||
default:
|
||||
if D.IsErrConnectionFailed(err) {
|
||||
time.Sleep(100 * time.Millisecond)
|
||||
cEventCh, cErrCh = cl.Events(ctx, dwOptions)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}()
|
||||
<-started
|
||||
|
||||
return eventCh, errCh
|
||||
}
|
||||
|
||||
var dwOptions = types.EventsOptions{Filters: filters.NewArgs(
|
||||
filters.Arg("type", "container"),
|
||||
filters.Arg("event", "start"),
|
||||
filters.Arg("event", "die"), // 'stop' already triggering 'die'
|
||||
)}
|
||||
33
src/watcher/event.go
Normal file
33
src/watcher/event.go
Normal file
@@ -0,0 +1,33 @@
|
||||
package watcher
|
||||
|
||||
import "fmt"
|
||||
|
||||
type (
|
||||
Event struct {
|
||||
ActorName string
|
||||
Action Action
|
||||
}
|
||||
Action string
|
||||
)
|
||||
|
||||
const (
|
||||
ActionModified Action = "MODIFIED"
|
||||
ActionDeleted Action = "DELETED"
|
||||
ActionCreated Action = "CREATED"
|
||||
)
|
||||
|
||||
func (e *Event) String() string {
|
||||
return fmt.Sprintf("%s %s", e.ActorName, e.Action)
|
||||
}
|
||||
|
||||
func (a Action) IsDelete() bool {
|
||||
return a == ActionDeleted
|
||||
}
|
||||
|
||||
func (a Action) IsModify() bool {
|
||||
return a == ActionModified
|
||||
}
|
||||
|
||||
func (a Action) IsCreate() bool {
|
||||
return a == ActionCreated
|
||||
}
|
||||
25
src/watcher/file_watcher.go
Normal file
25
src/watcher/file_watcher.go
Normal file
@@ -0,0 +1,25 @@
|
||||
package watcher
|
||||
|
||||
import (
|
||||
"context"
|
||||
"path"
|
||||
|
||||
E "github.com/yusing/go-proxy/error"
|
||||
)
|
||||
|
||||
type fileWatcher struct {
|
||||
filename string
|
||||
}
|
||||
|
||||
func NewFileWatcher(filename string) Watcher {
|
||||
if path.Base(filename) != filename {
|
||||
panic("filename must be a relative path")
|
||||
}
|
||||
return &fileWatcher{filename: filename}
|
||||
}
|
||||
|
||||
func (f *fileWatcher) Events(ctx context.Context) (<-chan Event, <-chan E.NestedError) {
|
||||
return fwHelper.Add(ctx, f)
|
||||
}
|
||||
|
||||
var fwHelper = newFileWatcherHelper()
|
||||
132
src/watcher/file_watcher_helper.go
Normal file
132
src/watcher/file_watcher_helper.go
Normal file
@@ -0,0 +1,132 @@
|
||||
package watcher
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"path"
|
||||
"sync"
|
||||
|
||||
"github.com/fsnotify/fsnotify"
|
||||
"github.com/sirupsen/logrus"
|
||||
"github.com/yusing/go-proxy/common"
|
||||
E "github.com/yusing/go-proxy/error"
|
||||
)
|
||||
|
||||
type fileWatcherHelper struct {
|
||||
w *fsnotify.Watcher
|
||||
m map[string]*fileWatcherStream
|
||||
wg sync.WaitGroup
|
||||
mu sync.Mutex
|
||||
}
|
||||
|
||||
type fileWatcherStream struct {
|
||||
*fileWatcher
|
||||
stopped chan struct{}
|
||||
eventCh chan Event
|
||||
errCh chan E.NestedError
|
||||
}
|
||||
|
||||
func newFileWatcherHelper() *fileWatcherHelper {
|
||||
w, err := fsnotify.NewWatcher()
|
||||
if err != nil {
|
||||
logrus.Panicf("unable to create fs watcher: %s", err)
|
||||
}
|
||||
// watch config path for all changes
|
||||
err = w.Add(common.ConfigBasePath)
|
||||
if err != nil {
|
||||
logrus.Panicf("unable to create fs watcher: %s", err)
|
||||
}
|
||||
helper := &fileWatcherHelper{
|
||||
w: w,
|
||||
m: make(map[string]*fileWatcherStream),
|
||||
}
|
||||
go helper.start()
|
||||
return helper
|
||||
}
|
||||
|
||||
func (h *fileWatcherHelper) Add(ctx context.Context, w *fileWatcher) (<-chan Event, <-chan E.NestedError) {
|
||||
h.mu.Lock()
|
||||
defer h.mu.Unlock()
|
||||
|
||||
// check if the watcher already exists
|
||||
s, ok := h.m[w.filename]
|
||||
if ok {
|
||||
return s.eventCh, s.errCh
|
||||
}
|
||||
s = &fileWatcherStream{
|
||||
fileWatcher: w,
|
||||
stopped: make(chan struct{}),
|
||||
eventCh: make(chan Event),
|
||||
errCh: make(chan E.NestedError),
|
||||
}
|
||||
go func() {
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
h.Remove(w)
|
||||
return
|
||||
case <-s.stopped:
|
||||
return
|
||||
}
|
||||
}()
|
||||
h.m[w.filename] = s
|
||||
return s.eventCh, s.errCh
|
||||
}
|
||||
|
||||
func (h *fileWatcherHelper) Remove(w *fileWatcher) {
|
||||
h.mu.Lock()
|
||||
defer h.mu.Unlock()
|
||||
|
||||
h.m[w.filename].stopped <- struct{}{}
|
||||
delete(h.m, w.filename)
|
||||
}
|
||||
|
||||
// deinit closes the fs watcher
|
||||
// and waits for the start() loop to finish
|
||||
func (h *fileWatcherHelper) close() {
|
||||
_ = h.w.Close()
|
||||
h.wg.Wait() // wait for `start()` loop to finish
|
||||
}
|
||||
|
||||
func (h *fileWatcherHelper) start() {
|
||||
defer h.wg.Done()
|
||||
|
||||
for {
|
||||
select {
|
||||
case event, ok := <-h.w.Events:
|
||||
if !ok {
|
||||
// closed manually?
|
||||
fsLogger.Error("channel closed")
|
||||
return
|
||||
}
|
||||
// retrieve the watcher
|
||||
w, ok := h.m[path.Base(event.Name)]
|
||||
if !ok {
|
||||
// watcher for this file does not exist
|
||||
continue
|
||||
}
|
||||
|
||||
msg := Event{ActorName: w.filename}
|
||||
switch {
|
||||
case event.Has(fsnotify.Create):
|
||||
msg.Action = ActionCreated
|
||||
case event.Has(fsnotify.Write):
|
||||
msg.Action = ActionModified
|
||||
case event.Has(fsnotify.Remove), event.Has(fsnotify.Rename):
|
||||
msg.Action = ActionDeleted
|
||||
default: // ignore other events
|
||||
continue
|
||||
}
|
||||
|
||||
// send event
|
||||
w.eventCh <- msg
|
||||
case err := <-h.w.Errors:
|
||||
if errors.Is(err, fsnotify.ErrClosed) {
|
||||
// closed manually?
|
||||
return
|
||||
}
|
||||
fsLogger.Error(err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var fsLogger = logrus.WithField("?", "fsnotify")
|
||||
11
src/watcher/watcher.go
Normal file
11
src/watcher/watcher.go
Normal file
@@ -0,0 +1,11 @@
|
||||
package watcher
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
E "github.com/yusing/go-proxy/error"
|
||||
)
|
||||
|
||||
type Watcher interface {
|
||||
Events(ctx context.Context) (<-chan Event, <-chan E.NestedError)
|
||||
}
|
||||
Reference in New Issue
Block a user