Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 3 additions & 8 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -3,16 +3,11 @@ module github.com/smartcontractkit/capabilities
go 1.27.1

require (
github.com/stretchr/testify v1.11.1
github.com/stretchr/testify v1.12.1
google.golang.org/protobuf v1.36.11
)

require (
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect
github.com/kr/pretty v0.3.1 // indirect
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect
github.com/rogpeppe/go-internal v1.14.1 // indirect
github.com/stretchr/objx v0.5.2 // indirect
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
github.com/stretchr/objx v0.5.3 // indirect
go.yaml.in/yaml/v3 v3.0.5 // indirect
)
31 changes: 6 additions & 25 deletions go.sum

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

286 changes: 286 additions & 0 deletions libs/capability/AGENTS.md

Large diffs are not rendered by default.

56 changes: 6 additions & 50 deletions libs/capability/capability.go
Original file line number Diff line number Diff line change
@@ -1,65 +1,21 @@
package capability

import (
"reflect"

"google.golang.org/protobuf/reflect/protoreflect"

"github.com/smartcontractkit/chainlink-common/pkg/capabilities"
"github.com/smartcontractkit/chainlink-common/pkg/logger"
"github.com/smartcontractkit/chainlink-common/pkg/services"
"github.com/smartcontractkit/chainlink-common/pkg/settings/limits"
"github.com/smartcontractkit/chainlink-common/pkg/types/core"
)

type Capability interface {
services.Service
capabilities.ExecutableAndTriggerCapability

// Service is the proto service this capability's server was generated from.
Service() protoreflect.ServiceDescriptor
}

// Config marks the struct a constructor asks for as the capability's own configuration
//
// type Config struct {
// capability.Config
// FastestScheduleIntervalSeconds int `usage:"fastest cron schedule a workflow may register"`
// }
//
// The run binds the struct's fields as flags on the root command, namespaced by the binary's
// name - cron's are --cron.fastest-schedule-interval-seconds, CRE_CRON_FASTEST_SCHEDULE_INTERVAL_SECONDS,
// or the same key in the config file - and hands the constructor the decoded value. A constructor
// declares at most one.
type Config struct{}

// 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"`
}

type Dependencies struct {
Logger logger.Logger
CapabilityRegistry core.CapabilitiesRegistry
LimitsFactory limits.Factory
}

func (d Dependencies) list() []any {
return []any{d.Logger, d.CapabilityRegistry, d.LimitsFactory}
}

func (d Dependencies) resolve(want reflect.Type) (reflect.Value, bool) {
for _, v := range d.list() {
if v == nil {
continue
}
if got := reflect.TypeOf(v); got.AssignableTo(want) {
return reflect.ValueOf(v), true
}
}
return reflect.Value{}, false
// 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
}
64 changes: 26 additions & 38 deletions libs/capability/config.go
Original file line number Diff line number Diff line change
@@ -1,10 +1,8 @@
package capability

import (
"fmt"
//go:generate go run ./internal/docgen docs

"github.com/spf13/cobra"
)
import "github.com/samber/do/v2"

const capabilitiesNamespace = "capabilities"

Expand All @@ -14,41 +12,31 @@ const settingsNamespace = "settings"

const defaultHost = "localhost"

// grpcConfig is where the gRPC listener the registry serves this process's capabilities on binds.
type grpcConfig struct {
AdvertiseHost string `usage:"host the gRPC servers this process opens bind to and are advertised at; empty binds every interface"`
// 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
}

