@@ -40,14 +40,16 @@ type DevcontainerSnippet struct {
4040type FolderSnippetActionType string
4141
4242const (
43- FolderSnippetActionMergeJSON FolderSnippetActionType = "mergeJSON" // merge JSON file from snippet with target JSON file
44- FolderSnippetActionCopyAndRun FolderSnippetActionType = "copyAndRun" // COPY and RUN script from snippet in the Dockerfile (as with single-file snippet)
43+ FolderSnippetActionMergeJSON FolderSnippetActionType = "mergeJSON" // merge JSON file from snippet with target JSON file
44+ FolderSnippetActionCopyAndRun FolderSnippetActionType = "copyAndRun" // COPY and RUN script from snippet in the Dockerfile (as with single-file snippet)
45+ FolderSnippetActionDockerfileSnippet FolderSnippetActionType = "dockerfileSnippet" // snippet to include as-is in the Dockerfile
4546)
4647
4748type FolderSnippetAction struct {
4849 Type FolderSnippetActionType `json:"type"`
49- SourcePath string `json:"source"` // for mergeJSON this is snippet-relative path to JSON. for copyAndRun this is the script filename
50- TargetPath string `json:"target"` // for mergeJSON this is project-relative path to JSON
50+ SourcePath string `json:"source"` // for mergeJSON this is snippet-relative path to JSON. for copyAndRun this is the script filename
51+ TargetPath string `json:"target"` // for mergeJSON this is project-relative path to JSON
52+ Content string `json:"content"` // for dockerfileSnippet this is the content to include
5153}
5254
5355// FolderSnippet maps to the content of the snippet.json file for folder-based snippets
@@ -177,6 +179,64 @@ func addSingleFileSnippetToDevContainer(projectFolder string, snippet *Devcontai
177179 err := copyAndRunScriptFile (projectFolder , snippet , snippetBasePath , scriptFolderPath , scriptFilename )
178180 return err
179181}
182+
183+ func addFolderSnippetToDevContainer (projectFolder string , snippet * DevcontainerSnippet ) error {
184+ if snippet .Type != DevcontainerSnippetTypeFolder {
185+ return fmt .Errorf ("Expected folder snippet" )
186+ }
187+
188+ snippetJSONPath := filepath .Join (snippet .Path , "snippet.json" )
189+ buf , err := ioutil .ReadFile (snippetJSONPath )
190+ if err != nil {
191+ return err
192+ }
193+ var snippetJSON FolderSnippet
194+ err = json .Unmarshal (buf , & snippetJSON )
195+ if err != nil {
196+ return err
197+ }
198+
199+ for _ , action := range snippetJSON .Actions {
200+ switch action .Type {
201+ case FolderSnippetActionMergeJSON :
202+ if action .SourcePath == "" {
203+ return fmt .Errorf ("source must be set for %s actions" , action .Type )
204+ }
205+ if action .TargetPath == "" {
206+ return fmt .Errorf ("target must be set for %s actions" , action .Type )
207+ }
208+ err = mergeJSON (projectFolder , snippet , action .SourcePath , action .TargetPath )
209+ if err != nil {
210+ return err
211+ }
212+ case FolderSnippetActionCopyAndRun :
213+ if action .SourcePath == "" {
214+ return fmt .Errorf ("source must be set for %s actions" , action .Type )
215+ }
216+ targetPath := filepath .Join (projectFolder , ".devcontainer" , "scripts" )
217+ sourceParent , sourceFileName := filepath .Split (action .SourcePath )
218+ sourceBasePath := filepath .Join (snippet .Path , sourceParent )
219+ err = copyAndRunScriptFile (projectFolder , snippet , sourceBasePath , targetPath , sourceFileName )
220+ if err != nil {
221+ return err
222+ }
223+ case FolderSnippetActionDockerfileSnippet :
224+ if action .Content == "" {
225+ return fmt .Errorf ("content must be set for %s actions" , action .Type )
226+ }
227+ dockerfileFilename := filepath .Join (projectFolder , ".devcontainer" , "Dockerfile" )
228+ err = insertDockerfileSnippet (dockerfileFilename , action .Content + "\n " )
229+ if err != nil {
230+ return err
231+ }
232+ default :
233+ return fmt .Errorf ("unhandled action type: %q" , action .Type )
234+ }
235+ }
236+
237+ return nil
238+ }
239+
180240func copyAndRunScriptFile (projectFolder string , snippet * DevcontainerSnippet , snippetBasePath string , targetPath , scriptFilename string ) error {
181241 if err := os .MkdirAll (targetPath , 0755 ); err != nil {
182242 return err
@@ -185,47 +245,57 @@ func copyAndRunScriptFile(projectFolder string, snippet *DevcontainerSnippet, sn
185245 return err
186246 }
187247
248+ snippetContent := fmt .Sprintf (`# %[1]s
249+ COPY scripts/%[2]s /tmp/
250+ RUN /tmp/%[2]s
251+ ` , snippet .Name , scriptFilename )
188252 dockerfileFilename := filepath .Join (projectFolder , ".devcontainer" , "Dockerfile" )
253+
254+ err := insertDockerfileSnippet (dockerfileFilename , snippetContent )
255+ return err
256+ }
257+
258+ func insertDockerfileSnippet (dockerfileFilename string , snippetContent string ) error {
259+
189260 buf , err := ioutil .ReadFile (dockerfileFilename )
190261 if err != nil {
191262 return fmt .Errorf ("Error reading Dockerfile: %s" , err )
192263 }
193264
194- snippetContent := fmt .Sprintf (`
195- # %[1]s
196- COPY scripts/%[2]s /tmp/
197- RUN /tmp/%[2]s
198- ` , snippet .Name , scriptFilename )
199-
200265 dockerfileContent := string (buf )
201266 dockerFileLines := strings .Split (dockerfileContent , "\n " )
202267 addSeparator := false
203268 addedSnippetContent := false
204269 var newContent bytes.Buffer
205270 for _ , line := range dockerFileLines {
271+ if addSeparator {
272+ if _ , err = newContent .WriteString ("\n " ); err != nil {
273+ return err
274+ }
275+ }
276+ addSeparator = true
277+
206278 if strings .Contains (line , "__DEVCONTAINER_SNIPPET_INSERT__" ) {
207279 if _ , err = newContent .WriteString (snippetContent ); err != nil {
208280 return err
209281 }
210282 if _ , err = newContent .WriteString ("\n " ); err != nil {
211283 return err
212284 }
285+ line += "\n "
213286 addedSnippetContent = true
214287 addSeparator = false // avoid extra separator
215288 }
216289
217- if addSeparator {
218- if _ , err = newContent .WriteString ("\n " ); err != nil {
219- return err
220- }
221- }
222- addSeparator = true
223290 if _ , err = newContent .WriteString (line ); err != nil {
224291 return err
225292 }
226293 }
227294
228295 if ! addedSnippetContent {
296+ if _ , err = newContent .WriteString ("\n " ); err != nil {
297+ return err
298+ }
229299 if _ , err = newContent .WriteString (snippetContent ); err != nil {
230300 return err
231301 }
@@ -234,47 +304,8 @@ RUN /tmp/%[2]s
234304 err = ioutil .WriteFile (dockerfileFilename , newContent .Bytes (), 0 )
235305
236306 return err
237- }
238307
239- func addFolderSnippetToDevContainer (projectFolder string , snippet * DevcontainerSnippet ) error {
240- if snippet .Type != DevcontainerSnippetTypeFolder {
241- return fmt .Errorf ("Expected folder snippet" )
242- }
243-
244- snippetJSONPath := filepath .Join (snippet .Path , "snippet.json" )
245- buf , err := ioutil .ReadFile (snippetJSONPath )
246- if err != nil {
247- return err
248- }
249- var snippetJSON FolderSnippet
250- err = json .Unmarshal (buf , & snippetJSON )
251- if err != nil {
252- return err
253- }
254-
255- for _ , action := range snippetJSON .Actions {
256- switch action .Type {
257- case FolderSnippetActionMergeJSON :
258- err = mergeJSON (projectFolder , snippet , action .SourcePath , action .TargetPath )
259- if err != nil {
260- return err
261- }
262- case FolderSnippetActionCopyAndRun :
263- targetPath := filepath .Join (projectFolder , ".devcontainer" , "scripts" )
264- sourceParent , sourceFileName := filepath .Split (action .SourcePath )
265- sourceBasePath := filepath .Join (snippet .Path , sourceParent )
266- err = copyAndRunScriptFile (projectFolder , snippet , sourceBasePath , targetPath , sourceFileName )
267- if err != nil {
268- return err
269- }
270- default :
271- return fmt .Errorf ("unhandled action type: %q" , action .Type )
272- }
273- }
274-
275- return nil
276308}
277-
278309func mergeJSON (projectFolder string , snippet * DevcontainerSnippet , relativeMergePath string , relativeBasePath string ) error {
279310 mergePath := filepath .Join (snippet .Path , relativeMergePath )
280311 _ , err := os .Stat (mergePath )
@@ -300,11 +331,20 @@ func mergeJSON(projectFolder string, snippet *DevcontainerSnippet, relativeMerge
300331 resultJSON , err := dora_ast .WriteJSONString (resultDocument )
301332
302333 // replace __DEVCONTAINER_NAME__ with name
303- devcontainerName , err := getDevcontainerName (filepath .Join (projectFolder , ".devcontainer/devcontainer.json" ))
304- if err != nil {
305- return err
334+ devcontainerName , devcontainerUserName := getDevcontainerNameAndUserName (filepath .Join (projectFolder , ".devcontainer/devcontainer.json" ))
335+ if devcontainerName == "" {
336+ return fmt . Errorf ( "failed to get dev container name" )
306337 }
307338 resultJSON = strings .ReplaceAll (resultJSON , "__DEVCONTAINER_NAME__" , devcontainerName )
339+ if devcontainerUserName == "" {
340+ devcontainerUserName = "root"
341+ }
342+ devcontainerHome := "/home/" + devcontainerUserName
343+ if devcontainerUserName == "root" {
344+ devcontainerHome = "/root"
345+ }
346+ resultJSON = strings .ReplaceAll (resultJSON , "__DEVCONTAINER_USER_NAME__" , devcontainerUserName )
347+ resultJSON = strings .ReplaceAll (resultJSON , "__DEVCONTAINER_HOME__" , devcontainerHome )
308348
309349 ioutil .WriteFile (basePath , []byte (resultJSON ), 0666 )
310350
@@ -326,23 +366,27 @@ func loadJSONDocument(path string) (*dora_ast.RootNode, error) {
326366 return & baseDocument , nil
327367}
328368
329- func getDevcontainerName (devContainerJsonPath string ) (string , error ) {
369+ func getDevcontainerNameAndUserName (devContainerJsonPath string ) (string , string ) {
330370 // This doesn't use standard `json` pkg as devcontainer.json permits comments (and the default templates include them!)
331371
332372 buf , err := ioutil .ReadFile (devContainerJsonPath )
333373 if err != nil {
334- return "" , err
374+ return "" , ""
335375 }
336376
337377 c , err := dora .NewFromBytes (buf )
338378 if err != nil {
339- return "" , err
379+ return "" , ""
340380 }
341381
342382 name , err := c .GetString ("$.name" )
343383 if err != nil {
344- return "" , err
384+ name = ""
385+ }
386+ userName , err := c .GetString ("$.remoteUser" )
387+ if err != nil {
388+ userName = ""
345389 }
346390
347- return name , nil
391+ return name , userName
348392}
0 commit comments