Skip to content

Commit 6002ace

Browse files
committed
wip
1 parent 4a55ba9 commit 6002ace

File tree

10 files changed

+118
-7
lines changed

10 files changed

+118
-7
lines changed

book/src/SUMMARY.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
- [Components Cleanup](framework/components/cleanup.md)
1919
- [Components Caching](framework/components/caching.md)
2020
- [Mocking Services](framework/components/mocking.md)
21+
- [Copying Files](framework/copying_files.md)
2122
- [External Environment](framework/components/external.md)
2223
- [Secrets]()
2324
- [Observability Stack](framework/observability/observability_stack.md)

book/src/framework/components/state.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ When deploying a component, you can explicitly configure port ranges if the defa
99
Defaults are:
1010
- [NodeSet](../components/chainlink/nodeset.md) (Node HTTP API): `10000..100XX`
1111
- [NodeSet](../components/chainlink/nodeset.md) (Node P2P API): `12000..120XX`
12+
- Shared `PostgreSQL` volume is called `postgresql_data`
1213
```
1314
[nodeset]
1415
# HTTP API port range start, each new node get port incremented (host machine)
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
# Copying Files
2+
3+
You can copy files to containers by using the `ContainerName` from the output and specifying the source `src` and destination `dst` paths.
4+
5+
However, using this API is discouraged and will be **deprecated** in the future, as it violates the principles of "black-box" testing. If your service relies on this functionality, consider designing a configuration or API to address the requirement instead.
6+
7+
```go
8+
bc, err := blockchain.NewBlockchainNetwork(&blockchain.Input{
9+
...
10+
})
11+
require.NoError(t, err)
12+
13+
err = dockerClient.CopyFile(bc.ContainerName, "local_file.txt", "/home")
14+
require.NoError(t, err)
15+
```

framework/components/blockchain/anvil.go

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -55,8 +55,9 @@ func deployAnvil(in *Input) (*Output, error) {
5555
return nil, err
5656
}
5757
return &Output{
58-
UseCache: true,
59-
ChainID: in.ChainID,
58+
UseCache: true,
59+
ChainID: in.ChainID,
60+
ContainerName: containerName,
6061
Nodes: []*Node{
6162
{
6263
HostWSUrl: fmt.Sprintf("ws://%s:%s", host, mp.Port()),

framework/components/blockchain/blockchain.go

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -17,9 +17,10 @@ type Input struct {
1717

1818
// Output is a blockchain network output, ChainID and one or more nodes that forms the network
1919
type Output struct {
20-
UseCache bool `toml:"use_cache"`
21-
ChainID string `toml:"chain_id"`
22-
Nodes []*Node `toml:"nodes"`
20+
UseCache bool `toml:"use_cache"`
21+
ContainerName string `toml:"container_name"`
22+
ChainID string `toml:"chain_id"`
23+
Nodes []*Node `toml:"nodes"`
2324
}
2425

2526
// Node represents blockchain node output, URLs required for connection locally and inside docker network

framework/components/clnode/clnode.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,7 @@ type Output struct {
6464
type NodeOut struct {
6565
APIAuthUser string `toml:"api_auth_user"`
6666
APIAuthPassword string `toml:"api_auth_password"`
67+
ContainerName string `toml:"container_name"`
6768
HostURL string `toml:"url"`
6869
HostP2PURL string `toml:"p2p_url"`
6970
DockerURL string `toml:"docker_internal_url"`
@@ -288,6 +289,7 @@ func newNode(in *Input, pgOut *postgres.Output) (*NodeOut, error) {
288289
return &NodeOut{
289290
APIAuthUser: DefaultAPIUser,
290291
APIAuthPassword: DefaultAPIPassword,
292+
ContainerName: containerName,
291293
HostURL: fmt.Sprintf("http://%s:%s", host, mp.Port()),
292294
HostP2PURL: fmt.Sprintf("http://%s:%s", host, mpP2P.Port()),
293295
DockerURL: fmt.Sprintf("http://%s:%s", containerName, DefaultHTTPPort),

framework/components/postgres/postgres.go

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,14 +33,15 @@ type Input struct {
3333

3434
type Output struct {
3535
Url string `toml:"url"`
36+
ContainerName string `toml:"container_name"`
3637
DockerInternalURL string `toml:"docker_internal_url"`
3738
}
3839

3940
func NewPostgreSQL(in *Input) (*Output, error) {
4041
ctx := context.Background()
4142

4243
bindPort := fmt.Sprintf("%s/tcp", Port)
43-
containerName := framework.DefaultTCName("postgresql")
44+
containerName := "ns-postgresql"
4445

4546
var sqlCommands []string
4647
for i := 0; i <= in.Databases; i++ {
@@ -126,6 +127,7 @@ func NewPostgreSQL(in *Input) (*Output, error) {
126127
return nil, err
127128
}
128129
return &Output{
130+
ContainerName: containerName,
129131
DockerInternalURL: fmt.Sprintf(
130132
"postgresql://%s:%s@%s:%s/%s?sslmode=disable",
131133
User,

framework/components/simple_node_set/reload.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ import (
99
// UpgradeNodeSet updates nodes configuration TOML files
1010
// this API is discouraged, however, you can use it if nodes require restart or configuration updates, temporarily!
1111
func UpgradeNodeSet(in *Input, bc *blockchain.Output, wait time.Duration) (*Output, error) {
12-
_, err := chaos.ExecPumba("rm --volumes=false re2:node.*|postgresql.*", wait)
12+
_, err := chaos.ExecPumba("rm --volumes=false re2:node.*|ns-postgresql.*", wait)
1313
if err != nil {
1414
return nil, err
1515
}

framework/docker.go

Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,17 @@
11
package framework
22

33
import (
4+
"archive/tar"
5+
"bytes"
46
"context"
57
"errors"
68
"fmt"
9+
"github.com/docker/docker/api/types/container"
710
"github.com/docker/docker/client"
811
"github.com/docker/go-connections/nat"
912
"github.com/google/uuid"
1013
tc "github.com/testcontainers/testcontainers-go"
14+
"io"
1115
"os"
1216
"os/exec"
1317
"strings"
@@ -132,3 +136,86 @@ func RebuildDockerImage(once *sync.Once, dockerfile string, buildContext string,
132136
}
133137
return fmt.Sprintf("localhost:5050/%s:latest", imageName), nil
134138
}
139+
140+
// DockerClient wraps a Docker API client and provides convenience methods
141+
type DockerClient struct {
142+
cli *client.Client
143+
}
144+
145+
// NewDockerClient creates a new instance of DockerClient
146+
func NewDockerClient() (*DockerClient, error) {
147+
cli, err := client.NewClientWithOpts(client.FromEnv, client.WithAPIVersionNegotiation())
148+
if err != nil {
149+
return nil, fmt.Errorf("failed to create Docker client: %w", err)
150+
}
151+
return &DockerClient{cli: cli}, nil
152+
}
153+
154+
// CopyFile copies a file into a container by name
155+
func (dc *DockerClient) CopyFile(containerName, sourceFile, targetPath string) error {
156+
ctx := context.Background()
157+
containerID, err := dc.findContainerIDByName(ctx, containerName)
158+
if err != nil {
159+
return fmt.Errorf("failed to find container ID by name: %s", containerName)
160+
}
161+
return dc.copyToContainer(containerID, sourceFile, targetPath)
162+
}
163+
164+
// findContainerIDByName finds a container ID by its name
165+
func (dc *DockerClient) findContainerIDByName(ctx context.Context, containerName string) (string, error) {
166+
containers, err := dc.cli.ContainerList(ctx, container.ListOptions{
167+
All: true,
168+
})
169+
if err != nil {
170+
return "", fmt.Errorf("failed to list containers: %w", err)
171+
}
172+
for _, c := range containers {
173+
for _, name := range c.Names {
174+
if name == "/"+containerName {
175+
return c.ID, nil
176+
}
177+
}
178+
}
179+
return "", fmt.Errorf("container with name %s not found", containerName)
180+
}
181+
182+
// copyToContainer copies a file into a container
183+
func (dc *DockerClient) copyToContainer(containerID, sourceFile, targetPath string) error {
184+
ctx := context.Background()
185+
src, err := os.Open(sourceFile)
186+
if err != nil {
187+
return fmt.Errorf("could not open source file: %w", err)
188+
}
189+
defer src.Close()
190+
191+
// Create a tar archive containing the file
192+
var buf bytes.Buffer
193+
tw := tar.NewWriter(&buf)
194+
info, err := src.Stat()
195+
if err != nil {
196+
return fmt.Errorf("could not stat source file: %w", err)
197+
}
198+
199+
// Add file to tar
200+
header := &tar.Header{
201+
Name: info.Name(),
202+
Size: info.Size(),
203+
Mode: int64(info.Mode()),
204+
}
205+
if err := tw.WriteHeader(header); err != nil {
206+
return fmt.Errorf("could not write tar header: %w", err)
207+
}
208+
if _, err := io.Copy(tw, src); err != nil {
209+
return fmt.Errorf("could not write file to tar archive: %w", err)
210+
}
211+
tw.Close()
212+
213+
// Copy the tar archive to the container
214+
err = dc.cli.CopyToContainer(ctx, containerID, targetPath, &buf, container.CopyToContainerOptions{
215+
AllowOverwriteDirWithFile: true,
216+
})
217+
if err != nil {
218+
return fmt.Errorf("could not copy file to container: %w", err)
219+
}
220+
return nil
221+
}
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
11
export LOKI_TENANT_ID=promtail
22
export LOKI_URL=http://localhost:3030/loki/api/v1/push
33
export TESTCONTAINERS_RYUK_DISABLED=true
4+
export CTF_LOG_LEVEL=info

0 commit comments

Comments
 (0)