func defaultConfig() *config {
return &config{
observability: *defaultObservability(),
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)
}
}
return nil
// registerSettings registers the run's own settings, as any dependency registers its config, for
// RunErr to bind and start to resolve. capabilities differs between the commands: embed's has no
// proxy to dial and defaults the DON, and capabilityConfig is built from it.
func registerSettings() {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

rename settings -> config -- settings refers to cre settings specifically

ProvideConfig(telemetryNamespace, TelemetryConfig{})
ProvideConfig(tracingNamespace, TracingConfig{SamplingRatio: 1})
ProvideConfig(chipIngressNamespace, ChipIngressConfig{})
ProvideConfig(pyroscopeNamespace, PyroscopeConfig{})
ProvideConfig(httpNamespace, HTTPConfig{Port: defaultHTTPPort})
ProvideConfig(grpcNamespace, grpcConfig{AdvertiseHost: defaultHost})
ProvideConfig(settingsNamespace, settingsConfig{})

ProvideConfigForRun(capabilitiesNamespace, capabilityConfig{})

ProvideConfigForEmbed("", embedConfig{Instances: 1})
ProvideConfigForEmbed(capabilitiesNamespace, embeddedCapabilityConfig{CapabilityDonID: defaultEmbeddedDonID})
ProvideForEmbed(func(i do.Injector) (capabilityConfig, error) {
cfg, err := do.Invoke[embeddedCapabilityConfig](i)
return capabilityConfig{CapabilityDonID: cfg.CapabilityDonID}, err
})
}
78 changes: 78 additions & 0 deletions libs/capability/doccomments_gen.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

52 changes: 52 additions & 0 deletions libs/capability/docs.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
package capability

import (
"maps"
"slices"

"github.com/spf13/cobra"

"github.com/smartcontractkit/chainlink-common/x/config/commentparsing"
"github.com/smartcontractkit/chainlink-common/x/config/markup/tomlmarkup"
)

// docsCommand generates the DocComments every config bound on the binary needs for its flags' help
// text, so a capability's own config is documented by running its binary rather than by a
// generator of its own:
//
// //go:generate go run . docs
//
// It writes a doccomments_gen.go beside each config struct - registered with ProvideConfig, by this
// package or any dependency - declared in the module enclosing the working directory. A struct from another module - this package's sections, when a capability
// binary runs it - is read from the DocComments that module already compiled in, so this package
// generates its own too (see internal/docgen).
//
// The binder decodes before any command runs, docs included, so a config with a required field
// has to be satisfied to generate its docs.
//
// It documents what both commands register: each registers configs the other does not -
// capabilities differs between them, say.
func docsCommand() *cobra.Command {
return &cobra.Command{
Use: "docs",
Short: "Generate the doc comments the config's help text is read from",
Args: cobra.NoArgs,
RunE: func(cmd *cobra.Command, _ []string) error {
var roots []any
for _, r := range []*registrations{runRegistrations, embedRegistrations} {
for _, namespace := range slices.Sorted(maps.Keys(r.configs)) {
roots = append(roots, r.configs[namespace].value)
}
}

return commentparsing.Run(commentparsing.RunArgs{
Roots: roots,
Markup: tomlmarkup.New(),
// Per binary: a run removes files its Tool wrote and did not produce this time, so
// two binaries in one module sharing a name would delete each other's.
Tool: cmd.Root().Name() + " docs",
LocalPrefix: "github.com/smartcontractkit",
})
},
}
}
20 changes: 20 additions & 0 deletions libs/capability/embed.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
package capability

// embedConfig is the embed command's own config, registered on that command alone.
type embedConfig struct {
// Instances is how many instances of the capability this process hosts.
Instances int `validate:"min=1"`
}

// defaultEmbeddedDonID is the DON an embedded run reports. An embed run is one DON in one process,
// so there is only ever the one, and requiring it to be configured would mean typing the same
// number into every embedded run.
const defaultEmbeddedDonID = 1

// embeddedCapabilityConfig is capabilityConfig as an embed run registers it, replacing run's under
// the same namespace. It keeps the DON ID and drops the proxy URL: there is no node behind an
// embedded run to dial, so only what this binary hosts resolves.
type embeddedCapabilityConfig struct {

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

?? it's this hard-coded to the same value every time? Do we need it?

// CapabilityDonID is the on-chain DON ID reported for the capabilities this process hosts.
CapabilityDonID uint32
}
Loading
Loading