Skip to content

Commit 4559cb5

Browse files
committed
rebuild docker images from config
1 parent 7d555ae commit 4559cb5

File tree

5 files changed

+99
-1
lines changed

5 files changed

+99
-1
lines changed

framework/README.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,4 +28,5 @@ This module includes the CTFv2 harness, a lightweight, modular, and data-driven
2828
| LOKI_URL | URL to `Loki` push api, should be like`${host}/loki/api/v1/push` | URL | - | If you use `Loki` then ✅ |
2929
| LOKI_TENANT_ID | Streams all components logs to `Loki`, see params below | `true`, `false` | - | If you use `Loki` then ✅ |
3030
| TESTCONTAINERS_RYUK_DISABLED | Testcontainers-Go reaper container, removes all the containers after the test exit | `true`, `false` | `false` | 🚫 |
31-
| RESTY_DEBUG | Log all Resty client HTTP calls | `true`, `false` | `false` | 🚫 |
31+
| CTF_BUILD_DOCKER_IMAGES | Rebuild and publish local docker images if there are changes | `true`, `false` | `false` | 🚫 |
32+
| RESTY_DEBUG | Log all Resty client HTTP calls | `true`, `false` | `false` | 🚫 |

framework/components/clnode/clnode.go

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import (
1111
"github.com/testcontainers/testcontainers-go/wait"
1212
"os"
1313
"path/filepath"
14+
"sync"
1415
"text/template"
1516
"time"
1617
)
@@ -20,6 +21,10 @@ const (
2021
P2PPort = "6690"
2122
)
2223

24+
var (
25+
once = &sync.Once{}
26+
)
27+
2328
// Input represents Chainlink node input
2429
type Input struct {
2530
DataProviderURL string `toml:"data_provider_url" validate:"required"`
@@ -32,6 +37,9 @@ type Input struct {
3237
type NodeInput struct {
3338
Image string `toml:"image" validate:"required"`
3439
Name string `toml:"name"`
40+
DockerFilePath string `toml:"docker_file"`
41+
DockerContext string `toml:"docker_ctx"`
42+
DockerImageName string `toml:"docker_image_name"`
3543
PullImage bool `toml:"pull_image"`
3644
CapabilitiesBinaryPaths []string `toml:"capabilities"`
3745
CapabilityContainerDir string `toml:"capabilities_container_dir"`
@@ -209,6 +217,13 @@ func newNode(in *Input, pgOut *postgres.Output) (*NodeOut, error) {
209217
})
210218
}
211219
req.Files = append(req.Files, files...)
220+
localImg, err := framework.RebuildDockerImage(once, in.Node.DockerFilePath, in.Node.DockerContext, in.Node.DockerImageName)
221+
if err != nil {
222+
return nil, err
223+
}
224+
if localImg != "" {
225+
req.Image = localImg
226+
}
212227
c, err := tc.GenericContainer(ctx, tc.GenericContainerRequest{
213228
ContainerRequest: req,
214229
Started: true,

framework/components/simple_node_set/node_set.go

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,9 @@ func oneNodeSharedDBConfiguration(in *Input, bcOut *blockchain.Output, fakeUrl s
5555
Image: in.NodeSpecs[overrideIdx].Node.Image,
5656
Name: nodeName,
5757
PullImage: in.NodeSpecs[overrideIdx].Node.PullImage,
58+
DockerFilePath: in.NodeSpecs[overrideIdx].Node.DockerFilePath,
59+
DockerContext: in.NodeSpecs[overrideIdx].Node.DockerContext,
60+
DockerImageName: in.NodeSpecs[overrideIdx].Node.DockerImageName,
5861
CapabilitiesBinaryPaths: in.NodeSpecs[overrideIdx].Node.CapabilitiesBinaryPaths,
5962
CapabilityContainerDir: in.NodeSpecs[overrideIdx].Node.CapabilityContainerDir,
6063
TestConfigOverrides: net,

framework/config.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ const (
2929
EnvVarTestConfigs = "CTF_CONFIGS"
3030
EnvVarLokiStream = "CTF_LOKI_STREAM"
3131
EnvVarAWSSecretsManager = "CTF_AWS_SECRETS_MANAGER"
32+
EnvVarDockerImagesBuild = "CTF_BUILD_DOCKER_IMAGES"
3233
// EnvVarCI this is a default env variable many CI runners use so code can detect we run in CI
3334
EnvVarCI = "CI"
3435
)

framework/docker.go

Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,15 @@ package framework
22

33
import (
44
"context"
5+
"errors"
56
"fmt"
67
"github.com/docker/go-connections/nat"
78
"github.com/google/uuid"
89
tc "github.com/testcontainers/testcontainers-go"
10+
"os"
11+
"os/exec"
12+
"strings"
13+
"sync"
914
)
1015

1116
func GetHost(container tc.Container) (string, error) {
@@ -41,3 +46,76 @@ func DefaultTCLabels() map[string]string {
4146
func DefaultTCName(name string) string {
4247
return fmt.Sprintf("%s-%s", name, uuid.NewString()[0:5])
4348
}
49+
50+
// BuildAndPublishLocalDockerImage runs Docker commands to set up a local registry, build an image, and push it.
51+
func BuildAndPublishLocalDockerImage(once *sync.Once, dockerfile string, buildContext string, imageName string) error {
52+
var retErr error
53+
once.Do(func() {
54+
registryRunning := isContainerRunning("local-registry")
55+
if registryRunning {
56+
fmt.Println("Local registry container is already running.")
57+
} else {
58+
L.Info().Msg("Starting local registry container...")
59+
err := runCommand("docker", "run", "-d", "-p", "5050:5000", "--name", "local-registry", "registry:2")
60+
if err != nil {
61+
retErr = fmt.Errorf("failed to start local registry: %w", err)
62+
}
63+
L.Info().Msg("Local registry started")
64+
}
65+
66+
img := fmt.Sprintf("localhost:5050/%s:latest", imageName)
67+
L.Info().Str("DockerFile", dockerfile).Str("Context", buildContext).Msg("Building Docker image")
68+
err := runCommand("docker", "build", "-t", fmt.Sprintf("localhost:5050/%s:latest", imageName), "-f", dockerfile, buildContext)
69+
if err != nil {
70+
retErr = fmt.Errorf("failed to build Docker image: %w", err)
71+
}
72+
L.Info().Msg("Docker image built successfully")
73+
74+
L.Info().Str("Image", img).Msg("Pushing Docker image to local registry")
75+
fmt.Println("Pushing Docker image to local registry...")
76+
err = runCommand("docker", "push", img)
77+
if err != nil {
78+
retErr = fmt.Errorf("failed to push Docker image: %w", err)
79+
}
80+
L.Info().Msg("Docker image pushed successfully")
81+
})
82+
return retErr
83+
}
84+
85+
// isContainerRunning checks if a Docker container with the given name is running.
86+
func isContainerRunning(containerName string) bool {
87+
cmd := exec.Command("docker", "ps", "--filter", fmt.Sprintf("name=%s", containerName), "--format", "{{.Names}}")
88+
output, err := cmd.Output()
89+
if err != nil {
90+
return false
91+
}
92+
return strings.Contains(string(output), containerName)
93+
}
94+
95+
// runCommand executes a command and prints the output.
96+
func runCommand(name string, args ...string) error {
97+
cmd := exec.Command(name, args...)
98+
cmd.Stdout = os.Stdout
99+
cmd.Stderr = os.Stderr
100+
return cmd.Run()
101+
}
102+
103+
// RebuildDockerImage rebuilds docker image if necessary
104+
func RebuildDockerImage(once *sync.Once, dockerfile string, buildContext string, imageName string) (string, error) {
105+
if os.Getenv(EnvVarDockerImagesBuild) == "true" {
106+
if dockerfile == "" {
107+
return "", errors.New("docker_file path must be provided")
108+
}
109+
if buildContext == "" {
110+
return "", errors.New("docker_ctx path must be provided")
111+
}
112+
if imageName == "" {
113+
return "", errors.New("docker_image_name must be provided")
114+
}
115+
if err := BuildAndPublishLocalDockerImage(once, dockerfile, buildContext, imageName); err != nil {
116+
return "", err
117+
}
118+
return fmt.Sprintf("localhost:5050/%s:latest", imageName), nil
119+
}
120+
return "", nil
121+
}

0 commit comments

Comments
 (0)