Initial QSfera import

This commit is contained in:
Курнат Андрей
2026-06-07 10:20:04 +03:00
commit 2315f25754
16485 changed files with 4826827 additions and 0 deletions
@@ -0,0 +1,13 @@
package source
import (
"crypto/md5"
"fmt"
)
// Sum returns the md5 checksum of the ChangeSet data.
func (c *ChangeSet) Sum() string {
h := md5.New()
h.Write(c.Data)
return fmt.Sprintf("%x", h.Sum(nil))
}
@@ -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)
```
@@ -0,0 +1,87 @@
// Package file is a file source. Expected format is json
package file
import (
"io"
"io/fs"
"os"
"go-micro.dev/v4/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}
}
@@ -0,0 +1,15 @@
package file
import (
"strings"
"go-micro.dev/v4/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()
}
@@ -0,0 +1,31 @@
package file
import (
"context"
"io/fs"
"go-micro.dev/v4/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)
}
}
@@ -0,0 +1,68 @@
//go:build !linux
// +build !linux
package file
import (
"os"
"github.com/fsnotify/fsnotify"
"go-micro.dev/v4/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()
}
@@ -0,0 +1,71 @@
//go:build linux
// +build linux
package file
import (
"os"
"github.com/fsnotify/fsnotify"
"go-micro.dev/v4/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()
}
+25
View File
@@ -0,0 +1,25 @@
package source
import (
"errors"
)
type noopWatcher struct {
exit chan struct{}
}
func (w *noopWatcher) Next() (*ChangeSet, error) {
<-w.exit
return nil, errors.New("noopWatcher stopped")
}
func (w *noopWatcher) Stop() error {
close(w.exit)
return nil
}
// NewNoopWatcher returns a watcher that blocks on Next() until Stop() is called.
func NewNoopWatcher() (Watcher, error) {
return &noopWatcher{exit: make(chan struct{})}, nil
}
+50
View File
@@ -0,0 +1,50 @@
package source
import (
"context"
"go-micro.dev/v4/client"
"go-micro.dev/v4/config/encoder"
"go-micro.dev/v4/config/encoder/json"
)
type Options struct {
// Encoder
Encoder encoder.Encoder
// for alternative data
Context context.Context
// Client to use for RPC
Client client.Client
}
type Option func(o *Options)
func NewOptions(opts ...Option) Options {
options := Options{
Encoder: json.NewEncoder(),
Context: context.Background(),
Client: client.DefaultClient,
}
for _, o := range opts {
o(&options)
}
return options
}
// WithEncoder sets the source encoder.
func WithEncoder(e encoder.Encoder) Option {
return func(o *Options) {
o.Encoder = e
}
}
// WithClient sets the source client.
func WithClient(c client.Client) Option {
return func(o *Options) {
o.Client = c
}
}
+35
View File
@@ -0,0 +1,35 @@
// Package source is the interface for sources
package source
import (
"errors"
"time"
)
var (
// ErrWatcherStopped is returned when source watcher has been stopped.
ErrWatcherStopped = errors.New("watcher stopped")
)
// Source is the source from which config is loaded.
type Source interface {
Read() (*ChangeSet, error)
Write(*ChangeSet) error
Watch() (Watcher, error)
String() string
}
// ChangeSet represents a set of changes from a source.
type ChangeSet struct {
Timestamp time.Time
Checksum string
Format string
Source string
Data []byte
}
// Watcher watches a source for changes.
type Watcher interface {
Next() (*ChangeSet, error)
Stop() error
}