|
| 1 | +## Developing Components |
| 2 | + |
| 3 | +To build a scalable framework that enables the reuse of our product deployments (contracts or services in Docker), we need to establish a clear component structure. |
| 4 | + |
| 5 | +```golang |
| 6 | +package mycomponent |
| 7 | + |
| 8 | +import ( |
| 9 | + "fmt" |
| 10 | + "github.com/smartcontractkit/chainlink-testing-framework/framework" |
| 11 | +) |
| 12 | + |
| 13 | +type Input struct { |
| 14 | + // inputs fields that component exposes for configuration |
| 15 | + ... |
| 16 | + // outputs are embedded into inputs so framework can automatically save them |
| 17 | + Out *Output `toml:"out"` |
| 18 | +} |
| 19 | + |
| 20 | +type Output struct { |
| 21 | + UseCache bool `toml:"use_cache"` |
| 22 | + // outputs that will be dumped to config and cached |
| 23 | +} |
| 24 | + |
| 25 | + |
| 26 | +func NewComponent(input *Input) (*Output, error) { |
| 27 | + if input.Out != nil && input.Out.UseCache { |
| 28 | + return input.Out, nil |
| 29 | + } |
| 30 | + |
| 31 | + // component logic here |
| 32 | + // deploy a docker container(s) |
| 33 | + // or deploy a set of smart contracts |
| 34 | + |
| 35 | + input.Out = &Output{ |
| 36 | + UseCache: true, |
| 37 | + // other fields |
| 38 | + ... |
| 39 | + } |
| 40 | + return out, nil |
| 41 | +} |
| 42 | +``` |
| 43 | + |
| 44 | +Each component can define inputs and outputs, following these rules: |
| 45 | + |
| 46 | +- Outputs should be included within inputs. |
| 47 | +- If your component is used for side effects output can be omitted. |
| 48 | +- `input.Out.UseCache` should be added if you'd like to use caching, see more [here](caching) |
| 49 | + |
| 50 | +### Docker components good practices for [testcontainers-go](https://golang.testcontainers.org/): |
| 51 | + |
| 52 | +An example [simple component](../../../../framework/components/blockchain/anvil.go) |
| 53 | + |
| 54 | +An example of [complex component](../../../../framework/components/clnode/clnode.go) |
| 55 | + |
| 56 | +An example of [composite component](../../../../framework/components/simple_node_set/node_set.go) |
| 57 | + |
| 58 | +- Inputs should include at least `image`, `tag` and `pull_image` field |
| 59 | +```golang |
| 60 | + Image string `toml:"image" validate:"required"` |
| 61 | + Tag string `toml:"tag" validate:"required"` |
| 62 | + PullImage bool `toml:"pull_image" validate:"required"` |
| 63 | +``` |
| 64 | + |
| 65 | +- `ContainerRequest` must contain labels, network and alias required for local observability stack and deployment isolation |
| 66 | +```golang |
| 67 | + Labels: framework.DefaultTCLabels(), |
| 68 | + Networks: []string{framework.DefaultNetworkName}, |
| 69 | + NetworkAliases: map[string][]string{ |
| 70 | + framework.DefaultNetworkName: {containerName}, |
| 71 | + }, |
| 72 | +``` |
| 73 | +- In order to copy files into container use `framework.WriteTmpFile(data string, fileName string)` |
| 74 | +```golang |
| 75 | + userSecretsOverridesFile, err := WriteTmpFile(in.Node.UserSecretsOverrides, "user-secrets-overrides.toml") |
| 76 | + if err != nil { |
| 77 | + return nil, err |
| 78 | + } |
| 79 | +``` |
| 80 | +- Output of docker component must contain all the URLs component exposes for access, both for internal docker usage and external test (host) usage |
| 81 | +```golang |
| 82 | + host, err := framework.GetHost(c) |
| 83 | + if err != nil { |
| 84 | + return nil, err |
| 85 | + } |
| 86 | + mp, err := c.MappedPort(ctx, nat.Port(bindPort)) |
| 87 | + if err != nil { |
| 88 | + return nil, err |
| 89 | + } |
| 90 | + |
| 91 | + return &NodeOut{ |
| 92 | + UseCache: true, |
| 93 | + DockerURL: fmt.Sprintf("http://%s:%s", containerName, in.Node.Port), |
| 94 | + HostURL: fmt.Sprintf("http://%s:%s", host, mp.Port()), |
| 95 | + }, nil |
| 96 | +``` |
0 commit comments