preparing for v0.5

This commit is contained in:
default
2024-08-01 10:06:42 +08:00
parent 24778d1093
commit 93359110a2
115 changed files with 5153 additions and 4395 deletions

View 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
View 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
}

View 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()

View 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
View 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)
}