github.com/smartcontractkit/chainlink-testing-framework/libs@v0.0.0-20240227141906-ec710b4eb1a3/config/README.md (about)

     1  # TOML Config
     2  
     3  These basic building blocks can be used to create a TOML config file. For example:
     4  ```golang
     5  import (
     6      ctf_config "github.com/smartcontractkit/chainlink-testing-framework/libs/config"
     7  	ctf_test_env "github.com/smartcontractkit/chainlink-testing-framework/libs/docker/test_env"
     8  )
     9  
    10  type TestConfig struct {
    11  	ChainlinkImage         *ctf_config.ChainlinkImageConfig `toml:"ChainlinkImage"`
    12  	ChainlinkUpgradeImage  *ctf_config.ChainlinkImageConfig `toml:"ChainlinkUpgradeImage"`
    13  	Logging                *ctf_config.LoggingConfig        `toml:"Logging"`
    14  	Network                *ctf_config.NetworkConfig        `toml:"Network"`
    15  	Pyroscope              *ctf_config.PyroscopeConfig      `toml:"Pyroscope"`
    16  	PrivateEthereumNetwork *ctf_test_env.EthereumNetwork    `toml:"PrivateEthereumNetwork"`
    17  }
    18  ```
    19  
    20  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](../config/examples/testconfig.go) to see one way it chould be done..
    21  
    22  `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.
    23  
    24  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
    25  
    26  ## Secrets in TOML config
    27  
    28  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"`.
    29  
    30  ## Working example
    31  
    32  For a full working example making use of all the building blocks see [testconfig.go](../config/examples/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:
    33  * `BASE64_CONFIG_OVERRIDE` env var
    34  * `overrides.toml`
    35  * `[product_name].toml`
    36  * `default.toml`
    37  
    38  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).
    39  
    40  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.
    41  
    42  ## IMPORTANT!
    43  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.
    44  
    45  ## Network config (and default RPC endpoints)
    46  
    47  Some more explanation is needed for the `NetworkConfig`:
    48  ```golang
    49  type NetworkConfig struct {
    50  	// list of networks that should be used for testing
    51  	SelectedNetworks []string            `toml:"selected_networks"`
    52  	// map of network name to EVMNetworks where key is network name and value is a pointer to EVMNetwork
    53  	// if not set, it will try to find the network from defined networks in MappedNetworks under known_networks.go
    54  	// it doesn't matter if you use `arbitrum_sepolia` or `ARBITRUM_SEPOLIA` or even `arbitrum_SEPOLIA` as key
    55  	// as all keys will be uppercased when loading the Default config
    56  	EVMNetworks map[string]*blockchain.EVMNetwork `toml:"EVMNetworks,omitempty"`
    57  	// map of network name to ForkConfigs where key is network name and value is a pointer to ForkConfig
    58  	// only used if network fork is needed, if provided, the network will be forked with the given config
    59  	// networkname is fetched first from the EVMNetworks and 
    60  	// if not defined with EVMNetworks, it will try to find the network from defined networks in MappedNetworks under known_networks.go
    61      ForkConfigs map[string]*ForkConfig `toml:"ForkConfigs,omitempty"`
    62  	// map of network name to RPC endpoints where key is network name and value is a list of RPC HTTP endpoints
    63  	RpcHttpUrls      map[string][]string `toml:"RpcHttpUrls"`
    64  	// map of network name to RPC endpoints where key is network name and value is a list of RPC WS endpoints
    65  	RpcWsUrls        map[string][]string `toml:"RpcWsUrls"`
    66  	// map of network name to wallet keys where key is network name and value is a list of private keys (aka funding keys)
    67  	WalletKeys       map[string][]string `toml:"WalletKeys"`
    68  }
    69  
    70  func (n *NetworkConfig) Default() error {
    71      ...
    72  }
    73  ```
    74  
    75  Sample TOML config:
    76  ```toml
    77  selected_networks = ["arbitrum_goerli", "optimism_goerli", "new_network"]
    78  
    79  [EVMNetworks.new_network]
    80  evm_name = "new_test_network"
    81  evm_chain_id = 100009
    82  evm_simulated = true
    83  evm_chainlink_transaction_limit = 5000
    84  evm_minimum_confirmations = 1
    85  evm_gas_estimation_buffer = 10000
    86  client_implementation = "Ethereum"
    87  evm_supports_eip1559 = true
    88  evm_default_gas_limit = 6000000
    89  
    90  [ForkConfigs.new_network]
    91  url = "ws://localhost:8546"
    92  block_number = 100
    93  
    94  [RpcHttpUrls]
    95  arbitrum_goerli = ["https://devnet-2.mt/ABC/rpc/"]
    96  new_network = ["http://localhost:8545"]
    97  
    98  [RpcWsUrls]
    99  arbitrum_goerli = ["wss://devnet-2.mt/ABC/ws/"]
   100  new_network = ["ws://localhost:8546"]
   101  
   102  [WalletKeys]
   103  arbitrum_goerli = ["1810868fc221b9f50b5b3e0186d8a5f343f892e51ce12a9e818f936ec0b651ed"]
   104  optimism_goerli = ["1810868fc221b9f50b5b3e0186d8a5f343f892e51ce12a9e818f936ec0b651ed"]
   105  new_network = ["ac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80"]
   106  ```
   107  
   108  Whenver you are adding a new EVMNetwork to the config, you can either 
   109  - 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.
   110  - 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.
   111  ```toml
   112  
   113  selected_networks = ["new_network"]
   114  
   115  [EVMNetworks.new_network]
   116  evm_name = "new_test_network"
   117  evm_chain_id = 100009
   118  evm_urls = ["ws://localhost:8546"]
   119  evm_http_urls = ["http://localhost:8545"]
   120  evm_keys = ["ac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80"]
   121  evm_simulated = true
   122  evm_chainlink_transaction_limit = 5000
   123  evm_minimum_confirmations = 1
   124  evm_gas_estimation_buffer = 10000
   125  client_implementation = "Ethereum"
   126  evm_supports_eip1559 = true
   127  evm_default_gas_limit = 6000000
   128  ```
   129  
   130  
   131  If your config struct looks like that:
   132  ```golang
   133  
   134  type TestConfig struct {
   135  	Network *ctf_config.NetworkConfig `toml:"Network"`
   136  }
   137  ```
   138  
   139  then your TOML file should look like that:
   140  ```toml
   141  [Network]
   142  selected_networks = ["arbitrum_goerli","new_network"]
   143  
   144  [Network.EVMNetworks.new_network]
   145  evm_name = "new_test_network"
   146  evm_chain_id = 100009
   147  evm_simulated = true
   148  evm_chainlink_transaction_limit = 5000
   149  evm_minimum_confirmations = 1
   150  evm_gas_estimation_buffer = 10000
   151  client_implementation = "Ethereum"
   152  evm_supports_eip1559 = true
   153  evm_default_gas_limit = 6000000
   154  
   155  [Network.RpcHttpUrls]
   156  arbitrum_goerli = ["https://devnet-2.mt/ABC/rpc/"]
   157  new_network = ["http://localhost:8545"]
   158  
   159  [Network.RpcWsUrls]
   160  arbitrum_goerli = ["ws://devnet-2.mt/ABC/rpc/"]
   161  new_network = ["ws://localhost:8546"]
   162  
   163  [Network.WalletKeys]
   164  arbitrum_goerli = ["1810868fc221b9f50b5b3e0186d8a5f343f892e51ce12a9e818f936ec0b651ed"]
   165  new_network = ["ac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80"]
   166  ```
   167  
   168  
   169  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.
   170  
   171  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.
   172  
   173  ## Providing custom values in the CI
   174  
   175  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:
   176  ```bash
   177  convert_to_toml_array() {
   178  	local IFS=','
   179  	local input_array=($1)
   180  	local toml_array_format="["
   181  
   182  	for element in "${input_array[@]}"; do
   183  		toml_array_format+="\"$element\","
   184  	done
   185  
   186  	toml_array_format="${toml_array_format%,}]"
   187  	echo "$toml_array_format"
   188  }
   189  
   190  selected_networks=$(convert_to_toml_array "$SELECTED_NETWORKS")      
   191  log_targets=$(convert_to_toml_array "$LOGSTREAM_LOG_TARGETS")             
   192  
   193  if [ -n "$PYROSCOPE_SERVER" ]; then
   194  	pyroscope_enabled=true
   195  else
   196  	pyroscope_enabled=false
   197  fi
   198  
   199  if [ -n "$ETH2_EL_CLIENT" ]; then
   200  	execution_layer="$ETH2_EL_CLIENT"
   201  else
   202  	execution_layer="geth"
   203  fi
   204  
   205  if [ -n "$TEST_LOG_COLLECT" ]; then
   206  	test_log_collect=true
   207  else
   208  	test_log_collect=false
   209  fi              
   210  
   211  cat << EOF > config.toml
   212  [Network]
   213  selected_networks=$selected_networks
   214  
   215  [ChainlinkImage]
   216  image="$CHAINLINK_IMAGE"
   217  version="$CHAINLINK_VERSION"
   218  
   219  [Pyroscope]
   220  enabled=$pyroscope_enabled
   221  server_url="$PYROSCOPE_SERVER"
   222  environment="$PYROSCOPE_ENVIRONMENT"
   223  key_secret="$PYROSCOPE_KEY"
   224  
   225  [Logging]
   226  test_log_collect=$test_log_collect
   227  run_id="$RUN_ID"
   228  
   229  [Logging.LogStream]
   230  log_targets=$log_targets
   231  
   232  [Logging.Loki]
   233  tenant_id="$LOKI_TENANT_ID"
   234  url="$LOKI_URL"
   235  basic_auth_secret="$LOKI_BASIC_AUTH"
   236  bearer_token_secret="$LOKI_BEARER_TOKEN"
   237  
   238  [Logging.Grafana]
   239  url="$GRAFANA_URL"
   240  EOF
   241  
   242  BASE64_CONFIG_OVERRIDE=$(cat config.toml | base64 -w 0)
   243  echo ::add-mask::$BASE64_CONFIG_OVERRIDE
   244  echo "BASE64_CONFIG_OVERRIDE=$BASE64_CONFIG_OVERRIDE" >> $GITHUB_ENV
   245  ```
   246  
   247  **These two lines in that very order are super important**
   248  ```bash
   249  BASE64_CONFIG_OVERRIDE=$(cat config.toml | base64 -w 0)
   250  echo ::add-mask::$BASE64_CONFIG_OVERRIDE
   251  ```
   252  
   253  `::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.
   254  
   255  ## Providing custom values for local execution
   256  For local execution it's best to put custom variables in `overrides.toml` file.
   257  
   258  ## Providing custom values in k8s
   259  It's easy. All you need to do is:
   260  * Create TOML file with these values
   261  * Base64 it: `cat your.toml | base64`
   262  * Set the base64 result as `BASE64_CONFIG_OVERRIDE` environment variable.
   263  
   264  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()`. 
   265  
   266  Quick example:
   267  ```bash
   268  BASE64_CONFIG_OVERRIDE=$(cat your.toml | base64) go test your-test-that-runs-in-k8s ./file/with/your/test
   269  ```
   270  
   271  # Not moved to TOML
   272  Not moved to TOML:
   273  * `SLACK_API_KEY`
   274  * `SLACK_USER`
   275  * `SLACK_CHANNEL`
   276  * `TEST_LOG_LEVEL`
   277  * `CHAINLINK_ENV_USER`
   278  * `DETACH_RUNNER` 
   279  * `ENV_JOB_IMAGE`
   280  * most of k8s-specific env variables were left untouched