snapshot of https://github.com/ThreeDotsLabs/watermill.git (master, 2026-09-15; blobs > 131072B omitted)
Some checks failed
master / ci (push) Failing after 0s

This commit is contained in:
upstream-mirror
2026-09-15 21:27:46 +05:00
commit ce933d1277
538 changed files with 46504 additions and 0 deletions

View File

@@ -0,0 +1,40 @@
# These are the current default settings for the Watermill CLI tool.
# Use them as a template for your own config
log: false
trace: false
debug: false
amqp:
uri: ""
durable: true
consume:
exchange: ""
queue: ""
produce:
exchange: ""
exchangetype: fanout
routingkey: ""
googlecloud:
projectid: ""
topic: ""
consume:
subscription: ""
produce:
subscription:
add:
ackdeadline: 10s
labels: ""
retainacked: false
retentionduration: 168h0m0s
rm:
# no flags for rm yet
kafka:
brokers: []
topic: ""
consume:
consumergroup: ""
frombeginning: false
produce:
# no flags for produce yet

2
tools/mill/Makefile Normal file
View File

@@ -0,0 +1,2 @@
mill:
go build -o mill main.go

100
tools/mill/README.md Normal file
View File

@@ -0,0 +1,100 @@
# mill - a simple CLI tool for Watermill
`mill` is a CLI tool for the [Watermill](https://watermill.io) library.
It has two basic functionalities, namely producing and consuming messages on the following
Pub/Subs:
1. Kafka
2. Google Cloud Pub/Sub
3. RabbitMQ
See Watermill's [Supported Pub/Subs](https://watermill.io/pubsubs) for more details on how this works.
## Installation
To install this tool, just execute:
```bash
go install github.com/ThreeDotsLabs/watermill/tools/mill@latest
```
This will install a `mill` binary in your system.
## Consume mode
In consume mode, the tool subscribes to a topic/queue/subscription (nomenclature depending on the particular Pub/Sub provider)
and prints the messages' payload to the standard output.
Other outputs, for example ones that add a timestamp or preserve UUIDs or metadata, are easily attainable by modification
of the marshaling function of the `io.Publisher` of the `consumeCmd`.
## Produce mode
In produce mode, subsequent lines of data from the standard input are transformed into messages outgoing to the requested
provider's topic/exchange.
The message's payload is set to the line from stdin, the UUID is auto-generated and the metadata is empty.
Similarly, the contents of the message could be parsed differently from stdin, by modification
of the unmarshaling function of the `io.Subscriber` of the `produceCmd`.
## Usage
The basic syntax of the tool is:
```bash
mill <provider> <command>
```
with the appropriate flags regulating the specific behaviour of each command.
`command` is usually one of `produce` or `consume`, but some providers may handle additional commands
that are specific for them.
The flags are context-specific, so the best way to find out about them is to use the `-h` flag and study
which flags are available/required for the specific context and act accordingly.
### Advanced usage
A neat feature of producers and consumers is that you can use the power of stdin/stdout piping for stuff like:
```bash
myservice | tee myservice.log | mill kafka produce -t myservice-logs --brokers kafka-host:8082
```
And on another host:
```bash
mill kafka consume -t myservice-logs --brokers kafka-host:8082 >> myservice.log
```
In the above example, the host on which `myservice` runs has its own copy of `myservice.log` and any host that consumes
from the kafka topic will replicate the log entries in their local copy.
## Additional functionalities
### Google Cloud Pub/Sub
#### Adding/Removing subscriptions
You can use `mill` to create/remove subscriptions for Google Cloud Pub/Sub:
```bash
mill googlecloud subscription add -t <topic> <subscription_id>
mill googlecloud subscription rm <subscription_id>
```
Additional flags are available for `subscription add` to regulate the newly created subscription's settings.
#### Listing subscriptions
You can use `mill` to list existing subscriptions:
```bash
mill googlecloud subscription ls [-t topic]
```
The topic is optional. If omitted, all topics will be listed with their subscriptions.

174
tools/mill/cmd/amqp.go Normal file
View File

@@ -0,0 +1,174 @@
package cmd
import (
"github.com/spf13/cobra"
"github.com/spf13/viper"
"github.com/ThreeDotsLabs/watermill-amqp/v3/pkg/amqp"
)
var amqpCmd = &cobra.Command{
Use: "amqp",
Short: "Commands for the AMQP Pub/Sub provider",
Long: `Consume or produce messages from the AMQP Pub/Sub provider.
For the configuration of consuming/producing of the messages, check the help of the relevant command.`,
PersistentPreRunE: func(cmd *cobra.Command, args []string) error {
err := rootCmd.PersistentPreRunE(cmd, args)
if err != nil {
return err
}
logger.Debug("Using AMQP Pub/Sub", nil)
if cmd.Use == "consume" {
consumer, err = amqp.NewSubscriber(amqpConsumerConfig(), logger)
if err != nil {
return err
}
}
if cmd.Use == "produce" {
producer, err = amqp.NewPublisher(amqpProducerConfig(), logger)
if err != nil {
return err
}
}
return nil
},
}
func amqpConsumerConfig() amqp.Config {
uri := viper.GetString("amqp.uri")
queue := viper.GetString("amqp.consume.queue")
exchangeName := viper.GetString("amqp.consume.exchange")
exchangeType := viper.GetString("amqp.produce.exchangeType")
durable := viper.GetBool("amqp.durable")
return amqp.Config{
Connection: amqp.ConnectionConfig{
AmqpURI: uri,
},
Marshaler: amqp.DefaultMarshaler{},
Queue: amqp.QueueConfig{
GenerateName: func(topic string) string {
return queue
},
Durable: durable,
},
Consume: amqp.ConsumeConfig{
Qos: amqp.QosConfig{
PrefetchCount: 1,
},
},
Exchange: amqp.ExchangeConfig{
GenerateName: func(topic string) string {
return exchangeName
},
Type: exchangeType,
Durable: durable,
},
}
}
func amqpProducerConfig() amqp.Config {
uri := viper.GetString("amqp.uri")
exchangeName := viper.GetString("amqp.produce.exchange")
exchangeType := viper.GetString("amqp.produce.exchangeType")
routingKey := viper.GetString("amqp.produce.routingKey")
durable := viper.GetBool("amqp.durable")
return amqp.Config{
Connection: amqp.ConnectionConfig{
AmqpURI: uri,
},
Marshaler: amqp.DefaultMarshaler{},
Exchange: amqp.ExchangeConfig{
GenerateName: func(topic string) string {
return exchangeName
},
Type: exchangeType,
Durable: durable,
},
Publish: amqp.PublishConfig{
GenerateRoutingKey: func(topic string) string {
return routingKey
},
},
}
}
func init() {
rootCmd.AddCommand(amqpCmd)
configureAmqpCmd()
consumeCmd := addConsumeCmd(amqpCmd, "amqp.consume.queue")
configureConsumeCmd(consumeCmd)
produceCmd := addProduceCmd(amqpCmd, "amqp.produce.exchange")
configureProduceCmd(produceCmd)
}
func configureAmqpCmd() {
amqpCmd.PersistentFlags().StringP(
"uri",
"u",
"",
"The URI to the AMQP instance (required)",
)
ensure(amqpCmd.MarkPersistentFlagRequired("uri"))
ensure(viper.BindPFlag("amqp.uri", amqpCmd.PersistentFlags().Lookup("uri")))
amqpCmd.PersistentFlags().Bool(
"durable",
true,
"If true, the queues and exchanges created automatically (if any) will be durable",
)
ensure(viper.BindPFlag("amqp.durable", amqpCmd.PersistentFlags().Lookup("durable")))
amqpCmd.PersistentFlags().String(
"exchange-type",
"fanout",
"If exchange needs to be created, it will be created with this type. The common types are 'direct', 'fanout', 'topic' and 'headers'.",
)
ensure(viper.BindPFlag("amqp.produce.exchangeType", amqpCmd.PersistentFlags().Lookup("exchange-type")))
}
func configureConsumeCmd(consumeCmd *cobra.Command) {
consumeCmd.PersistentFlags().StringP(
"queue",
"q",
"",
"The name of the AMQP queue to consume messages from (required)",
)
ensure(consumeCmd.MarkPersistentFlagRequired("queue"))
ensure(viper.BindPFlag("amqp.consume.queue", consumeCmd.PersistentFlags().Lookup("queue")))
consumeCmd.PersistentFlags().StringP(
"exchange",
"x",
"",
"If non-empty, an exchange with this name is created if it didn't exist. Then, the queue is bound to this exchange.",
)
ensure(viper.BindPFlag("amqp.consume.exchange", consumeCmd.PersistentFlags().Lookup("exchange")))
}
func configureProduceCmd(produceCmd *cobra.Command) {
produceCmd.PersistentFlags().StringP(
"exchange",
"x",
"",
"The name of the AMQP exchange to produce messages to (required)",
)
ensure(produceCmd.MarkPersistentFlagRequired("exchange"))
ensure(viper.BindPFlag("amqp.produce.exchange", produceCmd.PersistentFlags().Lookup("exchange")))
produceCmd.PersistentFlags().StringP(
"routing-key",
"r",
"",
"The routing key to use when publishing the message.",
)
ensure(produceCmd.MarkPersistentFlagRequired("routing-key"))
ensure(viper.BindPFlag("amqp.produce.routingKey", produceCmd.PersistentFlags().Lookup("routing-key")))
}

70
tools/mill/cmd/consume.go Normal file
View File

@@ -0,0 +1,70 @@
package cmd
import (
"context"
"os"
"time"
"github.com/pkg/errors"
"github.com/spf13/cobra"
"github.com/spf13/viper"
"github.com/ThreeDotsLabs/watermill-io/pkg/io"
"github.com/ThreeDotsLabs/watermill/message"
"github.com/ThreeDotsLabs/watermill/message/router/plugin"
)
// consumer is initialized by the parent command to the pub/sub provider of choice.
var consumer message.Subscriber
func addConsumeCmd(parent *cobra.Command, topicKey string) *cobra.Command {
cmd := &cobra.Command{
Use: "consume",
Short: "Consume messages from a pub/sub and print them to stdout",
Long: `Consume messages from the pub/sub of your choice and print them on the standard output.
For the configuration of particular pub/sub providers, see the help for the provider commands.`,
RunE: func(cmd *cobra.Command, args []string) error {
topic := viper.GetString(topicKey)
router, err := message.NewRouter(
message.RouterConfig{
CloseTimeout: 5 * time.Second,
},
logger,
)
if err != nil {
return errors.Wrap(err, "could not create router")
}
router.AddPlugin(plugin.SignalsHandler)
out, err := io.NewPublisher(
os.Stdout,
io.PublisherConfig{
MarshalFunc: io.PayloadMarshalFunc,
},
logger,
)
if err != nil {
return errors.Wrap(err, "could not create console producer")
}
router.AddHandler(
"dump_to_stdout",
topic,
consumer,
"",
out,
func(msg *message.Message) ([]*message.Message, error) {
// just forward the message to stdout
return message.Messages{msg}, nil
},
)
return router.Run(context.Background())
},
}
parent.AddCommand(cmd)
return cmd
}

View File

@@ -0,0 +1,487 @@
package cmd
import (
"context"
"fmt"
"os"
"strings"
"time"
"cloud.google.com/go/pubsub"
"github.com/ThreeDotsLabs/watermill"
"github.com/ThreeDotsLabs/watermill-googlecloud/pkg/googlecloud"
"github.com/ThreeDotsLabs/watermill/tools/mill/cmd/internal"
"github.com/pkg/errors"
"github.com/spf13/cobra"
"github.com/spf13/viper"
"google.golang.org/api/iterator"
"gopkg.in/yaml.v2"
)
var googleCloudTempSubscriptionID string
var googleCloudCmd = &cobra.Command{
Use: "googlecloud",
Short: "Commands for the Google Cloud Pub/Sub provider",
Long: `Consume or produce messages from the Google Cloud Pub/Sub provider. Manage subscriptions.
For the configuration of consuming/producing of the messages, check the help of the relevant command.`,
PersistentPreRunE: func(cmd *cobra.Command, args []string) error {
err := rootCmd.PersistentPreRunE(cmd, args)
if err != nil {
return err
}
logger.Debug("Using Google Cloud Pub/Sub", nil)
if cmd.Use == "consume" {
subName := viper.GetString("googlecloud.consume.subscription")
if subName == "" {
subName, err = generateTempSubscription()
if err != nil {
return err
}
}
consumer, err = googlecloud.NewSubscriber(
googlecloud.SubscriberConfig{
GenerateSubscriptionName: func(topic string) string {
return subName
},
ProjectID: projectID(),
},
logger,
)
if err != nil {
return err
}
}
if cmd.Use == "produce" {
producer, err = googlecloud.NewPublisher(
googlecloud.PublisherConfig{
ProjectID: projectID(),
},
logger,
)
if err != nil {
return err
}
}
return nil
},
PersistentPostRunE: func(cmd *cobra.Command, args []string) error {
logger.Debug("Google Cloud Pub/Sub cleanup", nil)
if googleCloudTempSubscriptionID != "" {
if err := removeTempSubscription(); err != nil {
return err
}
}
return nil
},
}
var googleCloudSubscriptionCmd = &cobra.Command{
Use: "subscription",
Short: "Manage Google Cloud Pub/Sub subscriptions",
Long: `Add or remove subscriptions for the Google Cloud Pub/Sub provider.`,
}
var googleCloudSubscriptionAddCmd = &cobra.Command{
Use: "add <subscription_id>",
Short: "Add a new subscription in Google Cloud Pub/Sub",
Args: cobra.ExactArgs(1),
ValidArgs: []string{"subscriptionID"},
RunE: func(cmd *cobra.Command, args []string) (err error) {
subID := args[0]
topic := viper.GetString("googlecloud.subscription.add.topic")
ackDeadline := viper.GetDuration("googlecloud.subscription.add.ackDeadline")
retainAcked := viper.GetBool("googlecloud.subscription.add.retainAcked")
retentionDuration := viper.GetDuration("googlecloud.subscription.add.retentionDuration")
// StringToString doesn't work with viper, so let's parse this manually
labels := strings.Split(viper.GetString("googlecloud.subscription.add.labels"), ",")
labelsMap := make(map[string]string, len(labels))
for _, l := range labels {
fields := strings.Split(l, "=")
if len(fields) < 2 {
continue
}
labelsMap[fields[0]] = fields[1]
}
logger := logger.With(watermill.LogFields{
"subscription_id": subID,
"topic": topic,
"ackDeadline": ackDeadline,
"retainAcked": retainAcked,
"retentionDuration": retentionDuration,
"labels": labelsMap,
})
logger.Info("Creating new subscription", nil)
defer func() {
if err == nil {
logger.Info("Subscription created", nil)
}
}()
return addSubscription(
subID,
topic,
ackDeadline,
retainAcked,
retentionDuration,
labelsMap,
)
},
}
var googleCloudSubscriptionRmCmd = &cobra.Command{
Use: "rm <subscription_id>",
Short: "Remove a subscription in Google Cloud Pub/Sub",
Args: cobra.ExactArgs(1),
ValidArgs: []string{"subscriptionID"},
RunE: func(cmd *cobra.Command, args []string) (err error) {
subID := args[0]
logger := logger.With(watermill.LogFields{
"subscription_id": subID,
})
logger.Info("Removing a subscription", nil)
defer func() {
if err == nil {
logger.Info("Subscription removed", nil)
}
}()
return removeSubscription(subID)
},
}
var googleCloudSubscriptionLsCmd = &cobra.Command{
Use: "ls",
Short: "List subscriptions in Google Cloud Pub/Sub. Topic may be provided optionally to filter subscriptions by topic.",
Args: cobra.NoArgs,
RunE: func(cmd *cobra.Command, args []string) (err error) {
topic := viper.GetString("googlecloud.subscription.ls.topic")
verbose := viper.GetBool("googlecloud.subscription.ls.verbose")
logger := logger
if topic != "" {
logger = logger.With(watermill.LogFields{
"topic": topic,
})
}
logger.Info("Listing all subscriptions", nil)
return listSubscriptions(topic, logger, verbose)
},
}
func generateTempSubscription() (id string, err error) {
defer func() {
if err == nil {
logger.Debug("Temp subscription created", watermill.LogFields{
"subscription_name": id,
})
googleCloudTempSubscriptionID = id
}
}()
randomID := "watermill_console_consumer_" + watermill.NewShortUUID()
return randomID, addSubscription(
randomID,
viper.GetString("googlecloud.topic"),
10*time.Second,
false,
10*time.Minute,
nil,
)
}
func addSubscription(
id string,
topic string,
ackDeadline time.Duration,
retainAckedMessages bool,
retentionDuration time.Duration,
labels map[string]string,
) error {
ctx, cancel := context.WithTimeout(context.Background(), time.Second*10)
defer cancel()
client, err := pubsub.NewClient(ctx, projectID())
if err != nil {
return errors.Wrap(err, "could not create pubsub client")
}
t := client.Topic(topic)
exists, err := t.Exists(ctx)
if err != nil {
return errors.Wrap(err, "could not check if topic exists")
}
if !exists {
t, err = client.CreateTopic(ctx, t.ID())
if err != nil {
return errors.Wrap(err, "could not create topic")
}
}
_, err = client.CreateSubscription(ctx, id, pubsub.SubscriptionConfig{
Topic: t,
AckDeadline: ackDeadline,
RetainAckedMessages: retainAckedMessages,
RetentionDuration: retentionDuration,
Labels: labels,
})
if err != nil {
return errors.Wrap(err, "could not create subscription")
}
return nil
}
func removeTempSubscription() (err error) {
defer func() {
if err == nil {
logger.Debug("Temporary subscription removed", watermill.LogFields{
"subscription_name": googleCloudTempSubscriptionID,
})
}
}()
return removeSubscription(googleCloudTempSubscriptionID)
}
func removeSubscription(id string) error {
ctx, cancel := context.WithTimeout(context.Background(), time.Second*10)
defer cancel()
client, err := pubsub.NewClient(ctx, projectID())
if err != nil {
return errors.Wrap(err, "could not create pubsub client")
}
sub := client.Subscription(id)
exists, err := sub.Exists(ctx)
if err != nil {
return errors.Wrap(err, "could not check if sub exists")
}
if !exists {
return nil
}
return sub.Delete(ctx)
}
func listSubscriptions(topic string, adapter watermill.LoggerAdapter, verbose bool) error {
ctx, cancel := context.WithTimeout(context.Background(), time.Second*10)
defer cancel()
client, err := pubsub.NewClient(ctx, projectID())
if err != nil {
return errors.Wrap(err, "could not create pubsub client")
}
if topic != "" {
topic := client.Topic(topic)
return listSubscriptionsForTopic(ctx, client, topic, logger, verbose)
}
it := client.Topics(ctx)
noTopics := true
for {
topic, err := it.Next()
if err == iterator.Done {
if noTopics {
logger.Info("No topics in project", nil)
}
return nil
}
if err != nil {
return errors.Wrap(err, "could not retrieve next subscription")
}
noTopics = false
err = listSubscriptionsForTopic(ctx, client, topic, logger, verbose)
if err != nil {
return errors.Wrap(err, "error listing subscriptions for topic")
}
}
return nil
}
func listSubscriptionsForTopic(ctx context.Context, client *pubsub.Client, topic *pubsub.Topic, logger watermill.LoggerAdapter, verbose bool) error {
noSubs := true
exists, err := topic.Exists(ctx)
if err != nil {
return errors.Wrap(err, "could not check if topic exists")
}
if !exists {
logger.Info("Topic does not exist", watermill.LogFields{"topic": topic.String()})
return nil
}
it := topic.Subscriptions(ctx)
for {
sub, err := it.Next()
if err == iterator.Done {
if noSubs {
logger.Info("No subscriptions for the topic", watermill.LogFields{"topic": topic.String()})
}
return nil
}
if err != nil {
return errors.Wrap(err, "could not retrieve next subscription")
}
if noSubs {
noSubs = false
fmt.Printf("Topic %s:\n", topic.String())
}
name := sub.String()
config, err := sub.Config(ctx)
if err != nil {
return errors.Wrapf(err, "could not retrieve subscription config for subscription '%s'", name)
}
err = printSubscriptionInfo(name, config)
if err != nil {
return errors.Wrapf(err, "error printing subscription '%s'", name)
}
}
}
func printSubscriptionInfo(name string, config pubsub.SubscriptionConfig) error {
b, err := yaml.Marshal(subscriptionConfig{
Name: name,
PushConfig: subscriptionConfigPushConfig{
Endpoint: config.PushConfig.Endpoint,
Attributes: config.PushConfig.Attributes,
},
AckDeadline: config.AckDeadline,
RetainAckedMessages: config.RetainAckedMessages,
RetentionDuration: config.RetentionDuration,
Labels: config.Labels,
})
if err != nil {
return err
}
fmt.Printf(internal.Indent(string(b), " "))
return nil
}
// subscriptionConfig provides a marshallable form to pubsub.SubscriptionConfig
type subscriptionConfig struct {
Name string
PushConfig subscriptionConfigPushConfig `yaml:"push_config"`
AckDeadline time.Duration `yaml:"ack_deadline"`
RetainAckedMessages bool `yaml:"retain_acked_messages"`
RetentionDuration time.Duration `yaml:"retention_duration"`
Labels map[string]string `yaml:"labels"`
}
type subscriptionConfigPushConfig struct {
Endpoint string
Attributes map[string]string
}
func projectID() string {
projectID := viper.GetString("googlecloud.projectID")
if projectID == "" {
projectID = os.Getenv("GOOGLE_CLOUD_PROJECT")
}
return projectID
}
func init() {
rootCmd.AddCommand(googleCloudCmd)
googleCloudCmd.PersistentFlags().StringP(
"topic",
"t",
"",
"The topic to produce messages to (produce), consume message from (consume), list from (ls) or the topic for the newly created subscription (subscription.add)",
)
ensure(viper.BindPFlag("googlecloud.topic", googleCloudCmd.PersistentFlags().Lookup("topic")))
ensure(googleCloudCmd.MarkPersistentFlagRequired("topic"))
consumeCmd := addConsumeCmd(googleCloudCmd, "googlecloud.topic")
addProduceCmd(googleCloudCmd, "googlecloud.topic")
// Cobra supports Persistent Flags which will work for this command
// and all subcommands, e.g.:
googleCloudCmd.PersistentFlags().String("project", "", "The projectID for Google Cloud Pub/Sub. Defaults to the GOOGLE_CLOUD_PROJECT environment variable.")
ensure(viper.BindPFlag("googlecloud.projectID", googleCloudCmd.PersistentFlags().Lookup("project")))
consumeCmd.PersistentFlags().StringP(
"subscription",
"s",
"",
"The subscription for Google Cloud Pub/Sub. If left empty, a temporary subscription is created and removed when the consumer is closed",
)
ensure(viper.BindPFlag("googlecloud.consume.subscription", consumeCmd.PersistentFlags().Lookup("subscription")))
googleCloudCmd.AddCommand(googleCloudSubscriptionCmd)
googleCloudSubscriptionCmd.AddCommand(googleCloudSubscriptionAddCmd)
googleCloudSubscriptionAddCmd.Flags().StringP("topic", "t", "", "The topic for the new subscription (required)")
ensure(googleCloudSubscriptionAddCmd.MarkFlagRequired("topic"))
ensure(viper.BindPFlag("googlecloud.subscription.add.topic", googleCloudSubscriptionAddCmd.Flags().Lookup("topic")))
googleCloudSubscriptionAddCmd.Flags().DurationP(
"ack-deadline",
"a",
10*time.Second,
"How long Pub/Sub waits for the subscriber to acknowledge receipt before resending the message. Deadline time is from 10 seconds to 600 seconds",
)
ensure(viper.BindPFlag("googlecloud.subscription.add.ackDeadline", googleCloudSubscriptionAddCmd.Flags().Lookup("ack-deadline")))
googleCloudSubscriptionAddCmd.Flags().Bool(
"retain-acked",
false,
"Acknowledged messages will be kept 7 days from publication unless set otherwise in \"message retention duration\".",
)
ensure(viper.BindPFlag("googlecloud.subscription.add.retainAcked", googleCloudSubscriptionAddCmd.Flags().Lookup("retain-acked")))
googleCloudSubscriptionAddCmd.Flags().Duration(
"retention-duration",
7*24*time.Hour,
"How long the retained messages will be kept. The allowed duration is from 10 minutes to 7 days, which is the default.",
)
ensure(viper.BindPFlag("googlecloud.subscription.add.retentionDuration", googleCloudSubscriptionAddCmd.Flags().Lookup("retention-duration")))
// StringToString doesn't work correctly with viper
googleCloudSubscriptionAddCmd.Flags().String(
"labels",
"",
"The set of labels for the subscription. Format: '--labels key1=value1,key2=value2,...'",
)
ensure(viper.BindPFlag("googlecloud.subscription.add.labels", googleCloudSubscriptionAddCmd.Flags().Lookup("labels")))
googleCloudSubscriptionCmd.AddCommand(googleCloudSubscriptionRmCmd)
googleCloudSubscriptionCmd.AddCommand(googleCloudSubscriptionLsCmd)
googleCloudSubscriptionLsCmd.Flags().StringP(
"topic",
"t",
"",
"The topic for the new subscription (optional, will list subscriptions for all topics if omitted)",
)
ensure(viper.BindPFlag("googlecloud.subscription.ls.topic", googleCloudSubscriptionLsCmd.Flags().Lookup("topic")))
googleCloudSubscriptionLsCmd.Flags().BoolP(
"verbose",
"v",
false,
"will print more information, including the subscription config",
)
ensure(viper.BindPFlag("googlecloud.subscription.ls.verbose", googleCloudSubscriptionLsCmd.Flags().Lookup("verbose")))
}

View File

@@ -0,0 +1,19 @@
package internal
import "strings"
// Indent indents all lines in the given string with a given prefix.
func Indent(s, prefix string) string {
endsWithNewline := strings.HasSuffix(s, "\n")
split := strings.Split(s, "\n")
for i, ss := range split {
split[i] = prefix + ss
}
joined := strings.Join(split, "\n")
if endsWithNewline {
joined += "\n"
}
return joined
}

92
tools/mill/cmd/kafka.go Normal file
View File

@@ -0,0 +1,92 @@
package cmd
import (
"github.com/IBM/sarama"
"github.com/spf13/cobra"
"github.com/spf13/viper"
"github.com/ThreeDotsLabs/watermill-kafka/v3/pkg/kafka"
)
var kafkaCmd = &cobra.Command{
Use: "kafka",
Short: "Commands for the kafka Pub/Sub provider",
Long: `Consume or produce messages from the kafka Pub/Sub provider.
For the configuration of consuming/producing of the messages, check the help of the relevant command.`,
PersistentPreRunE: func(cmd *cobra.Command, args []string) error {
err := rootCmd.PersistentPreRunE(cmd, args)
if err != nil {
return err
}
logger.Debug("Using kafka pub/sub", nil)
brokers := viper.GetStringSlice("kafka.brokers")
if cmd.Use == "consume" {
saramaSubscriberConfig := kafka.DefaultSaramaSubscriberConfig()
if viper.GetBool("kafka.consume.fromBeginning") {
logger.Trace("Configured sarama to consume messages from beginning", nil)
// equivalent of auto.offset.reset: earliest
saramaSubscriberConfig.Consumer.Offsets.Initial = sarama.OffsetOldest
}
consumer, err = kafka.NewSubscriber(
kafka.SubscriberConfig{
Brokers: brokers,
Unmarshaler: kafka.DefaultMarshaler{},
OverwriteSaramaConfig: saramaSubscriberConfig,
ConsumerGroup: viper.GetString("kafka.consume.consumerGroup"),
},
logger,
)
if err != nil {
return err
}
}
if cmd.Use == "produce" {
producer, err = kafka.NewPublisher(
kafka.PublisherConfig{
Brokers: brokers,
Marshaler: kafka.DefaultMarshaler{},
},
logger,
)
if err != nil {
return err
}
}
return nil
},
}
func init() {
rootCmd.AddCommand(kafkaCmd)
kafkaCmd.PersistentFlags().StringP(
"topic",
"t",
"",
"The topic to produce messages to (produce) or consume message from (consume)",
)
ensure(kafkaCmd.MarkPersistentFlagRequired("topic"))
ensure(viper.BindPFlag("kafka.topic", kafkaCmd.PersistentFlags().Lookup("topic")))
consumeCmd := addConsumeCmd(kafkaCmd, "kafka.topic")
_ = addProduceCmd(kafkaCmd, "kafka.topic")
// Cobra supports Persistent Flags which will work for this command
// and all subcommands, e.g.:
kafkaCmd.PersistentFlags().StringSliceP("brokers", "b", nil, "A list of kafka brokers")
ensure(kafkaCmd.MarkPersistentFlagRequired("brokers"))
ensure(viper.BindPFlag("kafka.brokers", kafkaCmd.PersistentFlags().Lookup("brokers")))
consumeCmd.PersistentFlags().Bool("from-beginning", false, "Equivalent to auto.offset.reset: earliest")
ensure(viper.BindPFlag("kafka.consume.fromBeginning", consumeCmd.PersistentFlags().Lookup("from-beginning")))
consumeCmd.PersistentFlags().StringP("consumer-group", "c", "", "The kafka consumer group. Defaults to empty.")
ensure(viper.BindPFlag("kafka.consume.consumerGroup", consumeCmd.PersistentFlags().Lookup("consumer-group")))
}

78
tools/mill/cmd/produce.go Normal file
View File

@@ -0,0 +1,78 @@
package cmd
import (
"context"
"os"
"time"
"github.com/pkg/errors"
"github.com/spf13/cobra"
"github.com/spf13/viper"
"github.com/ThreeDotsLabs/watermill-io/pkg/io"
"github.com/ThreeDotsLabs/watermill/message"
"github.com/ThreeDotsLabs/watermill/message/router/plugin"
)
// producer is initialized by parent command to the pub/sub provider of choice.
var producer message.Publisher
func addProduceCmd(parent *cobra.Command, topicKey string) *cobra.Command {
cmd := &cobra.Command{
Use: "produce",
Short: "Produce messages to a pub/sub from the stdin",
Long: `Produce messages to the pub/sub of your choice from the standard input.
For the configuration of particular pub/sub providers, see the help for the provider commands.`,
PreRunE: func(cmd *cobra.Command, args []string) error {
return nil
},
RunE: func(cmd *cobra.Command, args []string) error {
topic := viper.GetString(topicKey)
router, err := message.NewRouter(
message.RouterConfig{
CloseTimeout: 10 * time.Second,
},
logger,
)
if err != nil {
return errors.Wrap(err, "could not create router")
}
router.AddPlugin(plugin.SignalsHandler)
in, err := io.NewSubscriber(
os.Stdin,
io.SubscriberConfig{
PollInterval: time.Second,
UnmarshalFunc: io.PayloadUnmarshalFunc,
},
logger,
)
if err != nil {
return errors.Wrap(err, "could not create console subscriber")
}
router.AddHandler(
"produce_from_stdin",
"stdin",
in,
topic,
producer,
func(msg *message.Message) ([]*message.Message, error) {
if string(msg.Payload) == "\n" {
logger.Trace("Message is empty, don't publish", nil)
return nil, nil
}
// just pass the message along
return message.Messages{msg}, nil
},
)
return router.Run(context.Background())
},
}
parent.AddCommand(cmd)
return cmd
}

149
tools/mill/cmd/root.go Normal file
View File

@@ -0,0 +1,149 @@
package cmd
import (
"fmt"
"os"
homedir "github.com/mitchellh/go-homedir"
"github.com/pkg/errors"
"github.com/spf13/cobra"
"github.com/spf13/pflag"
"github.com/spf13/viper"
yaml "gopkg.in/yaml.v2"
"github.com/ThreeDotsLabs/watermill"
)
var cfgFile string
var logger watermill.LoggerAdapter
var rootCmd = &cobra.Command{
Use: "mill",
Short: "A CLI for Watermill.",
Long: `A CLI for Watermill.
Use console-based producer or consumer for various pub/sub providers.`,
PersistentPreRunE: func(cmd *cobra.Command, args []string) error {
log := viper.GetBool("log")
debug := viper.GetBool("debug")
trace := viper.GetBool("trace")
if log || debug || trace {
logger = watermill.NewStdLogger(debug, trace)
} else {
logger = watermill.NopLogger{}
}
if err := checkRequiredFlags(cmd.Flags()); err != nil {
return err
}
writeConfig := viper.GetString("writeConfig")
if writeConfig != "" {
settings := viper.AllSettings()
delete(settings, "writeconfig")
b, err := yaml.Marshal(settings)
if err != nil {
return errors.Wrap(err, "could not marshal config to yaml")
}
f, err := os.Create(writeConfig)
if err != nil {
return errors.Wrap(err, "could not create file for write")
}
_, err = fmt.Fprintf(f, "%s", b)
if err != nil {
return errors.Wrap(err, "could not write to file")
}
}
return nil
},
}
// Execute adds all child commands to the root command and sets flags appropriately.
// This is called by main.main(). It only needs to happen once to the rootCmd.
func Execute() {
if err := rootCmd.Execute(); err != nil {
fmt.Println(err)
os.Exit(1)
}
}
func init() {
cobra.OnInitialize(initConfig)
rootCmd.PersistentFlags().SortFlags = false
rootCmd.PersistentFlags().StringVar(&cfgFile, "config", "", "config file (default is $HOME/.mill.yaml)")
outputFlags := pflag.NewFlagSet("output", pflag.ExitOnError)
outputFlags.BoolP("log", "l", false, "If true, the logger output is sent to stderr. No logger output otherwise.")
ensure(viper.BindPFlag("log", outputFlags.Lookup("log")))
outputFlags.BoolP("debug", "d", false, "If true, debug output is enabled from the logger")
ensure(viper.BindPFlag("debug", outputFlags.Lookup("debug")))
outputFlags.Bool("trace", false, "If true, trace output is enabled from the logger")
ensure(viper.BindPFlag("trace", outputFlags.Lookup("trace")))
outputFlags.String("write-config", "", "Write the config of the current command as yaml to the specified path")
ensure(viper.BindPFlag("writeConfig", outputFlags.Lookup("write-config")))
rootCmd.PersistentFlags().AddFlagSet(outputFlags)
}
// initConfig reads in config file and ENV variables if set.
func initConfig() {
if cfgFile != "" {
// Use config file from the flag.
viper.SetConfigFile(cfgFile)
} else {
// Find home directory.
home, err := homedir.Dir()
if err != nil {
fmt.Println(err)
os.Exit(1)
}
viper.AddConfigPath(home)
viper.SetConfigName(".mill")
}
// read in environment variables that match
viper.AutomaticEnv()
// If a config file is found, read it in.
if err := viper.ReadInConfig(); err == nil {
fmt.Println("Using config file:", viper.ConfigFileUsed())
}
}
func ensure(err error) {
if err != nil {
panic(err)
}
}
func checkRequiredFlags(flags *pflag.FlagSet) error {
requiredError := false
flagName := ""
flags.VisitAll(func(flag *pflag.Flag) {
requiredAnnotation := flag.Annotations[cobra.BashCompOneRequiredFlag]
if len(requiredAnnotation) == 0 {
return
}
flagRequired := requiredAnnotation[0] == "true"
if flagRequired && !flag.Changed {
requiredError = true
flagName = flag.Name
}
})
if requiredError {
return errors.New("Required flag `" + flagName + "` has not been set")
}
return nil
}

85
tools/mill/go.mod Normal file
View File

@@ -0,0 +1,85 @@
module github.com/ThreeDotsLabs/watermill/tools/mill
go 1.25.0
require (
cloud.google.com/go/pubsub v1.50.2
github.com/IBM/sarama v1.48.1
github.com/ThreeDotsLabs/watermill v1.5.1
github.com/ThreeDotsLabs/watermill-amqp/v3 v3.0.2
github.com/ThreeDotsLabs/watermill-googlecloud v1.2.6
github.com/ThreeDotsLabs/watermill-io v1.1.2
github.com/ThreeDotsLabs/watermill-kafka/v3 v3.1.2
github.com/mitchellh/go-homedir v1.1.0
github.com/pkg/errors v0.9.1
github.com/spf13/cobra v1.10.2
github.com/spf13/pflag v1.0.10
github.com/spf13/viper v1.21.0
google.golang.org/api v0.279.0
gopkg.in/yaml.v2 v2.4.0
)
require (
cloud.google.com/go v0.123.0 // indirect
cloud.google.com/go/auth v0.20.0 // indirect
cloud.google.com/go/auth/oauth2adapt v0.2.8 // indirect
cloud.google.com/go/compute/metadata v0.9.0 // indirect
cloud.google.com/go/iam v1.11.0 // indirect
cloud.google.com/go/pubsub/v2 v2.6.0 // indirect
github.com/cenkalti/backoff/v3 v3.2.2 // indirect
github.com/cespare/xxhash/v2 v2.3.0 // indirect
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect
github.com/dnwe/otelsarama v0.0.0-20240308230250-9388d9d40bc0 // indirect
github.com/eapache/go-resiliency v1.7.0 // indirect
github.com/eapache/queue v1.1.0 // indirect
github.com/felixge/httpsnoop v1.0.4 // indirect
github.com/fsnotify/fsnotify v1.10.1 // indirect
github.com/go-logr/logr v1.4.3 // indirect
github.com/go-logr/stdr v1.2.2 // indirect
github.com/go-viper/mapstructure/v2 v2.5.0 // indirect
github.com/golang/groupcache v0.0.0-20241129210726-2c02b8208cf8 // indirect
github.com/google/s2a-go v0.1.9 // indirect
github.com/google/uuid v1.6.0 // indirect
github.com/googleapis/enterprise-certificate-proxy v0.3.15 // indirect
github.com/googleapis/gax-go/v2 v2.22.0 // indirect
github.com/hashicorp/errwrap v1.1.0 // indirect
github.com/hashicorp/go-multierror v1.1.1 // indirect
github.com/hashicorp/go-uuid v1.0.3 // indirect
github.com/inconshreveable/mousetrap v1.1.0 // indirect
github.com/jcmturner/aescts/v2 v2.0.0 // indirect
github.com/jcmturner/dnsutils/v2 v2.0.0 // indirect
github.com/jcmturner/gofork v1.7.6 // indirect
github.com/jcmturner/gokrb5/v8 v8.4.4 // indirect
github.com/jcmturner/rpc/v2 v2.0.3 // indirect
github.com/klauspost/compress v1.18.6 // indirect
github.com/lithammer/shortuuid/v3 v3.0.7 // indirect
github.com/oklog/ulid v1.3.1 // indirect
github.com/pelletier/go-toml/v2 v2.3.1 // indirect
github.com/pierrec/lz4/v4 v4.1.26 // indirect
github.com/rabbitmq/amqp091-go v1.11.0 // indirect
github.com/rcrowley/go-metrics v0.0.0-20250401214520-65e299d6c5c9 // indirect
github.com/sagikazarmark/locafero v0.12.0 // indirect
github.com/spf13/afero v1.15.0 // indirect
github.com/spf13/cast v1.10.0 // indirect
github.com/subosito/gotenv v1.6.0 // indirect
go.opencensus.io v0.24.0 // indirect
go.opentelemetry.io/auto/sdk v1.2.1 // indirect
go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.68.0 // indirect
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.68.0 // indirect
go.opentelemetry.io/otel v1.43.0 // indirect
go.opentelemetry.io/otel/metric v1.43.0 // indirect
go.opentelemetry.io/otel/trace v1.43.0 // indirect
go.yaml.in/yaml/v3 v3.0.4 // indirect
golang.org/x/crypto v0.51.0 // indirect
golang.org/x/net v0.54.0 // indirect
golang.org/x/oauth2 v0.36.0 // indirect
golang.org/x/sync v0.20.0 // indirect
golang.org/x/sys v0.44.0 // indirect
golang.org/x/text v0.37.0 // indirect
golang.org/x/time v0.15.0 // indirect
google.golang.org/genproto v0.0.0-20260511170946-3700d4141b60 // indirect
google.golang.org/genproto/googleapis/api v0.0.0-20260511170946-3700d4141b60 // indirect
google.golang.org/genproto/googleapis/rpc v0.0.0-20260511170946-3700d4141b60 // indirect
google.golang.org/grpc v1.81.0 // indirect
google.golang.org/protobuf v1.36.11 // indirect
)

320
tools/mill/go.sum Normal file
View File

@@ -0,0 +1,320 @@
cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
cloud.google.com/go v0.123.0 h1:2NAUJwPR47q+E35uaJeYoNhuNEM9kM8SjgRgdeOJUSE=
cloud.google.com/go v0.123.0/go.mod h1:xBoMV08QcqUGuPW65Qfm1o9Y4zKZBpGS+7bImXLTAZU=
cloud.google.com/go/auth v0.20.0 h1:kXTssoVb4azsVDoUiF8KvxAqrsQcQtB53DcSgta74CA=
cloud.google.com/go/auth v0.20.0/go.mod h1:942/yi/itH1SsmpyrbnTMDgGfdy2BUqIKyd0cyYLc5Q=
cloud.google.com/go/auth/oauth2adapt v0.2.8 h1:keo8NaayQZ6wimpNSmW5OPc283g65QNIiLpZnkHRbnc=
cloud.google.com/go/auth/oauth2adapt v0.2.8/go.mod h1:XQ9y31RkqZCcwJWNSx2Xvric3RrU88hAYYbjDWYDL+c=
cloud.google.com/go/compute/metadata v0.9.0 h1:pDUj4QMoPejqq20dK0Pg2N4yG9zIkYGdBtwLoEkH9Zs=
cloud.google.com/go/compute/metadata v0.9.0/go.mod h1:E0bWwX5wTnLPedCKqk3pJmVgCBSM6qQI1yTBdEb3C10=
cloud.google.com/go/iam v1.11.0 h1:KieQ9Pb+LLPak1O3Rv3GgCxhnmkYf7Xyh0P5HfF1jFM=
cloud.google.com/go/iam v1.11.0/go.mod h1:KP+nKGugNJW4LcLx1uEZcq1ok5sQHFaQehQNl4QDgV4=
cloud.google.com/go/kms v1.31.0 h1:LS8N92OxFDgOLg5NCo3OmbvjtQAIVT5gUHVLKIDHaFE=
cloud.google.com/go/kms v1.31.0/go.mod h1:YIyXZym11R5uovJJt4oN5eUL3oPmirF3yKeIh6QAf4U=
cloud.google.com/go/longrunning v0.13.0 h1:dUfqF8y0bHOeZzF5+tKPZ6RBCeEEDOejvwGwENv/eEc=
cloud.google.com/go/longrunning v0.13.0/go.mod h1:8nqFBPOO1U/XkhWl0I19AMZEphrHi73VNABIpKYaTwM=
cloud.google.com/go/pubsub v1.50.2 h1:54Up97HnThdP4H8jjWJSSQ/mnYG2EKon7ZSNETRq0tM=
cloud.google.com/go/pubsub v1.50.2/go.mod h1:jyCWeZdGFqd4mitSsBERnJcpqaHBsxQoPkNvjj4sp0w=
cloud.google.com/go/pubsub/v2 v2.6.0 h1:8pjR0id+GTB+krKx5G6AGJoYrHog58w2Q89PCOrfM64=
cloud.google.com/go/pubsub/v2 v2.6.0/go.mod h1:4anqvV/w8Pcgu2tO0qr2XgsF3GXHowzryfQ5gOnVmWY=
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
github.com/IBM/sarama v1.48.1 h1:x1dSWebprjjE7Wr7n8RVAxwa4mt4O9JejRxnZrGIXk0=
github.com/IBM/sarama v1.48.1/go.mod h1:m/Q1aFezH82/AglfTpJbw/fO0ZybYXhPgTmvajiZX50=
github.com/ThreeDotsLabs/watermill v1.5.1 h1:t5xMivyf9tpmU3iozPqyrCZXHvoV1XQDfihas4sV0fY=
github.com/ThreeDotsLabs/watermill v1.5.1/go.mod h1:Uop10dA3VeJWsSvis9qO3vbVY892LARrKAdki6WtXS4=
github.com/ThreeDotsLabs/watermill-amqp/v3 v3.0.2 h1:aeyFSR4SUsbszmocuFiYY13nsHorc6CXIS2Hy7+xgFU=
github.com/ThreeDotsLabs/watermill-amqp/v3 v3.0.2/go.mod h1:+8tCh6VCuBcQWhfETCwzRINKQ1uyeg9moH3h7jMKxQk=
github.com/ThreeDotsLabs/watermill-googlecloud v1.2.6 h1:Ll1NzWoiEXr3mtQ6APuVfMBoRNeGeLLvPcclYrpPTCA=
github.com/ThreeDotsLabs/watermill-googlecloud v1.2.6/go.mod h1:74wkEkvh9NawpHArWQ7OhHnuldkFj6+J//ZMi0Fgw58=
github.com/ThreeDotsLabs/watermill-io v1.1.2 h1:t3wismmE++6HbB+fDnSdvLtSKs0yYaSXRtLmyUcRyTk=
github.com/ThreeDotsLabs/watermill-io v1.1.2/go.mod h1:DF6rhoPWBOeWRW/1wWjNfLkke8rZsB5BUzBox/L6fRI=
github.com/ThreeDotsLabs/watermill-kafka/v3 v3.1.2 h1:lLmrzZnl8o8U5uLVhMLSFHGSuWLcsqhW1MOtltx2CbQ=
github.com/ThreeDotsLabs/watermill-kafka/v3 v3.1.2/go.mod h1:o1GcoF/1CSJ9JSmQzUkULvpZeO635pZe+WWrYNFlJNk=
github.com/cenkalti/backoff/v3 v3.2.2 h1:cfUAAO3yvKMYKPrvhDuHSwQnhZNk/RMHKdZqKTxfm6M=
github.com/cenkalti/backoff/v3 v3.2.2/go.mod h1:cIeZDE3IrqwwJl6VUwCN6trj1oXrTS4rc0ij+ULvLYs=
github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
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/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc=
github.com/cncf/xds/go v0.0.0-20260202195803-dba9d589def2 h1:aBangftG7EVZoUb69Os8IaYg++6uMOdKK83QtkkvJik=
github.com/cncf/xds/go v0.0.0-20260202195803-dba9d589def2/go.mod h1:qwXFYgsP6T7XnJtbKlf1HP8AjxZZyzxMmc+Lq5GjlU4=
github.com/cpuguy83/go-md2man/v2 v2.0.6/go.mod h1:oOW0eioCTA6cOiMLiUPZOpcVxMig6NIQQ7OS05n1F4g=
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/dnwe/otelsarama v0.0.0-20240308230250-9388d9d40bc0 h1:R2zQhFwSCyyd7L43igYjDrH0wkC/i+QBPELuY0HOu84=
github.com/dnwe/otelsarama v0.0.0-20240308230250-9388d9d40bc0/go.mod h1:2MqLKYJfjs3UriXXF9Fd0Qmh/lhxi/6tHXkqtXxyIHc=
github.com/eapache/go-resiliency v1.7.0 h1:n3NRTnBn5N0Cbi/IeOHuQn9s2UwVUH7Ga0ZWcP+9JTA=
github.com/eapache/go-resiliency v1.7.0/go.mod h1:5yPzW0MIvSe0JDsv0v+DvcjEv2FyD6iZYSs1ZI+iQho=
github.com/eapache/queue v1.1.0 h1:YOEu7KNc61ntiQlcEeUIoDTJ2o8mQznoNvUhiigpIqc=
github.com/eapache/queue v1.1.0/go.mod h1:6eCeP0CKFpHLu8blIFXhExK/dRa7WDZfr6jVFPTqq+I=
github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98=
github.com/envoyproxy/go-control-plane v0.14.0 h1:hbG2kr4RuFj222B6+7T83thSPqLjwBIfQawTkC++2HA=
github.com/envoyproxy/go-control-plane/envoy v1.37.0 h1:u3riX6BoYRfF4Dr7dwSOroNfdSbEPe9Yyl09/B6wBrQ=
github.com/envoyproxy/go-control-plane/envoy v1.37.0/go.mod h1:DReE9MMrmecPy+YvQOAOHNYMALuowAnbjjEMkkWOi6A=
github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
github.com/envoyproxy/protoc-gen-validate v1.3.3 h1:MVQghNeW+LZcmXe7SY1V36Z+WFMDjpqGAGacLe2T0ds=
github.com/envoyproxy/protoc-gen-validate v1.3.3/go.mod h1:TsndJ/ngyIdQRhMcVVGDDHINPLWB7C82oDArY51KfB0=
github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg=
github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U=
github.com/frankban/quicktest v1.14.6 h1:7Xjx+VpznH+oBnejlPUj8oUpdxnVs4f8XU8WnHkI4W8=
github.com/frankban/quicktest v1.14.6/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0=
github.com/fsnotify/fsnotify v1.10.1 h1:b0/UzAf9yR5rhf3RPm9gf3ehBPpf0oZKIjtpKrx59Ho=
github.com/fsnotify/fsnotify v1.10.1/go.mod h1:TLheqan6HD6GBK6PrDWyDPBaEV8LspOxvPSjC+bVfgo=
github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
github.com/go-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI=
github.com/go-logr/logr v1.4.3/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-viper/mapstructure/v2 v2.5.0 h1:vM5IJoUAy3d7zRSVtIwQgBj7BiWtMPfmPEgAXnvj1Ro=
github.com/go-viper/mapstructure/v2 v2.5.0/go.mod h1:oJDH3BJKyqBA2TXFhDsKDGDTlndYOZ6rGS0BRZIxGhM=
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
github.com/golang/groupcache v0.0.0-20241129210726-2c02b8208cf8 h1:f+oWsMOmNPc8JmEHVZIycC7hBoQxHH9pNKQORJNozsQ=
github.com/golang/groupcache v0.0.0-20241129210726-2c02b8208cf8/go.mod h1:wcDNUvekVysuuOpQKo3191zZyTpiI6se1N1ULghS0sw=
github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8=
github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA=
github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs=
github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w=
github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0=
github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8=
github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek=
github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps=
github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
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.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.3/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=
github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU=
github.com/google/s2a-go v0.1.9 h1:LGD7gtMgezd8a/Xak7mEWL0PjoTQFvpRudN895yqKW0=
github.com/google/s2a-go v0.1.9/go.mod h1:YA0Ei2ZQL3acow2O62kdp9UlnvMmU7kA6Eutn0dXayM=
github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/google/uuid v1.2.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
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/googleapis/enterprise-certificate-proxy v0.3.15 h1:xolVQTEXusUcAA5UgtyRLjelpFFHWlPQ4XfWGc7MBas=
github.com/googleapis/enterprise-certificate-proxy v0.3.15/go.mod h1:vqVt9yG9480NtzREnTlmGSBmFrA+bzb0yl0TxoBQXOg=
github.com/googleapis/gax-go/v2 v2.22.0 h1:PjIWBpgGIVKGoCXuiCoP64altEJCj3/Ei+kSU5vlZD4=
github.com/googleapis/gax-go/v2 v2.22.0/go.mod h1:irWBbALSr0Sk3qlqb9SyJ1h68WjgeFuiOzI4Rqw5+aY=
github.com/gorilla/securecookie v1.1.1/go.mod h1:ra0sb63/xPlUeL+yeDciTfxMRAA+MP+HVt/4epWDjd4=
github.com/gorilla/sessions v1.2.1/go.mod h1:dk2InVEVJ0sfLlnXv9EAgkf6ecYs/i80K/zI+bUmuGM=
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-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-uuid v1.0.2/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/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8=
github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw=
github.com/jcmturner/aescts/v2 v2.0.0 h1:9YKLH6ey7H4eDBXW8khjYslgyqG2xZikXP0EQFKrle8=
github.com/jcmturner/aescts/v2 v2.0.0/go.mod h1:AiaICIRyfYg35RUkr8yESTqvSy7csK90qZ5xfvvsoNs=
github.com/jcmturner/dnsutils/v2 v2.0.0 h1:lltnkeZGL0wILNvrNiVCR6Ro5PGU/SeBvVO/8c/iPbo=
github.com/jcmturner/dnsutils/v2 v2.0.0/go.mod h1:b0TnjGOvI/n42bZa+hmXL+kFJZsFT7G4t3HTlQ184QM=
github.com/jcmturner/gofork v1.7.6 h1:QH0l3hzAU1tfT3rZCnW5zXl+orbkNMMRGJfdJjHVETg=
github.com/jcmturner/gofork v1.7.6/go.mod h1:1622LH6i/EZqLloHfE7IeZ0uEJwMSUyQ/nDd82IeqRo=
github.com/jcmturner/goidentity/v6 v6.0.1 h1:VKnZd2oEIMorCTsFBnJWbExfNN7yZr3EhJAxwOkZg6o=
github.com/jcmturner/goidentity/v6 v6.0.1/go.mod h1:X1YW3bgtvwAXju7V3LCIMpY0Gbxyjn/mY9zx4tFonSg=
github.com/jcmturner/gokrb5/v8 v8.4.4 h1:x1Sv4HaTpepFkXbt2IkL29DXRf8sOfZXo8eRKh687T8=
github.com/jcmturner/gokrb5/v8 v8.4.4/go.mod h1:1btQEpgT6k+unzCwX1KdWMEwPPkkgBtP+F6aCACiMrs=
github.com/jcmturner/rpc/v2 v2.0.3 h1:7FXXj8Ti1IaVFpSAziCZWNzbNuZmnvw/i6CqLNdWfZY=
github.com/jcmturner/rpc/v2 v2.0.3/go.mod h1:VUJYCIDm3PVOEHw8sgt091/20OJjskO/YJki3ELg/Hc=
github.com/klauspost/compress v1.18.6 h1:2jupLlAwFm95+YDR+NwD2MEfFO9d4z4Prjl1XXDjuao=
github.com/klauspost/compress v1.18.6/go.mod h1:cwPg85FWrGar70rWktvGQj8/hthj3wpl0PGDogxkrSQ=
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
github.com/lithammer/shortuuid/v3 v3.0.7 h1:trX0KTHy4Pbwo/6ia8fscyHoGA+mf1jWbPJVuvyJQQ8=
github.com/lithammer/shortuuid/v3 v3.0.7/go.mod h1:vMk8ke37EmiewwolSO1NLW8vP4ZaKlRuDIi8tWWmAts=
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/oklog/ulid v1.3.1 h1:EGfNDEx6MqHz8B3uNV6QAib1UR2Lm97sHi3ocA6ESJ4=
github.com/oklog/ulid v1.3.1/go.mod h1:CirwcVhetQ6Lv90oh/F+FBtV6XMibvdAFo93nm5qn4U=
github.com/pelletier/go-toml/v2 v2.3.1 h1:MYEvvGnQjeNkRF1qUuGolNtNExTDwct51yp7olPtrEc=
github.com/pelletier/go-toml/v2 v2.3.1/go.mod h1:2gIqNv+qfxSVS7cM2xJQKtLSTLUE9V8t9Stt+h56mCY=
github.com/pierrec/lz4/v4 v4.1.26 h1:GrpZw1gZttORinvzBdXPUXATeqlJjqUG/D87TKMnhjY=
github.com/pierrec/lz4/v4 v4.1.26/go.mod h1:EoQMVJgeeEOMsCqCzqFm2O0cJvljX2nGZjcRIPL34O4=
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/planetscale/vtprotobuf v0.6.1-0.20240319094008-0393e58bdf10 h1:GFCKgmp0tecUJ0sJuv4pzYCqS9+RGSn52M3FUwPs+uo=
github.com/planetscale/vtprotobuf v0.6.1-0.20240319094008-0393e58bdf10/go.mod h1:t/avpk3KcrXxUnYOhZhMXJlSEyie6gQbtLq5NM3loB8=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
github.com/rabbitmq/amqp091-go v1.11.0 h1:HxIctVm9Gid/Vtn706necmZ7Wj6pgGI2eqplRbEY8O8=
github.com/rabbitmq/amqp091-go v1.11.0/go.mod h1:Hy4jKW5kQART1u+JkDTF9YYOQUHXqMuhrgxOEeS7G4o=
github.com/rcrowley/go-metrics v0.0.0-20250401214520-65e299d6c5c9 h1:bsUq1dX0N8AOIL7EB/X911+m4EHsnWEHeJ0c+3TTBrg=
github.com/rcrowley/go-metrics v0.0.0-20250401214520-65e299d6c5c9/go.mod h1:bCqnVzQkZxMG4s8nGwiZ5l3QUCyqpo9Y+/ZMZ9VjZe4=
github.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ=
github.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7so1lCWt35ZSgc=
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
github.com/sagikazarmark/locafero v0.12.0 h1:/NQhBAkUb4+fH1jivKHWusDYFjMOOKU88eegjfxfHb4=
github.com/sagikazarmark/locafero v0.12.0/go.mod h1:sZh36u/YSZ918v0Io+U9ogLYQJ9tLLBmM4eneO6WwsI=
github.com/spf13/afero v1.15.0 h1:b/YBCLWAJdFWJTN9cLhiXXcD7mzKn9Dm86dNnfyQw1I=
github.com/spf13/afero v1.15.0/go.mod h1:NC2ByUVxtQs4b3sIUphxK0NioZnmxgyCrfzeuq8lxMg=
github.com/spf13/cast v1.10.0 h1:h2x0u2shc1QuLHfxi+cTJvs30+ZAHOGRic8uyGTDWxY=
github.com/spf13/cast v1.10.0/go.mod h1:jNfB8QC9IA6ZuY2ZjDp0KtFO2LZZlg4S/7bzP6qqeHo=
github.com/spf13/cobra v1.10.2 h1:DMTTonx5m65Ic0GOoRY2c16WCbHxOOw6xxezuLaBpcU=
github.com/spf13/cobra v1.10.2/go.mod h1:7C1pvHqHw5A4vrJfjNwvOdzYu0Gml16OCs2GRiTUUS4=
github.com/spf13/pflag v1.0.9/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
github.com/spf13/pflag v1.0.10 h1:4EBh2KAYBwaONj6b2Ye1GiHfwjqyROoF4RwYO+vPwFk=
github.com/spf13/pflag v1.0.10/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
github.com/spf13/viper v1.21.0 h1:x5S+0EU27Lbphp4UKm1C+1oQO+rKx36vfCoaVebLFSU=
github.com/spf13/viper v1.21.0/go.mod h1:P0lhsswPGWD/1lZJ9ny3fYnVqxiegrlNrEmgLjbTCAY=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U=
github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U=
github.com/subosito/gotenv v1.6.0 h1:9NlTDc1FTs4qu0DDq7AEtTPNw6SVm7uBMsUCUjABIf8=
github.com/subosito/gotenv v1.6.0/go.mod h1:Dk4QP5c2W3ibzajGcXpNraDfq2IrhjMIvMSWPKKo0FU=
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
go.einride.tech/aip v0.83.0 h1:TI21IdeOnLTwZEJ3BxtImIZk6bsN2Q+sd0x99SLiQ+M=
go.einride.tech/aip v0.83.0/go.mod h1:E8+wdTApA70odnpFzJgsGogHozC2JCIhFJBKPr8bVig=
go.opencensus.io v0.24.0 h1:y73uSU6J157QMP2kn2r30vwW1A2W2WFwSCGnAVxeaD0=
go.opencensus.io v0.24.0/go.mod h1:vNK8G9p7aAivkbmorf4v+7Hgx+Zs0yY+0fOtgBfjQKo=
go.opentelemetry.io/auto/sdk v1.2.1 h1:jXsnJ4Lmnqd11kwkBV2LgLoFMZKizbCi5fNZ/ipaZ64=
go.opentelemetry.io/auto/sdk v1.2.1/go.mod h1:KRTj+aOaElaLi+wW1kO/DZRXwkF4C5xPbEe3ZiIhN7Y=
go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.68.0 h1:0Qx7VGBacMm9ZENQ7TnNObTYI4ShC+lHI16seduaxZo=
go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.68.0/go.mod h1:Sje3i3MjSPKTSPvVWCaL8ugBzJwik3u4smCjUeuupqg=
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.68.0 h1:CqXxU8VOmDefoh0+ztfGaymYbhdB/tT3zs79QaZTNGY=
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.68.0/go.mod h1:BuhAPThV8PBHBvg8ZzZ/Ok3idOdhWIodywz2xEcRbJo=
go.opentelemetry.io/otel v1.43.0 h1:mYIM03dnh5zfN7HautFE4ieIig9amkNANT+xcVxAj9I=
go.opentelemetry.io/otel v1.43.0/go.mod h1:JuG+u74mvjvcm8vj8pI5XiHy1zDeoCS2LB1spIq7Ay0=
go.opentelemetry.io/otel/metric v1.43.0 h1:d7638QeInOnuwOONPp4JAOGfbCEpYb+K6DVWvdxGzgM=
go.opentelemetry.io/otel/metric v1.43.0/go.mod h1:RDnPtIxvqlgO8GRW18W6Z/4P462ldprJtfxHxyKd2PY=
go.opentelemetry.io/otel/sdk v1.43.0 h1:pi5mE86i5rTeLXqoF/hhiBtUNcrAGHLKQdhg4h4V9Dg=
go.opentelemetry.io/otel/sdk v1.43.0/go.mod h1:P+IkVU3iWukmiit/Yf9AWvpyRDlUeBaRg6Y+C58QHzg=
go.opentelemetry.io/otel/sdk/metric v1.43.0 h1:S88dyqXjJkuBNLeMcVPRFXpRw2fuwdvfCGLEo89fDkw=
go.opentelemetry.io/otel/sdk/metric v1.43.0/go.mod h1:C/RJtwSEJ5hzTiUz5pXF1kILHStzb9zFlIEe85bhj6A=
go.opentelemetry.io/otel/trace v1.43.0 h1:BkNrHpup+4k4w+ZZ86CZoHHEkohws8AY+WTX09nk+3A=
go.opentelemetry.io/otel/trace v1.43.0/go.mod h1:/QJhyVBUUswCphDVxq+8mld+AvhXZLhe+8WVFxiFff0=
go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto=
go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE=
go.yaml.in/yaml/v3 v3.0.4 h1:tfq32ie2Jv2UxXFdLJdh3jXuOzWiL1fo0bu/FbuKpbc=
go.yaml.in/yaml/v3 v3.0.4/go.mod h1:DhzuOOF2ATzADvBadXxruRBLzYTpT36CKvDb3+aBEFg=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
golang.org/x/crypto v0.6.0/go.mod h1:OFC/31mSvZgRz0V1QTNCzfAI1aIRzbiufJtkMIlEp58=
golang.org/x/crypto v0.51.0 h1:IBPXwPfKxY7cWQZ38ZCIRPI50YLeevDLlLnyC5wRGTI=
golang.org/x/crypto v0.51.0/go.mod h1:8AdwkbraGNABw2kOX6YFPs3WM22XqI4EXEd8g+x7Oc8=
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU=
golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200114155413-6afb5195e5aa/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20201110031124-69a78807bb2b/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-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
golang.org/x/net v0.7.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
golang.org/x/net v0.54.0 h1:2zJIZAxAHV/OHCDTCOHAYehQzLfSXuf/5SoL/Dv6w/w=
golang.org/x/net v0.54.0/go.mod h1:Sj4oj8jK6XmHpBZU/zWHw3BV3abl4Kvi+Ut7cQcY+cQ=
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
golang.org/x/oauth2 v0.36.0 h1:peZ/1z27fi9hUOFCAZaHyrpWG5lwe0RJEEEeH0ThlIs=
golang.org/x/oauth2 v0.36.0/go.mod h1:YDBUJMTkDnJS+A4BP4eZBjCqtokkg1hODuPjwiGPO7Q=
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/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-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.20.0 h1:e0PTpb7pjO8GAtTs2dQ6jYa5BWYlMuX047Dco/pItO4=
golang.org/x/sync v0.20.0/go.mod h1:9xrNwdLfx4jkKbNva9FpL6vEN7evnE43NNNJQ2LF3+0=
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/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-20190412213103-97732733099d/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-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.44.0 h1:ildZl3J4uzeKP07r2F++Op7E9B29JRUy+a27EibtBTQ=
golang.org/x/sys v0.44.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
golang.org/x/text v0.37.0 h1:Cqjiwd9eSg8e0QAkyCaQTNHFIIzWtidPahFWR83rTrc=
golang.org/x/text v0.37.0/go.mod h1:a5sjxXGs9hsn/AJVwuElvCAo9v8QYLzvavO5z2PiM38=
golang.org/x/time v0.15.0 h1:bbrp8t3bGUeFOx08pvsMYRTCVSMk89u4tKbNOZbp88U=
golang.org/x/time v0.15.0/go.mod h1:Y4YMaQmXwGQZoFaVFk4YpCt4FLQMYKZe9oeV/f4MSno=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY=
golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
gonum.org/v1/gonum v0.17.0 h1:VbpOemQlsSMrYmn7T2OUvQ4dqxQXU+ouZFQsZOx50z4=
gonum.org/v1/gonum v0.17.0/go.mod h1:El3tOrEuMpv2UdMrbNlKEh9vd86bmQ6vqIcDwxEOc1E=
google.golang.org/api v0.279.0 h1:hsx2M2OaRcaKtVYK6vXEUnQvdjnend7ZYES+lYaot74=
google.golang.org/api v0.279.0/go.mod h1:B9TqLBwJqVjp1mtt7WeoQwWRwvu/400y5lETOql+giQ=
google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=
google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo=
google.golang.org/genproto v0.0.0-20260511170946-3700d4141b60 h1:rhBdfmsOlOZIvz3Y5/BdUzPg2CkO8L7QQPKj96B8554=
google.golang.org/genproto v0.0.0-20260511170946-3700d4141b60/go.mod h1:8xo2Pj1b20ZOCpzlU3B9qieMwVIAXx1QVZWLMlPL6sM=
google.golang.org/genproto/googleapis/api v0.0.0-20260511170946-3700d4141b60 h1:3WsB1FAbiRIf2tOxscWKs3pQBD9he1NsrnbhMuWfekc=
google.golang.org/genproto/googleapis/api v0.0.0-20260511170946-3700d4141b60/go.mod h1:7yoXV7RIh5gblj/xVYoogxAWvA9wUeVbpsK/M694l00=
google.golang.org/genproto/googleapis/rpc v0.0.0-20260511170946-3700d4141b60 h1:seT2EwLWM78plQ7wcDfuWBc/4FAEAXDDiaSol4ku4qo=
google.golang.org/genproto/googleapis/rpc v0.0.0-20260511170946-3700d4141b60/go.mod h1:4Hqkh8ycfw05ld/3BWL7rJOSfebL2Q+DVDeRgYgxUU8=
google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg=
google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY=
google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
google.golang.org/grpc v1.33.2/go.mod h1:JMHMWHQWaTccqQQlmk3MJZS+GWXOdAesneDmEnv2fbc=
google.golang.org/grpc v1.81.0 h1:W3G9N3KQf3BU+YuCtGKJk0CmxQNbAISICD/9AORxLIw=
google.golang.org/grpc v1.81.0/go.mod h1:xGH9GfzOyMTGIOXBJmXt+BX/V0kcdQbdcuwQ/zNw42I=
google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8=
google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0=
google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM=
google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE=
google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo=
google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c=
google.golang.org/protobuf v1.36.11 h1:fV6ZwhNocDyBLK0dj+fg8ektcVegBBuEolpbTQyBNVE=
google.golang.org/protobuf v1.36.11/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
gopkg.in/yaml.v2 v2.2.2/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.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=

9
tools/mill/main.go Normal file
View File

@@ -0,0 +1,9 @@
package main
import "github.com/ThreeDotsLabs/watermill/tools/mill/cmd"
func main() {
cmd.Execute()
}
// TODO: alternative input/output modes: json, gob, protobuf... (?)