config

package
v0.0.0-...-ec710b4 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Feb 27, 2024 License: MIT Imports: 12 Imported by: 0

README

TOML Config

These basic building blocks can be used to create a TOML config file. For example:

import (
    ctf_config "github.com/smartcontractkit/chainlink-testing-framework/libs/config"
	ctf_test_env "github.com/smartcontractkit/chainlink-testing-framework/libs/docker/test_env"
)

type TestConfig struct {
	ChainlinkImage         *ctf_config.ChainlinkImageConfig `toml:"ChainlinkImage"`
	ChainlinkUpgradeImage  *ctf_config.ChainlinkImageConfig `toml:"ChainlinkUpgradeImage"`
	Logging                *ctf_config.LoggingConfig        `toml:"Logging"`
	Network                *ctf_config.NetworkConfig        `toml:"Network"`
	Pyroscope              *ctf_config.PyroscopeConfig      `toml:"Pyroscope"`
	PrivateEthereumNetwork *ctf_test_env.EthereumNetwork    `toml:"PrivateEthereumNetwork"`
}

It's up to the user to provide a way to read the config from file and unmarshal it into the struct. You can check testconfig.go to see one way it chould be done..

Validate() should be used to ensure that the config is valid. Some of the building blocks have also a Default() method that can be used to get default values.

Also you might find BytesToAnyTomlStruct(logger zerolog.Logger, filename, configurationName string, target any, content []byte) error utility method useful for unmarshalling TOMLs read from env var or files into a struct

Secrets in TOML config

For all values regarded as secrets, their keys should end with the _secret suffix. For example, use basic_auth_secret="basic-auth" instead of basic_auth="basic-auth".

Working example

For a full working example making use of all the building blocks see testconfig.go. It provides methods for reading TOML, applying overrides and validating non-empty config blocks. It supports 4 levels of overrides, in order of precedence:

  • BASE64_CONFIG_OVERRIDE env var
  • overrides.toml
  • [product_name].toml
  • default.toml

All you need to do now to get the config is execute func GetConfig(configurationName string, product string) (TestConfig, error). It will first look for folder with file .root_dir and from there it will look for config files in all subfolders, so that you can place the config files in whatever folder(s) work for you. It assumes that all configuration versions for a single product are kept in [product_name].toml under different configuration names (that can represent anything you want: a single test, a test type, a test group, etc).

Overrides of config files are done in a super-simple way. We try to unmarshall consecutive files into the same struct. Since it's all pointer based only not-nil keys are overwritten.

IMPORTANT!

It is required to add overrides.toml to .gitignore in your project, so that you don't accidentally commit it as it might contain secrets.

Network config (and default RPC endpoints)

Some more explanation is needed for the NetworkConfig:

type NetworkConfig struct {
	// list of networks that should be used for testing
	SelectedNetworks []string            `toml:"selected_networks"`
	// map of network name to EVMNetworks where key is network name and value is a pointer to EVMNetwork
	// if not set, it will try to find the network from defined networks in MappedNetworks under known_networks.go
	// it doesn't matter if you use `arbitrum_sepolia` or `ARBITRUM_SEPOLIA` or even `arbitrum_SEPOLIA` as key
	// as all keys will be uppercased when loading the Default config
	EVMNetworks map[string]*blockchain.EVMNetwork `toml:"EVMNetworks,omitempty"`
	// map of network name to ForkConfigs where key is network name and value is a pointer to ForkConfig
	// only used if network fork is needed, if provided, the network will be forked with the given config
	// networkname is fetched first from the EVMNetworks and 
	// if not defined with EVMNetworks, it will try to find the network from defined networks in MappedNetworks under known_networks.go
    ForkConfigs map[string]*ForkConfig `toml:"ForkConfigs,omitempty"`
	// map of network name to RPC endpoints where key is network name and value is a list of RPC HTTP endpoints
	RpcHttpUrls      map[string][]string `toml:"RpcHttpUrls"`
	// map of network name to RPC endpoints where key is network name and value is a list of RPC WS endpoints
	RpcWsUrls        map[string][]string `toml:"RpcWsUrls"`
	// map of network name to wallet keys where key is network name and value is a list of private keys (aka funding keys)
	WalletKeys       map[string][]string `toml:"WalletKeys"`
}

