From e429e20233c765d732614d12ee5e63e0915185c4 Mon Sep 17 00:00:00 2001 From: Ryan Tinianov Date: Thu, 15 Oct 2026 16:12:58 -0400 Subject: [PATCH] Use x/cli for PSBs --- go.md | 5 +- libs/capability/capability.go | 17 ++- libs/capability/config.go | 55 +++---- libs/capability/doccomments_gen.go | 67 +++++++++ libs/capability/flags.go | 182 ------------------------ libs/capability/internal/docgen/main.go | 32 +++++ libs/capability/observability.go | 16 --- libs/capability/pyroscope.go | 9 +- libs/capability/registry.go | 4 +- libs/capability/registry_test.go | 12 +- libs/capability/run.go | 3 +- libs/capability/settings.go | 11 +- libs/capability/settings_test.go | 4 +- libs/capability/telemetry.go | 52 ++++--- libs/capability/telemetry_test.go | 12 +- libs/capability/webserver.go | 3 +- libs/go.mod | 21 +-- libs/go.sum | 31 +--- 18 files changed, 227 insertions(+), 309 deletions(-) create mode 100644 libs/capability/doccomments_gen.go delete mode 100644 libs/capability/flags.go create mode 100644 libs/capability/internal/docgen/main.go diff --git a/go.md b/go.md index da0f8df4d..e8a7fc5ed 100644 --- a/go.md +++ b/go.md @@ -31,7 +31,7 @@ flowchart LR capabilities/integration_tests --> chainlink/v2 capabilities/integration_tests --> cre-sdk-go/capabilities/blockchain/evm click capabilities/integration_tests href "https://github.com/smartcontractkit/capabilities" - capabilities/libs --> chainlink-common + capabilities/libs --> chainlink-common/x/config click capabilities/libs href "https://github.com/smartcontractkit/capabilities" ccip-contract-examples/chains/evm click ccip-contract-examples/chains/evm href "https://github.com/smartcontractkit/ccip-contract-examples" @@ -101,6 +101,8 @@ flowchart LR click chainlink-common/pkg/chipingress href "https://github.com/smartcontractkit/chainlink-common" chainlink-common/pkg/monitoring click chainlink-common/pkg/monitoring href "https://github.com/smartcontractkit/chainlink-common" + chainlink-common/x/config --> chainlink-common + click chainlink-common/x/config href "https://github.com/smartcontractkit/chainlink-common" chainlink-data-streams --> chainlink-common/keystore chainlink-data-streams --> chainlink-evm/gethwrappers click chainlink-data-streams href "https://github.com/smartcontractkit/chainlink-data-streams" @@ -265,6 +267,7 @@ flowchart LR chainlink-common/keystore chainlink-common/pkg/chipingress chainlink-common/pkg/monitoring + chainlink-common/x/config end click chainlink-common-repo href "https://github.com/smartcontractkit/chainlink-common" diff --git a/libs/capability/capability.go b/libs/capability/capability.go index 2474c2c6d..f52cf9895 100644 --- a/libs/capability/capability.go +++ b/libs/capability/capability.go @@ -15,11 +15,14 @@ type Capability interface { Service() protoreflect.ServiceDescriptor } -// capabilityConfig is what a capability binary needs from the node it runs beside. -type capabilityConfig struct { - ProxyURL string `usage:"gRPC target of the node's capability registry proxy (e.g. localhost:9000), used to resolve capabilities this binary does not host. Unset resolves only the capabilities this binary hosts"` - CapabilityDonID uint32 `usage:"on-chain DON ID of the capability DON this process was spawned for"` - - // Serves the Debug UI - HTTPDebug bool `usage:"serve the capability debug UI on the shared HTTP server, under /debug/capabilities"` +// CapabilitiesConfig is what a capability binary needs from the node it runs beside. +type CapabilitiesConfig struct { + // ProxyURL is the gRPC target of the node's capability registry proxy (e.g. localhost:9000), used + // to resolve capabilities this binary does not host. Unset resolves only the capabilities this + // binary hosts. + ProxyURL string + // CapabilityDonID is the on-chain DON ID of the capability DON this process was spawned for. + CapabilityDonID uint32 + // HTTPDebug serves the capability debug UI on the shared HTTP server, under /debug/capabilities. + HTTPDebug bool } diff --git a/libs/capability/config.go b/libs/capability/config.go index f013dea7e..eee6457f0 100644 --- a/libs/capability/config.go +++ b/libs/capability/config.go @@ -1,9 +1,14 @@ package capability +//go:generate go run ./internal/docgen + import ( - "fmt" + "errors" "github.com/spf13/cobra" + + "github.com/smartcontractkit/chainlink-common/x/config/cli" + "github.com/smartcontractkit/chainlink-common/x/config/markup/tomlmarkup" ) const capabilitiesNamespace = "capabilities" @@ -14,41 +19,43 @@ const settingsNamespace = "settings" const defaultHost = "localhost" -type grpcConfig struct { - AdvertiseHost string `usage:"host the gRPC servers this process opens bind to and are advertised at; empty binds every interface"` +// GRPCConfig configures the gRPC servers this process opens. +type GRPCConfig struct { + // AdvertiseHost is the host the gRPC servers this process opens bind to and are advertised at. + // Empty binds every interface. + AdvertiseHost string } // the standard configuration for a capability; does not include the config that a capability may request via its constructor. type config struct { observability observability - capabilities capabilityConfig - grpc grpcConfig - settings settingsConfig + capabilities CapabilitiesConfig + grpc GRPCConfig + settings SettingsConfig } func defaultConfig() *config { return &config{ observability: *defaultObservability(), - grpc: grpcConfig{AdvertiseHost: defaultHost}, + grpc: GRPCConfig{AdvertiseHost: defaultHost}, } } -// namespaced pairs every config with the namespace it is registered under, in the order the flags -// are registered. -func (c *config) namespaced() []section { - return append(c.observability.namespaced(), - section{capabilitiesNamespace, &c.capabilities}, - section{grpcNamespace, &c.grpc}, - section{settingsNamespace, &c.settings}, - ) -} - -// bind binds every config to root, each under the namespace that owns it. -func (c *config) bind(root *cobra.Command) error { - for _, s := range c.namespaced() { - if err := bindFlags(root, s.namespace, s.target); err != nil { - return fmt.Errorf("failed to register the %s settings: %w", s.namespace, err) - } +// bind binds every config to root, each under the namespace that owns it. Each is settable by +// flag, CRE_/CL_ env var, or config file, in that order of precedence, over its defaults. +func (c *config) bind(root *cobra.Command) (*cli.Binder, error) { + b, err := cli.New(cli.Options{Markup: tomlmarkup.New(), Prefixes: []string{"CRE", "CL"}}) + if err != nil { + return nil, err } - return nil + return b, errors.Join( + b.RegisterInNamespace(root, telemetryNamespace, &c.observability.telemetry), + b.RegisterInNamespace(root, tracingNamespace, &c.observability.tracing), + b.RegisterInNamespace(root, chipIngressNamespace, &c.observability.chipIngress), + b.RegisterInNamespace(root, pyroscopeNamespace, &c.observability.pyroscope), + b.RegisterInNamespace(root, httpNamespace, &c.observability.http), + b.RegisterInNamespace(root, capabilitiesNamespace, &c.capabilities), + b.RegisterInNamespace(root, grpcNamespace, &c.grpc), + b.RegisterInNamespace(root, settingsNamespace, &c.settings), + ) } diff --git a/libs/capability/doccomments_gen.go b/libs/capability/doccomments_gen.go new file mode 100644 index 000000000..874f42545 --- /dev/null +++ b/libs/capability/doccomments_gen.go @@ -0,0 +1,67 @@ +// Code generated by github.com/smartcontractkit/capabilities/libs/capability/internal/docgen, DO NOT EDIT. + +package capability + +import "github.com/smartcontractkit/chainlink-common/x/config/commentparsing" + +func (CapabilitiesConfig) DocComments() map[string]commentparsing.FieldDoc { + return map[string]commentparsing.FieldDoc{ + "CapabilityDonID": {Comment: "CapabilityDonID is the on-chain DON ID of the capability DON this process was spawned for."}, + "HTTPDebug": {Comment: "HTTPDebug serves the capability debug UI on the shared HTTP server, under /debug/capabilities."}, + "ProxyURL": {Comment: "ProxyURL is the gRPC target of the node's capability registry proxy (e.g. localhost:9000), used\nto resolve capabilities this binary does not host. Unset resolves only the capabilities this\nbinary hosts."}, + } +} + +func (ChipIngressConfig) DocComments() map[string]commentparsing.FieldDoc { + return map[string]commentparsing.FieldDoc{ + "Endpoint": {Comment: "Endpoint is the chip ingress gRPC endpoint. The emitter is disabled when unset."}, + "InsecureConnection": {Comment: "InsecureConnection connects to chip ingress over an insecure connection."}, + } +} + +func (GRPCConfig) DocComments() map[string]commentparsing.FieldDoc { + return map[string]commentparsing.FieldDoc{ + "AdvertiseHost": {Comment: "AdvertiseHost is the host the gRPC servers this process opens bind to and are advertised at.\nEmpty binds every interface."}, + } +} + +func (HTTPConfig) DocComments() map[string]commentparsing.FieldDoc { + return map[string]commentparsing.FieldDoc{ + "Port": {Comment: "Port serves /metrics, /debug/pprof, /healthz, /readyz and any routes a service registers."}, + } +} + +func (PyroscopeConfig) DocComments() map[string]commentparsing.FieldDoc { + return map[string]commentparsing.FieldDoc{ + "AuthToken": {Comment: "AuthToken is the pyroscope auth token."}, + "Environment": {Comment: "Environment is the environment tag attached to profiles."}, + "ServerAddress": {Comment: "ServerAddress is the pyroscope server address. Profiling is disabled when unset."}, + } +} + +func (SettingsConfig) DocComments() map[string]commentparsing.FieldDoc { + return map[string]commentparsing.FieldDoc{ + "URL": {Comment: "URL is the gRPC target of the host's CRE settings service (e.g. localhost:9001), which pushes\nsettings updates to this process. Unset leaves every limit at its compiled-in default."}, + } +} + +func (TelemetryConfig) DocComments() map[string]commentparsing.FieldDoc { + return map[string]commentparsing.FieldDoc{ + "Attributes": {Comment: "Attributes are extra telemetry resource attributes, as key=value pairs."}, + "AuthHeaders": {Comment: "AuthHeaders are telemetry auth headers, as key=value pairs."}, + "AuthHeadersTTL": {Comment: "AuthHeadersTTL is how long generated telemetry auth headers are valid for."}, + "AuthPubKeyHex": {Comment: "AuthPubKeyHex is the public key the telemetry auth headers are derived from."}, + "CACertFile": {Comment: "CACertFile is the CA certificate file used to verify the telemetry endpoint."}, + "Endpoint": {Comment: "Endpoint is the OTLP gRPC endpoint telemetry is exported to. Telemetry is disabled when unset."}, + "InsecureConnection": {Comment: "InsecureConnection exports telemetry over an insecure connection."}, + "PrometheusBridgeEnabled": {Comment: "PrometheusBridgeEnabled feeds metrics registered on the prometheus registry into the telemetry\npipeline."}, + } +} + +func (TracingConfig) DocComments() map[string]commentparsing.FieldDoc { + return map[string]commentparsing.FieldDoc{ + "Enabled": {Comment: "Enabled exports traces to the telemetry endpoint."}, + "SamplingRatio": {Comment: "SamplingRatio is the fraction of traces sampled, from 0 to 1."}, + "TLSCertFile": {Comment: "TLSCertFile is the TLS certificate file used by the trace exporter."}, + } +} diff --git a/libs/capability/flags.go b/libs/capability/flags.go deleted file mode 100644 index 80cda8ee0..000000000 --- a/libs/capability/flags.go +++ /dev/null @@ -1,182 +0,0 @@ -package capability - -import ( - "encoding" - "errors" - "fmt" - "os" - "reflect" - "strings" - "time" - - "github.com/go-viper/mapstructure/v2" - "github.com/iancoleman/strcase" - "github.com/spf13/cobra" - "github.com/spf13/pflag" - "github.com/spf13/viper" - - commonconfig "github.com/smartcontractkit/chainlink-common/pkg/config" -) - -var envPrefixes = []string{"CRE", "CL"} - -var ( - durationType = reflect.TypeFor[time.Duration]() - configDurationType = reflect.TypeFor[commonconfig.Duration]() - textUnmarshalerType = reflect.TypeFor[encoding.TextUnmarshaler]() -) - -type boundField struct { - name string - key string - envs []string - flag *pflag.Flag -} - -// set reports whether the user supplied a value. Unset fields keep whatever target held when it -// was bound, rather than a default round-tripped through its text form - a SecretString's text -// form is redacted. -func (f boundField) set(v *viper.Viper) bool { - if f.flag.Changed || v.InConfig(f.key) { - return true - } - for _, env := range f.envs { - if _, ok := os.LookupEnv(env); ok { - return true - } - } - return false -} - -// bindFlags binds the exported fields of the struct target points at as persistent flags on cmd, -// named namespace.kebab-case-field, and fills them in before cmd runs from, in order of -// precedence, the flag, the CRE_/CL_ env var, or the config file. -func bindFlags(cmd *cobra.Command, namespace string, target any) error { - rv := reflect.ValueOf(target) - if rv.Kind() != reflect.Pointer || rv.Elem().Kind() != reflect.Struct { - return fmt.Errorf("can only bind flags to a pointer to a struct, got %T", target) - } - elem := rv.Elem() - - v := viper.New() - var fields []boundField - for i := range elem.NumField() { - sf := elem.Type().Field(i) - if !sf.IsExported() || sf.Anonymous { - continue - } - - key := namespace + "." + strcase.ToKebab(sf.Name) - flag, err := addFlag(cmd.PersistentFlags(), key, sf.Tag.Get("usage"), elem.Field(i)) - if err != nil { - return err - } - if err := v.BindPFlag(key, flag); err != nil { - return err - } - - suffix := strings.ToUpper(strings.NewReplacer(".", "_", "-", "_").Replace(key)) - envs := make([]string, len(envPrefixes)) - for j, p := range envPrefixes { - envs[j] = p + "_" + suffix - } - if err := v.BindEnv(append([]string{key}, envs...)...); err != nil { - return err - } - - fields = append(fields, boundField{name: sf.Name, key: key, envs: envs, flag: flag}) - } - - prev := cmd.PersistentPreRunE - cmd.PersistentPreRunE = func(c *cobra.Command, args []string) error { - if err := readConfigFile(c, v); err != nil { - return err - } - values := map[string]any{} - for _, f := range fields { - if f.set(v) { - values[f.name] = v.Get(f.key) - } - } - if err := decode(values, target); err != nil { - return fmt.Errorf("invalid %s settings: %w", namespace, err) - } - if prev != nil { - return prev(c, args) - } - return nil - } - return nil -} - -func addFlag(fs *pflag.FlagSet, name, usage string, field reflect.Value) (*pflag.Flag, error) { - t := field.Type() - switch { - case t == configDurationType: - fs.Duration(name, field.Interface().(commonconfig.Duration).Duration(), usage) - case t == durationType: - fs.Duration(name, time.Duration(field.Int()), usage) - case reflect.PointerTo(t).Implements(textUnmarshalerType): - var def string - if !field.IsZero() { - def = fmt.Sprint(field.Interface()) - } - fs.String(name, def, usage) - case t.Kind() == reflect.Slice && t.Elem().Kind() == reflect.String: - fs.StringSlice(name, field.Interface().([]string), usage) - case t.Kind() == reflect.String: - fs.String(name, field.String(), usage) - case t.Kind() == reflect.Bool: - fs.Bool(name, field.Bool(), usage) - case t.Kind() >= reflect.Int && t.Kind() <= reflect.Int64: - fs.Int64(name, field.Int(), usage) - case t.Kind() >= reflect.Uint && t.Kind() <= reflect.Uint64: - fs.Uint64(name, field.Uint(), usage) - case t.Kind() == reflect.Float32 || t.Kind() == reflect.Float64: - fs.Float64(name, field.Float(), usage) - default: - return nil, fmt.Errorf("cannot bind %s: unsupported type %s", name, t) - } - return fs.Lookup(name), nil -} - -// readConfigFile reads --config, or ./config.toml if it exists when --config is unset. -func readConfigFile(cmd *cobra.Command, v *viper.Viper) error { - path, _ := cmd.Flags().GetString("config") - if path != "" { - v.SetConfigFile(path) - if err := v.ReadInConfig(); err != nil { - return fmt.Errorf("failed to read config file %q: %w", path, err) - } - return nil - } - - v.AddConfigPath(".") - v.SetConfigName("config") - v.SetConfigType("toml") - if err := v.ReadInConfig(); err != nil { - if errors.As(err, &viper.ConfigFileNotFoundError{}) { - return nil - } - return fmt.Errorf("failed to parse config file: %w", err) - } - return nil -} - -// decode is weakly typed because env vars, and the flags viper does not parse itself (durations, -// unsigned ints), arrive as strings. -func decode(values map[string]any, target any) error { - d, err := mapstructure.NewDecoder(&mapstructure.DecoderConfig{ - Result: target, - WeaklyTypedInput: true, - DecodeHook: mapstructure.ComposeDecodeHookFunc( - mapstructure.StringToTimeDurationHookFunc(), - mapstructure.StringToSliceHookFunc(","), - mapstructure.TextUnmarshallerHookFunc(), - ), - }) - if err != nil { - return err - } - return d.Decode(values) -} diff --git a/libs/capability/internal/docgen/main.go b/libs/capability/internal/docgen/main.go new file mode 100644 index 000000000..cfd9e0267 --- /dev/null +++ b/libs/capability/internal/docgen/main.go @@ -0,0 +1,32 @@ +// Command docgen generates the DocComments for this package's config sections, which the flags' +// help text is read from. +package main + +import ( + "log" + + "github.com/smartcontractkit/chainlink-common/x/config/commentparsing" + "github.com/smartcontractkit/chainlink-common/x/config/markup/tomlmarkup" + + "github.com/smartcontractkit/capabilities/libs/capability" +) + +func main() { + if err := commentparsing.Run(commentparsing.RunArgs{ + Markup: tomlmarkup.New(), + Roots: []any{ + &capability.TelemetryConfig{}, + &capability.TracingConfig{}, + &capability.ChipIngressConfig{}, + &capability.PyroscopeConfig{}, + &capability.HTTPConfig{}, + &capability.CapabilitiesConfig{}, + &capability.GRPCConfig{}, + &capability.SettingsConfig{}, + }, + Tool: "github.com/smartcontractkit/capabilities/libs/capability/internal/docgen", + LocalPrefix: "github.com/smartcontractkit", + }); err != nil { + log.Fatal(err) + } +} diff --git a/libs/capability/observability.go b/libs/capability/observability.go index 26f704117..5cf790e29 100644 --- a/libs/capability/observability.go +++ b/libs/capability/observability.go @@ -28,19 +28,3 @@ func defaultObservability() *observability { http: HTTPConfig{Port: defaultHTTPPort}, } } - -type section struct { - namespace string - target any -} - -// namespaced pairs each config with its namespace, in the order the flags are registered. -func (o *observability) namespaced() []section { - return []section{ - {telemetryNamespace, &o.telemetry}, - {tracingNamespace, &o.tracing}, - {chipIngressNamespace, &o.chipIngress}, - {pyroscopeNamespace, &o.pyroscope}, - {httpNamespace, &o.http}, - } -} diff --git a/libs/capability/pyroscope.go b/libs/capability/pyroscope.go index e4f63074f..b1ff98229 100644 --- a/libs/capability/pyroscope.go +++ b/libs/capability/pyroscope.go @@ -14,9 +14,12 @@ import ( // PyroscopeConfig configures continuous profiling. An empty ServerAddress leaves profiling off. type PyroscopeConfig struct { - ServerAddress string `usage:"pyroscope server address; profiling is disabled when unset"` - AuthToken commonconfig.SecretString `usage:"pyroscope auth token" flagdocs:"noexample"` - Environment string `usage:"environment tag attached to profiles"` + // ServerAddress is the pyroscope server address. Profiling is disabled when unset. + ServerAddress string + // AuthToken is the pyroscope auth token. + AuthToken commonconfig.SecretString + // Environment is the environment tag attached to profiles. + Environment string } // newProfiler returns continuous profiling as a service, or nil when no pyroscope server is diff --git a/libs/capability/registry.go b/libs/capability/registry.go index 0330aa219..5214cb614 100644 --- a/libs/capability/registry.go +++ b/libs/capability/registry.go @@ -22,7 +22,7 @@ import ( p2ptypes "github.com/smartcontractkit/libocr/ragep2p/types" ) -func newRegistry(lggr logger.Logger, cfg capabilityConfig, grpcCfg grpcConfig) (*registryService, error) { +func newRegistry(lggr logger.Logger, cfg CapabilitiesConfig, grpcCfg GRPCConfig) (*registryService, error) { r := ®istryService{local: registry.NewRegistry(lggr)} if cfg.ProxyURL != "" { @@ -56,7 +56,7 @@ func dial(target string) (*grpc.ClientConn, error) { } // startRegistry builds the registry and starts it. -func startRegistry(ctx context.Context, lggr logger.Logger, cfg capabilityConfig, grpcCfg grpcConfig) (*registryService, error) { +func startRegistry(ctx context.Context, lggr logger.Logger, cfg CapabilitiesConfig, grpcCfg GRPCConfig) (*registryService, error) { r, err := newRegistry(lggr, cfg, grpcCfg) if err != nil { return nil, err diff --git a/libs/capability/registry_test.go b/libs/capability/registry_test.go index e6917d980..b187088e7 100644 --- a/libs/capability/registry_test.go +++ b/libs/capability/registry_test.go @@ -16,7 +16,7 @@ import ( "github.com/smartcontractkit/chainlink-common/pkg/logger" ) -var testGRPC = grpcConfig{AdvertiseHost: defaultHost} +var testGRPC = GRPCConfig{AdvertiseHost: defaultHost} // TestNewRegistryDoesNotDial is the property the boot sequence rests on: the node starts this // process and the two race, so a registry proxy that is not up yet has to delay the first lookup @@ -24,7 +24,7 @@ var testGRPC = grpcConfig{AdvertiseHost: defaultHost} // // Nothing is listening on the port below, and building the registry still succeeds. func TestNewRegistryDoesNotDial(t *testing.T) { - reg, err := newRegistry(logger.Test(t), capabilityConfig{ProxyURL: "localhost:1", CapabilityDonID: 1}, testGRPC) + reg, err := newRegistry(logger.Test(t), CapabilitiesConfig{ProxyURL: "localhost:1", CapabilityDonID: 1}, testGRPC) require.NoError(t, err, "an unreachable proxy should not fail construction") require.NotNil(t, reg.fallback) @@ -42,7 +42,7 @@ func TestNewRegistryDoesNotDial(t *testing.T) { func TestNewRegistryDoesNotValidateTheTarget(t *testing.T) { for _, target := range []string{"!://not a target", "unknownscheme:///x"} { t.Run(target, func(t *testing.T) { - reg, err := newRegistry(logger.Test(t), capabilityConfig{ProxyURL: target}, testGRPC) + reg, err := newRegistry(logger.Test(t), CapabilitiesConfig{ProxyURL: target}, testGRPC) require.NoError(t, err) require.NoError(t, reg.Start(t.Context())) assert.NoError(t, reg.Close()) @@ -56,7 +56,7 @@ func TestNewRegistryDoesNotValidateTheTarget(t *testing.T) { // The metadata calls failing is the point rather than a shortcoming. A process holding capability // values has no way to know which DONs exist, so saying so beats answering with something invented. func TestNewRegistryWithoutAProxyIsLocalOnly(t *testing.T) { - reg, err := newRegistry(logger.Test(t), capabilityConfig{CapabilityDonID: 1}, testGRPC) + reg, err := newRegistry(logger.Test(t), CapabilitiesConfig{CapabilityDonID: 1}, testGRPC) require.NoError(t, err) t.Cleanup(func() { assert.NoError(t, reg.Close()) }) require.NoError(t, reg.Start(t.Context())) @@ -71,7 +71,7 @@ func TestNewRegistryWithoutAProxyIsLocalOnly(t *testing.T) { // TestRegistryAddHoldsUntilClose covers the local half: this process's registry resolves what was // added to it, and stops doing so once it closes. func TestRegistryAddHoldsUntilClose(t *testing.T) { - reg, err := newRegistry(logger.Test(t), capabilityConfig{}, testGRPC) + reg, err := newRegistry(logger.Test(t), CapabilitiesConfig{}, testGRPC) require.NoError(t, err) require.NoError(t, reg.Start(t.Context())) @@ -98,7 +98,7 @@ func TestRegistryAddHoldsUntilClose(t *testing.T) { func TestRegistryAddAnnouncesToTheNode(t *testing.T) { node := newSpyRegistry(t) - reg, err := newRegistry(logger.Test(t), capabilityConfig{ProxyURL: serveNodeRegistry(t, node)}, testGRPC) + reg, err := newRegistry(logger.Test(t), CapabilitiesConfig{ProxyURL: serveNodeRegistry(t, node)}, testGRPC) require.NoError(t, err) require.NoError(t, reg.Start(t.Context())) diff --git a/libs/capability/run.go b/libs/capability/run.go index eaedfa75d..e28893146 100644 --- a/libs/capability/run.go +++ b/libs/capability/run.go @@ -48,7 +48,6 @@ func RunErr(ctx context.Context, lggr logger.Logger, constructor func(do.Injecto Use: filepath.Base(os.Args[0]), Short: "A CRE capability", } - root.PersistentFlags().String("config", "", "Path to config file") lggr = logger.Named(lggr, root.Name()) @@ -56,7 +55,7 @@ func RunErr(ctx context.Context, lggr logger.Logger, constructor func(do.Injecto for _, opt := range opts { opt(cfg) } - if err := cfg.bind(root); err != nil { + if _, err := cfg.bind(root); err != nil { return err } diff --git a/libs/capability/settings.go b/libs/capability/settings.go index 4d2fefc07..c8e768def 100644 --- a/libs/capability/settings.go +++ b/libs/capability/settings.go @@ -21,8 +21,11 @@ import ( "github.com/smartcontractkit/chainlink-common/pkg/types/core" ) -type settingsConfig struct { - URL string `usage:"gRPC target of the host's CRE settings service (e.g. localhost:9001), which pushes settings updates to this process. Unset leaves every limit at its compiled-in default"` +// SettingsConfig points this process at the host's CRE settings service. +type SettingsConfig struct { + // URL is the gRPC target of the host's CRE settings service (e.g. localhost:9001), which pushes + // settings updates to this process. Unset leaves every limit at its compiled-in default. + URL string } // settingsService holds this process's CRE settings and, when the host serves them, keeps them @@ -35,7 +38,7 @@ type settingsService struct { conn *grpc.ClientConn } -func newSettingsService(lggr logger.Logger, cfg settingsConfig) (*settingsService, error) { +func newSettingsService(lggr logger.Logger, cfg SettingsConfig) (*settingsService, error) { s := &settingsService{values: newSettings(lggr)} if cfg.URL != "" { conn, err := dial(cfg.URL) @@ -48,7 +51,7 @@ func newSettingsService(lggr logger.Logger, cfg settingsConfig) (*settingsServic return s, nil } -func startSettings(ctx context.Context, lggr logger.Logger, cfg settingsConfig) (*settingsService, error) { +func startSettings(ctx context.Context, lggr logger.Logger, cfg SettingsConfig) (*settingsService, error) { s, err := newSettingsService(lggr, cfg) if err != nil { return nil, err diff --git a/libs/capability/settings_test.go b/libs/capability/settings_test.go index 0c5b32bdf..5ca23b3d3 100644 --- a/libs/capability/settings_test.go +++ b/libs/capability/settings_test.go @@ -134,7 +134,7 @@ func (f *fakeBroadcaster) closeStream() { // TestSettingsServiceWithoutAURL covers a run the host pushes no settings to: it starts and stops // cleanly, and every limit stays at its compiled-in default. func TestSettingsServiceWithoutAURL(t *testing.T) { - s, err := startSettings(t.Context(), logger.Test(t), settingsConfig{}) + s, err := startSettings(t.Context(), logger.Test(t), SettingsConfig{}) require.NoError(t, err) assert.Nil(t, s.conn) require.NoError(t, s.Close()) @@ -151,7 +151,7 @@ func TestSettingsServiceFollowsTheHost(t *testing.T) { go func() { _ = srv.Serve(l) }() t.Cleanup(srv.Stop) - s, err := startSettings(t.Context(), logger.Test(t), settingsConfig{URL: l.Addr().String()}) + s, err := startSettings(t.Context(), logger.Test(t), SettingsConfig{URL: l.Addr().String()}) require.NoError(t, err) t.Cleanup(func() { require.NoError(t, s.Close()) }) diff --git a/libs/capability/telemetry.go b/libs/capability/telemetry.go index f78d70548..79bb20781 100644 --- a/libs/capability/telemetry.go +++ b/libs/capability/telemetry.go @@ -27,35 +27,49 @@ const ( // TelemetryConfig is the beholder client's configuration: an empty Endpoint leaves telemetry off, and the global noop client in place, so // instruments created by services record nothing. type TelemetryConfig struct { - Endpoint string `usage:"OTLP gRPC endpoint telemetry is exported to; telemetry is disabled when unset"` - InsecureConnection bool `usage:"export telemetry over an insecure connection"` - CACertFile string `usage:"CA certificate file used to verify the telemetry endpoint"` - - // Attributes and AuthHeaders are key=value pairs ("env=staging") rather than maps, since - // flags cannot bind a map field. Entries from the legacy CL_TELEMETRY_ATTRIBUTE_ and - // CL_TELEMETRY_AUTH_HEADER_ env vars are merged in on top of these, so a plugin host - // setting one env var per entry keeps working. - Attributes []string `usage:"extra telemetry resource attributes, as key=value pairs" example:"['env=staging']"` - AuthHeaders []string `usage:"telemetry auth headers, as key=value pairs" flagdocs:"noexample"` - - AuthPubKeyHex string `usage:"public key the telemetry auth headers are derived from"` - AuthHeadersTTL commonconfig.Duration `usage:"how long generated telemetry auth headers are valid for"` - PrometheusBridgeEnabled bool `usage:"feed metrics registered on the prometheus registry into the telemetry pipeline"` + // Endpoint is the OTLP gRPC endpoint telemetry is exported to. Telemetry is disabled when unset. + Endpoint string + // InsecureConnection exports telemetry over an insecure connection. + InsecureConnection bool + // CACertFile is the CA certificate file used to verify the telemetry endpoint. + CACertFile string + + // Attributes and AuthHeaders are key=value pairs ("env=staging") rather than maps. Entries from + // the legacy CL_TELEMETRY_ATTRIBUTE_ and CL_TELEMETRY_AUTH_HEADER_ env vars are merged + // in on top of these, so a plugin host setting one env var per entry keeps working. + + // Attributes are extra telemetry resource attributes, as key=value pairs. + Attributes []string + // AuthHeaders are telemetry auth headers, as key=value pairs. + AuthHeaders []string + + // AuthPubKeyHex is the public key the telemetry auth headers are derived from. + AuthPubKeyHex string + // AuthHeadersTTL is how long generated telemetry auth headers are valid for. + AuthHeadersTTL commonconfig.Duration + // PrometheusBridgeEnabled feeds metrics registered on the prometheus registry into the telemetry + // pipeline. + PrometheusBridgeEnabled bool } // TracingConfig is the OTLP tracing configuration. Traces go to the telemetry endpoint, so Enabled // does nothing unless TelemetryConfig.Endpoint is set too. type TracingConfig struct { - Enabled bool `usage:"export traces to the telemetry endpoint"` - SamplingRatio float64 `usage:"fraction of traces sampled, from 0 to 1"` - TLSCertFile string `usage:"TLS certificate file used by the trace exporter"` + // Enabled exports traces to the telemetry endpoint. + Enabled bool + // SamplingRatio is the fraction of traces sampled, from 0 to 1. + SamplingRatio float64 + // TLSCertFile is the TLS certificate file used by the trace exporter. + TLSCertFile string } // ChipIngressConfig points the beholder client's chip ingress emitter at an endpoint. Emitting is // enabled by setting one. type ChipIngressConfig struct { - Endpoint string `usage:"chip ingress gRPC endpoint; the emitter is disabled when unset"` - InsecureConnection bool `usage:"connect to chip ingress over an insecure connection"` + // Endpoint is the chip ingress gRPC endpoint. The emitter is disabled when unset. + Endpoint string + // InsecureConnection connects to chip ingress over an insecure connection. + InsecureConnection bool } // newTelemetry builds the beholder client and the service that owns it. diff --git a/libs/capability/telemetry_test.go b/libs/capability/telemetry_test.go index bc38f1b5a..007268c2a 100644 --- a/libs/capability/telemetry_test.go +++ b/libs/capability/telemetry_test.go @@ -25,14 +25,22 @@ func settingsRoot(t *testing.T) (*cobra.Command, *config) { t.Helper() root := &cobra.Command{Use: "cron"} - root.PersistentFlags().String("config", "", "Path to config file") // The production defaults, so what these read is what a binary gets rather than a zero value. cfg := defaultConfig() - require.NoError(t, cfg.bind(root)) + _, err := cfg.bind(root) + require.NoError(t, err) return root, cfg } +// TestEveryFlagIsDocumented fails when a config field has no doc comment, or doccomments_gen.go is +// stale: run go generate. +func TestEveryFlagIsDocumented(t *testing.T) { + b, err := defaultConfig().bind(&cobra.Command{Use: "cron"}) + require.NoError(t, err) + require.Empty(t, b.Undocumented()) +} + // decoded runs args through the command tree and returns the settings as they were decoded. func decoded(t *testing.T, args ...string) config { t.Helper() diff --git a/libs/capability/webserver.go b/libs/capability/webserver.go index 32261e198..946343451 100644 --- a/libs/capability/webserver.go +++ b/libs/capability/webserver.go @@ -26,7 +26,8 @@ const defaultHTTPPort = 8080 // routes a service registers on the mux while it is being built - it is not only prometheus's, so // it is named for the transport it serves rather than for one of its handlers. type HTTPConfig struct { - Port uint16 `usage:"port serving /metrics, /debug/pprof, /healthz, /readyz and any routes a service registers"` + // Port serves /metrics, /debug/pprof, /healthz, /readyz and any routes a service registers. + Port uint16 } // newWebServer returns the shared HTTP server as a service. diff --git a/libs/go.mod b/libs/go.mod index 2f9ef087b..13bee6363 100644 --- a/libs/go.mod +++ b/libs/go.mod @@ -4,21 +4,18 @@ go 1.27.1 require ( github.com/cenkalti/backoff/v5 v5.0.3 - github.com/go-viper/mapstructure/v2 v2.5.0 github.com/google/go-cmp v0.7.0 github.com/google/uuid v1.6.0 github.com/grafana/pyroscope-go v1.2.8 github.com/hashicorp/go-plugin v1.8.0 - github.com/iancoleman/strcase v0.3.0 github.com/prometheus/client_golang v1.23.2 github.com/samber/do/v2 v2.1.0 github.com/shopspring/decimal v1.4.0 github.com/smartcontractkit/chainlink-common v0.11.2-0.20261005132539-b7d63d5fd549 + github.com/smartcontractkit/chainlink-common/x/config v0.0.0-20261014172408-e5560b3eeb33 github.com/smartcontractkit/chainlink-protos/cre/go v0.0.0-20260804191526-b7a850ae7648 github.com/smartcontractkit/libocr v0.0.0-20260810200708-618b5bf7f342 github.com/spf13/cobra v1.10.2 - github.com/spf13/pflag v1.0.10 - github.com/spf13/viper v1.17.0 github.com/stretchr/testify v1.12.1 go.opentelemetry.io/contrib/bridges/prometheus v0.68.0 go.opentelemetry.io/otel v1.46.0 @@ -49,6 +46,7 @@ require ( github.com/go-playground/locales v0.14.1 // indirect github.com/go-playground/universal-translator v0.18.1 // indirect github.com/go-playground/validator/v10 v10.26.0 // indirect + github.com/go-viper/mapstructure/v2 v2.5.0 // indirect github.com/golang/protobuf v1.5.4 // indirect github.com/grafana/otel-profiling-go v0.5.1 // indirect github.com/grafana/pyroscope-go/godeltaprof v0.1.9 // indirect @@ -56,8 +54,9 @@ require ( github.com/grpc-ecosystem/go-grpc-middleware/v2 v2.3.2 // indirect github.com/grpc-ecosystem/grpc-gateway/v2 v2.30.0 // indirect github.com/hashicorp/go-hclog v1.6.3 // indirect - github.com/hashicorp/hcl v1.0.0 // indirect github.com/hashicorp/yamux v0.1.2 // indirect + github.com/huandu/go-clone v1.7.3 // indirect + github.com/iancoleman/strcase v0.3.0 // indirect github.com/inconshreveable/mousetrap v1.1.0 // indirect github.com/invopop/jsonschema v0.13.0 // indirect github.com/jackc/pgpassfile v1.0.0 // indirect @@ -73,11 +72,9 @@ require ( github.com/klauspost/compress v1.18.5 // indirect github.com/leodido/go-urn v1.4.0 // indirect github.com/lib/pq v1.10.9 // indirect - github.com/magiconair/properties v1.8.7 // indirect github.com/mailru/easyjson v0.9.0 // indirect github.com/mattn/go-colorable v0.1.14 // indirect github.com/mattn/go-isatty v0.0.21 // indirect - github.com/mitchellh/mapstructure v1.5.0 // indirect github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect github.com/modern-go/reflect2 v1.0.2 // indirect github.com/mr-tron/base58 v1.2.0 // indirect @@ -89,8 +86,6 @@ require ( github.com/prometheus/client_model v0.6.2 // indirect github.com/prometheus/common v0.67.5 // indirect github.com/prometheus/procfs v0.20.1 // indirect - github.com/sagikazarmark/locafero v0.11.0 // indirect - github.com/sagikazarmark/slog-shim v0.1.0 // indirect github.com/samber/go-type-to-string v1.8.0 // indirect github.com/santhosh-tekuri/jsonschema/v5 v5.3.1 // indirect github.com/scylladb/go-reflectx v1.0.1 // indirect @@ -101,13 +96,10 @@ require ( github.com/smartcontractkit/chainlink-protos/node-platform v0.0.0-20260709145319-7782fb89eb16 // indirect github.com/smartcontractkit/freeport v0.1.3-0.20250716200817-cb5dfd0e369e // indirect github.com/smartcontractkit/grpc-proxy v0.0.0-20240830132753-a7e17fec5ab7 // indirect - github.com/sourcegraph/conc v0.3.1-0.20240121214520-5f936abd7ae8 // indirect - github.com/spf13/afero v1.15.0 // indirect - github.com/spf13/cast v1.5.1 // indirect + github.com/spf13/pflag v1.0.10 // indirect github.com/stellar/go-stellar-sdk v0.4.0 // indirect github.com/stellar/go-xdr v0.0.0-20260312225820-cc2b0611aabf // indirect github.com/stretchr/objx v0.5.3 // indirect - github.com/subosito/gotenv v1.6.0 // indirect github.com/wk8/go-ordered-map/v2 v2.1.8 // indirect github.com/x448/float16 v0.8.4 // indirect go.opentelemetry.io/auto/sdk v1.2.1 // indirect @@ -133,12 +125,13 @@ require ( go.yaml.in/yaml/v3 v3.0.5 // indirect golang.org/x/crypto v0.55.0 // indirect golang.org/x/exp v0.0.0-20260508232706-74f9aab9d74a // indirect + golang.org/x/mod v0.38.0 // indirect golang.org/x/net v0.58.0 // indirect golang.org/x/sync v0.22.0 // indirect golang.org/x/sys v0.47.0 // indirect golang.org/x/text v0.41.0 // indirect golang.org/x/time v0.15.0 // indirect + golang.org/x/tools v0.48.0 // indirect google.golang.org/genproto/googleapis/api v0.0.0-20260819154853-08b0e4226688 // indirect - gopkg.in/ini.v1 v1.67.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect ) diff --git a/libs/go.sum b/libs/go.sum index 9843ca43e..46ed662f7 100644 --- a/libs/go.sum +++ b/libs/go.sum @@ -37,8 +37,6 @@ github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7 github.com/fatih/color v1.13.0/go.mod h1:kLAiJbzzSOZDVNGyDpeOxJ47H46qBXwg5ILebYFFOfk= github.com/fatih/color v1.18.0 h1:S8gINlzdQ840/4pfAwic/ZE0djQEH3wM94VfqLTZcOM= github.com/fatih/color v1.18.0/go.mod h1:4FelSpRwEGDpQ12mAdzqdOukCy4u8WUtOY6lkT/6HfU= -github.com/frankban/quicktest v1.14.4 h1:g2rn0vABPOOXmZUj+vbmUp0lPoXEMuhTpIluN0XL9UY= -github.com/frankban/quicktest v1.14.4/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0= github.com/fsnotify/fsnotify v1.9.0 h1:2Ml+OJNzbYCTzsxtv8vKSFD9PbJjmhYF14k/jKC7S9k= github.com/fsnotify/fsnotify v1.9.0/go.mod h1:8jBTzvmWwFyi3Pb8djgCCO5IBqzKJ/Jwo8TRcHyHii0= github.com/fxamacker/cbor/v2 v2.9.0 h1:NpKPmjDBgUfBms6tr6JZkTHtfFGcMKsw3eGcmD/sapM= @@ -114,10 +112,12 @@ github.com/hashicorp/go-hclog v1.6.3 h1:Qr2kF+eVWjTiYmU7Y31tYlP1h0q/X3Nl3tPGdaB1 github.com/hashicorp/go-hclog v1.6.3/go.mod h1:W4Qnvbt70Wk/zYJryRzDRU/4r0kIg0PVHBcfoyhpF5M= github.com/hashicorp/go-plugin v1.8.0 h1:ie8S6RRY8RvB2usYZv+AAZ/wBvx2AU5p5QeP5j/FORs= github.com/hashicorp/go-plugin v1.8.0/go.mod h1:BExt6KEaIYx804z8k4gRzRLEvxKVb+kn0NMcihqOqb8= -github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4= -github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ= github.com/hashicorp/yamux v0.1.2 h1:XtB8kyFOyHXYVFnwT5C3+Bdo8gArse7j2AQ0DA0Uey8= github.com/hashicorp/yamux v0.1.2/go.mod h1:C+zze2n6e/7wshOZep2A70/aQU6QBRWJO/G6FT1wIns= +github.com/huandu/go-assert v1.1.5 h1:fjemmA7sSfYHJD7CUqs9qTwwfdNAx7/j2/ZlHXzNB3c= +github.com/huandu/go-assert v1.1.5/go.mod h1:yOLvuqZwmcHIC5rIzrBhT7D3Q9c3GFnd0JrPVhn/06U= +github.com/huandu/go-clone v1.7.3 h1:rtQODA+ABThEn6J5LBTppJfKmZy/FwfpMUWa8d01TTQ= +github.com/huandu/go-clone v1.7.3/go.mod h1:ReGivhG6op3GYr+UY3lS6mxjKp7MIGTknuU5TbTVaXE= github.com/iancoleman/strcase v0.3.0 h1:nTXanmYxhfFAMjZL34Ov6gkzEsSJZ5DbhxWjvSASxEI= github.com/iancoleman/strcase v0.3.0/go.mod h1:iwCmte+B7n89clKwxIoIXy/HfoL7AsD47ZCWhYzw7ho= github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8= @@ -159,8 +159,6 @@ github.com/leodido/go-urn v1.4.0 h1:WT9HwE9SGECu3lg4d/dIA+jxlljEa1/ffXKmRjqdmIQ= github.com/leodido/go-urn v1.4.0/go.mod h1:bvxc+MVxLKB4z00jd1z+Dvzr47oO32F/QSNjSBOlFxI= github.com/lib/pq v1.10.9 h1:YXG7RB+JIjhP29X+OtkiDnYaXQwpS4JEWq7dtCCRUEw= github.com/lib/pq v1.10.9/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o= -github.com/magiconair/properties v1.8.7 h1:IeQXZAiQcpL9mgcAe1Nu6cX9LLw6ExEHKjN0VQdvPDY= -github.com/magiconair/properties v1.8.7/go.mod h1:Dhd985XPs7jluiymwWYZ0G4Z61jb3vdS329zhj2hYo0= github.com/mailru/easyjson v0.9.0 h1:PrnmzHw7262yW8sTBwxi1PdJA3Iw/EKBa8psRf7d9a4= github.com/mailru/easyjson v0.9.0/go.mod h1:1+xMtQp2MRNVL/V1bOzuP3aP8VNwRW55fQUto+XFtTU= github.com/marcboeker/go-duckdb v1.8.5 h1:tkYp+TANippy0DaIOP5OEfBEwbUINqiFqgwMQ44jME0= @@ -176,8 +174,6 @@ github.com/mattn/go-isatty v0.0.21/go.mod h1:ZXfXG4SQHsB/w3ZeOYbR0PrPwLy+n6xiMrJ github.com/mattn/go-sqlite3 v1.14.22/go.mod h1:Uh1q+B4BYcTPb+yiD3kU8Ct7aC0hY9fxUwlHK0RXw+Y= github.com/mattn/go-sqlite3 v2.0.3+incompatible h1:gXHsfypPkaMZrKbD5209QV9jbUTJKjyR5WD3HYQSd+U= github.com/mattn/go-sqlite3 v2.0.3+incompatible/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc= -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 h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= @@ -216,10 +212,6 @@ github.com/prometheus/procfs v0.20.1/go.mod h1:o9EMBZGRyvDrSPH1RqdxhojkuXstoe4Ul 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.11.0 h1:1iurJgmM9G3PA/I+wWYIOw/5SyBtxapeHDcg+AAIFXc= -github.com/sagikazarmark/locafero v0.11.0/go.mod h1:nVIGvgyzw595SUSUE6tvCp3YYTeHs15MvlmU87WwIik= -github.com/sagikazarmark/slog-shim v0.1.0 h1:diDBnUNK9N/354PgrxMywXnAwEr1QZcOr6gto+ugjYE= -github.com/sagikazarmark/slog-shim v0.1.0/go.mod h1:SrcSrq8aKtyuqEI1uvTDTK1arOWRIczQRv+GVI1AkeQ= github.com/samber/do/v2 v2.1.0 h1:lqCHn05XvY3VqwxvZDQPSkH+jIGWSVHUrSVLEbPOopo= github.com/samber/do/v2 v2.1.0/go.mod h1:wJBoiaZcUZyGuraOhfz15b517ZMogGs+U03DvnqvT6Q= github.com/samber/go-type-to-string v1.8.0 h1:5z6tDTjtXxkIAoAuHAZYMYR8mkBZjVgeSH7jcSLqc8w= @@ -236,6 +228,8 @@ github.com/smartcontractkit/chainlink-common v0.11.2-0.20261005132539-b7d63d5fd5 github.com/smartcontractkit/chainlink-common v0.11.2-0.20261005132539-b7d63d5fd549/go.mod h1:/i+QJhDWFoBoGMOO7H466Jo7dLj9YrRsBbd5V/fu128= github.com/smartcontractkit/chainlink-common/pkg/chipingress v0.0.11-0.20261005132539-b7d63d5fd549 h1:J4vYRJxkBO8Yf//17wScKjYWmS45VZaFWx0zt+4ETEU= github.com/smartcontractkit/chainlink-common/pkg/chipingress v0.0.11-0.20261005132539-b7d63d5fd549/go.mod h1:Scyk7O80fde5rJF3Ty3fEE3xiS6j84eWbu8/2gl3pIY= +github.com/smartcontractkit/chainlink-common/x/config v0.0.0-20261014172408-e5560b3eeb33 h1:9aS43MBP2grzsxpppcqosVkaeKFEfqfcIG2puIec3y4= +github.com/smartcontractkit/chainlink-common/x/config v0.0.0-20261014172408-e5560b3eeb33/go.mod h1:BseXIPMhfBpV2z/S+tzEFI10LbAzr7HfRIEnPQE1QQY= github.com/smartcontractkit/chainlink-protos/cre/go v0.0.0-20260804191526-b7a850ae7648 h1:WEUMkKQPAgcNMRgES6CBWrRUiII+HKEWQjulKQBSuMA= github.com/smartcontractkit/chainlink-protos/cre/go v0.0.0-20260804191526-b7a850ae7648/go.mod h1:/i8hjTPFdVWHiY+QjeSiVS2Z3GB3WAZznGgXHstC02E= github.com/smartcontractkit/chainlink-protos/linking-service/go v0.0.0-20251002192024-d2ad9222409b h1:QuI6SmQFK/zyUlVWEf0GMkiUYBPY4lssn26nKSd/bOM= @@ -250,19 +244,11 @@ github.com/smartcontractkit/grpc-proxy v0.0.0-20240830132753-a7e17fec5ab7 h1:12i github.com/smartcontractkit/grpc-proxy v0.0.0-20240830132753-a7e17fec5ab7/go.mod h1:FX7/bVdoep147QQhsOPkYsPEXhGZjeYx6lBSaSXtZOA= github.com/smartcontractkit/libocr v0.0.0-20260810200708-618b5bf7f342 h1:pEcgcjTGA83MzpqbTbyIg9AJrOs62s77SooDdJGIg9w= github.com/smartcontractkit/libocr v0.0.0-20260810200708-618b5bf7f342/go.mod h1:5JPtsRwjugpyfsdEALC4RopfvohqK/G+3DHaR8uv+Bc= -github.com/sourcegraph/conc v0.3.1-0.20240121214520-5f936abd7ae8 h1:+jumHNA0Wrelhe64i8F6HNlS8pkoyMv5sreGx2Ry5Rw= -github.com/sourcegraph/conc v0.3.1-0.20240121214520-5f936abd7ae8/go.mod h1:3n1Cwaq1E1/1lhQhtRK2ts/ZwZEhjcQeJQ1RuC6Q/8U= -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.5.1 h1:R+kOtfhWQE6TVQzY+4D7wJLBgkdVasCEFxSUBYBYIlA= -github.com/spf13/cast v1.5.1/go.mod h1:b9PdjNptOpzXr7Rq1q9gJML/2cdGQAo69NKzQ10KN48= 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.17.0 h1:I5txKw7MJasPL/BrfkbA0Jyo/oELqVmux4pR/UxOMfI= -github.com/spf13/viper v1.17.0/go.mod h1:BmMMMLQXSbcHK6KAOiFLz0l5JHrU89OdIRHvsk0+yVI= github.com/stellar/go-stellar-sdk v0.4.0 h1:WBLeJk7EllU7WhwrTH1L5Mu2EhZor/xy5sVi/82JzNQ= github.com/stellar/go-stellar-sdk v0.4.0/go.mod h1:tLKAQPxa2I5UvGMabBbUXcY3fmgYnfDudrMeK7CDX4w= github.com/stellar/go-xdr v0.0.0-20260312225820-cc2b0611aabf h1:GY1RVbX3Hg7poPXEf6yojjP0hyypvgUgZmCqQU9D0xg= @@ -273,6 +259,7 @@ github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpE github.com/stretchr/objx v0.5.3 h1:jmXUvGomnU1o3W/V5h2VEradbpJDwGrzugQQvL0POH4= github.com/stretchr/objx v0.5.3/go.mod h1:rDQraq+vQZU7Fde9LOZLr8Tax6zZvy4kuNKF+QYS+U0= 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.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= @@ -281,8 +268,6 @@ github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= github.com/stretchr/testify v1.12.1 h1:EuwCh5fleGS7H32xRwO3wRGT7DxrDhLAT6FF8MpWDWE= github.com/stretchr/testify v1.12.1/go.mod h1:MDEgiDPPsNp5cuIrHPPCyornHKgEVbtFUmoNlxoYthg= -github.com/subosito/gotenv v1.6.0 h1:9NlTDc1FTs4qu0DDq7AEtTPNw6SVm7uBMsUCUjABIf8= -github.com/subosito/gotenv v1.6.0/go.mod h1:Dk4QP5c2W3ibzajGcXpNraDfq2IrhjMIvMSWPKKo0FU= github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw= github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc= github.com/wk8/go-ordered-map/v2 v2.1.8 h1:5h/BUHu93oj4gIdvHHHGsScSTMijfx5PeYkE/fJgbpc= @@ -470,8 +455,6 @@ google.golang.org/protobuf v1.36.12/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j 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/ini.v1 v1.67.0 h1:Dgnx+6+nfE+IfzjUEISNeydPJh9AXNNsWbGP9KzCsOA= -gopkg.in/ini.v1 v1.67.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= 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.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=