1
0
mirror of https://github.com/go-micro/go-micro.git synced 2025-08-10 21:52:01 +02:00

Plugins and profiles (#2764)

* feat: more plugins

* chore(ci): split out benchmarks

Attempt to resolve too many open files in ci

* chore(ci): split out benchmarks

* fix(ci): Attempt to resolve too many open files in ci

* fix: set DefaultX for cli flag and service option

* fix: restore http broker

* fix: default http broker

* feat: full nats profile

* chore: still ugly, not ready

* fix: better initialization for profiles

* fix(tests): comment out flaky listen tests

* fix: disable benchmarks on gha

* chore: cleanup, comments

* chore: add nats config source
This commit is contained in:
Brian Ketelsen
2025-05-20 13:24:06 -04:00
committed by GitHub
parent e12504ce3a
commit ddc34801ee
58 changed files with 6792 additions and 218 deletions

View File

@@ -0,0 +1,56 @@
# Nats Source
The nats source reads config from nats key/values
## Nats Format
The nats source expects keys under the default bucket `default` default key `micro_config`
Values are expected to be json
```
nats kv put default micro_config '{"nats": {"address": "10.0.0.1", "port": 8488}}'
```
```
conf.Get("nats")
```
## New Source
Specify source with data
```go
natsSource := nats.NewSource(
nats.WithUrl("127.0.0.1:4222"),
nats.WithBucket("my_bucket"),
nats.WithKey("my_key"),
)
```
## Load Source
Load the source into config
```go
// Create new config
conf := config.NewConfig()
// Load nats source
conf.Load(natsSource)
```
## Watch
```go
wh, _ := natsSource.Watch()
for {
v, err := watcher.Next()
if err != nil {
log.Fatalf("err %v", err)
}
log.Infof("data %v", string(v.Data))
}
```

134
config/source/nats/nats.go Normal file
View File

@@ -0,0 +1,134 @@
package nats
import (
"fmt"
"net"
"strings"
"time"
natsgo "github.com/nats-io/nats.go"
"go-micro.dev/v5/config/source"
log "go-micro.dev/v5/logger"
)
type nats struct {
url string
bucket string
key string
kv natsgo.KeyValue
opts source.Options
}
// DefaultBucket is the bucket that nats keys will be assumed to have if you
// haven't specified one.
var (
DefaultBucket = "default"
DefaultKey = "micro_config"
)
func (n *nats) Read() (*source.ChangeSet, error) {
e, err := n.kv.Get(n.key)
if err != nil {
if err == natsgo.ErrKeyNotFound {
return nil, nil
}
return nil, err
}
if e.Value() == nil || len(e.Value()) == 0 {
return nil, fmt.Errorf("source not found: %s", n.key)
}
cs := &source.ChangeSet{
Data: e.Value(),
Format: n.opts.Encoder.String(),
Source: n.String(),
Timestamp: time.Now(),
}
cs.Checksum = cs.Sum()
return cs, nil
}
func (n *nats) Write(cs *source.ChangeSet) error {
_, err := n.kv.Put(n.key, cs.Data)
if err != nil {
return err
}
return nil
}
func (n *nats) String() string {
return "nats"
}
func (n *nats) Watch() (source.Watcher, error) {
return newWatcher(n.kv, n.bucket, n.key, n.String(), n.opts.Encoder)
}
func NewSource(opts ...source.Option) source.Source {
options := source.NewOptions(opts...)
config := natsgo.GetDefaultOptions()
urls, ok := options.Context.Value(urlKey{}).([]string)
endpoints := []string{}
if ok {
for _, u := range urls {
addr, port, err := net.SplitHostPort(u)
if ae, ok := err.(*net.AddrError); ok && ae.Err == "missing port in address" {
port = "4222"
addr = u
endpoints = append(endpoints, fmt.Sprintf("%s:%s", addr, port))
} else if err == nil {
endpoints = append(endpoints, fmt.Sprintf("%s:%s", addr, port))
}
}
}
if len(endpoints) == 0 {
endpoints = append(endpoints, "127.0.0.1:4222")
}
bucket, ok := options.Context.Value(bucketKey{}).(string)
if !ok {
bucket = DefaultBucket
}
key, ok := options.Context.Value(keyKey{}).(string)
if !ok {
key = DefaultKey
}
config.Url = strings.Join(endpoints, ",")
nc, err := natsgo.Connect(config.Url)
if err != nil {
log.Error(err)
}
js, err := nc.JetStream(natsgo.MaxWait(10 * time.Second))
if err != nil {
log.Error(err)
}
kv, err := js.KeyValue(bucket)
if err == natsgo.ErrBucketNotFound || err == natsgo.ErrKeyNotFound {
kv, err = js.CreateKeyValue(&natsgo.KeyValueConfig{Bucket: bucket})
if err != nil {
log.Error(err)
}
}
if err != nil {
log.Error(err)
}
return &nats{
url: config.Url,
bucket: bucket,
key: key,
kv: kv,
opts: options,
}
}

View File

@@ -0,0 +1,54 @@
package nats
import (
"context"
"time"
natsgo "github.com/nats-io/nats.go"
"go-micro.dev/v5/config/source"
)
type (
urlKey struct{}
bucketKey struct{}
keyKey struct{}
)
// WithUrl sets the nats url.
func WithUrl(a ...string) source.Option {
return func(o *source.Options) {
if o.Context == nil {
o.Context = context.Background()
}
o.Context = context.WithValue(o.Context, urlKey{}, a)
}
}
// WithBucket sets the nats key.
func WithBucket(a string) source.Option {
return func(o *source.Options) {
if o.Context == nil {
o.Context = context.Background()
}
o.Context = context.WithValue(o.Context, bucketKey{}, a)
}
}
// WithKey sets the nats key.
func WithKey(a string) source.Option {
return func(o *source.Options) {
if o.Context == nil {
o.Context = context.Background()
}
o.Context = context.WithValue(o.Context, keyKey{}, a)
}
}
func Client(url string) (natsgo.JetStreamContext, error) {
nc, err := natsgo.Connect(url)
if err != nil {
return nil, err
}
return nc.JetStream(natsgo.MaxWait(10 * time.Second))
}

View File

@@ -0,0 +1,79 @@
package nats
import (
"time"
natsgo "github.com/nats-io/nats.go"
"go-micro.dev/v5/config/encoder"
"go-micro.dev/v5/config/source"
)
type watcher struct {
e encoder.Encoder
name string
bucket string
key string
ch chan *source.ChangeSet
exit chan bool
}
func newWatcher(kv natsgo.KeyValue, bucket, key, name string, e encoder.Encoder) (source.Watcher, error) {
w := &watcher{
e: e,
name: name,
bucket: bucket,
key: key,
ch: make(chan *source.ChangeSet),
exit: make(chan bool),
}
wh, _ := kv.Watch(key)
go func() {
for {
select {
case v := <-wh.Updates():
if v != nil {
w.handle(v.Value())
}
case <-w.exit:
_ = wh.Stop()
return
}
}
}()
return w, nil
}
func (w *watcher) handle(data []byte) {
cs := &source.ChangeSet{
Timestamp: time.Now(),
Format: w.e.String(),
Source: w.name,
Data: data,
}
cs.Checksum = cs.Sum()
w.ch <- cs
}
func (w *watcher) Next() (*source.ChangeSet, error) {
select {
case cs := <-w.ch:
return cs, nil
case <-w.exit:
return nil, source.ErrWatcherStopped
}
}
func (w *watcher) Stop() error {
select {
case <-w.exit:
return nil
default:
close(w.exit)
}
return nil
}