From d2d8cf21c5c265d04ec7732ebda1125cbcd6d2c7 Mon Sep 17 00:00:00 2001 From: Sergey Kudasov Date: Wed, 27 Nov 2024 23:50:42 +0100 Subject: [PATCH] simplify docker build, use testcontainers and build once, do not run Promtail in CI (#1401) * simplify docker build, use testcontainers and build once, do not run promtail in CI * changeset --- framework/.changeset/v0.2.13.md | 3 + framework/cmd/docker.go | 45 +---------- framework/cmd/interactive.go | 2 +- framework/cmd/main.go | 2 +- framework/components/clnode/clnode.go | 5 +- framework/config.go | 6 +- framework/docker.go | 106 +++++++------------------- 7 files changed, 40 insertions(+), 129 deletions(-) create mode 100644 framework/.changeset/v0.2.13.md diff --git a/framework/.changeset/v0.2.13.md b/framework/.changeset/v0.2.13.md new file mode 100644 index 000000000..f4a621cbf --- /dev/null +++ b/framework/.changeset/v0.2.13.md @@ -0,0 +1,3 @@ +- Simplify local Chainlink build +- Remove dependency on "local-registry" +- Skip Promtail to accelerate CI 🚀 \ No newline at end of file diff --git a/framework/cmd/docker.go b/framework/cmd/docker.go index b37ecc132..07f53081e 100644 --- a/framework/cmd/docker.go +++ b/framework/cmd/docker.go @@ -6,10 +6,9 @@ import ( "github.com/smartcontractkit/chainlink-testing-framework/framework" "os" "os/exec" - "strings" ) -func rmTestContainers() error { +func removeTestContainers() error { framework.L.Info().Str("label", "framework=ctf").Msg("Cleaning up docker containers") // Bash command for removing Docker containers and networks with "framework=ctf" label cmd := exec.Command("bash", "-c", ` @@ -45,48 +44,6 @@ func cleanUpDockerResources() error { return nil } -// BuildDocker runs Docker commands to set up a local registry, build an image, and push it. -func BuildDocker(dockerfile string, buildContext string, imageName string) error { - registryRunning := isContainerRunning("local-registry") - if registryRunning { - fmt.Println("Local registry container is already running.") - } else { - framework.L.Info().Msg("Starting local registry container...") - err := runCommand("docker", "run", "-d", "-p", "5050:5000", "--name", "local-registry", "registry:2") - if err != nil { - return fmt.Errorf("failed to start local registry: %w", err) - } - framework.L.Info().Msg("Local registry started") - } - - img := fmt.Sprintf("localhost:5050/%s:latest", imageName) - framework.L.Info().Str("DockerFile", dockerfile).Str("Context", buildContext).Msg("Building Docker image") - err := runCommand("docker", "build", "-t", fmt.Sprintf("localhost:5050/%s:latest", imageName), "-f", dockerfile, buildContext) - if err != nil { - return fmt.Errorf("failed to build Docker image: %w", err) - } - framework.L.Info().Msg("Docker image built successfully") - - framework.L.Info().Str("Image", img).Msg("Pushing Docker image to local registry") - fmt.Println("Pushing Docker image to local registry...") - err = runCommand("docker", "push", img) - if err != nil { - return fmt.Errorf("failed to push Docker image: %w", err) - } - framework.L.Info().Msg("Docker image pushed successfully") - return nil -} - -// isContainerRunning checks if a Docker container with the given name is running. -func isContainerRunning(containerName string) bool { - cmd := exec.Command("docker", "ps", "--filter", fmt.Sprintf("name=%s", containerName), "--format", "{{.Names}}") - output, err := cmd.Output() - if err != nil { - return false - } - return strings.Contains(string(output), containerName) -} - // runCommand executes a command and prints the output. func runCommand(name string, args ...string) error { cmd := exec.Command(name, args...) diff --git a/framework/cmd/interactive.go b/framework/cmd/interactive.go index 90636d682..5c685a9fa 100644 --- a/framework/cmd/interactive.go +++ b/framework/cmd/interactive.go @@ -94,7 +94,7 @@ func createComponentsFromForm(form *nodeSetForm) error { func cleanup(form *nodeSetForm) error { var err error f := func() { - err = rmTestContainers() + err = removeTestContainers() } err = spinner.New(). Title("Removing docker resources.."). diff --git a/framework/cmd/main.go b/framework/cmd/main.go index b8440f6dd..20dccd955 100644 --- a/framework/cmd/main.go +++ b/framework/cmd/main.go @@ -80,7 +80,7 @@ func main() { Aliases: []string{"rm"}, Usage: "Remove Docker containers and networks with 'framework=ctf' label", Action: func(c *cli.Context) error { - err := rmTestContainers() + err := removeTestContainers() if err != nil { return fmt.Errorf("failed to clean Docker resources: %w", err) } diff --git a/framework/components/clnode/clnode.go b/framework/components/clnode/clnode.go index 35f94b700..9be36fa12 100644 --- a/framework/components/clnode/clnode.go +++ b/framework/components/clnode/clnode.go @@ -21,6 +21,7 @@ import ( const ( DefaultHTTPPort = "6688" DefaultP2PPort = "6690" + TmpImageName = "chainlink-tmp:latest" ) var ( @@ -257,8 +258,8 @@ func newNode(in *Input, pgOut *postgres.Output) (*NodeOut, error) { return nil, errors.New("you provided both 'image' and one of 'docker_file', 'docker_ctx' fields. Please provide either 'image' or params to build a local one") } if req.Image == "" { - req.Image, err = framework.RebuildDockerImage(once, in.Node.DockerFilePath, in.Node.DockerContext, in.Node.DockerImageName) - if err != nil { + req.Image = TmpImageName + if err := framework.BuildImageOnce(ctx, once, in.Node.DockerContext, in.Node.DockerFilePath, req.Image); err != nil { return nil, err } req.KeepImage = false diff --git a/framework/config.go b/framework/config.go index e9b418393..56d3e1fe6 100644 --- a/framework/config.go +++ b/framework/config.go @@ -142,8 +142,10 @@ func Load[X any](t *testing.T) (*X, error) { //} err = DefaultNetwork(once) require.NoError(t, err) - err = NewPromtail() - require.NoError(t, err) + if os.Getenv(EnvVarCI) != "true" { + err = NewPromtail() + require.NoError(t, err) + } return input, nil } diff --git a/framework/docker.go b/framework/docker.go index 9b34598cd..ded5d550f 100644 --- a/framework/docker.go +++ b/framework/docker.go @@ -15,7 +15,6 @@ import ( "golang.org/x/sync/errgroup" "io" "os" - "os/exec" "path/filepath" "strings" "sync" @@ -70,84 +69,6 @@ func DefaultTCName(name string) string { return fmt.Sprintf("%s-%s", name, uuid.NewString()[0:5]) } -// BuildAndPublishLocalDockerImage runs Docker commands to set up a local registry, build an image, and push it. -func BuildAndPublishLocalDockerImage(once *sync.Once, dockerfile string, buildContext string, imageName string) error { - var retErr error - once.Do(func() { - L.Info(). - Str("Dockerfile", dockerfile). - Str("Ctx", buildContext). - Str("ImageName", imageName). - Msg("Building local docker file") - registryRunning := isContainerRunning("local-registry") - if registryRunning { - fmt.Println("Local registry container is already running.") - } else { - L.Info().Msg("Removing local registry") - _ = runCommand("docker", "stop", "local-registry") - _ = runCommand("docker", "rm", "local-registry") - L.Info().Msg("Starting local registry container...") - err := runCommand("docker", "run", "-d", "-p", "5050:5000", "--name", "local-registry", "registry:2") - if err != nil { - retErr = fmt.Errorf("failed to start local registry: %w", err) - } - L.Info().Msg("Local registry started") - } - - img := fmt.Sprintf("localhost:5050/%s:latest", imageName) - err := runCommand("docker", "build", "-t", fmt.Sprintf("localhost:5050/%s:latest", imageName), "-f", dockerfile, buildContext) - if err != nil { - retErr = fmt.Errorf("failed to build Docker image: %w", err) - } - L.Info().Msg("Docker image built successfully") - - L.Info().Str("Image", img).Msg("Pushing Docker image to local registry") - fmt.Println("Pushing Docker image to local registry...") - err = runCommand("docker", "push", img) - if err != nil { - retErr = fmt.Errorf("failed to push Docker image: %w", err) - } - L.Info().Msg("Docker image pushed successfully") - }) - return retErr -} - -// isContainerRunning checks if a Docker container with the given name is running. -func isContainerRunning(containerName string) bool { - cmd := exec.Command("docker", "ps", "--filter", fmt.Sprintf("name=%s", containerName), "--format", "{{.Names}}") - output, err := cmd.Output() - if err != nil { - return false - } - return strings.Contains(string(output), containerName) -} - -// runCommand executes a command and prints the output. -func runCommand(name string, args ...string) error { - cmd := exec.Command(name, args...) - cmd.Stdout = os.Stdout - cmd.Stderr = os.Stderr - return cmd.Run() -} - -// TODO: use tc.NewDockerProvider().BuildImage() to skip managing the registry container -// RebuildDockerImage rebuilds docker image if necessary -func RebuildDockerImage(once *sync.Once, dockerfile string, buildContext string, imageName string) (string, error) { - if dockerfile == "" { - return "", errors.New("docker_file path must be provided") - } - if buildContext == "" { - return "", errors.New("docker_ctx path must be provided") - } - if imageName == "" { - imageName = "ctftmp" - } - if err := BuildAndPublishLocalDockerImage(once, dockerfile, buildContext, imageName); err != nil { - return "", err - } - return fmt.Sprintf("localhost:5050/%s:latest", imageName), nil -} - // DockerClient wraps a Docker API client and provides convenience methods type DockerClient struct { cli *client.Client @@ -320,3 +241,30 @@ func WriteAllContainersLogs() error { } return eg.Wait() } + +func BuildImageOnce(ctx context.Context, once *sync.Once, dctx, dfile, nameAndTag string) error { + var ( + p *tc.DockerProvider + dockerCtx string + err error + ) + once.Do(func() { + nt := strings.Split(nameAndTag, ":") + if len(nt) != 2 { + err = errors.New("BuildImageOnce, tag must be in 'repo:tag' format") + return + } + p, err = tc.NewDockerProvider() + dockerCtx, err = filepath.Abs(dctx) + _, err = p.BuildImage(ctx, &tc.ContainerRequest{ + FromDockerfile: tc.FromDockerfile{ + Repo: nt[0], + Tag: nt[1], + Context: dockerCtx, + Dockerfile: dfile, + PrintBuildLog: true, + }, + }) + }) + return err +}