chore: import upstream snapshot with attribution
govulncheck / govulncheck (push) Has been cancelled
Lint / golangci-lint (push) Has been cancelled
Run Tests / Unit Tests (push) Has been cancelled
Run Tests / Etcd Integration Tests (push) Has been cancelled
Harness (E2E) / Harnesses (mock LLM) (push) Has been cancelled
Harness (E2E) / Provider harnesses (live LLM conformance) (push) Has been cancelled

This commit is contained in:
wehub-resource-sync
2026-07-13 12:40:33 +08:00
commit e071084ebe
982 changed files with 160368 additions and 0 deletions
+69
View File
@@ -0,0 +1,69 @@
# File Source
The file source reads config from a file.
It uses the File extension to determine the Format e.g `config.yaml` has the yaml format.
It does not make use of encoders or interpet the file data. If a file extension is not present
the source Format will default to the Encoder in options.
## Example
A config file format in json
```json
{
"hosts": {
"database": {
"address": "10.0.0.1",
"port": 3306
},
"cache": {
"address": "10.0.0.2",
"port": 6379
}
}
}
```
## New Source
Specify file source with path to file. Path is optional and will default to `config.json`
```go
fileSource := file.NewSource(
file.WithPath("/tmp/config.json"),
)
```
## File Format
To load different file formats e.g yaml, toml, xml simply specify them with their extension
```go
fileSource := file.NewSource(
file.WithPath("/tmp/config.yaml"),
)
```
If you want to specify a file without extension, ensure you set the encoder to the same format
```go
e := toml.NewEncoder()
fileSource := file.NewSource(
file.WithPath("/tmp/config"),
source.WithEncoder(e),
)
```
## Load Source
Load the source into config
```go
// Create new config
conf := config.NewConfig()
// Load file source
conf.Load(fileSource)
```
+87
View File
@@ -0,0 +1,87 @@
// Package file is a file source. Expected format is json
package file
import (
"io"
"io/fs"
"os"
"go-micro.dev/v6/config/source"
)
type file struct {
opts source.Options
fs fs.FS
path string
}
var (
DefaultPath = "config.json"
)
func (f *file) Read() (*source.ChangeSet, error) {
var fh fs.File
var err error
if f.fs != nil {
fh, err = f.fs.Open(f.path)
} else {
fh, err = os.Open(f.path)
}
if err != nil {
return nil, err
}
defer fh.Close()
b, err := io.ReadAll(fh)
if err != nil {
return nil, err
}
info, err := fh.Stat()
if err != nil {
return nil, err
}
cs := &source.ChangeSet{
Format: format(f.path, f.opts.Encoder),
Source: f.String(),
Timestamp: info.ModTime(),
Data: b,
}
cs.Checksum = cs.Sum()
return cs, nil
}
func (f *file) String() string {
return "file"
}
func (f *file) Watch() (source.Watcher, error) {
// do not watch if fs.FS instance is provided
if f.fs != nil {
return source.NewNoopWatcher()
}
if _, err := os.Stat(f.path); err != nil {
return nil, err
}
return newWatcher(f)
}
func (f *file) Write(cs *source.ChangeSet) error {
return nil
}
func NewSource(opts ...source.Option) source.Source {
options := source.NewOptions(opts...)
fs, _ := options.Context.Value(fsKey{}).(fs.FS)
path := DefaultPath
f, ok := options.Context.Value(filePathKey{}).(string)
if ok {
path = f
}
return &file{opts: options, fs: fs, path: path}
}
+89
View File
@@ -0,0 +1,89 @@
package file_test
import (
"fmt"
"os"
"path/filepath"
"testing"
"testing/fstest"
"time"
"go-micro.dev/v6/config"
"go-micro.dev/v6/config/source/file"
)
func TestConfig(t *testing.T) {
data := []byte(`{"foo": "bar"}`)
path := filepath.Join(os.TempDir(), fmt.Sprintf("file.%d", time.Now().UnixNano()))
fh, err := os.Create(path)
if err != nil {
t.Error(err)
}
defer func() {
fh.Close()
os.Remove(path)
}()
_, err = fh.Write(data)
if err != nil {
t.Error(err)
}
conf, err := config.NewConfig()
if err != nil {
t.Fatal(err)
}
conf.Load(file.NewSource(file.WithPath(path)))
// simulate multiple close
go conf.Close()
go conf.Close()
}
func TestFile(t *testing.T) {
data := []byte(`{"foo": "bar"}`)
path := filepath.Join(os.TempDir(), fmt.Sprintf("file.%d", time.Now().UnixNano()))
fh, err := os.Create(path)
if err != nil {
t.Error(err)
}
defer func() {
fh.Close()
os.Remove(path)
}()
_, err = fh.Write(data)
if err != nil {
t.Error(err)
}
f := file.NewSource(file.WithPath(path))
c, err := f.Read()
if err != nil {
t.Error(err)
}
if string(c.Data) != string(data) {
t.Logf("%+v", c)
t.Error("data from file does not match")
}
}
func TestWithFS(t *testing.T) {
data := []byte(`{"foo": "bar"}`)
path := fmt.Sprintf("file.%d", time.Now().UnixNano())
fsMock := fstest.MapFS{
path: &fstest.MapFile{
Data: data,
Mode: 0666,
},
}
f := file.NewSource(file.WithFS(fsMock), file.WithPath(path))
c, err := f.Read()
if err != nil {
t.Error(err)
}
if string(c.Data) != string(data) {
t.Logf("%+v", c)
t.Error("data from file does not match")
}
}
+15
View File
@@ -0,0 +1,15 @@
package file
import (
"strings"
"go-micro.dev/v6/config/encoder"
)
func format(p string, e encoder.Encoder) string {
parts := strings.Split(p, ".")
if len(parts) > 1 {
return parts[len(parts)-1]
}
return e.String()
}
+30
View File
@@ -0,0 +1,30 @@
package file
import (
"testing"
"go-micro.dev/v6/config/source"
)
func TestFormat(t *testing.T) {
opts := source.NewOptions()
e := opts.Encoder
testCases := []struct {
p string
f string
}{
{"/foo/bar.json", "json"},
{"/foo/bar.yaml", "yaml"},
{"/foo/bar.xml", "xml"},
{"/foo/bar.conf.ini", "ini"},
{"conf", e.String()},
}
for _, d := range testCases {
f := format(d.p, e)
if f != d.f {
t.Fatalf("%s: expected %s got %s", d.p, d.f, f)
}
}
}
+31
View File
@@ -0,0 +1,31 @@
package file
import (
"context"
"io/fs"
"go-micro.dev/v6/config/source"
)
type filePathKey struct{}
type fsKey struct{}
// WithPath sets the path to file.
func WithPath(p string) source.Option {
return func(o *source.Options) {
if o.Context == nil {
o.Context = context.Background()
}
o.Context = context.WithValue(o.Context, filePathKey{}, p)
}
}
// WithFS sets the underlying filesystem to lookup file from (default os.FS).
func WithFS(fs fs.FS) source.Option {
return func(o *source.Options) {
if o.Context == nil {
o.Context = context.Background()
}
o.Context = context.WithValue(o.Context, fsKey{}, fs)
}
}
+68
View File
@@ -0,0 +1,68 @@
//go:build !linux
// +build !linux
package file
import (
"os"
"github.com/fsnotify/fsnotify"
"go-micro.dev/v6/config/source"
)
type watcher struct {
f *file
fw *fsnotify.Watcher
}
func newWatcher(f *file) (source.Watcher, error) {
fw, err := fsnotify.NewWatcher()
if err != nil {
return nil, err
}
fw.Add(f.path)
return &watcher{
f: f,
fw: fw,
}, nil
}
func (w *watcher) Next() (*source.ChangeSet, error) {
// try get the event
select {
case event, ok := <-w.fw.Events:
// check if channel was closed (i.e. Watcher.Close() was called).
if !ok {
return nil, source.ErrWatcherStopped
}
if event.Has(fsnotify.Rename) {
// check existence of file, and add watch again
_, err := os.Stat(event.Name)
if err == nil || os.IsExist(err) {
w.fw.Add(event.Name)
}
}
c, err := w.f.Read()
if err != nil {
return nil, err
}
return c, nil
case err, ok := <-w.fw.Errors:
// check if channel was closed (i.e. Watcher.Close() was called).
if !ok {
return nil, source.ErrWatcherStopped
}
return nil, err
}
}
func (w *watcher) Stop() error {
return w.fw.Close()
}
+71
View File
@@ -0,0 +1,71 @@
//go:build linux
// +build linux
package file
import (
"os"
"github.com/fsnotify/fsnotify"
"go-micro.dev/v6/config/source"
)
type watcher struct {
f *file
fw *fsnotify.Watcher
}
func newWatcher(f *file) (source.Watcher, error) {
fw, err := fsnotify.NewWatcher()
if err != nil {
return nil, err
}
_ = fw.Add(f.path)
return &watcher{
f: f,
fw: fw,
}, nil
}
func (w *watcher) Next() (*source.ChangeSet, error) {
// try get the event
select {
case event, ok := <-w.fw.Events:
// check if channel was closed (i.e. Watcher.Close() was called).
if !ok {
return nil, source.ErrWatcherStopped
}
if event.Has(fsnotify.Rename) {
// check existence of file, and add watch again
_, err := os.Stat(event.Name)
if err == nil || os.IsExist(err) {
_ = w.fw.Add(event.Name)
}
}
c, err := w.f.Read()
if err != nil {
return nil, err
}
// add path again for the event bug of fsnotify
_ = w.fw.Add(w.f.path)
return c, nil
case err, ok := <-w.fw.Errors:
// check if channel was closed (i.e. Watcher.Close() was called).
if !ok {
return nil, source.ErrWatcherStopped
}
return nil, err
}
}
func (w *watcher) Stop() error {
return w.fw.Close()
}
+120
View File
@@ -0,0 +1,120 @@
package file_test
import (
"bytes"
"errors"
"fmt"
"os"
"path/filepath"
"testing"
"time"
"go-micro.dev/v6/config/source"
"go-micro.dev/v6/config/source/file"
)
// createTestFile a local helper to creates a temporary file with the given data
func createTestFile(data []byte) (*os.File, func(), string, error) {
path := filepath.Join(os.TempDir(), fmt.Sprintf("file.%d", time.Now().UnixNano()))
fh, err := os.Create(path)
if err != nil {
return nil, func() {}, "", err
}
_, err = fh.Write(data)
if err != nil {
return nil, func() {}, "", err
}
return fh, func() {
fh.Close()
os.Remove(path)
}, path, err
}
func TestWatcher(t *testing.T) {
data := []byte(`{"foo": "bar"}`)
fh, cleanup, path, err := createTestFile(data)
if err != nil {
t.Error(err)
}
defer cleanup()
f := file.NewSource(file.WithPath(path))
if err != nil {
t.Error(err)
}
// create a watcher
w, err := f.Watch()
if err != nil {
t.Error(err)
}
newdata := []byte(`{"foo": "baz"}`)
go func() {
sc, err := w.Next()
if err != nil {
t.Error(err)
return
}
if !bytes.Equal(sc.Data, newdata) {
t.Error("expected data to be different")
}
}()
// rewrite to the file to trigger a change
_, err = fh.WriteAt(newdata, 0)
if err != nil {
t.Error(err)
}
// wait for the underlying watcher to detect changes
time.Sleep(time.Second)
}
func TestWatcherStop(t *testing.T) {
data := []byte(`{"foo": "bar"}`)
_, cleanup, path, err := createTestFile(data)
if err != nil {
t.Error(err)
}
defer cleanup()
src := file.NewSource(file.WithPath(path))
if err != nil {
t.Error(err)
}
// create a watcher
w, err := src.Watch()
if err != nil {
t.Error(err)
}
defer func() {
var err error
c := make(chan struct{})
defer close(c)
go func() {
_, err = w.Next()
c <- struct{}{}
}()
select {
case <-time.After(2 * time.Second):
err = errors.New("timeout waiting for Watcher.Next() to return")
case <-c:
}
if !errors.Is(err, source.ErrWatcherStopped) {
t.Error(err)
}
}()
// stop the watcher
w.Stop()
}