func (n *NetworkConfig) Default() error {
    ...
}

Sample TOML config:

selected_networks = ["arbitrum_goerli", "optimism_goerli", "new_network"]

[EVMNetworks.new_network]
evm_name = "new_test_network"
evm_chain_id = 100009
evm_simulated = true
evm_chainlink_transaction_limit = 5000
evm_minimum_confirmations = 1
evm_gas_estimation_buffer = 10000
client_implementation = "Ethereum"
evm_supports_eip1559 = true
evm_default_gas_limit = 6000000

[ForkConfigs.new_network]
url = "ws://localhost:8546"
block_number = 100

[RpcHttpUrls]
arbitrum_goerli = ["https://devnet-2.mt/ABC/rpc/"]
new_network = ["http://localhost:8545"]

[RpcWsUrls]
arbitrum_goerli = ["wss://devnet-2.mt/ABC/ws/"]
new_network = ["ws://localhost:8546"]

[WalletKeys]
arbitrum_goerli = ["1810868fc221b9f50b5b3e0186d8a5f343f892e51ce12a9e818f936ec0b651ed"]
optimism_goerli = ["1810868fc221b9f50b5b3e0186d8a5f343f892e51ce12a9e818f936ec0b651ed"]
new_network = ["ac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80"]

Whenver you are adding a new EVMNetwork to the config, you can either

  • provide the rpcs and wallet keys in Rpc<Http/Ws>Urls and WalletKeys. Like in the example above, you can see that new_network is added to the selected_networks and EVMNetworks and then the rpcs and wallet keys are provided in RpcHttpUrls, RpcWsUrls and WalletKeys respectively.
  • provide the rpcs and wallet keys in the EVMNetworks itself. Like in the example below, you can see that new_network is added to the selected_networks and EVMNetworks and then the rpcs and wallet keys are provided in EVMNetworks itself.

selected_networks = ["new_network"]

[EVMNetworks.new_network]
evm_name = "new_test_network"
evm_chain_id = 100009
evm_urls = ["ws://localhost:8546"]
evm_http_urls = ["http://localhost:8545"]
evm_keys = ["ac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80"]
evm_simulated = true
evm_chainlink_transaction_limit = 5000
evm_minimum_confirmations = 1
evm_gas_estimation_buffer = 10000
client_implementation = "Ethereum"
evm_supports_eip1559 = true
evm_default_gas_limit = 6000000

If your config struct looks like that:


type TestConfig struct {
	Network *ctf_config.NetworkConfig `toml:"Network"`
}

then your TOML file should look like that:

[Network]
selected_networks = ["arbitrum_goerli","new_network"]

[Network.EVMNetworks.new_network]
evm_name = "new_test_network"
evm_chain_id = 100009
evm_simulated = true
evm_chainlink_transaction_limit = 5000
evm_minimum_confirmations = 1
evm_gas_estimation_buffer = 10000
client_implementation = "Ethereum"
evm_supports_eip1559 = true
evm_default_gas_limit = 6000000

[Network.RpcHttpUrls]
arbitrum_goerli = ["https://devnet-2.mt/ABC/rpc/"]
new_network = ["http://localhost:8545"]

[Network.RpcWsUrls]
arbitrum_goerli = ["ws://devnet-2.mt/ABC/rpc/"]
new_network = ["ws://localhost:8546"]

[Network.WalletKeys]
arbitrum_goerli = ["1810868fc221b9f50b5b3e0186d8a5f343f892e51ce12a9e818f936ec0b651ed"]
new_network = ["ac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80"]

It not only stores the configuration of selected networks and RPC endpoints and wallet keys, but via Default() method provides a way to read from env var BASE64_NETWORK_CONFIG a base64-ed configuration of RPC endpoints and wallet keys. This could prove useful in the CI, where we could store as a secret a default configuration of stable endpoints, so that when we run a test job all that we have to provide is the network name and nothing more as it's pretty tedious, especially for on-demand jobs, to have to pass the whole RPC/wallet configuration every time you run it.

