Skip to content

Commit cf4cb13

Browse files
authored
feat: add network config to cld engine (#246)
This adds the new structures to represent network configuration. Configuration is loaded from a YAML file, and can be filtered and transformed. These networks are used to configure the CLD environment.
1 parent e45f09f commit cf4cb13

File tree

8 files changed

+1363
-1
lines changed

8 files changed

+1363
-1
lines changed

.changeset/public-flowers-bet.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"chainlink-deployments-framework": minor
3+
---
4+
5+
Adds a network configuration package to the CLD engine

engine/cld/network/config.go

Lines changed: 188 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,188 @@
1+
package network
2+
3+
import (
4+
"fmt"
5+
"maps"
6+
"os"
7+
"slices"
8+
9+
"gopkg.in/yaml.v3"
10+
)
11+
12+
// Manifest is the YAML representation of network configuration.
13+
type Manifest struct {
14+
// A YAML array of networks.
15+
Networks []Network `yaml:"networks"`
16+
}
17+
18+
// Config represents the configuration of a collection of networks. This is loaded from the YAML
19+
// manifest file/s.
20+
type Config struct {
21+
// networks is a map of networks by their chain selector. This differs from the manifest
22+
// representation of the networks so that we can ensure uniqueness and quickly lookup a network
23+
// by its chain selector.
24+
networks map[uint64]Network
25+
}
26+
27+
// NewConfig creates a new config from a slice of networks. Any duplicate chain selectors will
28+
// be overwritten.
29+
func NewConfig(networks []Network) *Config {
30+
nmap := make(map[uint64]Network)
31+
32+
for _, network := range networks {
33+
nmap[network.ChainSelector] = network
34+
}
35+
36+
return &Config{
37+
networks: nmap,
38+
}
39+
}
40+
41+
// Networks returns a slice of all networks in the config.
42+
func (c *Config) Networks() []Network {
43+
return slices.Collect(maps.Values(c.networks))
44+
}
45+
46+
// NetworkBySelector retrieves a network by its chain selector. If the network is not found, an
47+
// error is returned.
48+
func (c *Config) NetworkBySelector(selector uint64) (Network, error) {
49+
network, ok := c.networks[selector]
50+
if !ok {
51+
return Network{}, fmt.Errorf("network with selector %d not found in configuration", selector)
52+
}
53+
54+
return network, nil
55+
}
56+
57+
// ChainSelectors returns a slice of all chain selectors from the Config.
58+
func (c *Config) ChainSelectors() []uint64 {
59+
return slices.Collect(maps.Keys(c.networks))
60+
}
61+
62+
// Merge merges another config into the current config.
63+
// It overwrites any networks with the same chain selector.
64+
func (c *Config) Merge(other *Config) {
65+
maps.Copy(c.networks, other.networks)
66+
}
67+
68+
// MarshalYAML implements the yaml.Marshaler interface for the Config struct.
69+
// It converts the internal map structure to a YAML format with a top-level "networks" key.
70+
func (c *Config) MarshalYAML() (any, error) {
71+
node := Manifest{
72+
Networks: c.Networks(),
73+
}
74+
75+
return node, nil
76+
}
77+
78+
// UnmarshalYAML implements the yaml.Unmarshaler interface for the Config struct.
79+
func (c *Config) UnmarshalYAML(value *yaml.Node) error {
80+
node := Manifest{}
81+
82+
if err := value.Decode(&node); err != nil {
83+
return err
84+
}
85+
86+
*c = *NewConfig(node.Networks)
87+
88+
return nil
89+
}
90+
91+
// NetworkFilter defines a function type that filters networks based on certain criteria.
92+
type NetworkFilter func(Network) bool
93+
94+
// FilterWith returns a new Config containing only Networks that pass all provided filter functions.
95+
// Filters are applied in sequence (AND logic) - a network must pass all filters to be included.
96+
func (c *Config) FilterWith(filters ...NetworkFilter) *Config {
97+
// Start with all networks from the current config
98+
networks := c.Networks()
99+
100+
// Apply each filter sequentially, removing networks that don't pass
101+
for _, filter := range filters {
102+
networks = slices.DeleteFunc(networks, func(network Network) bool {
103+
return !filter(network) // Delete networks that don't pass the filter
104+
})
105+
}
106+
107+
return NewConfig(networks)
108+
}
109+
110+
// TypesFilter returns a filter function that matches chains with the specified network types.
111+
func TypesFilter(networkTypes ...NetworkType) NetworkFilter {
112+
return func(network Network) bool {
113+
return slices.Contains(networkTypes, network.Type)
114+
}
115+
}
116+
117+
// ChainSelectorFilter returns a filter function that matches chains with the specified chain
118+
// selector
119+
func ChainSelectorFilter(selector uint64) NetworkFilter {
120+
return func(network Network) bool {
121+
return network.ChainSelector == selector
122+
}
123+
}
124+
125+
// ChainFamilyFilter returns a filter function that matches chains with the specified chain family.
126+
func ChainFamilyFilter(chainFamily string) NetworkFilter {
127+
return func(network Network) bool {
128+
family, err := network.ChainFamily()
129+
if err != nil {
130+
return false
131+
}
132+
133+
return family == chainFamily
134+
}
135+
}
136+
137+
// Load loads configuration from the specified file paths, and merges them into a single Config.
138+
//
139+
// It accepts load options to customize the loading behavior.
140+
func Load(filePaths []string, opts ...LoadOption) (*Config, error) {
141+
cfg := NewConfig([]Network{})
142+
143+
// Apply load options to populate the loading configuration.
144+
loadCfg := &loadConfig{}
145+
for _, opt := range opts {
146+
opt(loadCfg)
147+
}
148+
149+
// Load each file path into the config.
150+
for _, fp := range filePaths {
151+
data, err := os.ReadFile(fp)
152+
if err != nil {
153+
return nil, fmt.Errorf("failed to read networks file: %w", err)
154+
}
155+
156+
var fileCfg Config
157+
if err := yaml.Unmarshal(data, &fileCfg); err != nil {
158+
return nil, fmt.Errorf("failed to unmarshal networks YAML: %w", err)
159+
}
160+
161+
cfg.Merge(&fileCfg)
162+
}
163+
164+
// Apply the URL transformer if one is provided.
165+
if loadCfg.URLTransformer != nil {
166+
loadCfg.URLTransformer(cfg)
167+
}
168+
169+
return cfg, nil
170+
}
171+
172+
// LoadOption defines a function that modifies the load configuration.
173+
type LoadOption func(*loadConfig)
174+
175+
// URLTransformer is a function that transforms the URLs in the Config.
176+
type URLTransformer func(*Config)
177+
178+
// loadConfig holds the configuration for loading the config.
179+
type loadConfig struct {
180+
URLTransformer URLTransformer
181+
}
182+
183+
// WithURLTransform allows setting a custom URL transformer for the config.
184+
func WithURLTransform(t URLTransformer) func(opts *loadConfig) {
185+
return func(opts *loadConfig) {
186+
opts.URLTransformer = t
187+
}
188+
}

0 commit comments

Comments
 (0)