balena-cli/lib/actions/ssh.coffee

150 lines
4.8 KiB
CoffeeScript
Raw Normal View History

2016-04-24 19:52:41 +00:00
###
Copyright 2016-2017 Balena
2016-04-24 19:52:41 +00:00
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
###
2017-12-18 13:31:07 +00:00
commandOptions = require('./command-options')
{ normalizeUuidProp } = require('../utils/normalization')
2017-12-18 13:31:07 +00:00
2016-04-24 19:52:41 +00:00
module.exports =
signature: 'ssh [uuid]'
description: 'get a shell into the running app container of a device'
2016-04-24 19:52:41 +00:00
help: '''
Warning: 'balena ssh' requires an openssh-compatible client to be correctly
installed in your shell environment. For more information (including Windows
support) please check the README here: https://github.com/balena-io/balena-cli
2016-04-24 19:52:41 +00:00
Use this command to get a shell into the running application container of
your device.
Examples:
$ balena ssh MyApp
$ balena ssh 7cf02a6
$ balena ssh 7cf02a6 --port 8080
$ balena ssh 7cf02a6 -v
$ balena ssh 7cf02a6 -s
$ balena ssh 7cf02a6 --noninteractive
2016-04-24 19:52:41 +00:00
'''
permission: 'user'
primary: true
options: [
signature: 'port'
parameter: 'port'
description: 'ssh gateway port'
alias: 'p'
,
signature: 'verbose'
boolean: true
description: 'increase verbosity'
alias: 'v'
2017-12-18 13:31:07 +00:00
commandOptions.hostOSAccess,
2017-05-18 12:12:52 +00:00
signature: 'noproxy'
boolean: true
2017-05-18 22:25:01 +00:00
description: "don't use the proxy configuration for this connection.
Only makes sense if you've configured proxy globally."
,
signature: 'noninteractive'
boolean: true
description: 'run command non-interactively, do not automatically suggest devices to connect to if UUID not found'
2016-04-24 19:52:41 +00:00
]
action: (params, options, done) ->
normalizeUuidProp(params)
2016-04-24 19:52:41 +00:00
child_process = require('child_process')
2017-05-11 11:34:22 +00:00
Promise = require('bluebird')
balena = require('balena-sdk').fromSharedOptions()
2017-05-18 23:10:14 +00:00
_ = require('lodash')
bash = require('bash')
hasbin = require('hasbin')
{ getSubShellCommand } = require('../utils/helpers')
patterns = require('../utils/patterns')
2016-04-24 19:52:41 +00:00
2017-05-11 11:34:22 +00:00
options.port ?= 22
2016-04-24 19:52:41 +00:00
verbose = if options.verbose then '-vvv' else ''
2017-05-18 23:10:14 +00:00
proxyConfig = global.PROXY_CONFIG
useProxy = !!proxyConfig and not options.noproxy
getSshProxyCommand = (hasTunnelBin) ->
return '' if not useProxy
if not hasTunnelBin
console.warn('''
Proxy is enabled but the `proxytunnel` binary cannot be found.
Please install it if you want to route the `balena ssh` requests through the proxy.
Alternatively you can pass `--noproxy` param to the `balena ssh` command to ignore the proxy config
2017-05-18 23:10:14 +00:00
for the `ssh` requests.
Attemmpting the unproxied request for now.
''')
return ''
tunnelOptions =
proxy: "#{proxyConfig.host}:#{proxyConfig.port}"
dest: '%h:%p'
{ proxyAuth } = proxyConfig
if proxyAuth
i = proxyAuth.indexOf(':')
_.assign tunnelOptions,
user: proxyAuth.substring(0, i)
pass: proxyAuth.substring(i + 1)
proxyCommand = "proxytunnel #{bash.args(tunnelOptions, '--', '=')}"
return "-o #{bash.args({ ProxyCommand: proxyCommand }, '', '=')}"
Promise.try ->
return false if not params.uuid
return balena.models.device.has(params.uuid)
.then (uuidExists) ->
return params.uuid if uuidExists
if options.noninteractive
console.error("Could not find device: #{params.uuid}")
process.exit(1)
return patterns.inferOrSelectDevice()
.then (uuid) ->
2017-05-11 11:34:22 +00:00
console.info("Connecting to: #{uuid}")
balena.models.device.get(uuid)
.then (device) ->
patterns.exitWithExpectedError('Device is not online') if not device.is_online
Promise.props
username: balena.auth.whoami()
2017-04-21 10:10:30 +00:00
uuid: device.uuid
# get full uuid
containerId: if options.host then '' else balena.models.device.getApplicationInfo(device.uuid).get('containerId')
proxyUrl: balena.settings.get('proxyUrl')
2017-05-18 23:10:14 +00:00
hasTunnelBin: if useProxy then hasbin('proxytunnel') else null
.then ({ username, uuid, containerId, proxyUrl, hasTunnelBin }) ->
throw new Error('Did not find running application container') if not containerId?
Promise.try ->
2017-05-18 23:10:14 +00:00
sshProxyCommand = getSshProxyCommand(hasTunnelBin)
if options.host
accessCommand = "host #{uuid}"
2017-12-18 13:31:07 +00:00
else
accessCommand = "enter #{uuid} #{containerId}"
command = "ssh #{verbose} -t \
-o LogLevel=ERROR \
-o StrictHostKeyChecking=no \
-o UserKnownHostsFile=/dev/null \
2017-05-11 11:34:22 +00:00
#{sshProxyCommand} \
-p #{options.port} #{username}@ssh.#{proxyUrl} #{accessCommand}"
subShellCommand = getSubShellCommand(command)
child_process.spawn subShellCommand.program, subShellCommand.args,
stdio: 'inherit'
.nodeify(done)