@@ -127,6 +127,11 @@ type Options struct {
127
127
// DevcontainerDir. This can be used in cases where one wants
128
128
// to substitute an edited devcontainer.json file for the one
129
129
// that exists in the repo.
130
+ // If neither `DevcontainerDir` nor `DevcontainerJSONPath` is provided,
131
+ // envbuilder will browse following directories to locate it:
132
+ // 1. `.devcontainer/devcontainer.json`
133
+ // 2. `.devcontainer.json`
134
+ // 3. `.devcontainer/<folder>/devcontainer.json`
130
135
DevcontainerJSONPath string `env:"DEVCONTAINER_JSON_PATH"`
131
136
132
137
// DockerfilePath is a relative path to the Dockerfile that
@@ -422,22 +427,11 @@ func Run(ctx context.Context, options Options) error {
422
427
if options .DockerfilePath == "" {
423
428
// Only look for a devcontainer if a Dockerfile wasn't specified.
424
429
// devcontainer is a standard, so it's reasonable to be the default.
425
- devcontainerDir := options .DevcontainerDir
426
- if devcontainerDir == "" {
427
- devcontainerDir = ".devcontainer"
428
- }
429
- if ! filepath .IsAbs (devcontainerDir ) {
430
- devcontainerDir = filepath .Join (options .WorkspaceFolder , devcontainerDir )
431
- }
432
- devcontainerPath := options .DevcontainerJSONPath
433
- if devcontainerPath == "" {
434
- devcontainerPath = "devcontainer.json"
435
- }
436
- if ! filepath .IsAbs (devcontainerPath ) {
437
- devcontainerPath = filepath .Join (devcontainerDir , devcontainerPath )
438
- }
439
- _ , err := options .Filesystem .Stat (devcontainerPath )
440
- if err == nil {
430
+ devcontainerPath , devcontainerDir , err := findDevcontainerJSON (options )
431
+ if err != nil {
432
+ logf (codersdk .LogLevelError , "Failed to locate devcontainer.json: %s" , err .Error ())
433
+ logf (codersdk .LogLevelError , "Falling back to the default image..." )
434
+ } else {
441
435
// We know a devcontainer exists.
442
436
// Let's parse it and use it!
443
437
file , err := options .Filesystem .Open (devcontainerPath )
@@ -1201,3 +1195,70 @@ type osfsWithChmod struct {
1201
1195
func (fs * osfsWithChmod ) Chmod (name string , mode os.FileMode ) error {
1202
1196
return os .Chmod (name , mode )
1203
1197
}
1198
+
1199
+ func findDevcontainerJSON (options Options ) (string , string , error ) {
1200
+ // 0. Check if custom devcontainer directory or path is provided.
1201
+ if options .DevcontainerDir != "" || options .DevcontainerJSONPath != "" {
1202
+ devcontainerDir := options .DevcontainerDir
1203
+ if devcontainerDir == "" {
1204
+ devcontainerDir = ".devcontainer"
1205
+ }
1206
+ // If `devcontainerDir` is not an absolute path, assume it is relative to the workspace folder.
1207
+ if ! filepath .IsAbs (devcontainerDir ) {
1208
+ devcontainerDir = filepath .Join (options .WorkspaceFolder , devcontainerDir )
1209
+ }
1210
+
1211
+ // An absolute location always takes a precedence.
1212
+ devcontainerPath := options .DevcontainerJSONPath
1213
+ if filepath .IsAbs (devcontainerPath ) {
1214
+ return options .DevcontainerJSONPath , devcontainerDir , nil
1215
+ }
1216
+ // If an override is not provided, assume it is just `devcontainer.json`.
1217
+ if devcontainerPath == "" {
1218
+ devcontainerPath = "devcontainer.json"
1219
+ }
1220
+
1221
+ if ! filepath .IsAbs (devcontainerPath ) {
1222
+ devcontainerPath = filepath .Join (devcontainerDir , devcontainerPath )
1223
+ }
1224
+ return devcontainerPath , devcontainerDir , nil
1225
+ }
1226
+
1227
+ // 1. Check `options.WorkspaceFolder`/.devcontainer/devcontainer.json.
1228
+ location := filepath .Join (options .WorkspaceFolder , ".devcontainer" , "devcontainer.json" )
1229
+ if _ , err := options .Filesystem .Stat (location ); err == nil {
1230
+ return location , filepath .Dir (location ), nil
1231
+ }
1232
+
1233
+ // 2. Check `options.WorkspaceFolder`/devcontainer.json.
1234
+ location = filepath .Join (options .WorkspaceFolder , "devcontainer.json" )
1235
+ if _ , err := options .Filesystem .Stat (location ); err == nil {
1236
+ return location , filepath .Dir (location ), nil
1237
+ }
1238
+
1239
+ // 3. Check every folder: `options.WorkspaceFolder`/.devcontainer/<folder>/devcontainer.json.
1240
+ devcontainerDir := filepath .Join (options .WorkspaceFolder , ".devcontainer" )
1241
+
1242
+ fileInfos , err := options .Filesystem .ReadDir (devcontainerDir )
1243
+ if err != nil {
1244
+ return "" , "" , err
1245
+ }
1246
+
1247
+ logf := options .Logger
1248
+ for _ , fileInfo := range fileInfos {
1249
+ if ! fileInfo .IsDir () {
1250
+ logf (codersdk .LogLevelDebug , `%s is a file` , fileInfo .Name ())
1251
+ continue
1252
+ }
1253
+
1254
+ location := filepath .Join (devcontainerDir , fileInfo .Name (), "devcontainer.json" )
1255
+ if _ , err := options .Filesystem .Stat (location ); err != nil {
1256
+ logf (codersdk .LogLevelDebug , `stat %s failed: %s` , location , err .Error ())
1257
+ continue
1258
+ }
1259
+
1260
+ return location , filepath .Dir (location ), nil
1261
+ }
1262
+
1263
+ return "" , "" , errors .New ("can't find devcontainer.json, is it a correct spec?" )
1264
+ }
0 commit comments