diff --git a/integration/dcbuild/Dockerfile b/integration/dcbuild/Dockerfile index 31374f7718..4562ae9e36 100644 --- a/integration/dcbuild/Dockerfile +++ b/integration/dcbuild/Dockerfile @@ -1,6 +1,6 @@ -FROM golang:1.17-alpine -RUN apk add curl -WORKDIR /go/src/github.com/tilt-dev/integration/dcbuild/cmd/dcbuild +FROM alpine +RUN apk add busybox-extras curl +WORKDIR /app ADD . . -RUN go install github.com/tilt-dev/integration/dcbuild/cmd/dcbuild -ENTRYPOINT /go/bin/dcbuild +RUN ./compile.sh +ENTRYPOINT ./start.sh ./main.sh diff --git a/integration/dcbuild/Tiltfile b/integration/dcbuild/Tiltfile index ecc07db746..6b472d4b68 100644 --- a/integration/dcbuild/Tiltfile +++ b/integration/dcbuild/Tiltfile @@ -1,4 +1,12 @@ # -*- mode: Python -*- docker_compose('docker-compose.yaml') -docker_build('gcr.io/windmill-test-containers/dcbuild', 'cmd/dcbuild', dockerfile='Dockerfile') + +docker_build('gcr.io/windmill-test-containers/dcbuild', + '.', + dockerfile='Dockerfile', + live_update=[ + sync('.', '/app'), + run('/app/compile.sh'), + run('/app/restart.sh'), + ]) diff --git a/integration/dcbuild/cmd/dcbuild/go.mod b/integration/dcbuild/cmd/dcbuild/go.mod deleted file mode 100644 index 641723f84b..0000000000 --- a/integration/dcbuild/cmd/dcbuild/go.mod +++ /dev/null @@ -1,3 +0,0 @@ -module github.com/tilt-dev/integration/dcbuild/cmd/dcbuild - -go 1.17 diff --git a/integration/dcbuild/cmd/dcbuild/main.go b/integration/dcbuild/cmd/dcbuild/main.go deleted file mode 100644 index 26c2cd5945..0000000000 --- a/integration/dcbuild/cmd/dcbuild/main.go +++ /dev/null @@ -1,17 +0,0 @@ -package main - -import ( - "log" - "net/http" -) - -// One service deployed with docker-compose -func main() { - http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { - msg := "🍄 One-Up! 🍄" - _, _ = w.Write([]byte(msg)) - }) - - log.Println("Serving onedc on 8000") - _ = http.ListenAndServe(":8000", nil) -} diff --git a/integration/dcbuild/compile.sh b/integration/dcbuild/compile.sh new file mode 100755 index 0000000000..070ad50e29 --- /dev/null +++ b/integration/dcbuild/compile.sh @@ -0,0 +1,3 @@ +#!/bin/sh + +cat source.txt | sed "s/MESSAGE/🍄 One-Up! 🍄/" > compiled.txt diff --git a/integration/dcbuild/main.sh b/integration/dcbuild/main.sh new file mode 100755 index 0000000000..b80aa6e84a --- /dev/null +++ b/integration/dcbuild/main.sh @@ -0,0 +1,6 @@ +#!/bin/sh + +set -ex + +cp compiled.txt index.html +exec httpd -f -p 8000 diff --git a/integration/dcbuild/restart.sh b/integration/dcbuild/restart.sh new file mode 100755 index 0000000000..1ca45f6121 --- /dev/null +++ b/integration/dcbuild/restart.sh @@ -0,0 +1,25 @@ +#!/bin/sh +# +# A helper script to restart a given process as part of a Live Update. +# +# Further reading: +# https://docs.tilt.dev/live_update_reference.html#restarting-your-process +# +# Usage: +# Copy start.sh and restart.sh to your container working dir. +# +# Make your container entrypoint: +# ./start.sh path-to-binary [args] +# +# To restart the container: +# ./restart.sh + +set -u + +touch restart.txt +PID="$(cat process.txt)" +if [ $? -ne 0 ]; then + echo "unable to read process.txt. was your process started with start.sh?" + exit 1 +fi +kill "$PID" diff --git a/integration/dcbuild/source.txt b/integration/dcbuild/source.txt new file mode 100644 index 0000000000..5c935299f0 --- /dev/null +++ b/integration/dcbuild/source.txt @@ -0,0 +1 @@ +MESSAGE diff --git a/integration/dcbuild/start.sh b/integration/dcbuild/start.sh new file mode 100755 index 0000000000..26c3084225 --- /dev/null +++ b/integration/dcbuild/start.sh @@ -0,0 +1,44 @@ +#!/bin/sh +# +# A helper script to restart a given process as part of a Live Update. +# +# Further reading: +# https://docs.tilt.dev/live_update_reference.html#restarting-your-process +# +# Usage: +# Copy start.sh and restart.sh to your container working dir. +# +# Make your container entrypoint: +# ./start.sh path-to-binary [args] +# +# To restart the container: +# ./restart.sh + +set -eu + +process_id="" + +trap quit TERM INT + +quit() { + if [ -n "$process_id" ]; then + kill $process_id + fi +} + +while true; do + rm -f restart.txt + + "$@" & + process_id=$! + echo "$process_id" > process.txt + set +e + wait $process_id + EXIT_CODE=$? + set -e + if [ ! -f restart.txt ]; then + echo "Exiting with code $EXIT_CODE" + exit $EXIT_CODE + fi + echo "Restarting" +done diff --git a/integration/dcbuild_test.go b/integration/dcbuild_test.go index 3925a9b4eb..72020d6b93 100644 --- a/integration/dcbuild_test.go +++ b/integration/dcbuild_test.go @@ -7,8 +7,12 @@ import ( "context" "testing" "time" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" ) +// Ensures live-update works on tilt-handled image builds in dockercompose func TestDockerComposeImageBuild(t *testing.T) { f := newDCFixture(t, "dcbuild") @@ -24,11 +28,20 @@ func TestDockerComposeImageBuild(t *testing.T) { }) }, "gcr.io/windmill-test-containers/dcbuild") - f.CurlUntil(ctx, "dcbuild", "localhost:8000", "🍄 One-Up! 🍄") + f.CurlUntil(ctx, "dcbuild", "localhost:8000/index.html", "🍄 One-Up! 🍄") + + cID1, err := f.dockerContainerID("dcbuild") + require.NoError(t, err) - f.ReplaceContents("cmd/dcbuild/main.go", "One-Up", "Two-Up") + f.ReplaceContents("compile.sh", "One-Up", "Two-Up") ctx, cancel = context.WithTimeout(f.ctx, time.Minute) defer cancel() - f.CurlUntil(ctx, "dcbuild", "localhost:8000", "🍄 Two-Up! 🍄") + f.CurlUntil(ctx, "dcbuild", "localhost:8000/index.html", "🍄 Two-Up! 🍄") + + cID2, err := f.dockerContainerID("dcbuild") + require.NoError(t, err) + + // Make sure the container was updated in-place + assert.Equal(t, cID1, cID2) } diff --git a/internal/engine/buildcontrol/build_control.go b/internal/engine/buildcontrol/build_control.go index a8ec3813d4..6173d83ade 100644 --- a/internal/engine/buildcontrol/build_control.go +++ b/internal/engine/buildcontrol/build_control.go @@ -584,7 +584,8 @@ func IsLiveUpdateTargetWaitingOnDeploy(state store.EngineState, mt *store.Manife } } else if mt.Manifest.IsDC() { - cInfos := liveupdates.RunningContainersForDC(mt.State.DockerResource()) + dcs := state.DockerComposeServices[mt.Manifest.Name.String()] + cInfos := liveupdates.RunningContainersForDC(dcs) if len(cInfos) != 0 { return false } diff --git a/internal/engine/buildcontrol/extractors.go b/internal/engine/buildcontrol/extractors.go index b500db9a0e..efb2f6f51d 100644 --- a/internal/engine/buildcontrol/extractors.go +++ b/internal/engine/buildcontrol/extractors.go @@ -116,7 +116,7 @@ func extractImageTargetsForLiveUpdates(specs []model.TargetSpec, stateSet store. containers, err := liveupdates.RunningContainers( state.KubernetesSelector, state.KubernetesResource, - state.DockerResource) + state.DockerComposeService) if err != nil { return nil, RedirectToNextBuilderInfof("Error retrieving container info: %v", err) @@ -140,7 +140,7 @@ func extractImageTargetsForLiveUpdates(specs []model.TargetSpec, stateSet store. filesChanged: filesChanged, containers: containers, hasFileChangesIDs: hasFileChangesIDs, - isDC: state.DockerResource != nil, + isDC: state.DockerComposeService != nil, }) } diff --git a/internal/engine/buildcontroller.go b/internal/engine/buildcontroller.go index 5bc8589321..5ff28d33f6 100644 --- a/internal/engine/buildcontroller.go +++ b/internal/engine/buildcontroller.go @@ -15,7 +15,6 @@ import ( "github.com/tilt-dev/tilt/internal/engine/buildcontrol" "github.com/tilt-dev/tilt/internal/store" "github.com/tilt-dev/tilt/internal/store/buildcontrols" - "github.com/tilt-dev/tilt/internal/store/dcconv" "github.com/tilt-dev/tilt/internal/store/k8sconv" "github.com/tilt-dev/tilt/pkg/apis/core/v1alpha1" "github.com/tilt-dev/tilt/pkg/model" @@ -81,6 +80,7 @@ func (c *BuildController) needsBuild(ctx context.Context, st store.RStore) (buil buildReason := mt.NextBuildReason() targets := buildcontrol.BuildTargets(manifest) buildStateSet := buildStateSet(ctx, manifest, state.KubernetesResources[manifest.Name.String()], + state.DockerComposeServices[manifest.Name.String()], targets, ms, buildReason) return buildEntry{ @@ -202,7 +202,9 @@ func SpanIDForBuildLog(buildCount int) logstore.SpanID { // Extract a set of build states from a manifest for BuildAndDeploy. func buildStateSet(ctx context.Context, manifest model.Manifest, - kresource *k8sconv.KubernetesResource, specs []model.TargetSpec, + kresource *k8sconv.KubernetesResource, + dcs *v1alpha1.DockerComposeService, + specs []model.TargetSpec, ms *store.ManifestState, reason model.BuildReason) store.BuildStateSet { result := store.BuildStateSet{} @@ -241,7 +243,7 @@ func buildStateSet(ctx context.Context, manifest model.Manifest, } if manifest.IsDC() { - buildState.DockerResource = &dcconv.DockerResource{ContainerID: string(ms.DCRuntimeState().ContainerID)} + buildState.DockerComposeService = dcs } } } diff --git a/internal/engine/buildcontroller_test.go b/internal/engine/buildcontroller_test.go index ffa8a6e0dc..78273d30b4 100644 --- a/internal/engine/buildcontroller_test.go +++ b/internal/engine/buildcontroller_test.go @@ -201,7 +201,7 @@ func TestBuildControllerDockerCompose(t *testing.T) { call = f.nextCall() s := call.state[imageTarget.ID()] containers, err := liveupdates.RunningContainers( - nil, nil, s.DockerResource) + nil, nil, s.DockerComposeService) require.NoError(t, err) assert.Equal(t, "dc-sancho", containers[0].ContainerID.String()) diff --git a/internal/engine/testdata_test.go b/internal/engine/testdata_test.go index 197fa0fc1a..bf64c4975c 100644 --- a/internal/engine/testdata_test.go +++ b/internal/engine/testdata_test.go @@ -56,6 +56,7 @@ func NewSanchoLiveUpdateDCManifest(f Fixture) model.Manifest { return manifestbuilder.New(f, "sancho"). WithDockerCompose(). WithImageTarget(NewSanchoLiveUpdateImageTarget(f)). + WithLiveUpdateBAD(). Build() } diff --git a/internal/engine/upper_test.go b/internal/engine/upper_test.go index cdc97fe028..ffa043ad4a 100644 --- a/internal/engine/upper_test.go +++ b/internal/engine/upper_test.go @@ -252,7 +252,8 @@ type fakeBuildAndDeployer struct { resultsByID store.BuildResultSet // kClient registers deployed entities for subsequent retrieval. - kClient *k8s.FakeK8sClient + kClient *k8s.FakeK8sClient + dcClient *dockercompose.FakeDCClient ctrlClient ctrlclient.Client @@ -358,15 +359,16 @@ func (b *fakeBuildAndDeployer) BuildAndDeploy(ctx context.Context, st store.RSto } if !call.dc().Empty() && len(b.nextLiveUpdateContainerIDs) == 0 { - err = b.updateDockerComposeServiceStatus(ctx, call.dc(), iTargets) - if err != nil { - return result, err - } - dcContainerID := container.ID(fmt.Sprintf("dc-%s", path.Base(call.dc().ID().Name.String()))) if b.nextDockerComposeContainerID != "" { dcContainerID = b.nextDockerComposeContainerID } + b.dcClient.ContainerIdOutput = dcContainerID + + err = b.updateDockerComposeServiceStatus(ctx, call.dc(), iTargets) + if err != nil { + return result, err + } dcContainerState := b.nextDockerComposeContainerState result[call.dc().ID()] = store.NewDockerComposeDeployResult( @@ -545,13 +547,14 @@ func (b *fakeBuildAndDeployer) waitUntilBuildCompleted(ctx context.Context, key } } -func newFakeBuildAndDeployer(t *testing.T, kClient *k8s.FakeK8sClient, ctrlClient ctrlclient.Client, kaReconciler *kubernetesapply.Reconciler, dcReconciler *dockercomposeservice.Reconciler) *fakeBuildAndDeployer { +func newFakeBuildAndDeployer(t *testing.T, kClient *k8s.FakeK8sClient, dcClient *dockercompose.FakeDCClient, ctrlClient ctrlclient.Client, kaReconciler *kubernetesapply.Reconciler, dcReconciler *dockercomposeservice.Reconciler) *fakeBuildAndDeployer { return &fakeBuildAndDeployer{ t: t, calls: make(chan buildAndDeployCall, 20), buildLogOutput: make(map[model.TargetID]string), resultsByID: store.BuildResultSet{}, kClient: kClient, + dcClient: dcClient, ctrlClient: ctrlClient, kaReconciler: kaReconciler, dcReconciler: dcReconciler, @@ -3563,7 +3566,7 @@ func newTestFixture(t *testing.T, options ...fixtureOptions) *testFixture { dp := dockerprune.NewDockerPruner(dockerClient) dp.DisabledForTesting(true) - b := newFakeBuildAndDeployer(t, kClient, cdc, kar, dcr) + b := newFakeBuildAndDeployer(t, kClient, fakeDcc, cdc, kar, dcr) bc := NewBuildController(b) ret := &testFixture{ diff --git a/internal/store/build_result.go b/internal/store/build_result.go index 8e16379539..1aaa7b52b1 100644 --- a/internal/store/build_result.go +++ b/internal/store/build_result.go @@ -6,7 +6,6 @@ import ( "github.com/docker/distribution/reference" "github.com/tilt-dev/tilt/internal/container" - "github.com/tilt-dev/tilt/internal/store/dcconv" "github.com/tilt-dev/tilt/internal/store/k8sconv" "github.com/tilt-dev/tilt/pkg/apis/core/v1alpha1" "github.com/tilt-dev/tilt/pkg/model" @@ -274,7 +273,7 @@ type BuildState struct { KubernetesResource *k8sconv.KubernetesResource - DockerResource *dcconv.DockerResource + DockerComposeService *v1alpha1.DockerComposeService } func NewBuildState(result BuildResult, files []string, pendingDeps []model.TargetID) BuildState { diff --git a/internal/store/dcconv/resource.go b/internal/store/dcconv/resource.go deleted file mode 100644 index 8f1f5bc14a..0000000000 --- a/internal/store/dcconv/resource.go +++ /dev/null @@ -1,9 +0,0 @@ -package dcconv - -// A DockerResource exposes a high-level status that summarizes -// the containers we care about in a DockerCompose session. -// -// Long-term, this may become an explicit API server object. -type DockerResource struct { - ContainerID string -} diff --git a/internal/store/engine_state.go b/internal/store/engine_state.go index 92951749df..c79f11904c 100644 --- a/internal/store/engine_state.go +++ b/internal/store/engine_state.go @@ -11,7 +11,6 @@ import ( "github.com/tilt-dev/tilt/internal/container" "github.com/tilt-dev/tilt/internal/dockercompose" "github.com/tilt-dev/tilt/internal/k8s" - "github.com/tilt-dev/tilt/internal/store/dcconv" "github.com/tilt-dev/tilt/internal/store/k8sconv" "github.com/tilt-dev/tilt/internal/timecmp" "github.com/tilt-dev/tilt/internal/token" @@ -600,14 +599,6 @@ func (ms *ManifestState) DCRuntimeState() dockercompose.State { return ret } -func (ms *ManifestState) DockerResource() *dcconv.DockerResource { - ret, ok := ms.RuntimeState.(dockercompose.State) - if !ok { - return nil - } - return &dcconv.DockerResource{ContainerID: string(ret.ContainerID)} -} - func (ms *ManifestState) IsDC() bool { _, ok := ms.RuntimeState.(dockercompose.State) return ok diff --git a/internal/store/liveupdates/containers.go b/internal/store/liveupdates/containers.go index 42dc223a90..28ef96fc18 100644 --- a/internal/store/liveupdates/containers.go +++ b/internal/store/liveupdates/containers.go @@ -9,14 +9,13 @@ import ( "github.com/tilt-dev/tilt/internal/container" "github.com/tilt-dev/tilt/internal/k8s" "github.com/tilt-dev/tilt/internal/store" - "github.com/tilt-dev/tilt/internal/store/dcconv" "github.com/tilt-dev/tilt/internal/store/k8sconv" "github.com/tilt-dev/tilt/pkg/apis/core/v1alpha1" ) func AllRunningContainers(mt *store.ManifestTarget, state *store.EngineState) []Container { if mt.Manifest.IsDC() { - return RunningContainersForDC(mt.State.DockerResource()) + return RunningContainersForDC(state.DockerComposeServices[mt.Manifest.Name.String()]) } var result []Container @@ -38,12 +37,12 @@ func AllRunningContainers(mt *store.ManifestTarget, state *store.EngineState) [] return result } -func RunningContainers(selector *v1alpha1.LiveUpdateKubernetesSelector, k8sResource *k8sconv.KubernetesResource, dResource *dcconv.DockerResource) ([]Container, error) { +func RunningContainers(selector *v1alpha1.LiveUpdateKubernetesSelector, k8sResource *k8sconv.KubernetesResource, dcs *v1alpha1.DockerComposeService) ([]Container, error) { if selector != nil && k8sResource != nil { return RunningContainersForOnePod(selector, k8sResource) } - if dResource != nil { - return RunningContainersForDC(dResource), nil + if dcs != nil { + return RunningContainersForDC(dcs), nil } return nil, nil } @@ -98,12 +97,12 @@ func RunningContainersForOnePod(selector *v1alpha1.LiveUpdateKubernetesSelector, return containers, nil } -func RunningContainersForDC(dr *dcconv.DockerResource) []Container { - if dr == nil || dr.ContainerID == "" { +func RunningContainersForDC(dcs *v1alpha1.DockerComposeService) []Container { + if dcs == nil || dcs.Status.ContainerID == "" { return nil } return []Container{ - Container{ContainerID: container.ID(dr.ContainerID)}, + Container{ContainerID: container.ID(dcs.Status.ContainerID)}, } } diff --git a/internal/store/liveupdates/fake.go b/internal/store/liveupdates/fake.go index d1ac9b5be0..3e5206b427 100644 --- a/internal/store/liveupdates/fake.go +++ b/internal/store/liveupdates/fake.go @@ -4,7 +4,6 @@ import ( "sort" "github.com/tilt-dev/tilt/internal/store" - "github.com/tilt-dev/tilt/internal/store/dcconv" "github.com/tilt-dev/tilt/internal/store/k8sconv" "github.com/tilt-dev/tilt/pkg/apis/core/v1alpha1" ) @@ -16,7 +15,11 @@ func WithFakeK8sContainers(s store.BuildState, imageName string, containers []Co } func WithFakeDCContainer(s store.BuildState, container Container) store.BuildState { - s.DockerResource = &dcconv.DockerResource{ContainerID: string(container.ContainerID)} + s.DockerComposeService = &v1alpha1.DockerComposeService{ + Status: v1alpha1.DockerComposeServiceStatus{ + ContainerID: string(container.ContainerID), + }, + } return s } diff --git a/internal/testutils/manifestbuilder/manifestbuilder.go b/internal/testutils/manifestbuilder/manifestbuilder.go index d96a6949f8..33d6266ca6 100644 --- a/internal/testutils/manifestbuilder/manifestbuilder.go +++ b/internal/testutils/manifestbuilder/manifestbuilder.go @@ -169,9 +169,6 @@ func (b ManifestBuilder) Build() model.Manifest { iTarget.LiveUpdateReconciler = false } else if b.useLiveUpdateBAD { iTarget.LiveUpdateReconciler = false - } else if len(b.dcConfigPaths) > 0 { - // Docker Compose must use the buildAndDeployer - iTarget.LiveUpdateReconciler = false } else { iTarget.LiveUpdateReconciler = true } diff --git a/internal/tiltfile/docker_compose.go b/internal/tiltfile/docker_compose.go index 52f5b79896..77fb340f75 100644 --- a/internal/tiltfile/docker_compose.go +++ b/internal/tiltfile/docker_compose.go @@ -22,7 +22,9 @@ import ( "go.starlark.net/starlark" "github.com/tilt-dev/tilt/internal/container" + "github.com/tilt-dev/tilt/internal/controllers/apis/liveupdate" "github.com/tilt-dev/tilt/internal/dockercompose" + "github.com/tilt-dev/tilt/internal/feature" "github.com/tilt-dev/tilt/internal/tiltfile/io" "github.com/tilt-dev/tilt/internal/tiltfile/links" "github.com/tilt-dev/tilt/internal/tiltfile/starkit" @@ -399,6 +401,16 @@ func (s *tiltfileState) dcServiceToManifest(service *dcService, dcSet dcResource mds = append(mds, model.ManifestName(md)) } + if s.features.Get(feature.LiveUpdateV2) { + for i, iTarget := range iTargets { + if liveupdate.IsEmptySpec(iTarget.LiveUpdateSpec) { + continue + } + iTarget.LiveUpdateReconciler = true + iTargets[i] = iTarget + } + } + m := model.Manifest{ Name: model.ManifestName(service.Name), TriggerMode: um, diff --git a/internal/tiltfile/tiltfile_state.go b/internal/tiltfile/tiltfile_state.go index 51f0dc7bbb..1a172e13d1 100644 --- a/internal/tiltfile/tiltfile_state.go +++ b/internal/tiltfile/tiltfile_state.go @@ -1127,8 +1127,6 @@ func (s *tiltfileState) translateK8s(resources []*k8sResource, updateSettings mo return nil, errors.Wrapf(err, "getting image build info for %s", r.name) } - // Currently, only Kubernetes ImageTargets support the reconciler, - // and only if the user has flagged it on. if s.features.Get(feature.LiveUpdateV2) { for i, iTarget := range iTargets { if liveupdate.IsEmptySpec(iTarget.LiveUpdateSpec) { diff --git a/pkg/model/manifest.go b/pkg/model/manifest.go index da86706388..1b4b5cd246 100644 --- a/pkg/model/manifest.go +++ b/pkg/model/manifest.go @@ -284,7 +284,6 @@ func (m *Manifest) InferLiveUpdateSelectors() error { continue } - // TODO(nick): Also set docker-compose selectors once the model supports it. if m.IsK8s() { kSelector := luSpec.Selector.Kubernetes if kSelector == nil { @@ -312,6 +311,18 @@ func (m *Manifest) InferLiveUpdateSelectors() error { } } + if m.IsDC() { + dcSelector := luSpec.Selector.DockerCompose + if dcSelector == nil { + dcSelector = &v1alpha1.LiveUpdateDockerComposeSelector{} + luSpec.Selector.DockerCompose = dcSelector + } + + if dcSelector.Service == "" { + dcSelector.Service = m.Name.String() + } + } + luSpec.Sources = nil err := dag.VisitTree(iTarget, func(dep TargetSpec) error { // Relies on the idea that ImageTargets creates