github.com/tilt-dev/tilt@v0.33.15-0.20240515162809-0a22ed45d8a0/internal/controllers/core/dockercomposeservice/reconciler_test.go (about) 1 package dockercomposeservice 2 3 import ( 4 "context" 5 "testing" 6 "time" 7 8 dtypes "github.com/docker/docker/api/types" 9 "github.com/jonboulle/clockwork" 10 "github.com/stretchr/testify/assert" 11 "github.com/stretchr/testify/require" 12 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 13 "k8s.io/apimachinery/pkg/types" 14 "sigs.k8s.io/controller-runtime/pkg/reconcile" 15 16 "github.com/tilt-dev/tilt/internal/controllers/apicmp" 17 "github.com/tilt-dev/tilt/internal/controllers/fake" 18 "github.com/tilt-dev/tilt/internal/docker" 19 "github.com/tilt-dev/tilt/internal/dockercompose" 20 "github.com/tilt-dev/tilt/internal/store" 21 "github.com/tilt-dev/tilt/internal/store/dockercomposeservices" 22 "github.com/tilt-dev/tilt/internal/testutils/manifestbuilder" 23 "github.com/tilt-dev/tilt/internal/testutils/tempdir" 24 "github.com/tilt-dev/tilt/pkg/apis/core/v1alpha1" 25 ) 26 27 func TestImageIndexing(t *testing.T) { 28 f := newFixture(t) 29 obj := v1alpha1.DockerComposeService{ 30 ObjectMeta: metav1.ObjectMeta{ 31 Name: "a", 32 }, 33 Spec: v1alpha1.DockerComposeServiceSpec{ 34 ImageMaps: []string{"image-a", "image-c"}, 35 }, 36 } 37 f.Create(&obj) 38 39 // Verify we can index one image map. 40 reqs := f.r.indexer.Enqueue(context.Background(), 41 &v1alpha1.ImageMap{ObjectMeta: metav1.ObjectMeta{Name: "image-a"}}) 42 assert.ElementsMatch(t, []reconcile.Request{ 43 {NamespacedName: types.NamespacedName{Name: "a"}}, 44 }, reqs) 45 } 46 47 func TestForceApply(t *testing.T) { 48 f := newFixture(t) 49 nn := types.NamespacedName{Name: "fe"} 50 obj := v1alpha1.DockerComposeService{ 51 ObjectMeta: metav1.ObjectMeta{ 52 Name: "fe", 53 Annotations: map[string]string{ 54 v1alpha1.AnnotationManagedBy: "buildcontrol", 55 }, 56 }, 57 Spec: v1alpha1.DockerComposeServiceSpec{ 58 Service: "fe", 59 Project: v1alpha1.DockerComposeProject{ 60 YAML: "fake-yaml", 61 }, 62 }, 63 } 64 f.Create(&obj) 65 f.MustReconcile(nn) 66 f.MustGet(nn, &obj) 67 assert.True(t, obj.Status.LastApplyStartTime.IsZero()) 68 69 status := f.r.ForceApply(f.Context(), nn, obj.Spec, nil, false) 70 assert.False(t, status.LastApplyStartTime.IsZero()) 71 assert.Equal(t, "", status.ApplyError) 72 assert.Equal(t, true, status.ContainerState.Running) 73 74 f.MustReconcile(nn) 75 f.MustGet(nn, &obj) 76 assert.True(t, apicmp.DeepEqual(status, obj.Status)) 77 f.assertSteadyState(&obj) 78 } 79 80 func TestAutoApply(t *testing.T) { 81 f := newFixture(t) 82 nn := types.NamespacedName{Name: "fe"} 83 obj := v1alpha1.DockerComposeService{ 84 ObjectMeta: metav1.ObjectMeta{ 85 Name: "fe", 86 }, 87 Spec: v1alpha1.DockerComposeServiceSpec{ 88 Service: "fe", 89 Project: v1alpha1.DockerComposeProject{ 90 YAML: "fake-yaml", 91 }, 92 }, 93 } 94 f.Create(&obj) 95 f.MustReconcile(nn) 96 f.MustGet(nn, &obj) 97 98 assert.False(t, obj.Status.LastApplyStartTime.IsZero()) 99 assert.Equal(t, "", obj.Status.ApplyError) 100 assert.Equal(t, true, obj.Status.ContainerState.Running) 101 f.assertSteadyState(&obj) 102 } 103 104 func TestLogObject(t *testing.T) { 105 f := newFixture(t) 106 nn := types.NamespacedName{Name: "fe"} 107 obj := v1alpha1.DockerComposeService{ 108 ObjectMeta: metav1.ObjectMeta{ 109 Name: "fe", 110 }, 111 Spec: v1alpha1.DockerComposeServiceSpec{ 112 Service: "fe", 113 Project: v1alpha1.DockerComposeProject{ 114 YAML: "fake-yaml", 115 }, 116 }, 117 } 118 f.Create(&obj) 119 f.MustReconcile(nn) 120 f.MustGet(nn, &obj) 121 122 var log v1alpha1.DockerComposeLogStream 123 f.MustGet(nn, &log) 124 assert.False(t, obj.Status.LastApplyStartTime.IsZero()) 125 assert.Equal(t, "fe", log.Spec.Service) 126 assert.Equal(t, "fake-yaml", log.Spec.Project.YAML) 127 128 _, _ = f.Delete(&obj) 129 assert.False(t, f.Get(nn, &log)) 130 } 131 132 func TestContainerEvent(t *testing.T) { 133 f := newFixture(t) 134 nn := types.NamespacedName{Name: "fe"} 135 obj := v1alpha1.DockerComposeService{ 136 ObjectMeta: metav1.ObjectMeta{ 137 Name: "fe", 138 Annotations: map[string]string{ 139 v1alpha1.AnnotationManifest: "fe", 140 }, 141 }, 142 Spec: v1alpha1.DockerComposeServiceSpec{ 143 Service: "fe", 144 Project: v1alpha1.DockerComposeProject{ 145 YAML: "fake-yaml", 146 }, 147 }, 148 } 149 f.Create(&obj) 150 151 status := f.r.ForceApply(f.Context(), nn, obj.Spec, nil, false) 152 assert.Equal(t, "", status.ApplyError) 153 assert.Equal(t, true, status.ContainerState.Running) 154 155 container := dtypes.ContainerState{ 156 Status: "exited", 157 Running: false, 158 ExitCode: 0, 159 StartedAt: "2021-09-08T19:58:01.483005100Z", 160 FinishedAt: "2021-09-08T19:58:01.483005100Z", 161 } 162 containerID := "my-container-id" 163 f.dc.Containers[containerID] = container 164 165 event := dockercompose.Event{Type: dockercompose.TypeContainer, ID: containerID, Service: "fe"} 166 f.dcc.SendEvent(event) 167 168 require.Eventually(t, func() bool { 169 f.MustReconcile(nn) 170 f.MustGet(nn, &obj) 171 return obj.Status.ContainerState.Status == "exited" 172 }, time.Second, 10*time.Millisecond, "container exited") 173 174 assert.Equal(t, containerID, obj.Status.ContainerID) 175 176 f.MustReconcile(nn) 177 tmpf := tempdir.NewTempDirFixture(t) 178 s := store.NewState() 179 m := manifestbuilder.New(tmpf, "fe").WithDockerCompose().Build() 180 s.UpsertManifestTarget(store.NewManifestTarget(m)) 181 182 for _, action := range f.Store.Actions() { 183 switch action := action.(type) { 184 case dockercomposeservices.DockerComposeServiceUpsertAction: 185 dockercomposeservices.HandleDockerComposeServiceUpsertAction(s, action) 186 } 187 } 188 189 assert.Equal(t, "exited", 190 s.ManifestTargets["fe"].State.DCRuntimeState().ContainerState.Status) 191 } 192 193 func TestForceDelete(t *testing.T) { 194 f := newFixture(t) 195 nn := types.NamespacedName{Name: "fe"} 196 obj := v1alpha1.DockerComposeService{ 197 ObjectMeta: metav1.ObjectMeta{ 198 Name: "fe", 199 Annotations: map[string]string{ 200 v1alpha1.AnnotationManifest: "fe", 201 }, 202 }, 203 Spec: v1alpha1.DockerComposeServiceSpec{ 204 Service: "fe", 205 Project: v1alpha1.DockerComposeProject{ 206 YAML: "fake-yaml", 207 }, 208 }, 209 } 210 f.Create(&obj) 211 212 err := f.r.ForceDelete(f.Context(), nn, obj.Spec, "testing") 213 assert.Nil(f.T(), err) 214 assert.Equal(f.T(), f.dcc.RmCalls()[0].Specs[0].Service, "fe") 215 } 216 217 type fixture struct { 218 *fake.ControllerFixture 219 r *Reconciler 220 dc *docker.FakeClient 221 dcc *dockercompose.FakeDCClient 222 } 223 224 func newFixture(t *testing.T) *fixture { 225 cfb := fake.NewControllerFixtureBuilder(t) 226 dcCli := dockercompose.NewFakeDockerComposeClient(t, cfb.Context()) 227 dcCli.ContainerIDDefault = "fake-cid" 228 dCli := docker.NewFakeClient() 229 clock := clockwork.NewFakeClock() 230 watcher := NewDisableSubscriber(cfb.Context(), dcCli, clock) 231 r := NewReconciler(cfb.Client, dcCli, dCli, cfb.Store, v1alpha1.NewScheme(), watcher) 232 233 return &fixture{ 234 ControllerFixture: cfb.Build(r), 235 r: r, 236 dc: dCli, 237 dcc: dcCli, 238 } 239 } 240 241 func (f *fixture) assertSteadyState(s *v1alpha1.DockerComposeService) { 242 f.T().Helper() 243 f.MustReconcile(types.NamespacedName{Name: s.Name}) 244 var s2 v1alpha1.DockerComposeService 245 f.MustGet(types.NamespacedName{Name: s.Name}, &s2) 246 assert.Equal(f.T(), s.ResourceVersion, s2.ResourceVersion) 247 }