From c9b1456759452052f42649f8a8e69e671cdebb60 Mon Sep 17 00:00:00 2001 From: Aaron Walker Date: Sun, 17 May 2020 16:53:35 -0400 Subject: [PATCH 1/7] add docker exec as an attach command --- docs/configuration.md | 16 ++++++--------- docs/schema/vimspector.schema.json | 21 +++++++++++++++++--- python3/vimspector/debug_session.py | 30 +++++++++++++++++++++-------- 3 files changed, 46 insertions(+), 21 deletions(-) diff --git a/docs/configuration.md b/docs/configuration.md index 3ad67f3..c37f26b 100644 --- a/docs/configuration.md +++ b/docs/configuration.md @@ -394,7 +394,9 @@ and connect to it using debugpy. The usage pattern is to hit ``, enter `host` (the host where your app runs), `account` (the account it runs under), and `port` (a port that will be opened on -the remote host). Vimspector then orchestrates the various tools to set you up. +the remote host). Vimspector also supports exec'ing into Docker run containers +with `container` (the container name or id your app is running in). +Vimspector then orchestrates the various tools to set you up. ```json @@ -403,9 +405,10 @@ the remote host). Vimspector then orchestrates the various tools to set you up. "python-remote": { "port": "${port}", "host": "${host}", + "container": "${container}", "launch": { "remote": { - "host": "${host}", // Remote host to ssh to (mandatory) + "host": "${host}", // Remote host to ssh to (mandatory if not using container) "account": "${account}", // User to connect as (optional) // Optional.... Manual additional arguments for ssh @@ -432,14 +435,7 @@ the remote host). Vimspector then orchestrates the various tools to set you up. }, "attach": { "remote": { - "host": "${host}", // Remote host to ssh to (mandatory) - "account": "${account}", // User to connect as (optional) - - // Optional.... Manual additional arguments for ssh - // "ssh": { - // "args": [ "-o", "StrictHostKeyChecking=no" ] - // }, - + "container": "${container}" // Command to get the PID of the process to attach (mandatory) "pidCommand": [ // diff --git a/docs/schema/vimspector.schema.json b/docs/schema/vimspector.schema.json index ace8589..c8af196 100644 --- a/docs/schema/vimspector.schema.json +++ b/docs/schema/vimspector.schema.json @@ -78,8 +78,19 @@ "properties": { "remote": { "type": "object", - "required": [ "host" ], - "description": "Configures how Vimspector will marshal remote debugging requests. When remote debugging, Vimspector will ssh to 'account'@'host' and run 'pidCommand', 'attachCommands', 'runCommands', etc. based on the 'remote-command' option in the debug configuration. If 'remote-command' is 'launch', it runs 'runCommand(s)', otherwise (it's 'attach') vimspector runs 'pidCommand', followed by 'attachCommand(s)'.Then it starts up the debug adapter with the debug configuration as normal. Usually this is configured with an 'attach' request (whether we remotely 'launched' or not). Once the initialization exchange is complete, Vimspector runs the optional 'initCompleteCommand' which can be used to force the application to break, e.g. by sending it SIGINT. This is required on some platforms which have buggy gdbservers (for example)", + "oneOf": [ + { + "required": [ + "host" + ] + }, + { + "required": [ + "container" + ] + } + ], + "description": "Configures how Vimspector will marshal remote debugging requests. When remote debugging, Vimspector will either ssh to 'account'@'host' or docker exec -it to 'container' and run 'pidCommand', 'attachCommands', 'runCommands', etc. based on the 'remote-command' option in the debug configuration. If 'remote-command' is 'launch', it runs 'runCommand(s)', otherwise (it's 'attach') vimspector runs 'pidCommand', followed by 'attachCommand(s)'.Then it starts up the debug adapter with the debug configuration as normal. Usually this is configured with an 'attach' request (whether we remotely 'launched' or not). Once the initialization exchange is complete, Vimspector runs the optional 'initCompleteCommand' which can be used to force the application to break, e.g. by sending it SIGINT. This is required on some platforms which have buggy gdbservers (for example)", "properties": { "account": { "type": "string", @@ -87,7 +98,11 @@ }, "host": { "type": "string", - "description": "Name of the remote host to connect to (via passwordless SSH). " + "description": "Name of the remote host to connect to (via passwordless SSH)." + }, + "container": { + "type": "string", + "description": "Name or container id of the docker run container to connect to (via docker exec -it)." }, "pidCommand": { "type": "array", diff --git a/python3/vimspector/debug_session.py b/python3/vimspector/debug_session.py index c434c5a..14dca64 100644 --- a/python3/vimspector/debug_session.py +++ b/python3/vimspector/debug_session.py @@ -613,13 +613,13 @@ class DebugSession( object ): # e.g. expand variables when we use them, not all at once. This would # remove the whole %PID% hack. remote = atttach_config[ 'remote' ] - ssh = self._GetSSHCommand( remote ) + attach_cmd = self._GetAttachCommand( remote ) # FIXME: Why does this not use self._GetCommands ? - cmd = ssh + remote[ 'pidCommand' ] + pid_cmd = attach_cmd + remote[ 'pidCommand' ] - self._logger.debug( 'Getting PID: %s', cmd ) - pid = subprocess.check_output( cmd ).decode( 'utf-8' ).strip() + self._logger.debug( 'Getting PID: %s', pid_cmd ) + pid = subprocess.check_output( pid_cmd ).decode( 'utf-8' ).strip() self._logger.debug( 'Got PID: %s', pid ) if not pid: @@ -628,7 +628,7 @@ class DebugSession( object ): return if 'initCompleteCommand' in remote: - initcmd = ssh + remote[ 'initCompleteCommand' ][ : ] + initcmd = attach_cmd + remote[ 'initCompleteCommand' ][ : ] for index, item in enumerate( initcmd ): initcmd[ index ] = item.replace( '%PID%', pid ) @@ -638,7 +638,7 @@ class DebugSession( object ): commands = self._GetCommands( remote, 'attach' ) for command in commands: - cmd = ssh + command[ : ] + cmd = attach_cmd + command[ : ] for index, item in enumerate( cmd ): cmd[ index ] = item.replace( '%PID%', pid ) @@ -665,11 +665,11 @@ class DebugSession( object ): if 'remote' in run_config: remote = run_config[ 'remote' ] - ssh = self._GetSSHCommand( remote ) + attach_cmd = self._GetAttachCommand( remote ) commands = self._GetCommands( remote, 'run' ) for index, command in enumerate( commands ): - cmd = ssh + command[ : ] + cmd = attach_cmd + command[ : ] full_cmd = [] for item in cmd: if isinstance( command_line, list ): @@ -694,6 +694,20 @@ class DebugSession( object ): return ssh + def _GetDockerCommand( self, remote ): + docker = [ 'docker exec -it' ] + remote.get( 'docker', {} ).get( 'args', [] ) + if 'container' not in remote: + raise ValueError( "Invalid container; must be string" ) + docker.append( remote[ 'container' ] ) + return docker + + def _GetAttachCommand( self, remote ): + if any( remote.get( 'ssh' ), remote.get( 'account' ), remote.get( 'host' ) ): + return self._GetSSHCommand( remote ) + elif any( remote.get( 'docker' ), remote.get( 'container' ) ): + return self._GetDockerCommand( remote ) + raise ValueError( 'Could not determine attach command' ) + def _GetCommands( self, remote, pfx ): commands = remote.get( pfx + 'Commands', None ) From 62b3070c50470a27146d9306989b5d0001b1fff8 Mon Sep 17 00:00:00 2001 From: Aaron Walker Date: Sun, 17 May 2020 17:39:04 -0400 Subject: [PATCH 2/7] lint --- python3/vimspector/debug_session.py | 32 +++++++++++++++++++---------- 1 file changed, 21 insertions(+), 11 deletions(-) diff --git a/python3/vimspector/debug_session.py b/python3/vimspector/debug_session.py index 14dca64..21d2492 100644 --- a/python3/vimspector/debug_session.py +++ b/python3/vimspector/debug_session.py @@ -613,10 +613,10 @@ class DebugSession( object ): # e.g. expand variables when we use them, not all at once. This would # remove the whole %PID% hack. remote = atttach_config[ 'remote' ] - attach_cmd = self._GetAttachCommand( remote ) + remote_exec_cmd = self._GetRemoteExecCommand( remote ) # FIXME: Why does this not use self._GetCommands ? - pid_cmd = attach_cmd + remote[ 'pidCommand' ] + pid_cmd = remote_exec_cmd + remote[ 'pidCommand' ] self._logger.debug( 'Getting PID: %s', pid_cmd ) pid = subprocess.check_output( pid_cmd ).decode( 'utf-8' ).strip() @@ -628,7 +628,7 @@ class DebugSession( object ): return if 'initCompleteCommand' in remote: - initcmd = attach_cmd + remote[ 'initCompleteCommand' ][ : ] + initcmd = remote_exec_cmd + remote[ 'initCompleteCommand' ][ : ] for index, item in enumerate( initcmd ): initcmd[ index ] = item.replace( '%PID%', pid ) @@ -638,7 +638,7 @@ class DebugSession( object ): commands = self._GetCommands( remote, 'attach' ) for command in commands: - cmd = attach_cmd + command[ : ] + cmd = remote_exec_cmd + command[ : ] for index, item in enumerate( cmd ): cmd[ index ] = item.replace( '%PID%', pid ) @@ -665,11 +665,11 @@ class DebugSession( object ): if 'remote' in run_config: remote = run_config[ 'remote' ] - attach_cmd = self._GetAttachCommand( remote ) + remote_exec_cmd = self._GetRemoteExecCommand( remote ) commands = self._GetCommands( remote, 'run' ) for index, command in enumerate( commands ): - cmd = attach_cmd + command[ : ] + cmd = remote_exec_cmd + command[ : ] full_cmd = [] for item in cmd: if isinstance( command_line, list ): @@ -695,16 +695,26 @@ class DebugSession( object ): return ssh def _GetDockerCommand( self, remote ): - docker = [ 'docker exec -it' ] + remote.get( 'docker', {} ).get( 'args', [] ) + args = remote.get( 'docker', {} ).get( 'args', [] ) + docker = [ 'docker exec -it' ] + args if 'container' not in remote: - raise ValueError( "Invalid container; must be string" ) + raise ValueError( 'Invalid container; must be string' ) docker.append( remote[ 'container' ] ) return docker - def _GetAttachCommand( self, remote ): - if any( remote.get( 'ssh' ), remote.get( 'account' ), remote.get( 'host' ) ): + def _GetRemoteExecCommand( self, remote ): + is_ssh_cmd = any( + remote.get( 'ssh' ), + remote.get( 'account' ), + remote.get( 'host' ), + ) + is_docker_cmd = any( + remote.get( 'docker' ), + remote.get( 'container' ), + ) + if is_ssh_cmd: return self._GetSSHCommand( remote ) - elif any( remote.get( 'docker' ), remote.get( 'container' ) ): + elif is_docker_cmd: return self._GetDockerCommand( remote ) raise ValueError( 'Could not determine attach command' ) From 0bf511debcc814b7bd8c735fcf8af4f85f432462 Mon Sep 17 00:00:00 2001 From: Aaron Walker Date: Sun, 17 May 2020 20:22:40 -0400 Subject: [PATCH 3/7] add docker docs --- docs/configuration.md | 121 ++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 116 insertions(+), 5 deletions(-) diff --git a/docs/configuration.md b/docs/configuration.md index c37f26b..7ac5991 100644 --- a/docs/configuration.md +++ b/docs/configuration.md @@ -383,8 +383,8 @@ research that. Vimspector's tools are intended to automate your existing process for setting this up rather than to offer batteries-included approach. Ultimately, all -vimspector is going to do is run your commands over SSH and co-ordinate with the -adapter. +vimspector is going to do is run your commands over SSH, or docker, and +co-ordinate with the adapter. ### Python (debugpy) Example @@ -405,7 +405,6 @@ Vimspector then orchestrates the various tools to set you up. "python-remote": { "port": "${port}", "host": "${host}", - "container": "${container}", "launch": { "remote": { "host": "${host}", // Remote host to ssh to (mandatory if not using container) @@ -435,7 +434,8 @@ Vimspector then orchestrates the various tools to set you up. }, "attach": { "remote": { - "container": "${container}" + "host": "${host}", // Remote host to ssh to (mandatory if not using container) + "account": "${account}", // User to connect as (optional) // Command to get the PID of the process to attach (mandatory) "pidCommand": [ // @@ -518,7 +518,7 @@ Vimspector then orchestrates the various tools to set you up. ### C-family (gdbserver) Example -This example uses vimspector to remotely luanch or attach to a binary using +This example uses vimspector to remotely launch or attach to a binary using `gdbserver` and then instructs vscode-cpptools to attach to that `gdbserver`. The appraoch is very similar to the above for python, just that we use gdbserver @@ -606,6 +606,117 @@ and have to tell cpptools a few more options. } ``` +### Docker Example + +This example uses vimspector to remotely launch or attach to a docker container +port. + +``` json +{ + "adapters": { + "python-remote": { + "port": "${port}", + "launch": { + "remote": { + "container": "${container}", // Docker container id or name to exec into to. + + // Command to launch the debugee and attach the debugger; + // %CMD% replaced with the remote-cmdLine configured in the launch + // configuration. (mandatory) + "launchCommmand": [ + "python", "-m", "debugpy", "--listen 0.0.0.0:${port}", + "%CMD%" + ] + + // Optional alternative to launchCommmand (if you need to run multiple + // commands) + // "launchCommmands": [ + // [ /* first command */ ], + // [ /* second command */ ] + // ] + + } + }, + "attach": { + "remote": { + "container": "${container}", // Docker container id or name to exec into. + // Command to get the PID of the process to attach (mandatory) + // This command gets appended to "docker exec ${container}" + "pidCommand": [ + // + // Remember taht you can use ${var} to ask for input. I use this to + // call a custom command to returm the PID for a named service, so + // here's an examle: + // + "sh", "-c", "pgrep", "-f ${filename}" + ], + + // Command to attach the debugger; %PID% replaced with output of + // pidCommand above (mandatory) + "attachCommand": [ + "sh", "-c", "python", "-m", "debugpy", "--listen 0.0.0.0:${port}", + "--pid", "%PID%" + ] + + // Optional alternative to attachCommand (if you need to run multiple + // commands) + // "attachCommands": [ + // [ /* first command */ ], + // [ /* second command */ ] + // ], + + // Optional.... useful with buggy gdbservers to kill -TRAP %PID% + // "initCompleteCommand": [ + // /* optional command to run after initialized */ + // ] + + } + } + } + }, + "configurations": { + "remote-launch": { + "adapter": "python-remote", + + "remote-request": "launch", + "remote-cmdLine": [ + "${RemoteRoot}/${fileBasename}", "*${args}" + ], + + "configuration": { + "request": "attach", + "pathMappings": [ + { + "localRoot": "${workspaceRoot}", + "remoteRoot": "${RemoteRoot}" + } + ] + } + }, + "remote-attach": { + "variables": { + // Just an example of how to specify a variable manually rather than + // vimspector asking for input from the user + "FileName": "${fileName}" + }, + + "adapter": "python-remote", + "remote-request": "attach", + + "configuration": { + "request": "attach", + "pathMappings": [ + { + "localRoot": "${workspaceRoot}", + "remoteRoot": "${RemoteRoot}" + } + ] + } + } + } +} +``` + ## Appendix: Configuration file format The configuration files are text files which must be UTF-8 encoded. They From 5e64b07e8dc31b382355459e8388e706443afe4e Mon Sep 17 00:00:00 2001 From: Aaron Walker Date: Sun, 17 May 2020 20:22:53 -0400 Subject: [PATCH 4/7] flake and update docker exec command --- python3/vimspector/debug_session.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/python3/vimspector/debug_session.py b/python3/vimspector/debug_session.py index 21d2492..456fd54 100644 --- a/python3/vimspector/debug_session.py +++ b/python3/vimspector/debug_session.py @@ -695,10 +695,10 @@ class DebugSession( object ): return ssh def _GetDockerCommand( self, remote ): - args = remote.get( 'docker', {} ).get( 'args', [] ) - docker = [ 'docker exec -it' ] + args if 'container' not in remote: raise ValueError( 'Invalid container; must be string' ) + + docker = [ 'docker exec' ] docker.append( remote[ 'container' ] ) return docker @@ -716,7 +716,7 @@ class DebugSession( object ): return self._GetSSHCommand( remote ) elif is_docker_cmd: return self._GetDockerCommand( remote ) - raise ValueError( 'Could not determine attach command' ) + raise ValueError( 'Could not determine remote exec command' ) def _GetCommands( self, remote, pfx ): From cd1b304d3052f0b982c1121d634540f6c558e2dc Mon Sep 17 00:00:00 2001 From: Aaron Walker Date: Mon, 18 May 2020 00:57:13 -0400 Subject: [PATCH 5/7] relint --- python3/vimspector/debug_session.py | 30 ++++++++++++++--------------- 1 file changed, 15 insertions(+), 15 deletions(-) diff --git a/python3/vimspector/debug_session.py b/python3/vimspector/debug_session.py index 456fd54..775a03c 100644 --- a/python3/vimspector/debug_session.py +++ b/python3/vimspector/debug_session.py @@ -696,27 +696,27 @@ class DebugSession( object ): def _GetDockerCommand( self, remote ): if 'container' not in remote: - raise ValueError( 'Invalid container; must be string' ) + raise ValueError( 'Invalid container; must be string' ) docker = [ 'docker exec' ] docker.append( remote[ 'container' ] ) return docker def _GetRemoteExecCommand( self, remote ): - is_ssh_cmd = any( - remote.get( 'ssh' ), - remote.get( 'account' ), - remote.get( 'host' ), - ) - is_docker_cmd = any( - remote.get( 'docker' ), - remote.get( 'container' ), - ) - if is_ssh_cmd: - return self._GetSSHCommand( remote ) - elif is_docker_cmd: - return self._GetDockerCommand( remote ) - raise ValueError( 'Could not determine remote exec command' ) + is_ssh_cmd = any( + remote.get( 'ssh' ), + remote.get( 'account' ), + remote.get( 'host' ), + ) + is_docker_cmd = any( + remote.get( 'docker' ), + remote.get( 'container' ), + ) + if is_ssh_cmd: + return self._GetSSHCommand( remote ) + elif is_docker_cmd: + return self._GetDockerCommand( remote ) + raise ValueError( 'Could not determine remote exec command' ) def _GetCommands( self, remote, pfx ): From 3d113eaec49cc9d1bc801a3e68d1929dd78b27ff Mon Sep 17 00:00:00 2001 From: Aaron Walker Date: Wed, 20 May 2020 00:44:32 -0400 Subject: [PATCH 6/7] comments --- docs/configuration.md | 10 +++++----- python3/vimspector/debug_session.py | 17 ++++------------- 2 files changed, 9 insertions(+), 18 deletions(-) diff --git a/docs/configuration.md b/docs/configuration.md index 7ac5991..6c82584 100644 --- a/docs/configuration.md +++ b/docs/configuration.md @@ -419,7 +419,7 @@ Vimspector then orchestrates the various tools to set you up. // %CMD% replaced with the remote-cmdLine configured in the launch // configuration. (mandatory) "launchCommmand": [ - "python", "-m", "debugpy", "--listen 0.0.0.0:${port}", + "python", "-m", "debugpy", "--listen", "0.0.0.0:${port}", "%CMD%" ] @@ -449,7 +449,7 @@ Vimspector then orchestrates the various tools to set you up. // Command to attach the debugger; %PID% replaced with output of // pidCommand above (mandatory) "attachCommand": [ - "python", "-m", "debugpy", "--listen 0.0.0.0:${port}", + "python", "-m", "debugpy", "--listen", "0.0.0.0:${port}", "--pid", "%PID%" ] @@ -624,7 +624,7 @@ port. // %CMD% replaced with the remote-cmdLine configured in the launch // configuration. (mandatory) "launchCommmand": [ - "python", "-m", "debugpy", "--listen 0.0.0.0:${port}", + "python", "-m", "debugpy", "--listen", "0.0.0.0:${port}", "%CMD%" ] @@ -648,13 +648,13 @@ port. // call a custom command to returm the PID for a named service, so // here's an examle: // - "sh", "-c", "pgrep", "-f ${filename}" + "sh", "-c", "pgrep", "-f", "${filename}" ], // Command to attach the debugger; %PID% replaced with output of // pidCommand above (mandatory) "attachCommand": [ - "sh", "-c", "python", "-m", "debugpy", "--listen 0.0.0.0:${port}", + "sh", "-c", "python", "-m", "debugpy", "--listen", "0.0.0.0:${port}", "--pid", "%PID%" ] diff --git a/python3/vimspector/debug_session.py b/python3/vimspector/debug_session.py index 775a03c..6e65f20 100644 --- a/python3/vimspector/debug_session.py +++ b/python3/vimspector/debug_session.py @@ -695,23 +695,14 @@ class DebugSession( object ): return ssh def _GetDockerCommand( self, remote ): - if 'container' not in remote: - raise ValueError( 'Invalid container; must be string' ) - - docker = [ 'docker exec' ] + docker = [ 'docker', 'exec' ] docker.append( remote[ 'container' ] ) return docker def _GetRemoteExecCommand( self, remote ): - is_ssh_cmd = any( - remote.get( 'ssh' ), - remote.get( 'account' ), - remote.get( 'host' ), - ) - is_docker_cmd = any( - remote.get( 'docker' ), - remote.get( 'container' ), - ) + is_ssh_cmd = any( key in remote for key in [ 'ssh','host', 'account' ] ) + is_docker_cmd = 'container' in remote + if is_ssh_cmd: return self._GetSSHCommand( remote ) elif is_docker_cmd: From ae2ba01c5b08eb0cd0f52d3ea21ee4727d96b8e7 Mon Sep 17 00:00:00 2001 From: Aaron Walker Date: Wed, 20 May 2020 00:53:02 -0400 Subject: [PATCH 7/7] lint --- python3/vimspector/debug_session.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/python3/vimspector/debug_session.py b/python3/vimspector/debug_session.py index 6e65f20..9bf5c15 100644 --- a/python3/vimspector/debug_session.py +++ b/python3/vimspector/debug_session.py @@ -700,7 +700,9 @@ class DebugSession( object ): return docker def _GetRemoteExecCommand( self, remote ): - is_ssh_cmd = any( key in remote for key in [ 'ssh','host', 'account' ] ) + is_ssh_cmd = any( key in remote for key in [ 'ssh', + 'host', + 'account', ] ) is_docker_cmd = 'container' in remote if is_ssh_cmd: