{"action":"created","issue":{"url":"https://api.github.com/repos/capistrano/sshkit/issues/234","labels_url":"https://api.github.com/repos/capistrano/sshkit/issues/234/labels{/name}","comments_url":"https://api.github.com/repos/capistrano/sshkit/issues/234/comments","events_url":"https://api.github.com/repos/capistrano/sshkit/issues/234/events","html_url":"https://github.com/capistrano/sshkit/pull/234","id":69929284,"number":234,"title":"Support pty or interaction handlers","user":{"login":"robd","id":11879,"avatar_url":"https://avatars.githubusercontent.com/u/11879?v=3","gravatar_id":"","url":"https://api.github.com/users/robd","html_url":"https://github.com/robd","followers_url":"https://api.github.com/users/robd/followers","following_url":"https://api.github.com/users/robd/following{/other_user}","gists_url":"https://api.github.com/users/robd/gists{/gist_id}","starred_url":"https://api.github.com/users/robd/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/robd/subscriptions","organizations_url":"https://api.github.com/users/robd/orgs","repos_url":"https://api.github.com/users/robd/repos","events_url":"https://api.github.com/users/robd/events{/privacy}","received_events_url":"https://api.github.com/users/robd/received_events","type":"User","site_admin":false},"labels":[],"state":"open","locked":false,"assignee":null,"milestone":null,"comments":15,"created_at":"2015-04-21T19:20:17Z","updated_at":"2015-04-28T11:54:45Z","closed_at":null,"pull_request":{"url":"https://api.github.com/repos/capistrano/sshkit/pulls/234","html_url":"https://github.com/capistrano/sshkit/pull/234","diff_url":"https://github.com/capistrano/sshkit/pull/234.diff","patch_url":"https://github.com/capistrano/sshkit/pull/234.patch"},"body":"I know there has been a lot of confusion and misunderstanding around the `Netssh.config.pty` option with a lot of people (myself included - sorry!) thinking that interactive sessions are supported in SSHKit. My understanding is that full interactivity support is not a direction SSHKit should go in (I agree). So this is an attempt at implementing a minimal patch to allow users to write their own programmatic, rules-based handlers to support interactive workflows. ie 'When the server responds with x', 'send y'.\r\n\r\nThis PR adds support for an enhanced `:pty` option on `Command` in addition to the existing global level  `Netssh.config.pty` option. This `:pty` option still supports values of `true` and `false`/`nil` as before, but also now supports passing a handler object which defines `on_stdout(data, channel)` and/or `on_sterr(data, channel)`. If specified, this handler is called once per line from the server and provides the channel so that data can be written in response to particular server responses.\r\n\r\nI do not propose to add any specific handlers to SSHKit - these could be provided by one or more external gems, but below is an example of a handler to support interactive sudo based on the [working implementation](https://github.com/kentaroi/sshkit-sudo/blob/2f1eefef53efb9f57f732159a7d75a7f26c6fc51/lib/sshkit/sudo/backends_ext/netssh.rb#L28-L37) in [ssh-sudo](https://github.com/kentaroi/sshkit-sudo):\r\n\r\n```ruby\r\n# The following would not be included in SSHKit, \r\n# but here is an example of a pty handler which supports interactive sudo\r\n\r\nclass InteractiveSudo\r\n  def on_stdout(data, ch)\r\n    if data =~ /Sorry.*\\stry\\sagain/\r\n      @cached_password = nil\r\n    end\r\n    if data =~ /password.*:/\r\n      pass = @cached_password\r\n      unless pass\r\n        pass = $stdin.noecho(&:gets)\r\n        @cached_password = pass\r\n      end\r\n      ch.send_data(pass)\r\n    end\r\n  end\r\nend\r\n\r\nINTERACTIVE_SUDO = InteractiveSudo.new()\r\n\r\n# Client code:\r\n\r\nexecute(:sudo, 'Some command', :pty => INTERACTIVE_SUDO)\r\n```\r\n\r\nAs part of this work, I also formalised the mutations allowed on the `@stdout`, `@stderr`, `@full_stdout` and `@full_stderr` attributes on `Command`, and removed the writers. I have some questions around this and ideas for a bit of further cleanup / tests here too.\r\n\r\nThe test suite passes, but I didn't want to spend time adding more tests until I had some indication that this PR is desirable. As well as tests, I'd also add comprehensive docs about the interactivity support, and try to add comments to all the issues / SO questions to steer people in the right direction.\r\n\r\nThere are a number of outstanding questions, especially around local server support, but we can get into those if you're happy with the basic idea. This work was based on discussions with @kentaroi on his [ssh-sudo](https://github.com/kentaroi/sshkit-sudo) project.\r\n\r\nEDIT: It turns out, the changes to pty are not necessary to support the basic use case of interacting with the server, so I reworked this PR see comments below for subsequent developments"},"comment":{"url":"https://api.github.com/repos/capistrano/sshkit/issues/comments/97034258","html_url":"https://github.com/capistrano/sshkit/pull/234#issuecomment-97034258","issue_url":"https://api.github.com/repos/capistrano/sshkit/issues/234","id":97034258,"user":{"login":"leehambley","id":18952,"avatar_url":"https://avatars.githubusercontent.com/u/18952?v=3","gravatar_id":"","url":"https://api.github.com/users/leehambley","html_url":"https://github.com/leehambley","followers_url":"https://api.github.com/users/leehambley/followers","following_url":"https://api.github.com/users/leehambley/following{/other_user}","gists_url":"https://api.github.com/users/leehambley/gists{/gist_id}","starred_url":"https://api.github.com/users/leehambley/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/leehambley/subscriptions","organizations_url":"https://api.github.com/users/leehambley/orgs","repos_url":"https://api.github.com/users/leehambley/repos","events_url":"https://api.github.com/users/leehambley/events{/privacy}","received_events_url":"https://api.github.com/users/leehambley/received_events","type":"User","site_admin":false},"created_at":"2015-04-28T11:54:45Z","updated_at":"2015-04-28T11:54:45Z","body":"> whether a program requires a pty is completely dependant on the implementation details of the program.\r\n\r\nRight, and as my colleague likes to say, since most things these days seem to be implemented by kids using scripting languages, they often don't behave correctly. i.e just because `$stdin` isn't a tty, doesn't mean you can't read from it! (good example `echo yes | apt-get install something` vs. `apt-get install -y something`)\r\n\r\n> I guess one problem with the current implementation in this PR, is that it will not support the use case of sending input directly to a command which has no prompt.\r\n\r\nIndeed, I'd have to check, but I'm pretty sure, for example that `apt-get` just fails if there's a question to be answered, and it can't read from stdin, and stdout isn't a prompt. Usually reads from stdin (like any reads) would block until there is something to be read, programs might make use of [`man 2s stat`] and the value of `S_ISCHR` to find out if stdin is a *character device*, this also won't work in this case, as far as I know. Ruby implements that as [`FileTest.chardev?`](http://ruby-doc.org/core-2.2.2/FileTest.html#method-i-chardev-3F), but I've honestly never seen how a simple program that uses this responds when called via Capistrano/Net::SSH/SSHKit, et al.\r\n\r\nFinal word on this about \"if it doesn't prompt for input\", it's poorly written, harking back to the `apt-get` example, they've provided three ways to give it answers:\r\n\r\n1. Pipe something in to it's stdin\r\n2. Give it a command line flag\r\n3. Answer the prompt\r\n\r\nThis is what I might call \"well written\", without finger pointing other programs that don't behave this way. \r\n\r\nI'm glad you enjoyed \"A whole new world\", I didn't enjoy the ending :)"}}