1
0
mirror of https://github.com/gogf/gf.git synced 2025-04-05 03:05:05 +08:00

feat(contrib/registry/consul): add consul registry support (#4016)

This commit is contained in:
John Guo 2024-12-09 23:12:29 +08:00 committed by GitHub
parent 13bc192e36
commit 2e788be1d3
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
21 changed files with 1911 additions and 10 deletions

View File

@ -3,7 +3,7 @@ version: '3.7'
services:
consul-server:
image: loads/consul:1.15
image: consul:1.15
container_name: consul-server
restart: always
volumes:
@ -17,7 +17,7 @@ services:
command: "agent"
consul-client:
image: loads/consul:1.15
image: consul:1.15
container_name: consul-client
restart: always
volumes:

View File

@ -14,6 +14,7 @@ on:
- feature/**
- enhance/**
- fix/**
- feat/**
pull_request:
branches:
- master
@ -22,12 +23,13 @@ on:
- feature/**
- enhance/**
- fix/**
- feat/**
jobs:
golangci:
strategy:
matrix:
go-version: [ '1.20','1.21.4','1.22','1.23' ]
go-version: [ 'stable' ]
name: golangci-lint
runs-on: ubuntu-latest
steps:
@ -42,4 +44,38 @@ jobs:
with:
# Required: the version of golangci-lint is required and must be specified without patch version: we always use the latest patch version.
version: v1.62.2
args: --timeout 3m0s
only-new-issues: true
github-token: ${{ secrets.GITHUB_TOKEN }}
args: --timeout 3m0s --fix
- name: Install gci
run: go install github.com/daixiang0/gci@latest
- name: Run gci
run: |
gci write --custom-order \
--skip-generated \
--skip-vendor \
-s standard \
-s blank \
-s default \
-s dot \
-s "prefix(github.com/gogf/gf/v2)" \
-s "prefix(github.com/gogf/gf/cmd)" \
-s "prefix(github.com/gogf/gf/contrib)" \
-s "prefix(github.com/gogf/gf/example)" \
./
- name: Check for changes
id: check_changes
run: |
if [[ -n "$(git status --porcelain)" ]]; then
echo "HAS_CHANGES=true" >> $GITHUB_ENV
else
echo "HAS_CHANGES=false" >> $GITHUB_ENV
fi
- name: Commit and push changes
if: env.HAS_CHANGES == 'true'
run: |
git config --global user.name "github-actions[bot]"
git config --global user.email "github-actions[bot]@users.noreply.github.com"
git add .
git commit -m "Apply gci import order changes"
git push

View File

@ -125,7 +125,7 @@ linters-settings:
custom-order: true
# Drops lexical ordering for custom sections.
# Default: false
no-lex-order: true
no-lex-order: false
# https://golangci-lint.run/usage/linters/#revive
# https://github.com/mgechev/revive/blob/master/RULES_DESCRIPTIONS.md
revive:

View File

@ -0,0 +1,104 @@
# GoFrame Consul Registry
Use `consul` as service registration and discovery management.
## Installation
```bash
go get -u github.com/gogf/gf/contrib/registry/consul/v2
```
suggested using `go.mod`:
```bash
require github.com/gogf/gf/contrib/registry/consul/v2 latest
```
## Example
### HTTP Server
```go
package main
import (
"context"
"github.com/gogf/gf/contrib/registry/consul/v2"
"github.com/gogf/gf/v2/frame/g"
"github.com/gogf/gf/v2/net/ghttp"
"github.com/gogf/gf/v2/net/gsvc"
)
func main() {
registry, err := consul.New(consul.WithAddress("127.0.0.1:8500"))
if err != nil {
g.Log().Fatal(context.Background(), err)
}
gsvc.SetRegistry(registry)
s := g.Server("hello.svc")
s.BindHandler("/", func(r *ghttp.Request) {
g.Log().Info(r.Context(), "request received")
r.Response.Write("Hello world")
})
s.Run()
}
```
### HTTP Client
```go
package main
import (
"context"
"fmt"
"time"
"github.com/gogf/gf/contrib/registry/consul/v2"
"github.com/gogf/gf/v2/frame/g"
"github.com/gogf/gf/v2/net/gsel"
"github.com/gogf/gf/v2/net/gsvc"
"github.com/gogf/gf/v2/os/gctx"
)
func main() {
registry, err := consul.New(consul.WithAddress("127.0.0.1:8500"))
if err != nil {
g.Log().Fatal(context.Background(), err)
}
gsvc.SetRegistry(registry)
gsel.SetBuilder(gsel.NewBuilderRoundRobin())
client := g.Client()
for i := 0; i < 100; i++ {
res, err := client.Get(gctx.New(), "http://hello.svc/")
if err != nil {
panic(err)
}
fmt.Println(res.ReadAllString())
res.Close()
time.Sleep(time.Second)
}
}
```
## Configuration Options
The registry supports the following configuration options:
- `WithAddress(address string)`: Sets the Consul server address (default: "127.0.0.1:8500")
- `WithToken(token string)`: Sets the ACL token for Consul authentication
## Features
- Service registration with TTL health check
- Service discovery with health status filtering
- Service metadata support
- Watch support for service changes
- Consul ACL token support
## Requirements
- Go 1.18 or higher
- Consul 1.0 or higher
## License
`GoFrame Consul` is licensed under the [MIT License](../../../LICENSE), 100% free and open-source, forever.

View File

@ -0,0 +1,199 @@
// Copyright GoFrame Author(https://goframe.org). All Rights Reserved.
//
// This Source Code Form is subject to the terms of the MIT License.
// If a copy of the MIT was not distributed with this file,
// You can obtain one at https://github.com/gogf/gf.
// Package consul implements service Registry and Discovery using consul.
package consul
import (
"context"
"encoding/json"
"fmt"
"sync"
"time"
"github.com/hashicorp/consul/api"
"github.com/gogf/gf/v2/errors/gerror"
"github.com/gogf/gf/v2/net/gsvc"
)
const (
// DefaultTTL is the default TTL for service registration
DefaultTTL = 20 * time.Second
// DefaultHealthCheckInterval is the default interval for health check
DefaultHealthCheckInterval = 10 * time.Second
)
var (
_ gsvc.Registry = (*Registry)(nil)
)
// Registry implements gsvc.Registry interface using consul.
type Registry struct {
client *api.Client // Consul client
address string // Consul address
options map[string]string // Additional options
mu sync.RWMutex // Mutex for thread safety
}
// Option is the configuration option type for registry.
type Option func(r *Registry)
// WithAddress sets the address for consul client.
func WithAddress(address string) Option {
return func(r *Registry) {
r.mu.Lock()
r.address = address
r.mu.Unlock()
}
}
// WithToken sets the ACL token for consul client.
func WithToken(token string) Option {
return func(r *Registry) {
r.mu.Lock()
r.options["token"] = token
r.mu.Unlock()
}
}
// New creates and returns a new Registry.
func New(opts ...Option) (gsvc.Registry, error) {
r := &Registry{
address: "127.0.0.1:8500",
options: make(map[string]string),
}
// Apply options
for _, opt := range opts {
opt(r)
}
// Create consul config
config := api.DefaultConfig()
r.mu.RLock()
config.Address = r.address
if token, ok := r.options["token"]; ok {
config.Token = token
}
r.mu.RUnlock()
// Create consul client
client, err := api.NewClient(config)
if err != nil {
return nil, err
}
r.client = client
return r, nil
}
// Register registers a service to consul.
func (r *Registry) Register(ctx context.Context, service gsvc.Service) (gsvc.Service, error) {
metadata := service.GetMetadata()
if metadata == nil {
metadata = make(map[string]interface{})
}
// Convert metadata to string map
meta := make(map[string]string)
if len(metadata) > 0 {
metadataBytes, err := json.Marshal(metadata)
if err != nil {
return nil, gerror.Wrap(err, "failed to marshal metadata")
}
meta["metadata"] = string(metadataBytes)
}
// Add version to meta
meta["version"] = service.GetVersion()
endpoints := service.GetEndpoints()
if len(endpoints) == 0 {
return nil, gerror.New("no endpoints found in service")
}
// Create service ID
serviceID := fmt.Sprintf("%s-%s-%s:%d", service.GetName(), service.GetVersion(), endpoints[0].Host(), endpoints[0].Port())
// Create registration
reg := &api.AgentServiceRegistration{
ID: serviceID,
Name: service.GetName(),
Tags: []string{service.GetVersion()},
Meta: meta,
Address: endpoints[0].Host(),
Port: endpoints[0].Port(),
}
// Add health check
checkID := fmt.Sprintf("service:%s", serviceID)
reg.Check = &api.AgentServiceCheck{
CheckID: checkID,
TTL: DefaultTTL.String(),
DeregisterCriticalServiceAfter: "1m",
}
// Register service
if err := r.client.Agent().ServiceRegister(reg); err != nil {
return nil, gerror.Wrap(err, "failed to register service")
}
// Start TTL health check
if err := r.client.Agent().PassTTL(checkID, ""); err != nil {
// Try to deregister service if health check fails
_ = r.client.Agent().ServiceDeregister(serviceID)
return nil, gerror.Wrap(err, "failed to pass TTL health check")
}
// Start TTL health check goroutine
go r.ttlHealthCheck(serviceID)
return service, nil
}
// Deregister deregisters a service from consul.
func (r *Registry) Deregister(ctx context.Context, service gsvc.Service) error {
endpoints := service.GetEndpoints()
if len(endpoints) == 0 {
return gerror.New("no endpoints found in service")
}
// Create service ID
serviceID := fmt.Sprintf("%s-%s-%s:%d", service.GetName(), service.GetVersion(), endpoints[0].Host(), endpoints[0].Port())
return r.client.Agent().ServiceDeregister(serviceID)
}
// ttlHealthCheck maintains the TTL health check for a service
func (r *Registry) ttlHealthCheck(serviceID string) {
ticker := time.NewTicker(DefaultHealthCheckInterval)
defer ticker.Stop()
checkID := fmt.Sprintf("service:%s", serviceID)
for range ticker.C {
if err := r.client.Agent().PassTTL(checkID, ""); err != nil {
return
}
}
}
// GetAddress returns the consul address
func (r *Registry) GetAddress() string {
r.mu.RLock()
defer r.mu.RUnlock()
return r.address
}
// Watch creates and returns a watcher for specified service.
func (r *Registry) Watch(ctx context.Context, key string) (gsvc.Watcher, error) {
watcher, err := newWatcher(r, key)
if err != nil {
return nil, err
}
return watcher, nil
}

View File

@ -0,0 +1,91 @@
// Copyright GoFrame Author(https://goframe.org). All Rights Reserved.
//
// This Source Code Form is subject to the terms of the MIT License.
// If a copy of the MIT was not distributed with this file,
// You can obtain one at https://github.com/gogf/gf.
package consul
import (
"context"
"encoding/json"
"fmt"
"time"
"github.com/hashicorp/consul/api"
"github.com/gogf/gf/v2/errors/gerror"
"github.com/gogf/gf/v2/net/gsvc"
)
// Search searches and returns services with specified condition.
func (r *Registry) Search(ctx context.Context, in gsvc.SearchInput) ([]gsvc.Service, error) {
// Get services from consul
services, _, err := r.client.Health().Service(in.Name, "", true, &api.QueryOptions{
WaitTime: time.Second * 3,
})
if err != nil {
return nil, gerror.Wrap(err, "failed to get services from consul")
}
var result []gsvc.Service
for _, service := range services {
if service.Checks.AggregatedStatus() != api.HealthPassing {
continue
}
// Parse metadata
var metadata map[string]interface{}
if metaStr, ok := service.Service.Meta["metadata"]; ok && metaStr != "" {
if err = json.Unmarshal([]byte(metaStr), &metadata); err != nil {
return nil, gerror.Wrap(err, "failed to unmarshal service metadata")
}
}
// Skip if version doesn't match
if in.Version != "" {
if len(service.Service.Tags) == 0 || service.Service.Tags[0] != in.Version {
continue
}
}
// Skip if metadata doesn't match
if len(in.Metadata) > 0 {
if metadata == nil {
continue
}
match := true
for k, v := range in.Metadata {
if mv, ok := metadata[k]; !ok || mv != v {
match = false
break
}
}
if !match {
continue
}
}
// Get version from tags
version := ""
if len(service.Service.Tags) > 0 {
version = service.Service.Tags[0]
}
// Create service instance
localService := &gsvc.LocalService{
Head: "",
Deployment: "",
Namespace: "",
Name: service.Service.Service,
Version: version,
Endpoints: []gsvc.Endpoint{
gsvc.NewEndpoint(fmt.Sprintf("%s:%d", service.Service.Address, service.Service.Port)),
},
Metadata: metadata,
}
result = append(result, localService)
}
return result, nil
}

View File

@ -0,0 +1,445 @@
// Copyright GoFrame Author(https://goframe.org). All Rights Reserved.
//
// This Source Code Form is subject to the terms of the MIT License.
// If a copy of the MIT was not distributed with this file,
// You can obtain one at https://github.com/gogf/gf.
package consul
import (
"context"
"fmt"
"testing"
"time"
"github.com/gogf/gf/v2/net/gsvc"
"github.com/gogf/gf/v2/test/gtest"
)
const (
testServiceName = "test-service"
testServiceVersion = "1.0.0"
testServiceAddress = "127.0.0.1"
testServicePort = 8000
)
func createTestService() gsvc.Service {
return &gsvc.LocalService{
Name: testServiceName,
Version: testServiceVersion,
Metadata: map[string]interface{}{
"region": "cn-east-1",
"zone": "a",
},
Endpoints: []gsvc.Endpoint{
gsvc.NewEndpoint(fmt.Sprintf("%s:%d", testServiceAddress, testServicePort)),
},
}
}
func Test_Registry_Basic(t *testing.T) {
gtest.C(t, func(t *gtest.T) {
// Create registry
registry, err := New()
t.AssertNil(err)
t.Assert(registry != nil, true)
// Test invalid service
invalidService := &gsvc.LocalService{
Name: testServiceName,
Version: testServiceVersion,
}
_, err = registry.Register(context.Background(), invalidService)
t.AssertNE(err, nil) // Should fail due to no endpoints
// Create service with invalid metadata
serviceWithInvalidMeta := &gsvc.LocalService{
Name: testServiceName,
Version: testServiceVersion,
Metadata: map[string]interface{}{
"invalid": make(chan int), // This will fail JSON marshaling
},
Endpoints: []gsvc.Endpoint{
gsvc.NewEndpoint(fmt.Sprintf("%s:%d", testServiceAddress, testServicePort)),
},
}
_, err = registry.Register(context.Background(), serviceWithInvalidMeta)
t.AssertNE(err, nil) // Should fail due to invalid metadata
// Create service
service := createTestService()
// Register service
ctx := context.Background()
registeredService, err := registry.Register(ctx, service)
t.AssertNil(err)
t.Assert(registeredService != nil, true)
// Wait for service to be registered
time.Sleep(2 * time.Second)
// Search service
services, err := registry.Search(ctx, gsvc.SearchInput{
Name: testServiceName,
Version: testServiceVersion,
})
t.AssertNil(err)
t.Assert(len(services), 1)
// Test service properties
foundService := services[0]
t.Assert(foundService.GetName(), testServiceName)
t.Assert(foundService.GetVersion(), testServiceVersion)
t.Assert(len(foundService.GetEndpoints()), 1)
endpoint := foundService.GetEndpoints()[0]
t.Assert(endpoint.Host(), testServiceAddress)
t.Assert(endpoint.Port(), testServicePort)
metadata := foundService.GetMetadata()
t.Assert(metadata != nil, true)
t.Assert(metadata["region"], "cn-east-1")
t.Assert(metadata["zone"], "a")
// Search with invalid metadata
servicesWithInvalidMeta, err := registry.Search(ctx, gsvc.SearchInput{
Name: testServiceName,
Version: testServiceVersion,
Metadata: map[string]interface{}{"nonexistent": "value"},
})
t.AssertNil(err)
t.Assert(len(servicesWithInvalidMeta), 0)
// Test deregister with invalid service
err = registry.Deregister(ctx, invalidService)
t.AssertNE(err, nil) // Should fail due to no endpoints
// Deregister service
err = registry.Deregister(ctx, service)
t.AssertNil(err)
// Wait for service to be deregistered
time.Sleep(2 * time.Second)
// Verify service is deregistered
deregisteredServices, err := registry.Search(ctx, gsvc.SearchInput{
Name: testServiceName,
Version: testServiceVersion,
})
t.AssertNil(err)
t.Assert(len(deregisteredServices), 0)
})
}
func Test_Registry_Watch(t *testing.T) {
gtest.C(t, func(t *gtest.T) {
// Create registry
registry, err := New()
t.AssertNil(err)
// Create service
service := createTestService()
// Register service first
ctx := context.Background()
_, err = registry.Register(ctx, service)
t.AssertNil(err)
defer registry.Deregister(ctx, service)
// Wait for service to be registered
time.Sleep(time.Second)
// Create watcher after service is registered
watcher, err := registry.Watch(ctx, testServiceName)
t.AssertNil(err)
t.Assert(watcher != nil, true)
defer watcher.Close()
// Wait for initial service query
time.Sleep(time.Second)
// Should receive initial service list
services, err := watcher.Proceed()
t.AssertNil(err)
t.Assert(len(services), 1)
t.Assert(services[0].GetName(), testServiceName)
t.Assert(services[0].GetVersion(), testServiceVersion)
// Test closing watcher
err = watcher.Close()
t.AssertNil(err)
// Test watch with invalid service name
watcher, err = registry.Watch(ctx, "nonexistent-service")
t.AssertNil(err)
defer watcher.Close()
// Wait for initial query
time.Sleep(time.Second)
// Should receive empty service list for non-existent service
services, err = watcher.Proceed()
t.AssertNil(err)
t.Assert(len(services), 0)
// Test watch after service deregistration
watcher, err = registry.Watch(ctx, testServiceName)
t.AssertNil(err)
defer watcher.Close()
// Wait for initial query
time.Sleep(time.Second)
err = registry.Deregister(ctx, service)
t.AssertNil(err)
// Wait for service to be deregistered
time.Sleep(time.Second)
// Should receive empty service list after deregistration
services, err = watcher.Proceed()
t.AssertNil(err)
t.Assert(len(services), 0)
})
}
func Test_Registry_MultipleServices(t *testing.T) {
gtest.C(t, func(t *gtest.T) {
// Create registry
registry, err := New()
t.AssertNil(err)
// Create multiple services
service1 := &gsvc.LocalService{
Name: testServiceName,
Version: "1.0.0",
Metadata: map[string]interface{}{
"region": "us-east-1",
},
Endpoints: []gsvc.Endpoint{
gsvc.NewEndpoint("127.0.0.1:8001"),
},
}
service2 := &gsvc.LocalService{
Name: testServiceName,
Version: "2.0.0",
Metadata: map[string]interface{}{
"region": "us-west-1",
},
Endpoints: []gsvc.Endpoint{
gsvc.NewEndpoint("127.0.0.1:8002"),
},
}
// Register services
ctx := context.Background()
_, err = registry.Register(ctx, service1)
t.AssertNil(err)
defer registry.Deregister(ctx, service1)
_, err = registry.Register(ctx, service2)
t.AssertNil(err)
defer registry.Deregister(ctx, service2)
// Wait for services to be registered
time.Sleep(2 * time.Second)
// Search all services without version filter
allServices, err := registry.Search(ctx, gsvc.SearchInput{
Name: testServiceName,
})
t.AssertNil(err)
t.Assert(len(allServices), 2)
// Test search with different versions
services1, err := registry.Search(ctx, gsvc.SearchInput{
Name: testServiceName,
Version: "1.0.0",
})
t.AssertNil(err)
t.Assert(len(services1), 1)
t.Assert(services1[0].GetVersion(), "1.0.0")
services2, err := registry.Search(ctx, gsvc.SearchInput{
Name: testServiceName,
Version: "2.0.0",
})
t.AssertNil(err)
t.Assert(len(services2), 1)
t.Assert(services2[0].GetVersion(), "2.0.0")
// Test search with metadata
servicesEast, err := registry.Search(ctx, gsvc.SearchInput{
Name: testServiceName,
Metadata: map[string]interface{}{
"region": "us-east-1",
},
})
t.AssertNil(err)
t.Assert(len(servicesEast), 1)
t.Assert(servicesEast[0].GetMetadata()["region"], "us-east-1")
// Watch both services
watcher, err := registry.Watch(ctx, testServiceName)
t.AssertNil(err)
defer watcher.Close()
// Wait for initial query
time.Sleep(time.Second)
// Should receive updates for both services
services, err := watcher.Proceed()
t.AssertNil(err)
t.Assert(len(services), 2)
// Verify services are sorted by version
t.Assert(services[0].GetVersion() < services[1].GetVersion(), true)
})
}
func Test_Registry_Options(t *testing.T) {
gtest.C(t, func(t *gtest.T) {
// Test with custom address
registry1, err := New(WithAddress("localhost:8500"))
t.AssertNil(err)
t.Assert(registry1.(*Registry).GetAddress(), "localhost:8500")
// Test with token
registry2, err := New(WithAddress("localhost:8500"), WithToken("test-token"))
t.AssertNil(err)
t.Assert(registry2.(*Registry).options["token"], "test-token")
// Test with invalid address (should still create registry but fail on operations)
registry3, err := New(WithAddress("invalid:99999"))
t.AssertNil(err)
_, err = registry3.Register(context.Background(), createTestService())
t.AssertNE(err, nil)
})
}
func Test_Registry_MultipleServicesMetadataFiltering(t *testing.T) {
gtest.C(t, func(t *gtest.T) {
// Create registry
registry, err := New()
t.AssertNil(err)
// Create multiple services
service1 := &gsvc.LocalService{
Name: testServiceName,
Version: "1.0.0",
Metadata: map[string]interface{}{
"region": "us-east-1",
"env": "dev",
},
Endpoints: []gsvc.Endpoint{
gsvc.NewEndpoint("127.0.0.1:8001"),
},
}
service2 := &gsvc.LocalService{
Name: testServiceName,
Version: "2.0.0",
Metadata: map[string]interface{}{
"region": "us-west-1",
"env": "prod",
},
Endpoints: []gsvc.Endpoint{
gsvc.NewEndpoint("127.0.0.1:8002"),
},
}
// Register services
ctx := context.Background()
_, err = registry.Register(ctx, service1)
t.AssertNil(err)
defer registry.Deregister(ctx, service1)
_, err = registry.Register(ctx, service2)
t.AssertNil(err)
defer registry.Deregister(ctx, service2)
time.Sleep(time.Second) // Wait for services to be registered
// Test search with metadata filtering
servicesDev, err := registry.Search(ctx, gsvc.SearchInput{
Name: testServiceName,
Metadata: map[string]interface{}{
"env": "dev",
},
})
t.AssertNil(err)
t.Assert(len(servicesDev), 1)
t.Assert(servicesDev[0].GetMetadata()["env"], "dev")
servicesProd, err := registry.Search(ctx, gsvc.SearchInput{
Name: testServiceName,
Metadata: map[string]interface{}{
"env": "prod",
},
})
t.AssertNil(err)
t.Assert(len(servicesProd), 1)
t.Assert(servicesProd[0].GetMetadata()["env"], "prod")
})
}
func Test_Registry_MultipleServicesVersionFiltering(t *testing.T) {
gtest.C(t, func(t *gtest.T) {
// Create registry
registry, err := New()
t.AssertNil(err)
// Create multiple services
service1 := &gsvc.LocalService{
Name: testServiceName,
Version: "1.0.0",
Metadata: map[string]interface{}{
"region": "us-east-1",
},
Endpoints: []gsvc.Endpoint{
gsvc.NewEndpoint("127.0.0.1:8001"),
},
}
service2 := &gsvc.LocalService{
Name: testServiceName,
Version: "2.0.0",
Metadata: map[string]interface{}{
"region": "us-west-1",
},
Endpoints: []gsvc.Endpoint{
gsvc.NewEndpoint("127.0.0.1:8002"),
},
}
// Register services
ctx := context.Background()
_, err = registry.Register(ctx, service1)
t.AssertNil(err)
defer registry.Deregister(ctx, service1)
_, err = registry.Register(ctx, service2)
t.AssertNil(err)
defer registry.Deregister(ctx, service2)
time.Sleep(time.Second) // Wait for services to be registered
// Test search with version filtering
services, err := registry.Search(ctx, gsvc.SearchInput{
Name: testServiceName,
Version: "1.0.0",
})
t.AssertNil(err)
t.Assert(len(services), 1)
t.Assert(services[0].GetVersion(), "1.0.0")
services, err = registry.Search(ctx, gsvc.SearchInput{
Name: testServiceName,
Version: "2.0.0",
})
t.AssertNil(err)
t.Assert(len(services), 1)
t.Assert(services[0].GetVersion(), "2.0.0")
})
}

View File

@ -0,0 +1,209 @@
// Copyright GoFrame Author(https://goframe.org). All Rights Reserved.
//
// This Source Code Form is subject to the terms of the MIT License.
// If a copy of the MIT was not distributed with this file,
// You can obtain one at https://github.com/gogf/gf.
package consul
import (
"encoding/json"
"fmt"
"sort"
"sync"
"github.com/hashicorp/consul/api"
"github.com/hashicorp/consul/api/watch"
"github.com/gogf/gf/v2/errors/gerror"
"github.com/gogf/gf/v2/net/gsvc"
)
// Watcher watches the service changes.
type Watcher struct {
registry *Registry // The registry instance
key string // The service name to watch
closeChan chan struct{} // Channel for closing
eventChan chan struct{} // Channel for notifying changes
mu sync.RWMutex // Mutex for thread safety
plan *watch.Plan // The watch plan
services []gsvc.Service // Current services
}
// New creates and returns a new watcher.
func newWatcher(registry *Registry, key string) (*Watcher, error) {
w := &Watcher{
registry: registry,
key: key,
closeChan: make(chan struct{}),
eventChan: make(chan struct{}, 1),
}
// Start watching
go w.watch()
return w, nil
}
// watch starts the watching process.
func (w *Watcher) watch() {
// Get initial service list
initServices, err := w.Services()
if err != nil {
return
}
// Set initial services
w.mu.Lock()
w.services = initServices
w.mu.Unlock()
// Create watch plan
plan, err := watch.Parse(map[string]interface{}{
"type": "service",
"service": w.key,
})
if err != nil {
return
}
w.mu.Lock()
w.plan = plan
w.mu.Unlock()
// Set handler
plan.Handler = func(idx uint64, data interface{}) {
// Check if watcher is closed
select {
case <-w.closeChan:
return
default:
}
// Get current services
services, _ := w.Services()
// Update services
w.mu.Lock()
w.services = services
w.mu.Unlock()
// Notify changes
select {
case w.eventChan <- struct{}{}:
default:
}
}
// Start watching
go func() {
defer func() {
w.mu.Lock()
if w.plan != nil {
w.plan.Stop()
w.plan = nil
}
w.mu.Unlock()
}()
if err = plan.Run(w.registry.GetAddress()); err != nil {
return
}
}()
// Wait for close signal
<-w.closeChan
}
// Proceed returns current services and waits for the next service change.
func (w *Watcher) Proceed() ([]gsvc.Service, error) {
// Check if watcher is closed
select {
case <-w.closeChan:
return nil, gerror.New("watcher closed")
default:
}
w.mu.RLock()
services := w.services
w.mu.RUnlock()
// Wait for changes
select {
case <-w.closeChan:
return nil, gerror.New("watcher closed")
case <-w.eventChan:
w.mu.RLock()
services = w.services
w.mu.RUnlock()
return services, nil
}
}
// Close closes the watcher.
func (w *Watcher) Close() error {
w.mu.Lock()
defer w.mu.Unlock()
select {
case <-w.closeChan:
return nil
default:
close(w.closeChan)
if w.plan != nil {
w.plan.Stop()
w.plan = nil
}
return nil
}
}
// Services returns current services from the watcher.
func (w *Watcher) Services() ([]gsvc.Service, error) {
// Query services directly from Consul
entries, _, err := w.registry.client.Health().Service(w.key, "", true, &api.QueryOptions{})
if err != nil {
return nil, err
}
// Convert entries to services
var services []gsvc.Service
for _, entry := range entries {
if entry.Checks.AggregatedStatus() == api.HealthPassing {
metadata := make(map[string]interface{})
if entry.Service.Meta != nil {
if metaStr, ok := entry.Service.Meta["metadata"]; ok {
if err := json.Unmarshal([]byte(metaStr), &metadata); err != nil {
return nil, gerror.Wrap(err, "failed to unmarshal metadata")
}
}
}
// Get version from metadata or tags
version := ""
if v, ok := entry.Service.Meta["version"]; ok {
version = v
} else if len(entry.Service.Tags) > 0 {
version = entry.Service.Tags[0]
}
// Create service instance
service := &gsvc.LocalService{
Name: entry.Service.Service,
Version: version,
Metadata: metadata,
Endpoints: []gsvc.Endpoint{
gsvc.NewEndpoint(fmt.Sprintf("%s:%d", entry.Service.Address, entry.Service.Port)),
},
}
services = append(services, service)
}
}
// Sort services by version
if len(services) > 0 {
sort.Slice(services, func(i, j int) bool {
return services[i].GetVersion() < services[j].GetVersion()
})
}
return services, nil
}

View File

@ -0,0 +1,46 @@
module github.com/gogf/gf/contrib/registry/consul/v2
go 1.18
require (
github.com/gogf/gf/v2 v2.8.1
github.com/hashicorp/consul/api v1.26.1
)
require (
github.com/BurntSushi/toml v1.4.0 // indirect
github.com/armon/go-metrics v0.4.1 // indirect
github.com/clbanning/mxj/v2 v2.7.0 // indirect
github.com/emirpasic/gods v1.18.1 // indirect
github.com/fatih/color v1.17.0 // indirect
github.com/fsnotify/fsnotify v1.7.0 // indirect
github.com/go-logr/logr v1.4.2 // indirect
github.com/go-logr/stdr v1.2.2 // indirect
github.com/gorilla/websocket v1.5.3 // indirect
github.com/grokify/html-strip-tags-go v0.1.0 // indirect
github.com/hashicorp/errwrap v1.1.0 // indirect
github.com/hashicorp/go-cleanhttp v0.5.2 // indirect
github.com/hashicorp/go-hclog v1.5.0 // indirect
github.com/hashicorp/go-immutable-radix v1.3.1 // indirect
github.com/hashicorp/go-multierror v1.1.1 // indirect
github.com/hashicorp/go-rootcerts v1.0.2 // indirect
github.com/hashicorp/golang-lru v0.5.4 // indirect
github.com/hashicorp/serf v0.10.1 // indirect
github.com/magiconair/properties v1.8.7 // indirect
github.com/mattn/go-colorable v0.1.13 // indirect
github.com/mattn/go-isatty v0.0.20 // indirect
github.com/mattn/go-runewidth v0.0.15 // indirect
github.com/mitchellh/go-homedir v1.1.0 // indirect
github.com/mitchellh/mapstructure v1.5.0 // indirect
github.com/olekukonko/tablewriter v0.0.5 // indirect
github.com/rivo/uniseg v0.4.7 // indirect
go.opentelemetry.io/otel v1.24.0 // indirect
go.opentelemetry.io/otel/metric v1.24.0 // indirect
go.opentelemetry.io/otel/sdk v1.24.0 // indirect
go.opentelemetry.io/otel/trace v1.24.0 // indirect
golang.org/x/exp v0.0.0-20230817173708-d852ddb80c63 // indirect
golang.org/x/net v0.27.0 // indirect
golang.org/x/sys v0.22.0 // indirect
golang.org/x/text v0.16.0 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
)

View File

@ -0,0 +1,255 @@
github.com/BurntSushi/toml v1.4.0 h1:kuoIxZQy2WRRk1pttg9asf+WVv6tWQuBNVmK8+nqPr0=
github.com/BurntSushi/toml v1.4.0/go.mod h1:ukJfTF/6rtPPRCnwkur4qwRxa8vTRFBF0uk2lLoLwho=
github.com/DataDog/datadog-go v3.2.0+incompatible/go.mod h1:LButxg5PwREeZtORoXG3tL4fMGNddJ+vMq1mwgfaqoQ=
github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
github.com/alecthomas/units v0.0.0-20190717042225-c3de453c63f4/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
github.com/armon/circbuf v0.0.0-20150827004946-bbbad097214e/go.mod h1:3U/XgcO3hCbHZ8TKRvWD2dDTCfh9M9ya+I9JpbB7O8o=
github.com/armon/go-metrics v0.0.0-20180917152333-f0300d1749da/go.mod h1:Q73ZrmVTwzkszR9V5SSuryQ31EELlFMUz1kKyl939pY=
github.com/armon/go-metrics v0.4.1 h1:hR91U9KYmb6bLBYLQjyM+3j+rcd/UhE+G78SFnF8gJA=
github.com/armon/go-metrics v0.4.1/go.mod h1:E6amYzXo6aW1tqzoZGT755KkbgrJsSdpwZ+3JqfkOG4=
github.com/armon/go-radix v0.0.0-20180808171621-7fddfc383310/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8=
github.com/armon/go-radix v1.0.0/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8=
github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q=
github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8=
github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw=
github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs=
github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
github.com/circonus-labs/circonus-gometrics v2.3.1+incompatible/go.mod h1:nmEj6Dob7S7YxXgwXpfOuvO54S+tGdZdw9fuRZt25Ag=
github.com/circonus-labs/circonusllhist v0.1.3/go.mod h1:kMXHVDlOchFAehlya5ePtbp5jckzBHf4XRpQvBOLI+I=
github.com/clbanning/mxj/v2 v2.7.0 h1:WA/La7UGCanFe5NpHF0Q3DNtnCsVoxbPKuyBNHWRyME=
github.com/clbanning/mxj/v2 v2.7.0/go.mod h1:hNiWqW14h+kc+MdF9C6/YoRfjEJoR3ou6tn/Qo+ve2s=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM=
github.com/emirpasic/gods v1.18.1 h1:FXtiHYKDGKCW2KzwZKx0iC0PQmdlorYgdFG9jPXJ1Bc=
github.com/emirpasic/gods v1.18.1/go.mod h1:8tpGGwCnJ5H4r6BWwaV6OrWmMoPhUl5jm/FMNAnJvWQ=
github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4=
github.com/fatih/color v1.9.0/go.mod h1:eQcE1qtQxscV5RaZvpXrrb8Drkc3/DdQ+uUYCNjL+zU=
github.com/fatih/color v1.13.0/go.mod h1:kLAiJbzzSOZDVNGyDpeOxJ47H46qBXwg5ILebYFFOfk=
github.com/fatih/color v1.17.0 h1:GlRw1BRJxkpqUCBKzKOw098ed57fEsKeNjpTe3cSjK4=
github.com/fatih/color v1.17.0/go.mod h1:YZ7TlrGPkiz6ku9fK3TLD/pl3CpsiFyu8N92HLgmosI=
github.com/fsnotify/fsnotify v1.7.0 h1:8JEhPFa5W2WU7YfeZzPNqzMP6Lwt7L2715Ggo0nosvA=
github.com/fsnotify/fsnotify v1.7.0/go.mod h1:40Bi/Hjc2AVfZrqy+aj+yEI+/bRxZnMJyTJwOpGvigM=
github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
github.com/go-kit/kit v0.9.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE=
github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk=
github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
github.com/go-logr/logr v1.4.2 h1:6pFjapn8bFcIbiKo3XT4j/BhANplGihG6tvd+8rYgrY=
github.com/go-logr/logr v1.4.2/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag=
github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE=
github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY=
github.com/gogf/gf/v2 v2.8.1 h1:1oVQg3G5OgCats4qWFTH3pHLe92nfUQeUDta05tUs1g=
github.com/gogf/gf/v2 v2.8.1/go.mod h1:6iYuZZ+A0ZcH8+4MDS/P0SvTPCvKzRvyAsY1kbkJYJc=
github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
github.com/google/btree v1.0.1 h1:gK4Kx5IaGY9CD5sPJ36FHiBJ6ZXl0kilRiiCj+jdYp4=
github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
github.com/gorilla/websocket v1.5.3 h1:saDtZ6Pbx/0u+bgYQ3q96pZgCzfhKXGPqt7kZ72aNNg=
github.com/gorilla/websocket v1.5.3/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
github.com/grokify/html-strip-tags-go v0.1.0 h1:03UrQLjAny8xci+R+qjCce/MYnpNXCtgzltlQbOBae4=
github.com/grokify/html-strip-tags-go v0.1.0/go.mod h1:ZdzgfHEzAfz9X6Xe5eBLVblWIxXfYSQ40S/VKrAOGpc=
github.com/hashicorp/consul/api v1.26.1 h1:5oSXOO5fboPZeW5SN+TdGFP/BILDgBm19OrPZ/pICIM=
github.com/hashicorp/consul/api v1.26.1/go.mod h1:B4sQTeaSO16NtynqrAdwOlahJ7IUDZM9cj2420xYL8A=
github.com/hashicorp/consul/sdk v0.15.0 h1:2qK9nDrr4tiJKRoxPGhm6B7xJjLVIQqkjiab2M4aKjU=
github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4=
github.com/hashicorp/errwrap v1.1.0 h1:OxrOeh75EUXMY8TBjag2fzXGZ40LB6IKw45YeGUDY2I=
github.com/hashicorp/errwrap v1.1.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4=
github.com/hashicorp/go-cleanhttp v0.5.0/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80=
github.com/hashicorp/go-cleanhttp v0.5.2 h1:035FKYIWjmULyFRBKPs8TBQoi0x6d9G4xc9neXJWAZQ=
github.com/hashicorp/go-cleanhttp v0.5.2/go.mod h1:kO/YDlP8L1346E6Sodw+PrpBSV4/SoxCXGY6BqNFT48=
github.com/hashicorp/go-hclog v1.5.0 h1:bI2ocEMgcVlz55Oj1xZNBsVi900c7II+fWDyV9o+13c=
github.com/hashicorp/go-hclog v1.5.0/go.mod h1:W4Qnvbt70Wk/zYJryRzDRU/4r0kIg0PVHBcfoyhpF5M=
github.com/hashicorp/go-immutable-radix v1.0.0/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60=
github.com/hashicorp/go-immutable-radix v1.3.1 h1:DKHmCUm2hRBK510BaiZlwvpD40f8bJFeZnpfm2KLowc=
github.com/hashicorp/go-immutable-radix v1.3.1/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60=
github.com/hashicorp/go-msgpack v0.5.3/go.mod h1:ahLV/dePpqEmjfWmKiqvPkv/twdG7iPBM1vqhUKIvfM=
github.com/hashicorp/go-msgpack v0.5.5 h1:i9R9JSrqIz0QVLz3sz+i3YJdT7TTSLcfLLzJi9aZTuI=
github.com/hashicorp/go-multierror v1.0.0/go.mod h1:dHtQlpGsu+cZNNAkkCN/P3hoUDHhCYQXV3UM06sGGrk=
github.com/hashicorp/go-multierror v1.1.0/go.mod h1:spPvp8C1qA32ftKqdAHm4hHTbPw+vmowP0z+KUhOZdA=
github.com/hashicorp/go-multierror v1.1.1 h1:H5DkEtf6CXdFp0N0Em5UCwQpXMWke8IA0+lD48awMYo=
github.com/hashicorp/go-multierror v1.1.1/go.mod h1:iw975J/qwKPdAO1clOe2L8331t/9/fmwbPZ6JB6eMoM=
github.com/hashicorp/go-retryablehttp v0.5.3/go.mod h1:9B5zBasrRhHXnJnui7y6sL7es7NDiJgTc6Er0maI1Xs=
github.com/hashicorp/go-rootcerts v1.0.2 h1:jzhAVGtqPKbwpyCPELlgNWhE1znq+qwJtW5Oi2viEzc=
github.com/hashicorp/go-rootcerts v1.0.2/go.mod h1:pqUvnprVnM5bf7AOirdbb01K4ccR319Vf4pU3K5EGc8=
github.com/hashicorp/go-sockaddr v1.0.0/go.mod h1:7Xibr9yA9JjQq1JpNB2Vw7kxv8xerXegt+ozgdvDeDU=
github.com/hashicorp/go-sockaddr v1.0.2 h1:ztczhD1jLxIRjVejw8gFomI1BQZOe2WoVOu0SyteCQc=
github.com/hashicorp/go-syslog v1.0.0/go.mod h1:qPfqrKkXGihmCqbJM2mZgkZGvKG1dFdvsLplgctolz4=
github.com/hashicorp/go-uuid v1.0.0/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro=
github.com/hashicorp/go-uuid v1.0.1/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro=
github.com/hashicorp/go-uuid v1.0.3 h1:2gKiV6YVmrJ1i2CKKa9obLvRieoRGviZFL26PcT/Co8=
github.com/hashicorp/go-version v1.2.1 h1:zEfKbn2+PDgroKdiOzqiE8rsmLqU2uwi5PB5pBJ3TkI=
github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
github.com/hashicorp/golang-lru v0.5.4 h1:YDjusn29QI/Das2iO9M0BHnIbxPeyuCHsjMW+lJfyTc=
github.com/hashicorp/golang-lru v0.5.4/go.mod h1:iADmTwqILo4mZ8BN3D2Q6+9jd8WM5uGBxy+E8yxSoD4=
github.com/hashicorp/logutils v1.0.0/go.mod h1:QIAnNjmIWmVIIkWDTG1z5v++HQmx9WQRO+LraFDTW64=
github.com/hashicorp/mdns v1.0.4/go.mod h1:mtBihi+LeNXGtG8L9dX59gAEa12BDtBQSp4v/YAJqrc=
github.com/hashicorp/memberlist v0.5.0 h1:EtYPN8DpAURiapus508I4n9CzHs2W+8NZGbmmR/prTM=
github.com/hashicorp/memberlist v0.5.0/go.mod h1:yvyXLpo0QaGE59Y7hDTsTzDD25JYBZ4mHgHUZ8lrOI0=
github.com/hashicorp/serf v0.10.1 h1:Z1H2J60yRKvfDYAOZLd2MU0ND4AH/WDz7xYHDWQsIPY=
github.com/hashicorp/serf v0.10.1/go.mod h1:yL2t6BqATOLGc5HF7qbFkTfXoPIY0WZdWHfEvMqbG+4=
github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU=
github.com/json-iterator/go v1.1.9/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w=
github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc=
github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI=
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
github.com/magiconair/properties v1.8.7 h1:IeQXZAiQcpL9mgcAe1Nu6cX9LLw6ExEHKjN0VQdvPDY=
github.com/magiconair/properties v1.8.7/go.mod h1:Dhd985XPs7jluiymwWYZ0G4Z61jb3vdS329zhj2hYo0=
github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU=
github.com/mattn/go-colorable v0.1.4/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE=
github.com/mattn/go-colorable v0.1.6/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc=
github.com/mattn/go-colorable v0.1.9/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc=
github.com/mattn/go-colorable v0.1.12/go.mod h1:u5H1YNBxpqRaxsYJYSkiCWKzEfiAb1Gb520KVy5xxl4=
github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA=
github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg=
github.com/mattn/go-isatty v0.0.3/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4=
github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s=
github.com/mattn/go-isatty v0.0.11/go.mod h1:PhnuNfih5lzO57/f3n+odYbM4JtupLOxQOAqxQCu2WE=
github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU=
github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94=
github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
github.com/mattn/go-runewidth v0.0.9/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI=
github.com/mattn/go-runewidth v0.0.15 h1:UNAjwbU9l54TA3KzvqLGxwWjHmMgBUVhBiTjelZgg3U=
github.com/mattn/go-runewidth v0.0.15/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w=
github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0=
github.com/miekg/dns v1.1.26/go.mod h1:bPDLeHnStXmXAq1m/Ch/hvfNHr14JKNPMBo3VZKjuso=
github.com/miekg/dns v1.1.41 h1:WMszZWJG0XmzbK9FEmzH2TVcqYzFesusSIB41b8KHxY=
github.com/miekg/dns v1.1.41/go.mod h1:p6aan82bvRIyn+zDIv9xYNUpwa73JcSh9BKwknJysuI=
github.com/mitchellh/cli v1.1.0/go.mod h1:xcISNoH86gajksDmfB23e/pu+B+GeFRMYmoHXxx3xhI=
github.com/mitchellh/go-homedir v1.1.0 h1:lukF9ziXFxDFPkA1vsr5zpc1XuPDn/wFntq5mG+4E0Y=
github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0=
github.com/mitchellh/mapstructure v0.0.0-20160808181253-ca63d7c062ee/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y=
github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY=
github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo=
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U=
github.com/olekukonko/tablewriter v0.0.5 h1:P2Ga83D34wi1o9J6Wh1mRuqd4mF/x/lgBS7N7AbDhec=
github.com/olekukonko/tablewriter v0.0.5/go.mod h1:hPp6KlRPjbx+hW8ykQs1w3UBbZlj6HuIJcUGPhkA7kY=
github.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc=
github.com/pascaldekloe/goe v0.1.0 h1:cBOtyMzM9HTpWjXfbbunk26uA6nG3a8n06Wieeh0MwY=
github.com/pascaldekloe/goe v0.1.0/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc=
github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U=
github.com/posener/complete v1.1.1/go.mod h1:em0nMJCgc9GFtwrmVmEMR/ZL6WyhyjMBndrE9hABlRI=
github.com/posener/complete v1.2.3/go.mod h1:WZIdtGGp+qx0sLrYKtIRAruyNpv6hFCicSgv7Sy7s/s=
github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw=
github.com/prometheus/client_golang v1.0.0/go.mod h1:db9x61etRT2tGnBNRi70OPL5FsnadC4Ky3P0J6CfImo=
github.com/prometheus/client_golang v1.4.0/go.mod h1:e9GMxYsXl05ICDXkRhurwBS4Q3OK1iX/F2sw+iXX5zU=
github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo=
github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
github.com/prometheus/client_model v0.2.0/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
github.com/prometheus/common v0.4.1/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4=
github.com/prometheus/common v0.9.1/go.mod h1:yhUN8i9wzaXS3w1O07YhxHEBxD+W35wd8bs7vj7HSQ4=
github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk=
github.com/prometheus/procfs v0.0.2/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA=
github.com/prometheus/procfs v0.0.8/go.mod h1:7Qr8sr6344vo1JqZ6HhLceV9o3AJ1Ff+GxbHq6oeK9A=
github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
github.com/rivo/uniseg v0.4.7 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ=
github.com/rivo/uniseg v0.4.7/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88=
github.com/ryanuber/columnize v0.0.0-20160712163229-9b3edd62028f/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts=
github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529 h1:nn5Wsu0esKSJiIVhscUtVbo7ada43DJhG55ua/hjS5I=
github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg65j358z/aeFdxmN0P9QXhEzd20vsDc=
github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo=
github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.5.0 h1:1zr/of2m5FGMsad5YfcqgdqdWrIhu+EBEJRhR1U7z/c=
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
github.com/stretchr/testify v1.7.2/go.mod h1:R6va5+xMeoiuVRoj+gSkQ7d3FALtqAAGI1FQKckRals=
github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk=
github.com/tv42/httpunix v0.0.0-20150427012821-b75d8614f926/go.mod h1:9ESjWnEqriFuLhtthL60Sar/7RFoluCcXsuvEwTV5KM=
go.opentelemetry.io/otel v1.24.0 h1:0LAOdjNmQeSTzGBzduGe/rU4tZhMwL5rWgtp9Ku5Jfo=
go.opentelemetry.io/otel v1.24.0/go.mod h1:W7b9Ozg4nkF5tWI5zsXkaKKDjdVjpD4oAt9Qi/MArHo=
go.opentelemetry.io/otel/metric v1.24.0 h1:6EhoGWWK28x1fbpA4tYTOWBkPefTDQnb8WSGXlc88kI=
go.opentelemetry.io/otel/metric v1.24.0/go.mod h1:VYhLe1rFfxuTXLgj4CBiyz+9WYBA8pNGJgDcSFRKBco=
go.opentelemetry.io/otel/sdk v1.24.0 h1:YMPPDNymmQN3ZgczicBY3B6sf9n62Dlj9pWD3ucgoDw=
go.opentelemetry.io/otel/sdk v1.24.0/go.mod h1:KVrIYw6tEubO9E96HQpcmpTKDVn9gdv35HoYiQWGDFg=
go.opentelemetry.io/otel/trace v1.24.0 h1:CsKnnL4dUAr/0llH9FKuc698G04IrpWV0MQA/Y1YELI=
go.opentelemetry.io/otel/trace v1.24.0/go.mod h1:HPc3Xr/cOApsBI154IU0OI0HJexz+aw5uPdbs3UCjNU=
golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20190923035154-9ee001bba392/go.mod h1:/lpIB1dKB+9EgE3H3cr1v9wB50oz8l4C4h62xy7jSTY=
golang.org/x/exp v0.0.0-20230817173708-d852ddb80c63 h1:m64FZMko/V45gv0bNmrNYoDEq8U5YUhetc9cBWKS1TQ=
golang.org/x/exp v0.0.0-20230817173708-d852ddb80c63/go.mod h1:0v4NqG35kSWCMzLaMeX+IQrlSnVE/bqGSyC2cz/9Le8=
golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190613194153-d28f0bde5980/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20190923162816-aa69164e4478/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
golang.org/x/net v0.0.0-20210410081132-afb366fc7cd1/go.mod h1:9tjilg8BloeKEkVJvy7fQ90B1CfIiPueXVOjqfkSzI8=
golang.org/x/net v0.27.0 h1:5K3Njcw06/l2y9vpGCSdcxWOYHOUk3dVNGDXN+FvAys=
golang.org/x/net v0.27.0/go.mod h1:dDi0PyhWNoiUOrAS8uXv/vnScO4wnHQO4mj9fn/RytE=
golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sys v0.0.0-20180823144017-11551d06cbcc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190922100055-0a153f010e69/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190924154521-2837fb4f24fe/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200122134326-e047566fdf82/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210303074136-134d130e1a04/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220503163025-988cb79eb6c6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220728004956-3c1f35247d10/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.22.0 h1:RI27ohtqKCnwULzJLqkv897zojh5/DwS/ENaMzUOaWI=
golang.org/x/sys v0.22.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.16.0 h1:a94ExnEXNtEwYLGJSIUxnWoxoRz/ZcCsV63ROupILh4=
golang.org/x/text v0.16.0/go.mod h1:GhwF1Be+LQoKShO3cGOHzqOgRrGaYc9AvblQOmPVHnI=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20190907020128-2ca718005c18/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 h1:YR8cESwS4TdDjEe65xsg0ogRM/Nc3DYOhEAlW+xobZo=
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.5/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=

View File

@ -11,6 +11,7 @@ require (
github.com/gogf/gf/contrib/drivers/mysql/v2 v2.8.2
github.com/gogf/gf/contrib/metric/otelmetric/v2 v2.8.2
github.com/gogf/gf/contrib/nosql/redis/v2 v2.8.2
github.com/gogf/gf/contrib/registry/consul/v2 v2.8.2
github.com/gogf/gf/contrib/registry/etcd/v2 v2.8.2
github.com/gogf/gf/contrib/registry/file/v2 v2.8.2
github.com/gogf/gf/contrib/registry/nacos/v2 v2.8.2
@ -19,7 +20,7 @@ require (
github.com/gogf/gf/contrib/trace/otlpgrpc/v2 v2.8.2
github.com/gogf/gf/contrib/trace/otlphttp/v2 v2.8.2
github.com/gogf/gf/v2 v2.8.2
github.com/hashicorp/consul/api v1.24.0
github.com/hashicorp/consul/api v1.26.1
github.com/hashicorp/go-cleanhttp v0.5.2
github.com/nacos-group/nacos-sdk-go/v2 v2.2.7
github.com/polarismesh/polaris-go v1.5.8
@ -168,6 +169,7 @@ replace (
github.com/gogf/gf/contrib/drivers/mysql/v2 => ../contrib/drivers/mysql/
github.com/gogf/gf/contrib/metric/otelmetric/v2 => ../contrib/metric/otelmetric
github.com/gogf/gf/contrib/nosql/redis/v2 => ../contrib/nosql/redis/
github.com/gogf/gf/contrib/registry/consul/v2 => ../contrib/registry/consul/
github.com/gogf/gf/contrib/registry/etcd/v2 => ../contrib/registry/etcd/
github.com/gogf/gf/contrib/registry/file/v2 => ../contrib/registry/file/
github.com/gogf/gf/contrib/registry/nacos/v2 => ../contrib/registry/nacos/

View File

@ -445,11 +445,11 @@ github.com/grpc-ecosystem/grpc-gateway v1.16.0/go.mod h1:BDjrQk3hbvj6Nolgz8mAMFb
github.com/grpc-ecosystem/grpc-gateway/v2 v2.22.0 h1:asbCHRVmodnJTuQ3qamDwqVOIjwqUPTYmYuemVOx+Ys=
github.com/grpc-ecosystem/grpc-gateway/v2 v2.22.0/go.mod h1:ggCgvZ2r7uOoQjOyu2Y1NhHmEPPzzuhWgcza5M1Ji1I=
github.com/hashicorp/consul/api v1.1.0/go.mod h1:VmuI/Lkw1nC05EYQWNKwWGbkg+FbDBtguAZLlVdkD9Q=
github.com/hashicorp/consul/api v1.24.0 h1:u2XyStA2j0jnCiVUU7Qyrt8idjRn4ORhK6DlvZ3bWhA=
github.com/hashicorp/consul/api v1.24.0/go.mod h1:NZJGRFYruc/80wYowkPFCp1LbGmJC9L8izrwfyVx/Wg=
github.com/hashicorp/consul/api v1.26.1 h1:5oSXOO5fboPZeW5SN+TdGFP/BILDgBm19OrPZ/pICIM=
github.com/hashicorp/consul/api v1.26.1/go.mod h1:B4sQTeaSO16NtynqrAdwOlahJ7IUDZM9cj2420xYL8A=
github.com/hashicorp/consul/sdk v0.1.1/go.mod h1:VKf9jXwCTEY1QZP2MOLRhb5i/I/ssyNV1vwHyQBF0x8=
github.com/hashicorp/consul/sdk v0.14.1 h1:ZiwE2bKb+zro68sWzZ1SgHF3kRMBZ94TwOCFRF4ylPs=
github.com/hashicorp/consul/sdk v0.14.1/go.mod h1:vFt03juSzocLRFo59NkeQHHmQa6+g7oU0pfzdI1mUhg=
github.com/hashicorp/consul/sdk v0.15.0 h1:2qK9nDrr4tiJKRoxPGhm6B7xJjLVIQqkjiab2M4aKjU=
github.com/hashicorp/consul/sdk v0.15.0/go.mod h1:r/OmRRPbHOe0yxNahLw7G9x5WG17E1BIECMtCjcPSNo=
github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4=
github.com/hashicorp/errwrap v1.1.0 h1:OxrOeh75EUXMY8TBjag2fzXGZ40LB6IKw45YeGUDY2I=
github.com/hashicorp/errwrap v1.1.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4=

View File

@ -0,0 +1,39 @@
// Copyright GoFrame Author(https://goframe.org). All Rights Reserved.
//
// This Source Code Form is subject to the terms of the MIT License.
// If a copy of the MIT was not distributed with this file,
// You can obtain one at https://github.com/gogf/gf.
package main
import (
"context"
"github.com/gogf/gf/v2/frame/g"
"github.com/gogf/gf/v2/os/gctx"
"github.com/gogf/gf/contrib/registry/consul/v2"
"github.com/gogf/gf/contrib/rpc/grpcx/v2"
"github.com/gogf/gf/example/registry/consul/grpc/protobuf"
)
func main() {
registry, err := consul.New(consul.WithAddress("127.0.0.1:8500"))
if err != nil {
g.Log().Fatal(context.Background(), err)
}
grpcx.Resolver.Register(registry)
var (
ctx = gctx.New()
conn = grpcx.Client.MustNewGrpcClientConn("demo")
client = protobuf.NewGreeterClient(conn)
)
res, err := client.SayHello(ctx, &protobuf.HelloRequest{Name: "World"})
if err != nil {
g.Log().Error(ctx, err)
return
}
g.Log().Debug(ctx, "Response:", res.Message)
}

View File

@ -0,0 +1,28 @@
// Copyright GoFrame Author(https://goframe.org). All Rights Reserved.
//
// This Source Code Form is subject to the terms of the MIT License.
// If a copy of the MIT was not distributed with this file,
// You can obtain one at https://github.com/gogf/gf.
package controller
import (
"context"
"github.com/gogf/gf/contrib/rpc/grpcx/v2"
"github.com/gogf/gf/example/registry/consul/grpc/protobuf"
)
type Controller struct {
protobuf.UnimplementedGreeterServer
}
func Register(s *grpcx.GrpcServer) {
protobuf.RegisterGreeterServer(s.Server, &Controller{})
}
// SayHello implements helloworld.GreeterServer
func (s *Controller) SayHello(ctx context.Context, in *protobuf.HelloRequest) (*protobuf.HelloReply, error) {
return &protobuf.HelloReply{Message: "Hello " + in.GetName()}, nil
}

View File

@ -0,0 +1,217 @@
// protoc --go_out=paths=source_relative:. --go-grpc_out=paths=source_relative:. *.proto
// Code generated by protoc-gen-go. DO NOT EDIT.
// versions:
// protoc-gen-go v1.28.1
// protoc v3.21.12
// source: helloworld.proto
package protobuf
import (
protoreflect "google.golang.org/protobuf/reflect/protoreflect"
protoimpl "google.golang.org/protobuf/runtime/protoimpl"
reflect "reflect"
sync "sync"
)
const (
// Verify that this generated code is sufficiently up-to-date.
_ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion)
// Verify that runtime/protoimpl is sufficiently up-to-date.
_ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20)
)
// The request message containing the user's name.
type HelloRequest struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
Name string `protobuf:"bytes,1,opt,name=name,proto3" json:"name,omitempty"`
}
func (x *HelloRequest) Reset() {
*x = HelloRequest{}
if protoimpl.UnsafeEnabled {
mi := &file_helloworld_proto_msgTypes[0]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
}
func (x *HelloRequest) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*HelloRequest) ProtoMessage() {}
func (x *HelloRequest) ProtoReflect() protoreflect.Message {
mi := &file_helloworld_proto_msgTypes[0]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use HelloRequest.ProtoReflect.Descriptor instead.
func (*HelloRequest) Descriptor() ([]byte, []int) {
return file_helloworld_proto_rawDescGZIP(), []int{0}
}
func (x *HelloRequest) GetName() string {
if x != nil {
return x.Name
}
return ""
}
// The response message containing the greetings
type HelloReply struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
Message string `protobuf:"bytes,1,opt,name=message,proto3" json:"message,omitempty"`
}
func (x *HelloReply) Reset() {
*x = HelloReply{}
if protoimpl.UnsafeEnabled {
mi := &file_helloworld_proto_msgTypes[1]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
}
func (x *HelloReply) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*HelloReply) ProtoMessage() {}
func (x *HelloReply) ProtoReflect() protoreflect.Message {
mi := &file_helloworld_proto_msgTypes[1]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use HelloReply.ProtoReflect.Descriptor instead.
func (*HelloReply) Descriptor() ([]byte, []int) {
return file_helloworld_proto_rawDescGZIP(), []int{1}
}
func (x *HelloReply) GetMessage() string {
if x != nil {
return x.Message
}
return ""
}
var File_helloworld_proto protoreflect.FileDescriptor
var file_helloworld_proto_rawDesc = []byte{
0x0a, 0x10, 0x68, 0x65, 0x6c, 0x6c, 0x6f, 0x77, 0x6f, 0x72, 0x6c, 0x64, 0x2e, 0x70, 0x72, 0x6f,
0x74, 0x6f, 0x12, 0x08, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x22, 0x22, 0x0a, 0x0c,
0x48, 0x65, 0x6c, 0x6c, 0x6f, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x12, 0x0a, 0x04,
0x6e, 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65,
0x22, 0x26, 0x0a, 0x0a, 0x48, 0x65, 0x6c, 0x6c, 0x6f, 0x52, 0x65, 0x70, 0x6c, 0x79, 0x12, 0x18,
0x0a, 0x07, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52,
0x07, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x32, 0x45, 0x0a, 0x07, 0x47, 0x72, 0x65, 0x65,
0x74, 0x65, 0x72, 0x12, 0x3a, 0x0a, 0x08, 0x53, 0x61, 0x79, 0x48, 0x65, 0x6c, 0x6c, 0x6f, 0x12,
0x16, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x48, 0x65, 0x6c, 0x6c, 0x6f,
0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x14, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62,
0x75, 0x66, 0x2e, 0x48, 0x65, 0x6c, 0x6c, 0x6f, 0x52, 0x65, 0x70, 0x6c, 0x79, 0x22, 0x00, 0x42,
0x35, 0x5a, 0x33, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x67, 0x6f,
0x67, 0x66, 0x2f, 0x67, 0x66, 0x2f, 0x67, 0x72, 0x70, 0x63, 0x2f, 0x65, 0x78, 0x61, 0x6d, 0x70,
0x6c, 0x65, 0x2f, 0x68, 0x65, 0x6c, 0x6c, 0x6f, 0x77, 0x6f, 0x72, 0x6c, 0x64, 0x2f, 0x70, 0x72,
0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33,
}
var (
file_helloworld_proto_rawDescOnce sync.Once
file_helloworld_proto_rawDescData = file_helloworld_proto_rawDesc
)
func file_helloworld_proto_rawDescGZIP() []byte {
file_helloworld_proto_rawDescOnce.Do(func() {
file_helloworld_proto_rawDescData = protoimpl.X.CompressGZIP(file_helloworld_proto_rawDescData)
})
return file_helloworld_proto_rawDescData
}
var file_helloworld_proto_msgTypes = make([]protoimpl.MessageInfo, 2)
var file_helloworld_proto_goTypes = []interface{}{
(*HelloRequest)(nil), // 0: protobuf.HelloRequest
(*HelloReply)(nil), // 1: protobuf.HelloReply
}
var file_helloworld_proto_depIdxs = []int32{
0, // 0: protobuf.Greeter.SayHello:input_type -> protobuf.HelloRequest
1, // 1: protobuf.Greeter.SayHello:output_type -> protobuf.HelloReply
1, // [1:2] is the sub-list for method output_type
0, // [0:1] is the sub-list for method input_type
0, // [0:0] is the sub-list for extension type_name
0, // [0:0] is the sub-list for extension extendee
0, // [0:0] is the sub-list for field type_name
}
func init() { file_helloworld_proto_init() }
func file_helloworld_proto_init() {
if File_helloworld_proto != nil {
return
}
if !protoimpl.UnsafeEnabled {
file_helloworld_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*HelloRequest); i {
case 0:
return &v.state
case 1:
return &v.sizeCache
case 2:
return &v.unknownFields
default:
return nil
}
}
file_helloworld_proto_msgTypes[1].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*HelloReply); i {
case 0:
return &v.state
case 1:
return &v.sizeCache
case 2:
return &v.unknownFields
default:
return nil
}
}
}
type x struct{}
out := protoimpl.TypeBuilder{
File: protoimpl.DescBuilder{
GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
RawDescriptor: file_helloworld_proto_rawDesc,
NumEnums: 0,
NumMessages: 2,
NumExtensions: 0,
NumServices: 1,
},
GoTypes: file_helloworld_proto_goTypes,
DependencyIndexes: file_helloworld_proto_depIdxs,
MessageInfos: file_helloworld_proto_msgTypes,
}.Build()
File_helloworld_proto = out.File
file_helloworld_proto_rawDesc = nil
file_helloworld_proto_goTypes = nil
file_helloworld_proto_depIdxs = nil
}

View File

@ -0,0 +1,24 @@
// protoc --go_out=paths=source_relative:. --go-grpc_out=paths=source_relative:. *.proto
syntax = "proto3";
package protobuf;
option go_package = "github.com/gogf/gf/grpc/example/helloworld/protobuf";
// The greeting service definition.
service Greeter {
// Sends a greeting
rpc SayHello (HelloRequest) returns (HelloReply) {}
}
// The request message containing the user's name.
message HelloRequest {
string name = 1;
}
// The response message containing the greetings
message HelloReply {
string message = 1;
}

View File

@ -0,0 +1,107 @@
// Code generated by protoc-gen-go-grpc. DO NOT EDIT.
// versions:
// - protoc-gen-go-grpc v1.2.0
// - protoc v3.21.12
// source: helloworld.proto
package protobuf
import (
context "context"
grpc "google.golang.org/grpc"
codes "google.golang.org/grpc/codes"
status "google.golang.org/grpc/status"
)
// This is a compile-time assertion to ensure that this generated file
// is compatible with the grpc package it is being compiled against.
// Requires gRPC-Go v1.32.0 or later.
const _ = grpc.SupportPackageIsVersion7
// GreeterClient is the client API for Greeter service.
//
// For semantics around ctx use and closing/ending streaming RPCs, please refer to https://pkg.go.dev/google.golang.org/grpc/?tab=doc#ClientConn.NewStream.
type GreeterClient interface {
// Sends a greeting
SayHello(ctx context.Context, in *HelloRequest, opts ...grpc.CallOption) (*HelloReply, error)
}
type greeterClient struct {
cc grpc.ClientConnInterface
}
func NewGreeterClient(cc grpc.ClientConnInterface) GreeterClient {
return &greeterClient{cc}
}
func (c *greeterClient) SayHello(ctx context.Context, in *HelloRequest, opts ...grpc.CallOption) (*HelloReply, error) {
out := new(HelloReply)
err := c.cc.Invoke(ctx, "/protobuf.Greeter/SayHello", in, out, opts...)
if err != nil {
return nil, err
}
return out, nil
}
// GreeterServer is the server API for Greeter service.
// All implementations must embed UnimplementedGreeterServer
// for forward compatibility
type GreeterServer interface {
// Sends a greeting
SayHello(context.Context, *HelloRequest) (*HelloReply, error)
mustEmbedUnimplementedGreeterServer()
}
// UnimplementedGreeterServer must be embedded to have forward compatible implementations.
type UnimplementedGreeterServer struct {
}
func (UnimplementedGreeterServer) SayHello(context.Context, *HelloRequest) (*HelloReply, error) {
return nil, status.Errorf(codes.Unimplemented, "method SayHello not implemented")
}
func (UnimplementedGreeterServer) mustEmbedUnimplementedGreeterServer() {}
// UnsafeGreeterServer may be embedded to opt out of forward compatibility for this service.
// Use of this interface is not recommended, as added methods to GreeterServer will
// result in compilation errors.
type UnsafeGreeterServer interface {
mustEmbedUnimplementedGreeterServer()
}
func RegisterGreeterServer(s grpc.ServiceRegistrar, srv GreeterServer) {
s.RegisterService(&Greeter_ServiceDesc, srv)
}
func _Greeter_SayHello_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
in := new(HelloRequest)
if err := dec(in); err != nil {
return nil, err
}
if interceptor == nil {
return srv.(GreeterServer).SayHello(ctx, in)
}
info := &grpc.UnaryServerInfo{
Server: srv,
FullMethod: "/protobuf.Greeter/SayHello",
}
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
return srv.(GreeterServer).SayHello(ctx, req.(*HelloRequest))
}
return interceptor(ctx, in, info, handler)
}
// Greeter_ServiceDesc is the grpc.ServiceDesc for Greeter service.
// It's only intended for direct use with grpc.RegisterService,
// and not to be introspected or modified (even as a copy)
var Greeter_ServiceDesc = grpc.ServiceDesc{
ServiceName: "protobuf.Greeter",
HandlerType: (*GreeterServer)(nil),
Methods: []grpc.MethodDesc{
{
MethodName: "SayHello",
Handler: _Greeter_SayHello_Handler,
},
},
Streams: []grpc.StreamDesc{},
Metadata: "helloworld.proto",
}

View File

@ -0,0 +1,8 @@
grpc:
name: "demo"
logPath: "./log"
logStdout: true
errorLogEnabled: true
accessLogEnabled: true
errorStack: true

View File

@ -0,0 +1,30 @@
// Copyright GoFrame Author(https://goframe.org). All Rights Reserved.
//
// This Source Code Form is subject to the terms of the MIT License.
// If a copy of the MIT was not distributed with this file,
// You can obtain one at https://github.com/gogf/gf.
package main
import (
"context"
"github.com/gogf/gf/v2/frame/g"
"github.com/gogf/gf/contrib/registry/consul/v2"
"github.com/gogf/gf/contrib/rpc/grpcx/v2"
"github.com/gogf/gf/example/registry/consul/grpc/controller"
)
func main() {
registry, err := consul.New(consul.WithAddress("127.0.0.1:8500"))
if err != nil {
g.Log().Fatal(context.Background(), err)
}
grpcx.Resolver.Register(registry)
s := grpcx.Server.New()
controller.Register(s)
s.Run()
}

View File

@ -0,0 +1,29 @@
// Copyright GoFrame Author(https://goframe.org). All Rights Reserved.
//
// This Source Code Form is subject to the terms of the MIT License.
// If a copy of the MIT was not distributed with this file,
// You can obtain one at https://github.com/gogf/gf.
package main
import (
"context"
"github.com/gogf/gf/v2/frame/g"
"github.com/gogf/gf/v2/net/gsvc"
"github.com/gogf/gf/v2/os/gctx"
"github.com/gogf/gf/contrib/registry/consul/v2"
)
func main() {
registry, err := consul.New(consul.WithAddress("127.0.0.1:8500"))
if err != nil {
g.Log().Fatal(context.Background(), err)
}
gsvc.SetRegistry(registry)
ctx := gctx.New()
res := g.Client().GetContent(ctx, `http://hello.svc/`)
g.Log().Info(ctx, res)
}

View File

@ -0,0 +1,32 @@
// Copyright GoFrame Author(https://goframe.org). All Rights Reserved.
//
// This Source Code Form is subject to the terms of the MIT License.
// If a copy of the MIT was not distributed with this file,
// You can obtain one at https://github.com/gogf/gf.
package main
import (
"context"
"github.com/gogf/gf/v2/frame/g"
"github.com/gogf/gf/v2/net/ghttp"
"github.com/gogf/gf/v2/net/gsvc"
"github.com/gogf/gf/contrib/registry/consul/v2"
)
func main() {
registry, err := consul.New(consul.WithAddress("127.0.0.1:8500"))
if err != nil {
g.Log().Fatal(context.Background(), err)
}
gsvc.SetRegistry(registry)
s := g.Server(`hello.svc`)
s.BindHandler("/", func(r *ghttp.Request) {
g.Log().Info(r.Context(), `request received`)
r.Response.Write(`Hello world`)
})
s.Run()
}