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  }