If in your product config you want to support case-insensitive network names and map keys remember to run NetworkConfig.UpperCaseNetworkNames() on your config before using it.

Providing custom values in the CI

Up to this point when we wanted to modify some dynamic tests parameters in the CI we would simply set env vars. That approach won't work anymore. The way to go around it is to build a TOML file, base64 it, mask it and then set is as BASE64_CONFIG_OVERRIDE env var that will be read by tests. Here's an example of a working snippet of how that could look:

convert_to_toml_array() {
	local IFS=','
	local input_array=($1)
	local toml_array_format="["

	for element in "${input_array[@]}"; do
		toml_array_format+="\"$element\","
	done

	toml_array_format="${toml_array_format%,}]"
	echo "$toml_array_format"
}

selected_networks=$(convert_to_toml_array "$SELECTED_NETWORKS")      
log_targets=$(convert_to_toml_array "$LOGSTREAM_LOG_TARGETS")             

if [ -n "$PYROSCOPE_SERVER" ]; then
	pyroscope_enabled=true
else
	pyroscope_enabled=false
fi

if [ -n "$ETH2_EL_CLIENT" ]; then
	execution_layer="$ETH2_EL_CLIENT"
else
	execution_layer="geth"
fi

if [ -n "$TEST_LOG_COLLECT" ]; then
	test_log_collect=true
else
	test_log_collect=false
fi              

cat << EOF > config.toml
[Network]
selected_networks=$selected_networks

[ChainlinkImage]
image="$CHAINLINK_IMAGE"
version="$CHAINLINK_VERSION"

[Pyroscope]
enabled=$pyroscope_enabled
server_url="$PYROSCOPE_SERVER"
environment="$PYROSCOPE_ENVIRONMENT"
key_secret="$PYROSCOPE_KEY"

[Logging]
test_log_collect=$test_log_collect
run_id="$RUN_ID"

[Logging.LogStream]
log_targets=$log_targets

[Logging.Loki]
tenant_id="$LOKI_TENANT_ID"
url="$LOKI_URL"
basic_auth_secret="$LOKI_BASIC_AUTH"
bearer_token_secret="$LOKI_BEARER_TOKEN"

[Logging.Grafana]
url="$GRAFANA_URL"
EOF

BASE64_CONFIG_OVERRIDE=$(cat config.toml | base64 -w 0)
echo ::add-mask::$BASE64_CONFIG_OVERRIDE
echo "BASE64_CONFIG_OVERRIDE=$BASE64_CONFIG_OVERRIDE" >> $GITHUB_ENV

These two lines in that very order are super important

BASE64_CONFIG_OVERRIDE=$(cat config.toml | base64 -w 0)
echo ::add-mask::$BASE64_CONFIG_OVERRIDE

::add-mask:: has to be called only after env var has been set to it's final value, otherwise it won't be recognized and masked properly and secrets will be exposed in the logs.

Providing custom values for local execution

For local execution it's best to put custom variables in overrides.toml file.

Providing custom values in k8s

It's easy. All you need to do is:

  • Create TOML file with these values
  • Base64 it: cat your.toml | base64
  • Set the base64 result as BASE64_CONFIG_OVERRIDE environment variable.

Both BASE64_CONFIG_OVERRIDE and BASE64_NETWORK_CONFIG will be automatically forwarded to k8s (as long as they are set and available to the test process), when creating the environment programmatically via environment.New().

Quick example:

BASE64_CONFIG_OVERRIDE=$(cat your.toml | base64) go test your-test-that-runs-in-k8s ./file/with/your/test

Not moved to TOML

Not moved to TOML:

  • SLACK_API_KEY
  • SLACK_USER
  • SLACK_CHANNEL
  • TEST_LOG_LEVEL
  • CHAINLINK_ENV_USER
  • DETACH_RUNNER
  • ENV_JOB_IMAGE
  • most of k8s-specific env variables were left untouched

Documentation

Index

Constants

View Source
const (
	Base64NetworkConfigEnvVarName = "BASE64_NETWORK_CONFIG"
)

Variables

This section is empty.

Functions

func BytesToAnyTomlStruct

func BytesToAnyTomlStruct(logger zerolog.Logger, filename, configurationName string, target any, content []byte) error

BytesToAnyTomlStruct unmarshals the given bytes into the given target struct, apart from unmarshalling the root it also looks for given configuration name and unmarshals it into the target struct overwriting the root. Target needs to be a struct with exported fields with `toml:"field_name"` tags.

func MightConfigOverridePyroscopeKey

func MightConfigOverridePyroscopeKey(config *PyroscopeConfig, target interface{})

MightConfigOverridePyroscope will override the pyroscope config in property maps from the passed config. If the config is nil, or the enabled flag is not set, or the key is not set, then this function will do nothing.

func MustConfigOverrideChainlinkVersion

func MustConfigOverrideChainlinkVersion(config *ChainlinkImageConfig, target interface{})

MustConfigOverrideChainlinkVersion will override the chainlink image and version in property maps from the passed config. It will panic if the config is nil.

Types

type ChainlinkImageConfig

type ChainlinkImageConfig struct {
	Image   *string `toml:"image"`
	Version *string `toml:"version"`
}

func (*ChainlinkImageConfig) Validate

func (c *ChainlinkImageConfig) Validate() error

Validate checks that the chainlink image config is valid, which means that both image and version are set and non-empty

type ForkConfig

type ForkConfig struct {
	URL              string `toml:"url"`                          // URL is the URL of the node to fork from. Refer to https://book.getfoundry.sh/reference/anvil/#options
	BlockNumber      int64  `toml:"block_number"`                 // BlockNumber is the block number to fork from. Refer to https://book.getfoundry.sh/reference/anvil/#options
	BlockTime        int64  `toml:"block_time"`                   // how frequent blocks are mined. By default, it automatically generates a new block as soon as a transaction is submitted. Refer to https://book.getfoundry.sh/reference/anvil/#options
	Retries          int    `toml:"retries,omitempty"`            //  Number of retry requests for spurious networks (timed out requests). Refer to https://book.getfoundry.sh/reference/anvil/#options
	Timeout          int64  `toml:"timeout,omitempty"`            //  Timeout in ms for requests sent to remote JSON-RPC server in forking mode. Refer to https://book.getfoundry.sh/reference/anvil/#options
	ComputePerSecond int64  `toml:"compute_per_second,omitempty"` // Sets the number of assumed available compute units per second for this provider. Refer to https://book.getfoundry.sh/reference/anvil/#options
	RateLimitEnabled bool   `toml:"rate_limit_enabled,omitempty"` //  rate limiting for this node’s provider. Refer to https://book.getfoundry.sh/reference/anvil/#options
}

type GrafanaConfig

type GrafanaConfig struct {
	BaseUrl      *string `toml:"base_url"`
	DashboardUrl *string `toml:"dashboard_url"`
	BearerToken  *string `toml:"bearer_token_secret"`
}

func (*GrafanaConfig) Validate

func (c *GrafanaConfig) Validate() error

Validate checks that the grafana config is valid, which means that base url is a valid URL and dashboard url and bearer token are not empty but that only applies if they are set

type LogStreamConfig

type LogStreamConfig struct {
	LogTargets            []string                `toml:"log_targets"`
	LogProducerTimeout    *blockchain.StrDuration `toml:"log_producer_timeout"`
	LogProducerRetryLimit *uint                   `toml:"log_producer_retry_limit"`
}

func (*LogStreamConfig) Validate

func (l *LogStreamConfig) Validate() error

Validate checks that the log stream config is valid, which means that log targets are valid and log producer timeout is greater than 0

type LoggingConfig

type LoggingConfig struct {
	TestLogCollect *bool            `toml:"test_log_collect"`
	RunId          *string          `toml:"run_id"`
	Loki           *LokiConfig      `toml:"Loki"`
	Grafana        *GrafanaConfig   `toml:"Grafana"`
	LogStream      *LogStreamConfig `toml:"LogStream"`
}

func (*LoggingConfig) Validate

func (l *LoggingConfig) Validate() error

Validate executes config validation for LogStream, Grafana and Loki

type LokiConfig

