@@ -229,7 +229,7 @@ func contentFolderExists(path string, log log.Logger) (bool, error) {
229229
230230func createContentFolder (path string , log log.Logger ) error {
231231 log .WithFields (logrus.Fields {"path" : path }).Debug ("create content folder" )
232- if err := os .MkdirAll (path , 0o777 ); err != nil {
232+ if err := os .MkdirAll (path , 0o750 ); err != nil {
233233 return fmt .Errorf ("make workspace folder: %w" , err )
234234 }
235235 return nil
@@ -307,7 +307,7 @@ func (w *workspaceInitializer) initialize() error {
307307 return err
308308 }
309309
310- w .configureDockerDaemon ()
310+ w .tryConfigureDockerDaemon ()
311311 return nil
312312}
313313
@@ -319,7 +319,7 @@ func (w *workspaceInitializer) setupDaemonIfNeeded() {
319319 }
320320}
321321
322- func (w * workspaceInitializer ) configureDockerDaemon () {
322+ func (w * workspaceInitializer ) tryConfigureDockerDaemon () {
323323 if ! w .shouldConfigureDockerDaemon () {
324324 w .logger .Debug ("skipping configuring docker daemon" )
325325 return
@@ -392,8 +392,11 @@ func (w *workspaceInitializer) ensureDockerInstalled() (string, error) {
392392 }
393393
394394 if dockerCmd != "docker" {
395- _ , err := exec .LookPath (dockerCmd )
396- return "" , err
395+ path , err := exec .LookPath (dockerCmd )
396+ if err != nil {
397+ return "" , fmt .Errorf ("custom docker path %q not found: %w" , dockerCmd , err )
398+ }
399+ return path , nil
397400 }
398401
399402 if w .isDockerInstallDisabled () {
@@ -429,6 +432,8 @@ func (w *workspaceInitializer) prepareWorkspaceContent() error {
429432 })
430433}
431434
435+ // waitForDocker waits for the Docker installation to complete.
436+ // Note: This function modifies workspaceInfo.Agent.Docker.Path if Docker was installed.
432437func (w * workspaceInitializer ) waitForDocker (resultChan <- chan dockerInstallResult ) error {
433438 result := <- resultChan
434439
@@ -465,6 +470,8 @@ type prepareWorkspaceParams struct {
465470 log log.Logger
466471}
467472
473+ // prepareWorkspace initializes the workspace content folder and downloads/prepares the workspace source.
474+ // Note: This function modifies params.workspaceInfo.ContentFolder when platform is enabled with a local folder.
468475func prepareWorkspace (params prepareWorkspaceParams ) error {
469476 if params .workspaceInfo .CLIOptions .Platform .Enabled && params .workspaceInfo .Workspace .Source .LocalFolder != "" {
470477 params .workspaceInfo .ContentFolder = agent .GetAgentWorkspaceContentDir (params .workspaceInfo .Origin )
@@ -653,52 +660,40 @@ func prepareImage(workspaceDir, image string) error {
653660 return os .WriteFile (filepath .Join (workspaceDir , ".devcontainer.json" ), devcontainerConfig , 0o600 )
654661}
655662
663+ // installDocker installs Docker and returns the path to the docker binary.
664+ // This function assumes docker does not already exist - the caller should check first.
656665func installDocker (log log.Logger ) (dockerPath string , err error ) {
657- if ! command .Exists ("docker" ) {
658- writer := log .Writer (logrus .InfoLevel , false )
659- defer func () { _ = writer .Close () }()
660-
661- log .Debug ("Installing Docker" )
662-
663- dockerPath , err = dockerinstall .Install (writer , writer )
664- } else {
665- dockerPath = "docker"
666- }
667- return dockerPath , err
666+ writer := log .Writer (logrus .InfoLevel , false )
667+ defer func () { _ = writer .Close () }()
668+ log .Debug ("installing Docker" )
669+ return dockerinstall .Install (writer , writer )
668670}
669671
670672func configureDockerDaemon (ctx context.Context , log log.Logger ) error {
671673 log .Info ("configuring docker daemon" )
672674
673- daemonConfig := []byte (`{
674- "features": {
675- "containerd-snapshotter": true
676- }
677- }` )
678-
679- if err := writeDockerDaemonConfig (daemonConfig ); err != nil {
675+ if err := mergeDockerDaemonConfig (); err != nil {
680676 return err
681677 }
682678
683679 return reloadDockerDaemon (ctx )
684680}
685681
686- func writeDockerDaemonConfig ( config [] byte ) error {
687- rootlessErr := tryWriteRootlessDockerConfig ( config )
682+ func mergeDockerDaemonConfig ( ) error {
683+ rootlessErr := tryMergeRootlessDockerConfig ( )
688684 if rootlessErr == nil {
689685 return nil
690686 }
691687
692- // #nosec G306 -- daemon.json needs to be readable by docker daemon
693- rootErr := os .WriteFile ("/etc/docker/daemon.json" , config , 0644 )
688+ rootErr := tryMergeRootDockerConfig ()
694689 if rootErr == nil {
695690 return nil
696691 }
697692
698693 return fmt .Errorf ("failed to write docker daemon config (rootless: %v, root: %v)" , rootlessErr , rootErr )
699694}
700695
701- func tryWriteRootlessDockerConfig ( config [] byte ) error {
696+ func tryMergeRootlessDockerConfig ( ) error {
702697 homeDir , err := util .UserHomeDir ()
703698 if err != nil {
704699 return err
@@ -709,10 +704,60 @@ func tryWriteRootlessDockerConfig(config []byte) error {
709704 return err
710705 }
711706
707+ configPath := filepath .Join (dockerConfigDir , "daemon.json" )
708+ return mergeContainerdSnapshotterConfig (configPath )
709+ }
710+
711+ func tryMergeRootDockerConfig () error {
712+ return mergeContainerdSnapshotterConfig ("/etc/docker/daemon.json" )
713+ }
714+
715+ func mergeContainerdSnapshotterConfig (configPath string ) error {
716+ existingConfig := make (map [string ]any )
717+ data , err := os .ReadFile (configPath )
718+ if err != nil && ! errors .Is (err , os .ErrNotExist ) {
719+ return fmt .Errorf ("read existing config: %w" , err )
720+ }
721+
722+ if len (data ) > 0 {
723+ if err := json .Unmarshal (data , & existingConfig ); err != nil {
724+ return fmt .Errorf ("parse existing config: %w" , err )
725+ }
726+ }
727+
728+ features , ok := existingConfig ["features" ].(map [string ]any )
729+ if ! ok {
730+ features = make (map [string ]any )
731+ existingConfig ["features" ] = features
732+ }
733+ features ["containerd-snapshotter" ] = true
734+
735+ mergedData , err := json .MarshalIndent (existingConfig , "" , " " )
736+ if err != nil {
737+ return fmt .Errorf ("marshal config: %w" , err )
738+ }
739+
740+ if err := os .MkdirAll (filepath .Dir (configPath ), 0755 ); err != nil {
741+ return fmt .Errorf ("create config directory: %w" , err )
742+ }
743+
712744 // #nosec G306 -- daemon.json needs to be readable by docker daemon
713- return os .WriteFile (filepath .Join (dockerConfigDir , "daemon.json" ), config , 0644 )
745+ if err := os .WriteFile (configPath , mergedData , 0644 ); err != nil {
746+ return fmt .Errorf ("write config: %w" , err )
747+ }
748+
749+ return nil
714750}
715751
716752func reloadDockerDaemon (ctx context.Context ) error {
717- return exec .CommandContext (ctx , "pkill" , "-HUP" , "dockerd" ).Run ()
753+ err := exec .CommandContext (ctx , "pkill" , "-HUP" , "dockerd" ).Run ()
754+ if err != nil {
755+ // pkill returns exit code 1 if no processes matched
756+ var exitErr * exec.ExitError
757+ if errors .As (err , & exitErr ) && exitErr .ExitCode () == 1 {
758+ return nil // No dockerd process found, nothing to reload
759+ }
760+ return err
761+ }
762+ return nil
718763}
0 commit comments