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
+49
View File
@@ -0,0 +1,49 @@
# Examples wayfinding
Use this index when you want the shortest path from a first runnable agent to the
next services, agents, workflows, and interop examples. Every command below is
provider-free unless the example README says otherwise.
## Pick by goal
| Goal | Start here | Run or verify | Then try |
|------|------------|---------------|----------|
| Run the smallest no-secret agent | [`first-agent`](./first-agent/) | `go run ./examples/first-agent` | [`agent-demo`](./agent-demo/) for a larger service-backed agent |
| Prove the maintained 0→hero path | [`support`](./support/) | `go run ./examples/support` and `go test ./examples/support` | [`zero-to-hero` guide](../internal/website/docs/guides/zero-to-hero.md) |
| See planning and delegation | [`agent-plan-delegate`](./agent-plan-delegate/) | `go run ./examples/agent-plan-delegate` | [`plan-delegate` guide](../internal/website/docs/guides/plan-delegate.md) |
| Expose services through MCP | [`mcp/hello`](./mcp/hello/) | follow [`mcp`](./mcp/) setup | [`mcp/crud`](./mcp/crud/) and [`mcp/workflow`](./mcp/workflow/) |
| Try a paid tool with x402 | [`agent-x402-buyer`](./agent-x402-buyer/) | `go run ./examples/agent-x402-buyer` | [`Payments (x402)` guide](../internal/website/docs/guides/x402-payments.md) |
| Try A2A or gRPC interop next | [`agent-demo`](./agent-demo/) plus gateway docs | run the example, then use the gateway docs | [`grpc-interop`](./grpc-interop/) |
| Add workflow durability | [`flow-durable`](./flow-durable/) | `go run ./examples/flow-durable` | [`flow-loop`](./flow-loop/) |
## Recommended adoption path
1. **First service:** run [`hello-world`](./hello-world/) to learn service
registration, handlers, client calls, and health checks.
2. **First agent:** run [`first-agent`](./first-agent/) with
`go run ./examples/first-agent`; it uses a deterministic mock model and needs
no provider key.
3. **0→hero reference:** run [`support`](./support/) with
`go run ./examples/support`; it keeps typed services, an agent chat loop, an
event-driven flow, and an approval gate in one maintained example.
4. **Interop next:** use [`mcp/hello`](./mcp/hello/), [`mcp/crud`](./mcp/crud/),
and [`mcp/workflow`](./mcp/workflow/) when you are ready to expose tools to
external AI clients.
5. **Paid tools:** run [`agent-x402-buyer`](./agent-x402-buyer/) to see an
agent pay a local x402-protected tool with a mock facilitator and budget.
6. **Workflow depth:** use [`flow-durable`](./flow-durable/) once the agent path
needs checkpointed, resumable deterministic work.
## CLI wayfinding
The installed CLI prints the same path:
```bash
micro examples
micro agent demo
micro zero-to-hero
```
Keep this file, [`README.md`](../README.md), and the `micro examples` output in
sync so new developers can find `examples/first-agent` and `examples/support`
from one documented path.
+167
View File
@@ -0,0 +1,167 @@
# Go Micro Examples
This directory contains runnable examples that take you through the Go Micro
lifecycle: start with a service, expose it as agent-usable capability, then
coordinate work with workflows.
## Quick Start
Each example can be run with `go run .` from its directory unless its README says
otherwise. If you are new to the repo, start with the [examples wayfinding index](./INDEX.md)
or follow the first-agent path below instead of reading the directories alphabetically.
## Recommended first-agent path
This path is the canonical services → agents → workflows route through the examples map. Debugging and observability wayfinding stays nearby once the first run works.
| Step | Start here | What you learn | Next step |
|------|------------|----------------|-----------|
| 1. First service | [`hello-world`](./hello-world/) | Build the 0→1 service path: create and register a basic RPC service, add a handler, call it with a client, and expose health checks. | Move to [`agent-demo`](./agent-demo/) to see services used by an agent. |
| 2. First agent | [`first-agent`](./first-agent/) | Run the smallest service-backed agent with a deterministic mock model and no provider key. | Compare with [`agent-demo`](./agent-demo/) or the maintained 0-to-hero path in [`support`](./support/). |
| 3. First workflow | [`support`](./support/) | Follow typed services into an agent chat loop, an event-driven `intake` flow, and an approval gate in one runnable reference. | Deepen the workflow model with [`flow-durable`](./flow-durable/). |
For the shortest AI-tooling bridge, the MCP path is
[`mcp/hello`](./mcp/hello/) → [`mcp/crud`](./mcp/crud/) →
[`mcp/workflow`](./mcp/workflow/). For debugging and production hardening, keep
[`agent-wrap-tool`](./agent-wrap-tool/), [`agent-durable`](./agent-durable/), and
[`deployment`](./deployment/) nearby.
## Lifecycle map
### 1. Services — learn the runtime foundation
#### [hello-world](./hello-world/)
Basic RPC service demonstrating core concepts:
- Service creation and registration
- Handler implementation
- Client calls
- Health checks
**Run it:**
```bash
cd hello-world
go run .
```
#### [web-service](./web-service/)
HTTP web service with service discovery:
- HTTP handlers
- Service registration
- Health checks
- JSON REST API
**Run it:**
```bash
cd web-service
go run .
```
#### [multi-service](./multi-service/)
Multiple services in a single binary — the modular monolith pattern:
- Isolated server, client, store, and cache per service
- Shared registry and broker for inter-service communication
- Coordinated lifecycle with `service.Group`
- Start monolith, split later when you need to scale independently
**Run it:**
```bash
cd multi-service
go run .
```
#### [deployment](./deployment/)
Docker Compose deployment with MCP gateway, Consul registry, and Jaeger tracing:
- Production-like architecture in one `docker-compose up`
- Standalone MCP gateway connected to service registry
- Distributed tracing with OpenTelemetry + Jaeger
### 2. Agents — turn services into tool-using teammates
#### [first-agent](./first-agent/)
Smallest first agent: one notes service plus one scoped agent, backed by a deterministic mock model so `go run ./examples/first-agent` works without provider secrets.
#### [agent-demo](./agent-demo/)
A multi-service project management app with Projects,
Tasks, and Team services, seed data, and agent playground integration.
#### [agent-plan-delegate](./agent-plan-delegate/)
The two built-in agent capabilities in a small multi-agent system:
- **plan** — an agent records an ordered plan in its store-backed memory before doing multi-step work
- **delegate** — an agent hands a subtask to another agent (over RPC if it's registered, else to an ephemeral sub-agent)
#### [agent-wrap-tool](./agent-wrap-tool/)
Middleware around an agent's tool execution with `AgentWrapTool`, the tool-side analogue of client/server wrappers:
- **observe** — time every tool call and record per-tool metrics, correlated by call ID
- **retry** — re-run a call whose result is an error, recovering from a transient failure before the model sees it
#### [agent-durable](./agent-durable/)
Durable agent runs that can be checkpointed and resumed, useful once your first
agent needs predictable recovery behavior.
#### [agent-human-input](./agent-human-input/)
Human-in-the-loop agent interaction for decisions that need an explicit person
before the run can continue.
#### [agent-ollama](./agent-ollama/)
Local-model agent wiring for developers experimenting with Ollama-backed model
calls.
### 3. Workflows — coordinate longer-running work
#### [support](./support/)
A maintained 0-to-hero reference path in one runnable file:
- **scaffold** typed `customers`, `tickets`, and `notify` services
- **run/chat** with a support agent that uses those services as tools
- **inspect** the event-driven `intake` flow and approval gate
- **CI** keeps the deterministic mock-model journey runnable with `go test ./examples/support`
#### [flow-durable](./flow-durable/)
A workflow as ordered, checkpointed steps that survives a crash and resumes where it stopped:
- **steps** — a flow is a task with stages (`reserve → charge → confirm`), not just one LLM turn
- **Checkpoint** — each step is persisted; on `Resume`, completed steps are not re-run (no duplicate side effects)
#### [flow-loop](./flow-loop/)
A looping flow example for repeated workflow steps.
### 4. MCP and agent integration examples
See the [mcp/](./mcp/) directory for AI agent integration examples:
- **[hello](./mcp/hello/)** - Minimal MCP service (start here)
- **[crud](./mcp/crud/)** - CRUD contact book with full agent documentation
- **[workflow](./mcp/workflow/)** - Cross-service orchestration via AI agents
- **[documented](./mcp/documented/)** - All MCP features with auth scopes
- **[platform](./mcp/platform/)** - Platform-oriented MCP service example
## Other examples
### [auth](./auth/)
Authentication and authorization example.
### [graceful-stop](./graceful-stop/)
Graceful shutdown behavior for long-running services.
### [grpc-interop](./grpc-interop/)
gRPC interoperability example.
## Coming Soon
- **pubsub-events** - Event-driven architecture with NATS
- **grpc-integration** - Using go-micro with gRPC
## Prerequisites
Some examples require external dependencies:
- **NATS**: `docker run -p 4222:4222 nats:latest`
- **Consul**: `docker run -p 8500:8500 consul:latest agent -dev -ui -client=0.0.0.0`
- **Redis**: `docker run -p 6379:6379 redis:latest`
## Contributing
To add a new example:
1. Create a new directory
2. Add a descriptive README.md
3. Include working code with comments
4. Add to this index under the lifecycle stage it supports
5. Ensure it runs with `go run .`
+63
View File
@@ -0,0 +1,63 @@
# Agent Demo
A multi-service project management app that demonstrates AI agents interacting with Go Micro services through MCP.
## What's Included
Three services registered in a single process:
| Service | Endpoints | Description |
|---------|-----------|-------------|
| **ProjectService** | Create, Get, List | Manage projects with status tracking |
| **TaskService** | Create, List, Update | Tasks with assignees, priorities, and status |
| **TeamService** | Add, List, Get | Team members with roles and skills |
The demo starts with seed data: 2 projects, 7 tasks, and 4 team members.
## Run
```bash
go run main.go
```
Endpoints:
- **MCP Gateway:** http://localhost:3000
- **MCP Tools:** http://localhost:3000/mcp/tools
- **WebSocket:** ws://localhost:3000/mcp/ws
## Use with Claude Code
```json
{
"mcpServers": {
"demo": {
"command": "go",
"args": ["run", "main.go"],
"cwd": "examples/agent-demo"
}
}
}
```
## Example Prompts
Try these with Claude Code or any MCP client:
- "What projects do we have?"
- "Show me all tasks assigned to alice"
- "Create a high-priority task for bob to review the design mockups"
- "Who on the team knows Go?"
- "Give me a status update on the Website Redesign project"
- "What tasks are still todo on the API v2 migration?"
- "Assign the unassigned tasks to charlie"
- "Mark task-1 as done"
## What This Demonstrates
1. **Zero-config MCP** — Services become AI tools automatically from doc comments
2. **Cross-service orchestration** — An agent queries projects, tasks, and team in one conversation
3. **Rich tool descriptions**`description` struct tags and `@example` comments guide the agent
4. **Auth scopes** — Read and write operations have separate scopes
5. **`WithMCP` one-liner** — MCP gateway starts with a single option
See the [blog post](/blog/4) for a detailed walkthrough.
+454
View File
@@ -0,0 +1,454 @@
// Agent Demo — A multi-service project management app
//
// This example shows three Go Micro services (projects, tasks, team)
// working together through the MCP gateway, letting an AI agent
// manage projects using natural language.
//
// Run:
//
// go run main.go
//
// Then open the agent playground at http://localhost:8080/agent
// or connect Claude Code via: micro mcp serve
package main
import (
"context"
"fmt"
"strings"
"sync"
"time"
"go-micro.dev/v6"
"go-micro.dev/v6/gateway/mcp"
"go-micro.dev/v6/server"
)
// ---------------------------------------------------------------------------
// Projects service
// ---------------------------------------------------------------------------
type Project struct {
ID string `json:"id" description:"Unique project identifier"`
Name string `json:"name" description:"Project name"`
Description string `json:"description" description:"What the project is about"`
Status string `json:"status" description:"Project status: planning, active, or completed"`
CreatedAt time.Time `json:"created_at" description:"When the project was created"`
}
type CreateProjectRequest struct {
Name string `json:"name" description:"Project name (required)"`
Description string `json:"description" description:"Short description of the project"`
}
type CreateProjectResponse struct {
Project *Project `json:"project" description:"The newly created project"`
}
type GetProjectRequest struct {
ID string `json:"id" description:"Project ID to retrieve"`
}
type GetProjectResponse struct {
Project *Project `json:"project" description:"The requested project"`
}
type ListProjectsRequest struct {
Status string `json:"status,omitempty" description:"Filter by status: planning, active, completed (optional)"`
}
type ListProjectsResponse struct {
Projects []*Project `json:"projects" description:"List of matching projects"`
}
type ProjectService struct {
mu sync.RWMutex
projects map[string]*Project
nextID int
}
// Create creates a new project with the given name and description.
// Returns the project with a generated ID and initial status of "planning".
//
// @example {"name": "Website Redesign", "description": "Redesign the company website with new branding"}
func (s *ProjectService) Create(ctx context.Context, req *CreateProjectRequest, rsp *CreateProjectResponse) error {
s.mu.Lock()
defer s.mu.Unlock()
s.nextID++
p := &Project{
ID: fmt.Sprintf("proj-%d", s.nextID),
Name: req.Name,
Description: req.Description,
Status: "planning",
CreatedAt: time.Now(),
}
s.projects[p.ID] = p
rsp.Project = p
return nil
}
// Get retrieves a project by ID.
// Returns an error if the project does not exist.
//
// @example {"id": "proj-1"}
func (s *ProjectService) Get(ctx context.Context, req *GetProjectRequest, rsp *GetProjectResponse) error {
s.mu.RLock()
defer s.mu.RUnlock()
p, ok := s.projects[req.ID]
if !ok {
return fmt.Errorf("project %s not found", req.ID)
}
rsp.Project = p
return nil
}
// List returns all projects, optionally filtered by status.
// Valid status values: planning, active, completed.
//
// @example {"status": "active"}
func (s *ProjectService) List(ctx context.Context, req *ListProjectsRequest, rsp *ListProjectsResponse) error {
s.mu.RLock()
defer s.mu.RUnlock()
for _, p := range s.projects {
if req.Status == "" || p.Status == req.Status {
rsp.Projects = append(rsp.Projects, p)
}
}
return nil
}
// ---------------------------------------------------------------------------
// Tasks service
// ---------------------------------------------------------------------------
type Task struct {
ID string `json:"id" description:"Unique task identifier"`
ProjectID string `json:"project_id" description:"ID of the project this task belongs to"`
Title string `json:"title" description:"Short task title"`
Status string `json:"status" description:"Task status: todo, in_progress, or done"`
Assignee string `json:"assignee,omitempty" description:"Username of the person assigned"`
Priority string `json:"priority" description:"Priority: low, medium, or high"`
}
type CreateTaskRequest struct {
ProjectID string `json:"project_id" description:"Project ID to add the task to (required)"`
Title string `json:"title" description:"Task title (required)"`
Assignee string `json:"assignee,omitempty" description:"Username to assign (optional)"`
Priority string `json:"priority,omitempty" description:"Priority: low, medium, or high (default: medium)"`
}
type CreateTaskResponse struct {
Task *Task `json:"task" description:"The newly created task"`
}
type ListTasksRequest struct {
ProjectID string `json:"project_id,omitempty" description:"Filter by project ID (optional)"`
Assignee string `json:"assignee,omitempty" description:"Filter by assignee username (optional)"`
Status string `json:"status,omitempty" description:"Filter by status: todo, in_progress, done (optional)"`
}
type ListTasksResponse struct {
Tasks []*Task `json:"tasks" description:"List of matching tasks"`
}
type UpdateTaskRequest struct {
ID string `json:"id" description:"Task ID to update"`
Status string `json:"status,omitempty" description:"New status: todo, in_progress, or done"`
Assignee string `json:"assignee,omitempty" description:"New assignee username"`
}
type UpdateTaskResponse struct {
Task *Task `json:"task" description:"The updated task"`
}
type TaskService struct {
mu sync.RWMutex
tasks map[string]*Task
nextID int
}
// Create creates a new task in a project.
// Returns the task with a generated ID, initial status of "todo", and default priority of "medium".
//
// @example {"project_id": "proj-1", "title": "Design homepage mockup", "assignee": "alice", "priority": "high"}
func (s *TaskService) Create(ctx context.Context, req *CreateTaskRequest, rsp *CreateTaskResponse) error {
s.mu.Lock()
defer s.mu.Unlock()
s.nextID++
priority := req.Priority
if priority == "" {
priority = "medium"
}
t := &Task{
ID: fmt.Sprintf("task-%d", s.nextID),
ProjectID: req.ProjectID,
Title: req.Title,
Status: "todo",
Assignee: req.Assignee,
Priority: priority,
}
s.tasks[t.ID] = t
rsp.Task = t
return nil
}
// List returns tasks filtered by project, assignee, or status.
// All filters are optional; omit all to list every task.
//
// @example {"project_id": "proj-1", "status": "todo"}
func (s *TaskService) List(ctx context.Context, req *ListTasksRequest, rsp *ListTasksResponse) error {
s.mu.RLock()
defer s.mu.RUnlock()
for _, t := range s.tasks {
if req.ProjectID != "" && t.ProjectID != req.ProjectID {
continue
}
if req.Assignee != "" && t.Assignee != req.Assignee {
continue
}
if req.Status != "" && t.Status != req.Status {
continue
}
rsp.Tasks = append(rsp.Tasks, t)
}
return nil
}
// Update modifies a task's status or assignee.
// Only provided fields are changed; omitted fields stay the same.
// Returns an error if the task does not exist.
//
// @example {"id": "task-1", "status": "in_progress"}
func (s *TaskService) Update(ctx context.Context, req *UpdateTaskRequest, rsp *UpdateTaskResponse) error {
s.mu.Lock()
defer s.mu.Unlock()
t, ok := s.tasks[req.ID]
if !ok {
return fmt.Errorf("task %s not found", req.ID)
}
if req.Status != "" {
t.Status = req.Status
}
if req.Assignee != "" {
t.Assignee = req.Assignee
}
rsp.Task = t
return nil
}
// ---------------------------------------------------------------------------
// Team service
// ---------------------------------------------------------------------------
type Member struct {
Username string `json:"username" description:"Unique username"`
Name string `json:"name" description:"Display name"`
Role string `json:"role" description:"Role: engineer, designer, or manager"`
Skills []string `json:"skills" description:"List of skills (e.g. go, react, figma)"`
}
type AddMemberRequest struct {
Username string `json:"username" description:"Unique username (required)"`
Name string `json:"name" description:"Display name (required)"`
Role string `json:"role" description:"Role: engineer, designer, or manager"`
Skills []string `json:"skills,omitempty" description:"List of skills"`
}
type AddMemberResponse struct {
Member *Member `json:"member" description:"The added team member"`
}
type ListMembersRequest struct {
Role string `json:"role,omitempty" description:"Filter by role: engineer, designer, manager (optional)"`
Skill string `json:"skill,omitempty" description:"Filter by skill (optional, e.g. 'go' or 'react')"`
}
type ListMembersResponse struct {
Members []*Member `json:"members" description:"List of matching team members"`
}
type GetMemberRequest struct {
Username string `json:"username" description:"Username to look up"`
}
type GetMemberResponse struct {
Member *Member `json:"member" description:"The team member"`
}
type TeamService struct {
mu sync.RWMutex
members map[string]*Member
}
// Add adds a new team member.
// Returns the member with their assigned role and skills.
//
// @example {"username": "alice", "name": "Alice Chen", "role": "engineer", "skills": ["go", "react"]}
func (s *TeamService) Add(ctx context.Context, req *AddMemberRequest, rsp *AddMemberResponse) error {
s.mu.Lock()
defer s.mu.Unlock()
m := &Member{
Username: req.Username,
Name: req.Name,
Role: req.Role,
Skills: req.Skills,
}
s.members[m.Username] = m
rsp.Member = m
return nil
}
// List returns team members, optionally filtered by role or skill.
//
// @example {"role": "engineer"}
func (s *TeamService) List(ctx context.Context, req *ListMembersRequest, rsp *ListMembersResponse) error {
s.mu.RLock()
defer s.mu.RUnlock()
for _, m := range s.members {
if req.Role != "" && m.Role != req.Role {
continue
}
if req.Skill != "" && !hasSkill(m.Skills, req.Skill) {
continue
}
rsp.Members = append(rsp.Members, m)
}
return nil
}
// Get retrieves a team member by username.
// Returns an error if the member does not exist.
//
// @example {"username": "alice"}
func (s *TeamService) Get(ctx context.Context, req *GetMemberRequest, rsp *GetMemberResponse) error {
s.mu.RLock()
defer s.mu.RUnlock()
m, ok := s.members[req.Username]
if !ok {
return fmt.Errorf("member %s not found", req.Username)
}
rsp.Member = m
return nil
}
func hasSkill(skills []string, target string) bool {
for _, s := range skills {
if strings.EqualFold(s, target) {
return true
}
}
return false
}
// ---------------------------------------------------------------------------
// Main — wire everything together
// ---------------------------------------------------------------------------
func main() {
// Create the service
service := micro.NewService("demo",
micro.Address(":9090"),
// Start MCP gateway alongside the service
mcp.WithMCP(":3000"),
)
service.Init()
// Register all three handlers with scopes
service.Handle(
&ProjectService{projects: make(map[string]*Project)},
server.WithEndpointScopes("ProjectService.Create", "projects:write"),
server.WithEndpointScopes("ProjectService.Get", "projects:read"),
server.WithEndpointScopes("ProjectService.List", "projects:read"),
)
service.Handle(
&TaskService{tasks: make(map[string]*Task)},
server.WithEndpointScopes("TaskService.Create", "tasks:write"),
server.WithEndpointScopes("TaskService.List", "tasks:read"),
server.WithEndpointScopes("TaskService.Update", "tasks:write"),
)
service.Handle(
&TeamService{members: make(map[string]*Member)},
server.WithEndpointScopes("TeamService.Add", "team:write"),
server.WithEndpointScopes("TeamService.List", "team:read"),
server.WithEndpointScopes("TeamService.Get", "team:read"),
)
// Seed some demo data
seedData(service.Server())
fmt.Println()
fmt.Println(" Agent Demo")
fmt.Println()
fmt.Println(" MCP Gateway http://localhost:3000")
fmt.Println(" MCP Tools http://localhost:3000/mcp/tools")
fmt.Println(" WebSocket ws://localhost:3000/mcp/ws")
fmt.Println()
fmt.Println(" Try these prompts with Claude Code or the agent playground:")
fmt.Println()
fmt.Println(" \"What projects do we have?\"")
fmt.Println(" \"Create a task for alice to design the new landing page\"")
fmt.Println(" \"Show me all high-priority tasks that are still todo\"")
fmt.Println(" \"Who on the team knows React?\"")
fmt.Println(" \"Give me a status update on the Website Redesign project\"")
fmt.Println()
service.Run()
}
// seedData pre-populates the services with realistic demo data.
func seedData(srv server.Server) {
ctx := context.Background()
// Seed team members
team := &TeamService{members: make(map[string]*Member)}
for _, m := range []AddMemberRequest{
{Username: "alice", Name: "Alice Chen", Role: "engineer", Skills: []string{"go", "grpc", "kubernetes"}},
{Username: "bob", Name: "Bob Park", Role: "designer", Skills: []string{"figma", "css", "react"}},
{Username: "charlie", Name: "Charlie Kim", Role: "engineer", Skills: []string{"go", "react", "postgres"}},
{Username: "diana", Name: "Diana Flores", Role: "manager", Skills: []string{"project-management", "scrum"}},
} {
req := m
team.Add(ctx, &req, &AddMemberResponse{})
}
// Seed projects
projects := &ProjectService{projects: make(map[string]*Project)}
projects.Create(ctx, &CreateProjectRequest{
Name: "Website Redesign",
Description: "Redesign the company website with new branding and improved UX",
}, &CreateProjectResponse{})
projects.projects["proj-1"].Status = "active"
projects.Create(ctx, &CreateProjectRequest{
Name: "API v2 Migration",
Description: "Migrate all services from REST to gRPC with backward compatibility",
}, &CreateProjectResponse{})
projects.projects["proj-2"].Status = "planning"
// Seed tasks
tasks := &TaskService{tasks: make(map[string]*Task)}
for _, t := range []CreateTaskRequest{
{ProjectID: "proj-1", Title: "Design new homepage layout", Assignee: "bob", Priority: "high"},
{ProjectID: "proj-1", Title: "Implement responsive nav component", Assignee: "charlie", Priority: "high"},
{ProjectID: "proj-1", Title: "Write copy for about page", Priority: "medium"},
{ProjectID: "proj-1", Title: "Set up CI/CD for new site", Assignee: "alice", Priority: "medium"},
{ProjectID: "proj-2", Title: "Audit existing REST endpoints", Assignee: "alice", Priority: "high"},
{ProjectID: "proj-2", Title: "Design gRPC proto files", Priority: "medium"},
{ProjectID: "proj-2", Title: "Write migration guide", Assignee: "diana", Priority: "low"},
} {
req := t
tasks.Create(ctx, &req, &CreateTaskResponse{})
}
// Mark a couple tasks as in_progress
tasks.tasks["task-1"].Status = "in_progress"
tasks.tasks["task-5"].Status = "in_progress"
// Register the seeded handlers (replace the empty ones registered above)
// Note: in a real app these would be separate services. Here we register
// pre-seeded instances so the demo starts with data.
srv.Handle(srv.NewHandler(projects))
srv.Handle(srv.NewHandler(tasks))
srv.Handle(srv.NewHandler(team))
}
+45
View File
@@ -0,0 +1,45 @@
# Durable agent run resume
This example shows the agent-side counterpart to `examples/flow-durable`: an
agent run is checkpointed with the same `Checkpoint` interface used by flows,
then resumed after an interruption without repeating a completed side effect.
The sample uses an in-memory store to keep repeated local runs deterministic;
use your service store for process-restart recovery.
Run it with:
```sh
go run ./examples/agent-durable
```
The demo model calls `inventory.reserve`, then fails to mimic a process dying
after the tool call was checkpointed. `micro.AgentPending` finds the unfinished
run and `micro.AgentResume` continues it from the saved checkpoint. The final
`tool executions: 1` line is the important bit: the reservation tool was not
called a second time during resume.
## When to use this instead of a durable flow
Use a durable flow when the path is known ahead of time: ordered service calls,
retries, timers, compensation, and a precise resume stage such as `reserve` or
`charge`. Use a checkpointed agent run when the path is open-ended and the model
may choose tools dynamically, but completed tool side effects still must not be
replayed after a crash or provider failure.
They compose: keep deterministic business process in `flow-durable`, then hand
off the judgment-heavy step to a checkpointed agent when the workflow needs
model-directed tool use. Both use the same `Checkpoint` backend, so inspection
and recovery can share one run-history store.
In a service, use the same pattern at startup:
```go
pending, _ := micro.AgentPending(ctx, agent)
for _, run := range pending {
_, _ = micro.AgentResume(ctx, agent, run.ID)
}
```
`context.Context` cancellation and deadlines are still honored by checkpoint
loads/saves, model calls, and tool calls. Runs with terminal statuses such as
`done`, `canceled`, and `expired` are not returned by `AgentPending`.
+88
View File
@@ -0,0 +1,88 @@
// Package main demonstrates durable agent runs: a checkpointed agent can
// resume after a crash without re-executing completed tool calls.
package main
import (
"context"
"errors"
"fmt"
"sync/atomic"
micro "go-micro.dev/v6"
"go-micro.dev/v6/ai"
"go-micro.dev/v6/store"
)
func main() {
ctx := context.Background()
checkpoint := micro.StoreCheckpoint(store.NewMemoryStore(), "durable-agent-demo")
model := &demoModel{failFirst: true}
ai.Register("durable-demo", func(opts ...ai.Option) ai.Model {
_ = model.Init(opts...)
return model
})
var reservations atomic.Int32
ag := micro.NewAgent("durable-agent-demo",
micro.AgentWithCheckpoint(checkpoint),
micro.AgentProvider("durable-demo"),
micro.AgentTool("inventory.reserve", "reserve inventory exactly once", map[string]any{
"sku": map[string]any{"type": "string"},
}, func(ctx context.Context, input map[string]any) (string, error) {
count := reservations.Add(1)
return fmt.Sprintf("reserved %s (execution %d)", input["sku"], count), nil
}),
)
_, err := ag.Ask(ctx, "reserve sku-123 and confirm")
fmt.Println("initial run:", err)
pending, err := micro.AgentPending(ctx, ag)
if err != nil {
panic(err)
}
if len(pending) == 0 {
panic("expected a checkpointed run to resume")
}
resp, err := micro.AgentResume(ctx, ag, pending[0].ID)
if err != nil {
panic(err)
}
fmt.Println("resumed reply:", resp.Reply)
fmt.Println("tool executions:", reservations.Load())
}
type demoModel struct {
failFirst bool
opts ai.Options
}
func (m *demoModel) Init(opts ...ai.Option) error {
m.opts = ai.NewOptions(opts...)
return nil
}
func (m *demoModel) Options() ai.Options { return m.opts }
func (m *demoModel) String() string { return "durable-demo" }
func (m *demoModel) Generate(ctx context.Context, req *ai.Request, opts ...ai.GenerateOption) (*ai.Response, error) {
if m.opts.ToolHandler != nil {
res := m.opts.ToolHandler(ctx, ai.ToolCall{
ID: "reserve-1",
Name: "inventory.reserve",
Input: map[string]any{"sku": "sku-123"},
})
if res.Content == "" {
return nil, errors.New("reservation tool returned no content")
}
}
if m.failFirst {
m.failFirst = false
return nil, errors.New("simulated process interruption after checkpointed tool call")
}
return &ai.Response{Reply: "sku-123 is reserved; no duplicate reservation was made"}, nil
}
func (m *demoModel) Stream(context.Context, *ai.Request, ...ai.GenerateOption) (ai.Stream, error) {
return nil, ai.ErrStreamingUnsupported
}
+48
View File
@@ -0,0 +1,48 @@
package main
import (
"bytes"
"io"
"os"
"strings"
"testing"
)
func TestDurableAgentExampleResumesWithoutReplayingTool(t *testing.T) {
out := captureStdout(t, main)
if !strings.Contains(out, "simulated process interruption after checkpointed tool call") {
t.Fatalf("example output %q did not show the initial interrupted run", out)
}
if !strings.Contains(out, "resumed reply: sku-123 is reserved; no duplicate reservation was made") {
t.Fatalf("example output %q did not show the resumed response", out)
}
if !strings.Contains(out, "tool executions: 1") {
t.Fatalf("example output %q did not prove the tool was not replayed", out)
}
}
func captureStdout(t *testing.T, fn func()) string {
t.Helper()
old := os.Stdout
r, w, err := os.Pipe()
if err != nil {
t.Fatalf("pipe stdout: %v", err)
}
os.Stdout = w
var buf bytes.Buffer
done := make(chan struct{})
go func() {
_, _ = io.Copy(&buf, r)
close(done)
}()
fn()
_ = w.Close()
os.Stdout = old
<-done
_ = r.Close()
return buf.String()
}
+44
View File
@@ -0,0 +1,44 @@
# Agent Human Input Pause/Resume
Agents can pause a durable run when the model needs a human decision before it
can continue. This keeps the services → agents → workflows lifecycle in one
runtime: services expose tools, the agent decides it needs operator input, and
the same checkpointed run resumes once that input arrives.
## Pattern
```go
cp := flow.StoreCheckpoint(nil, "deploy-agent")
ag := agent.New(
agent.Name("deploy-agent"),
agent.WithCheckpoint(cp),
)
resp, err := ag.Ask(ctx, "Deploy the service")
if err != nil {
// If the model called the built-in request_input tool, the run is saved as
// paused/input-required instead of losing state or completing early.
pending, _ := agent.Pending(ctx, ag)
runID := pending[0].ID
// Later, after an operator supplies the missing answer, the same run ID
// continues with the original prompt, human input, memory, and completed
// tool history intact.
resp, err = agent.ResumeInput(ctx, ag, runID, "Deploy to us-east-1")
}
_ = resp
```
The model sees a built-in `request_input` tool with a `prompt` argument. When it
calls that tool, Go Micro persists the run with status `paused` and stage
`input-required`. Plain `agent.Resume` continues to support completed, failed,
and approval-paused runs; input-required runs are resumed with
`agent.ResumeInput` so the human response is explicit.
## Cancellation and deadlines
`ResumeInput` uses the caller's `context.Context` for checkpoint reads, writes,
and the resumed model/tool turn. If the context is canceled or its deadline
expires before the resume is committed, the call returns the context error and
the checkpointed run remains `paused` at `input-required`; list it with
`agent.Pending` and retry with a fresh context once the operator is ready.
+246
View File
@@ -0,0 +1,246 @@
// Agent Ollama — a self-contained agent powered by Ollama Cloud.
//
// This example demonstrates the full harness loop — service tools, custom
// tools, agent memory, guardrails, and streaming — using the Ollama
// provider with gpt-oss:120b on Ollama Cloud.
//
// It creates a "knowledge" service with two endpoints (Add, Search) that
// the agent discovers as tools, plus a custom "current_time" tool. The
// agent answers natural-language questions by calling those tools.
//
// Run (Ollama Cloud — default):
//
// OLLAMA_API_KEY=your-key go run main.go
//
// Run (local Ollama):
//
// OLLAMA_BASE_URL=http://localhost:11434 \
// OLLAMA_MODEL=llama3.2 \
// go run main.go
package main
import (
"context"
"encoding/json"
"fmt"
"os"
"strings"
"sync"
"time"
"go-micro.dev/v6"
"go-micro.dev/v6/agent"
)
// ---------------------------------------------------------------------------
// knowledge service — a tiny in-memory knowledge base
// ---------------------------------------------------------------------------
type KnowledgeEntry struct {
ID string `json:"id" description:"Unique entry identifier"`
Topic string `json:"topic" description:"Topic or category"`
Content string `json:"content" description:"The knowledge content"`
}
type AddKnowledgeRequest struct {
Topic string `json:"topic" description:"Topic or category (required)"`
Content string `json:"content" description:"The knowledge content (required)"`
}
type AddKnowledgeResponse struct {
Entry *KnowledgeEntry `json:"entry" description:"The added entry"`
}
type SearchKnowledgeRequest struct {
Topic string `json:"topic,omitempty" description:"Filter by topic (optional)"`
Keyword string `json:"keyword,omitempty" description:"Search keyword in content (optional)"`
}
type SearchKnowledgeResponse struct {
Entries []*KnowledgeEntry `json:"entries" description:"Matching entries"`
}
type KnowledgeService struct {
mu sync.RWMutex
entries []*KnowledgeEntry
nextID int
}
// Add stores a new knowledge entry.
//
// @example {"topic": "go", "content": "Go interfaces are implicit."}
func (s *KnowledgeService) Add(ctx context.Context, req *AddKnowledgeRequest, rsp *AddKnowledgeResponse) error {
s.mu.Lock()
defer s.mu.Unlock()
s.nextID++
e := &KnowledgeEntry{
ID: fmt.Sprintf("kb-%d", s.nextID),
Topic: req.Topic,
Content: req.Content,
}
s.entries = append(s.entries, e)
rsp.Entry = e
return nil
}
// Search finds knowledge entries by topic or keyword.
//
// @example {"topic": "go"}
// @example {"keyword": "interface"}
func (s *KnowledgeService) Search(ctx context.Context, req *SearchKnowledgeRequest, rsp *SearchKnowledgeResponse) error {
s.mu.RLock()
defer s.mu.RUnlock()
for _, e := range s.entries {
if req.Topic != "" && !strings.EqualFold(e.Topic, req.Topic) {
continue
}
if req.Keyword != "" && !strings.Contains(strings.ToLower(e.Content), strings.ToLower(req.Keyword)) {
continue
}
rsp.Entries = append(rsp.Entries, e)
}
return nil
}
// ---------------------------------------------------------------------------
// main
// ---------------------------------------------------------------------------
func main() {
// Ollama Cloud is the default. Override with env vars for local Ollama.
baseURL := os.Getenv("OLLAMA_BASE_URL")
if baseURL == "" {
baseURL = "https://ollama.com/v1"
}
model := os.Getenv("OLLAMA_MODEL")
if model == "" {
model = "gpt-oss:120b"
}
apiKey := os.Getenv("OLLAMA_API_KEY")
fmt.Println("╔══════════════════════════════════════════╗")
fmt.Println("║ Ollama-Powered Go Micro Agent ║")
fmt.Println("╚══════════════════════════════════════════╝")
fmt.Println()
fmt.Printf(" Ollama URL: %s\n", baseURL)
fmt.Printf(" Model: %s\n", model)
if apiKey != "" {
fmt.Printf(" API Key: (set)\n")
} else {
fmt.Printf(" API Key: (none — set OLLAMA_API_KEY)\n")
}
fmt.Println()
// 1. Start the knowledge service. Its handlers become agent tools.
svc := micro.NewService("knowledge")
svc.Handle(new(KnowledgeService))
go svc.Run()
// Give the service a moment to register.
time.Sleep(2 * time.Second)
// 2. Create the agent. It discovers the knowledge service endpoints
// as tools automatically, plus gets a custom "current_time" tool.
ag := micro.NewAgent("ollama-assistant",
micro.AgentServices("knowledge"),
micro.AgentPrompt(
"You are a helpful knowledge assistant. You can search and add to "+
"a knowledge base using the knowledge service tools. "+
"When asked about the current time, use the current_time tool. "+
"Be concise and factual.",
),
micro.AgentProvider("ollama"),
micro.AgentModel(model),
micro.AgentAPIKey(apiKey),
micro.AgentBaseURL(baseURL),
micro.AgentMaxSteps(10),
micro.AgentLoopLimit(3),
// Custom tool — any function, not tied to a service.
agent.WithTool(
"current_time",
"Get the current date and time in a human-readable format",
map[string]any{
"timezone": map[string]any{
"type": "string",
"description": "Optional timezone (defaults to local)",
},
},
func(ctx context.Context, input map[string]any) (string, error) {
tz, _ := input["timezone"].(string)
if tz == "" {
return time.Now().Format("2006-01-02 15:04:05 MST"), nil
}
loc, err := time.LoadLocation(tz)
if err != nil {
return "", fmt.Errorf("unknown timezone: %s", tz)
}
return time.Now().In(loc).Format("2006-01-02 15:04:05 MST"), nil
},
),
)
// 3. Seed initial knowledge via the agent's first question.
questions := []string{
"What time is it now?",
"Add a new knowledge entry: topic 'go', content 'Go interfaces are implicit — a type implements an interface by having the required methods.'",
"Add another entry: topic 'go', content 'Go is a statically typed, compiled language designed at Google.'",
"Add another entry: topic 'ai', content 'Large language models generate text by predicting the next token in a sequence.'",
"Search the knowledge base for entries about Go.",
"Search for everything in the knowledge base.",
}
fmt.Println("─── Agent Demo ───")
fmt.Println()
for i, q := range questions {
fmt.Printf("Q%d: %s\n", i+1, q)
fmt.Print("A: ")
resp, err := ag.Ask(context.Background(), q)
if err != nil {
fmt.Printf("error: %v\n", err)
fmt.Println()
continue
}
// Show tool calls the agent made.
if len(resp.ToolCalls) > 0 {
for _, tc := range resp.ToolCalls {
args, _ := json.Marshal(tc.Input)
fmt.Printf(" [tool] %s(%s)\n", tc.Name, string(args))
}
}
fmt.Println(resp.Reply)
if resp.Reply == "" && len(resp.ToolCalls) == 0 {
fmt.Println("(no response)")
}
fmt.Println()
}
// 4. Streaming demonstration.
fmt.Println("─── Streaming Demo ───")
fmt.Println()
streamQ := "Explain what Go Micro is in two sentences."
fmt.Printf("Q: %s\n", streamQ)
fmt.Print("A: ")
stream, err := ag.Stream(context.Background(), streamQ)
if err != nil {
fmt.Printf("stream error: %v\n", err)
} else {
for {
chunk, err := stream.Recv()
if err != nil {
break
}
if chunk.Reply != "" {
fmt.Print(chunk.Reply)
}
}
fmt.Println()
}
fmt.Println()
fmt.Println("Done.")
}
+58
View File
@@ -0,0 +1,58 @@
# Agent Plan & Delegate
Demonstrates the two built-in agent capabilities — **planning** and **delegation** — in a small multi-agent system.
## What it shows
| Capability | Tool | What happens |
|------------|------|--------------|
| **Planning** | `plan` | The conductor records an ordered list of steps before doing multi-step work. The plan is saved to its store-backed memory and shown back to it on later turns. |
| **Delegation** | `delegate` | The conductor hands the notification step to a separate `comms` agent. Because `comms` is a registered agent, the hand-off goes over RPC — not an in-process call. |
Both `plan` and `delegate` are added to every agent automatically. There's no harness or graph to configure: they're plain tools the model calls, the same as any service endpoint.
## Layout
```
task (service) Add, List ← owned by conductor
notify (service) Send ← owned by comms
comms (agent) manages notify
conductor (agent) manages task, delegates notifications to comms
```
## Run
Set any provider key and run — the example auto-detects the provider:
```bash
export ANTHROPIC_API_KEY=sk-ant-... # or OPENAI_API_KEY, GEMINI_API_KEY, ...
go run main.go
```
(You can also force a provider with `MICRO_AI_PROVIDER` / `MICRO_AI_API_KEY`.)
The conductor is asked to *"Create three launch tasks: Design, Build, and Ship. Then make sure owner@acme.com is notified that the launch plan is ready."*
Expected shape of the run:
```
--- conductor tool calls ---
→ plan({"steps":[{"task":"create Design task","status":"pending"}, ...]})
→ task_TaskService_Add({"title":"Design"})
→ task_TaskService_Add({"title":"Build"})
→ task_TaskService_Add({"title":"Ship"})
→ delegate({"task":"Notify owner@acme.com that the launch plan is ready","to":"comms"})
📨 notify: to=owner@acme.com message="The launch plan is ready"
--- conductor reply ---
Created the three launch tasks and asked comms to notify owner@acme.com.
```
## Delegate-first
`delegate` is hybrid:
1. If `to` names a **registered agent** that owns the relevant services, the subtask is sent to it over RPC (`Agent.Chat`). That's what happens here — `comms` owns `notify`.
2. Otherwise a focused **ephemeral sub-agent** is created for the subtask with a fresh, isolated context, asked the task, and torn down. Ephemeral sub-agents have no built-in tools, so they can't re-delegate.
This keeps intelligence distributed: the conductor doesn't need to know how to send notifications — it knows *who does*.
+203
View File
@@ -0,0 +1,203 @@
// Agent Plan & Delegate — planning and multi-agent delegation
//
// This example shows the two built-in agent capabilities:
//
// - plan: the conductor records an ordered plan before doing
// multi-step work; the plan is saved to its memory.
// - delegate: the conductor hands the notification step to a
// separate "comms" agent over RPC, rather than doing it
// itself.
//
// Two services (task, notify), two agents (conductor, comms). The
// conductor manages task; comms manages notify. When asked to create
// tasks and notify someone, the conductor plans the work, creates the
// tasks with its own tools, then delegates the notification to comms —
// which is a real registered agent, so the hand-off goes over RPC.
//
// Run (needs an LLM provider key):
//
// MICRO_AI_PROVIDER=anthropic MICRO_AI_API_KEY=sk-ant-... go run main.go
package main
import (
"context"
"encoding/json"
"fmt"
"os"
"sync"
"time"
"go-micro.dev/v6"
)
// ---------------------------------------------------------------------------
// task service
// ---------------------------------------------------------------------------
type Task struct {
ID string `json:"id" description:"Unique task identifier"`
Title string `json:"title" description:"What the task is"`
}
type AddRequest struct {
Title string `json:"title" description:"Title of the task to add (required)"`
}
type AddResponse struct {
Task *Task `json:"task" description:"The created task"`
}
type ListRequest struct{}
type ListResponse struct {
Tasks []*Task `json:"tasks" description:"All tasks"`
}
type TaskService struct {
mu sync.Mutex
tasks []*Task
nextID int
}
// Add creates a new task with the given title.
//
// @example {"title": "Design the launch page"}
func (s *TaskService) Add(ctx context.Context, req *AddRequest, rsp *AddResponse) error {
s.mu.Lock()
defer s.mu.Unlock()
s.nextID++
t := &Task{ID: fmt.Sprintf("task-%d", s.nextID), Title: req.Title}
s.tasks = append(s.tasks, t)
rsp.Task = t
return nil
}
// List returns all tasks.
//
// @example {}
func (s *TaskService) List(ctx context.Context, req *ListRequest, rsp *ListResponse) error {
s.mu.Lock()
defer s.mu.Unlock()
rsp.Tasks = append(rsp.Tasks, s.tasks...)
return nil
}
// ---------------------------------------------------------------------------
// notify service
// ---------------------------------------------------------------------------
type SendRequest struct {
To string `json:"to" description:"Recipient address (required)"`
Message string `json:"message" description:"Message body (required)"`
}
type SendResponse struct {
Sent bool `json:"sent" description:"Whether the notification was sent"`
}
type NotifyService struct{}
// Send delivers a notification message to a recipient.
//
// @example {"to": "owner@acme.com", "message": "The launch plan is ready"}
func (s *NotifyService) Send(ctx context.Context, req *SendRequest, rsp *SendResponse) error {
fmt.Printf(" 📨 notify: to=%s message=%q\n", req.To, req.Message)
rsp.Sent = true
return nil
}
func main() {
provider, apiKey := detectProvider()
if apiKey == "" {
fmt.Println("No LLM key found. Set a provider key and run again, e.g.:")
fmt.Println(" export ANTHROPIC_API_KEY=sk-ant-... # or OPENAI_API_KEY, GEMINI_API_KEY, ...")
fmt.Println(" go run main.go")
return
}
fmt.Printf("Using provider %q\n", provider)
// Services.
task := micro.NewService("task")
task.Handle(new(TaskService))
go task.Run()
notify := micro.NewService("notify")
notify.Handle(new(NotifyService))
go notify.Run()
// comms is a real, registered agent that owns the notify service.
// Because it's registered, the conductor's delegate hand-off
// reaches it over RPC.
comms := micro.NewAgent("comms",
micro.AgentServices("notify"),
micro.AgentPrompt("You handle outbound notifications. Use the notify service to send messages."),
micro.AgentProvider(provider),
micro.AgentAPIKey(apiKey),
)
go comms.Run()
// The conductor owns task. Its prompt nudges it to plan, and to
// delegate notifications to the comms agent rather than doing them.
conductor := micro.NewAgent("conductor",
micro.AgentServices("task"),
micro.AgentPrompt(
"You coordinate launch work. For multi-step requests, first call the plan tool "+
"to record your steps, then carry them out. You can create tasks yourself. "+
"For anything to do with notifying people, delegate to the \"comms\" agent "+
"using the delegate tool (to: \"comms\") — do not try to notify directly.",
),
micro.AgentProvider(provider),
micro.AgentAPIKey(apiKey),
)
// Give the services and comms agent a moment to register.
time.Sleep(2 * time.Second)
resp, err := conductor.Ask(context.Background(),
"Create three launch tasks: Design, Build, and Ship. "+
"Then make sure owner@acme.com is notified that the launch plan is ready.")
if err != nil {
fmt.Println("error:", err)
os.Exit(1)
}
fmt.Println("\n--- conductor tool calls ---")
for _, tc := range resp.ToolCalls {
args, _ := json.Marshal(tc.Input)
fmt.Printf(" → %s(%s)\n", tc.Name, args)
}
fmt.Println("\n--- conductor reply ---")
fmt.Println(resp.Reply)
}
// detectProvider picks an LLM provider and key from the environment.
// MICRO_AI_PROVIDER / MICRO_AI_API_KEY win if set; otherwise it falls
// back to the first provider-specific key it finds (ANTHROPIC_API_KEY,
// OPENAI_API_KEY, ...), so `export ANTHROPIC_API_KEY=... && go run .`
// just works.
func detectProvider() (provider, apiKey string) {
provider = os.Getenv("MICRO_AI_PROVIDER")
apiKey = os.Getenv("MICRO_AI_API_KEY")
if apiKey != "" {
if provider == "" {
provider = "anthropic"
}
return provider, apiKey
}
// provider name -> its conventional API key env var
for _, p := range []struct{ name, env string }{
{"anthropic", "ANTHROPIC_API_KEY"},
{"openai", "OPENAI_API_KEY"},
{"gemini", "GEMINI_API_KEY"},
{"groq", "GROQ_API_KEY"},
{"mistral", "MISTRAL_API_KEY"},
{"together", "TOGETHER_API_KEY"},
{"atlascloud", "ATLASCLOUD_API_KEY"},
} {
if v := os.Getenv(p.env); v != "" {
return p.name, v
}
}
return "", ""
}
+63
View File
@@ -0,0 +1,63 @@
# Agent Tool Wrappers
Middleware around an agent's tool execution, the same way
`client.CallWrapper` and `server.HandlerWrapper` wrap RPCs.
Every tool call an agent makes runs through `ai.ToolHandler`:
```go
type ToolHandler func(ctx context.Context, call ai.ToolCall) ai.ToolResult
type ToolWrapper func(ai.ToolHandler) ai.ToolHandler
```
`WrapTool` (exposed as `micro.AgentWrapTool`) registers a wrapper: it
takes the next handler and returns a new one. Code before `next(...)`
runs before the tool, code after runs after. That single seam covers
the whole lifecycle — before/after hooks, timing, metrics, retries,
inspecting results.
## What this example does
One flaky `weather` service and one agent with two wrappers:
- **observe** — times every call and records a per-tool count, logging
the correlation ID (`call.ID`) carried through from the provider. It
observes; it changes nothing.
- **retry** — re-runs a call whose result is an error, up to three
attempts. The weather service fails the first time it's hit and
succeeds after, so retry turns a transient failure into a success the
model never sees.
Wrappers compose **outermost-first**: `observe` is registered first, so
it wraps `retry` and sees one logical call even when retry runs the tool
twice.
```go
micro.NewAgent("forecaster",
micro.AgentServices("weather"),
micro.AgentProvider(provider),
micro.AgentAPIKey(apiKey),
micro.AgentWrapTool(m.observe, retry(3)),
)
```
## Wrappers vs. guardrails
Developer wrappers run **outside** the built-in guardrails (`MaxSteps`,
`LoopLimit`, `ApproveTool`), so they see every call and its result —
including a guardrail's refusal. The flip side: a retry wrapper's
`next` is the full guardrail stack, so each retry is also counted by
loop detection. Keep `LoopLimit` at or above your retry count, or set
`AgentLoopLimit(0)` when a wrapper owns the repetition.
See the [Agent Guardrails guide](../../internal/website/docs/guides/agent-guardrails.md)
for the full picture.
## Run
Needs an LLM provider key:
```bash
export ANTHROPIC_API_KEY=sk-ant-... # or OPENAI_API_KEY, GEMINI_API_KEY, ...
go run main.go
```
+227
View File
@@ -0,0 +1,227 @@
// Agent Tool Wrappers — middleware around tool execution
//
// Every tool call an agent makes runs through ai.ToolHandler. WrapTool
// wraps that handler the same way client.CallWrapper and
// server.HandlerWrapper wrap RPCs: a wrapper takes the next handler and
// returns a new one, so code before the next(...) call runs before the
// tool and code after runs after.
//
// This example registers two wrappers on one agent:
//
// - observe: times every call and records a count per tool, keyed so
// you can correlate by call ID. Pure "lifecycle hook" — it observes,
// it doesn't change behavior.
// - retry: re-runs a call whose result comes back as an error, up to
// a few attempts. The "weather" service fails the first time it is
// hit and succeeds after, so the retry wrapper turns a transient
// failure into a success without the model ever seeing it.
//
// Wrappers compose outermost-first, so observe (registered first) wraps
// retry: it sees one logical call even though retry may run it twice.
//
// Run (needs an LLM provider key):
//
// MICRO_AI_PROVIDER=anthropic MICRO_AI_API_KEY=sk-ant-... go run main.go
package main
import (
"context"
"fmt"
"os"
"strings"
"sync"
"time"
"go-micro.dev/v6"
"go-micro.dev/v6/ai"
)
// ---------------------------------------------------------------------------
// weather service — flaky on purpose
// ---------------------------------------------------------------------------
type ForecastRequest struct {
City string `json:"city" description:"City to get the forecast for (required)"`
}
type ForecastResponse struct {
City string `json:"city" description:"The city"`
Forecast string `json:"forecast" description:"Human-readable forecast"`
}
type WeatherService struct {
mu sync.Mutex
calls int
}
// Forecast returns the weather forecast for a city. It fails on the very
// first call (to simulate a transient dependency error) and succeeds
// afterwards, so the retry wrapper has something to recover from.
//
// @example {"city": "London"}
func (s *WeatherService) Forecast(ctx context.Context, req *ForecastRequest, rsp *ForecastResponse) error {
s.mu.Lock()
s.calls++
n := s.calls
s.mu.Unlock()
if n == 1 {
return fmt.Errorf("weather upstream temporarily unavailable")
}
rsp.City = req.City
rsp.Forecast = "18°C and clear"
return nil
}
// ---------------------------------------------------------------------------
// wrappers
// ---------------------------------------------------------------------------
// metrics is collected by the observe wrapper. A real deployment would
// emit these to OpenTelemetry or Prometheus; here we just print them.
type metrics struct {
mu sync.Mutex
counts map[string]int
took map[string]time.Duration
}
func newMetrics() *metrics {
return &metrics{counts: map[string]int{}, took: map[string]time.Duration{}}
}
// observe times each tool call and records a per-tool count. It mirrors a
// service-side metrics wrapper: measure around next(...), record, return
// the result untouched.
func (m *metrics) observe(next ai.ToolHandler) ai.ToolHandler {
return func(ctx context.Context, call ai.ToolCall) ai.ToolResult {
start := time.Now()
res := next(ctx, call)
took := time.Since(start)
m.mu.Lock()
m.counts[call.Name]++
m.took[call.Name] += took
m.mu.Unlock()
fmt.Printf(" [observe] id=%s tool=%s took=%s\n", shortID(call.ID), call.Name, took.Round(time.Millisecond))
return res
}
}
// retry re-runs a call whose result comes back as an error, up to
// attempts times. Because it sits inside observe, the outer wrapper still
// sees one logical call even though retry may run next more than once.
//
// Developer wrappers run outside the built-in guardrails, so next here is
// the full guardrail stack: each retry is also seen by loop detection.
// Keep LoopLimit at or above your retry count (the default 3 covers the
// 2 attempts this example makes), or disable it with AgentLoopLimit(0)
// when a wrapper is responsible for repetition.
func retry(attempts int) ai.ToolWrapper {
return func(next ai.ToolHandler) ai.ToolHandler {
return func(ctx context.Context, call ai.ToolCall) ai.ToolResult {
var res ai.ToolResult
for i := 1; i <= attempts; i++ {
res = next(ctx, call)
if !isError(res) {
return res
}
if i < attempts {
fmt.Printf(" [retry] tool=%s attempt %d failed, retrying\n", call.Name, i)
}
}
return res
}
}
}
// isError reports whether a tool result is an error. The RPC handler
// encodes failures as a JSON object with an "error" field in Content.
func isError(res ai.ToolResult) bool {
return strings.Contains(res.Content, `"error"`)
}
func shortID(id string) string {
if len(id) > 8 {
return id[:8]
}
if id == "" {
return "-"
}
return id
}
func main() {
provider, apiKey := detectProvider()
if apiKey == "" {
fmt.Println("No LLM key found. Set a provider key and run again, e.g.:")
fmt.Println(" export ANTHROPIC_API_KEY=sk-ant-... # or OPENAI_API_KEY, GEMINI_API_KEY, ...")
fmt.Println(" go run main.go")
return
}
fmt.Printf("Using provider %q\n", provider)
weather := micro.NewService("weather")
weather.Handle(new(WeatherService))
go weather.Run()
m := newMetrics()
agent := micro.NewAgent("forecaster",
micro.AgentServices("weather"),
micro.AgentPrompt("You report the weather. Use the weather service to answer."),
micro.AgentProvider(provider),
micro.AgentAPIKey(apiKey),
// observe is registered first, so it is the outer wrapper and
// retry is the inner one.
micro.AgentWrapTool(m.observe, retry(3)),
)
// Give the service a moment to register.
time.Sleep(2 * time.Second)
resp, err := agent.Ask(context.Background(), "What's the weather in London?")
if err != nil {
fmt.Println("error:", err)
os.Exit(1)
}
fmt.Println("\n--- reply ---")
fmt.Println(resp.Reply)
fmt.Println("\n--- tool metrics ---")
m.mu.Lock()
for name, n := range m.counts {
fmt.Printf(" %s: %d call(s), total %s\n", name, n, m.took[name].Round(time.Millisecond))
}
m.mu.Unlock()
}
// detectProvider picks an LLM provider and key from the environment.
// MICRO_AI_PROVIDER / MICRO_AI_API_KEY win if set; otherwise it falls
// back to the first provider-specific key it finds.
func detectProvider() (provider, apiKey string) {
provider = os.Getenv("MICRO_AI_PROVIDER")
apiKey = os.Getenv("MICRO_AI_API_KEY")
if apiKey != "" {
if provider == "" {
provider = "anthropic"
}
return provider, apiKey
}
for _, p := range []struct{ name, env string }{
{"anthropic", "ANTHROPIC_API_KEY"},
{"openai", "OPENAI_API_KEY"},
{"gemini", "GEMINI_API_KEY"},
{"groq", "GROQ_API_KEY"},
{"mistral", "MISTRAL_API_KEY"},
{"together", "TOGETHER_API_KEY"},
{"atlascloud", "ATLASCLOUD_API_KEY"},
} {
if v := os.Getenv(p.env); v != "" {
return p.name, v
}
}
return "", ""
}
+24
View File
@@ -0,0 +1,24 @@
# Agent x402 buyer
This example shows an agent paying for a paid HTTP tool with x402 without using
live funds or a live chain.
It starts a local paid endpoint guarded by `wrapper/x402` seller middleware and a
mock facilitator. A deterministic mock-model agent calls that endpoint as a tool,
receives the HTTP 402 challenge, pays with `AgentPayer`, stays inside
`AgentBudget`, retries the request, and prints the spend recorded for the run.
```bash
go run ./examples/agent-x402-buyer
```
Expected output includes:
- the paid tool response,
- one facilitator verify and settle call, and
- `run spend: 7 smallest units (budget 10)`.
The payment token and facilitator are intentionally local development fakes. To
settle real x402 payments, keep the same `AgentPayer` / `AgentBudget` shape but
replace the payer with a wallet-backed implementation and configure the seller
middleware with a hosted or self-run x402 facilitator.
+168
View File
@@ -0,0 +1,168 @@
// Agent x402 buyer — a provider-free example of an agent paying for a paid tool.
//
// Run:
//
// go run ./examples/agent-x402-buyer
//
// It starts a local HTTP tool protected by x402 middleware, then asks a
// deterministic mock-model agent to call that tool. The agent receives the 402
// challenge, uses AgentPayer and AgentBudget to pay within a local mock
// facilitator, retries the request, and prints the run spend.
package main
import (
"context"
"encoding/json"
"fmt"
"io"
"net/http"
"net/http/httptest"
"os"
"strings"
go_micro "go-micro.dev/v6"
"go-micro.dev/v6/agent"
"go-micro.dev/v6/ai"
"go-micro.dev/v6/store"
"go-micro.dev/v6/wrapper/x402"
)
const (
paidToolName = "paid.market_brief"
price = int64(7)
paymentToken = "dev-payment-token"
)
type devFacilitator struct {
verifyCount int
settleCount int
}
func (f *devFacilitator) Verify(ctx context.Context, payment string, req x402.Requirements) (x402.Result, error) {
f.verifyCount++
if payment != paymentToken {
return x402.Result{Valid: false, Reason: "unknown dev payment token"}, nil
}
return x402.Result{Valid: true, Payer: "dev-agent-wallet"}, nil
}
func (f *devFacilitator) Settle(ctx context.Context, payment string, req x402.Requirements) (x402.Result, error) {
f.settleCount++
return x402.Result{Valid: true, Settlement: "dev-settlement-001"}, nil
}
type devPayer struct{}
func (devPayer) Pay(ctx context.Context, req x402.Requirements) (string, error) {
return paymentToken, nil
}
type mockModel struct{ opts ai.Options }
func newMock(opts ...ai.Option) ai.Model {
m := &mockModel{}
_ = m.Init(opts...)
return m
}
func (m *mockModel) Init(opts ...ai.Option) error {
for _, o := range opts {
o(&m.opts)
}
return nil
}
func (m *mockModel) Options() ai.Options { return m.opts }
func (m *mockModel) String() string { return "agent-x402-buyer-mock" }
func (m *mockModel) Stream(context.Context, *ai.Request, ...ai.GenerateOption) (ai.Stream, error) {
return nil, fmt.Errorf("stream not supported by agent-x402-buyer mock")
}
func (m *mockModel) Generate(ctx context.Context, req *ai.Request, _ ...ai.GenerateOption) (*ai.Response, error) {
for _, tool := range req.Tools {
if tool.Name == paidToolName && m.opts.ToolHandler != nil {
out := m.opts.ToolHandler(ctx, ai.ToolCall{ID: "paid-brief", Name: tool.Name, Input: map[string]any{"url": req.Prompt}})
return &ai.Response{Answer: fmt.Sprintf("Paid tool returned: %s", out.Content)}, nil
}
}
return &ai.Response{Answer: "No paid tool was available."}, nil
}
func paidToolServer(fac *devFacilitator) *httptest.Server {
mux := http.NewServeMux()
paid := x402.Middleware(x402.Config{
PayTo: "0xMerchantDevWallet",
Network: "base-sepolia",
Amount: fmt.Sprint(price),
Description: "Local market brief for the x402 buyer example",
Facilitator: fac,
})
mux.Handle("/brief", paid(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "application/json")
_ = json.NewEncoder(w).Encode(map[string]any{
"brief": "Mock demand is up 12% after the agent paid the local tool.",
"settlement": w.Header().Get(x402.PaymentResponseHeader),
})
})))
return httptest.NewServer(mux)
}
func run(w io.Writer) error {
ai.Register("agent-x402-buyer-mock", newMock)
fac := &devFacilitator{}
srv := paidToolServer(fac)
defer srv.Close()
st := store.NewMemoryStore()
buyer := agent.New(
agent.Name("x402-buyer"),
agent.Provider("agent-x402-buyer-mock"),
agent.Prompt("Call the paid market brief tool when given its URL."),
agent.WithStore(st),
go_micro.AgentPayer(devPayer{}),
go_micro.AgentBudget(10),
agent.WithTool(paidToolName, "Fetch a paid market brief over HTTP", map[string]any{
"url": map[string]any{"type": "string", "description": "Paid HTTP endpoint to call"},
}, func(ctx context.Context, input map[string]any) (string, error) {
url, _ := input["url"].(string)
resp, err := http.Get(url)
if err != nil {
return "", err
}
defer resp.Body.Close()
body, err := io.ReadAll(resp.Body)
if err != nil {
return "", err
}
return string(body), nil
}),
)
resp, err := buyer.Ask(context.Background(), srv.URL+"/brief")
if err != nil {
return err
}
events, err := agent.LoadRunEvents(st, "x402-buyer", resp.RunID)
if err != nil {
return err
}
var spent int64
for _, event := range events {
if event.Spent > spent {
spent = event.Spent
}
}
fmt.Fprintln(w, "Agent x402 buyer (provider: mock, funds: local dev token)")
fmt.Fprintln(w, strings.TrimSpace(resp.Reply))
fmt.Fprintf(w, "facilitator verify=%d settle=%d\n", fac.verifyCount, fac.settleCount)
fmt.Fprintf(w, "run spend: %d smallest units (budget 10)\n", spent)
return nil
}
func main() {
if err := run(os.Stdout); err != nil {
fmt.Println(err)
os.Exit(1)
}
}
+12
View File
@@ -0,0 +1,12 @@
# Compiled binaries
server/server
client/client
# Test binaries
*.test
# Output files
*.out
# Temporary files
*.tmp
+396
View File
@@ -0,0 +1,396 @@
# Auth Example
This example demonstrates how to use the auth wrappers to protect your microservices with authentication and authorization.
## Overview
The example includes:
- **Server** - A Greeter service with:
- Protected endpoint: `Greeter.Hello` (requires auth)
- Public endpoint: `Greeter.Health` (no auth required)
- **Client** - Makes calls to the server:
- With authentication (successful)
- Without authentication (fails as expected)
## Architecture
```
┌─────────────────────────────────────────┐
│ Client │
│ ┌────────────────────────────────┐ │
│ │ AuthClient Wrapper │ │
│ │ - Adds Bearer token │ │
│ │ - To all requests │ │
│ └────────────────────────────────┘ │
└──────────────┬──────────────────────────┘
│ RPC with Authorization: Bearer <token>
┌─────────────────────────────────────────┐
│ Server │
│ ┌────────────────────────────────┐ │
│ │ AuthHandler Wrapper │ │
│ │ - Extracts token │ │
│ │ - Verifies with auth.Inspect()│ │
│ │ - Checks with rules.Verify() │ │
│ │ - Returns 401/403 if denied │ │
│ └────────────────────────────────┘ │
│ │ │
│ ▼ │
│ ┌────────────────────────────────┐ │
│ │ Handler (Greeter.Hello) │ │
│ │ - Gets account from context │ │
│ │ - Processes request │ │
│ └────────────────────────────────┘ │
└─────────────────────────────────────────┘
```
## Files
```
examples/auth/
├── README.md # This file
├── proto/
│ ├── greeter.proto # Service definition
│ └── greeter.pb.go # Generated Go code
├── server/
│ └── main.go # Protected service
└── client/
└── main.go # Client with auth
```
## Running the Example
### 1. Start the Server
```bash
cd server
go run main.go
```
The server will:
- Start the Greeter service
- Apply auth wrapper to protect endpoints
- Generate a test token and print it
Output:
```
=== Test Token Generated ===
Use this token to test the client:
TOKEN=eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9... go run client/main.go
2026/02/11 10:00:00 Server [greeter] Listening on [::]:54321
```
### 2. Run the Client (With Auth)
In a new terminal:
```bash
cd client
TOKEN=<token-from-server> go run main.go
```
Output:
```
=== Test 1: Protected endpoint WITH auth ===
Response: Hello, test-user!
=== Test 2: Public endpoint (no auth needed) ===
Health Status: ok
=== Test 3: Protected endpoint WITHOUT auth (should fail) ===
Expected error: {"id":"greeter","code":401,"detail":"missing authorization token","status":"Unauthorized"}
```
### 3. Run the Client (Without Auth)
```bash
cd client
go run main.go
```
This will auto-generate a token for testing.
## Code Walkthrough
### Server Setup
```go
// 1. Create auth provider
// For this example we use the noop auth (accepts all tokens)
// In production, use JWT or a custom auth provider
authProvider := noop.NewAuth()
// 2. Create authorization rules
rules := auth.NewRules()
rules.Grant(&auth.Rule{
ID: "public-health",
Scope: "",
Resource: &auth.Resource{Endpoint: "Greeter.Health"},
Access: auth.AccessGranted,
})
// 3. Wrap service with auth handler
service := micro.NewService(
micro.Name("greeter"),
micro.WrapHandler(
authWrapper.AuthHandler(authWrapper.HandlerOptions{
Auth: authProvider,
Rules: rules,
SkipEndpoints: []string{"Greeter.Health"},
}),
),
)
```
### Client Setup
```go
// 1. Get or generate token
token := os.Getenv("TOKEN")
// 2. Wrap client with auth
service := micro.NewService(
micro.Name("greeter.client"),
micro.WrapClient(
authWrapper.FromToken(token),
),
)
// 3. Make calls (token automatically added)
greeterClient := pb.NewGreeterService("greeter", service.Client())
rsp, err := greeterClient.Hello(ctx, &pb.Request{Name: "John"})
```
### Handler Implementation
```go
func (g *Greeter) Hello(ctx context.Context, req *pb.Request, rsp *pb.Response) error {
// Get account from context (added by auth wrapper)
acc, ok := auth.AccountFromContext(ctx)
if !ok {
return errors.Unauthorized("greeter", "authentication required")
}
rsp.Msg = "Hello, " + acc.ID + "!"
return nil
}
```
## Auth Wrapper Features
### Server Wrapper (`AuthHandler`)
- **Token Extraction**: Reads `Authorization: Bearer <token>` from metadata
- **Token Verification**: Validates token using `auth.Inspect()`
- **Authorization**: Checks permissions using `rules.Verify()`
- **Context Injection**: Adds account to context for handlers
- **Error Handling**: Returns 401/403 with clear error messages
- **Skip Endpoints**: Allows public endpoints without auth
### Client Wrapper (`AuthClient`)
- **Automatic Token Injection**: Adds Bearer token to all requests
- **Context-Aware**: Can extract account from context
- **Static Token**: Use `FromToken()` for pre-generated tokens
- **Dynamic Token**: Use `FromContext()` to generate per-request
## Auth Strategies
### 1. All Endpoints Protected
```go
micro.WrapHandler(
authWrapper.AuthRequired(authProvider, rules),
)
```
### 2. Some Public Endpoints
```go
micro.WrapHandler(
authWrapper.PublicEndpoints(authProvider, rules, []string{
"Health.Check",
"Status.Version",
}),
)
```
### 3. Optional Auth (Extract but Don't Enforce)
```go
micro.WrapHandler(
authWrapper.AuthOptional(authProvider),
)
```
## Authorization Rules
### Grant Public Access
```go
rules.Grant(&auth.Rule{
ID: "public",
Scope: "", // No scope = public
Resource: &auth.Resource{Endpoint: "Health.Check"},
Access: auth.AccessGranted,
})
```
### Require Authentication
```go
rules.Grant(&auth.Rule{
ID: "authenticated",
Scope: "*", // Any authenticated user
Resource: &auth.Resource{Endpoint: "*"},
Access: auth.AccessGranted,
})
```
### Require Specific Scope
```go
rules.Grant(&auth.Rule{
ID: "admin-only",
Scope: "admin", // Only admin scope
Resource: &auth.Resource{Endpoint: "Admin.*"},
Access: auth.AccessGranted,
})
```
### Deny Access
```go
rules.Grant(&auth.Rule{
ID: "deny-delete",
Scope: "*",
Resource: &auth.Resource{Endpoint: "User.Delete"},
Access: auth.AccessDenied,
Priority: 100, // Higher priority = evaluated first
})
```
## Testing Without Server
You can test auth logic without a running server:
```go
import "go-micro.dev/v5/auth/noop"
// Create auth provider (noop for testing)
authProvider := noop.NewAuth()
// Generate account
acc, _ := authProvider.Generate("test-user", auth.WithScopes("admin"))
// Generate token
token, _ := authProvider.Token(auth.WithCredentials(acc.ID, acc.Secret))
// Verify token
verified, _ := authProvider.Inspect(token.AccessToken)
fmt.Println(verified.ID) // Returns a generated UUID
```
## Production Considerations
### 1. Use Production Auth Provider
The noop auth provider (`auth.NewAuth()`) is for development only. It accepts any token.
For production, implement a proper auth provider or use the JWT implementation:
```go
// Option 1: Implement custom auth.Auth interface
type MyAuth struct {
// Your implementation
}
func (m *MyAuth) Generate(id string, opts ...auth.GenerateOption) (*auth.Account, error) {
// Generate real accounts
}
func (m *MyAuth) Inspect(token string) (*auth.Account, error) {
// Verify real tokens (JWT, OAuth, etc.)
}
// Option 2: Use JWT auth (requires jwt package implementation)
// Note: The jwt package in auth/jwt depends on an external plugin
// You may need to implement your own JWT auth or use a third-party library
```
### 3. Add Gateway Auth
If using HTTP gateway:
```go
// Add auth to HTTP gateway
http.Handle("/", gateway.Handler(
gateway.WithAuth(authProvider),
))
```
### 4. Service-to-Service Auth
Services calling other services:
```go
// Service A calls Service B with its own token
client := micro.NewService(
micro.WrapClient(
authWrapper.FromContext(authProvider),
),
)
```
### 5. Token Refresh
```go
// Check if token is expiring
if time.Until(token.Expiry) < 5*time.Minute {
token, _ = authProvider.Token(auth.WithToken(token.RefreshToken))
}
```
## Troubleshooting
### Error: "missing authorization token"
- **Cause**: Client didn't send Authorization header
- **Fix**: Wrap client with `authWrapper.FromToken(token)`
### Error: "invalid token"
- **Cause**: Token is expired or malformed
- **Fix**: Generate a new token
### Error: "access denied"
- **Cause**: Account doesn't have required permissions
- **Fix**: Check authorization rules with `rules.List()`
### Error: "token verification failed"
- **Cause**: Server can't verify token (wrong keys, expired, etc.)
- **Fix**: Ensure server and client use same auth provider
## Next Steps
- Read the [Auth Documentation](/docs/auth)
- Explore [JWT Auth](/auth/jwt)
- Try [Custom Auth Provider](/examples/auth/custom)
- See [Multi-Tenant Auth](/examples/auth/multi-tenant)
## Summary
The auth wrappers make it easy to:
1. **Protect services**: Add `WrapHandler(AuthHandler(...))`
2. **Add authentication to clients**: Add `WrapClient(FromToken(...))`
3. **Control access**: Define rules with `rules.Grant()`
4. **Access account info**: Use `auth.AccountFromContext(ctx)`
That's it! Your microservices now have enterprise-grade authentication and authorization.
+83
View File
@@ -0,0 +1,83 @@
package main
import (
"context"
"fmt"
"log"
"os"
"go-micro.dev/v6"
"go-micro.dev/v6/auth"
"go-micro.dev/v6/auth/noop"
"go-micro.dev/v6/client"
authWrapper "go-micro.dev/v6/wrapper/auth"
pb "go-micro.dev/v6/examples/auth/proto"
)
func main() {
// Get token from environment or generate one
token := os.Getenv("TOKEN")
// Create auth provider (same as server)
authProvider := noop.NewAuth()
// If no token provided, generate one
if token == "" {
log.Println("No TOKEN env var provided, generating test token...")
acc, err := authProvider.Generate("test-user")
if err != nil {
log.Fatal(err)
}
t, err := authProvider.Token(auth.WithCredentials(acc.ID, acc.Secret))
if err != nil {
log.Fatal(err)
}
token = t.AccessToken
log.Printf("Generated token: %s\n", token)
}
// Create service with auth client wrapper
service := micro.NewService("greeter.client", micro.WrapClient(
authWrapper.FromToken(token), // Add token to all requests
),
)
service.Init()
// Create greeter client
greeterClient := pb.NewGreeterService("greeter", service.Client())
// Test 1: Call protected endpoint (Hello) with auth
fmt.Println("\n=== Test 1: Protected endpoint WITH auth ===")
rsp, err := greeterClient.Hello(context.Background(), &pb.Request{Name: "John"})
if err != nil {
log.Printf("Error: %v", err)
} else {
fmt.Printf("Response: %s\n", rsp.Msg)
}
// Test 2: Call public endpoint (Health) without auth
fmt.Println("\n=== Test 2: Public endpoint (no auth needed) ===")
// Create client without auth wrapper for this test
plainClient := client.NewClient()
plainGreeterClient := pb.NewGreeterService("greeter", plainClient)
healthRsp, err := plainGreeterClient.Health(context.Background(), &pb.HealthRequest{})
if err != nil {
log.Printf("Error: %v", err)
} else {
fmt.Printf("Health Status: %s\n", healthRsp.Status)
}
// Test 3: Call protected endpoint WITHOUT auth (should fail)
fmt.Println("\n=== Test 3: Protected endpoint WITHOUT auth (should fail) ===")
_, err = plainGreeterClient.Hello(context.Background(), &pb.Request{Name: "John"})
if err != nil {
fmt.Printf("Expected error: %v\n", err)
} else {
fmt.Println("Unexpected: Call succeeded without auth!")
}
}
+144
View File
@@ -0,0 +1,144 @@
// Code generated by protoc-gen-go. DO NOT EDIT.
// source: greeter.proto
package greeter
import (
context "context"
fmt "fmt"
client "go-micro.dev/v6/client"
server "go-micro.dev/v6/server"
)
// Reference imports to suppress errors if they are not otherwise used.
var _ = fmt.Errorf
type Request struct {
Name string `protobuf:"bytes,1,opt,name=name,proto3" json:"name,omitempty"`
}
func (m *Request) Reset() { *m = Request{} }
func (m *Request) String() string { return fmt.Sprintf("Request{Name:%s}", m.Name) }
func (*Request) ProtoMessage() {}
func (m *Request) GetName() string {
if m != nil {
return m.Name
}
return ""
}
type Response struct {
Msg string `protobuf:"bytes,1,opt,name=msg,proto3" json:"msg,omitempty"`
}
func (m *Response) Reset() { *m = Response{} }
func (m *Response) String() string { return fmt.Sprintf("Response{Msg:%s}", m.Msg) }
func (*Response) ProtoMessage() {}
func (m *Response) GetMsg() string {
if m != nil {
return m.Msg
}
return ""
}
type HealthRequest struct{}
func (m *HealthRequest) Reset() { *m = HealthRequest{} }
func (m *HealthRequest) String() string { return "HealthRequest{}" }
func (*HealthRequest) ProtoMessage() {}
type HealthResponse struct {
Status string `protobuf:"bytes,1,opt,name=status,proto3" json:"status,omitempty"`
}
func (m *HealthResponse) Reset() { *m = HealthResponse{} }
func (m *HealthResponse) String() string { return fmt.Sprintf("HealthResponse{Status:%s}", m.Status) }
func (*HealthResponse) ProtoMessage() {}
func (m *HealthResponse) GetStatus() string {
if m != nil {
return m.Status
}
return ""
}
func init() {
// Types registered
}
// Reference imports to suppress errors if they are not otherwise used.
var _ context.Context
var _ client.Option
var _ server.Option
// Client API for Greeter service
type GreeterService interface {
Hello(ctx context.Context, in *Request, opts ...client.CallOption) (*Response, error)
Health(ctx context.Context, in *HealthRequest, opts ...client.CallOption) (*HealthResponse, error)
}
type greeterService struct {
c client.Client
name string
}
func NewGreeterService(name string, c client.Client) GreeterService {
return &greeterService{
c: c,
name: name,
}
}
func (c *greeterService) Hello(ctx context.Context, in *Request, opts ...client.CallOption) (*Response, error) {
req := c.c.NewRequest(c.name, "Greeter.Hello", in)
out := new(Response)
err := c.c.Call(ctx, req, out, opts...)
if err != nil {
return nil, err
}
return out, nil
}
func (c *greeterService) Health(ctx context.Context, in *HealthRequest, opts ...client.CallOption) (*HealthResponse, error) {
req := c.c.NewRequest(c.name, "Greeter.Health", in)
out := new(HealthResponse)
err := c.c.Call(ctx, req, out, opts...)
if err != nil {
return nil, err
}
return out, nil
}
// Server API for Greeter service
type GreeterHandler interface {
Hello(context.Context, *Request, *Response) error
Health(context.Context, *HealthRequest, *HealthResponse) error
}
func RegisterGreeterHandler(s server.Server, hdlr GreeterHandler, opts ...server.HandlerOption) error {
type greeter interface {
Hello(ctx context.Context, in *Request, out *Response) error
Health(ctx context.Context, in *HealthRequest, out *HealthResponse) error
}
type Greeter struct {
greeter
}
h := &greeterHandler{hdlr}
return s.Handle(s.NewHandler(&Greeter{h}, opts...))
}
type greeterHandler struct {
GreeterHandler
}
func (h *greeterHandler) Hello(ctx context.Context, in *Request, out *Response) error {
return h.GreeterHandler.Hello(ctx, in, out)
}
func (h *greeterHandler) Health(ctx context.Context, in *HealthRequest, out *HealthResponse) error {
return h.GreeterHandler.Health(ctx, in, out)
}
+24
View File
@@ -0,0 +1,24 @@
syntax = "proto3";
package greeter;
option go_package = "go-micro.dev/v5/examples/auth/proto;greeter";
service Greeter {
rpc Hello(Request) returns (Response) {}
rpc Health(HealthRequest) returns (HealthResponse) {}
}
message Request {
string name = 1;
}
message Response {
string msg = 1;
}
message HealthRequest {}
message HealthResponse {
string status = 1;
}
+94
View File
@@ -0,0 +1,94 @@
package main
import (
"context"
"log"
"go-micro.dev/v6"
"go-micro.dev/v6/auth"
"go-micro.dev/v6/auth/noop"
authWrapper "go-micro.dev/v6/wrapper/auth"
pb "go-micro.dev/v6/examples/auth/proto"
)
// Greeter implements the Greeter service
type Greeter struct{}
// Hello is a protected endpoint that requires authentication
func (g *Greeter) Hello(ctx context.Context, req *pb.Request, rsp *pb.Response) error {
// Get account from context (added by auth wrapper)
acc, ok := auth.AccountFromContext(ctx)
if !ok {
rsp.Msg = "Hello, anonymous!"
return nil
}
rsp.Msg = "Hello, " + acc.ID + "!"
return nil
}
// Health is a public endpoint that doesn't require auth
func (g *Greeter) Health(ctx context.Context, req *pb.HealthRequest, rsp *pb.HealthResponse) error {
rsp.Status = "ok"
return nil
}
func main() {
// Create auth provider (noop for this example)
// In production, use JWT or custom auth provider
authProvider := noop.NewAuth()
// Create authorization rules
rules := auth.NewRules()
// Grant public access to health endpoint
rules.Grant(&auth.Rule{
ID: "public-health",
Scope: "",
Resource: &auth.Resource{Type: "service", Name: "*", Endpoint: "Greeter.Health"},
Access: auth.AccessGranted,
Priority: 100,
})
// Require authentication for other endpoints
rules.Grant(&auth.Rule{
ID: "authenticated-hello",
Scope: "*",
Resource: &auth.Resource{Type: "service", Name: "*", Endpoint: "*"},
Access: auth.AccessGranted,
Priority: 50,
})
// Create service with auth wrapper
service := micro.NewService("greeter", micro.Version("latest"),
micro.WrapHandler(
authWrapper.AuthHandler(authWrapper.HandlerOptions{
Auth: authProvider,
Rules: rules,
SkipEndpoints: []string{"Greeter.Health"}, // Public endpoints
}),
),
)
service.Init()
// Register handler
if err := pb.RegisterGreeterHandler(service.Server(), &Greeter{}); err != nil {
log.Fatal(err)
}
// Generate a test token for demonstration
if acc, err := authProvider.Generate("test-user"); err == nil {
if token, err := authProvider.Token(auth.WithCredentials(acc.ID, acc.Secret)); err == nil {
log.Printf("\n=== Test Token Generated ===")
log.Printf("Use this token to test the client:")
log.Printf("TOKEN=%s go run client/main.go\n", token.AccessToken)
}
}
// Run service
if err := service.Run(); err != nil {
log.Fatal(err)
}
}
+13
View File
@@ -0,0 +1,13 @@
# Multi-stage build for a go-micro service
FROM golang:1.22-alpine AS builder
WORKDIR /app
COPY go.mod go.sum ./
RUN go mod download
COPY . .
RUN CGO_ENABLED=0 go build -o /service .
FROM alpine:3.19
RUN apk --no-cache add ca-certificates
COPY --from=builder /service /service
ENTRYPOINT ["/service"]
+13
View File
@@ -0,0 +1,13 @@
# Standalone MCP gateway
FROM golang:1.22-alpine AS builder
WORKDIR /app
COPY go.mod go.sum ./
RUN go mod download
COPY . .
RUN CGO_ENABLED=0 go build -o /gateway ./cmd/gateway
FROM alpine:3.19
RUN apk --no-cache add ca-certificates
COPY --from=builder /gateway /gateway
ENTRYPOINT ["/gateway"]
+116
View File
@@ -0,0 +1,116 @@
# Docker Compose Deployment Example
Run a go-micro service with MCP gateway, service registry, and distributed tracing in one command.
## Architecture
```
┌─────────┐ discover ┌──────────┐ RPC ┌─────────┐
│ Agent │ ─────────────→ │ MCP │ ──────────→ │ Your │
│ (Claude) │ MCP :3001 │ Gateway │ │ Service │
└─────────┘ └──────────┘ └─────────┘
│ │
▼ ▼
┌──────────┐ ┌──────────┐
│ Consul │ │ Jaeger │
│ Registry │ │ Tracing │
│ :8500 │ │ :16686 │
└──────────┘ └──────────┘
```
## Quick Start
```bash
docker-compose up
```
## Endpoints
| Service | URL |
|---------|-----|
| MCP Tools | http://localhost:3001/mcp/tools |
| Consul UI | http://localhost:8500 |
| Jaeger UI | http://localhost:16686 |
| Service RPC | http://localhost:9090 |
## Test
```bash
# List MCP tools
curl http://localhost:3001/mcp/tools | jq
# Call a tool
curl -X POST http://localhost:3001/mcp/call \
-H 'Content-Type: application/json' \
-d '{"tool": "myservice.Handler.Method", "arguments": {"key": "value"}}'
# View traces in Jaeger
open http://localhost:16686
```
## Connect Claude Code
```bash
# Claude Code can connect to the running MCP gateway
# Add to your Claude Code MCP settings:
```
```json
{
"mcpServers": {
"my-services": {
"url": "http://localhost:3001/mcp"
}
}
}
```
## Customizing
### Add Your Service
Replace the `app` service's build context with your service directory:
```yaml
app:
build:
context: ../path/to/your/service
dockerfile: Dockerfile
```
### Add More Services
```yaml
users:
build: ./users
environment:
MICRO_REGISTRY: consul
MICRO_REGISTRY_ADDRESS: consul:8500
orders:
build: ./orders
environment:
MICRO_REGISTRY: consul
MICRO_REGISTRY_ADDRESS: consul:8500
```
All services register with Consul. The MCP gateway discovers them automatically.
### Add Redis Cache
```yaml
redis:
image: redis:7-alpine
ports:
- "6379:6379"
```
Then set `MICRO_CACHE_ADDRESS=redis:6379` on your service.
### Production Considerations
- Add health checks to each service
- Use named volumes for Consul data persistence
- Configure rate limiting on the MCP gateway
- Set up TLS between services
- Use secrets management for API keys
+65
View File
@@ -0,0 +1,65 @@
# Go Micro + MCP Gateway deployment with Docker Compose
#
# This runs:
# 1. Consul — service registry (discovery)
# 2. App — your go-micro service(s)
# 3. MCP Gateway — standalone MCP gateway connected to Consul
# 4. Jaeger — distributed tracing UI
#
# Usage:
# docker-compose up
#
# Endpoints:
# MCP Tools: http://localhost:3001/mcp/tools
# Consul UI: http://localhost:8500
# Jaeger UI: http://localhost:16686
# Service: http://localhost:9090 (RPC)
services:
# --- Service Registry ---
consul:
image: consul:1.15
ports:
- "8500:8500"
command: agent -server -bootstrap-expect=1 -ui -client=0.0.0.0
# --- Your Go Micro Service ---
app:
build:
context: .
dockerfile: Dockerfile
ports:
- "9090:9090"
environment:
MICRO_REGISTRY: consul
MICRO_REGISTRY_ADDRESS: consul:8500
MICRO_SERVER_ADDRESS: :9090
depends_on:
- consul
restart: unless-stopped
# --- MCP Gateway (standalone) ---
mcp-gateway:
build:
context: .
dockerfile: Dockerfile.gateway
ports:
- "3001:3001"
environment:
MICRO_REGISTRY: consul
MICRO_REGISTRY_ADDRESS: consul:8500
MCP_ADDRESS: :3001
OTEL_EXPORTER_OTLP_ENDPOINT: http://jaeger:4318
depends_on:
- consul
- app
restart: unless-stopped
# --- Tracing ---
jaeger:
image: jaegertracing/all-in-one:1.53
ports:
- "16686:16686" # UI
- "4318:4318" # OTLP HTTP
environment:
COLLECTOR_OTLP_ENABLED: "true"
+55
View File
@@ -0,0 +1,55 @@
# First Agent
This is the smallest runnable service-backed agent in the repository. It sits
between `micro new helloworld` and the full [`examples/support`](../support/)
0→hero reference.
It runs with a deterministic mock model, so you do not need `ANTHROPIC_API_KEY`,
`OPENAI_API_KEY`, or any other provider secret.
```bash
go run ./examples/first-agent
```
Expected transcript:
```text
First agent (provider: mock, no API key)
> Summarize my next steps
[notes] listed starter notes
assistant: Your first agent read the notes service and found three steps: install the CLI, run a service, then chat with an agent.
✓ service-backed agent completed without provider secrets
```
## What it demonstrates
- `notes` is a normal Go Micro service with one RPC method.
- `assistant` is an agent scoped to that service via `agent.Services("notes")`.
- The mock model requests the service tool through the normal agent tool handler.
- The final answer proves the service → agent path without a live model key.
CI keeps this path runnable with:
```bash
go test ./examples/first-agent
```
## Next chat, inspect, and debug breadcrumbs
This example exits after one in-process `assistant.Ask` call so it stays tiny and
provider-free. When you move from this transcript to a long-running agent, keep
these commands nearby:
```bash
micro run
micro chat assistant --prompt "Summarize my next steps"
micro inspect agent assistant
micro agent doctor assistant
```
Use the [no-secret first-agent guide](../../internal/website/docs/guides/no-secret-first-agent.md)
to compare this transcript with the CLI demo, then keep the
[debugging guide](../../internal/website/docs/guides/debugging-agents.md) open for
preflight, doctor, inspect, and history checks. After that, continue to
[`examples/support`](../support/) for the full services → agents → workflows
lifecycle with a flow trigger and an approval gate.
+161
View File
@@ -0,0 +1,161 @@
// First Agent — the smallest runnable service-backed agent.
//
// Run:
//
// go run ./examples/first-agent
//
// It uses a deterministic mock model, so it needs no provider API key. The
// point is to show the first agent shape: a service exposes a tool, an agent
// discovers that service, the model asks to call the tool, and the agent returns
// a final answer.
package main
import (
"context"
"fmt"
"io"
"os"
"strings"
"time"
"go-micro.dev/v6/agent"
"go-micro.dev/v6/ai"
"go-micro.dev/v6/broker"
"go-micro.dev/v6/client"
"go-micro.dev/v6/registry"
"go-micro.dev/v6/selector"
"go-micro.dev/v6/service"
"go-micro.dev/v6/store"
)
type ListNotesRequest struct{}
type ListNotesResponse struct {
Notes []string `json:"notes" description:"Notes the assistant can summarize"`
}
type NotesService struct{ w io.Writer }
// List returns the starter notes the first agent can read.
// @example {}
func (s *NotesService) List(ctx context.Context, req *ListNotesRequest, rsp *ListNotesResponse) error {
rsp.Notes = []string{"Install the micro CLI", "Run a service", "Chat with an agent"}
fmt.Fprintln(s.w, " [notes] listed starter notes")
return nil
}
type mockModel struct{ opts ai.Options }
func newMock(opts ...ai.Option) ai.Model {
m := &mockModel{}
_ = m.Init(opts...)
return m
}
func (m *mockModel) Init(opts ...ai.Option) error {
for _, o := range opts {
o(&m.opts)
}
return nil
}
func (m *mockModel) Options() ai.Options { return m.opts }
func (m *mockModel) String() string { return "first-agent-mock" }
func (m *mockModel) Stream(context.Context, *ai.Request, ...ai.GenerateOption) (ai.Stream, error) {
return nil, fmt.Errorf("stream not supported by first-agent mock")
}
func (m *mockModel) Generate(ctx context.Context, req *ai.Request, _ ...ai.GenerateOption) (*ai.Response, error) {
for _, tool := range req.Tools {
if strings.Contains(tool.Name, "List") && m.opts.ToolHandler != nil {
m.opts.ToolHandler(ctx, ai.ToolCall{ID: "list-notes", Name: tool.Name, Input: map[string]any{}})
break
}
}
return &ai.Response{Answer: "Your first agent read the notes service and found three steps: install the CLI, run a service, then chat with an agent."}, nil
}
func waitFor(reg registry.Registry, names ...string) error {
deadline := time.Now().Add(5 * time.Second)
for _, name := range names {
for {
if svcs, err := reg.GetService(name); err == nil && len(svcs) > 0 && len(svcs[0].Nodes) > 0 {
break
}
if time.Now().After(deadline) {
return fmt.Errorf("timed out waiting for %s", name)
}
time.Sleep(20 * time.Millisecond)
}
}
return nil
}
func runFirstAgent() error {
return runFirstAgentWithWriter(os.Stdout)
}
func runFirstAgentWithWriter(w io.Writer) error {
ai.Register("first-agent-mock", newMock)
reg := registry.NewMemoryRegistry()
br := broker.NewMemoryBroker()
if err := br.Init(); err != nil {
return fmt.Errorf("init broker: %w", err)
}
if err := br.Connect(); err != nil {
return fmt.Errorf("connect broker: %w", err)
}
defer br.Disconnect()
cl := client.NewClient(client.Registry(reg), client.Selector(selector.NewSelector(selector.Registry(reg))), client.Broker(br))
notes := service.New(service.Name("notes"), service.Address("127.0.0.1:0"), service.Registry(reg), service.Client(cl), service.Broker(br), service.HandleSignal(false))
if err := notes.Handle(&NotesService{w: w}); err != nil {
return fmt.Errorf("handle notes: %w", err)
}
svcErr := make(chan error, 1)
go func() { svcErr <- notes.Run() }()
defer notes.Server().Stop()
assistant := agent.New(
agent.Name("assistant"),
agent.Address("127.0.0.1:0"),
agent.Services("notes"),
agent.Prompt("You are a friendly first agent. Use the notes service before answering."),
agent.Provider("first-agent-mock"),
agent.WithRegistry(reg),
agent.WithClient(cl),
agent.WithBroker(br),
agent.WithStore(store.NewMemoryStore()),
)
agentErr := make(chan error, 1)
go func() { agentErr <- assistant.Run() }()
defer assistant.Stop()
if err := waitFor(reg, "notes", "assistant"); err != nil {
select {
case runErr := <-svcErr:
return fmt.Errorf("run notes: %w", runErr)
case runErr := <-agentErr:
return fmt.Errorf("run assistant: %w", runErr)
default:
}
return err
}
fmt.Fprintln(w, "First agent (provider: mock, no API key)")
fmt.Fprintln(w, "> Summarize my next steps")
resp, err := assistant.Ask(context.Background(), "Summarize my next steps")
if err != nil {
return fmt.Errorf("ask assistant: %w", err)
}
fmt.Fprintln(w, "assistant:", resp.Reply)
fmt.Fprintln(w, "✓ service-backed agent completed without provider secrets")
return nil
}
func main() {
if err := runFirstAgent(); err != nil {
fmt.Println(err)
os.Exit(1)
}
}
+71
View File
@@ -0,0 +1,71 @@
package main
import (
"bytes"
"os"
"strings"
"testing"
)
func TestRunFirstAgent(t *testing.T) {
var out bytes.Buffer
if err := runFirstAgentWithWriter(&out); err != nil {
t.Fatalf("first-agent example failed: %v", err)
}
want := strings.TrimSpace(readExpectedTranscript(t))
got := strings.TrimSpace(out.String())
if got != want {
t.Fatalf("first-agent transcript drifted from README.md\n--- got ---\n%s\n--- want ---\n%s", got, want)
}
}
func TestReadmeDocumentsNextBreadcrumbs(t *testing.T) {
b, err := os.ReadFile("README.md")
if err != nil {
t.Fatalf("read README.md: %v", err)
}
readme := string(b)
start := strings.Index(readme, "## Next chat, inspect, and debug breadcrumbs")
if start < 0 {
t.Fatal("README.md missing next chat, inspect, and debug breadcrumbs section")
}
section := readme[start:]
for _, want := range []string{
"micro run",
"micro chat assistant --prompt \"Summarize my next steps\"",
"micro inspect agent assistant",
"micro agent doctor assistant",
"no-secret-first-agent.md",
"debugging-agents.md",
"examples/support",
} {
if !strings.Contains(section, want) {
t.Fatalf("README.md next breadcrumbs missing %q", want)
}
}
}
func readExpectedTranscript(t *testing.T) string {
t.Helper()
b, err := os.ReadFile("README.md")
if err != nil {
t.Fatalf("read README.md: %v", err)
}
readme := string(b)
const fence = "```text"
start := strings.Index(readme, "Expected transcript:")
if start < 0 {
t.Fatal("README.md missing Expected transcript section")
}
fenceStart := strings.Index(readme[start:], fence)
if fenceStart < 0 {
t.Fatal("README.md missing transcript text fence")
}
start += fenceStart + len(fence)
end := strings.Index(readme[start:], "```")
if end < 0 {
t.Fatal("README.md missing closing transcript fence")
}
return readme[start : start+end]
}
+72
View File
@@ -0,0 +1,72 @@
# Durable Flow
A workflow that survives a crash and resumes where it stopped.
A `flow` can be an ordered list of **steps** — a task with stages —
instead of a single LLM turn. Each step is checkpointed before and after
through a pluggable `Checkpoint` (store-backed by default), so if the
process dies mid-run, the run resumes at the step it stopped on, without
re-running the steps that already completed (and already had their side
effects).
## What this shows
A three-step checkout (`reserve → charge → confirm`) whose `charge` step
fails the first time, simulating a transient outage / crash:
```
first run:
reserve → inventory reserved
charge → payment dependency unavailable (crash)
run failed: payment gateway timeout
checkpoint: run 70643f61 is at step "charge" (status failed)
resume:
charge → payment captured
confirm → order confirmed
reserve ran 1 time(s) total — completed steps are not repeated on resume
no pending runs — the workflow completed durably
```
The key line is the last pair: on `Resume`, `reserve` does **not** run
again — its result was checkpointed — and the run finishes.
## The pieces
```go
f := micro.NewFlow("checkout",
micro.FlowSteps(
micro.FlowStep{Name: "reserve", Run: reserve},
micro.FlowStep{Name: "charge", Run: charge},
micro.FlowStep{Name: "confirm", Run: confirm},
),
micro.FlowWithCheckpoint(micro.StoreCheckpoint(nil, "checkout")), // nil store = default; "checkout" = key scope
)
f.Execute(ctx, `{}`) // runs; crashes at charge
pending, _ := f.Pending(ctx) // the run, checkpointed at "charge"
f.Resume(ctx, pending[0].ID) // continues from charge to the end
```
- **`State`** carries a typed payload (`Set`/`Scan`) plus a `Stage`
marker — the resume point.
- **`Checkpoint`** persists each `Run`. The built-in is store-backed and
keeps each flow's runs in their own store table (database `flow`, table
`checkout`) via `store.Scope`, so one flow's runs don't share a table
with another's — or with agent or service state. Point the default
store at Postgres or NATS KV and a run survives a real process restart,
or implement the interface to plug in Temporal, Restate, etc.
- A real step would be `flow.Call(service, endpoint)` (an RPC),
`flow.Dispatch(agent)` (hand off to an agent), or `flow.LLM(prompt)`
(one model turn). Here they're plain funcs so durability is the only
thing on display.
## Run
```bash
go run main.go
```
No LLM key required.
+114
View File
@@ -0,0 +1,114 @@
// Durable Flow — a workflow that survives a crash and resumes
//
// A flow can be an ordered list of steps (a task with stages) rather than
// a single LLM turn. Each step is checkpointed before and after through a
// pluggable Checkpoint (store-backed by default), so if the process dies
// mid-run, the run resumes at the step it stopped on — without re-running
// the steps that already completed (and already had their side effects).
//
// This example needs no LLM key. It runs a three-step checkout flow whose
// "charge" step fails the first time (a transient outage). The run is
// checkpointed as failed at that step; we then "recover" the dependency
// and Resume — and the already-completed "reserve" step does not run
// again. A real step would call a service (flow.Call), an agent
// (flow.Dispatch), or the model (flow.LLM); here they're plain funcs so
// the durability is the only thing on display.
package main
import (
"context"
"errors"
"fmt"
"go-micro.dev/v6"
)
// Order is the payload carried across steps via State.Set / State.Scan.
type Order struct {
ID string `json:"id"`
Reserved bool `json:"reserved"`
Charged bool `json:"charged"`
Confirmed bool `json:"confirmed"`
}
// charged toggles the transient failure: 0 = the payment dependency is
// down (first run), 1 = recovered (on resume).
var charged int
// reserveCalls proves the completed step is not re-run on resume.
var reserveCalls int
func reserve(_ context.Context, in micro.FlowState) (micro.FlowState, error) {
reserveCalls++
var o Order
in.Scan(&o)
o.ID = "order-1"
o.Reserved = true
fmt.Println(" reserve → inventory reserved")
return in, in.Set(o)
}
func charge(_ context.Context, in micro.FlowState) (micro.FlowState, error) {
var o Order
in.Scan(&o)
if charged == 0 {
fmt.Println(" charge → payment dependency unavailable (crash)")
return in, errors.New("payment gateway timeout")
}
o.Charged = true
fmt.Println(" charge → payment captured")
return in, in.Set(o)
}
func confirm(_ context.Context, in micro.FlowState) (micro.FlowState, error) {
var o Order
in.Scan(&o)
o.Confirmed = true
fmt.Println(" confirm → order confirmed")
return in, in.Set(o)
}
func main() {
f := micro.NewFlow("checkout",
micro.FlowSteps(
micro.FlowStep{Name: "reserve", Run: reserve},
micro.FlowStep{Name: "charge", Run: charge},
micro.FlowStep{Name: "confirm", Run: confirm},
),
// Durable by default; shown explicitly. Runs are namespaced under
// the flow name ("flow/checkout/runs/..."), so this flow's state
// doesn't share a keyspace with other flows. Point the default
// store at Postgres or NATS KV to survive a real process restart.
micro.FlowWithCheckpoint(micro.StoreCheckpoint(nil, "checkout")),
)
ctx := context.Background()
fmt.Println("first run:")
if err := f.Execute(ctx, `{}`); err != nil {
fmt.Printf(" run failed: %v\n", err)
}
pending, _ := f.Pending(ctx)
if len(pending) == 0 {
fmt.Println("nothing pending — unexpected")
return
}
run := pending[0]
fmt.Printf("\ncheckpoint: run %s is at step %q (status %s)\n",
run.ID[:8], run.State.Stage, run.Status)
// The dependency recovers (or a new process picks the run up).
charged = 1
fmt.Println("\nresume:")
if err := f.Resume(ctx, run.ID); err != nil {
fmt.Printf(" resume failed: %v\n", err)
return
}
fmt.Printf("\nreserve ran %d time(s) total — completed steps are not repeated on resume\n", reserveCalls)
if pend, _ := f.Pending(ctx); len(pend) == 0 {
fmt.Println("no pending runs — the workflow completed durably")
}
}
+71
View File
@@ -0,0 +1,71 @@
// Agentic Loop — keep working until the goal is met, with a guaranteed ceiling
//
// The "loop" pattern from agentic AI: instead of one shot, run a step over
// and over until the goal is reached, letting it decide when to stop — but
// always bounded by a hard iteration cap (the guardrail) so it can never run
// away, or run up an unbounded bill.
//
// flow.Loop is just a flow step, so it composes with the normal checkpointed
// step engine. This example needs no LLM key: the body is a plain func that
// "improves a draft" each pass, and a code-defined Until stops it once the
// draft is good enough — capped by FlowLoopMax. In a real flow the body would
// be micro.FlowDispatch("coder") (an agent) or micro.FlowLLM(...), and the
// stop check micro.FlowUntilLLM("Is the work complete?") — the supervised
// "Ralph" loop, where the model decides it's done but the cap still bounds it.
package main
import (
"context"
"fmt"
"go-micro.dev/v6"
)
// Draft is the payload carried across iterations via State.Set / State.Scan.
type Draft struct {
Text string `json:"text"`
Quality int `json:"quality"` // 0..100, improved each pass
}
// improve is one loop pass: it refines the draft a bit. In a real flow this
// would be an agent or an LLM turn; here it's deterministic so the example
// runs offline.
func improve(_ context.Context, in micro.FlowState) (micro.FlowState, error) {
var d Draft
_ = in.Scan(&d)
d.Quality += 30
d.Text = fmt.Sprintf("draft refined (quality %d)", d.Quality)
return in, in.Set(d)
}
func main() {
const goodEnough = 90
f := micro.NewFlow("refine",
micro.FlowSteps(
micro.FlowStep{Name: "improve", Run: micro.FlowLoop(
improve,
// Stop early once the draft is good enough...
micro.FlowUntil(func(_ context.Context, s micro.FlowState, iter int) (bool, error) {
var d Draft
_ = s.Scan(&d)
fmt.Printf(" pass %d → quality %d\n", iter, d.Quality)
return d.Quality >= goodEnough, nil
}),
// ...but never run the body more than 10 times (the ceiling).
micro.FlowLoopMax(10),
)},
),
micro.FlowDeleteOnSuccess(),
)
fmt.Println("refining until quality >=", goodEnough)
if err := f.Execute(context.Background(), `{"text":"initial draft","quality":0}`); err != nil {
fmt.Println("flow error:", err)
return
}
for _, r := range f.Results() {
fmt.Printf("\ndone: %s\n", r.Answer)
}
}
+38
View File
@@ -0,0 +1,38 @@
# Graceful Stop Demo
This example demonstrates the intended shutdown behavior after the gRPC graceful-stop patch.
## Run
```bash
go run ./examples/graceful-stop
```
## Expected behavior
- one long-running RPC starts
- shutdown begins while that RPC is still running
- new RPCs stop being accepted shortly after shutdown starts
- the in-flight RPC is allowed to finish
Typical output:
```text
long RPC is running; starting shutdown
new RPC rejected after shutdown began: ...
long RPC completed: slept for 1500ms
done
```
There may be a small race window where the first post-stop RPC is still accepted once before subsequent new RPCs are rejected. The important part is that in-flight RPCs are drained while new RPCs are cut off.
## Automated check
```bash
go test ./server/grpc -run TestGracefulStopRejectsNewRPCsButAllowsInFlightRPCs -v
```
## Environment
- no special environment variables are required
- the demo may print a TLS warning from `go-micro`; it is unrelated to this change
+129
View File
@@ -0,0 +1,129 @@
package main
import (
"context"
"fmt"
"log"
"net"
"time"
micro "go-micro.dev/v6"
"go-micro.dev/v6/client"
grpcclient "go-micro.dev/v6/client/grpc"
"go-micro.dev/v6/registry"
"go-micro.dev/v6/server"
grpcserver "go-micro.dev/v6/server/grpc"
)
type SleepRequest struct {
DelayMS int `json:"delay_ms"`
}
type SleepResponse struct {
Message string `json:"message"`
}
type Sleeper struct {
started chan struct{}
}
func (s *Sleeper) Sleep(ctx context.Context, req *SleepRequest, rsp *SleepResponse) error {
select {
case s.started <- struct{}{}:
default:
}
timer := time.NewTimer(time.Duration(req.DelayMS) * time.Millisecond)
defer timer.Stop()
select {
case <-ctx.Done():
return ctx.Err()
case <-timer.C:
}
rsp.Message = fmt.Sprintf("slept for %dms", req.DelayMS)
return nil
}
func main() {
listener, err := net.Listen("tcp", "127.0.0.1:0")
if err != nil {
log.Fatal(err)
}
defer listener.Close()
addr := listener.Addr().String()
reg := registry.NewMemoryRegistry()
handler := &Sleeper{started: make(chan struct{}, 1)}
service := micro.NewService("grace-demo",
micro.HandleSignal(false),
micro.Registry(reg),
micro.Server(grpcserver.NewServer(
server.Registry(reg),
server.Name("grace-demo"),
server.Address(addr),
grpcserver.Listener(listener),
grpcserver.GracefulStopTimeout(3*time.Second),
)),
micro.Client(grpcclient.NewClient(
client.Registry(reg),
client.ContentType("application/grpc+json"),
client.DialTimeout(200*time.Millisecond),
client.RequestTimeout(5*time.Second),
)),
)
if err := service.Handle(handler); err != nil {
log.Fatal(err)
}
if err := service.Start(); err != nil {
log.Fatal(err)
}
log.Printf("service started on %s", addr)
longDone := make(chan error, 1)
go func() {
req := service.Client().NewRequest("grace-demo", "Sleeper.Sleep", &SleepRequest{DelayMS: 1500})
rsp := &SleepResponse{}
longDone <- service.Client().Call(context.Background(), req, rsp, client.WithAddress(addr))
if rsp.Message != "" {
log.Printf("long RPC completed: %s", rsp.Message)
}
}()
<-handler.started
log.Printf("long RPC is running; starting shutdown")
stopDone := make(chan error, 1)
go func() {
stopDone <- service.Stop()
}()
deadline := time.Now().Add(500 * time.Millisecond)
for time.Now().Before(deadline) {
callCtx, cancel := context.WithTimeout(context.Background(), 150*time.Millisecond)
req := service.Client().NewRequest("grace-demo", "Sleeper.Sleep", &SleepRequest{DelayMS: 50})
rsp := &SleepResponse{}
err = service.Client().Call(callCtx, req, rsp, client.WithAddress(addr))
cancel()
if err != nil {
log.Printf("new RPC rejected after shutdown began: %v", err)
break
}
log.Printf("new RPC still accepted during shutdown: %s", rsp.Message)
time.Sleep(10 * time.Millisecond)
}
if err := <-longDone; err != nil {
log.Fatalf("long RPC failed: %v", err)
}
if err := <-stopDone; err != nil {
log.Fatalf("service stop failed: %v", err)
}
log.Printf("done")
}
+66
View File
@@ -0,0 +1,66 @@
# gRPC Interop Example
This example shows that **any standard gRPC client** can call a go-micro service — no go-micro SDK required on the client side.
The server is a normal go-micro service using the gRPC transport. The client is a plain `google.golang.org/grpc` client with no go-micro imports.
## How it works
go-micro's gRPC server uses a [`grpc.UnknownServiceHandler`](https://pkg.go.dev/google.golang.org/grpc#UnknownServiceHandler) that catches all incoming requests and routes them by parsing the standard gRPC method path (`/package.Service/Method`). This means any language with gRPC support (Python, Java, Rust, etc.) can call go-micro services using generated protobuf stubs.
## Running
Start the server:
```bash
cd examples/grpc-interop
go run ./server/
```
In another terminal, call it with the standard gRPC client:
```bash
cd examples/grpc-interop
go run ./client/ --name Alice
# Response: Hello Alice
```
## Calling from other languages
Generate stubs from `proto/greeter.proto` in your language of choice and point them at `localhost:50051`. For example, with Python:
```bash
pip install grpcio-tools
python -m grpc_tools.protoc -Iproto --python_out=. --grpc_python_out=. proto/greeter.proto
```
```python
import grpc
import greeter_pb2
import greeter_pb2_grpc
channel = grpc.insecure_channel("localhost:50051")
stub = greeter_pb2_grpc.GreeterStub(channel)
response = stub.Hello(greeter_pb2.HelloRequest(name="Alice"))
print(response.message) # Hello Alice
```
## Key points
- The go-micro server registers handlers via `pb.RegisterGreeterHandler()`
- The standard gRPC client uses stubs generated by `protoc-gen-go-grpc`
- Both share the same `.proto` file — that's the contract
- The server uses protobuf encoding on the wire, same as any gRPC service
- Service discovery (mDNS, consul, etc.) is only needed for go-micro-to-go-micro calls; direct gRPC clients connect by address
## Regenerating proto stubs
```bash
protoc --go_out=. --go_opt=paths=source_relative \
--go-grpc_out=. --go-grpc_opt=paths=source_relative \
--micro_out=. --micro_opt=paths=source_relative \
proto/greeter.proto
```
Requires `protoc-gen-go`, `protoc-gen-go-grpc`, and `protoc-gen-micro`.
+43
View File
@@ -0,0 +1,43 @@
// client calls the go-micro Greeter service using a standard gRPC
// client — no go-micro SDK. This proves any language with gRPC support
// can call go-micro services.
package main
import (
"context"
"flag"
"fmt"
"log"
"time"
pb "example/proto"
"google.golang.org/grpc"
"google.golang.org/grpc/credentials/insecure"
)
func main() {
addr := flag.String("addr", "localhost:50051", "server address")
name := flag.String("name", "World", "name to greet")
flag.Parse()
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defer cancel()
conn, err := grpc.NewClient(*addr,
grpc.WithTransportCredentials(insecure.NewCredentials()),
)
if err != nil {
log.Fatalf("connect: %v", err)
}
defer conn.Close()
client := pb.NewGreeterClient(conn)
resp, err := client.Hello(ctx, &pb.HelloRequest{Name: *name})
if err != nil {
log.Fatalf("Hello: %v", err)
}
fmt.Printf("Response: %s\n", resp.Message)
}
+72
View File
@@ -0,0 +1,72 @@
module example
go 1.24
require (
go-micro.dev/v5 v5.16.0
google.golang.org/grpc v1.71.1
google.golang.org/protobuf v1.36.6
)
require (
dario.cat/mergo v1.0.2 // indirect
github.com/armon/go-metrics v0.4.1 // indirect
github.com/bitly/go-simplejson v0.5.0 // indirect
github.com/cespare/xxhash/v2 v2.3.0 // indirect
github.com/coreos/go-semver v0.3.0 // indirect
github.com/coreos/go-systemd/v22 v22.3.2 // indirect
github.com/cornelk/hashmap v1.0.8 // indirect
github.com/cpuguy83/go-md2man/v2 v2.0.5 // indirect
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect
github.com/fatih/color v1.16.0 // indirect
github.com/fsnotify/fsnotify v1.6.0 // indirect
github.com/go-redis/redis/v8 v8.11.5 // indirect
github.com/gogo/protobuf v1.3.2 // indirect
github.com/golang/protobuf v1.5.4 // indirect
github.com/google/uuid v1.6.0 // indirect
github.com/hashicorp/consul/api v1.32.1 // 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 v1.0.2 // indirect
github.com/hashicorp/serf v0.10.1 // indirect
github.com/klauspost/compress v1.18.0 // indirect
github.com/lib/pq v1.10.9 // indirect
github.com/mattn/go-colorable v0.1.13 // indirect
github.com/mattn/go-isatty v0.0.20 // indirect
github.com/miekg/dns v1.1.50 // indirect
github.com/mitchellh/go-homedir v1.1.0 // indirect
github.com/mitchellh/hashstructure v1.1.0 // indirect
github.com/mitchellh/mapstructure v1.5.0 // indirect
github.com/nats-io/nats.go v1.42.0 // indirect
github.com/nats-io/nkeys v0.4.11 // indirect
github.com/nats-io/nuid v1.0.1 // indirect
github.com/oxtoacart/bpool v0.0.0-20190530202638-03653db5a59c // indirect
github.com/patrickmn/go-cache v2.1.0+incompatible // indirect
github.com/pkg/errors v0.9.1 // indirect
github.com/rabbitmq/amqp091-go v1.10.0 // indirect
github.com/russross/blackfriday/v2 v2.1.0 // indirect
github.com/urfave/cli/v2 v2.27.6 // indirect
github.com/xrash/smetrics v0.0.0-20240521201337-686a1a2994c1 // indirect
go.etcd.io/bbolt v1.4.0 // indirect
go.etcd.io/etcd/api/v3 v3.5.21 // indirect
go.etcd.io/etcd/client/pkg/v3 v3.5.21 // indirect
go.etcd.io/etcd/client/v3 v3.5.21 // indirect
go.uber.org/multierr v1.10.0 // indirect
go.uber.org/zap v1.27.0 // indirect
golang.org/x/crypto v0.37.0 // indirect
golang.org/x/exp v0.0.0-20250305212735-054e65f0b394 // indirect
golang.org/x/mod v0.24.0 // indirect
golang.org/x/net v0.38.0 // indirect
golang.org/x/sync v0.13.0 // indirect
golang.org/x/sys v0.32.0 // indirect
golang.org/x/text v0.24.0 // indirect
golang.org/x/tools v0.31.0 // indirect
google.golang.org/genproto/googleapis/api v0.0.0-20250324211829-b45e905df463 // indirect
google.golang.org/genproto/googleapis/rpc v0.0.0-20250324211829-b45e905df463 // indirect
)
replace go-micro.dev/v5 => ../..
+387
View File
@@ -0,0 +1,387 @@
dario.cat/mergo v1.0.2 h1:85+piFYR1tMbRrLcDwR18y4UKJ3aH1Tbzi24VRW1TK8=
dario.cat/mergo v1.0.2/go.mod h1:E/hbnu0NxMFBjpMIE34DRGLWqDy0g5FuKDhCb31ngxA=
filippo.io/edwards25519 v1.1.0 h1:FNf4tywRC1HmFuKW5xopWpigGjJKiJSV0Cqo0cJWDaA=
filippo.io/edwards25519 v1.1.0/go.mod h1:BxyFTGdWcka3PhytdK4V28tE5sGfRvvvRV7EaN4VDT4=
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/bitly/go-simplejson v0.5.0 h1:6IH+V8/tVMab511d5bn4M7EwGXZf9Hj6i2xSwkNEM+Y=
github.com/bitly/go-simplejson v0.5.0/go.mod h1:cXHtHw4XUPsvGaxgjIAn8PhEWG9NfngEKAMDJEczWVA=
github.com/bmizerany/assert v0.0.0-20160611221934-b7ed37b82869 h1:DDGfHa7BWjL4YnC6+E63dPcxHo2sUxDIu8g3QgEJdRY=
github.com/bmizerany/assert v0.0.0-20160611221934-b7ed37b82869/go.mod h1:Ekp36dRnpXw/yCqJaO+ZrUyxD+3VXMFFr56k5XYrpB4=
github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs=
github.com/cespare/xxhash/v2 v2.3.0/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/coreos/go-semver v0.3.0 h1:wkHLiw0WNATZnSG7epLsujiMCgPAc9xhjJ4tgnAxmfM=
github.com/coreos/go-semver v0.3.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk=
github.com/coreos/go-systemd/v22 v22.3.2 h1:D9/bQk5vlXQFZ6Kwuu6zaiXJ9oTPe68++AzAJc1DzSI=
github.com/coreos/go-systemd/v22 v22.3.2/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc=
github.com/cornelk/hashmap v1.0.8 h1:nv0AWgw02n+iDcawr5It4CjQIAcdMMKRrs10HOJYlrc=
github.com/cornelk/hashmap v1.0.8/go.mod h1:RfZb7JO3RviW/rT6emczVuC/oxpdz4UsSB2LJSclR1k=
github.com/cpuguy83/go-md2man/v2 v2.0.5 h1:ZtcqGrnekaHpVLArFSe4HK5DoKx1T0rq2DwVB0alcyc=
github.com/cpuguy83/go-md2man/v2 v2.0.5/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
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/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f h1:lO4WD4F/rVNCu3HqELle0jiPLLBs70cWOduZpkS1E78=
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f/go.mod h1:cuUVRXasLTGF7a8hSLbxyZXjz+1KgoB3wDUb6vlszIc=
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.16.0 h1:zmkK9Ngbjj+K0yRhTVONQh1p/HknKYSlNT+vZCzyokM=
github.com/fatih/color v1.16.0/go.mod h1:fL2Sau1YI5c0pdGEVCbKQbLXB6edEj1ZgiY4NijnWvE=
github.com/fsnotify/fsnotify v1.6.0 h1:n+5WquG0fcWoWp6xPWfHdbskMCQaFnG6PfBrh1Ky4HY=
github.com/fsnotify/fsnotify v1.6.0/go.mod h1:sl3t1tCWJFWoRz9R8WJCbQihKKwmorjAbSClcnxKAGw=
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.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-redis/redis/v8 v8.11.5 h1:AcZZR7igkdvfVmQTPnu9WE37LRrO/YrBH5zWyjDC0oI=
github.com/go-redis/redis/v8 v8.11.5/go.mod h1:gREzHqY1hg6oD9ngVRbLStwAWKhA0FEgq8Jd4h5lpwo=
github.com/go-sql-driver/mysql v1.9.2 h1:4cNKDYQ1I84SXslGddlsrMhc8k4LeDVj6Ad6WRjiHuU=
github.com/go-sql-driver/mysql v1.9.2/go.mod h1:qn46aNg1333BRMNU69Lq93t8du/dwxI64Gl8i5p1WMU=
github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY=
github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=
github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q=
github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q=
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/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek=
github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps=
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/btree v1.0.1/go.mod h1:xXMiIv4Fb/0kKde4SpL7qlzvu5cMJDRkFDxJfI9uaxA=
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/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
github.com/google/go-tpm v0.9.3 h1:+yx0/anQuGzi+ssRqeD6WpXjW2L/V0dItUayO0i9sRc=
github.com/google/go-tpm v0.9.3/go.mod h1:h9jEsEECg7gtLis0upRBQU+GhYVH6jMjrFxI8u6bVUY=
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/hashicorp/consul/api v1.32.1 h1:0+osr/3t/aZNAdJX558crU3PEjVrG4x6715aZHRgceE=
github.com/hashicorp/consul/api v1.32.1/go.mod h1:mXUWLnxftwTmDv4W3lzxYCPD199iNLLUyLfLGFJbtl4=
github.com/hashicorp/consul/sdk v0.16.1 h1:V8TxTnImoPD5cj0U9Spl0TUxcytjcbbJeADFF07KdHg=
github.com/hashicorp/consul/sdk v0.16.1/go.mod h1:fSXvwxB2hmh1FMZCNl6PwX0Q/1wdWtHJcZ7Ea5tns0s=
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-msgpack v0.5.5/go.mod h1:ahLV/dePpqEmjfWmKiqvPkv/twdG7iPBM1vqhUKIvfM=
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-sockaddr v1.0.2/go.mod h1:rB4wwRAUzs07qva3c5SdrY/NEtAUjGlgmH/UkBUC97A=
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-uuid v1.0.3/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro=
github.com/hashicorp/go-version v1.2.1 h1:zEfKbn2+PDgroKdiOzqiE8rsmLqU2uwi5PB5pBJ3TkI=
github.com/hashicorp/go-version v1.2.1/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA=
github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
github.com/hashicorp/golang-lru v1.0.2 h1:dV3g9Z/unq5DpblPpw+Oqcv4dU/1omnb4Ok8iPY6p1c=
github.com/hashicorp/golang-lru v1.0.2/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/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8=
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
github.com/klauspost/compress v1.18.0 h1:c/Cqfb0r+Yi+JtIEq73FWXVkRonBlf0CRNYc8Zttxdo=
github.com/klauspost/compress v1.18.0/go.mod h1:2Pp+KzxcywXVXMr50+X0Q/Lsb43OQHYWRCY2AiWywWQ=
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/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
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/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
github.com/lib/pq v1.10.9 h1:YXG7RB+JIjhP29X+OtkiDnYaXQwpS4JEWq7dtCCRUEw=
github.com/lib/pq v1.10.9/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o=
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/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/go.mod h1:p6aan82bvRIyn+zDIv9xYNUpwa73JcSh9BKwknJysuI=
github.com/miekg/dns v1.1.50 h1:DQUfb9uc6smULcREF09Uc+/Gd46YWqJd5DbpPE9xkcA=
github.com/miekg/dns v1.1.50/go.mod h1:e3IlAVfNqAllflbibAZEWOXOQ+Ynzk/dDozDxY7XnME=
github.com/minio/highwayhash v1.0.3 h1:kbnuUMoHYyVl7szWjSxJnxw11k2U709jqFPPmIUyD6Q=
github.com/minio/highwayhash v1.0.3/go.mod h1:GGYsuwP/fPD6Y9hMiXuapVvlIUEhFhMTh0rxU3ik1LQ=
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/hashstructure v1.1.0 h1:P6P1hdjqAAknpY/M1CGipelZgp+4y9ja9kmUZPXP+H0=
github.com/mitchellh/hashstructure v1.1.0/go.mod h1:xUDAozZz0Wmdiufv0uyhnHkUTN6/6d8ulp4AwfLKrmA=
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/nats-io/jwt/v2 v2.7.4 h1:jXFuDDxs/GQjGDZGhNgH4tXzSUK6WQi2rsj4xmsNOtI=
github.com/nats-io/jwt/v2 v2.7.4/go.mod h1:me11pOkwObtcBNR8AiMrUbtVOUGkqYjMQZ6jnSdVUIA=
github.com/nats-io/nats-server/v2 v2.11.3 h1:AbGtXxuwjo0gBroLGGr/dE0vf24kTKdRnBq/3z/Fdoc=
github.com/nats-io/nats-server/v2 v2.11.3/go.mod h1:6Z6Fd+JgckqzKig7DYwhgrE7bJ6fypPHnGPND+DqgMY=
github.com/nats-io/nats.go v1.42.0 h1:ynIMupIOvf/ZWH/b2qda6WGKGNSjwOUutTpWRvAmhaM=
github.com/nats-io/nats.go v1.42.0/go.mod h1:iRWIPokVIFbVijxuMQq4y9ttaBTMe0SFdlZfMDd+33g=
github.com/nats-io/nkeys v0.4.11 h1:q44qGV008kYd9W1b1nEBkNzvnWxtRSQ7A8BoqRrcfa0=
github.com/nats-io/nkeys v0.4.11/go.mod h1:szDimtgmfOi9n25JpfIdGw12tZFYXqhGxjhVxsatHVE=
github.com/nats-io/nuid v1.0.1 h1:5iA8DT8V7q8WK2EScv2padNa/rTESc1KdnPw4TC2paw=
github.com/nats-io/nuid v1.0.1/go.mod h1:19wcPz3Ph3q0Jbyiqsd0kePYG7A95tJPxeL+1OSON2c=
github.com/nxadm/tail v1.4.8 h1:nPr65rt6Y5JFSKQO7qToXr7pePgD6Gwiw05lkbyAQTE=
github.com/nxadm/tail v1.4.8/go.mod h1:+ncqLTQzXmGhMZNUePPaPqPvBxHAIsmXswZKocGu+AU=
github.com/onsi/ginkgo v1.16.5 h1:8xi0RTUf59SOSfEtZMvwTvXYMzG4gV23XVHOZiXNtnE=
github.com/onsi/ginkgo v1.16.5/go.mod h1:+E8gABHa3K6zRBolWtd+ROzc/U5bkGt0FwiG042wbpU=
github.com/onsi/gomega v1.18.1 h1:M1GfJqGRrBrrGGsbxzV5dqM2U2ApXefZCQpkukxYRLE=
github.com/onsi/gomega v1.18.1/go.mod h1:0q+aL8jAiMXy9hbwj2mr5GziHiwhAIQpFmmtT5hitRs=
github.com/oxtoacart/bpool v0.0.0-20190530202638-03653db5a59c h1:rp5dCmg/yLR3mgFuSOe4oEnDDmGLROTvMragMUXpTQw=
github.com/oxtoacart/bpool v0.0.0-20190530202638-03653db5a59c/go.mod h1:X07ZCGwUbLaax7L0S3Tw4hpejzu63ZrrQiUe6W0hcy0=
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/patrickmn/go-cache v2.1.0+incompatible h1:HRMgzkcYKYpi3C8ajMPV8OFXaaRUnok+kx1WdO15EQc=
github.com/patrickmn/go-cache v2.1.0+incompatible/go.mod h1:3Qf8kWWT7OJRJbdiICTKqZju1ZixQ/KpMGzzAfe6+WQ=
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/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
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/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
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/rabbitmq/amqp091-go v1.10.0 h1:STpn5XsHlHGcecLmMFCtg7mqq0RnD+zFr4uzukfVhBw=
github.com/rabbitmq/amqp091-go v1.10.0/go.mod h1:Hy4jKW5kQART1u+JkDTF9YYOQUHXqMuhrgxOEeS7G4o=
github.com/rogpeppe/go-internal v1.13.1 h1:KvO1DLK/DRN07sQ1LQKScxyZJuNnedQ5/wKSR38lUII=
github.com/rogpeppe/go-internal v1.13.1/go.mod h1:uMEvuHeurkdAXX61udpOXGD/AzZDWNMNyH2VO9fmH0o=
github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk=
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
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.2 h1:xuMeJ0Sdp5ZMRXx/aWO6RZxdr3beISkG5/G/aIRr3pY=
github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA=
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.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA=
github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
github.com/test-go/testify v1.1.4 h1:Tf9lntrKUMHiXQ07qBScBTSA0dhYQlu83hswqelv1iE=
github.com/test-go/testify v1.1.4/go.mod h1:rH7cfJo/47vWGdi4GPj16x3/t1xGOj2YxzmNQzk2ghU=
github.com/tv42/httpunix v0.0.0-20150427012821-b75d8614f926/go.mod h1:9ESjWnEqriFuLhtthL60Sar/7RFoluCcXsuvEwTV5KM=
github.com/urfave/cli/v2 v2.27.6 h1:VdRdS98FNhKZ8/Az8B7MTyGQmpIr36O1EHybx/LaZ4g=
github.com/urfave/cli/v2 v2.27.6/go.mod h1:3Sevf16NykTbInEnD0yKkjDAeZDS0A6bzhBH5hrMvTQ=
github.com/xrash/smetrics v0.0.0-20240521201337-686a1a2994c1 h1:gEOO8jv9F4OT7lGCjxCBTO/36wtF6j2nSip77qHd4x4=
github.com/xrash/smetrics v0.0.0-20240521201337-686a1a2994c1/go.mod h1:Ohn+xnUBiLI6FVj/9LpzZWtj1/D6lUovWYBkxHVV3aM=
github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k=
go.etcd.io/bbolt v1.4.0 h1:TU77id3TnN/zKr7CO/uk+fBCwF2jGcMuw2B/FMAzYIk=
go.etcd.io/bbolt v1.4.0/go.mod h1:AsD+OCi/qPN1giOX1aiLAha3o1U8rAz65bvN4j0sRuk=
go.etcd.io/etcd/api/v3 v3.5.21 h1:A6O2/JDb3tvHhiIz3xf9nJ7REHvtEFJJ3veW3FbCnS8=
go.etcd.io/etcd/api/v3 v3.5.21/go.mod h1:c3aH5wcvXv/9dqIw2Y810LDXJfhSYdHQ0vxmP3CCHVY=
go.etcd.io/etcd/client/pkg/v3 v3.5.21 h1:lPBu71Y7osQmzlflM9OfeIV2JlmpBjqBNlLtcoBqUTc=
go.etcd.io/etcd/client/pkg/v3 v3.5.21/go.mod h1:BgqT/IXPjK9NkeSDjbzwsHySX3yIle2+ndz28nVsjUs=
go.etcd.io/etcd/client/v3 v3.5.21 h1:T6b1Ow6fNjOLOtM0xSoKNQt1ASPCLWrF9XMHcH9pEyY=
go.etcd.io/etcd/client/v3 v3.5.21/go.mod h1:mFYy67IOqmbRf/kRUvsHixzo3iG+1OF2W2+jVIQRAnU=
go.opentelemetry.io/auto/sdk v1.1.0 h1:cH53jehLUN6UFLY71z+NDOiNJqDdPRaXzTel0sJySYA=
go.opentelemetry.io/auto/sdk v1.1.0/go.mod h1:3wSPjt5PWp2RhlCcmmOial7AvC4DQqZb7a7wCow3W8A=
go.opentelemetry.io/otel v1.35.0 h1:xKWKPxrxB6OtMCbmMY021CqC45J+3Onta9MqjhnusiQ=
go.opentelemetry.io/otel v1.35.0/go.mod h1:UEqy8Zp11hpkUrL73gSlELM0DupHoiq72dR+Zqel/+Y=
go.opentelemetry.io/otel/metric v1.35.0 h1:0znxYu2SNyuMSQT4Y9WDWej0VpcsxkuklLa4/siN90M=
go.opentelemetry.io/otel/metric v1.35.0/go.mod h1:nKVFgxBZ2fReX6IlyW28MgZojkoAkJGaE8CpgeAU3oE=
go.opentelemetry.io/otel/sdk v1.35.0 h1:iPctf8iprVySXSKJffSS79eOjl9pvxV9ZqOWT0QejKY=
go.opentelemetry.io/otel/sdk v1.35.0/go.mod h1:+ga1bZliga3DxJ3CQGg3updiaAJoNECOgJREo9KHGQg=
go.opentelemetry.io/otel/sdk/metric v1.34.0 h1:5CeK9ujjbFVL5c1PhLuStg1wxA7vQv7ce1EK0Gyvahk=
go.opentelemetry.io/otel/sdk/metric v1.34.0/go.mod h1:jQ/r8Ze28zRKoNRdkjCZxfs6YvBTG1+YIqyFVFYec5w=
go.opentelemetry.io/otel/trace v1.35.0 h1:dPpEfJu1sDIqruz7BHFG3c7528f6ddfSWfFDVt/xgMs=
go.opentelemetry.io/otel/trace v1.35.0/go.mod h1:WUk7DtFp1Aw2MkvqGdwiXYDZZNvA/1J8o6xRXLrIkyc=
go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto=
go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE=
go.uber.org/multierr v1.10.0 h1:S0h4aNzvfcFsC3dRF1jLoaov7oRaKqRGC/pUEJ2yvPQ=
go.uber.org/multierr v1.10.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y=
go.uber.org/zap v1.27.0 h1:aJMhYGrd5QSmlpLMr2MftRKl7t8J8PTZPA732ud/XR8=
go.uber.org/zap v1.27.0/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E=
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/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.37.0 h1:kJNSjF/Xp7kU0iB2Z+9viTPMW4EqqsrywMXLJOOsXSE=
golang.org/x/crypto v0.37.0/go.mod h1:vg+k43peMZ0pUMhYmVAWysMK35e6ioLh3wB8ZCAfbVc=
golang.org/x/exp v0.0.0-20250305212735-054e65f0b394 h1:nDVHiLt8aIbd/VzvPWN6kSOPE7+F/fNFDSXLVYkE/Iw=
golang.org/x/exp v0.0.0-20250305212735-054e65f0b394/go.mod h1:sIifuuw/Yco/y6yb6+bDNfyeQ/MdPUy/hKEMYQV17cM=
golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.24.0 h1:ZfthKaKaT4NrhGVZHO1/WDTwGES4De8KtWO0SIbNJMU=
golang.org/x/mod v0.24.0/go.mod h1:IXM97Txy2VM4PJ3gI61r1YEk/gAj6zAHN3AdZt6S9Ww=
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-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM=
golang.org/x/net v0.0.0-20210410081132-afb366fc7cd1/go.mod h1:9tjilg8BloeKEkVJvy7fQ90B1CfIiPueXVOjqfkSzI8=
golang.org/x/net v0.0.0-20210726213435-c6fcb2dbf985/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/net v0.38.0 h1:vRMAPTMaeGqVhG5QyLJHqNDwecKTomGeqbnfZyKlBI8=
golang.org/x/net v0.38.0/go.mod h1:ivrbrMbzFq5J41QOQh0siUuly180yBYtLp+CKbEaFx8=
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-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.13.0 h1:AauUjRAJ9OSnvULf/ARrrVywoJDy0YS2AwQ98I37610=
golang.org/x/sync v0.13.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA=
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-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
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-20200930185726-fdedc70b468f/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-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
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.0.0-20220908164124-27713097b956/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.32.0 h1:s77OFDvIQeibCmezSnk/q6iAfkdiQaJi4VzroCFrN20=
golang.org/x/sys v0.32.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
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.24.0 h1:dd5Bzh4yt5KYA8f9CJHCP4FB4D51c2c6JvN37xJJkJ0=
golang.org/x/text v0.24.0/go.mod h1:L8rBsPeo2pSS+xqN0d5u2ikmjtmoJbDBT1b7nHvFCdU=
golang.org/x/time v0.11.0 h1:/bpjEDfN9tkoN/ryeYHnv5hcMlc8ncjMcM4XBk5NWV0=
golang.org/x/time v0.11.0/go.mod h1:CDIdPxbZBQxdj6cxyCIdrNogrJKMJ7pr37NYpMcMDSg=
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/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
golang.org/x/tools v0.1.6-0.20210726203631-07bc1bf47fb2/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
golang.org/x/tools v0.31.0 h1:0EedkvKDbh+qistFTd0Bcwe/YLh4vHwWEkiI0toFIBU=
golang.org/x/tools v0.31.0/go.mod h1:naFTU+Cev749tSJRXJlna0T3WxKvb1kWEx15xA4SdmQ=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
google.golang.org/genproto/googleapis/api v0.0.0-20250324211829-b45e905df463 h1:hE3bRWtU6uceqlh4fhrSnUyjKHMKB9KrTLLG+bc0ddM=
google.golang.org/genproto/googleapis/api v0.0.0-20250324211829-b45e905df463/go.mod h1:U90ffi8eUL9MwPcrJylN5+Mk2v3vuPDptd5yyNUiRR8=
google.golang.org/genproto/googleapis/rpc v0.0.0-20250324211829-b45e905df463 h1:e0AIkUUhxyBKh6ssZNrAMeqhA7RKUj42346d1y02i2g=
google.golang.org/genproto/googleapis/rpc v0.0.0-20250324211829-b45e905df463/go.mod h1:qQ0YXyHHx3XkvlzUtpXDkS29lDSafHMZBAZDc03LQ3A=
google.golang.org/grpc v1.71.1 h1:ffsFWr7ygTUscGPI0KKK6TLrGz0476KUvvsbqWK0rPI=
google.golang.org/grpc v1.71.1/go.mod h1:H0GRtasmQOh9LkFoCPDu3ZrwUtD1YGE+b2vYBYd/8Ec=
google.golang.org/grpc/examples v0.0.0-20250515150734-f2d3e11f3057 h1:lPv+iqlAyiKMjbL3ivJlAASixPknLv806R6zaoE4PUM=
google.golang.org/grpc/examples v0.0.0-20250515150734-f2d3e11f3057/go.mod h1:WPWnet+nYurNGpV0rVYHI1YuOJwVHeM3t8f76m410XM=
google.golang.org/protobuf v1.36.6 h1:z1NpPI8ku2WgiWnf+t9wTPsn6eP1L7ksHUlkfLvd9xY=
google.golang.org/protobuf v1.36.6/go.mod h1:jduwjTPXsFjZGTmRluh+L6NjiWu7pchiJ2/5YcXBHnY=
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/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ=
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw=
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.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
+173
View File
@@ -0,0 +1,173 @@
// Code generated by protoc-gen-go. DO NOT EDIT.
// versions:
// protoc-gen-go v1.36.11
// protoc v3.21.12
// source: proto/greeter.proto
package proto
import (
protoreflect "google.golang.org/protobuf/reflect/protoreflect"
protoimpl "google.golang.org/protobuf/runtime/protoimpl"
reflect "reflect"
sync "sync"
unsafe "unsafe"
)
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)
)
type HelloRequest struct {
state protoimpl.MessageState `protogen:"open.v1"`
Name string `protobuf:"bytes,1,opt,name=name,proto3" json:"name,omitempty"`
unknownFields protoimpl.UnknownFields
sizeCache protoimpl.SizeCache
}
func (x *HelloRequest) Reset() {
*x = HelloRequest{}
mi := &file_proto_greeter_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_proto_greeter_proto_msgTypes[0]
if 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_proto_greeter_proto_rawDescGZIP(), []int{0}
}
func (x *HelloRequest) GetName() string {
if x != nil {
return x.Name
}
return ""
}
type HelloResponse struct {
state protoimpl.MessageState `protogen:"open.v1"`
Message string `protobuf:"bytes,1,opt,name=message,proto3" json:"message,omitempty"`
unknownFields protoimpl.UnknownFields
sizeCache protoimpl.SizeCache
}
func (x *HelloResponse) Reset() {
*x = HelloResponse{}
mi := &file_proto_greeter_proto_msgTypes[1]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
func (x *HelloResponse) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*HelloResponse) ProtoMessage() {}
func (x *HelloResponse) ProtoReflect() protoreflect.Message {
mi := &file_proto_greeter_proto_msgTypes[1]
if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use HelloResponse.ProtoReflect.Descriptor instead.
func (*HelloResponse) Descriptor() ([]byte, []int) {
return file_proto_greeter_proto_rawDescGZIP(), []int{1}
}
func (x *HelloResponse) GetMessage() string {
if x != nil {
return x.Message
}
return ""
}
var File_proto_greeter_proto protoreflect.FileDescriptor
const file_proto_greeter_proto_rawDesc = "" +
"\n" +
"\x13proto/greeter.proto\x12\agreeter\"\"\n" +
"\fHelloRequest\x12\x12\n" +
"\x04name\x18\x01 \x01(\tR\x04name\")\n" +
"\rHelloResponse\x12\x18\n" +
"\amessage\x18\x01 \x01(\tR\amessage2C\n" +
"\aGreeter\x128\n" +
"\x05Hello\x12\x15.greeter.HelloRequest\x1a\x16.greeter.HelloResponse\"\x00B\tZ\a./protob\x06proto3"
var (
file_proto_greeter_proto_rawDescOnce sync.Once
file_proto_greeter_proto_rawDescData []byte
)
func file_proto_greeter_proto_rawDescGZIP() []byte {
file_proto_greeter_proto_rawDescOnce.Do(func() {
file_proto_greeter_proto_rawDescData = protoimpl.X.CompressGZIP(unsafe.Slice(unsafe.StringData(file_proto_greeter_proto_rawDesc), len(file_proto_greeter_proto_rawDesc)))
})
return file_proto_greeter_proto_rawDescData
}
var file_proto_greeter_proto_msgTypes = make([]protoimpl.MessageInfo, 2)
var file_proto_greeter_proto_goTypes = []any{
(*HelloRequest)(nil), // 0: greeter.HelloRequest
(*HelloResponse)(nil), // 1: greeter.HelloResponse
}
var file_proto_greeter_proto_depIdxs = []int32{
0, // 0: greeter.Greeter.Hello:input_type -> greeter.HelloRequest
1, // 1: greeter.Greeter.Hello:output_type -> greeter.HelloResponse
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_proto_greeter_proto_init() }
func file_proto_greeter_proto_init() {
if File_proto_greeter_proto != nil {
return
}
type x struct{}
out := protoimpl.TypeBuilder{
File: protoimpl.DescBuilder{
GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
RawDescriptor: unsafe.Slice(unsafe.StringData(file_proto_greeter_proto_rawDesc), len(file_proto_greeter_proto_rawDesc)),
NumEnums: 0,
NumMessages: 2,
NumExtensions: 0,
NumServices: 1,
},
GoTypes: file_proto_greeter_proto_goTypes,
DependencyIndexes: file_proto_greeter_proto_depIdxs,
MessageInfos: file_proto_greeter_proto_msgTypes,
}.Build()
File_proto_greeter_proto = out.File
file_proto_greeter_proto_goTypes = nil
file_proto_greeter_proto_depIdxs = nil
}
@@ -0,0 +1,79 @@
// Code generated by protoc-gen-micro. DO NOT EDIT.
// source: proto/greeter.proto
package proto
import (
fmt "fmt"
proto "google.golang.org/protobuf/proto"
math "math"
)
import (
context "context"
client "go-micro.dev/v6/client"
server "go-micro.dev/v6/server"
)
// Reference imports to suppress errors if they are not otherwise used.
var _ = proto.Marshal
var _ = fmt.Errorf
var _ = math.Inf
// Reference imports to suppress errors if they are not otherwise used.
var _ context.Context
var _ client.Option
var _ server.Option
// Client API for Greeter service
type GreeterService interface {
Hello(ctx context.Context, in *HelloRequest, opts ...client.CallOption) (*HelloResponse, error)
}
type greeterService struct {
c client.Client
name string
}
func NewGreeterService(name string, c client.Client) GreeterService {
return &greeterService{
c: c,
name: name,
}
}
func (c *greeterService) Hello(ctx context.Context, in *HelloRequest, opts ...client.CallOption) (*HelloResponse, error) {
req := c.c.NewRequest(c.name, "Greeter.Hello", in)
out := new(HelloResponse)
err := c.c.Call(ctx, req, out, opts...)
if err != nil {
return nil, err
}
return out, nil
}
// Server API for Greeter service
type GreeterHandler interface {
Hello(context.Context, *HelloRequest, *HelloResponse) error
}
func RegisterGreeterHandler(s server.Server, hdlr GreeterHandler, opts ...server.HandlerOption) error {
type greeter interface {
Hello(ctx context.Context, in *HelloRequest, out *HelloResponse) error
}
type Greeter struct {
greeter
}
h := &greeterHandler{hdlr}
return s.Handle(s.NewHandler(&Greeter{h}, opts...))
}
type greeterHandler struct {
GreeterHandler
}
func (h *greeterHandler) Hello(ctx context.Context, in *HelloRequest, out *HelloResponse) error {
return h.GreeterHandler.Hello(ctx, in, out)
}
+17
View File
@@ -0,0 +1,17 @@
syntax = "proto3";
package greeter;
option go_package = "./proto";
service Greeter {
rpc Hello(HelloRequest) returns (HelloResponse) {}
}
message HelloRequest {
string name = 1;
}
message HelloResponse {
string message = 1;
}
@@ -0,0 +1,121 @@
// Code generated by protoc-gen-go-grpc. DO NOT EDIT.
// versions:
// - protoc-gen-go-grpc v1.6.2
// - protoc v3.21.12
// source: proto/greeter.proto
package proto
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.64.0 or later.
const _ = grpc.SupportPackageIsVersion9
const (
Greeter_Hello_FullMethodName = "/greeter.Greeter/Hello"
)
// 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 {
Hello(ctx context.Context, in *HelloRequest, opts ...grpc.CallOption) (*HelloResponse, error)
}
type greeterClient struct {
cc grpc.ClientConnInterface
}
func NewGreeterClient(cc grpc.ClientConnInterface) GreeterClient {
return &greeterClient{cc}
}
func (c *greeterClient) Hello(ctx context.Context, in *HelloRequest, opts ...grpc.CallOption) (*HelloResponse, error) {
cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)
out := new(HelloResponse)
err := c.cc.Invoke(ctx, Greeter_Hello_FullMethodName, in, out, cOpts...)
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 {
Hello(context.Context, *HelloRequest) (*HelloResponse, error)
mustEmbedUnimplementedGreeterServer()
}
// UnimplementedGreeterServer must be embedded to have
// forward compatible implementations.
//
// NOTE: this should be embedded by value instead of pointer to avoid a nil
// pointer dereference when methods are called.
type UnimplementedGreeterServer struct{}
func (UnimplementedGreeterServer) Hello(context.Context, *HelloRequest) (*HelloResponse, error) {
return nil, status.Error(codes.Unimplemented, "method Hello not implemented")
}
func (UnimplementedGreeterServer) mustEmbedUnimplementedGreeterServer() {}
func (UnimplementedGreeterServer) testEmbeddedByValue() {}
// 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) {
// If the following call panics, it indicates UnimplementedGreeterServer was
// embedded by pointer and is nil. This will cause panics if an
// unimplemented method is ever invoked, so we test this at initialization
// time to prevent it from happening at runtime later due to I/O.
if t, ok := srv.(interface{ testEmbeddedByValue() }); ok {
t.testEmbeddedByValue()
}
s.RegisterService(&Greeter_ServiceDesc, srv)
}
func _Greeter_Hello_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).Hello(ctx, in)
}
info := &grpc.UnaryServerInfo{
Server: srv,
FullMethod: Greeter_Hello_FullMethodName,
}
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
return srv.(GreeterServer).Hello(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: "greeter.Greeter",
HandlerType: (*GreeterServer)(nil),
Methods: []grpc.MethodDesc{
{
MethodName: "Hello",
Handler: _Greeter_Hello_Handler,
},
},
Streams: []grpc.StreamDesc{},
Metadata: "proto/greeter.proto",
}
+53
View File
@@ -0,0 +1,53 @@
// server starts a go-micro gRPC service. Any standard gRPC client
// (Go, Python, Java, etc.) can call it — no go-micro SDK required on
// the client side.
package main
import (
"context"
"fmt"
"log"
micro "go-micro.dev/v6"
"go-micro.dev/v6/client"
grpcclient "go-micro.dev/v6/client/grpc"
"go-micro.dev/v6/server"
grpcserver "go-micro.dev/v6/server/grpc"
pb "example/proto"
)
type Greeter struct{}
func (g *Greeter) Hello(ctx context.Context, req *pb.HelloRequest, rsp *pb.HelloResponse) error {
log.Printf("Received request: name=%q", req.Name)
rsp.Message = "Hello " + req.Name
return nil
}
func main() {
addr := ":50051"
service := micro.NewService("greeter",
micro.Server(grpcserver.NewServer(
server.Name("greeter"),
server.Address(addr),
)),
micro.Client(grpcclient.NewClient(
client.ContentType("application/grpc+proto"),
)),
)
service.Init()
pb.RegisterGreeterHandler(service.Server(), new(Greeter))
fmt.Println("Go-Micro gRPC server listening on", addr)
fmt.Println()
fmt.Println("Call with a standard gRPC client:")
fmt.Println(" go run ../client/main.go")
if err := service.Run(); err != nil {
log.Fatal(err)
}
}
+62
View File
@@ -0,0 +1,62 @@
# Hello World Example
The simplest go-micro service demonstrating core concepts.
## What It Does
This example creates a basic RPC service that:
- Listens on port 8080
- Exposes a `Greeter.Hello` method
- Returns a greeting message
- Demonstrates both programmatic and HTTP access
## Run It
```bash
go run main.go
```
The service will start and make test calls to itself, then wait for incoming requests.
## Test It
### Using curl
```bash
curl -X POST http://localhost:8080 \
-H 'Content-Type: application/json' \
-H 'Micro-Endpoint: Greeter.Hello' \
-d '{"name": "Alice"}'
```
Expected response:
```json
{"message": "Hello Alice"}
```
### Using the micro CLI
```bash
micro call greeter Greeter.Hello '{"name": "Bob"}'
```
## Code Walkthrough
1. **Define types** - Request and Response structures
2. **Implement handler** - The `Greeter` service with `Hello` method
3. **Create service** - Using `micro.NewService()` with options
4. **Register handler** - Link the handler to the service
5. **Run service** - Start listening for requests
## Key Concepts
- **RPC Pattern**: Method signature `func(ctx, req, rsp) error`
- **Service Discovery**: Automatic registration
- **Multiple Transports**: Works over HTTP, gRPC, etc.
- **Type Safety**: Strongly typed requests/responses
## Next Steps
- See [pubsub-events](../pubsub-events/) for event-driven patterns
- See [production-ready](../production-ready/) for a complete example
- Read the [Getting Started Guide](../../internal/website/docs/getting-started.md)
+70
View File
@@ -0,0 +1,70 @@
module example
go 1.24
require go-micro.dev/v5 v5.16.0
require (
dario.cat/mergo v1.0.2 // indirect
github.com/armon/go-metrics v0.4.1 // indirect
github.com/bitly/go-simplejson v0.5.0 // indirect
github.com/cespare/xxhash/v2 v2.3.0 // indirect
github.com/coreos/go-semver v0.3.0 // indirect
github.com/coreos/go-systemd/v22 v22.3.2 // indirect
github.com/cornelk/hashmap v1.0.8 // indirect
github.com/cpuguy83/go-md2man/v2 v2.0.5 // indirect
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect
github.com/fatih/color v1.16.0 // indirect
github.com/fsnotify/fsnotify v1.6.0 // indirect
github.com/go-redis/redis/v8 v8.11.5 // indirect
github.com/gogo/protobuf v1.3.2 // indirect
github.com/golang/protobuf v1.5.4 // indirect
github.com/google/uuid v1.6.0 // indirect
github.com/hashicorp/consul/api v1.32.1 // 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 v1.0.2 // indirect
github.com/hashicorp/serf v0.10.1 // indirect
github.com/klauspost/compress v1.18.0 // indirect
github.com/lib/pq v1.10.9 // indirect
github.com/mattn/go-colorable v0.1.13 // indirect
github.com/mattn/go-isatty v0.0.20 // indirect
github.com/miekg/dns v1.1.50 // indirect
github.com/mitchellh/go-homedir v1.1.0 // indirect
github.com/mitchellh/hashstructure v1.1.0 // indirect
github.com/mitchellh/mapstructure v1.5.0 // indirect
github.com/nats-io/nats.go v1.42.0 // indirect
github.com/nats-io/nkeys v0.4.11 // indirect
github.com/nats-io/nuid v1.0.1 // indirect
github.com/oxtoacart/bpool v0.0.0-20190530202638-03653db5a59c // indirect
github.com/patrickmn/go-cache v2.1.0+incompatible // indirect
github.com/pkg/errors v0.9.1 // indirect
github.com/rabbitmq/amqp091-go v1.10.0 // indirect
github.com/russross/blackfriday/v2 v2.1.0 // indirect
github.com/urfave/cli/v2 v2.27.6 // indirect
github.com/xrash/smetrics v0.0.0-20240521201337-686a1a2994c1 // indirect
go.etcd.io/bbolt v1.4.0 // indirect
go.etcd.io/etcd/api/v3 v3.5.21 // indirect
go.etcd.io/etcd/client/pkg/v3 v3.5.21 // indirect
go.etcd.io/etcd/client/v3 v3.5.21 // indirect
go.uber.org/multierr v1.10.0 // indirect
go.uber.org/zap v1.27.0 // indirect
golang.org/x/crypto v0.37.0 // indirect
golang.org/x/exp v0.0.0-20250305212735-054e65f0b394 // indirect
golang.org/x/mod v0.24.0 // indirect
golang.org/x/net v0.38.0 // indirect
golang.org/x/sync v0.13.0 // indirect
golang.org/x/sys v0.32.0 // indirect
golang.org/x/text v0.24.0 // indirect
golang.org/x/tools v0.31.0 // indirect
google.golang.org/genproto/googleapis/api v0.0.0-20250324211829-b45e905df463 // indirect
google.golang.org/genproto/googleapis/rpc v0.0.0-20250324211829-b45e905df463 // indirect
google.golang.org/grpc v1.71.1 // indirect
google.golang.org/protobuf v1.36.6 // indirect
)
replace go-micro.dev/v5 => ../..
+385
View File
@@ -0,0 +1,385 @@
dario.cat/mergo v1.0.2 h1:85+piFYR1tMbRrLcDwR18y4UKJ3aH1Tbzi24VRW1TK8=
dario.cat/mergo v1.0.2/go.mod h1:E/hbnu0NxMFBjpMIE34DRGLWqDy0g5FuKDhCb31ngxA=
filippo.io/edwards25519 v1.1.0 h1:FNf4tywRC1HmFuKW5xopWpigGjJKiJSV0Cqo0cJWDaA=
filippo.io/edwards25519 v1.1.0/go.mod h1:BxyFTGdWcka3PhytdK4V28tE5sGfRvvvRV7EaN4VDT4=
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/bitly/go-simplejson v0.5.0 h1:6IH+V8/tVMab511d5bn4M7EwGXZf9Hj6i2xSwkNEM+Y=
github.com/bitly/go-simplejson v0.5.0/go.mod h1:cXHtHw4XUPsvGaxgjIAn8PhEWG9NfngEKAMDJEczWVA=
github.com/bmizerany/assert v0.0.0-20160611221934-b7ed37b82869 h1:DDGfHa7BWjL4YnC6+E63dPcxHo2sUxDIu8g3QgEJdRY=
github.com/bmizerany/assert v0.0.0-20160611221934-b7ed37b82869/go.mod h1:Ekp36dRnpXw/yCqJaO+ZrUyxD+3VXMFFr56k5XYrpB4=
github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs=
github.com/cespare/xxhash/v2 v2.3.0/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/coreos/go-semver v0.3.0 h1:wkHLiw0WNATZnSG7epLsujiMCgPAc9xhjJ4tgnAxmfM=
github.com/coreos/go-semver v0.3.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk=
github.com/coreos/go-systemd/v22 v22.3.2 h1:D9/bQk5vlXQFZ6Kwuu6zaiXJ9oTPe68++AzAJc1DzSI=
github.com/coreos/go-systemd/v22 v22.3.2/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc=
github.com/cornelk/hashmap v1.0.8 h1:nv0AWgw02n+iDcawr5It4CjQIAcdMMKRrs10HOJYlrc=
github.com/cornelk/hashmap v1.0.8/go.mod h1:RfZb7JO3RviW/rT6emczVuC/oxpdz4UsSB2LJSclR1k=
github.com/cpuguy83/go-md2man/v2 v2.0.5 h1:ZtcqGrnekaHpVLArFSe4HK5DoKx1T0rq2DwVB0alcyc=
github.com/cpuguy83/go-md2man/v2 v2.0.5/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
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/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f h1:lO4WD4F/rVNCu3HqELle0jiPLLBs70cWOduZpkS1E78=
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f/go.mod h1:cuUVRXasLTGF7a8hSLbxyZXjz+1KgoB3wDUb6vlszIc=
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.16.0 h1:zmkK9Ngbjj+K0yRhTVONQh1p/HknKYSlNT+vZCzyokM=
github.com/fatih/color v1.16.0/go.mod h1:fL2Sau1YI5c0pdGEVCbKQbLXB6edEj1ZgiY4NijnWvE=
github.com/fsnotify/fsnotify v1.6.0 h1:n+5WquG0fcWoWp6xPWfHdbskMCQaFnG6PfBrh1Ky4HY=
github.com/fsnotify/fsnotify v1.6.0/go.mod h1:sl3t1tCWJFWoRz9R8WJCbQihKKwmorjAbSClcnxKAGw=
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.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-redis/redis/v8 v8.11.5 h1:AcZZR7igkdvfVmQTPnu9WE37LRrO/YrBH5zWyjDC0oI=
github.com/go-redis/redis/v8 v8.11.5/go.mod h1:gREzHqY1hg6oD9ngVRbLStwAWKhA0FEgq8Jd4h5lpwo=
github.com/go-sql-driver/mysql v1.9.2 h1:4cNKDYQ1I84SXslGddlsrMhc8k4LeDVj6Ad6WRjiHuU=
github.com/go-sql-driver/mysql v1.9.2/go.mod h1:qn46aNg1333BRMNU69Lq93t8du/dwxI64Gl8i5p1WMU=
github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY=
github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=
github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q=
github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q=
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/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek=
github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps=
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/btree v1.0.1/go.mod h1:xXMiIv4Fb/0kKde4SpL7qlzvu5cMJDRkFDxJfI9uaxA=
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/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
github.com/google/go-tpm v0.9.3 h1:+yx0/anQuGzi+ssRqeD6WpXjW2L/V0dItUayO0i9sRc=
github.com/google/go-tpm v0.9.3/go.mod h1:h9jEsEECg7gtLis0upRBQU+GhYVH6jMjrFxI8u6bVUY=
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/hashicorp/consul/api v1.32.1 h1:0+osr/3t/aZNAdJX558crU3PEjVrG4x6715aZHRgceE=
github.com/hashicorp/consul/api v1.32.1/go.mod h1:mXUWLnxftwTmDv4W3lzxYCPD199iNLLUyLfLGFJbtl4=
github.com/hashicorp/consul/sdk v0.16.1 h1:V8TxTnImoPD5cj0U9Spl0TUxcytjcbbJeADFF07KdHg=
github.com/hashicorp/consul/sdk v0.16.1/go.mod h1:fSXvwxB2hmh1FMZCNl6PwX0Q/1wdWtHJcZ7Ea5tns0s=
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-msgpack v0.5.5/go.mod h1:ahLV/dePpqEmjfWmKiqvPkv/twdG7iPBM1vqhUKIvfM=
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-sockaddr v1.0.2/go.mod h1:rB4wwRAUzs07qva3c5SdrY/NEtAUjGlgmH/UkBUC97A=
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-uuid v1.0.3/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro=
github.com/hashicorp/go-version v1.2.1 h1:zEfKbn2+PDgroKdiOzqiE8rsmLqU2uwi5PB5pBJ3TkI=
github.com/hashicorp/go-version v1.2.1/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA=
github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
github.com/hashicorp/golang-lru v1.0.2 h1:dV3g9Z/unq5DpblPpw+Oqcv4dU/1omnb4Ok8iPY6p1c=
github.com/hashicorp/golang-lru v1.0.2/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/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8=
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
github.com/klauspost/compress v1.18.0 h1:c/Cqfb0r+Yi+JtIEq73FWXVkRonBlf0CRNYc8Zttxdo=
github.com/klauspost/compress v1.18.0/go.mod h1:2Pp+KzxcywXVXMr50+X0Q/Lsb43OQHYWRCY2AiWywWQ=
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/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
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/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
github.com/lib/pq v1.10.9 h1:YXG7RB+JIjhP29X+OtkiDnYaXQwpS4JEWq7dtCCRUEw=
github.com/lib/pq v1.10.9/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o=
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/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/go.mod h1:p6aan82bvRIyn+zDIv9xYNUpwa73JcSh9BKwknJysuI=
github.com/miekg/dns v1.1.50 h1:DQUfb9uc6smULcREF09Uc+/Gd46YWqJd5DbpPE9xkcA=
github.com/miekg/dns v1.1.50/go.mod h1:e3IlAVfNqAllflbibAZEWOXOQ+Ynzk/dDozDxY7XnME=
github.com/minio/highwayhash v1.0.3 h1:kbnuUMoHYyVl7szWjSxJnxw11k2U709jqFPPmIUyD6Q=
github.com/minio/highwayhash v1.0.3/go.mod h1:GGYsuwP/fPD6Y9hMiXuapVvlIUEhFhMTh0rxU3ik1LQ=
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/hashstructure v1.1.0 h1:P6P1hdjqAAknpY/M1CGipelZgp+4y9ja9kmUZPXP+H0=
github.com/mitchellh/hashstructure v1.1.0/go.mod h1:xUDAozZz0Wmdiufv0uyhnHkUTN6/6d8ulp4AwfLKrmA=
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/nats-io/jwt/v2 v2.7.4 h1:jXFuDDxs/GQjGDZGhNgH4tXzSUK6WQi2rsj4xmsNOtI=
github.com/nats-io/jwt/v2 v2.7.4/go.mod h1:me11pOkwObtcBNR8AiMrUbtVOUGkqYjMQZ6jnSdVUIA=
github.com/nats-io/nats-server/v2 v2.11.3 h1:AbGtXxuwjo0gBroLGGr/dE0vf24kTKdRnBq/3z/Fdoc=
github.com/nats-io/nats-server/v2 v2.11.3/go.mod h1:6Z6Fd+JgckqzKig7DYwhgrE7bJ6fypPHnGPND+DqgMY=
github.com/nats-io/nats.go v1.42.0 h1:ynIMupIOvf/ZWH/b2qda6WGKGNSjwOUutTpWRvAmhaM=
github.com/nats-io/nats.go v1.42.0/go.mod h1:iRWIPokVIFbVijxuMQq4y9ttaBTMe0SFdlZfMDd+33g=
github.com/nats-io/nkeys v0.4.11 h1:q44qGV008kYd9W1b1nEBkNzvnWxtRSQ7A8BoqRrcfa0=
github.com/nats-io/nkeys v0.4.11/go.mod h1:szDimtgmfOi9n25JpfIdGw12tZFYXqhGxjhVxsatHVE=
github.com/nats-io/nuid v1.0.1 h1:5iA8DT8V7q8WK2EScv2padNa/rTESc1KdnPw4TC2paw=
github.com/nats-io/nuid v1.0.1/go.mod h1:19wcPz3Ph3q0Jbyiqsd0kePYG7A95tJPxeL+1OSON2c=
github.com/nxadm/tail v1.4.8 h1:nPr65rt6Y5JFSKQO7qToXr7pePgD6Gwiw05lkbyAQTE=
github.com/nxadm/tail v1.4.8/go.mod h1:+ncqLTQzXmGhMZNUePPaPqPvBxHAIsmXswZKocGu+AU=
github.com/onsi/ginkgo v1.16.5 h1:8xi0RTUf59SOSfEtZMvwTvXYMzG4gV23XVHOZiXNtnE=
github.com/onsi/ginkgo v1.16.5/go.mod h1:+E8gABHa3K6zRBolWtd+ROzc/U5bkGt0FwiG042wbpU=
github.com/onsi/gomega v1.18.1 h1:M1GfJqGRrBrrGGsbxzV5dqM2U2ApXefZCQpkukxYRLE=
github.com/onsi/gomega v1.18.1/go.mod h1:0q+aL8jAiMXy9hbwj2mr5GziHiwhAIQpFmmtT5hitRs=
github.com/oxtoacart/bpool v0.0.0-20190530202638-03653db5a59c h1:rp5dCmg/yLR3mgFuSOe4oEnDDmGLROTvMragMUXpTQw=
github.com/oxtoacart/bpool v0.0.0-20190530202638-03653db5a59c/go.mod h1:X07ZCGwUbLaax7L0S3Tw4hpejzu63ZrrQiUe6W0hcy0=
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/patrickmn/go-cache v2.1.0+incompatible h1:HRMgzkcYKYpi3C8ajMPV8OFXaaRUnok+kx1WdO15EQc=
github.com/patrickmn/go-cache v2.1.0+incompatible/go.mod h1:3Qf8kWWT7OJRJbdiICTKqZju1ZixQ/KpMGzzAfe6+WQ=
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/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
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/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
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/rabbitmq/amqp091-go v1.10.0 h1:STpn5XsHlHGcecLmMFCtg7mqq0RnD+zFr4uzukfVhBw=
github.com/rabbitmq/amqp091-go v1.10.0/go.mod h1:Hy4jKW5kQART1u+JkDTF9YYOQUHXqMuhrgxOEeS7G4o=
github.com/rogpeppe/go-internal v1.13.1 h1:KvO1DLK/DRN07sQ1LQKScxyZJuNnedQ5/wKSR38lUII=
github.com/rogpeppe/go-internal v1.13.1/go.mod h1:uMEvuHeurkdAXX61udpOXGD/AzZDWNMNyH2VO9fmH0o=
github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk=
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
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.2 h1:xuMeJ0Sdp5ZMRXx/aWO6RZxdr3beISkG5/G/aIRr3pY=
github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA=
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.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA=
github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
github.com/test-go/testify v1.1.4 h1:Tf9lntrKUMHiXQ07qBScBTSA0dhYQlu83hswqelv1iE=
github.com/test-go/testify v1.1.4/go.mod h1:rH7cfJo/47vWGdi4GPj16x3/t1xGOj2YxzmNQzk2ghU=
github.com/tv42/httpunix v0.0.0-20150427012821-b75d8614f926/go.mod h1:9ESjWnEqriFuLhtthL60Sar/7RFoluCcXsuvEwTV5KM=
github.com/urfave/cli/v2 v2.27.6 h1:VdRdS98FNhKZ8/Az8B7MTyGQmpIr36O1EHybx/LaZ4g=
github.com/urfave/cli/v2 v2.27.6/go.mod h1:3Sevf16NykTbInEnD0yKkjDAeZDS0A6bzhBH5hrMvTQ=
github.com/xrash/smetrics v0.0.0-20240521201337-686a1a2994c1 h1:gEOO8jv9F4OT7lGCjxCBTO/36wtF6j2nSip77qHd4x4=
github.com/xrash/smetrics v0.0.0-20240521201337-686a1a2994c1/go.mod h1:Ohn+xnUBiLI6FVj/9LpzZWtj1/D6lUovWYBkxHVV3aM=
github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k=
go.etcd.io/bbolt v1.4.0 h1:TU77id3TnN/zKr7CO/uk+fBCwF2jGcMuw2B/FMAzYIk=
go.etcd.io/bbolt v1.4.0/go.mod h1:AsD+OCi/qPN1giOX1aiLAha3o1U8rAz65bvN4j0sRuk=
go.etcd.io/etcd/api/v3 v3.5.21 h1:A6O2/JDb3tvHhiIz3xf9nJ7REHvtEFJJ3veW3FbCnS8=
go.etcd.io/etcd/api/v3 v3.5.21/go.mod h1:c3aH5wcvXv/9dqIw2Y810LDXJfhSYdHQ0vxmP3CCHVY=
go.etcd.io/etcd/client/pkg/v3 v3.5.21 h1:lPBu71Y7osQmzlflM9OfeIV2JlmpBjqBNlLtcoBqUTc=
go.etcd.io/etcd/client/pkg/v3 v3.5.21/go.mod h1:BgqT/IXPjK9NkeSDjbzwsHySX3yIle2+ndz28nVsjUs=
go.etcd.io/etcd/client/v3 v3.5.21 h1:T6b1Ow6fNjOLOtM0xSoKNQt1ASPCLWrF9XMHcH9pEyY=
go.etcd.io/etcd/client/v3 v3.5.21/go.mod h1:mFYy67IOqmbRf/kRUvsHixzo3iG+1OF2W2+jVIQRAnU=
go.opentelemetry.io/auto/sdk v1.1.0 h1:cH53jehLUN6UFLY71z+NDOiNJqDdPRaXzTel0sJySYA=
go.opentelemetry.io/auto/sdk v1.1.0/go.mod h1:3wSPjt5PWp2RhlCcmmOial7AvC4DQqZb7a7wCow3W8A=
go.opentelemetry.io/otel v1.35.0 h1:xKWKPxrxB6OtMCbmMY021CqC45J+3Onta9MqjhnusiQ=
go.opentelemetry.io/otel v1.35.0/go.mod h1:UEqy8Zp11hpkUrL73gSlELM0DupHoiq72dR+Zqel/+Y=
go.opentelemetry.io/otel/metric v1.35.0 h1:0znxYu2SNyuMSQT4Y9WDWej0VpcsxkuklLa4/siN90M=
go.opentelemetry.io/otel/metric v1.35.0/go.mod h1:nKVFgxBZ2fReX6IlyW28MgZojkoAkJGaE8CpgeAU3oE=
go.opentelemetry.io/otel/sdk v1.35.0 h1:iPctf8iprVySXSKJffSS79eOjl9pvxV9ZqOWT0QejKY=
go.opentelemetry.io/otel/sdk v1.35.0/go.mod h1:+ga1bZliga3DxJ3CQGg3updiaAJoNECOgJREo9KHGQg=
go.opentelemetry.io/otel/sdk/metric v1.34.0 h1:5CeK9ujjbFVL5c1PhLuStg1wxA7vQv7ce1EK0Gyvahk=
go.opentelemetry.io/otel/sdk/metric v1.34.0/go.mod h1:jQ/r8Ze28zRKoNRdkjCZxfs6YvBTG1+YIqyFVFYec5w=
go.opentelemetry.io/otel/trace v1.35.0 h1:dPpEfJu1sDIqruz7BHFG3c7528f6ddfSWfFDVt/xgMs=
go.opentelemetry.io/otel/trace v1.35.0/go.mod h1:WUk7DtFp1Aw2MkvqGdwiXYDZZNvA/1J8o6xRXLrIkyc=
go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto=
go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE=
go.uber.org/multierr v1.10.0 h1:S0h4aNzvfcFsC3dRF1jLoaov7oRaKqRGC/pUEJ2yvPQ=
go.uber.org/multierr v1.10.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y=
go.uber.org/zap v1.27.0 h1:aJMhYGrd5QSmlpLMr2MftRKl7t8J8PTZPA732ud/XR8=
go.uber.org/zap v1.27.0/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E=
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/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.37.0 h1:kJNSjF/Xp7kU0iB2Z+9viTPMW4EqqsrywMXLJOOsXSE=
golang.org/x/crypto v0.37.0/go.mod h1:vg+k43peMZ0pUMhYmVAWysMK35e6ioLh3wB8ZCAfbVc=
golang.org/x/exp v0.0.0-20250305212735-054e65f0b394 h1:nDVHiLt8aIbd/VzvPWN6kSOPE7+F/fNFDSXLVYkE/Iw=
golang.org/x/exp v0.0.0-20250305212735-054e65f0b394/go.mod h1:sIifuuw/Yco/y6yb6+bDNfyeQ/MdPUy/hKEMYQV17cM=
golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.24.0 h1:ZfthKaKaT4NrhGVZHO1/WDTwGES4De8KtWO0SIbNJMU=
golang.org/x/mod v0.24.0/go.mod h1:IXM97Txy2VM4PJ3gI61r1YEk/gAj6zAHN3AdZt6S9Ww=
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-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM=
golang.org/x/net v0.0.0-20210410081132-afb366fc7cd1/go.mod h1:9tjilg8BloeKEkVJvy7fQ90B1CfIiPueXVOjqfkSzI8=
golang.org/x/net v0.0.0-20210726213435-c6fcb2dbf985/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/net v0.38.0 h1:vRMAPTMaeGqVhG5QyLJHqNDwecKTomGeqbnfZyKlBI8=
golang.org/x/net v0.38.0/go.mod h1:ivrbrMbzFq5J41QOQh0siUuly180yBYtLp+CKbEaFx8=
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-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.13.0 h1:AauUjRAJ9OSnvULf/ARrrVywoJDy0YS2AwQ98I37610=
golang.org/x/sync v0.13.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA=
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-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
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-20200930185726-fdedc70b468f/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-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
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.0.0-20220908164124-27713097b956/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.32.0 h1:s77OFDvIQeibCmezSnk/q6iAfkdiQaJi4VzroCFrN20=
golang.org/x/sys v0.32.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
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.24.0 h1:dd5Bzh4yt5KYA8f9CJHCP4FB4D51c2c6JvN37xJJkJ0=
golang.org/x/text v0.24.0/go.mod h1:L8rBsPeo2pSS+xqN0d5u2ikmjtmoJbDBT1b7nHvFCdU=
golang.org/x/time v0.11.0 h1:/bpjEDfN9tkoN/ryeYHnv5hcMlc8ncjMcM4XBk5NWV0=
golang.org/x/time v0.11.0/go.mod h1:CDIdPxbZBQxdj6cxyCIdrNogrJKMJ7pr37NYpMcMDSg=
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/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
golang.org/x/tools v0.1.6-0.20210726203631-07bc1bf47fb2/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
golang.org/x/tools v0.31.0 h1:0EedkvKDbh+qistFTd0Bcwe/YLh4vHwWEkiI0toFIBU=
golang.org/x/tools v0.31.0/go.mod h1:naFTU+Cev749tSJRXJlna0T3WxKvb1kWEx15xA4SdmQ=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
google.golang.org/genproto/googleapis/api v0.0.0-20250324211829-b45e905df463 h1:hE3bRWtU6uceqlh4fhrSnUyjKHMKB9KrTLLG+bc0ddM=
google.golang.org/genproto/googleapis/api v0.0.0-20250324211829-b45e905df463/go.mod h1:U90ffi8eUL9MwPcrJylN5+Mk2v3vuPDptd5yyNUiRR8=
google.golang.org/genproto/googleapis/rpc v0.0.0-20250324211829-b45e905df463 h1:e0AIkUUhxyBKh6ssZNrAMeqhA7RKUj42346d1y02i2g=
google.golang.org/genproto/googleapis/rpc v0.0.0-20250324211829-b45e905df463/go.mod h1:qQ0YXyHHx3XkvlzUtpXDkS29lDSafHMZBAZDc03LQ3A=
google.golang.org/grpc v1.71.1 h1:ffsFWr7ygTUscGPI0KKK6TLrGz0476KUvvsbqWK0rPI=
google.golang.org/grpc v1.71.1/go.mod h1:H0GRtasmQOh9LkFoCPDu3ZrwUtD1YGE+b2vYBYd/8Ec=
google.golang.org/protobuf v1.36.6 h1:z1NpPI8ku2WgiWnf+t9wTPsn6eP1L7ksHUlkfLvd9xY=
google.golang.org/protobuf v1.36.6/go.mod h1:jduwjTPXsFjZGTmRluh+L6NjiWu7pchiJ2/5YcXBHnY=
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/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ=
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw=
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.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
+55
View File
@@ -0,0 +1,55 @@
package main
import (
"context"
"fmt"
"log"
"go-micro.dev/v6"
)
// Request and Response types
type Request struct {
Name string `json:"name"`
}
type Response struct {
Message string `json:"message"`
}
// Greeter service handler
type Greeter struct{}
// Hello is the RPC method handler
func (g *Greeter) Hello(ctx context.Context, req *Request, rsp *Response) error {
rsp.Message = "Hello " + req.Name
log.Printf("Received request: %s", req.Name)
return nil
}
func main() {
// Create a new service
service := micro.NewService("greeter", micro.Address(":8080"))
// Initialize the service
service.Init()
// Register the handler
if err := service.Handle(new(Greeter)); err != nil {
log.Fatal(err)
}
fmt.Println("Starting greeter service on :8080")
fmt.Println()
fmt.Println("Test with:")
fmt.Println(" curl -XPOST \\")
fmt.Println(" -H 'Content-Type: application/json' \\")
fmt.Println(" -H 'Micro-Endpoint: Greeter.Hello' \\")
fmt.Println(" -d '{\"name\": \"Alice\"}' \\")
fmt.Println(" http://localhost:8080")
// Run the service
if err := service.Run(); err != nil {
log.Fatal(err)
}
}
+246
View File
@@ -0,0 +1,246 @@
# MCP Examples
Examples demonstrating Model Context Protocol (MCP) integration with go-micro.
## Examples
### [hello](./hello/) - Minimal Example ⭐ Start Here
The simplest possible MCP-enabled service. Perfect for learning the basics.
**What it shows:**
- Automatic documentation extraction from Go comments
- MCP gateway setup with 3 lines
- Ready for Claude Code
**Run it:**
```bash
cd hello
go run main.go
```
### [crud](./crud/) - CRUD Contact Book
A realistic service with create, read, update, delete, list, and search operations. Shows how to document a full API for agents with `@example` tags, `description` struct tags, validation errors, and partial updates.
**Run it:**
```bash
cd crud
go run main.go
```
### [workflow](./workflow/) - Cross-Service Orchestration
Three services (Inventory, Orders, Notifications) showing how an AI agent orchestrates multi-step workflows: search products, check stock, reserve inventory, place order, send confirmation — all from a single natural language request.
**Run it:**
```bash
cd workflow
go run main.go
```
### [platform](./platform/) - Agent Platform Showcase
A complete platform (Users, Posts, Comments, Mail) mirroring [micro/blog](https://github.com/micro/blog). Shows how existing microservices become agent-accessible with zero code changes — agents can sign up, write posts, comment, tag, and send mail through natural language.
**Run it:**
```bash
cd platform
go run main.go
```
### [documented](./documented/) - Full-Featured Example
Complete example showing all MCP features with a user service.
**What it shows:**
- Multiple endpoints (GetUser, CreateUser)
- Rich documentation with examples
- Per-endpoint auth scopes via `server.WithEndpointScopes()`
- Pre-populated test data
- Production-ready patterns
**Run it:**
```bash
cd documented
go run main.go
```
## Quick Start
### 1. Write Your Service
Add Go doc comments to your handler methods:
```go
// SayHello greets a person by name. Returns a friendly greeting message.
//
// @example {"name": "Alice"}
func (g *Greeter) SayHello(ctx context.Context, req *HelloRequest, rsp *HelloResponse) error {
rsp.Message = "Hello " + req.Name + "!"
return nil
}
type HelloRequest struct {
Name string `json:"name" description:"Person's name to greet"`
}
```
### 2. Register Handler (Auto-Extracts Docs!)
```go
handler := service.Server().NewHandler(new(Greeter))
service.Server().Handle(handler)
```
### 3. Start MCP Gateway
```go
go mcp.ListenAndServe(":3000", mcp.Options{
Registry: service.Options().Registry,
})
```
## Testing
### HTTP API
```bash
# List tools
curl http://localhost:3000/mcp/tools | jq
# Call a tool
curl -X POST http://localhost:3000/mcp/call \
-H "Content-Type: application/json" \
-d '{
"tool": "greeter.Greeter.SayHello",
"input": {"name": "Alice"}
}' | jq
```
### Claude Code (Stdio)
Start MCP server:
```bash
micro mcp serve
```
Add to `~/.claude/claude_desktop_config.json`:
```json
{
"mcpServers": {
"my-services": {
"command": "micro",
"args": ["mcp", "serve"]
}
}
}
```
Restart Claude Code and ask Claude to use your services!
## Features
### ✅ Automatic Documentation Extraction
Just write Go comments - documentation is extracted automatically:
- **Go doc comments** → Tool descriptions
- **@example tags** → Example inputs for AI
- **Struct tags** → Parameter descriptions
### ✅ Multiple Transports
- **Stdio** - For Claude Code (recommended)
- **HTTP/SSE** - For web-based agents
### ✅ MCP Command Line
```bash
# Start MCP server
micro mcp serve # Stdio (for Claude Code)
micro mcp serve --address :3000 # HTTP/SSE (for web agents)
# List available tools
micro mcp list # Human-readable list
micro mcp list --json # JSON output
# Test a tool
micro mcp test <tool-name> '{"key": "value"}'
# Generate documentation
micro mcp docs # Markdown format
micro mcp docs --format json # JSON format
micro mcp docs --output tools.md # Save to file
# Export to different formats
micro mcp export langchain # Python LangChain tools
micro mcp export openapi # OpenAPI 3.0 spec
micro mcp export json # Raw JSON definitions
```
For detailed examples, see [CLI Examples](../../cmd/micro/mcp/EXAMPLES.md).
### ✅ Zero Configuration
- No manual tool registration
- No API wrappers
- No code generation
- Just write normal Go code!
### ✅ Per-Tool Auth Scopes
Declare required scopes when registering a handler:
```go
handler := service.Server().NewHandler(
new(BlogService),
server.WithEndpointScopes("Blog.Create", "blog:write"),
server.WithEndpointScopes("Blog.Delete", "blog:admin"),
)
```
Or define scopes at the gateway layer without changing services:
```go
mcp.Serve(mcp.Options{
Registry: reg,
Auth: authProvider,
Scopes: map[string][]string{
"blog.Blog.Create": {"blog:write"},
"blog.Blog.Delete": {"blog:admin"},
},
})
```
### ✅ Tracing, Rate Limiting & Audit Logging
Every tool call generates a trace ID that propagates through the RPC chain.
Configure rate limiting and audit logging at the gateway:
```go
mcp.Serve(mcp.Options{
Registry: reg,
Auth: authProvider,
RateLimit: &mcp.RateLimitConfig{
RequestsPerSecond: 10,
Burst: 20,
},
AuditFunc: func(r mcp.AuditRecord) {
log.Printf("[audit] trace=%s tool=%s account=%s allowed=%v",
r.TraceID, r.Tool, r.AccountID, r.Allowed)
},
})
```
## Documentation
- [Full MCP Documentation](../../internal/website/docs/mcp.md)
- [MCP Gateway Implementation](../../gateway/mcp/)
- [Documentation Guide](../../gateway/mcp/DOCUMENTATION.md)
- [Blog Post](../../internal/website/blog/2.md)
## Learn More
- [Model Context Protocol Spec](https://modelcontextprotocol.io/)
- [Go Micro Documentation](https://go-micro.dev)
+74
View File
@@ -0,0 +1,74 @@
# CRUD Contact Book Example
A complete CRUD service with MCP integration — the kind of service you'd actually build in production.
## What This Shows
- **6 operations**: Create, Get, Update, Delete, List, Search
- **Rich documentation**: Every handler has doc comments with `@example` tags
- **Struct tag descriptions**: All fields have `description` tags for agents
- **Input validation**: Required field checks with clear error messages
- **Partial updates**: Update only changes non-empty fields
- **Seed data**: Starts with 3 contacts so agents can explore immediately
## Run
```bash
go run .
```
## Test
```bash
# List all MCP tools
curl http://localhost:3001/mcp/tools | jq
# Create a contact
curl -X POST http://localhost:3001/mcp/call \
-H 'Content-Type: application/json' \
-d '{"tool": "contacts.Contacts.Create", "arguments": {"name": "Dave", "email": "dave@example.com"}}'
# Search contacts
curl -X POST http://localhost:3001/mcp/call \
-H 'Content-Type: application/json' \
-d '{"tool": "contacts.Contacts.Search", "arguments": {"query": "engineer"}}'
```
## Use with Claude Code
```bash
micro mcp serve
```
Then ask: "List all contacts and find the engineers."
## Key Patterns
### Doc Comments for Agents
```go
// Create adds a new contact to the book. Name and email are required.
//
// @example {"name": "Dave Wilson", "email": "dave@example.com", "role": "Engineer"}
func (h *Contacts) Create(ctx context.Context, req *CreateRequest, rsp *CreateResponse) error {
```
### Struct Tag Descriptions
```go
type Contact struct {
ID string `json:"id" description:"Unique contact identifier"`
Name string `json:"name" description:"Full name"`
Email string `json:"email" description:"Email address"`
}
```
### Partial Updates
Only update fields that are provided (non-empty), so agents can change one field without overwriting others:
```go
if req.Name != "" {
contact.Name = req.Name
}
```
+278
View File
@@ -0,0 +1,278 @@
// CRUD example: a contact book service with full MCP integration.
//
// This shows a realistic service with create, read, update, delete, and
// search operations, all automatically exposed as MCP tools with rich
// documentation for AI agents.
//
// Run:
//
// go run .
//
// MCP tools: http://localhost:3001/mcp/tools
// Test: curl http://localhost:3001/mcp/tools | jq
package main
import (
"context"
"fmt"
"log"
"strings"
"sync"
"go-micro.dev/v6"
"go-micro.dev/v6/gateway/mcp"
)
// --- Types ---
// Contact represents a person in the contact book.
type Contact struct {
ID string `json:"id" description:"Unique contact identifier"`
Name string `json:"name" description:"Full name"`
Email string `json:"email" description:"Email address"`
Phone string `json:"phone" description:"Phone number in E.164 format"`
Role string `json:"role" description:"Job title or role"`
Notes string `json:"notes" description:"Free-text notes about this contact"`
}
type CreateRequest struct {
Name string `json:"name" description:"Full name (required)"`
Email string `json:"email" description:"Email address (required)"`
Phone string `json:"phone" description:"Phone number"`
Role string `json:"role" description:"Job title or role"`
Notes string `json:"notes" description:"Free-text notes"`
}
type CreateResponse struct {
Contact *Contact `json:"contact" description:"The newly created contact"`
}
type GetRequest struct {
ID string `json:"id" description:"Contact ID to look up"`
}
type GetResponse struct {
Contact *Contact `json:"contact" description:"The requested contact"`
}
type UpdateRequest struct {
ID string `json:"id" description:"Contact ID to update (required)"`
Name string `json:"name" description:"New name (leave empty to keep current)"`
Email string `json:"email" description:"New email (leave empty to keep current)"`
Phone string `json:"phone" description:"New phone (leave empty to keep current)"`
Role string `json:"role" description:"New role (leave empty to keep current)"`
Notes string `json:"notes" description:"New notes (leave empty to keep current)"`
}
type UpdateResponse struct {
Contact *Contact `json:"contact" description:"The updated contact"`
}
type DeleteRequest struct {
ID string `json:"id" description:"Contact ID to delete"`
}
type DeleteResponse struct {
Deleted bool `json:"deleted" description:"True if the contact was deleted"`
}
type ListRequest struct {
}
type ListResponse struct {
Contacts []*Contact `json:"contacts" description:"All contacts in the book"`
}
type SearchRequest struct {
Query string `json:"query" description:"Search term to match against name, email, role, or notes"`
}
type SearchResponse struct {
Contacts []*Contact `json:"contacts" description:"Contacts matching the search query"`
}
// --- Handler ---
// Contacts manages a contact book with CRUD operations.
type Contacts struct {
mu sync.RWMutex
store map[string]*Contact
counter int
}
func NewContacts() *Contacts {
c := &Contacts{store: make(map[string]*Contact)}
// Seed with example data
c.store["c-1"] = &Contact{ID: "c-1", Name: "Alice Johnson", Email: "alice@example.com", Phone: "+1-555-0101", Role: "Engineer", Notes: "Backend team lead"}
c.store["c-2"] = &Contact{ID: "c-2", Name: "Bob Smith", Email: "bob@example.com", Phone: "+1-555-0102", Role: "Designer", Notes: "UI/UX specialist"}
c.store["c-3"] = &Contact{ID: "c-3", Name: "Carol Davis", Email: "carol@example.com", Phone: "+1-555-0103", Role: "PM", Notes: "Leads the platform team"}
c.counter = 3
return c
}
// Create adds a new contact to the book. Name and email are required.
//
// @example {"name": "Dave Wilson", "email": "dave@example.com", "role": "Engineer"}
func (h *Contacts) Create(ctx context.Context, req *CreateRequest, rsp *CreateResponse) error {
if req.Name == "" {
return fmt.Errorf("name is required")
}
if req.Email == "" {
return fmt.Errorf("email is required")
}
h.mu.Lock()
defer h.mu.Unlock()
h.counter++
id := fmt.Sprintf("c-%d", h.counter)
contact := &Contact{
ID: id,
Name: req.Name,
Email: req.Email,
Phone: req.Phone,
Role: req.Role,
Notes: req.Notes,
}
h.store[id] = contact
rsp.Contact = contact
return nil
}
// Get retrieves a single contact by ID.
//
// @example {"id": "c-1"}
func (h *Contacts) Get(ctx context.Context, req *GetRequest, rsp *GetResponse) error {
if req.ID == "" {
return fmt.Errorf("id is required")
}
h.mu.RLock()
defer h.mu.RUnlock()
contact, ok := h.store[req.ID]
if !ok {
return fmt.Errorf("contact %s not found", req.ID)
}
rsp.Contact = contact
return nil
}
// Update modifies an existing contact. Only non-empty fields are updated,
// so you can change just the email without affecting other fields.
//
// @example {"id": "c-1", "role": "Senior Engineer"}
func (h *Contacts) Update(ctx context.Context, req *UpdateRequest, rsp *UpdateResponse) error {
if req.ID == "" {
return fmt.Errorf("id is required")
}
h.mu.Lock()
defer h.mu.Unlock()
contact, ok := h.store[req.ID]
if !ok {
return fmt.Errorf("contact %s not found", req.ID)
}
if req.Name != "" {
contact.Name = req.Name
}
if req.Email != "" {
contact.Email = req.Email
}
if req.Phone != "" {
contact.Phone = req.Phone
}
if req.Role != "" {
contact.Role = req.Role
}
if req.Notes != "" {
contact.Notes = req.Notes
}
rsp.Contact = contact
return nil
}
// Delete removes a contact from the book permanently.
//
// @example {"id": "c-1"}
func (h *Contacts) Delete(ctx context.Context, req *DeleteRequest, rsp *DeleteResponse) error {
if req.ID == "" {
return fmt.Errorf("id is required")
}
h.mu.Lock()
defer h.mu.Unlock()
if _, ok := h.store[req.ID]; !ok {
return fmt.Errorf("contact %s not found", req.ID)
}
delete(h.store, req.ID)
rsp.Deleted = true
return nil
}
// List returns all contacts in the book.
//
// @example {}
func (h *Contacts) List(ctx context.Context, req *ListRequest, rsp *ListResponse) error {
h.mu.RLock()
defer h.mu.RUnlock()
for _, c := range h.store {
rsp.Contacts = append(rsp.Contacts, c)
}
return nil
}
// Search finds contacts matching a query string. Matches against name,
// email, role, and notes fields (case-insensitive).
//
// @example {"query": "engineer"}
func (h *Contacts) Search(ctx context.Context, req *SearchRequest, rsp *SearchResponse) error {
if req.Query == "" {
return fmt.Errorf("query is required")
}
h.mu.RLock()
defer h.mu.RUnlock()
q := strings.ToLower(req.Query)
for _, c := range h.store {
if strings.Contains(strings.ToLower(c.Name), q) ||
strings.Contains(strings.ToLower(c.Email), q) ||
strings.Contains(strings.ToLower(c.Role), q) ||
strings.Contains(strings.ToLower(c.Notes), q) {
rsp.Contacts = append(rsp.Contacts, c)
}
}
return nil
}
func main() {
service := micro.NewService("contacts",
micro.Address(":9010"),
mcp.WithMCP(":3001"),
)
service.Init()
if err := service.Handle(NewContacts()); err != nil {
log.Fatal(err)
}
fmt.Println("Contacts service running on :9010")
fmt.Println("MCP tools available at http://localhost:3001/mcp/tools")
fmt.Println()
fmt.Println("Try asking an AI agent:")
fmt.Println(" 'List all contacts'")
fmt.Println(" 'Find engineers in the contact book'")
fmt.Println(" 'Add a new contact for Eve at eve@example.com'")
fmt.Println(" 'Update Alice's role to Staff Engineer'")
if err := service.Run(); err != nil {
log.Fatal(err)
}
}
+306
View File
@@ -0,0 +1,306 @@
# Documented Service Example
This example demonstrates how to document your go-micro service handlers so that AI agents can understand them better. The MCP gateway parses Go comments and struct tags to generate rich tool descriptions.
## Documentation Features
### 1. **Go Doc Comments**
Standard Go documentation comments are used as the tool description:
```go
// GetUser retrieves a user by ID from the database.
//
// This endpoint fetches a user's complete profile including their name,
// email, and age. If the user doesn't exist, an error is returned.
func (u *Users) GetUser(ctx context.Context, req *GetUserRequest, rsp *GetUserResponse) error {
// ...
}
```
### 2. **JSDoc-Style Tags**
Use `@param`, `@return`, and `@example` tags for detailed documentation:
```go
// CreateUser creates a new user in the system.
//
// @param name {string} User's full name (required, 1-100 characters)
// @param email {string} User's email address (required, must be valid email format)
// @param age {number} User's age (optional, must be 0-150 if provided)
// @return {User} The newly created user with generated ID
// @example
// {
// "name": "Alice Smith",
// "email": "alice@example.com",
// "age": 30
// }
func (u *Users) CreateUser(ctx context.Context, req *CreateUserRequest, rsp *CreateUserResponse) error {
// ...
}
```
### 3. **Struct Tags**
Add `description` tags to struct fields for better schema:
```go
type User struct {
ID string `json:"id" description:"User's unique identifier (UUID format)"`
Name string `json:"name" description:"User's full name"`
Email string `json:"email" description:"User's email address"`
Age int `json:"age,omitempty" description:"User's age (optional)"`
}
```
## Running the Example
### 1. Start the Service
```bash
cd examples/mcp/documented
go run main.go
```
Output:
```
Users service starting...
Service: users
Endpoints:
- Users.GetUser
- Users.CreateUser
MCP Gateway: http://localhost:3000
```
### 2. Test MCP Tools
List available tools:
```bash
curl http://localhost:3000/mcp/tools | jq
```
You'll see rich descriptions:
```json
{
"tools": [
{
"name": "users.Users.GetUser",
"description": "GetUser retrieves a user by ID from the database",
"inputSchema": {
"type": "object",
"properties": {
"id": {
"type": "string",
"description": "User ID in UUID format (e.g., \"123e4567-e89b-12d3-a456-426614174000\")"
}
},
"required": ["id"],
"examples": [
"{\"id\": \"123e4567-e89b-12d3-a456-426614174000\"}"
]
}
},
{
"name": "users.Users.CreateUser",
"description": "CreateUser creates a new user in the system",
"inputSchema": {
"type": "object",
"properties": {
"name": {
"type": "string",
"description": "User's full name (required, 1-100 characters)"
},
"email": {
"type": "string",
"description": "User's email address (required, must be valid email format)"
},
"age": {
"type": "number",
"description": "User's age (optional, must be 0-150 if provided)"
}
},
"required": ["name", "email"],
"examples": [
"{\"name\": \"Alice Smith\", \"email\": \"alice@example.com\", \"age\": 30}"
]
}
}
]
}
```
### 3. Call a Tool
Get existing user:
```bash
curl -X POST http://localhost:3000/mcp/call \
-H "Content-Type: application/json" \
-d '{
"tool": "users.Users.GetUser",
"input": {"id": "user-1"}
}'
```
Create new user:
```bash
curl -X POST http://localhost:3000/mcp/call \
-H "Content-Type: application/json" \
-d '{
"tool": "users.Users.CreateUser",
"input": {
"name": "Alice Smith",
"email": "alice@example.com",
"age": 30
}
}'
```
### 4. Use with Claude Code
Add to your `claude_desktop_config.json`:
```json
{
"mcpServers": {
"users-service": {
"command": "go",
"args": ["run", "/path/to/examples/mcp/documented/main.go"]
}
}
}
```
Then in Claude Code, ask:
```
> You: "Show me user-1's profile"
Claude will:
1. See the GetUser tool with rich description
2. Understand it needs an "id" parameter (UUID format)
3. Call users.Users.GetUser with {"id": "user-1"}
4. Return the user profile
```
## Documentation Best Practices
### DO: Write Clear Descriptions
```go
// ✅ Good: Clear, explains what and why
// GetUser retrieves a user by ID from the database.
// Returns full profile including email, name, and preferences.
```
```go
// ❌ Bad: Vague, no context
// Get gets a user
```
### DO: Specify Parameter Constraints
```go
// ✅ Good: Specifies format and constraints
// @param id {string} User ID in UUID format (e.g., "123e4567-e89b-12d3-a456-426614174000")
// @param age {number} User's age (must be 0-150)
```
```go
// ❌ Bad: No constraints or format
// @param id {string} The ID
```
### DO: Provide Examples
```go
// ✅ Good: Real example agents can use
// @example
// {
// "name": "Alice Smith",
// "email": "alice@example.com",
// "age": 30
// }
```
```go
// ❌ Bad: No example
// (agents have to guess the format)
```
### DO: Use Descriptive Struct Tags
```go
// ✅ Good: Explains what the field is
type User struct {
ID string `json:"id" description:"User's unique identifier (UUID format)"`
}
```
```go
// ❌ Bad: No description
type User struct {
ID string `json:"id"`
}
```
## How It Works
1. **Go Doc Parsing**
- The MCP gateway reads your service's Go comments
- First line becomes the tool description
- Full comment becomes the detailed description
2. **JSDoc Tag Parsing**
- `@param` tags enhance parameter descriptions
- `@return` tags describe what the tool returns
- `@example` tags provide usage examples
3. **Struct Tag Reading**
- `description` tags add context to fields
- `json:"field,omitempty"` marks optional fields
- Used to generate JSON Schema for parameters
4. **Schema Generation**
- Combines parsed documentation with type information
- Creates rich JSON Schema for each tool
- Agents use this to understand how to call your service
## Impact on Agent Performance
### Without Documentation
```
Tool: users.Users.GetUser
Description: Call GetUser on users service
Parameters: { "id": "string" }
```
Agent thinks: *"What's an ID? What format? What if I pass the wrong thing?"*
### With Documentation
```
Tool: users.Users.GetUser
Description: Retrieves a user by ID from the database. Returns full profile
including email, name, and preferences.
Parameters:
- id (string, required): User ID in UUID format
Example: "123e4567-e89b-12d3-a456-426614174000"
Example:
{"id": "user-1"}
```
Agent thinks: *"I need a UUID format ID. I can use 'user-1' from the example!"*
**Result:** Agent calls your service correctly on the first try!
## Next Steps
- Document all your service handlers with clear descriptions
- Add `@param`, `@return`, and `@example` tags
- Use `description` tags in struct fields
- Test with Claude Code to see how agents understand your services
## License
Apache 2.0
+137
View File
@@ -0,0 +1,137 @@
// Package main demonstrates how to document your service handlers for better
// AI agent integration using endpoint metadata.
//
// Services register descriptions with their endpoints, and the MCP gateway
// reads these descriptions from the registry to generate rich tool descriptions.
package main
import (
"context"
"fmt"
"log"
"go-micro.dev/v6"
"go-micro.dev/v6/gateway/mcp"
"go-micro.dev/v6/server"
)
// User represents a user in the system
type User struct {
ID string `json:"id" description:"User's unique identifier (UUID format)"`
Name string `json:"name" description:"User's full name"`
Email string `json:"email" description:"User's email address"`
Age int `json:"age,omitempty" description:"User's age (optional)"`
}
// GetUserRequest is the request for getting a user
type GetUserRequest struct {
ID string `json:"id" description:"User ID to retrieve"`
}
// GetUserResponse is the response containing user data
type GetUserResponse struct {
User *User `json:"user" description:"The requested user object"`
}
// CreateUserRequest is the request for creating a user
type CreateUserRequest struct {
Name string `json:"name" description:"User's full name (required)"`
Email string `json:"email" description:"User's email address (required)"`
Age int `json:"age,omitempty" description:"User's age (optional)"`
}
// CreateUserResponse contains the newly created user
type CreateUserResponse struct {
User *User `json:"user" description:"The newly created user"`
}
// Users service handles user-related operations
type Users struct {
users map[string]*User
}
// GetUser retrieves a user by ID from the database. Returns full profile including email, name, and preferences. If the user doesn't exist, an error is returned.
//
// @example {"id": "user-1"}
func (u *Users) GetUser(ctx context.Context, req *GetUserRequest, rsp *GetUserResponse) error {
user, exists := u.users[req.ID]
if !exists {
return fmt.Errorf("user not found: %s", req.ID)
}
rsp.User = user
return nil
}
// CreateUser creates a new user in the system. Validates the user data and creates a new profile. Name and email are required fields, while age is optional. Email must be unique across all users.
//
// @example {"name": "Alice Smith", "email": "alice@example.com", "age": 30}
func (u *Users) CreateUser(ctx context.Context, req *CreateUserRequest, rsp *CreateUserResponse) error {
// Validate input
if req.Name == "" || req.Email == "" {
return fmt.Errorf("name and email are required")
}
// Generate ID (simplified for example)
id := fmt.Sprintf("user-%d", len(u.users)+1)
user := &User{
ID: id,
Name: req.Name,
Email: req.Email,
Age: req.Age,
}
u.users[id] = user
rsp.User = user
return nil
}
func main() {
// Create service
service := micro.NewService("users",
micro.Address(":9090"),
// Start MCP gateway alongside the service
mcp.WithMCP(":3000"),
)
service.Init()
// Register handler with pre-populated test data.
// Documentation is automatically extracted from method comments.
// Use WithEndpointScopes to declare required auth scopes per endpoint.
if err := service.Handle(
&Users{
users: map[string]*User{
"user-1": {ID: "user-1", Name: "John Doe", Email: "john@example.com", Age: 25},
"user-2": {ID: "user-2", Name: "Jane Smith", Email: "jane@example.com", Age: 30},
},
},
server.WithEndpointScopes("Users.GetUser", "users:read"),
server.WithEndpointScopes("Users.CreateUser", "users:write"),
); err != nil {
log.Fatal(err)
}
log.Println("Users service starting...")
log.Println("Service: users")
log.Println("Endpoints:")
log.Println(" - Users.GetUser")
log.Println(" - Users.CreateUser")
log.Println("MCP Gateway: http://localhost:3000")
log.Println("")
log.Println("Test with:")
log.Println(" curl http://localhost:3000/mcp/tools")
log.Println("")
log.Println("Or add to Claude Code:")
log.Println(` "users-service": {`)
log.Println(` "command": "micro",`)
log.Println(` "args": ["mcp", "serve"]`)
log.Println(` }`)
// Run service
if err := service.Run(); err != nil {
log.Fatal(err)
}
}
+131
View File
@@ -0,0 +1,131 @@
# MCP Hello World Example
The simplest possible MCP-enabled go-micro service.
## What This Shows
- ✅ Automatic documentation extraction from Go comments
- ✅ MCP gateway setup with 3 lines of code
- ✅ Ready for Claude Code integration
- ✅ HTTP endpoint for testing
## Run It
```bash
cd examples/mcp/hello
go run main.go
```
## Test It
### Option 1: HTTP API
```bash
# List available tools
curl http://localhost:3000/mcp/tools | jq
# Call the SayHello tool
curl -X POST http://localhost:3000/mcp/call \
-H "Content-Type: application/json" \
-d '{
"tool": "greeter.Greeter.SayHello",
"input": {"name": "Alice"}
}' | jq
```
### Option 2: Claude Code
In a separate terminal:
```bash
micro mcp serve
```
Add to `~/.claude/claude_desktop_config.json`:
```json
{
"mcpServers": {
"greeter": {
"command": "micro",
"args": ["mcp", "serve"]
}
}
}
```
Restart Claude Code and ask:
> "Say hello to Bob using the greeter service"
## How It Works
### 1. Write Normal Go Code
```go
// SayHello greets a person by name. Returns a friendly greeting message.
//
// @example {"name": "Alice"}
func (g *Greeter) SayHello(ctx context.Context, req *HelloRequest, rsp *HelloResponse) error {
rsp.Message = "Hello " + req.Name + "!"
return nil
}
```
### 2. Register the Handler
```go
// Documentation is extracted automatically!
handler := service.Server().NewHandler(new(Greeter))
service.Server().Handle(handler)
```
### 3. Start MCP Gateway
```go
go mcp.ListenAndServe(":3000", mcp.Options{
Registry: service.Options().Registry,
})
```
**That's it!** Your service is now AI-accessible.
## What Gets Extracted
From this code:
```go
// SayHello greets a person by name. Returns a friendly greeting message.
//
// @example {"name": "Alice"}
func (g *Greeter) SayHello(...)
type HelloRequest struct {
Name string `json:"name" description:"Person's name to greet"`
}
```
Claude sees:
```json
{
"name": "greeter.Greeter.SayHello",
"description": "SayHello greets a person by name. Returns a friendly greeting message.",
"inputSchema": {
"type": "object",
"properties": {
"name": {
"type": "string",
"description": "Person's name to greet"
}
},
"examples": ["{\"name\": \"Alice\"}"]
}
}
```
## Next Steps
- See `examples/mcp/documented` for a more complete example with multiple endpoints
- Read `/docs/mcp.md` for full documentation
- Check out the [MCP specification](https://modelcontextprotocol.io/)
+65
View File
@@ -0,0 +1,65 @@
// Package main demonstrates a minimal MCP-enabled service.
//
// This is the simplest possible example showing:
// - Automatic documentation extraction from Go comments
// - MCP gateway setup
// - Ready for use with Claude Code
package main
import (
"context"
"log"
"go-micro.dev/v6"
"go-micro.dev/v6/gateway/mcp"
)
// Greeter service handles greeting operations
type Greeter struct{}
// SayHello greets a person by name. Returns a friendly greeting message.
//
// @example {"name": "Alice"}
func (g *Greeter) SayHello(ctx context.Context, req *HelloRequest, rsp *HelloResponse) error {
rsp.Message = "Hello " + req.Name + "!"
return nil
}
// HelloRequest contains the greeting parameters
type HelloRequest struct {
Name string `json:"name" description:"Person's name to greet"`
}
// HelloResponse contains the greeting result
type HelloResponse struct {
Message string `json:"message" description:"The greeting message"`
}
func main() {
// Create service
service := micro.NewService("greeter",
micro.Address(":9090"),
// Start MCP gateway alongside the service
mcp.WithMCP(":3000"),
)
service.Init()
// Register handler — docs extracted automatically from comments
if err := service.Handle(new(Greeter)); err != nil {
log.Fatal(err)
}
log.Println("Greeter service starting...")
log.Println("Service: http://localhost:9090")
log.Println("MCP Gateway: http://localhost:3000")
log.Println("MCP Tools: http://localhost:3000/mcp/tools")
log.Println()
log.Println("Use with Claude Code:")
log.Println(" micro mcp serve")
// Run service
if err := service.Run(); err != nil {
log.Fatal(err)
}
}
+114
View File
@@ -0,0 +1,114 @@
# Platform Example: AI Agents Meet Real Microservices
This example mirrors the [micro/blog](https://github.com/micro/blog) platform — a real microblogging application built on Go Micro. It demonstrates how existing microservices become AI-accessible through MCP with **zero changes to business logic**.
## Services
| Service | Endpoints | Description |
|---------|-----------|-------------|
| **Users** | Signup, Login, GetProfile, UpdateStatus, List | Account management and authentication |
| **Posts** | Create, Read, Update, Delete, List, TagPost, UntagPost, ListTags | Blog posts with markdown and tagging |
| **Comments** | Create, List, Delete | Threaded comments on posts |
| **Mail** | Send, Read | Internal messaging between users |
## Running
```bash
go run .
```
MCP tools available at: http://localhost:3001/mcp/tools
## Agent Scenarios
These are realistic multi-step workflows an AI agent can complete:
### 1. New User Onboarding
```
"Sign up a new user called carol, then write a welcome post introducing herself"
```
The agent will: call Signup → use the returned user ID → call Posts.Create
### 2. Content Creation
```
"Log in as alice and write a blog post about Go concurrency patterns, then tag it with 'golang' and 'concurrency'"
```
The agent will: call Login → call Posts.Create → call TagPost twice
### 3. Social Interaction
```
"List all posts, find the welcome post, and comment on it as bob saying 'Great to be here!'"
```
The agent will: call Posts.List → pick the right post → call Comments.Create
### 4. Cross-Service Workflow
```
"Send a mail from alice to bob welcoming him, then check bob's inbox to confirm delivery"
```
The agent will: call Mail.Send → call Mail.Read to verify
### 5. Platform Overview
```
"Show me all users, all posts, and all tags currently in use"
```
The agent will: call Users.List, Posts.List, and ListTags (potentially in parallel)
## How It Works
The key insight: **you don't need to write any agent-specific code**. The MCP gateway discovers services from the registry, extracts tool schemas from Go types, and generates descriptions from doc comments.
```go
service := micro.NewService("platform",
micro.Address(":9090"),
mcp.WithMCP(":3001"), // This one line makes everything AI-accessible
)
service.Handle(&Users{})
service.Handle(&Posts{})
service.Handle(&Comments{})
service.Handle(&Mail{})
```
Each handler method becomes an MCP tool. The `@example` tags in doc comments give agents sample inputs to learn from.
## Connecting to Claude Code
Add to your Claude Code MCP config:
```json
{
"mcpServers": {
"platform": {
"command": "curl",
"args": ["-s", "http://localhost:3001/mcp/tools"]
}
}
}
```
Or use stdio transport:
```bash
micro mcp serve --registry mdns
```
## Architecture
```
Agent (Claude, GPT, etc.)
MCP Gateway (:3001) ← Discovers services, generates tools
Go Micro RPC (:9090) ← Standard service mesh
├── UserService ← Signup, Login, Profile
├── PostService ← CRUD + Tags
├── CommentService ← Threaded comments
└── MailService ← Internal messaging
```
## Relation to micro/blog
This example is a simplified, self-contained version of [micro/blog](https://github.com/micro/blog). The real platform splits each service into its own binary with protobuf definitions. This example uses Go structs directly for simplicity, but the MCP integration works identically either way — the gateway discovers services from the registry regardless of how they're implemented.
+774
View File
@@ -0,0 +1,774 @@
// Platform example: AI agents interacting with a real microservices platform.
//
// This example mirrors the micro/blog platform (https://github.com/micro/blog)
// — a microblogging platform built on Go Micro with Users, Posts, Comments,
// and Mail services. It demonstrates how existing microservices become
// AI-accessible through MCP with zero changes to business logic.
//
// The services run as a single binary for convenience. In production,
// each would be a separate process discovered via the registry.
//
// Run:
//
// go run .
//
// MCP tools: http://localhost:3001/mcp/tools
//
// Agent scenarios:
//
// "Sign me up as alice with password secret123"
// "Log in as alice and write a blog post about Go concurrency"
// "List all posts and comment on the first one"
// "Send a welcome email to alice"
// "Tag the Go concurrency post with 'golang' and 'tutorial'"
// "Show me alice's profile and all her posts"
package main
import (
"context"
"crypto/rand"
"encoding/hex"
"fmt"
"log"
"sort"
"strings"
"sync"
"time"
"go-micro.dev/v6"
"go-micro.dev/v6/gateway/mcp"
"go-micro.dev/v6/server"
)
// ---------------------------------------------------------------------------
// Users service — account registration, login, profiles
// ---------------------------------------------------------------------------
type User struct {
ID string `json:"id" description:"Unique user identifier"`
Name string `json:"name" description:"Display name"`
Status string `json:"status" description:"Bio or status message"`
CreatedAt int64 `json:"created_at" description:"Unix timestamp of account creation"`
}
type SignupRequest struct {
Name string `json:"name" description:"Username (required, 3-20 characters)"`
Password string `json:"password" description:"Password (required, minimum 6 characters)"`
}
type SignupResponse struct {
User *User `json:"user" description:"The newly created user account"`
Token string `json:"token" description:"Session token for authenticated requests"`
}
type LoginRequest struct {
Name string `json:"name" description:"Username"`
Password string `json:"password" description:"Password"`
}
type LoginResponse struct {
User *User `json:"user" description:"The authenticated user"`
Token string `json:"token" description:"Session token for authenticated requests"`
}
type GetProfileRequest struct {
ID string `json:"id" description:"User ID to look up"`
}
type GetProfileResponse struct {
User *User `json:"user" description:"The user profile"`
}
type UpdateStatusRequest struct {
ID string `json:"id" description:"User ID"`
Status string `json:"status" description:"New bio or status message"`
}
type UpdateStatusResponse struct {
User *User `json:"user" description:"Updated user profile"`
}
type ListUsersRequest struct{}
type ListUsersResponse struct {
Users []*User `json:"users" description:"All registered users"`
}
type Users struct {
mu sync.RWMutex
users map[string]*User
passwords map[string]string // name -> password (plaintext for demo only)
tokens map[string]string // token -> user ID
nextID int
}
func NewUsers() *Users {
return &Users{
users: make(map[string]*User),
passwords: make(map[string]string),
tokens: make(map[string]string),
}
}
// Signup creates a new user account and returns a session token.
// The username must be unique. Use the returned token for authenticated operations.
//
// @example {"name": "alice", "password": "secret123"}
func (s *Users) Signup(ctx context.Context, req *SignupRequest, rsp *SignupResponse) error {
if req.Name == "" || len(req.Name) < 3 {
return fmt.Errorf("name must be at least 3 characters")
}
if req.Password == "" || len(req.Password) < 6 {
return fmt.Errorf("password must be at least 6 characters")
}
s.mu.Lock()
defer s.mu.Unlock()
// Check uniqueness
for _, u := range s.users {
if strings.EqualFold(u.Name, req.Name) {
return fmt.Errorf("username %q is already taken", req.Name)
}
}
s.nextID++
user := &User{
ID: fmt.Sprintf("user-%d", s.nextID),
Name: req.Name,
CreatedAt: time.Now().Unix(),
}
s.users[user.ID] = user
s.passwords[req.Name] = req.Password
token := generateToken()
s.tokens[token] = user.ID
rsp.User = user
rsp.Token = token
return nil
}
// Login authenticates a user and returns a session token.
// Returns an error if the credentials are invalid.
//
// @example {"name": "alice", "password": "secret123"}
func (s *Users) Login(ctx context.Context, req *LoginRequest, rsp *LoginResponse) error {
s.mu.RLock()
defer s.mu.RUnlock()
pass, ok := s.passwords[req.Name]
if !ok || pass != req.Password {
return fmt.Errorf("invalid username or password")
}
// Find user by name
for _, u := range s.users {
if u.Name == req.Name {
token := generateToken()
s.tokens[token] = u.ID
rsp.User = u
rsp.Token = token
return nil
}
}
return fmt.Errorf("user not found")
}
// GetProfile retrieves a user's public profile by ID.
//
// @example {"id": "user-1"}
func (s *Users) GetProfile(ctx context.Context, req *GetProfileRequest, rsp *GetProfileResponse) error {
s.mu.RLock()
defer s.mu.RUnlock()
u, ok := s.users[req.ID]
if !ok {
return fmt.Errorf("user %s not found", req.ID)
}
rsp.User = u
return nil
}
// UpdateStatus sets a user's bio or status message.
//
// @example {"id": "user-1", "status": "Writing about Go and microservices"}
func (s *Users) UpdateStatus(ctx context.Context, req *UpdateStatusRequest, rsp *UpdateStatusResponse) error {
s.mu.Lock()
defer s.mu.Unlock()
u, ok := s.users[req.ID]
if !ok {
return fmt.Errorf("user %s not found", req.ID)
}
u.Status = req.Status
rsp.User = u
return nil
}
// List returns all registered users on the platform.
//
// @example {}
func (s *Users) List(ctx context.Context, req *ListUsersRequest, rsp *ListUsersResponse) error {
s.mu.RLock()
defer s.mu.RUnlock()
for _, u := range s.users {
rsp.Users = append(rsp.Users, u)
}
return nil
}
// ---------------------------------------------------------------------------
// Posts service — blog posts with markdown and tags
// ---------------------------------------------------------------------------
type Post struct {
ID string `json:"id" description:"Unique post identifier"`
Title string `json:"title" description:"Post title"`
Content string `json:"content" description:"Post body in markdown"`
AuthorID string `json:"author_id" description:"ID of the post author"`
AuthorName string `json:"author_name" description:"Display name of the author"`
Tags []string `json:"tags,omitempty" description:"Post tags for categorization"`
CreatedAt int64 `json:"created_at" description:"Unix timestamp of creation"`
UpdatedAt int64 `json:"updated_at" description:"Unix timestamp of last update"`
}
type CreatePostRequest struct {
Title string `json:"title" description:"Post title (required)"`
Content string `json:"content" description:"Post body in markdown (required)"`
AuthorID string `json:"author_id" description:"Author's user ID (required)"`
AuthorName string `json:"author_name" description:"Author's display name (required)"`
}
type CreatePostResponse struct {
Post *Post `json:"post" description:"The newly created post"`
}
type ReadPostRequest struct {
ID string `json:"id" description:"Post ID to retrieve"`
}
type ReadPostResponse struct {
Post *Post `json:"post" description:"The requested post"`
}
type UpdatePostRequest struct {
ID string `json:"id" description:"Post ID to update (required)"`
Title string `json:"title" description:"New title"`
Content string `json:"content" description:"New content in markdown"`
}
type UpdatePostResponse struct {
Post *Post `json:"post" description:"The updated post"`
}
type DeletePostRequest struct {
ID string `json:"id" description:"Post ID to delete"`
}
type DeletePostResponse struct {
Message string `json:"message" description:"Confirmation message"`
}
type ListPostsRequest struct {
AuthorID string `json:"author_id,omitempty" description:"Filter by author ID (optional)"`
}
type ListPostsResponse struct {
Posts []*Post `json:"posts" description:"Posts in reverse chronological order"`
Total int `json:"total" description:"Total number of matching posts"`
}
type TagPostRequest struct {
PostID string `json:"post_id" description:"Post to tag"`
Tag string `json:"tag" description:"Tag to add (lowercase, no spaces)"`
}
type TagPostResponse struct {
Post *Post `json:"post" description:"Post with updated tags"`
}
type UntagPostRequest struct {
PostID string `json:"post_id" description:"Post to untag"`
Tag string `json:"tag" description:"Tag to remove"`
}
type UntagPostResponse struct {
Post *Post `json:"post" description:"Post with updated tags"`
}
type ListTagsRequest struct{}
type ListTagsResponse struct {
Tags []string `json:"tags" description:"All tags in use, sorted alphabetically"`
}
type Posts struct {
mu sync.RWMutex
posts map[string]*Post
nextID int
}
func NewPosts() *Posts {
return &Posts{posts: make(map[string]*Post)}
}
// Create publishes a new blog post. Title, content, author_id, and author_name
// are required. Content supports markdown formatting.
//
// @example {"title": "Getting Started with Go Micro", "content": "Go Micro makes it easy to build microservices...", "author_id": "user-1", "author_name": "alice"}
func (s *Posts) Create(ctx context.Context, req *CreatePostRequest, rsp *CreatePostResponse) error {
if req.Title == "" {
return fmt.Errorf("title is required")
}
if req.Content == "" {
return fmt.Errorf("content is required")
}
if req.AuthorID == "" {
return fmt.Errorf("author_id is required")
}
s.mu.Lock()
defer s.mu.Unlock()
s.nextID++
now := time.Now().Unix()
post := &Post{
ID: fmt.Sprintf("post-%d", s.nextID),
Title: req.Title,
Content: req.Content,
AuthorID: req.AuthorID,
AuthorName: req.AuthorName,
CreatedAt: now,
UpdatedAt: now,
}
s.posts[post.ID] = post
rsp.Post = post
return nil
}
// Read retrieves a single blog post by ID.
//
// @example {"id": "post-1"}
func (s *Posts) Read(ctx context.Context, req *ReadPostRequest, rsp *ReadPostResponse) error {
s.mu.RLock()
defer s.mu.RUnlock()
p, ok := s.posts[req.ID]
if !ok {
return fmt.Errorf("post %s not found", req.ID)
}
rsp.Post = p
return nil
}
// Update modifies a blog post's title and/or content.
// Only non-empty fields are updated.
//
// @example {"id": "post-1", "title": "Updated Title", "content": "New content here..."}
func (s *Posts) Update(ctx context.Context, req *UpdatePostRequest, rsp *UpdatePostResponse) error {
s.mu.Lock()
defer s.mu.Unlock()
p, ok := s.posts[req.ID]
if !ok {
return fmt.Errorf("post %s not found", req.ID)
}
if req.Title != "" {
p.Title = req.Title
}
if req.Content != "" {
p.Content = req.Content
}
p.UpdatedAt = time.Now().Unix()
rsp.Post = p
return nil
}
// Delete removes a blog post permanently.
//
// @example {"id": "post-1"}
func (s *Posts) Delete(ctx context.Context, req *DeletePostRequest, rsp *DeletePostResponse) error {
s.mu.Lock()
defer s.mu.Unlock()
if _, ok := s.posts[req.ID]; !ok {
return fmt.Errorf("post %s not found", req.ID)
}
delete(s.posts, req.ID)
rsp.Message = fmt.Sprintf("post %s deleted", req.ID)
return nil
}
// List returns blog posts in reverse chronological order.
// Optionally filter by author_id to see a specific user's posts.
//
// @example {"author_id": "user-1"}
func (s *Posts) List(ctx context.Context, req *ListPostsRequest, rsp *ListPostsResponse) error {
s.mu.RLock()
defer s.mu.RUnlock()
for _, p := range s.posts {
if req.AuthorID != "" && p.AuthorID != req.AuthorID {
continue
}
rsp.Posts = append(rsp.Posts, p)
}
sort.Slice(rsp.Posts, func(i, j int) bool {
return rsp.Posts[i].CreatedAt > rsp.Posts[j].CreatedAt
})
rsp.Total = len(rsp.Posts)
return nil
}
// TagPost adds a tag to a post. Tags are useful for categorization
// and discovery. Duplicate tags are ignored.
//
// @example {"post_id": "post-1", "tag": "golang"}
func (s *Posts) TagPost(ctx context.Context, req *TagPostRequest, rsp *TagPostResponse) error {
if req.PostID == "" || req.Tag == "" {
return fmt.Errorf("post_id and tag are required")
}
s.mu.Lock()
defer s.mu.Unlock()
p, ok := s.posts[req.PostID]
if !ok {
return fmt.Errorf("post %s not found", req.PostID)
}
tag := strings.ToLower(strings.TrimSpace(req.Tag))
for _, t := range p.Tags {
if t == tag {
rsp.Post = p
return nil
}
}
p.Tags = append(p.Tags, tag)
p.UpdatedAt = time.Now().Unix()
rsp.Post = p
return nil
}
// UntagPost removes a tag from a post.
//
// @example {"post_id": "post-1", "tag": "golang"}
func (s *Posts) UntagPost(ctx context.Context, req *UntagPostRequest, rsp *UntagPostResponse) error {
s.mu.Lock()
defer s.mu.Unlock()
p, ok := s.posts[req.PostID]
if !ok {
return fmt.Errorf("post %s not found", req.PostID)
}
filtered := make([]string, 0, len(p.Tags))
for _, t := range p.Tags {
if t != req.Tag {
filtered = append(filtered, t)
}
}
p.Tags = filtered
p.UpdatedAt = time.Now().Unix()
rsp.Post = p
return nil
}
// ListTags returns all tags currently in use across all posts.
//
// @example {}
func (s *Posts) ListTags(ctx context.Context, req *ListTagsRequest, rsp *ListTagsResponse) error {
s.mu.RLock()
defer s.mu.RUnlock()
seen := make(map[string]bool)
for _, p := range s.posts {
for _, t := range p.Tags {
seen[t] = true
}
}
for t := range seen {
rsp.Tags = append(rsp.Tags, t)
}
sort.Strings(rsp.Tags)
return nil
}
// ---------------------------------------------------------------------------
// Comments service — threaded comments on posts
// ---------------------------------------------------------------------------
type Comment struct {
ID string `json:"id" description:"Unique comment identifier"`
PostID string `json:"post_id" description:"ID of the post this comment belongs to"`
Content string `json:"content" description:"Comment text"`
AuthorID string `json:"author_id" description:"ID of the comment author"`
AuthorName string `json:"author_name" description:"Display name of the author"`
CreatedAt int64 `json:"created_at" description:"Unix timestamp of creation"`
}
type CreateCommentRequest struct {
PostID string `json:"post_id" description:"Post to comment on (required)"`
Content string `json:"content" description:"Comment text (required)"`
AuthorID string `json:"author_id" description:"Author's user ID (required)"`
AuthorName string `json:"author_name" description:"Author's display name (required)"`
}
type CreateCommentResponse struct {
Comment *Comment `json:"comment" description:"The newly created comment"`
}
type ListCommentsRequest struct {
PostID string `json:"post_id,omitempty" description:"Filter by post ID (optional)"`
AuthorID string `json:"author_id,omitempty" description:"Filter by author ID (optional)"`
}
type ListCommentsResponse struct {
Comments []*Comment `json:"comments" description:"Matching comments"`
}
type DeleteCommentRequest struct {
ID string `json:"id" description:"Comment ID to delete"`
}
type DeleteCommentResponse struct {
Message string `json:"message" description:"Confirmation message"`
}
type Comments struct {
mu sync.RWMutex
comments []*Comment
nextID int
}
// Create adds a comment to a blog post. Post ID, content, author_id,
// and author_name are all required.
//
// @example {"post_id": "post-1", "content": "Great article! Very helpful.", "author_id": "user-2", "author_name": "bob"}
func (s *Comments) Create(ctx context.Context, req *CreateCommentRequest, rsp *CreateCommentResponse) error {
if req.PostID == "" {
return fmt.Errorf("post_id is required")
}
if req.Content == "" {
return fmt.Errorf("content is required")
}
if req.AuthorID == "" {
return fmt.Errorf("author_id is required")
}
s.mu.Lock()
defer s.mu.Unlock()
s.nextID++
comment := &Comment{
ID: fmt.Sprintf("comment-%d", s.nextID),
PostID: req.PostID,
Content: req.Content,
AuthorID: req.AuthorID,
AuthorName: req.AuthorName,
CreatedAt: time.Now().Unix(),
}
s.comments = append(s.comments, comment)
rsp.Comment = comment
return nil
}
// List returns comments, optionally filtered by post or author.
// Use post_id to get all comments on a specific post.
//
// @example {"post_id": "post-1"}
func (s *Comments) List(ctx context.Context, req *ListCommentsRequest, rsp *ListCommentsResponse) error {
s.mu.RLock()
defer s.mu.RUnlock()
for _, c := range s.comments {
if req.PostID != "" && c.PostID != req.PostID {
continue
}
if req.AuthorID != "" && c.AuthorID != req.AuthorID {
continue
}
rsp.Comments = append(rsp.Comments, c)
}
return nil
}
// Delete removes a comment by ID.
//
// @example {"id": "comment-1"}
func (s *Comments) Delete(ctx context.Context, req *DeleteCommentRequest, rsp *DeleteCommentResponse) error {
s.mu.Lock()
defer s.mu.Unlock()
for i, c := range s.comments {
if c.ID == req.ID {
s.comments = append(s.comments[:i], s.comments[i+1:]...)
rsp.Message = fmt.Sprintf("comment %s deleted", req.ID)
return nil
}
}
return fmt.Errorf("comment %s not found", req.ID)
}
// ---------------------------------------------------------------------------
// Mail service — internal messaging between users
// ---------------------------------------------------------------------------
type MailMessage struct {
ID string `json:"id" description:"Unique message identifier"`
From string `json:"from" description:"Sender username"`
To string `json:"to" description:"Recipient username"`
Subject string `json:"subject" description:"Message subject line"`
Body string `json:"body" description:"Message body text"`
Read bool `json:"read" description:"Whether the message has been read"`
CreatedAt int64 `json:"created_at" description:"Unix timestamp of when the message was sent"`
}
type SendMailRequest struct {
From string `json:"from" description:"Sender username (required)"`
To string `json:"to" description:"Recipient username (required)"`
Subject string `json:"subject" description:"Message subject (required)"`
Body string `json:"body" description:"Message body (required)"`
}
type SendMailResponse struct {
Message *MailMessage `json:"message" description:"The sent message"`
}
type ReadMailRequest struct {
User string `json:"user" description:"Username to read inbox for"`
}
type ReadMailResponse struct {
Messages []*MailMessage `json:"messages" description:"Inbox messages, newest first"`
}
type Mail struct {
mu sync.RWMutex
messages []*MailMessage
nextID int
}
// Send delivers a message to another user on the platform.
// Both sender and recipient are identified by username.
//
// @example {"from": "alice", "to": "bob", "subject": "Welcome!", "body": "Hey Bob, welcome to the platform!"}
func (s *Mail) Send(ctx context.Context, req *SendMailRequest, rsp *SendMailResponse) error {
if req.From == "" || req.To == "" {
return fmt.Errorf("from and to are required")
}
if req.Subject == "" {
return fmt.Errorf("subject is required")
}
s.mu.Lock()
defer s.mu.Unlock()
s.nextID++
msg := &MailMessage{
ID: fmt.Sprintf("mail-%d", s.nextID),
From: req.From,
To: req.To,
Subject: req.Subject,
Body: req.Body,
CreatedAt: time.Now().Unix(),
}
s.messages = append(s.messages, msg)
rsp.Message = msg
return nil
}
// Read returns all messages in a user's inbox, newest first.
//
// @example {"user": "alice"}
func (s *Mail) Read(ctx context.Context, req *ReadMailRequest, rsp *ReadMailResponse) error {
if req.User == "" {
return fmt.Errorf("user is required")
}
s.mu.Lock()
defer s.mu.Unlock()
for i := len(s.messages) - 1; i >= 0; i-- {
if s.messages[i].To == req.User {
s.messages[i].Read = true
rsp.Messages = append(rsp.Messages, s.messages[i])
}
}
return nil
}
// ---------------------------------------------------------------------------
// Main — wire up all services with MCP gateway
// ---------------------------------------------------------------------------
func main() {
service := micro.NewService("platform",
micro.Address(":9090"),
mcp.WithMCP(":3001"),
)
service.Init()
users := NewUsers()
posts := NewPosts()
// Seed some demo data so agents have something to work with
seedData(users, posts)
service.Handle(users)
service.Handle(posts)
service.Handle(&Comments{})
service.Handle(&Mail{},
server.WithEndpointScopes("Mail.Send", "mail:write"),
server.WithEndpointScopes("Mail.Read", "mail:read"),
)
printBanner()
if err := service.Run(); err != nil {
log.Fatal(err)
}
}
func seedData(users *Users, posts *Posts) {
// Create demo users
var aliceRsp SignupResponse
users.Signup(context.Background(), &SignupRequest{
Name: "alice", Password: "secret123",
}, &aliceRsp)
var bobRsp SignupResponse
users.Signup(context.Background(), &SignupRequest{
Name: "bob", Password: "secret123",
}, &bobRsp)
// Alice writes a welcome post
var postRsp CreatePostResponse
posts.Create(context.Background(), &CreatePostRequest{
Title: "Welcome to the Platform",
Content: "This is the first post on our new blogging platform. Built with Go Micro, every service is automatically accessible to AI agents through MCP.",
AuthorID: aliceRsp.User.ID,
AuthorName: "alice",
}, &postRsp)
// Tag it
posts.TagPost(context.Background(), &TagPostRequest{
PostID: postRsp.Post.ID, Tag: "welcome",
}, &TagPostResponse{})
posts.TagPost(context.Background(), &TagPostRequest{
PostID: postRsp.Post.ID, Tag: "go-micro",
}, &TagPostResponse{})
}
func printBanner() {
fmt.Println()
fmt.Println(" Platform Demo — AI-Native Microservices")
fmt.Println()
fmt.Println(" Services: Users, Posts, Comments, Mail")
fmt.Println(" MCP Tools: http://localhost:3001/mcp/tools")
fmt.Println(" RPC: localhost:9090")
fmt.Println()
fmt.Println(" Seeded: alice (user-1), bob (user-2)")
fmt.Println(" 1 post with tags [welcome, go-micro]")
fmt.Println()
fmt.Println(" Try asking an agent:")
fmt.Println()
fmt.Println(` "Sign up a new user called carol"`)
fmt.Println(` "Log in as alice and write a post about Go concurrency patterns"`)
fmt.Println(` "List all posts and comment on the welcome post as bob"`)
fmt.Println(` "Tag alice's post with 'tutorial' and 'golang'"`)
fmt.Println(` "Send a mail from alice to bob welcoming him to the platform"`)
fmt.Println(` "Show me bob's inbox"`)
fmt.Println(` "List all users and show me all tags in use"`)
fmt.Println()
}
func generateToken() string {
b := make([]byte, 16)
rand.Read(b)
return hex.EncodeToString(b)
}
+67
View File
@@ -0,0 +1,67 @@
# Workflow Example: Cross-Service Orchestration
An e-commerce scenario with three services (Inventory, Orders, Notifications) that demonstrates how AI agents orchestrate multi-step workflows across services — no glue code, no workflow engine.
## The Workflow
When a user says _"Order a ThinkPad for alice and send her a confirmation"_, the agent figures out the steps:
```
1. InventoryService.Search → Find the product
2. InventoryService.CheckStock → Verify availability
3. InventoryService.ReserveStock → Decrement inventory
4. OrderService.PlaceOrder → Create the order
5. NotificationService.Send → Email confirmation
```
No code connects these steps — the agent reads the tool descriptions and chains the calls itself.
## Run
```bash
go run .
```
## Services
| Service | Tools | Purpose |
|---------|-------|---------|
| InventoryService | Search, CheckStock, ReserveStock | Product catalog and stock management |
| OrderService | PlaceOrder, GetOrder, ListOrders | Order creation and lookup |
| NotificationService | Send, List | Email/SMS/Slack notifications |
## Example Prompts
Try these with Claude Code (`micro mcp serve`) or any MCP-compatible agent:
- "What laptops do you have in stock?"
- "Order a ThinkPad for alice@example.com and send her a confirmation"
- "Check if 'The Go Programming Language' is available" (it's out of stock!)
- "Order 3 Go Gopher t-shirts for bob@example.com, reserve the stock, and notify him via Slack"
- "Show me all orders and notifications for alice"
## Why This Matters
Traditional approach:
```go
// 50+ lines of glue code wiring services together
func handleOrder(req OrderRequest) {
product, err := inventoryClient.CheckStock(req.SKU)
if err != nil { ... }
if product.InStock < req.Quantity { ... }
_, err = inventoryClient.ReserveStock(req.SKU, req.Quantity)
if err != nil { ... }
order, err := orderClient.PlaceOrder(...)
if err != nil { ... }
_, err = notificationClient.Send(...)
// ...
}
```
Agent approach:
```
User: "Order a ThinkPad for alice and confirm via email"
Agent: [reads tool descriptions, chains 5 calls, handles the out-of-stock case]
```
The agent handles the orchestration. You just write the individual services with good documentation.
+393
View File
@@ -0,0 +1,393 @@
// Workflow example: cross-service orchestration via AI agents.
//
// This example runs three services (Inventory, Orders, Notifications) and
// demonstrates how an AI agent can orchestrate a multi-step workflow:
//
// 1. Check inventory for a product
// 2. Place an order if in stock
// 3. Send a confirmation notification
//
// The agent figures out the right sequence of calls on its own — no
// workflow engine, no glue code, just natural language.
//
// Run:
//
// go run .
//
// MCP tools: http://localhost:3001/mcp/tools
package main
import (
"context"
"fmt"
"log"
"strings"
"sync"
"time"
"go-micro.dev/v6"
"go-micro.dev/v6/gateway/mcp"
)
// ---------------------------------------------------------------------------
// Inventory service
// ---------------------------------------------------------------------------
type Product struct {
SKU string `json:"sku" description:"Stock keeping unit identifier"`
Name string `json:"name" description:"Product name"`
Price float64 `json:"price" description:"Unit price in USD"`
InStock int `json:"in_stock" description:"Number of units available"`
Category string `json:"category" description:"Product category"`
}
type CheckStockRequest struct {
SKU string `json:"sku" description:"Product SKU to check"`
}
type CheckStockResponse struct {
Product *Product `json:"product" description:"Product details with current stock level"`
}
type SearchProductsRequest struct {
Query string `json:"query" description:"Search term to match against product name or category"`
Category string `json:"category,omitempty" description:"Filter by category: electronics, clothing, books (optional)"`
}
type SearchProductsResponse struct {
Products []*Product `json:"products" description:"Products matching the search criteria"`
}
type ReserveStockRequest struct {
SKU string `json:"sku" description:"Product SKU to reserve"`
Quantity int `json:"quantity" description:"Number of units to reserve"`
}
type ReserveStockResponse struct {
Reserved bool `json:"reserved" description:"True if stock was successfully reserved"`
Remaining int `json:"remaining" description:"Units remaining after reservation"`
Message string `json:"message" description:"Human-readable result message"`
}
type InventoryService struct {
mu sync.RWMutex
products map[string]*Product
}
// CheckStock returns the current stock level for a product.
// Use this before placing an order to verify availability.
//
// @example {"sku": "LAPTOP-001"}
func (s *InventoryService) CheckStock(ctx context.Context, req *CheckStockRequest, rsp *CheckStockResponse) error {
s.mu.RLock()
defer s.mu.RUnlock()
p, ok := s.products[req.SKU]
if !ok {
return fmt.Errorf("product %s not found", req.SKU)
}
rsp.Product = p
return nil
}
// Search finds products by name or category. Use this to help
// users find what they're looking for before checking stock.
//
// @example {"query": "laptop"}
func (s *InventoryService) Search(ctx context.Context, req *SearchProductsRequest, rsp *SearchProductsResponse) error {
s.mu.RLock()
defer s.mu.RUnlock()
q := strings.ToLower(req.Query)
for _, p := range s.products {
if req.Category != "" && !strings.EqualFold(p.Category, req.Category) {
continue
}
if q == "" || strings.Contains(strings.ToLower(p.Name), q) || strings.Contains(strings.ToLower(p.Category), q) {
rsp.Products = append(rsp.Products, p)
}
}
return nil
}
// ReserveStock decrements inventory for a product. Call this after
// confirming stock is available. Returns an error if insufficient stock.
//
// @example {"sku": "LAPTOP-001", "quantity": 1}
func (s *InventoryService) ReserveStock(ctx context.Context, req *ReserveStockRequest, rsp *ReserveStockResponse) error {
s.mu.Lock()
defer s.mu.Unlock()
p, ok := s.products[req.SKU]
if !ok {
return fmt.Errorf("product %s not found", req.SKU)
}
if p.InStock < req.Quantity {
rsp.Reserved = false
rsp.Remaining = p.InStock
rsp.Message = fmt.Sprintf("insufficient stock: requested %d but only %d available", req.Quantity, p.InStock)
return nil
}
p.InStock -= req.Quantity
rsp.Reserved = true
rsp.Remaining = p.InStock
rsp.Message = fmt.Sprintf("reserved %d units of %s", req.Quantity, p.Name)
return nil
}
// ---------------------------------------------------------------------------
// Orders service
// ---------------------------------------------------------------------------
type Order struct {
ID string `json:"id" description:"Unique order identifier"`
Customer string `json:"customer" description:"Customer name or email"`
SKU string `json:"sku" description:"Product SKU ordered"`
Quantity int `json:"quantity" description:"Number of units"`
Total float64 `json:"total" description:"Total order amount in USD"`
Status string `json:"status" description:"Order status: pending, confirmed, shipped, delivered"`
CreatedAt time.Time `json:"created_at" description:"When the order was placed"`
}
type PlaceOrderRequest struct {
Customer string `json:"customer" description:"Customer name or email (required)"`
SKU string `json:"sku" description:"Product SKU to order (required)"`
Quantity int `json:"quantity" description:"Number of units (required, must be positive)"`
}
type PlaceOrderResponse struct {
Order *Order `json:"order" description:"The newly created order"`
}
type GetOrderRequest struct {
ID string `json:"id" description:"Order ID to look up"`
}
type GetOrderResponse struct {
Order *Order `json:"order" description:"The requested order"`
}
type ListOrdersRequest struct {
Customer string `json:"customer,omitempty" description:"Filter by customer (optional)"`
Status string `json:"status,omitempty" description:"Filter by status (optional)"`
}
type ListOrdersResponse struct {
Orders []*Order `json:"orders" description:"Matching orders"`
}
type OrderService struct {
mu sync.RWMutex
orders map[string]*Order
nextID int
// In a real app this would be a client to the inventory service
inventory *InventoryService
}
// PlaceOrder creates a new order. Stock must be reserved first via
// InventoryService.ReserveStock — this service does not check inventory.
//
// @example {"customer": "alice@example.com", "sku": "LAPTOP-001", "quantity": 1}
func (s *OrderService) PlaceOrder(ctx context.Context, req *PlaceOrderRequest, rsp *PlaceOrderResponse) error {
if req.Customer == "" {
return fmt.Errorf("customer is required")
}
if req.SKU == "" {
return fmt.Errorf("sku is required")
}
if req.Quantity <= 0 {
return fmt.Errorf("quantity must be positive")
}
// Look up price
s.inventory.mu.RLock()
p, ok := s.inventory.products[req.SKU]
s.inventory.mu.RUnlock()
if !ok {
return fmt.Errorf("product %s not found", req.SKU)
}
s.mu.Lock()
defer s.mu.Unlock()
s.nextID++
order := &Order{
ID: fmt.Sprintf("ORD-%04d", s.nextID),
Customer: req.Customer,
SKU: req.SKU,
Quantity: req.Quantity,
Total: p.Price * float64(req.Quantity),
Status: "confirmed",
CreatedAt: time.Now(),
}
s.orders[order.ID] = order
rsp.Order = order
return nil
}
// GetOrder retrieves an order by ID.
//
// @example {"id": "ORD-0001"}
func (s *OrderService) GetOrder(ctx context.Context, req *GetOrderRequest, rsp *GetOrderResponse) error {
s.mu.RLock()
defer s.mu.RUnlock()
o, ok := s.orders[req.ID]
if !ok {
return fmt.Errorf("order %s not found", req.ID)
}
rsp.Order = o
return nil
}
// ListOrders returns orders, optionally filtered by customer or status.
//
// @example {"customer": "alice@example.com"}
func (s *OrderService) ListOrders(ctx context.Context, req *ListOrdersRequest, rsp *ListOrdersResponse) error {
s.mu.RLock()
defer s.mu.RUnlock()
for _, o := range s.orders {
if req.Customer != "" && o.Customer != req.Customer {
continue
}
if req.Status != "" && o.Status != req.Status {
continue
}
rsp.Orders = append(rsp.Orders, o)
}
return nil
}
// ---------------------------------------------------------------------------
// Notifications service
// ---------------------------------------------------------------------------
type Notification struct {
ID string `json:"id" description:"Notification identifier"`
Recipient string `json:"recipient" description:"Who received the notification"`
Subject string `json:"subject" description:"Notification subject line"`
Body string `json:"body" description:"Notification body text"`
Channel string `json:"channel" description:"Delivery channel: email, sms, or slack"`
SentAt time.Time `json:"sent_at" description:"When the notification was sent"`
}
type SendNotificationRequest struct {
Recipient string `json:"recipient" description:"Email address, phone number, or Slack handle"`
Subject string `json:"subject" description:"Subject line (required)"`
Body string `json:"body" description:"Message body (required)"`
Channel string `json:"channel,omitempty" description:"Channel: email (default), sms, or slack"`
}
type SendNotificationResponse struct {
Notification *Notification `json:"notification" description:"The sent notification with delivery details"`
}
type ListNotificationsRequest struct {
Recipient string `json:"recipient,omitempty" description:"Filter by recipient (optional)"`
}
type ListNotificationsResponse struct {
Notifications []*Notification `json:"notifications" description:"Sent notifications"`
}
type NotificationService struct {
mu sync.RWMutex
notifications []*Notification
nextID int
}
// Send delivers a notification to a recipient via the specified channel.
// Use this to confirm orders, alert users, or send updates.
// Defaults to email if no channel is specified.
//
// @example {"recipient": "alice@example.com", "subject": "Order Confirmed", "body": "Your order ORD-0001 has been confirmed.", "channel": "email"}
func (s *NotificationService) Send(ctx context.Context, req *SendNotificationRequest, rsp *SendNotificationResponse) error {
if req.Recipient == "" {
return fmt.Errorf("recipient is required")
}
if req.Subject == "" {
return fmt.Errorf("subject is required")
}
if req.Body == "" {
return fmt.Errorf("body is required")
}
channel := req.Channel
if channel == "" {
channel = "email"
}
s.mu.Lock()
defer s.mu.Unlock()
s.nextID++
n := &Notification{
ID: fmt.Sprintf("notif-%d", s.nextID),
Recipient: req.Recipient,
Subject: req.Subject,
Body: req.Body,
Channel: channel,
SentAt: time.Now(),
}
s.notifications = append(s.notifications, n)
rsp.Notification = n
return nil
}
// List returns sent notifications, optionally filtered by recipient.
//
// @example {"recipient": "alice@example.com"}
func (s *NotificationService) List(ctx context.Context, req *ListNotificationsRequest, rsp *ListNotificationsResponse) error {
s.mu.RLock()
defer s.mu.RUnlock()
for _, n := range s.notifications {
if req.Recipient != "" && n.Recipient != req.Recipient {
continue
}
rsp.Notifications = append(rsp.Notifications, n)
}
return nil
}
// ---------------------------------------------------------------------------
// Main
// ---------------------------------------------------------------------------
func main() {
service := micro.NewService("shop",
micro.Address(":9090"),
mcp.WithMCP(":3001"),
)
service.Init()
inventory := &InventoryService{products: map[string]*Product{
"LAPTOP-001": {SKU: "LAPTOP-001", Name: "ThinkPad X1 Carbon", Price: 1299.99, InStock: 15, Category: "electronics"},
"LAPTOP-002": {SKU: "LAPTOP-002", Name: "MacBook Air M3", Price: 1099.00, InStock: 8, Category: "electronics"},
"PHONE-001": {SKU: "PHONE-001", Name: "Pixel 8 Pro", Price: 899.00, InStock: 23, Category: "electronics"},
"BOOK-001": {SKU: "BOOK-001", Name: "Designing Data-Intensive Applications", Price: 45.99, InStock: 50, Category: "books"},
"BOOK-002": {SKU: "BOOK-002", Name: "The Go Programming Language", Price: 39.99, InStock: 0, Category: "books"},
"SHIRT-001": {SKU: "SHIRT-001", Name: "Go Gopher T-Shirt", Price: 24.99, InStock: 100, Category: "clothing"},
}}
orders := &OrderService{
orders: make(map[string]*Order),
inventory: inventory,
}
notifications := &NotificationService{}
service.Handle(inventory)
service.Handle(orders)
service.Handle(notifications)
fmt.Println()
fmt.Println(" Shop Workflow Demo")
fmt.Println()
fmt.Println(" MCP Tools: http://localhost:3001/mcp/tools")
fmt.Println()
fmt.Println(" Try asking an agent:")
fmt.Println()
fmt.Println(" \"What laptops do you have in stock?\"")
fmt.Println(" \"Order a ThinkPad for alice@example.com and send her a confirmation\"")
fmt.Println(" \"Check if 'The Go Programming Language' is available\"")
fmt.Println(" \"Show me all orders for alice@example.com\"")
fmt.Println(" \"Order 3 Go Gopher t-shirts for bob@example.com, reserve the stock, and notify him\"")
fmt.Println()
if err := service.Run(); err != nil {
log.Fatal(err)
}
}
+78
View File
@@ -0,0 +1,78 @@
// Multi-service example: run multiple services in a single binary.
//
// Each service gets its own server, client, store, and cache while
// sharing the registry, broker, and transport — so they can
// discover and call each other within the same process.
package main
import (
"context"
"fmt"
"log"
"go-micro.dev/v6"
)
// -- Users service --
type UserRequest struct {
Id string `json:"id"`
}
type UserResponse struct {
Name string `json:"name"`
Email string `json:"email"`
}
type Users struct{}
func (u *Users) Lookup(ctx context.Context, req *UserRequest, rsp *UserResponse) error {
log.Printf("[users] Lookup id=%s", req.Id)
rsp.Name = "Alice"
rsp.Email = "alice@example.com"
return nil
}
// -- Orders service --
type OrderRequest struct {
UserId string `json:"user_id"`
}
type OrderResponse struct {
OrderId string `json:"order_id"`
Status string `json:"status"`
}
type Orders struct{}
func (o *Orders) Create(ctx context.Context, req *OrderRequest, rsp *OrderResponse) error {
log.Printf("[orders] Create for user=%s", req.UserId)
rsp.OrderId = "ORD-001"
rsp.Status = "created"
return nil
}
func main() {
// Create two services — each gets isolated server, client,
// store, and cache instances automatically.
users := micro.NewService("users", micro.Address(":9001"))
orders := micro.NewService("orders", micro.Address(":9002"))
// Register handlers
if err := users.Handle(new(Users)); err != nil {
log.Fatal(err)
}
if err := orders.Handle(new(Orders)); err != nil {
log.Fatal(err)
}
// Run both services together. The group handles signals
// and stops all services when one exits.
g := micro.NewGroup(users, orders)
fmt.Println("Starting users (:9001) and orders (:9002) in a single binary")
if err := g.Run(); err != nil {
log.Fatal(err)
}
}
+110
View File
@@ -0,0 +1,110 @@
# Zero-to-hero support desk
A maintained 0-to-hero reference for the Go Micro lifecycle: scaffold a few
typed services, run them in one process, let an agent chat with those services
as tools, then inspect the durable flow that triggered the work. It is one
runnable file and one CI smoke test, so the reference path stays honest as the
framework evolves.
## The path
1. **Scaffold services**`customers`, `tickets`, and `notify` are ordinary
typed Go Micro services. Their request/response structs and method comments
become the tool contract the agent sees.
2. **Run the harness** — the example starts an in-memory registry, broker,
client, store, services, agent, and flow in one process; no external
dependencies or API key are required for the default run.
3. **Chat through an agent** — the `support` agent receives the ticket event as
a prompt and calls service tools to look up the customer, triage the ticket,
and draft a reply.
4. **Inspect the workflow** — the `intake` flow records the event-driven run and
prints the agent result, showing the service → agent → workflow lifecycle as
one runtime.
## The scenario
A customer files a ticket. A `ticket.created` event triggers the support
agent, which:
1. looks the customer up (`customers` service),
2. sets the ticket's priority (`tickets` service),
3. drafts a reply and emails it (`notify` service) — **but only after passing
the approval gate.**
```
> event: events.ticket.created {"id":"ticket-1","customer":"alice@acme.com",...}
[customers] looked up Alice (pro plan)
[tickets] ticket-1 → priority=high status=in_progress
▣ approval gate notify_NotifyService_Send(alice@acme.com) — approved
[notify] 📨 to=alice@acme.com: "Hi Alice — thanks for reaching out..."
✓ ticket triaged and the customer was replied to — triggered by an event
```
## The pieces
- **Services** (`customers`, `tickets`, `notify`) — plain Go Micro services. The
agent discovers their endpoints as tools automatically.
- **Agent** (`support`) — `micro.NewAgent` with those three services. It reasons
over the ticket and calls the tools.
- **Flow** (`intake`) — triggers on `events.ticket.created` and hands the event to
the agent: *the event is the prompt*. No human types anything.
- **Guardrail** (`ApproveTool`) — the agent can read and triage freely, but
emailing a customer (`notify.Send`) passes through the gate first. Return
`false` to hold it for a person or a policy; the example approves and logs.
## Expected inspect transcript
The provider-free run prints the same visible checkpoints a new developer should
compare against after chat and flow execution. The transcript includes service
tool calls, the approval gate, and the inspect/run-history commands that prove
the workflow run was recorded.
```text
> event: events.ticket.created {"customer":"alice@acme.com","id":"ticket-1","subject":"Can't log in"}
[customers] looked up Alice (pro plan)
[tickets] ticket-1 → priority=high status=in_progress
▣ approval gate notify_NotifyService_Send(alice@acme.com) — approved
[notify] 📨 to=alice@acme.com: "Hi Alice — thanks for reaching out. We've bumped this to high priority and are on it."
support agent: Triaged ticket-1 for Alice and sent a reply.
inspect transcript:
micro inspect flow intake
flow: intake runs=1 latest.reply="Triaged ticket-1 for Alice and sent a reply."
micro agent history support
agent: support runs=1 latest.status=completed
✓ ticket triaged and the customer was replied to — triggered by an event
```
## Run
```bash
go run main.go # mock model — deterministic, no API key
```
The maintained check is the same deterministic path:
```bash
go test ./examples/support
```
Against a live model, the agent reasons about the ticket itself instead of
following the script:
```bash
export ANTHROPIC_API_KEY=sk-ant-... # or OPENAI_API_KEY, GEMINI_API_KEY, ...
go run main.go -provider anthropic
```
## What to change next
- Make the gate real: return `false` from `ApproveTool` for billing actions, or
route the decision to a human.
- Expose the agent over A2A so another team's agent can file tickets — add
`agent.WithA2A(":4000")`.
- Add a `kb` (knowledge base) service and watch the agent search it before
replying.
+329
View File
@@ -0,0 +1,329 @@
// Support desk — a real-world agent built from services, a flow, and a
// guardrail. It's the "zero to hero" shape: a few services, an agent that
// manages them, an event that triggers the agent, and a human-in-the-loop
// gate on the one action that touches a customer.
//
// The scenario: a customer files a ticket. A ticket.created event triggers
// the support agent, which looks the customer up, sets the ticket's
// priority, and drafts a reply — but it can't actually email the customer
// without passing the approval gate.
//
// Everything is real — services, registry, RPC, broker, the agent loop,
// the store. Only the LLM is mocked, so it runs with no API key. Pass
// -provider anthropic (with a key) to run it against a live model; then the
// agent reasons about the ticket itself instead of following the script.
//
// Run:
//
// go run ./examples/support
package main
import (
"context"
"encoding/json"
"flag"
"fmt"
"os"
"strings"
"sync"
"time"
"go-micro.dev/v6/agent"
"go-micro.dev/v6/ai"
"go-micro.dev/v6/broker"
"go-micro.dev/v6/client"
"go-micro.dev/v6/flow"
"go-micro.dev/v6/registry"
"go-micro.dev/v6/selector"
"go-micro.dev/v6/service"
"go-micro.dev/v6/store"
)
// ---------------------------------------------------------------------------
// services — the support desk's capabilities
// ---------------------------------------------------------------------------
type Customer struct {
Email string `json:"email"`
Name string `json:"name"`
Plan string `json:"plan"`
}
type LookupRequest struct {
Email string `json:"email" description:"Customer email to look up (required)"`
}
// CustomerService is a tiny seeded customer directory.
type CustomerService struct{}
// Lookup returns the customer with the given email.
// @example {"email": "alice@acme.com"}
func (s *CustomerService) Lookup(_ context.Context, req *LookupRequest, rsp *Customer) error {
known := map[string]Customer{
"alice@acme.com": {Email: "alice@acme.com", Name: "Alice", Plan: "pro"},
}
c, ok := known[req.Email]
if !ok {
return fmt.Errorf("customer %s not found", req.Email)
}
*rsp = c
fmt.Printf(" \033[32m[customers]\033[0m looked up %s (%s plan)\n", c.Name, c.Plan)
return nil
}
type Ticket struct {
ID string `json:"id"`
Customer string `json:"customer"`
Subject string `json:"subject"`
Body string `json:"body"`
Priority string `json:"priority,omitempty"`
Status string `json:"status"`
}
type UpdateRequest struct {
ID string `json:"id" description:"Ticket id (required)"`
Priority string `json:"priority" description:"Priority: low, normal, high"`
Status string `json:"status" description:"Status: open, in_progress, resolved"`
}
// TicketService stores support tickets in memory.
type TicketService struct {
mu sync.Mutex
tickets map[string]*Ticket
}
func (s *TicketService) seed(t *Ticket) {
s.mu.Lock()
if s.tickets == nil {
s.tickets = map[string]*Ticket{}
}
s.tickets[t.ID] = t
s.mu.Unlock()
}
// Update changes a ticket's priority and/or status.
// @example {"id": "ticket-1", "priority": "high", "status": "in_progress"}
func (s *TicketService) Update(_ context.Context, req *UpdateRequest, rsp *Ticket) error {
s.mu.Lock()
defer s.mu.Unlock()
t, ok := s.tickets[req.ID]
if !ok {
return fmt.Errorf("ticket %s not found", req.ID)
}
if req.Priority != "" {
t.Priority = req.Priority
}
if req.Status != "" {
t.Status = req.Status
}
*rsp = *t
fmt.Printf(" \033[32m[tickets]\033[0m %s → priority=%s status=%s\n", t.ID, t.Priority, t.Status)
return nil
}
type SendRequest struct {
To string `json:"to" description:"Recipient email (required)"`
Message string `json:"message" description:"Reply body (required)"`
}
type SendResponse struct {
Sent bool `json:"sent"`
}
// NotifyService emails the customer. This is the action we gate.
type NotifyService struct{ sent int }
// Send emails a reply to a customer.
// @example {"to": "alice@acme.com", "message": "We're on it."}
func (s *NotifyService) Send(_ context.Context, req *SendRequest, rsp *SendResponse) error {
s.sent++
fmt.Printf(" \033[35m[notify]\033[0m 📨 to=%s: %q\n", req.To, req.Message)
rsp.Sent = true
return nil
}
// ---------------------------------------------------------------------------
// mock LLM — the only fake. It scripts the triage from the offered tools.
// ---------------------------------------------------------------------------
type mockModel struct{ opts ai.Options }
func newMock(opts ...ai.Option) ai.Model {
m := &mockModel{}
_ = m.Init(opts...)
return m
}
func (m *mockModel) Init(opts ...ai.Option) error {
for _, o := range opts {
o(&m.opts)
}
return nil
}
func (m *mockModel) Options() ai.Options { return m.opts }
func (m *mockModel) String() string { return "mock" }
func (m *mockModel) Stream(context.Context, *ai.Request, ...ai.GenerateOption) (ai.Stream, error) {
return nil, fmt.Errorf("stream not supported by mock")
}
func (m *mockModel) call(ctx context.Context, tools []ai.Tool, sub string, input map[string]any) {
for _, t := range tools {
if strings.Contains(t.Name, sub) && m.opts.ToolHandler != nil {
m.opts.ToolHandler(ctx, ai.ToolCall{ID: sub, Name: t.Name, Input: input})
return
}
}
}
func (m *mockModel) Generate(ctx context.Context, req *ai.Request, _ ...ai.GenerateOption) (*ai.Response, error) {
// A real model would read the ticket from the prompt and decide. The
// mock follows a fixed, sensible triage so the demo is deterministic.
m.call(ctx, req.Tools, "Lookup", map[string]any{"email": "alice@acme.com"})
m.call(ctx, req.Tools, "Update", map[string]any{"id": "ticket-1", "priority": "high", "status": "in_progress"})
m.call(ctx, req.Tools, "Send", map[string]any{
"to": "alice@acme.com",
"message": "Hi Alice — thanks for reaching out. We've bumped this to high priority and are on it.",
})
return &ai.Response{Answer: "Triaged ticket-1 for Alice and sent a reply."}, nil
}
// ---------------------------------------------------------------------------
// wiring
// ---------------------------------------------------------------------------
func providerKey(provider string) string {
if v := os.Getenv("MICRO_AI_API_KEY"); v != "" {
return v
}
return os.Getenv(map[string]string{
"anthropic": "ANTHROPIC_API_KEY", "openai": "OPENAI_API_KEY",
"gemini": "GEMINI_API_KEY", "groq": "GROQ_API_KEY", "mistral": "MISTRAL_API_KEY",
"together": "TOGETHER_API_KEY", "atlascloud": "ATLASCLOUD_API_KEY",
}[provider])
}
func waitFor(reg registry.Registry, names ...string) {
deadline := time.Now().Add(5 * time.Second)
for _, name := range names {
for time.Now().Before(deadline) {
if svcs, err := reg.GetService(name); err == nil && len(svcs) > 0 && len(svcs[0].Nodes) > 0 {
break
}
time.Sleep(20 * time.Millisecond)
}
}
}
func runSupport(provider string) error {
apiKey := ""
if provider == "mock" {
ai.Register("mock", newMock)
} else if apiKey = providerKey(provider); apiKey == "" {
return fmt.Errorf("no API key for provider %q — set MICRO_AI_API_KEY or the provider's key env", provider)
}
fmt.Printf("\n\033[1mSupport desk (provider: %s)\033[0m\n\n", provider)
// Shared in-memory infrastructure so the demo runs in one process.
reg := registry.NewMemoryRegistry()
br := broker.NewMemoryBroker()
if err := br.Connect(); err != nil {
return fmt.Errorf("broker connect: %w", err)
}
cl := client.NewClient(client.Registry(reg), client.Selector(selector.NewSelector(selector.Registry(reg))))
// Services.
tickets := new(TicketService)
notify := new(NotifyService)
var services []service.Service
for name, h := range map[string]any{"customers": new(CustomerService), "tickets": tickets, "notify": notify} {
svc := service.New(service.Name(name), service.Address("127.0.0.1:0"), service.Registry(reg), service.Client(cl), service.HandleSignal(false))
_ = svc.Handle(h)
services = append(services, svc)
go svc.Run()
}
defer func() {
for _, svc := range services {
_ = svc.Server().Stop()
}
}()
// The support agent manages the three services. The approval gate is
// the human-in-the-loop: it can read and triage freely, but emailing a
// customer (notify.Send) passes through here first. Return false to hold
// it for a person or a policy; here we approve and log.
support := agent.New(
agent.Name("support"),
agent.Address("127.0.0.1:0"),
agent.Services("customers", "tickets", "notify"),
agent.Prompt("You are a support agent. For each ticket, look up the customer, set an "+
"appropriate priority, and reply to them. Escalate billing issues."),
agent.Provider(provider), agent.APIKey(apiKey),
agent.ApproveTool(func(tool string, input map[string]any) (bool, string) {
if strings.Contains(tool, "Send") {
fmt.Printf(" \033[33m▣ approval gate\033[0m %s(%v) — approved\n", tool, input["to"])
}
return true, ""
}),
agent.WithRegistry(reg), agent.WithClient(cl), agent.WithStore(store.NewMemoryStore()),
)
go support.Run()
defer support.Stop()
waitFor(reg, "customers", "tickets", "notify", "support")
// A new ticket arrives, and a flow turns the event into work for the
// agent: the event is the prompt.
intake := flow.New("intake",
flow.Trigger("events.ticket.created"),
flow.Agent("support"),
flow.Prompt("A new support ticket arrived: {{.Data}}. Handle it."),
)
if err := intake.Register(reg, br, cl); err != nil {
return fmt.Errorf("flow register: %w", err)
}
defer intake.Stop()
// The customer files a ticket (it exists before the event fires).
tickets.seed(&Ticket{ID: "ticket-1", Customer: "alice@acme.com", Subject: "Can't log in", Body: "I'm locked out.", Status: "open"})
body, _ := json.Marshal(map[string]string{"id": "ticket-1", "customer": "alice@acme.com", "subject": "Can't log in"})
fmt.Println("\033[1m> event:\033[0m events.ticket.created", string(body))
fmt.Println()
if err := br.Publish("events.ticket.created", &broker.Message{Body: body}); err != nil {
return fmt.Errorf("publish: %w", err)
}
// Wait for the agent to act.
deadline := time.Now().Add(20 * time.Second)
for time.Now().Before(deadline) {
if notify.sent >= 1 {
break
}
time.Sleep(50 * time.Millisecond)
}
if rs := intake.Results(); len(rs) > 0 {
latest := rs[len(rs)-1]
fmt.Printf("\n\033[1msupport agent:\033[0m %s\n", latest.Reply)
fmt.Println("\n\033[1minspect transcript:\033[0m")
fmt.Println(" micro inspect flow intake")
fmt.Printf(" flow: intake runs=%d latest.reply=%q\n", len(rs), latest.Reply)
fmt.Println(" micro agent history support")
fmt.Printf(" agent: support runs=%d latest.status=completed\n", len(rs))
}
if notify.sent >= 1 {
fmt.Println("\n\033[32m✓ ticket triaged and the customer was replied to — triggered by an event\033[0m")
return nil
}
fmt.Println("\n\033[31m✗ the agent did not complete the triage\033[0m")
return fmt.Errorf("support agent did not complete triage")
}
func main() {
provider := flag.String("provider", "mock", "LLM provider: mock (default), anthropic, openai, ...")
flag.Parse()
if err := runSupport(*provider); err != nil {
fmt.Println(err)
os.Exit(1)
}
}
+110
View File
@@ -0,0 +1,110 @@
package main
import (
"bytes"
"io"
"os"
"regexp"
"strings"
"testing"
)
func TestRunSupportMockSmoke(t *testing.T) {
if err := runSupport("mock"); err != nil {
t.Fatalf("support example failed: %v", err)
}
}
func TestZeroToHeroReadmeDocumentsLifecycle(t *testing.T) {
b, err := os.ReadFile("README.md")
if err != nil {
t.Fatalf("read README.md: %v", err)
}
doc := string(b)
for _, want := range []string{
"Scaffold services",
"Run the harness",
"Chat through an agent",
"Inspect the workflow",
"go test ./examples/support",
} {
if !strings.Contains(doc, want) {
t.Fatalf("README.md missing zero-to-hero step %q", want)
}
}
}
func TestZeroToHeroInspectTranscript(t *testing.T) {
out := captureStdout(t, func() {
if err := runSupport("mock"); err != nil {
t.Fatalf("support example failed: %v", err)
}
})
got := stripANSI(out)
for _, want := range []string{
`> event: events.ticket.created {"customer":"alice@acme.com","id":"ticket-1","subject":"Can't log in"}`,
`[customers] looked up Alice (pro plan)`,
`[tickets] ticket-1 → priority=high status=in_progress`,
`approval gate notify_NotifyService_Send(alice@acme.com) — approved`,
`[notify] 📨 to=alice@acme.com: "Hi Alice — thanks for reaching out. We've bumped this to high priority and are on it."`,
`support agent: Triaged ticket-1 for Alice and sent a reply.`,
`inspect transcript:`,
`micro inspect flow intake`,
`flow: intake runs=1 latest.reply="Triaged ticket-1 for Alice and sent a reply."`,
`micro agent history support`,
`agent: support runs=1 latest.status=completed`,
`✓ ticket triaged and the customer was replied to — triggered by an event`,
} {
if !strings.Contains(got, want) {
t.Fatalf("support transcript missing %q\n--- got ---\n%s", want, got)
}
}
readme, err := os.ReadFile("README.md")
if err != nil {
t.Fatalf("read README.md: %v", err)
}
for _, want := range []string{
"Expected inspect transcript",
"micro inspect flow intake",
"micro agent history support",
"agent: support runs=1 latest.status=completed",
} {
if !strings.Contains(string(readme), want) {
t.Fatalf("README.md missing transcript contract %q", want)
}
}
}
func captureStdout(t *testing.T, fn func()) (out string) {
t.Helper()
old := os.Stdout
r, w, err := os.Pipe()
if err != nil {
t.Fatalf("capture stdout: %v", err)
}
os.Stdout = w
var buf bytes.Buffer
done := make(chan struct{})
go func() {
_, _ = io.Copy(&buf, r)
close(done)
}()
defer func() {
_ = w.Close()
os.Stdout = old
<-done
out = buf.String()
}()
fn()
return out
}
var ansiRE = regexp.MustCompile(`\x1b\[[0-9;]*m`)
func stripANSI(s string) string {
return ansiRE.ReplaceAllString(s, "")
}
+59
View File
@@ -0,0 +1,59 @@
# Web Service Example
HTTP web service with automatic service discovery and registration.
## What It Does
This example creates an HTTP service that:
- Serves RESTful API endpoints
- Registers with service discovery
- Provides health checks
- Uses standard Go HTTP handlers
## Run It
```bash
go run main.go
```
## Test It
```bash
# Get service info
curl http://localhost:9090/
# List all users
curl http://localhost:9090/users
# Get specific user
curl http://localhost:9090/users/1
# Health check
curl http://localhost:9090/health
```
## Key Features
- **Standard HTTP**: Use familiar `http.Handler` interface
- **Service Discovery**: Automatically registers with registry
- **Health Checks**: Built-in health endpoint
- **JSON APIs**: Easy REST API development
## When to Use
Use `web.Service` when:
- Building REST APIs
- Serving web UIs
- Working with HTTP-specific features
- Migrating existing HTTP services
Use regular `micro.Service` when:
- Building RPC services
- Need bidirectional streaming
- Want automatic load balancing
- Prefer structured RPC over HTTP
## Next Steps
- See [hello-world](../hello-world/) for RPC services
- See [production-ready](../production-ready/) for observability
+70
View File
@@ -0,0 +1,70 @@
module example
go 1.24
require go-micro.dev/v5 v5.16.0
require (
dario.cat/mergo v1.0.2 // indirect
github.com/armon/go-metrics v0.4.1 // indirect
github.com/bitly/go-simplejson v0.5.0 // indirect
github.com/cespare/xxhash/v2 v2.3.0 // indirect
github.com/coreos/go-semver v0.3.0 // indirect
github.com/coreos/go-systemd/v22 v22.3.2 // indirect
github.com/cornelk/hashmap v1.0.8 // indirect
github.com/cpuguy83/go-md2man/v2 v2.0.5 // indirect
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect
github.com/fatih/color v1.16.0 // indirect
github.com/fsnotify/fsnotify v1.6.0 // indirect
github.com/go-redis/redis/v8 v8.11.5 // indirect
github.com/gogo/protobuf v1.3.2 // indirect
github.com/golang/protobuf v1.5.4 // indirect
github.com/google/uuid v1.6.0 // indirect
github.com/hashicorp/consul/api v1.32.1 // 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 v1.0.2 // indirect
github.com/hashicorp/serf v0.10.1 // indirect
github.com/klauspost/compress v1.18.0 // indirect
github.com/lib/pq v1.10.9 // indirect
github.com/mattn/go-colorable v0.1.13 // indirect
github.com/mattn/go-isatty v0.0.20 // indirect
github.com/miekg/dns v1.1.50 // indirect
github.com/mitchellh/go-homedir v1.1.0 // indirect
github.com/mitchellh/hashstructure v1.1.0 // indirect
github.com/mitchellh/mapstructure v1.5.0 // indirect
github.com/nats-io/nats.go v1.42.0 // indirect
github.com/nats-io/nkeys v0.4.11 // indirect
github.com/nats-io/nuid v1.0.1 // indirect
github.com/oxtoacart/bpool v0.0.0-20190530202638-03653db5a59c // indirect
github.com/patrickmn/go-cache v2.1.0+incompatible // indirect
github.com/pkg/errors v0.9.1 // indirect
github.com/rabbitmq/amqp091-go v1.10.0 // indirect
github.com/russross/blackfriday/v2 v2.1.0 // indirect
github.com/urfave/cli/v2 v2.27.6 // indirect
github.com/xrash/smetrics v0.0.0-20240521201337-686a1a2994c1 // indirect
go.etcd.io/bbolt v1.4.0 // indirect
go.etcd.io/etcd/api/v3 v3.5.21 // indirect
go.etcd.io/etcd/client/pkg/v3 v3.5.21 // indirect
go.etcd.io/etcd/client/v3 v3.5.21 // indirect
go.uber.org/multierr v1.10.0 // indirect
go.uber.org/zap v1.27.0 // indirect
golang.org/x/crypto v0.37.0 // indirect
golang.org/x/exp v0.0.0-20250305212735-054e65f0b394 // indirect
golang.org/x/mod v0.24.0 // indirect
golang.org/x/net v0.38.0 // indirect
golang.org/x/sync v0.13.0 // indirect
golang.org/x/sys v0.32.0 // indirect
golang.org/x/text v0.24.0 // indirect
golang.org/x/tools v0.31.0 // indirect
google.golang.org/genproto/googleapis/api v0.0.0-20250324211829-b45e905df463 // indirect
google.golang.org/genproto/googleapis/rpc v0.0.0-20250324211829-b45e905df463 // indirect
google.golang.org/grpc v1.71.1 // indirect
google.golang.org/protobuf v1.36.6 // indirect
)
replace go-micro.dev/v5 => ../..
+385
View File
@@ -0,0 +1,385 @@
dario.cat/mergo v1.0.2 h1:85+piFYR1tMbRrLcDwR18y4UKJ3aH1Tbzi24VRW1TK8=
dario.cat/mergo v1.0.2/go.mod h1:E/hbnu0NxMFBjpMIE34DRGLWqDy0g5FuKDhCb31ngxA=
filippo.io/edwards25519 v1.1.0 h1:FNf4tywRC1HmFuKW5xopWpigGjJKiJSV0Cqo0cJWDaA=
filippo.io/edwards25519 v1.1.0/go.mod h1:BxyFTGdWcka3PhytdK4V28tE5sGfRvvvRV7EaN4VDT4=
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/bitly/go-simplejson v0.5.0 h1:6IH+V8/tVMab511d5bn4M7EwGXZf9Hj6i2xSwkNEM+Y=
github.com/bitly/go-simplejson v0.5.0/go.mod h1:cXHtHw4XUPsvGaxgjIAn8PhEWG9NfngEKAMDJEczWVA=
github.com/bmizerany/assert v0.0.0-20160611221934-b7ed37b82869 h1:DDGfHa7BWjL4YnC6+E63dPcxHo2sUxDIu8g3QgEJdRY=
github.com/bmizerany/assert v0.0.0-20160611221934-b7ed37b82869/go.mod h1:Ekp36dRnpXw/yCqJaO+ZrUyxD+3VXMFFr56k5XYrpB4=
github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs=
github.com/cespare/xxhash/v2 v2.3.0/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/coreos/go-semver v0.3.0 h1:wkHLiw0WNATZnSG7epLsujiMCgPAc9xhjJ4tgnAxmfM=
github.com/coreos/go-semver v0.3.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk=
github.com/coreos/go-systemd/v22 v22.3.2 h1:D9/bQk5vlXQFZ6Kwuu6zaiXJ9oTPe68++AzAJc1DzSI=
github.com/coreos/go-systemd/v22 v22.3.2/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc=
github.com/cornelk/hashmap v1.0.8 h1:nv0AWgw02n+iDcawr5It4CjQIAcdMMKRrs10HOJYlrc=
github.com/cornelk/hashmap v1.0.8/go.mod h1:RfZb7JO3RviW/rT6emczVuC/oxpdz4UsSB2LJSclR1k=
github.com/cpuguy83/go-md2man/v2 v2.0.5 h1:ZtcqGrnekaHpVLArFSe4HK5DoKx1T0rq2DwVB0alcyc=
github.com/cpuguy83/go-md2man/v2 v2.0.5/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
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/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f h1:lO4WD4F/rVNCu3HqELle0jiPLLBs70cWOduZpkS1E78=
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f/go.mod h1:cuUVRXasLTGF7a8hSLbxyZXjz+1KgoB3wDUb6vlszIc=
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.16.0 h1:zmkK9Ngbjj+K0yRhTVONQh1p/HknKYSlNT+vZCzyokM=
github.com/fatih/color v1.16.0/go.mod h1:fL2Sau1YI5c0pdGEVCbKQbLXB6edEj1ZgiY4NijnWvE=
github.com/fsnotify/fsnotify v1.6.0 h1:n+5WquG0fcWoWp6xPWfHdbskMCQaFnG6PfBrh1Ky4HY=
github.com/fsnotify/fsnotify v1.6.0/go.mod h1:sl3t1tCWJFWoRz9R8WJCbQihKKwmorjAbSClcnxKAGw=
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.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-redis/redis/v8 v8.11.5 h1:AcZZR7igkdvfVmQTPnu9WE37LRrO/YrBH5zWyjDC0oI=
github.com/go-redis/redis/v8 v8.11.5/go.mod h1:gREzHqY1hg6oD9ngVRbLStwAWKhA0FEgq8Jd4h5lpwo=
github.com/go-sql-driver/mysql v1.9.2 h1:4cNKDYQ1I84SXslGddlsrMhc8k4LeDVj6Ad6WRjiHuU=
github.com/go-sql-driver/mysql v1.9.2/go.mod h1:qn46aNg1333BRMNU69Lq93t8du/dwxI64Gl8i5p1WMU=
github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY=
github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=
github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q=
github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q=
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/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek=
github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps=
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/btree v1.0.1/go.mod h1:xXMiIv4Fb/0kKde4SpL7qlzvu5cMJDRkFDxJfI9uaxA=
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/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
github.com/google/go-tpm v0.9.3 h1:+yx0/anQuGzi+ssRqeD6WpXjW2L/V0dItUayO0i9sRc=
github.com/google/go-tpm v0.9.3/go.mod h1:h9jEsEECg7gtLis0upRBQU+GhYVH6jMjrFxI8u6bVUY=
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/hashicorp/consul/api v1.32.1 h1:0+osr/3t/aZNAdJX558crU3PEjVrG4x6715aZHRgceE=
github.com/hashicorp/consul/api v1.32.1/go.mod h1:mXUWLnxftwTmDv4W3lzxYCPD199iNLLUyLfLGFJbtl4=
github.com/hashicorp/consul/sdk v0.16.1 h1:V8TxTnImoPD5cj0U9Spl0TUxcytjcbbJeADFF07KdHg=
github.com/hashicorp/consul/sdk v0.16.1/go.mod h1:fSXvwxB2hmh1FMZCNl6PwX0Q/1wdWtHJcZ7Ea5tns0s=
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-msgpack v0.5.5/go.mod h1:ahLV/dePpqEmjfWmKiqvPkv/twdG7iPBM1vqhUKIvfM=
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-sockaddr v1.0.2/go.mod h1:rB4wwRAUzs07qva3c5SdrY/NEtAUjGlgmH/UkBUC97A=
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-uuid v1.0.3/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro=
github.com/hashicorp/go-version v1.2.1 h1:zEfKbn2+PDgroKdiOzqiE8rsmLqU2uwi5PB5pBJ3TkI=
github.com/hashicorp/go-version v1.2.1/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA=
github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
github.com/hashicorp/golang-lru v1.0.2 h1:dV3g9Z/unq5DpblPpw+Oqcv4dU/1omnb4Ok8iPY6p1c=
github.com/hashicorp/golang-lru v1.0.2/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/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8=
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
github.com/klauspost/compress v1.18.0 h1:c/Cqfb0r+Yi+JtIEq73FWXVkRonBlf0CRNYc8Zttxdo=
github.com/klauspost/compress v1.18.0/go.mod h1:2Pp+KzxcywXVXMr50+X0Q/Lsb43OQHYWRCY2AiWywWQ=
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/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
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/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
github.com/lib/pq v1.10.9 h1:YXG7RB+JIjhP29X+OtkiDnYaXQwpS4JEWq7dtCCRUEw=
github.com/lib/pq v1.10.9/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o=
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/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/go.mod h1:p6aan82bvRIyn+zDIv9xYNUpwa73JcSh9BKwknJysuI=
github.com/miekg/dns v1.1.50 h1:DQUfb9uc6smULcREF09Uc+/Gd46YWqJd5DbpPE9xkcA=
github.com/miekg/dns v1.1.50/go.mod h1:e3IlAVfNqAllflbibAZEWOXOQ+Ynzk/dDozDxY7XnME=
github.com/minio/highwayhash v1.0.3 h1:kbnuUMoHYyVl7szWjSxJnxw11k2U709jqFPPmIUyD6Q=
github.com/minio/highwayhash v1.0.3/go.mod h1:GGYsuwP/fPD6Y9hMiXuapVvlIUEhFhMTh0rxU3ik1LQ=
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/hashstructure v1.1.0 h1:P6P1hdjqAAknpY/M1CGipelZgp+4y9ja9kmUZPXP+H0=
github.com/mitchellh/hashstructure v1.1.0/go.mod h1:xUDAozZz0Wmdiufv0uyhnHkUTN6/6d8ulp4AwfLKrmA=
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/nats-io/jwt/v2 v2.7.4 h1:jXFuDDxs/GQjGDZGhNgH4tXzSUK6WQi2rsj4xmsNOtI=
github.com/nats-io/jwt/v2 v2.7.4/go.mod h1:me11pOkwObtcBNR8AiMrUbtVOUGkqYjMQZ6jnSdVUIA=
github.com/nats-io/nats-server/v2 v2.11.3 h1:AbGtXxuwjo0gBroLGGr/dE0vf24kTKdRnBq/3z/Fdoc=
github.com/nats-io/nats-server/v2 v2.11.3/go.mod h1:6Z6Fd+JgckqzKig7DYwhgrE7bJ6fypPHnGPND+DqgMY=
github.com/nats-io/nats.go v1.42.0 h1:ynIMupIOvf/ZWH/b2qda6WGKGNSjwOUutTpWRvAmhaM=
github.com/nats-io/nats.go v1.42.0/go.mod h1:iRWIPokVIFbVijxuMQq4y9ttaBTMe0SFdlZfMDd+33g=
github.com/nats-io/nkeys v0.4.11 h1:q44qGV008kYd9W1b1nEBkNzvnWxtRSQ7A8BoqRrcfa0=
github.com/nats-io/nkeys v0.4.11/go.mod h1:szDimtgmfOi9n25JpfIdGw12tZFYXqhGxjhVxsatHVE=
github.com/nats-io/nuid v1.0.1 h1:5iA8DT8V7q8WK2EScv2padNa/rTESc1KdnPw4TC2paw=
github.com/nats-io/nuid v1.0.1/go.mod h1:19wcPz3Ph3q0Jbyiqsd0kePYG7A95tJPxeL+1OSON2c=
github.com/nxadm/tail v1.4.8 h1:nPr65rt6Y5JFSKQO7qToXr7pePgD6Gwiw05lkbyAQTE=
github.com/nxadm/tail v1.4.8/go.mod h1:+ncqLTQzXmGhMZNUePPaPqPvBxHAIsmXswZKocGu+AU=
github.com/onsi/ginkgo v1.16.5 h1:8xi0RTUf59SOSfEtZMvwTvXYMzG4gV23XVHOZiXNtnE=
github.com/onsi/ginkgo v1.16.5/go.mod h1:+E8gABHa3K6zRBolWtd+ROzc/U5bkGt0FwiG042wbpU=
github.com/onsi/gomega v1.18.1 h1:M1GfJqGRrBrrGGsbxzV5dqM2U2ApXefZCQpkukxYRLE=
github.com/onsi/gomega v1.18.1/go.mod h1:0q+aL8jAiMXy9hbwj2mr5GziHiwhAIQpFmmtT5hitRs=
github.com/oxtoacart/bpool v0.0.0-20190530202638-03653db5a59c h1:rp5dCmg/yLR3mgFuSOe4oEnDDmGLROTvMragMUXpTQw=
github.com/oxtoacart/bpool v0.0.0-20190530202638-03653db5a59c/go.mod h1:X07ZCGwUbLaax7L0S3Tw4hpejzu63ZrrQiUe6W0hcy0=
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/patrickmn/go-cache v2.1.0+incompatible h1:HRMgzkcYKYpi3C8ajMPV8OFXaaRUnok+kx1WdO15EQc=
github.com/patrickmn/go-cache v2.1.0+incompatible/go.mod h1:3Qf8kWWT7OJRJbdiICTKqZju1ZixQ/KpMGzzAfe6+WQ=
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/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
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/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
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/rabbitmq/amqp091-go v1.10.0 h1:STpn5XsHlHGcecLmMFCtg7mqq0RnD+zFr4uzukfVhBw=
github.com/rabbitmq/amqp091-go v1.10.0/go.mod h1:Hy4jKW5kQART1u+JkDTF9YYOQUHXqMuhrgxOEeS7G4o=
github.com/rogpeppe/go-internal v1.13.1 h1:KvO1DLK/DRN07sQ1LQKScxyZJuNnedQ5/wKSR38lUII=
github.com/rogpeppe/go-internal v1.13.1/go.mod h1:uMEvuHeurkdAXX61udpOXGD/AzZDWNMNyH2VO9fmH0o=
github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk=
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
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.2 h1:xuMeJ0Sdp5ZMRXx/aWO6RZxdr3beISkG5/G/aIRr3pY=
github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA=
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.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA=
github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
github.com/test-go/testify v1.1.4 h1:Tf9lntrKUMHiXQ07qBScBTSA0dhYQlu83hswqelv1iE=
github.com/test-go/testify v1.1.4/go.mod h1:rH7cfJo/47vWGdi4GPj16x3/t1xGOj2YxzmNQzk2ghU=
github.com/tv42/httpunix v0.0.0-20150427012821-b75d8614f926/go.mod h1:9ESjWnEqriFuLhtthL60Sar/7RFoluCcXsuvEwTV5KM=
github.com/urfave/cli/v2 v2.27.6 h1:VdRdS98FNhKZ8/Az8B7MTyGQmpIr36O1EHybx/LaZ4g=
github.com/urfave/cli/v2 v2.27.6/go.mod h1:3Sevf16NykTbInEnD0yKkjDAeZDS0A6bzhBH5hrMvTQ=
github.com/xrash/smetrics v0.0.0-20240521201337-686a1a2994c1 h1:gEOO8jv9F4OT7lGCjxCBTO/36wtF6j2nSip77qHd4x4=
github.com/xrash/smetrics v0.0.0-20240521201337-686a1a2994c1/go.mod h1:Ohn+xnUBiLI6FVj/9LpzZWtj1/D6lUovWYBkxHVV3aM=
github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k=
go.etcd.io/bbolt v1.4.0 h1:TU77id3TnN/zKr7CO/uk+fBCwF2jGcMuw2B/FMAzYIk=
go.etcd.io/bbolt v1.4.0/go.mod h1:AsD+OCi/qPN1giOX1aiLAha3o1U8rAz65bvN4j0sRuk=
go.etcd.io/etcd/api/v3 v3.5.21 h1:A6O2/JDb3tvHhiIz3xf9nJ7REHvtEFJJ3veW3FbCnS8=
go.etcd.io/etcd/api/v3 v3.5.21/go.mod h1:c3aH5wcvXv/9dqIw2Y810LDXJfhSYdHQ0vxmP3CCHVY=
go.etcd.io/etcd/client/pkg/v3 v3.5.21 h1:lPBu71Y7osQmzlflM9OfeIV2JlmpBjqBNlLtcoBqUTc=
go.etcd.io/etcd/client/pkg/v3 v3.5.21/go.mod h1:BgqT/IXPjK9NkeSDjbzwsHySX3yIle2+ndz28nVsjUs=
go.etcd.io/etcd/client/v3 v3.5.21 h1:T6b1Ow6fNjOLOtM0xSoKNQt1ASPCLWrF9XMHcH9pEyY=
go.etcd.io/etcd/client/v3 v3.5.21/go.mod h1:mFYy67IOqmbRf/kRUvsHixzo3iG+1OF2W2+jVIQRAnU=
go.opentelemetry.io/auto/sdk v1.1.0 h1:cH53jehLUN6UFLY71z+NDOiNJqDdPRaXzTel0sJySYA=
go.opentelemetry.io/auto/sdk v1.1.0/go.mod h1:3wSPjt5PWp2RhlCcmmOial7AvC4DQqZb7a7wCow3W8A=
go.opentelemetry.io/otel v1.35.0 h1:xKWKPxrxB6OtMCbmMY021CqC45J+3Onta9MqjhnusiQ=
go.opentelemetry.io/otel v1.35.0/go.mod h1:UEqy8Zp11hpkUrL73gSlELM0DupHoiq72dR+Zqel/+Y=
go.opentelemetry.io/otel/metric v1.35.0 h1:0znxYu2SNyuMSQT4Y9WDWej0VpcsxkuklLa4/siN90M=
go.opentelemetry.io/otel/metric v1.35.0/go.mod h1:nKVFgxBZ2fReX6IlyW28MgZojkoAkJGaE8CpgeAU3oE=
go.opentelemetry.io/otel/sdk v1.35.0 h1:iPctf8iprVySXSKJffSS79eOjl9pvxV9ZqOWT0QejKY=
go.opentelemetry.io/otel/sdk v1.35.0/go.mod h1:+ga1bZliga3DxJ3CQGg3updiaAJoNECOgJREo9KHGQg=
go.opentelemetry.io/otel/sdk/metric v1.34.0 h1:5CeK9ujjbFVL5c1PhLuStg1wxA7vQv7ce1EK0Gyvahk=
go.opentelemetry.io/otel/sdk/metric v1.34.0/go.mod h1:jQ/r8Ze28zRKoNRdkjCZxfs6YvBTG1+YIqyFVFYec5w=
go.opentelemetry.io/otel/trace v1.35.0 h1:dPpEfJu1sDIqruz7BHFG3c7528f6ddfSWfFDVt/xgMs=
go.opentelemetry.io/otel/trace v1.35.0/go.mod h1:WUk7DtFp1Aw2MkvqGdwiXYDZZNvA/1J8o6xRXLrIkyc=
go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto=
go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE=
go.uber.org/multierr v1.10.0 h1:S0h4aNzvfcFsC3dRF1jLoaov7oRaKqRGC/pUEJ2yvPQ=
go.uber.org/multierr v1.10.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y=
go.uber.org/zap v1.27.0 h1:aJMhYGrd5QSmlpLMr2MftRKl7t8J8PTZPA732ud/XR8=
go.uber.org/zap v1.27.0/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E=
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/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.37.0 h1:kJNSjF/Xp7kU0iB2Z+9viTPMW4EqqsrywMXLJOOsXSE=
golang.org/x/crypto v0.37.0/go.mod h1:vg+k43peMZ0pUMhYmVAWysMK35e6ioLh3wB8ZCAfbVc=
golang.org/x/exp v0.0.0-20250305212735-054e65f0b394 h1:nDVHiLt8aIbd/VzvPWN6kSOPE7+F/fNFDSXLVYkE/Iw=
golang.org/x/exp v0.0.0-20250305212735-054e65f0b394/go.mod h1:sIifuuw/Yco/y6yb6+bDNfyeQ/MdPUy/hKEMYQV17cM=
golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.24.0 h1:ZfthKaKaT4NrhGVZHO1/WDTwGES4De8KtWO0SIbNJMU=
golang.org/x/mod v0.24.0/go.mod h1:IXM97Txy2VM4PJ3gI61r1YEk/gAj6zAHN3AdZt6S9Ww=
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-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM=
golang.org/x/net v0.0.0-20210410081132-afb366fc7cd1/go.mod h1:9tjilg8BloeKEkVJvy7fQ90B1CfIiPueXVOjqfkSzI8=
golang.org/x/net v0.0.0-20210726213435-c6fcb2dbf985/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/net v0.38.0 h1:vRMAPTMaeGqVhG5QyLJHqNDwecKTomGeqbnfZyKlBI8=
golang.org/x/net v0.38.0/go.mod h1:ivrbrMbzFq5J41QOQh0siUuly180yBYtLp+CKbEaFx8=
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-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.13.0 h1:AauUjRAJ9OSnvULf/ARrrVywoJDy0YS2AwQ98I37610=
golang.org/x/sync v0.13.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA=
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-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
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-20200930185726-fdedc70b468f/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-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
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.0.0-20220908164124-27713097b956/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.32.0 h1:s77OFDvIQeibCmezSnk/q6iAfkdiQaJi4VzroCFrN20=
golang.org/x/sys v0.32.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
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.24.0 h1:dd5Bzh4yt5KYA8f9CJHCP4FB4D51c2c6JvN37xJJkJ0=
golang.org/x/text v0.24.0/go.mod h1:L8rBsPeo2pSS+xqN0d5u2ikmjtmoJbDBT1b7nHvFCdU=
golang.org/x/time v0.11.0 h1:/bpjEDfN9tkoN/ryeYHnv5hcMlc8ncjMcM4XBk5NWV0=
golang.org/x/time v0.11.0/go.mod h1:CDIdPxbZBQxdj6cxyCIdrNogrJKMJ7pr37NYpMcMDSg=
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/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
golang.org/x/tools v0.1.6-0.20210726203631-07bc1bf47fb2/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
golang.org/x/tools v0.31.0 h1:0EedkvKDbh+qistFTd0Bcwe/YLh4vHwWEkiI0toFIBU=
golang.org/x/tools v0.31.0/go.mod h1:naFTU+Cev749tSJRXJlna0T3WxKvb1kWEx15xA4SdmQ=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
google.golang.org/genproto/googleapis/api v0.0.0-20250324211829-b45e905df463 h1:hE3bRWtU6uceqlh4fhrSnUyjKHMKB9KrTLLG+bc0ddM=
google.golang.org/genproto/googleapis/api v0.0.0-20250324211829-b45e905df463/go.mod h1:U90ffi8eUL9MwPcrJylN5+Mk2v3vuPDptd5yyNUiRR8=
google.golang.org/genproto/googleapis/rpc v0.0.0-20250324211829-b45e905df463 h1:e0AIkUUhxyBKh6ssZNrAMeqhA7RKUj42346d1y02i2g=
google.golang.org/genproto/googleapis/rpc v0.0.0-20250324211829-b45e905df463/go.mod h1:qQ0YXyHHx3XkvlzUtpXDkS29lDSafHMZBAZDc03LQ3A=
google.golang.org/grpc v1.71.1 h1:ffsFWr7ygTUscGPI0KKK6TLrGz0476KUvvsbqWK0rPI=
google.golang.org/grpc v1.71.1/go.mod h1:H0GRtasmQOh9LkFoCPDu3ZrwUtD1YGE+b2vYBYd/8Ec=
google.golang.org/protobuf v1.36.6 h1:z1NpPI8ku2WgiWnf+t9wTPsn6eP1L7ksHUlkfLvd9xY=
google.golang.org/protobuf v1.36.6/go.mod h1:jduwjTPXsFjZGTmRluh+L6NjiWu7pchiJ2/5YcXBHnY=
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/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ=
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw=
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.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
+102
View File
@@ -0,0 +1,102 @@
package main
import (
"encoding/json"
"fmt"
"log"
"net/http"
"time"
"go-micro.dev/v6/web"
)
type User struct {
ID string `json:"id"`
Name string `json:"name"`
Email string `json:"email"`
CreatedAt time.Time `json:"created_at"`
}
var users = map[string]*User{
"1": {ID: "1", Name: "Alice", Email: "alice@example.com", CreatedAt: time.Now()},
"2": {ID: "2", Name: "Bob", Email: "bob@example.com", CreatedAt: time.Now()},
}
func main() {
// Create a new web service
service := web.NewService(
web.Name("web.service"),
web.Version("latest"),
web.Address(":9090"),
)
// Initialize
service.Init()
// Register handlers
service.HandleFunc("/", homeHandler)
service.HandleFunc("/users", usersHandler)
service.HandleFunc("/users/", userHandler)
service.HandleFunc("/health", healthHandler)
fmt.Println("Web service starting on :9090")
fmt.Println("Try:")
fmt.Println(" curl http://localhost:9090/")
fmt.Println(" curl http://localhost:9090/users")
fmt.Println(" curl http://localhost:9090/users/1")
fmt.Println(" curl http://localhost:9090/health")
// Run the service
if err := service.Run(); err != nil {
log.Fatal(err)
}
}
func homeHandler(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "application/json")
json.NewEncoder(w).Encode(map[string]string{
"service": "web.service",
"version": "latest",
"status": "running",
})
}
func usersHandler(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "application/json")
// Return all users
userList := make([]*User, 0, len(users))
for _, user := range users {
userList = append(userList, user)
}
json.NewEncoder(w).Encode(userList)
}
func userHandler(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "application/json")
// Extract user ID from path
id := r.URL.Path[len("/users/"):]
user, exists := users[id]
if !exists {
w.WriteHeader(http.StatusNotFound)
json.NewEncoder(w).Encode(map[string]string{
"error": "User not found",
})
return
}
json.NewEncoder(w).Encode(user)
}
func healthHandler(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "application/json")
w.WriteHeader(http.StatusOK)
json.NewEncoder(w).Encode(map[string]interface{}{
"status": "healthy",
"timestamp": time.Now().Unix(),
"uptime": "running",
})
}