@@ -24,6 +24,7 @@ import (
24
24
"github.com/coder/envbuilder"
25
25
"github.com/coder/envbuilder/devcontainer/features"
26
26
"github.com/coder/envbuilder/testutil/gittest"
27
+ "github.com/coder/envbuilder/testutil/mwtest"
27
28
"github.com/coder/envbuilder/testutil/registrytest"
28
29
clitypes "github.com/docker/cli/cli/config/types"
29
30
"github.com/docker/docker/api/types"
@@ -776,7 +777,7 @@ func TestPrivateRegistry(t *testing.T) {
776
777
t .Parallel ()
777
778
// Even if something goes wrong with auth,
778
779
// the pull will fail as "scratch" is a reserved name.
779
- image := setupPassthroughRegistry (t , "scratch" , & registryAuth {
780
+ image := setupPassthroughRegistry (t , "scratch" , & setupPassthroughRegistryOptions {
780
781
Username : "user" ,
781
782
Password : "test" ,
782
783
})
@@ -795,7 +796,7 @@ func TestPrivateRegistry(t *testing.T) {
795
796
})
796
797
t .Run ("Auth" , func (t * testing.T ) {
797
798
t .Parallel ()
798
- image := setupPassthroughRegistry (t , "envbuilder-test-alpine:latest" , & registryAuth {
799
+ image := setupPassthroughRegistry (t , "envbuilder-test-alpine:latest" , & setupPassthroughRegistryOptions {
799
800
Username : "user" ,
800
801
Password : "test" ,
801
802
})
@@ -827,7 +828,7 @@ func TestPrivateRegistry(t *testing.T) {
827
828
t .Parallel ()
828
829
// Even if something goes wrong with auth,
829
830
// the pull will fail as "scratch" is a reserved name.
830
- image := setupPassthroughRegistry (t , "scratch" , & registryAuth {
831
+ image := setupPassthroughRegistry (t , "scratch" , & setupPassthroughRegistryOptions {
831
832
Username : "user" ,
832
833
Password : "banana" ,
833
834
})
@@ -857,38 +858,43 @@ func TestPrivateRegistry(t *testing.T) {
857
858
})
858
859
}
859
860
860
- type registryAuth struct {
861
+ type setupPassthroughRegistryOptions struct {
861
862
Username string
862
863
Password string
864
+ Upstream string
863
865
}
864
866
865
- func setupPassthroughRegistry (t * testing.T , image string , auth * registryAuth ) string {
867
+ func setupPassthroughRegistry (t * testing.T , image string , opts * setupPassthroughRegistryOptions ) string {
866
868
t .Helper ()
867
- dockerURL , err := url .Parse ("http://localhost:5000" )
869
+ if opts .Upstream == "" {
870
+ // Default to local test registry
871
+ opts .Upstream = "http://localhost:5000"
872
+ }
873
+ upstreamURL , err := url .Parse (opts .Upstream )
868
874
require .NoError (t , err )
869
- proxy := httputil .NewSingleHostReverseProxy (dockerURL )
875
+ proxy := httputil .NewSingleHostReverseProxy (upstreamURL )
870
876
871
877
// The Docker registry uses short-lived JWTs to authenticate
872
878
// anonymously to pull images. To test our MITM auth, we need to
873
879
// generate a JWT for the proxy to use.
874
- registry , err := name .NewRegistry ("localhost:5000" )
880
+ registry , err := name .NewRegistry (upstreamURL . Host )
875
881
require .NoError (t , err )
876
882
proxy .Transport , err = transport .NewWithContext (context .Background (), registry , authn .Anonymous , http .DefaultTransport , []string {})
877
883
require .NoError (t , err )
878
884
879
885
srv := httptest .NewServer (http .HandlerFunc (func (w http.ResponseWriter , r * http.Request ) {
880
- r .Host = "localhost:5000"
881
- r .URL .Host = "localhost:5000"
882
- r .URL .Scheme = "http"
886
+ r .Host = upstreamURL . Host
887
+ r .URL .Host = upstreamURL . Host
888
+ r .URL .Scheme = upstreamURL . Scheme
883
889
884
- if auth != nil {
890
+ if opts != nil {
885
891
user , pass , ok := r .BasicAuth ()
886
892
if ! ok {
887
893
w .Header ().Set ("WWW-Authenticate" , "Basic realm=\" Access to the staging site\" , charset=\" UTF-8\" " )
888
894
w .WriteHeader (http .StatusUnauthorized )
889
895
return
890
896
}
891
- if user != auth .Username || pass != auth .Password {
897
+ if user != opts .Username || pass != opts .Password {
892
898
w .WriteHeader (http .StatusUnauthorized )
893
899
return
894
900
}
@@ -1008,7 +1014,7 @@ func TestPushImage(t *testing.T) {
1008
1014
})
1009
1015
1010
1016
// Given: an empty registry
1011
- testReg := setupInMemoryRegistry (t )
1017
+ testReg := setupInMemoryRegistry (t , setupInMemoryRegistryOpts {} )
1012
1018
testRepo := testReg + "/test"
1013
1019
ref , err := name .ParseReference (testRepo + ":latest" )
1014
1020
require .NoError (t , err )
@@ -1062,7 +1068,7 @@ func TestPushImage(t *testing.T) {
1062
1068
})
1063
1069
1064
1070
// Given: an empty registry
1065
- testReg := setupInMemoryRegistry (t )
1071
+ testReg := setupInMemoryRegistry (t , setupInMemoryRegistryOpts {} )
1066
1072
testRepo := testReg + "/test"
1067
1073
ref , err := name .ParseReference (testRepo + ":latest" )
1068
1074
require .NoError (t , err )
@@ -1101,6 +1107,130 @@ func TestPushImage(t *testing.T) {
1101
1107
require .NoError (t , err )
1102
1108
})
1103
1109
1110
+ t .Run ("CacheAndPushAuth" , func (t * testing.T ) {
1111
+ t .Parallel ()
1112
+
1113
+ srv := createGitServer (t , gitServerOptions {
1114
+ files : map [string ]string {
1115
+ ".devcontainer/Dockerfile" : fmt .Sprintf ("FROM %s\n RUN date --utc > /root/date.txt" , testImageAlpine ),
1116
+ ".devcontainer/devcontainer.json" : `{
1117
+ "name": "Test",
1118
+ "build": {
1119
+ "dockerfile": "Dockerfile"
1120
+ },
1121
+ }` ,
1122
+ },
1123
+ })
1124
+
1125
+ // Given: an empty registry
1126
+ opts := setupInMemoryRegistryOpts {
1127
+ Username : "testing" ,
1128
+ Password : "testing" ,
1129
+ }
1130
+ remoteAuthOpt := remote .WithAuth (& authn.Basic {Username : opts .Username , Password : opts .Password })
1131
+ testReg := setupInMemoryRegistry (t , opts )
1132
+ testRepo := testReg + "/test"
1133
+ regAuthJSON , err := json .Marshal (envbuilder.DockerConfig {
1134
+ AuthConfigs : map [string ]clitypes.AuthConfig {
1135
+ testRepo : {
1136
+ Username : opts .Username ,
1137
+ Password : opts .Password ,
1138
+ },
1139
+ },
1140
+ })
1141
+ require .NoError (t , err )
1142
+ ref , err := name .ParseReference (testRepo + ":latest" )
1143
+ require .NoError (t , err )
1144
+ _ , err = remote .Image (ref , remoteAuthOpt )
1145
+ require .ErrorContains (t , err , "NAME_UNKNOWN" , "expected image to not be present before build + push" )
1146
+
1147
+ // When: we run envbuilder with GET_CACHED_IMAGE
1148
+ _ , err = runEnvbuilder (t , options {env : []string {
1149
+ envbuilderEnv ("GIT_URL" , srv .URL ),
1150
+ envbuilderEnv ("CACHE_REPO" , testRepo ),
1151
+ envbuilderEnv ("GET_CACHED_IMAGE" , "1" ),
1152
+ }})
1153
+ require .ErrorContains (t , err , "error probing build cache: uncached command" )
1154
+ // Then: it should fail to build the image and nothing should be pushed
1155
+ _ , err = remote .Image (ref , remoteAuthOpt )
1156
+ require .ErrorContains (t , err , "NAME_UNKNOWN" , "expected image to not be present before build + push" )
1157
+
1158
+ // When: we run envbuilder with PUSH_IMAGE set
1159
+ _ , err = runEnvbuilder (t , options {env : []string {
1160
+ envbuilderEnv ("GIT_URL" , srv .URL ),
1161
+ envbuilderEnv ("CACHE_REPO" , testRepo ),
1162
+ envbuilderEnv ("PUSH_IMAGE" , "1" ),
1163
+ envbuilderEnv ("DOCKER_CONFIG_BASE64" , base64 .StdEncoding .EncodeToString (regAuthJSON )),
1164
+ }})
1165
+ require .NoError (t , err )
1166
+
1167
+ // Then: the image should be pushed
1168
+ _ , err = remote .Image (ref , remoteAuthOpt )
1169
+ require .NoError (t , err , "expected image to be present after build + push" )
1170
+
1171
+ // Then: re-running envbuilder with GET_CACHED_IMAGE should succeed
1172
+ _ , err = runEnvbuilder (t , options {env : []string {
1173
+ envbuilderEnv ("GIT_URL" , srv .URL ),
1174
+ envbuilderEnv ("CACHE_REPO" , testRepo ),
1175
+ envbuilderEnv ("GET_CACHED_IMAGE" , "1" ),
1176
+ envbuilderEnv ("DOCKER_CONFIG_BASE64" , base64 .StdEncoding .EncodeToString (regAuthJSON )),
1177
+ }})
1178
+ require .NoError (t , err )
1179
+ })
1180
+
1181
+ t .Run ("CacheAndPushAuthFail" , func (t * testing.T ) {
1182
+ t .Parallel ()
1183
+
1184
+ srv := createGitServer (t , gitServerOptions {
1185
+ files : map [string ]string {
1186
+ ".devcontainer/Dockerfile" : fmt .Sprintf ("FROM %s\n RUN date --utc > /root/date.txt" , testImageAlpine ),
1187
+ ".devcontainer/devcontainer.json" : `{
1188
+ "name": "Test",
1189
+ "build": {
1190
+ "dockerfile": "Dockerfile"
1191
+ },
1192
+ }` ,
1193
+ },
1194
+ })
1195
+
1196
+ // Given: an empty registry
1197
+ opts := setupInMemoryRegistryOpts {
1198
+ Username : "testing" ,
1199
+ Password : "testing" ,
1200
+ }
1201
+ remoteAuthOpt := remote .WithAuth (& authn.Basic {Username : opts .Username , Password : opts .Password })
1202
+ testReg := setupInMemoryRegistry (t , opts )
1203
+ testRepo := testReg + "/test"
1204
+ ref , err := name .ParseReference (testRepo + ":latest" )
1205
+ require .NoError (t , err )
1206
+ _ , err = remote .Image (ref , remoteAuthOpt )
1207
+ require .ErrorContains (t , err , "NAME_UNKNOWN" , "expected image to not be present before build + push" )
1208
+
1209
+ // When: we run envbuilder with GET_CACHED_IMAGE
1210
+ _ , err = runEnvbuilder (t , options {env : []string {
1211
+ envbuilderEnv ("GIT_URL" , srv .URL ),
1212
+ envbuilderEnv ("CACHE_REPO" , testRepo ),
1213
+ envbuilderEnv ("GET_CACHED_IMAGE" , "1" ),
1214
+ }})
1215
+ require .ErrorContains (t , err , "error probing build cache: uncached command" )
1216
+ // Then: it should fail to build the image and nothing should be pushed
1217
+ _ , err = remote .Image (ref , remoteAuthOpt )
1218
+ require .ErrorContains (t , err , "NAME_UNKNOWN" , "expected image to not be present before build + push" )
1219
+
1220
+ // When: we run envbuilder with PUSH_IMAGE set
1221
+ _ , err = runEnvbuilder (t , options {env : []string {
1222
+ envbuilderEnv ("GIT_URL" , srv .URL ),
1223
+ envbuilderEnv ("CACHE_REPO" , testRepo ),
1224
+ envbuilderEnv ("PUSH_IMAGE" , "1" ),
1225
+ }})
1226
+ // Then: it should fail with an Unauthorized error
1227
+ require .ErrorContains (t , err , "401 Unauthorized" , "expected unauthorized error using no auth when cache repo requires it" )
1228
+
1229
+ // Then: the image should not be pushed
1230
+ _ , err = remote .Image (ref , remoteAuthOpt )
1231
+ require .ErrorContains (t , err , "NAME_UNKNOWN" , "expected image to not be present before build + push" )
1232
+ })
1233
+
1104
1234
t .Run ("CacheAndPushMultistage" , func (t * testing.T ) {
1105
1235
// Currently fails with:
1106
1236
// /home/coder/src/coder/envbuilder/integration/integration_test.go:1417: "error: unable to get cached image: error fake building stage: failed to optimize instructions: failed to get files used from context: failed to get fileinfo for /.envbuilder/0/root/date.txt: lstat /.envbuilder/0/root/date.txt: no such file or directory"
@@ -1122,7 +1252,7 @@ COPY --from=a /root/date.txt /date.txt`, testImageAlpine, testImageAlpine),
1122
1252
})
1123
1253
1124
1254
// Given: an empty registry
1125
- testReg := setupInMemoryRegistry (t )
1255
+ testReg := setupInMemoryRegistry (t , setupInMemoryRegistryOpts {} )
1126
1256
testRepo := testReg + "/test"
1127
1257
ref , err := name .ParseReference (testRepo + ":latest" )
1128
1258
require .NoError (t , err )
@@ -1224,11 +1354,17 @@ COPY --from=a /root/date.txt /date.txt`, testImageAlpine, testImageAlpine),
1224
1354
})
1225
1355
}
1226
1356
1227
- func setupInMemoryRegistry (t * testing.T ) string {
1357
+ type setupInMemoryRegistryOpts struct {
1358
+ Username string
1359
+ Password string
1360
+ }
1361
+
1362
+ func setupInMemoryRegistry (t * testing.T , opts setupInMemoryRegistryOpts ) string {
1228
1363
t .Helper ()
1229
1364
tempDir := t .TempDir ()
1230
- testReg := registry .New (registry .WithBlobHandler (registry .NewDiskBlobHandler (tempDir )))
1231
- regSrv := httptest .NewServer (testReg )
1365
+ regHandler := registry .New (registry .WithBlobHandler (registry .NewDiskBlobHandler (tempDir )))
1366
+ authHandler := mwtest .BasicAuthMW (opts .Username , opts .Password )(regHandler )
1367
+ regSrv := httptest .NewServer (authHandler )
1232
1368
t .Cleanup (func () { regSrv .Close () })
1233
1369
regSrvURL , err := url .Parse (regSrv .URL )
1234
1370
require .NoError (t , err )
@@ -1274,7 +1410,7 @@ type gitServerOptions struct {
1274
1410
func createGitServer (t * testing.T , opts gitServerOptions ) * httptest.Server {
1275
1411
t .Helper ()
1276
1412
if opts .authMW == nil {
1277
- opts .authMW = gittest .BasicAuthMW (opts .username , opts .password )
1413
+ opts .authMW = mwtest .BasicAuthMW (opts .username , opts .password )
1278
1414
}
1279
1415
commits := make ([]gittest.CommitFunc , 0 )
1280
1416
for path , content := range opts .files {
0 commit comments