github.com/tilt-dev/tilt@v0.33.15-0.20240515162809-0a22ed45d8a0/internal/dockercompose/client_test.go (about) 1 package dockercompose 2 3 import ( 4 "context" 5 "testing" 6 7 "github.com/compose-spec/compose-go/types" 8 "github.com/stretchr/testify/assert" 9 "github.com/stretchr/testify/require" 10 11 "github.com/tilt-dev/tilt/internal/docker" 12 "github.com/tilt-dev/tilt/internal/testutils" 13 "github.com/tilt-dev/tilt/internal/testutils/tempdir" 14 "github.com/tilt-dev/tilt/pkg/apis/core/v1alpha1" 15 ) 16 17 // TestVariableInterpolation both ensures Tilt properly passes environment to Compose for interpolation 18 // as well as catches potential regressions in the upstream YAML parsing from compose-go (currently, a 19 // fallback mechanism relies on the user having the v1 (Python) docker-compose CLI installed to work 20 // around bugs in the compose-go library (also used by the v2 CLI, and is susceptible to the same 21 // issues) 22 func TestVariableInterpolation(t *testing.T) { 23 if testing.Short() { 24 // remove this once the fallback to docker-compose CLI for YAML parse is eliminated 25 // (dependent upon compose-go upstream bugs being fixed) 26 t.Skip("skipping test that invokes docker-compose CLI in short mode") 27 } 28 29 f := newDCFixture(t) 30 31 output := `services: 32 app: 33 command: sh -c 'node app.js' 34 image: "$DC_TEST_IMAGE" 35 build: 36 context: $DC_TEST_CONTEXT 37 dockerfile: ${DC_TEST_DOCKERFILE} 38 ports: 39 - target: $DC_TEST_PORT 40 published: 8080 41 protocol: tcp 42 mode: ingress 43 ` 44 45 // the value is already quoted - Compose should NOT add extra quotes 46 testutils.Setenv(t, "DC_TEST_IMAGE", "myimage") 47 // unquoted 0 is a number in YAML, but Compose SHOULD quote this properly for the field string 48 // N.B. the path MUST exist or Compose will fail loading! 49 testutils.Setenv(t, "DC_TEST_CONTEXT", "0") 50 f.tmpdir.MkdirAll("0") 51 // unquoted Y is a bool in YAML, but Compose SHOULD quote this properly for the field string 52 testutils.Setenv(t, "DC_TEST_DOCKERFILE", "Y") 53 // Compose should NOT quote this since the field is numeric 54 testutils.Setenv(t, "DC_TEST_PORT", "8081") 55 56 proj := f.loadProject(output) 57 if assert.Len(t, proj.Services, 1) { 58 svc := proj.Services[0] 59 assert.Equal(t, "myimage", svc.Image) 60 if assert.NotNil(t, svc.Build) { 61 assert.Equal(t, f.tmpdir.JoinPath("0"), svc.Build.Context) 62 // resolved Dockerfile path is relative to the context 63 assert.Equal(t, "Y", svc.Build.Dockerfile) 64 } 65 if assert.Len(t, svc.Ports, 1) { 66 assert.Equal(t, 8081, int(svc.Ports[0].Target)) 67 } 68 } 69 } 70 71 func TestParseComposeVersionOutput(t *testing.T) { 72 type tc struct { 73 version string 74 build string 75 output []byte 76 } 77 tcs := []tc{ 78 { 79 version: "v1.4.0", 80 output: []byte(`docker-compose version: 1.4.0 81 docker-py version: 1.3.1 82 CPython version: 2.7.9 83 OpenSSL version: OpenSSL 1.0.1e 11 Feb 2013 84 `), 85 }, 86 { 87 version: "v1.29.2", 88 build: "5becea4c", 89 output: []byte(`docker-compose version 1.29.2, build 5becea4c 90 docker-py version: 5.0.0 91 CPython version: 3.7.10 92 OpenSSL version: OpenSSL 1.1.0l 10 Sep 2019 93 `), 94 }, 95 { 96 version: "v2.0.0-rc.3", 97 output: []byte("Docker Compose version v2.0.0-rc.3\n"), 98 }, 99 { 100 version: "v2.0.0-rc.3", 101 build: "bu1ld-info", 102 // NOTE: this format is valid semver but as of v2.0.0, has not been used by Compose but is supported 103 output: []byte("Docker Compose version v2.0.0-rc.3+bu1ld-info\n"), 104 }, 105 } 106 for _, tc := range tcs { 107 name := tc.version 108 if tc.build != "" { 109 name += "+" + tc.build 110 } 111 t.Run(name, func(t *testing.T) { 112 version, build, err := parseComposeVersionOutput(tc.output) 113 require.NoError(t, err) 114 require.Equal(t, tc.version, version) 115 require.Equal(t, tc.build, build) 116 }) 117 } 118 } 119 120 func TestLoadEnvFile(t *testing.T) { 121 if testing.Short() { 122 // remove this once the fallback to docker-compose CLI for YAML parse is eliminated 123 // (dependent upon compose-go upstream bugs being fixed) 124 t.Skip("skipping test that invokes docker-compose CLI in short mode") 125 } 126 127 f := newDCFixture(t) 128 f.tmpdir.WriteFile(".env", "COMMAND=foo") 129 130 dcYAML := `services: 131 foo: 132 command: ${COMMAND} 133 image: asdf 134 ` 135 proj := f.loadProject(dcYAML) 136 require.Equal(t, types.ShellCommand{"foo"}, proj.Services[0].Command) 137 } 138 139 type dcFixture struct { 140 t testing.TB 141 ctx context.Context 142 cli DockerComposeClient 143 tmpdir *tempdir.TempDirFixture 144 } 145 146 func newDCFixture(t testing.TB) *dcFixture { 147 ctx, _, _ := testutils.CtxAndAnalyticsForTest() 148 149 tmpdir := tempdir.NewTempDirFixture(t) 150 151 return &dcFixture{ 152 t: t, 153 ctx: ctx, 154 cli: NewDockerComposeClient(docker.LocalEnv{}), 155 tmpdir: tmpdir, 156 } 157 } 158 159 func (f *dcFixture) loadProject(composeYAML string) *types.Project { 160 f.t.Helper() 161 f.tmpdir.WriteFile("docker-compose.yaml", composeYAML) 162 proj, err := f.cli.Project(f.ctx, v1alpha1.DockerComposeProject{ConfigPaths: []string{f.tmpdir.JoinPath("docker-compose.yaml")}}) 163 require.NoError(f.t, err, "Failed to parse compose YAML") 164 return proj 165 }