type LokiConfig struct {
	TenantId    *string `toml:"tenant_id"`
	Endpoint    *string `toml:"endpoint"`
	BasicAuth   *string `toml:"basic_auth_secret"`
	BearerToken *string `toml:"bearer_token_secret"`
}

func (*LokiConfig) Validate

func (l *LokiConfig) Validate() error

Validate checks that the loki config is valid, which means that endpoint is a valid URL and tenant id is not empty

type NetworkConfig

type NetworkConfig struct {
	SelectedNetworks []string `toml:"selected_networks"`
	// EVMNetworks is the configuration for the EVM networks, key is the network name as declared in selected_networks slice.
	// if not set, it will try to find the network from defined networks in MappedNetworks under known_networks.go
	EVMNetworks map[string]*blockchain.EVMNetwork `toml:"EVMNetworks,omitempty"`
	// ForkConfigs is the configuration for forking from a node,
	// key is the network name as declared in selected_networks slice
	ForkConfigs map[string]*ForkConfig `toml:"ForkConfigs,omitempty"`
	// RpcHttpUrls is the RPC HTTP endpoints for each network,
	// key is the network name as declared in selected_networks slice
	RpcHttpUrls map[string][]string `toml:"RpcHttpUrls"`
	// RpcWsUrls is the RPC WS endpoints for each network,
	// key is the network name as declared in selected_networks slice
	RpcWsUrls map[string][]string `toml:"RpcWsUrls"`
	// WalletKeys is the private keys for the funding wallets for each network,
	// key is the network name as declared in selected_networks slice
	WalletKeys map[string][]string `toml:"WalletKeys"`
}

NetworkConfig is the configuration for the networks to be used

func (*NetworkConfig) Default

func (n *NetworkConfig) Default() error

Default applies default values to the network config after reading it from BASE64_NETWORK_CONFIG env var. It will only fill in the gaps, not override meaning that if you provided WS RPC endpoint in your network config, but not the HTTP one, then only HTTP will be taken from default config (provided it's there)

func (*NetworkConfig) OverrideURLsAndKeysFromEVMNetwork

func (n *NetworkConfig) OverrideURLsAndKeysFromEVMNetwork()

OverrideURLsAndKeysFromEVMNetwork applies the URLs and keys from the EVMNetworks to the NetworkConfig it overrides the URLs and Keys present in RpcHttpUrls, RpcWsUrls and WalletKeys in the NetworkConfig with the URLs and Keys provided in the EVMNetworks

func (*NetworkConfig) UpperCaseNetworkNames

func (n *NetworkConfig) UpperCaseNetworkNames()

UpperCaseNetworkNames converts all network name keys for wallet keys, rpc endpoints maps and selected network slice to upper case

func (*NetworkConfig) Validate

func (n *NetworkConfig) Validate() error

Validate checks if all required fields are set, meaning that there must be at least 1 selected network and unless it's a simulated network, there must be at least 1 rpc endpoint for HTTP and WS and 1 private key for funding wallet

type PyroscopeConfig

type PyroscopeConfig struct {
	Enabled     *bool   `toml:"enabled"`
	ServerUrl   *string `toml:"server_url"`
	Key         *string `toml:"key_secret"`
	Environment *string `toml:"environment"`
}

func (*PyroscopeConfig) Validate

func (c *PyroscopeConfig) Validate() error

Validate checks that the pyroscope config is valid, which means that server url, environment and key are set and non-empty, but only if pyroscope is enabled

type WaspAutoBuildConfig

type WaspAutoBuildConfig struct {
	Namespace           *string `toml:"namespace"`
	RepoImageVersionURI *string `toml:"repo_image_version_uri"`
	TestBinaryName      *string `toml:"test_binary_name"`
	TestName            *string `toml:"test_name"`
	TestTimeout         *string `toml:"test_timeout"`
	KeepJobs            bool    `toml:"keep_jobs"`
	WaspLogLevel        *string `toml:"wasp_log_level"`
	WaspJobs            *string `toml:"wasp_jobs"`
	UpdateImage         bool    `toml:"update_image"`
}

func (*WaspAutoBuildConfig) Validate

func (c *WaspAutoBuildConfig) Validate() error

Directories

Path Synopsis

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL