mirror of
https://github.com/docker/compose.git
synced 2026-10-12 02:13:08 +00:00
test(e2e): volume and build-once scenarios
- volumes: external-volume switching, volume recreation on definition change and image volumes become scenarios; the two-compose-file fixtures collapse into one model with an interpolated volume name or label, so the changed definition is visible in the test. The rebuild scenario locks the Recreated expectation and scopes the source image to the project. TestLocalComposeVolume (fixed host port, HTTP) and TestProjectVolumeBind (host file written mid-test) stay legacy. - build-once: the three run --build regression cases each get a scenario; OutputMatchesCount joins the vocabulary to count the build reports, replacing the hand-rolled regex counter, and image names are project-scoped instead of random suffixes. Signed-off-by: Nicolas De Loof <[email protected]>
This commit is contained in:
committed by
Nicolas De loof
parent
ee7f323fed
commit
f4f6ac5324
@@ -25,6 +25,7 @@ package e2e
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"regexp"
|
||||
"runtime"
|
||||
"slices"
|
||||
"strings"
|
||||
@@ -88,6 +89,21 @@ func StdoutContains(sub string) Check {
|
||||
}
|
||||
}
|
||||
|
||||
// OutputMatchesCount expects the regular expression to match the command's
|
||||
// stdout exactly n times, e.g. counting how many times a service was built.
|
||||
func OutputMatchesCount(pattern string, n int) Check {
|
||||
return Check{
|
||||
name: fmt.Sprintf("output matches %q %d time(s)", pattern, n),
|
||||
fn: func(ctx *CheckContext) error {
|
||||
matches := regexp.MustCompile(pattern).FindAllString(ctx.result.Stdout(), -1)
|
||||
if len(matches) != n {
|
||||
return fmt.Errorf("matched %d time(s)", len(matches))
|
||||
}
|
||||
return nil
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// StderrContains expects the command's stderr to contain a string, e.g. a
|
||||
// container's stderr stream relayed by up.
|
||||
func StderrContains(sub string) Check {
|
||||
|
||||
@@ -17,87 +17,47 @@
|
||||
package e2e
|
||||
|
||||
import (
|
||||
"crypto/rand"
|
||||
"encoding/hex"
|
||||
"fmt"
|
||||
"regexp"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"gotest.tools/v3/assert"
|
||||
)
|
||||
|
||||
// TestRunBuildOnce tests that services with pull_policy: build are only built once
|
||||
// when using 'docker compose run', even when they are dependencies.
|
||||
// This addresses a bug where dependencies were built twice: once in startDependencies
|
||||
// and once in ensureImagesExists.
|
||||
func TestRunBuildOnce(t *testing.T) {
|
||||
c := NewParallelCLI(t)
|
||||
// The build-once scenarios lock a regression where run built pull_policy:
|
||||
// build dependencies twice — once in startDependencies and once in
|
||||
// ensureImagesExists. Counting the "naming to ... done" build reports in the
|
||||
// verbose output is the observable.
|
||||
|
||||
t.Run("dependency with pull_policy build is built only once", func(t *testing.T) {
|
||||
projectName := randomProjectName("build-once")
|
||||
composeFile := "./fixtures/run-test/build-once.yaml"
|
||||
t.Cleanup(func() {
|
||||
c.RunDockerComposeCmd(t, "-p", projectName, "-f", composeFile, "down", "--rmi", "local", "--remove-orphans", "-v")
|
||||
})
|
||||
res := c.RunDockerComposeCmd(t, "-p", projectName, "-f", composeFile, "--verbose", "run", "--build", "--rm", "curl")
|
||||
|
||||
output := res.Stdout()
|
||||
|
||||
nginxBuilds := countServiceBuilds(output, projectName, "nginx")
|
||||
|
||||
assert.Equal(t, nginxBuilds, 1, "nginx should build once, built %d times\nOutput:\n%s", nginxBuilds, output)
|
||||
assert.Assert(t, strings.Contains(res.Stdout(), "curl service"))
|
||||
})
|
||||
|
||||
t.Run("nested dependencies build only once each", func(t *testing.T) {
|
||||
projectName := randomProjectName("build-nested")
|
||||
composeFile := "./fixtures/run-test/build-once-nested.yaml"
|
||||
t.Cleanup(func() {
|
||||
c.RunDockerComposeCmd(t, "-p", projectName, "-f", composeFile, "down", "--rmi", "local", "--remove-orphans", "-v")
|
||||
})
|
||||
res := c.RunDockerComposeCmd(t, "-p", projectName, "-f", composeFile, "--verbose", "run", "--build", "--rm", "app")
|
||||
|
||||
output := res.Stdout()
|
||||
|
||||
dbBuilds := countServiceBuilds(output, projectName, "db")
|
||||
apiBuilds := countServiceBuilds(output, projectName, "api")
|
||||
appBuilds := countServiceBuilds(output, projectName, "app")
|
||||
|
||||
assert.Equal(t, dbBuilds, 1, "db should build once, built %d times\nOutput:\n%s", dbBuilds, output)
|
||||
assert.Equal(t, apiBuilds, 1, "api should build once, built %d times\nOutput:\n%s", apiBuilds, output)
|
||||
assert.Equal(t, appBuilds, 1, "app should build once, built %d times\nOutput:\n%s", appBuilds, output)
|
||||
assert.Assert(t, strings.Contains(output, "App running"))
|
||||
})
|
||||
|
||||
t.Run("service with no dependencies builds once", func(t *testing.T) {
|
||||
projectName := randomProjectName("build-simple")
|
||||
composeFile := "./fixtures/run-test/build-once-no-deps.yaml"
|
||||
t.Cleanup(func() {
|
||||
c.RunDockerComposeCmd(t, "-p", projectName, "-f", composeFile, "down", "--rmi", "local", "--remove-orphans", "-v")
|
||||
})
|
||||
res := c.RunDockerComposeCmd(t, "-p", projectName, "-f", composeFile, "run", "--build", "--rm", "simple")
|
||||
|
||||
output := res.Stdout()
|
||||
|
||||
simpleBuilds := countServiceBuilds(output, projectName, "simple")
|
||||
|
||||
assert.Equal(t, simpleBuilds, 1, "simple should build once, built %d times\nOutput:\n%s", simpleBuilds, output)
|
||||
assert.Assert(t, strings.Contains(res.Stdout(), "Simple service"))
|
||||
})
|
||||
func builtOnce(project, service string) Check {
|
||||
return OutputMatchesCount(`naming to .*`+regexp.QuoteMeta(project)+`-`+regexp.QuoteMeta(service)+`.* done`, 1)
|
||||
}
|
||||
|
||||
// countServiceBuilds counts how many times a service was built by matching
|
||||
// the "naming to *{projectName}-{serviceName}* done" pattern in the output
|
||||
func countServiceBuilds(output, projectName, serviceName string) int {
|
||||
pattern := regexp.MustCompile(`naming to .*` + regexp.QuoteMeta(projectName) + `-` + regexp.QuoteMeta(serviceName) + `.* done`)
|
||||
return len(pattern.FindAllString(output, -1))
|
||||
func TestRunBuildOnceDependency(t *testing.T) {
|
||||
s := NewScenario(t, "run --build must build a pull_policy: build dependency exactly once")
|
||||
s.Defer(DockerCmd("image", "rm", "-f", s.Project()+"-nginx").MayFail()).
|
||||
Step("run builds the dependency once and executes the service",
|
||||
ComposeCmd("--verbose", "run", "--build", "--rm", "curl"),
|
||||
builtOnce(s.Project(), "nginx"),
|
||||
StdoutContains("curl service"))
|
||||
}
|
||||
|
||||
// randomProjectName generates a unique project name for parallel test execution
|
||||
// Format: prefix-<8 random hex chars> (e.g., "build-once-3f4a9b2c")
|
||||
func randomProjectName(prefix string) string {
|
||||
b := make([]byte, 4) // 4 bytes = 8 hex chars
|
||||
rand.Read(b)
|
||||
return fmt.Sprintf("%s-%s", prefix, hex.EncodeToString(b))
|
||||
func TestRunBuildOnceNestedDependencies(t *testing.T) {
|
||||
s := NewScenario(t, "run --build must build each service of a dependency chain exactly once")
|
||||
s.Defer(
|
||||
DockerCmd("image", "rm", "-f", s.Project()+"-db").MayFail(),
|
||||
DockerCmd("image", "rm", "-f", s.Project()+"-api").MayFail(),
|
||||
DockerCmd("image", "rm", "-f", s.Project()+"-app").MayFail()).
|
||||
Step("run builds the whole chain, each image once",
|
||||
ComposeCmd("--verbose", "run", "--build", "--rm", "app"),
|
||||
builtOnce(s.Project(), "db"),
|
||||
builtOnce(s.Project(), "api"),
|
||||
builtOnce(s.Project(), "app"),
|
||||
StdoutContains("App running"))
|
||||
}
|
||||
|
||||
func TestRunBuildOnceNoDeps(t *testing.T) {
|
||||
s := NewScenario(t, "run --build on a dependency-less service must build it exactly once")
|
||||
s.Defer(DockerCmd("image", "rm", "-f", s.Project()+"-simple").MayFail()).
|
||||
Step("run builds the service once and executes it",
|
||||
ComposeCmd("run", "--build", "--rm", "simple"),
|
||||
builtOnce(s.Project(), "simple"),
|
||||
StdoutContains("Simple service"))
|
||||
}
|
||||
|
||||
@@ -1,21 +0,0 @@
|
||||
# syntax=docker/dockerfile:1
|
||||
#
|
||||
# Copyright 2020 Docker Compose CLI authors
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
#
|
||||
|
||||
FROM alpine
|
||||
WORKDIR /app
|
||||
ARG CONTENT=initial
|
||||
RUN echo "$CONTENT" > /app/content.txt
|
||||
@@ -1,10 +0,0 @@
|
||||
services:
|
||||
app:
|
||||
image: alpine
|
||||
volumes:
|
||||
- my_vol:/my_vol
|
||||
|
||||
volumes:
|
||||
my_vol:
|
||||
labels:
|
||||
foo: zot
|
||||
@@ -1,10 +0,0 @@
|
||||
services:
|
||||
app:
|
||||
image: alpine
|
||||
volumes:
|
||||
- my_vol:/my_vol
|
||||
|
||||
volumes:
|
||||
my_vol:
|
||||
external: true
|
||||
name: test_external_volume_2
|
||||
+1
-1
@@ -7,4 +7,4 @@ services:
|
||||
source: nginx:alpine
|
||||
target: /mnt/image
|
||||
image:
|
||||
subpath: usr/share/nginx/html/
|
||||
subpath: usr/share/nginx/html/
|
||||
@@ -0,0 +1,5 @@
|
||||
FROM alpine
|
||||
WORKDIR /app
|
||||
ARG CONTENT=initial
|
||||
RUN echo "$CONTENT" > /app/content.txt
|
||||
|
||||
+2
-2
@@ -3,7 +3,7 @@ services:
|
||||
build:
|
||||
context: .
|
||||
dockerfile: Dockerfile
|
||||
image: image-volume-source
|
||||
image: ${SOURCE_IMAGE}
|
||||
|
||||
consumer:
|
||||
image: alpine
|
||||
@@ -12,7 +12,7 @@ services:
|
||||
command: ["cat", "/data/content.txt"]
|
||||
volumes:
|
||||
- type: image
|
||||
source: image-volume-source
|
||||
source: ${SOURCE_IMAGE}
|
||||
target: /data
|
||||
image:
|
||||
subpath: app
|
||||
pkg/e2e/fixtures/run-test/build-once.yaml → pkg/e2e/testdata/TestRunBuildOnceDependency/compose.yaml
Vendored
-4
@@ -1,6 +1,4 @@
|
||||
services:
|
||||
# Service with pull_policy: build to ensure it always rebuilds
|
||||
# This is the key to testing the bug - without the fix, this would build twice
|
||||
nginx:
|
||||
pull_policy: build
|
||||
build:
|
||||
@@ -9,10 +7,8 @@ services:
|
||||
RUN echo "Nginx built at $(date)" > /build-time.txt
|
||||
CMD sleep 3600
|
||||
|
||||
# Service that depends on nginx
|
||||
curl:
|
||||
image: alpine
|
||||
depends_on:
|
||||
- nginx
|
||||
command: echo "curl service"
|
||||
|
||||
-4
@@ -1,5 +1,4 @@
|
||||
services:
|
||||
# Database service with build
|
||||
db:
|
||||
pull_policy: build
|
||||
build:
|
||||
@@ -8,7 +7,6 @@ services:
|
||||
RUN echo "DB built at $(date)" > /db-build.txt
|
||||
CMD sleep 3600
|
||||
|
||||
# API service that depends on db
|
||||
api:
|
||||
pull_policy: build
|
||||
build:
|
||||
@@ -19,7 +17,6 @@ services:
|
||||
depends_on:
|
||||
- db
|
||||
|
||||
# App service that depends on api (which depends on db)
|
||||
app:
|
||||
pull_policy: build
|
||||
build:
|
||||
@@ -29,4 +26,3 @@ services:
|
||||
CMD echo "App running"
|
||||
depends_on:
|
||||
- api
|
||||
|
||||
-2
@@ -1,5 +1,4 @@
|
||||
services:
|
||||
# Simple service with no dependencies
|
||||
simple:
|
||||
pull_policy: build
|
||||
build:
|
||||
@@ -7,4 +6,3 @@ services:
|
||||
FROM alpine
|
||||
RUN echo "Simple built at $(date)" > /build.txt
|
||||
CMD echo "Simple service"
|
||||
|
||||
pkg/e2e/fixtures/recreate-volumes/compose.yaml → pkg/e2e/testdata/TestUpRecreateVolumes/compose.yaml
Vendored
+1
-1
@@ -7,4 +7,4 @@ services:
|
||||
volumes:
|
||||
my_vol:
|
||||
labels:
|
||||
foo: bar
|
||||
foo: ${VOL_LABEL:-bar}
|
||||
Vendored
+1
-1
@@ -7,4 +7,4 @@ services:
|
||||
volumes:
|
||||
my_vol:
|
||||
external: true
|
||||
name: test_external_volume
|
||||
name: ${EXTERNAL_VOLUME}
|
||||
+63
-102
@@ -17,7 +17,6 @@
|
||||
package e2e
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
"path/filepath"
|
||||
"runtime"
|
||||
@@ -121,74 +120,57 @@ func TestProjectVolumeBind(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestUpSwitchVolumes(t *testing.T) {
|
||||
c := NewCLI(t)
|
||||
const projectName = "compose-e2e-switch-volumes"
|
||||
t.Cleanup(func() {
|
||||
c.cleanupWithDown(t, projectName)
|
||||
c.RunDockerCmd(t, "volume", "rm", "-f", "test_external_volume")
|
||||
c.RunDockerCmd(t, "volume", "rm", "-f", "test_external_volume_2")
|
||||
})
|
||||
|
||||
c.RunDockerCmd(t, "volume", "create", "test_external_volume")
|
||||
c.RunDockerCmd(t, "volume", "create", "test_external_volume_2")
|
||||
|
||||
c.RunDockerComposeCmd(t, "-f", "./fixtures/switch-volumes/compose.yaml", "--project-name", projectName, "up", "-d")
|
||||
|
||||
res := c.RunDockerCmd(t, "inspect", fmt.Sprintf("%s-app-1", projectName), "-f", "{{ (index .Mounts 0).Name }}")
|
||||
res.Assert(t, icmd.Expected{Out: "test_external_volume"})
|
||||
|
||||
c.RunDockerComposeCmd(t, "-f", "./fixtures/switch-volumes/compose2.yaml", "--project-name", projectName, "up", "-d")
|
||||
res = c.RunDockerCmd(t, "inspect", fmt.Sprintf("%s-app-1", projectName), "-f", "{{ (index .Mounts 0).Name }}")
|
||||
res.Assert(t, icmd.Expected{Out: "test_external_volume_2"})
|
||||
s := NewScenario(t, "switching a service's external volume must reconnect the container to the new one")
|
||||
vol1, vol2 := s.Project()+"-ext-1", s.Project()+"-ext-2"
|
||||
s.Defer(
|
||||
DockerCmd("volume", "rm", "-f", vol1).MayFail(),
|
||||
DockerCmd("volume", "rm", "-f", vol2).MayFail()).
|
||||
Step("create the two external volumes",
|
||||
DockerCmd("volume", "create", vol1)).
|
||||
Step("and the second one",
|
||||
DockerCmd("volume", "create", vol2)).
|
||||
Step("up mounts the first volume",
|
||||
ComposeCmd("up", "-d").WithEnv("EXTERNAL_VOLUME="+vol1)).
|
||||
Step("the container's mount points at the first volume",
|
||||
DockerCmd("inspect", s.Project()+"-app-1", "-f", "{{ (index .Mounts 0).Name }}"),
|
||||
OutputContains(vol1)).
|
||||
Step("up with the other volume reconnects the service",
|
||||
ComposeCmd("up", "-d").WithEnv("EXTERNAL_VOLUME="+vol2)).
|
||||
Step("the container's mount points at the second volume",
|
||||
DockerCmd("inspect", s.Project()+"-app-1", "-f", "{{ (index .Mounts 0).Name }}"),
|
||||
OutputContains(vol2))
|
||||
}
|
||||
|
||||
func TestUpRecreateVolumes(t *testing.T) {
|
||||
c := NewCLI(t)
|
||||
const projectName = "compose-e2e-recreate-volumes"
|
||||
t.Cleanup(func() {
|
||||
c.cleanupWithDown(t, projectName)
|
||||
})
|
||||
|
||||
c.RunDockerComposeCmd(t, "-f", "./fixtures/recreate-volumes/compose.yaml", "--project-name", projectName, "up", "-d")
|
||||
|
||||
res := c.RunDockerCmd(t, "volume", "inspect", fmt.Sprintf("%s_my_vol", projectName), "-f", "{{ index .Labels \"foo\" }}")
|
||||
res.Assert(t, icmd.Expected{Out: "bar"})
|
||||
|
||||
c.RunDockerComposeCmd(t, "-f", "./fixtures/recreate-volumes/compose2.yaml", "--project-name", projectName, "up", "-d", "-y")
|
||||
res = c.RunDockerCmd(t, "volume", "inspect", fmt.Sprintf("%s_my_vol", projectName), "-f", "{{ index .Labels \"foo\" }}")
|
||||
res.Assert(t, icmd.Expected{Out: "zot"})
|
||||
s := NewScenario(t, "a volume definition change must recreate the volume on an approved up")
|
||||
s.Step("up creates the volume with its declared label",
|
||||
ComposeCmd("up", "-d")).
|
||||
Step("the volume carries the initial label",
|
||||
DockerCmd("volume", "inspect", s.Project()+"_my_vol", "-f", `{{ index .Labels "foo" }}`),
|
||||
OutputContains("bar")).
|
||||
Step("up -y with a changed label recreates the volume",
|
||||
ComposeCmd("up", "-d", "-y").WithEnv("VOL_LABEL=zot")).
|
||||
Step("the volume carries the new label",
|
||||
DockerCmd("volume", "inspect", s.Project()+"_my_vol", "-f", `{{ index .Labels "foo" }}`),
|
||||
OutputContains("zot"))
|
||||
}
|
||||
|
||||
func TestUpRecreateVolumes_IgnoreBinds(t *testing.T) {
|
||||
c := NewCLI(t)
|
||||
const projectName = "compose-e2e-recreate-volumes"
|
||||
t.Cleanup(func() {
|
||||
c.cleanupWithDown(t, projectName)
|
||||
})
|
||||
|
||||
c.RunDockerComposeCmd(t, "-f", "./fixtures/recreate-volumes/bind.yaml", "--project-name", projectName, "up", "-d")
|
||||
|
||||
res := c.RunDockerComposeCmd(t, "-f", "./fixtures/recreate-volumes/bind.yaml", "--project-name", projectName, "up", "-d")
|
||||
assert.Check(t, !strings.Contains(res.Combined(), "Recreated"))
|
||||
func TestUpRecreateVolumesIgnoreBinds(t *testing.T) {
|
||||
NewScenario(t, "a bind mount must not be mistaken for a volume change on an unchanged up").
|
||||
Step("up starts the service with its bind mount",
|
||||
ComposeCmd("up", "-d")).
|
||||
Step("an unchanged up leaves the container alone",
|
||||
ComposeCmd("up", "-d"),
|
||||
OutputNotContains("Recreated"),
|
||||
NotRecreated("app"))
|
||||
}
|
||||
|
||||
func TestImageVolume(t *testing.T) {
|
||||
c := NewCLI(t)
|
||||
const projectName = "compose-e2e-image-volume"
|
||||
t.Cleanup(func() {
|
||||
c.cleanupWithDown(t, projectName)
|
||||
})
|
||||
|
||||
version := c.RunDockerCmd(t, "version", "-f", "{{.Server.Version}}")
|
||||
major, _, found := strings.Cut(version.Combined(), ".")
|
||||
assert.Assert(t, found)
|
||||
if major == "26" || major == "27" {
|
||||
t.Skip("Skipping test due to docker version < 28")
|
||||
}
|
||||
|
||||
res := c.RunDockerComposeCmd(t, "-f", "./fixtures/volumes/compose.yaml", "--project-name", projectName, "up", "with_image")
|
||||
out := res.Combined()
|
||||
assert.Check(t, strings.Contains(out, "index.html"))
|
||||
NewScenario(t, "an image volume must mount the source image's content at the requested subpath").
|
||||
Requires(EngineVersionAtLeast(28)).
|
||||
Step("up mounts the image content",
|
||||
ComposeCmd("up", "with_image"),
|
||||
OutputContains("index.html"))
|
||||
}
|
||||
|
||||
func TestImageVolumeImageAlreadyLocal(t *testing.T) {
|
||||
@@ -212,45 +194,24 @@ func TestImageVolumeImageAlreadyLocal(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestImageVolumeRecreateOnRebuild(t *testing.T) {
|
||||
c := NewCLI(t)
|
||||
const projectName = "compose-e2e-image-volume-recreate"
|
||||
t.Cleanup(func() {
|
||||
c.cleanupWithDown(t, projectName)
|
||||
c.RunDockerOrExitError(t, "rmi", "-f", "image-volume-source")
|
||||
})
|
||||
|
||||
version := c.RunDockerCmd(t, "version", "-f", "{{.Server.Version}}")
|
||||
major, _, found := strings.Cut(version.Combined(), ".")
|
||||
assert.Assert(t, found)
|
||||
if major == "26" || major == "27" {
|
||||
t.Skip("Skipping test due to docker version < 28")
|
||||
}
|
||||
|
||||
// First build and run with initial content
|
||||
c.RunDockerComposeCmd(t, "-f", "./fixtures/image-volume-recreate/compose.yaml",
|
||||
"--project-name", projectName, "build", "--build-arg", "CONTENT=foo")
|
||||
res := c.RunDockerComposeCmd(t, "-f", "./fixtures/image-volume-recreate/compose.yaml",
|
||||
"--project-name", projectName, "up", "-d")
|
||||
assert.Check(t, !strings.Contains(res.Combined(), "error"))
|
||||
|
||||
// Check initial content
|
||||
res = c.RunDockerComposeCmd(t, "-f", "./fixtures/image-volume-recreate/compose.yaml",
|
||||
"--project-name", projectName, "logs", "consumer")
|
||||
assert.Check(t, strings.Contains(res.Combined(), "foo"), "Expected 'foo' in output, got: %s", res.Combined())
|
||||
|
||||
// Rebuild source image with different content
|
||||
c.RunDockerComposeCmd(t, "-f", "./fixtures/image-volume-recreate/compose.yaml",
|
||||
"--project-name", projectName, "build", "--build-arg", "CONTENT=bar")
|
||||
|
||||
// Run up again - consumer should be recreated because source image changed
|
||||
res = c.RunDockerComposeCmd(t, "-f", "./fixtures/image-volume-recreate/compose.yaml",
|
||||
"--project-name", projectName, "up", "-d")
|
||||
// The consumer container should be recreated
|
||||
assert.Check(t, strings.Contains(res.Combined(), "Recreate") || strings.Contains(res.Combined(), "Created"),
|
||||
"Expected container to be recreated, got: %s", res.Combined())
|
||||
|
||||
// Check updated content
|
||||
res = c.RunDockerComposeCmd(t, "-f", "./fixtures/image-volume-recreate/compose.yaml",
|
||||
"--project-name", projectName, "logs", "consumer")
|
||||
assert.Check(t, strings.Contains(res.Combined(), "bar"), "Expected 'bar' in output after rebuild, got: %s", res.Combined())
|
||||
s := NewScenario(t, "rebuilding an image-volume source must recreate its consumer to expose the new content")
|
||||
image := s.Project() + "-source-image"
|
||||
s.Requires(EngineVersionAtLeast(28)).
|
||||
Env("SOURCE_IMAGE="+image).
|
||||
Defer(DockerCmd("image", "rm", "-f", image).MayFail()).
|
||||
Step("build the source image with its initial content",
|
||||
ComposeCmd("build", "--build-arg", "CONTENT=foo")).
|
||||
Step("up mounts the image volume into the consumer",
|
||||
ComposeCmd("up", "-d")).
|
||||
Step("the consumer read the initial content",
|
||||
ComposeCmd("logs", "consumer"),
|
||||
OutputContains("foo")).
|
||||
Step("rebuild the source with new content",
|
||||
ComposeCmd("build", "--build-arg", "CONTENT=bar")).
|
||||
Step("up recreates the consumer against the rebuilt image",
|
||||
ComposeCmd("up", "-d"),
|
||||
Recreated("consumer")).
|
||||
Step("the consumer read the new content",
|
||||
ComposeCmd("logs", "consumer"),
|
||||
OutputContains("bar"))
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user