Merge lp:~dimitern/juju-core/302-lp-1281577-ssh-command-extra-args into lp:~go-bot/juju-core/trunk
- 302-lp-1281577-ssh-command-extra-args
- Merge into trunk
Status: | Merged |
---|---|
Approved by: | Dimiter Naydenov |
Approved revision: | no longer in the source branch. |
Merged at revision: | 2348 |
Proposed branch: | lp:~dimitern/juju-core/302-lp-1281577-ssh-command-extra-args |
Merge into: | lp:~go-bot/juju-core/trunk |
Prerequisite: | lp:~dimitern/juju-core/301-lp-1282553-use-curl-not-wget-precise |
Diff against target: |
662 lines (+185/-87) 12 files modified
cmd/juju/addmachine.go (+1/-0) cmd/juju/debughooks_test.go (+2/-2) cmd/juju/scp.go (+34/-12) cmd/juju/scp_test.go (+40/-11) cmd/juju/ssh.go (+10/-8) cmd/juju/ssh_test.go (+12/-18) utils/ssh/run_test.go (+1/-1) utils/ssh/ssh.go (+15/-7) utils/ssh/ssh_gocrypto.go (+2/-1) utils/ssh/ssh_gocrypto_test.go (+1/-1) utils/ssh/ssh_openssh.go (+48/-14) utils/ssh/ssh_test.go (+19/-12) |
To merge this branch: | bzr merge lp:~dimitern/juju-core/302-lp-1281577-ssh-command-extra-args |
Related bugs: |
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
Adam Collard (community) | Needs Fixing | ||
Juju Engineering | Pending | ||
Review via email: mp+207498@code.launchpad.net |
Commit message
cmd/juju: ssh/scp commands extra arguments; fixes
Removed the extraneous "--" that the ssh command
adds to the passed arguments, so any extra arguments
after the command will be forwarded to ssh properly.
It worked in 1.16, so this fixes a regression.
This fixes bug #1281577.
The scp command claims in its help that it supports
-- to pass extra arguments to scp, but it does not
and also does not support multiple local/remote
targets to be specified, as OpenSSH scp command does.
This fixes bug #1283412.
Also, improved a lot of ssh-related tests in both
utils/ssh/ and in cmd/juju/, adding new tests as
needed for the extra arguments.
Improved help texts for ssh, scp and add-machine
commands (included examples for the extra args
and multiple targets for scp, added ssh: example
for add-machine).
All changes are tested live:
- bootstrapping on EC2 and running ssh or scp
to test extra args and multiple targets;
- manual bootstrapping
- add-machine ssh:user@host to an existing env;
- using juju on Windows, to test the embedded
go.crypto ssh command.
https:/
R=axwalk, gz
Description of the change
cmd/juju: ssh/scp commands extra arguments; fixes
Removed the extraneous "--" that the ssh command
adds to the passed arguments, so any extra arguments
after the command will be forwarded to ssh properly.
It worked in 1.16, so this fixes a regression.
This fixes bug #1281577.
The scp command claims in its help that it supports
-- to pass extra arguments to scp, but it does not
and also does not support multiple local/remote
targets to be specified, as OpenSSH scp command does.
This fixes bug #1283412.
Also, improved a lot of ssh-related tests in both
utils/ssh/ and in cmd/juju/, adding new tests as
needed for the extra arguments.
Improved help texts for ssh, scp and add-machine
commands (included examples for the extra args
and multiple targets for scp, added ssh: example
for add-machine).
All changes are tested live:
- bootstrapping on EC2 and running ssh or scp
to test extra args and multiple targets;
- manual bootstrapping
- add-machine ssh:user@host to an existing env;
- using juju on Windows, to test the embedded
go.crypto ssh command.
Dimiter Naydenov (dimitern) wrote : | # |
Martin Packman (gz) wrote : | # |
LGTM, but there are a few other questionable bits involved with the
original change in r2116 that rely on sane -- behaviour - perhaps we
should bug Andrew as well.
Andrew Wilkins (axwalk) wrote : | # |
On 2014/02/20 17:14:00, gz wrote:
> LGTM, but there are a few other questionable bits involved with the
original
> change in r2116 that rely on sane -- behaviour - perhaps we should bug
Andrew as
> well.
Which bits?
I don't recall my rationale, but it was probably something like: this is
relying on implementation details, namely that we're using OpenSSH.
We're not necessarily using OpenSSH now, so those args passed through
will break if the embedded client is used. I suppose it's not a big
deal.
Dimiter Naydenov (dimitern) wrote : | # |
Please take a look.
Andrew Wilkins (axwalk) wrote : | # |
https:/
File cmd/juju/scp.go (left):
https:/
cmd/juju/scp.go:73: // BUG(dfc) This will not work for IPv6 addresses
like 2001:db8:
The BUG is still relevant, no?
https:/
File cmd/juju/scp.go (right):
https:/
cmd/juju/scp.go:32: Copy 2 files from two units to the local backup/
directory, passig -v
s/passig/passing/
https:/
cmd/juju/scp.go:35: juju scp ubuntu/
ubuntu/
While it may be possible to do this, is it useful to highlight it as an
example? It seems a bit pathological, and not actually useful, so I'd
just move "-- -v" to the end.
Also, we shouldn't *need* -- at all if the options are at the end,
right? It would good if you could just do:
juju scp 0:/remote/dir /local/dir -r
We may want to explicitly state that options are not supported unless
OpenSSH is available on the system... although, Copy isn't even
implemented using go.crypto at the moment.
Frankly, I think it's a bad idea to rely on implementation details like
this (which is not your fault of course). It would be better if we just
implemented a subset of the scp options and passed them through. Then
we're more easily able to implement those for the go.crypto client.
https:/
cmd/juju/scp.go:91: if strings.
Actually, one of the more common usages of pass-through args that I've
seen so far is "-o", "option". This won't work, since the option value
will end up in the wrong place.
https:/
File utils/ssh/ssh.go (right):
https:/
utils/ssh/
remote host(s).
s/a /and /
(or just "local and remote")
https:/
utils/ssh/
they are passed
Client is implementation agnostic. Talking about scp here is wrong.
https:/
File utils/ssh/
https:/
utils/ssh/
map[string]
What's the point of this change? To group options? Why? Seems to be
complicating things for no practical benefit.
Adam Collard (adam-collard) wrote : | # |
https:/
cmd/juju/scp.go:32: Copy 2 files from two units to the local backup/
directory, passig -v
s/passig/passing/
It doesn't look like this comment got addressed.
http://
Shows it still says "passig"
Dimiter Naydenov (dimitern) wrote : | # |
I'm about to propose a follow-up with the last review's suggestions, as the branch landed before that.
Dimiter Naydenov (dimitern) wrote : | # |
Thanks for the review!
Find below my answers, and please review the follow-up which implements
the suggestions, here: https:/
https:/
File cmd/juju/scp.go (left):
https:/
cmd/juju/scp.go:73: // BUG(dfc) This will not work for IPv6 addresses
like 2001:db8:
On 2014/02/24 04:29:36, axw wrote:
> The BUG is still relevant, no?
I thought I fixed it, but I forgot. According to
http://
`\[`+host+`\]:` so that IPv6 addresses will be properly separated from
the path. Fixed and added a test for it.
https:/
File cmd/juju/scp.go (right):
https:/
cmd/juju/scp.go:32: Copy 2 files from two units to the local backup/
directory, passig -v
On 2014/02/24 04:29:36, axw wrote:
> s/passig/passing/
Done.
https:/
cmd/juju/scp.go:35: juju scp ubuntu/
ubuntu/
On 2014/02/24 04:29:36, axw wrote:
> While it may be possible to do this, is it useful to highlight it as
an example?
> It seems a bit pathological, and not actually useful, so I'd just move
"-- -v"
> to the end.
> Also, we shouldn't *need* -- at all if the options are at the end,
right? It
> would good if you could just do:
> juju scp 0:/remote/dir /local/dir -r
Good point, I've changed the implementation, help text, and tests to
accept extra arguments at the end only and -- is no longer needed or
allowed.
> We may want to explicitly state that options are not supported unless
OpenSSH is
> available on the system... although, Copy isn't even implemented using
go.crypto
> at the moment.
Mentioning OpenSSH is useful, added.
Also improved the error message given by go.crypto's Copy, because:
$ juju scp xx yy
ERROR Copy is not implemented
> Frankly, I think it's a bad idea to rely on implementation details
like this
> (which is not your fault of course). It would be better if we just
implemented a
> subset of the scp options and passed them through. Then we're more
easily able
> to implement those for the go.crypto client.
Yeah, that's definitely a future improvement worth doing, but not now.
https:/
cmd/juju/scp.go:91: if strings.
On 2014/02/24 04:29:36, axw wrote:
> Actually, one of the more common usages of pass-through args that I've
seen so
> far is "-o", "option". This won't work, since the option value will
end up in
> the wrong place.
It works actually, added -o SomeOption in the extra args tests.
https:/
File utils/ssh/ssh.go (right):
https:/
utils/ssh/
remote host(s).
On 2014/02/24 04:29:36, axw wrote:
> s/a /and /
> (or just "local and remote"...
Preview Diff
1 | === modified file 'cmd/juju/addmachine.go' |
2 | --- cmd/juju/addmachine.go 2013-12-20 09:25:57 +0000 |
3 | +++ cmd/juju/addmachine.go 2014-02-22 15:58:57 +0000 |
4 | @@ -48,6 +48,7 @@ |
5 | juju add-machine lxc (starts a new machine with an lxc container) |
6 | juju add-machine lxc:4 (starts a new lxc container on machine 4) |
7 | juju add-machine --constraints mem=8G (starts a machine with at least 8GB RAM) |
8 | + juju add-machine ssh:user@10.10.0.3 (manually provisions a machine with ssh) |
9 | |
10 | See Also: |
11 | juju help constraints |
12 | |
13 | === modified file 'cmd/juju/debughooks_test.go' |
14 | --- cmd/juju/debughooks_test.go 2013-12-03 14:19:47 +0000 |
15 | +++ cmd/juju/debughooks_test.go 2014-02-22 15:58:57 +0000 |
16 | @@ -29,10 +29,10 @@ |
17 | stderr string |
18 | }{{ |
19 | args: []string{"mysql/0"}, |
20 | - result: regexp.QuoteMeta(debugHooksArgs + "ubuntu@dummyenv-0.dns -- sudo /bin/bash -c 'F=$(mktemp); echo IyEvYmluL2Jhc2gKKAojIExvY2sgdGhlIGp1anUtPHVuaXQ+LWRlYnVnIGxvY2tmaWxlLgpmbG9jayAtbiA4IHx8IChlY2hvICJGYWlsZWQgdG8gYWNxdWlyZSAvdG1wL2p1anUtdW5pdC1teXNxbC0wLWRlYnVnLWhvb2tzOiB1bml0IGlzIGFscmVhZHkgYmVpbmcgZGVidWdnZWQiIDI+JjE7IGV4aXQgMSkKKAojIENsb3NlIHRoZSBpbmhlcml0ZWQgbG9jayBGRCwgb3IgdG11eCB3aWxsIGtlZXAgaXQgb3Blbi4KZXhlYyA4PiYtCgojIFdyaXRlIG91dCB0aGUgZGVidWctaG9va3MgYXJncy4KZWNobyAiZTMwSyIgfCBiYXNlNjQgLWQgPiAvdG1wL2p1anUtdW5pdC1teXNxbC0wLWRlYnVnLWhvb2tzCgojIExvY2sgdGhlIGp1anUtPHVuaXQ+LWRlYnVnLWV4aXQgbG9ja2ZpbGUuCmZsb2NrIC1uIDkgfHwgZXhpdCAxCgojIFdhaXQgZm9yIHRtdXggdG8gYmUgaW5zdGFsbGVkLgp3aGlsZSBbICEgLWYgL3Vzci9iaW4vdG11eCBdOyBkbwogICAgc2xlZXAgMQpkb25lCgppZiBbICEgLWYgfi8udG11eC5jb25mIF07IHRoZW4KICAgICAgICBpZiBbIC1mIC91c3Ivc2hhcmUvYnlvYnUvcHJvZmlsZXMvdG11eCBdOyB0aGVuCiAgICAgICAgICAgICAgICAjIFVzZSBieW9idS90bXV4IHByb2ZpbGUgZm9yIGZhbWlsaWFyIGtleWJpbmRpbmdzIGFuZCBicmFuZGluZwogICAgICAgICAgICAgICAgZWNobyAic291cmNlLWZpbGUgL3Vzci9zaGFyZS9ieW9idS9wcm9maWxlcy90bXV4IiA+IH4vLnRtdXguY29uZgogICAgICAgIGVsc2UKICAgICAgICAgICAgICAgICMgT3RoZXJ3aXNlLCB1c2UgdGhlIGxlZ2FjeSBqdWp1L3RtdXggY29uZmlndXJhdGlvbgogICAgICAgICAgICAgICAgY2F0ID4gfi8udG11eC5jb25mIDw8RU5ECiAgICAgICAgICAgICAgICAKIyBTdGF0dXMgYmFyCnNldC1vcHRpb24gLWcgc3RhdHVzLWJnIGJsYWNrCnNldC1vcHRpb24gLWcgc3RhdHVzLWZnIHdoaXRlCgpzZXQtd2luZG93LW9wdGlvbiAtZyB3aW5kb3ctc3RhdHVzLWN1cnJlbnQtYmcgcmVkCnNldC13aW5kb3ctb3B0aW9uIC1nIHdpbmRvdy1zdGF0dXMtY3VycmVudC1hdHRyIGJyaWdodAoKc2V0LW9wdGlvbiAtZyBzdGF0dXMtcmlnaHQgJycKCiMgUGFuZXMKc2V0LW9wdGlvbiAtZyBwYW5lLWJvcmRlci1mZyB3aGl0ZQpzZXQtb3B0aW9uIC1nIHBhbmUtYWN0aXZlLWJvcmRlci1mZyB3aGl0ZQoKIyBNb25pdG9yIGFjdGl2aXR5IG9uIHdpbmRvd3MKc2V0LXdpbmRvdy1vcHRpb24gLWcgbW9uaXRvci1hY3Rpdml0eSBvbgoKIyBTY3JlZW4gYmluZGluZ3MsIHNpbmNlIHBlb3BsZSBhcmUgbW9yZSBmYW1pbGlhciB3aXRoIHRoYXQuCnNldC1vcHRpb24gLWcgcHJlZml4IEMtYQpiaW5kIEMtYSBsYXN0LXdpbmRvdwpiaW5kIGEgc2VuZC1rZXkgQy1hCgpiaW5kIHwgc3BsaXQtd2luZG93IC1oCmJpbmQgLSBzcGxpdC13aW5kb3cgLXYKCiMgRml4IENUUkwtUEdVUC9QR0RPV04gZm9yIHZpbQpzZXQtd2luZG93LW9wdGlvbiAtZyB4dGVybS1rZXlzIG9uCgojIFByZXZlbnQgRVNDIGtleSBmcm9tIGFkZGluZyBkZWxheSBhbmQgYnJlYWtpbmcgVmltJ3MgRVNDID4gYXJyb3cga2V5CnNldC1vcHRpb24gLXMgZXNjYXBlLXRpbWUgMAoKRU5ECiAgICAgICAgZmkKZmkKCigKICAgICMgQ2xvc2UgdGhlIGluaGVyaXRlZCBsb2NrIEZELCBvciB0bXV4IHdpbGwga2VlcCBpdCBvcGVuLgogICAgZXhlYyA5PiYtCiAgICBleGVjIHRtdXggbmV3LXNlc3Npb24gLXMgbXlzcWwvMAopCikgOT4vdG1wL2p1anUtdW5pdC1teXNxbC0wLWRlYnVnLWhvb2tzLWV4aXQKKSA4Pi90bXAvanVqdS11bml0LW15c3FsLTAtZGVidWctaG9va3MKZXhpdCAkPwo= | base64 -d > $F; . $F'\n"), |
21 | + result: regexp.QuoteMeta(debugHooksArgs + "ubuntu@dummyenv-0.dns sudo /bin/bash -c 'F=$(mktemp); echo IyEvYmluL2Jhc2gKKAojIExvY2sgdGhlIGp1anUtPHVuaXQ+LWRlYnVnIGxvY2tmaWxlLgpmbG9jayAtbiA4IHx8IChlY2hvICJGYWlsZWQgdG8gYWNxdWlyZSAvdG1wL2p1anUtdW5pdC1teXNxbC0wLWRlYnVnLWhvb2tzOiB1bml0IGlzIGFscmVhZHkgYmVpbmcgZGVidWdnZWQiIDI+JjE7IGV4aXQgMSkKKAojIENsb3NlIHRoZSBpbmhlcml0ZWQgbG9jayBGRCwgb3IgdG11eCB3aWxsIGtlZXAgaXQgb3Blbi4KZXhlYyA4PiYtCgojIFdyaXRlIG91dCB0aGUgZGVidWctaG9va3MgYXJncy4KZWNobyAiZTMwSyIgfCBiYXNlNjQgLWQgPiAvdG1wL2p1anUtdW5pdC1teXNxbC0wLWRlYnVnLWhvb2tzCgojIExvY2sgdGhlIGp1anUtPHVuaXQ+LWRlYnVnLWV4aXQgbG9ja2ZpbGUuCmZsb2NrIC1uIDkgfHwgZXhpdCAxCgojIFdhaXQgZm9yIHRtdXggdG8gYmUgaW5zdGFsbGVkLgp3aGlsZSBbICEgLWYgL3Vzci9iaW4vdG11eCBdOyBkbwogICAgc2xlZXAgMQpkb25lCgppZiBbICEgLWYgfi8udG11eC5jb25mIF07IHRoZW4KICAgICAgICBpZiBbIC1mIC91c3Ivc2hhcmUvYnlvYnUvcHJvZmlsZXMvdG11eCBdOyB0aGVuCiAgICAgICAgICAgICAgICAjIFVzZSBieW9idS90bXV4IHByb2ZpbGUgZm9yIGZhbWlsaWFyIGtleWJpbmRpbmdzIGFuZCBicmFuZGluZwogICAgICAgICAgICAgICAgZWNobyAic291cmNlLWZpbGUgL3Vzci9zaGFyZS9ieW9idS9wcm9maWxlcy90bXV4IiA+IH4vLnRtdXguY29uZgogICAgICAgIGVsc2UKICAgICAgICAgICAgICAgICMgT3RoZXJ3aXNlLCB1c2UgdGhlIGxlZ2FjeSBqdWp1L3RtdXggY29uZmlndXJhdGlvbgogICAgICAgICAgICAgICAgY2F0ID4gfi8udG11eC5jb25mIDw8RU5ECiAgICAgICAgICAgICAgICAKIyBTdGF0dXMgYmFyCnNldC1vcHRpb24gLWcgc3RhdHVzLWJnIGJsYWNrCnNldC1vcHRpb24gLWcgc3RhdHVzLWZnIHdoaXRlCgpzZXQtd2luZG93LW9wdGlvbiAtZyB3aW5kb3ctc3RhdHVzLWN1cnJlbnQtYmcgcmVkCnNldC13aW5kb3ctb3B0aW9uIC1nIHdpbmRvdy1zdGF0dXMtY3VycmVudC1hdHRyIGJyaWdodAoKc2V0LW9wdGlvbiAtZyBzdGF0dXMtcmlnaHQgJycKCiMgUGFuZXMKc2V0LW9wdGlvbiAtZyBwYW5lLWJvcmRlci1mZyB3aGl0ZQpzZXQtb3B0aW9uIC1nIHBhbmUtYWN0aXZlLWJvcmRlci1mZyB3aGl0ZQoKIyBNb25pdG9yIGFjdGl2aXR5IG9uIHdpbmRvd3MKc2V0LXdpbmRvdy1vcHRpb24gLWcgbW9uaXRvci1hY3Rpdml0eSBvbgoKIyBTY3JlZW4gYmluZGluZ3MsIHNpbmNlIHBlb3BsZSBhcmUgbW9yZSBmYW1pbGlhciB3aXRoIHRoYXQuCnNldC1vcHRpb24gLWcgcHJlZml4IEMtYQpiaW5kIEMtYSBsYXN0LXdpbmRvdwpiaW5kIGEgc2VuZC1rZXkgQy1hCgpiaW5kIHwgc3BsaXQtd2luZG93IC1oCmJpbmQgLSBzcGxpdC13aW5kb3cgLXYKCiMgRml4IENUUkwtUEdVUC9QR0RPV04gZm9yIHZpbQpzZXQtd2luZG93LW9wdGlvbiAtZyB4dGVybS1rZXlzIG9uCgojIFByZXZlbnQgRVNDIGtleSBmcm9tIGFkZGluZyBkZWxheSBhbmQgYnJlYWtpbmcgVmltJ3MgRVNDID4gYXJyb3cga2V5CnNldC1vcHRpb24gLXMgZXNjYXBlLXRpbWUgMAoKRU5ECiAgICAgICAgZmkKZmkKCigKICAgICMgQ2xvc2UgdGhlIGluaGVyaXRlZCBsb2NrIEZELCBvciB0bXV4IHdpbGwga2VlcCBpdCBvcGVuLgogICAgZXhlYyA5PiYtCiAgICBleGVjIHRtdXggbmV3LXNlc3Npb24gLXMgbXlzcWwvMAopCikgOT4vdG1wL2p1anUtdW5pdC1teXNxbC0wLWRlYnVnLWhvb2tzLWV4aXQKKSA4Pi90bXAvanVqdS11bml0LW15c3FsLTAtZGVidWctaG9va3MKZXhpdCAkPwo= | base64 -d > $F; . $F'\n"), |
22 | }, { |
23 | args: []string{"mongodb/1"}, |
24 | - result: regexp.QuoteMeta(debugHooksArgs + "ubuntu@dummyenv-2.dns -- sudo /bin/bash -c 'F=$(mktemp); echo IyEvYmluL2Jhc2gKKAojIExvY2sgdGhlIGp1anUtPHVuaXQ+LWRlYnVnIGxvY2tmaWxlLgpmbG9jayAtbiA4IHx8IChlY2hvICJGYWlsZWQgdG8gYWNxdWlyZSAvdG1wL2p1anUtdW5pdC1tb25nb2RiLTEtZGVidWctaG9va3M6IHVuaXQgaXMgYWxyZWFkeSBiZWluZyBkZWJ1Z2dlZCIgMj4mMTsgZXhpdCAxKQooCiMgQ2xvc2UgdGhlIGluaGVyaXRlZCBsb2NrIEZELCBvciB0bXV4IHdpbGwga2VlcCBpdCBvcGVuLgpleGVjIDg+Ji0KCiMgV3JpdGUgb3V0IHRoZSBkZWJ1Zy1ob29rcyBhcmdzLgplY2hvICJlMzBLIiB8IGJhc2U2NCAtZCA+IC90bXAvanVqdS11bml0LW1vbmdvZGItMS1kZWJ1Zy1ob29rcwoKIyBMb2NrIHRoZSBqdWp1LTx1bml0Pi1kZWJ1Zy1leGl0IGxvY2tmaWxlLgpmbG9jayAtbiA5IHx8IGV4aXQgMQoKIyBXYWl0IGZvciB0bXV4IHRvIGJlIGluc3RhbGxlZC4Kd2hpbGUgWyAhIC1mIC91c3IvYmluL3RtdXggXTsgZG8KICAgIHNsZWVwIDEKZG9uZQoKaWYgWyAhIC1mIH4vLnRtdXguY29uZiBdOyB0aGVuCiAgICAgICAgaWYgWyAtZiAvdXNyL3NoYXJlL2J5b2J1L3Byb2ZpbGVzL3RtdXggXTsgdGhlbgogICAgICAgICAgICAgICAgIyBVc2UgYnlvYnUvdG11eCBwcm9maWxlIGZvciBmYW1pbGlhciBrZXliaW5kaW5ncyBhbmQgYnJhbmRpbmcKICAgICAgICAgICAgICAgIGVjaG8gInNvdXJjZS1maWxlIC91c3Ivc2hhcmUvYnlvYnUvcHJvZmlsZXMvdG11eCIgPiB+Ly50bXV4LmNvbmYKICAgICAgICBlbHNlCiAgICAgICAgICAgICAgICAjIE90aGVyd2lzZSwgdXNlIHRoZSBsZWdhY3kganVqdS90bXV4IGNvbmZpZ3VyYXRpb24KICAgICAgICAgICAgICAgIGNhdCA+IH4vLnRtdXguY29uZiA8PEVORAogICAgICAgICAgICAgICAgCiMgU3RhdHVzIGJhcgpzZXQtb3B0aW9uIC1nIHN0YXR1cy1iZyBibGFjawpzZXQtb3B0aW9uIC1nIHN0YXR1cy1mZyB3aGl0ZQoKc2V0LXdpbmRvdy1vcHRpb24gLWcgd2luZG93LXN0YXR1cy1jdXJyZW50LWJnIHJlZApzZXQtd2luZG93LW9wdGlvbiAtZyB3aW5kb3ctc3RhdHVzLWN1cnJlbnQtYXR0ciBicmlnaHQKCnNldC1vcHRpb24gLWcgc3RhdHVzLXJpZ2h0ICcnCgojIFBhbmVzCnNldC1vcHRpb24gLWcgcGFuZS1ib3JkZXItZmcgd2hpdGUKc2V0LW9wdGlvbiAtZyBwYW5lLWFjdGl2ZS1ib3JkZXItZmcgd2hpdGUKCiMgTW9uaXRvciBhY3Rpdml0eSBvbiB3aW5kb3dzCnNldC13aW5kb3ctb3B0aW9uIC1nIG1vbml0b3ItYWN0aXZpdHkgb24KCiMgU2NyZWVuIGJpbmRpbmdzLCBzaW5jZSBwZW9wbGUgYXJlIG1vcmUgZmFtaWxpYXIgd2l0aCB0aGF0LgpzZXQtb3B0aW9uIC1nIHByZWZpeCBDLWEKYmluZCBDLWEgbGFzdC13aW5kb3cKYmluZCBhIHNlbmQta2V5IEMtYQoKYmluZCB8IHNwbGl0LXdpbmRvdyAtaApiaW5kIC0gc3BsaXQtd2luZG93IC12CgojIEZpeCBDVFJMLVBHVVAvUEdET1dOIGZvciB2aW0Kc2V0LXdpbmRvdy1vcHRpb24gLWcgeHRlcm0ta2V5cyBvbgoKIyBQcmV2ZW50IEVTQyBrZXkgZnJvbSBhZGRpbmcgZGVsYXkgYW5kIGJyZWFraW5nIFZpbSdzIEVTQyA+IGFycm93IGtleQpzZXQtb3B0aW9uIC1zIGVzY2FwZS10aW1lIDAKCkVORAogICAgICAgIGZpCmZpCgooCiAgICAjIENsb3NlIHRoZSBpbmhlcml0ZWQgbG9jayBGRCwgb3IgdG11eCB3aWxsIGtlZXAgaXQgb3Blbi4KICAgIGV4ZWMgOT4mLQogICAgZXhlYyB0bXV4IG5ldy1zZXNzaW9uIC1zIG1vbmdvZGIvMQopCikgOT4vdG1wL2p1anUtdW5pdC1tb25nb2RiLTEtZGVidWctaG9va3MtZXhpdAopIDg+L3RtcC9qdWp1LXVuaXQtbW9uZ29kYi0xLWRlYnVnLWhvb2tzCmV4aXQgJD8K | base64 -d > $F; . $F'\n"), |
25 | + result: regexp.QuoteMeta(debugHooksArgs + "ubuntu@dummyenv-2.dns sudo /bin/bash -c 'F=$(mktemp); echo IyEvYmluL2Jhc2gKKAojIExvY2sgdGhlIGp1anUtPHVuaXQ+LWRlYnVnIGxvY2tmaWxlLgpmbG9jayAtbiA4IHx8IChlY2hvICJGYWlsZWQgdG8gYWNxdWlyZSAvdG1wL2p1anUtdW5pdC1tb25nb2RiLTEtZGVidWctaG9va3M6IHVuaXQgaXMgYWxyZWFkeSBiZWluZyBkZWJ1Z2dlZCIgMj4mMTsgZXhpdCAxKQooCiMgQ2xvc2UgdGhlIGluaGVyaXRlZCBsb2NrIEZELCBvciB0bXV4IHdpbGwga2VlcCBpdCBvcGVuLgpleGVjIDg+Ji0KCiMgV3JpdGUgb3V0IHRoZSBkZWJ1Zy1ob29rcyBhcmdzLgplY2hvICJlMzBLIiB8IGJhc2U2NCAtZCA+IC90bXAvanVqdS11bml0LW1vbmdvZGItMS1kZWJ1Zy1ob29rcwoKIyBMb2NrIHRoZSBqdWp1LTx1bml0Pi1kZWJ1Zy1leGl0IGxvY2tmaWxlLgpmbG9jayAtbiA5IHx8IGV4aXQgMQoKIyBXYWl0IGZvciB0bXV4IHRvIGJlIGluc3RhbGxlZC4Kd2hpbGUgWyAhIC1mIC91c3IvYmluL3RtdXggXTsgZG8KICAgIHNsZWVwIDEKZG9uZQoKaWYgWyAhIC1mIH4vLnRtdXguY29uZiBdOyB0aGVuCiAgICAgICAgaWYgWyAtZiAvdXNyL3NoYXJlL2J5b2J1L3Byb2ZpbGVzL3RtdXggXTsgdGhlbgogICAgICAgICAgICAgICAgIyBVc2UgYnlvYnUvdG11eCBwcm9maWxlIGZvciBmYW1pbGlhciBrZXliaW5kaW5ncyBhbmQgYnJhbmRpbmcKICAgICAgICAgICAgICAgIGVjaG8gInNvdXJjZS1maWxlIC91c3Ivc2hhcmUvYnlvYnUvcHJvZmlsZXMvdG11eCIgPiB+Ly50bXV4LmNvbmYKICAgICAgICBlbHNlCiAgICAgICAgICAgICAgICAjIE90aGVyd2lzZSwgdXNlIHRoZSBsZWdhY3kganVqdS90bXV4IGNvbmZpZ3VyYXRpb24KICAgICAgICAgICAgICAgIGNhdCA+IH4vLnRtdXguY29uZiA8PEVORAogICAgICAgICAgICAgICAgCiMgU3RhdHVzIGJhcgpzZXQtb3B0aW9uIC1nIHN0YXR1cy1iZyBibGFjawpzZXQtb3B0aW9uIC1nIHN0YXR1cy1mZyB3aGl0ZQoKc2V0LXdpbmRvdy1vcHRpb24gLWcgd2luZG93LXN0YXR1cy1jdXJyZW50LWJnIHJlZApzZXQtd2luZG93LW9wdGlvbiAtZyB3aW5kb3ctc3RhdHVzLWN1cnJlbnQtYXR0ciBicmlnaHQKCnNldC1vcHRpb24gLWcgc3RhdHVzLXJpZ2h0ICcnCgojIFBhbmVzCnNldC1vcHRpb24gLWcgcGFuZS1ib3JkZXItZmcgd2hpdGUKc2V0LW9wdGlvbiAtZyBwYW5lLWFjdGl2ZS1ib3JkZXItZmcgd2hpdGUKCiMgTW9uaXRvciBhY3Rpdml0eSBvbiB3aW5kb3dzCnNldC13aW5kb3ctb3B0aW9uIC1nIG1vbml0b3ItYWN0aXZpdHkgb24KCiMgU2NyZWVuIGJpbmRpbmdzLCBzaW5jZSBwZW9wbGUgYXJlIG1vcmUgZmFtaWxpYXIgd2l0aCB0aGF0LgpzZXQtb3B0aW9uIC1nIHByZWZpeCBDLWEKYmluZCBDLWEgbGFzdC13aW5kb3cKYmluZCBhIHNlbmQta2V5IEMtYQoKYmluZCB8IHNwbGl0LXdpbmRvdyAtaApiaW5kIC0gc3BsaXQtd2luZG93IC12CgojIEZpeCBDVFJMLVBHVVAvUEdET1dOIGZvciB2aW0Kc2V0LXdpbmRvdy1vcHRpb24gLWcgeHRlcm0ta2V5cyBvbgoKIyBQcmV2ZW50IEVTQyBrZXkgZnJvbSBhZGRpbmcgZGVsYXkgYW5kIGJyZWFraW5nIFZpbSdzIEVTQyA+IGFycm93IGtleQpzZXQtb3B0aW9uIC1zIGVzY2FwZS10aW1lIDAKCkVORAogICAgICAgIGZpCmZpCgooCiAgICAjIENsb3NlIHRoZSBpbmhlcml0ZWQgbG9jayBGRCwgb3IgdG11eCB3aWxsIGtlZXAgaXQgb3Blbi4KICAgIGV4ZWMgOT4mLQogICAgZXhlYyB0bXV4IG5ldy1zZXNzaW9uIC1zIG1vbmdvZGIvMQopCikgOT4vdG1wL2p1anUtdW5pdC1tb25nb2RiLTEtZGVidWctaG9va3MtZXhpdAopIDg+L3RtcC9qdWp1LXVuaXQtbW9uZ29kYi0xLWRlYnVnLWhvb2tzCmV4aXQgJD8K | base64 -d > $F; . $F'\n"), |
26 | }, { |
27 | info: `"*" is a valid hook name: it means hook everything`, |
28 | args: []string{"mysql/0", "*"}, |
29 | |
30 | === modified file 'cmd/juju/scp.go' |
31 | --- cmd/juju/scp.go 2014-01-14 02:53:16 +0000 |
32 | +++ cmd/juju/scp.go 2014-02-22 15:58:57 +0000 |
33 | @@ -17,17 +17,23 @@ |
34 | } |
35 | |
36 | const scpDoc = ` |
37 | -Lauch an scp command to copy files. <to> and <from> are either local |
38 | -file paths or remote locations of the form <target>:<path>, where |
39 | -<target> can be either a machine id as listed by "juju status" in the |
40 | +Launch an scp command to copy files. Each argument <file1> ... <file2> |
41 | +is either local file path or remote locations of the form <target>:<path>, |
42 | +where <target> can be either a machine id as listed by "juju status" in the |
43 | "machines" section or a unit name as listed in the "services" section. |
44 | +Any extra arguments to scp can be passed after --, in any place. |
45 | |
46 | -Examples |
47 | +Examples: |
48 | |
49 | Copy a single file from machine 2 to the local machine: |
50 | |
51 | juju scp 2:/var/log/syslog . |
52 | |
53 | +Copy 2 files from two units to the local backup/ directory, passig -v |
54 | +to scp as an extra argument: |
55 | + |
56 | + juju scp ubuntu/0:/path/file1 -- -v ubuntu/1:/path/file2 backup/ |
57 | + |
58 | Recursively copy the directory /var/log/mongodb/ on the first mongodb |
59 | server to the local directory remote-logs: |
60 | |
61 | @@ -41,7 +47,7 @@ |
62 | func (c *SCPCommand) Info() *cmd.Info { |
63 | return &cmd.Info{ |
64 | Name: "scp", |
65 | - Args: "[-- scp-option...] <from> <to>", |
66 | + Args: "[-- scp-option...] <file1> ... <file2> [-- scp-option...]", |
67 | Purpose: "launch a scp command to copy files to/from remote machine(s)", |
68 | Doc: scpDoc, |
69 | } |
70 | @@ -67,17 +73,33 @@ |
71 | } |
72 | defer c.apiClient.Close() |
73 | |
74 | - // translate arguments in the form 0:/somepath or service/0:/somepath into |
75 | - // ubuntu@machine:/somepath so they can be presented to scp. |
76 | - for i := range c.Args { |
77 | - // BUG(dfc) This will not work for IPv6 addresses like 2001:db8::1:2:/somepath. |
78 | - if v := strings.SplitN(c.Args[i], ":", 2); len(v) > 1 { |
79 | + // Parse all arguments, translating those in the form 0:/somepath |
80 | + // or service/0:/somepath into ubuntu@machine:/somepath so they |
81 | + // can be given to scp as targets (source(s) and destination(s)), |
82 | + // and passing any others that look like extra arguments (starting |
83 | + // with "-") verbatim to scp. |
84 | + var targets, extraArgs []string |
85 | + for _, arg := range c.Args { |
86 | + if v := strings.SplitN(arg, ":", 2); len(v) > 1 { |
87 | host, err := c.hostFromTarget(v[0]) |
88 | if err != nil { |
89 | return err |
90 | } |
91 | - c.Args[i] = "ubuntu@" + host + ":" + v[1] |
92 | + targets = append(targets, "ubuntu@"+host+":"+v[1]) |
93 | + continue |
94 | + } |
95 | + if strings.HasPrefix(arg, "-") { |
96 | + // Allow -- to be specified last, in which case |
97 | + // we need to strip it. |
98 | + arg = strings.TrimSpace(strings.TrimPrefix(arg, "--")) |
99 | + if arg != "" { |
100 | + // Extra argument(s). |
101 | + extraArgs = append(extraArgs, arg) |
102 | + } |
103 | + } else { |
104 | + // Local path |
105 | + targets = append(targets, arg) |
106 | } |
107 | } |
108 | - return ssh.Copy(c.Args[0], c.Args[1], nil) |
109 | + return ssh.Copy(targets, extraArgs, nil) |
110 | } |
111 | |
112 | === modified file 'cmd/juju/scp_test.go' |
113 | --- cmd/juju/scp_test.go 2014-01-13 06:25:28 +0000 |
114 | +++ cmd/juju/scp_test.go 2014-02-22 15:58:57 +0000 |
115 | @@ -13,7 +13,6 @@ |
116 | gc "launchpad.net/gocheck" |
117 | |
118 | "launchpad.net/juju-core/charm" |
119 | - "launchpad.net/juju-core/cmd" |
120 | coretesting "launchpad.net/juju-core/testing" |
121 | ) |
122 | |
123 | @@ -24,28 +23,54 @@ |
124 | } |
125 | |
126 | var scpTests = []struct { |
127 | + about string |
128 | args []string |
129 | result string |
130 | }{ |
131 | { |
132 | + "scp from machine 0 to current dir", |
133 | []string{"0:foo", "."}, |
134 | commonArgs + "ubuntu@dummyenv-0.dns:foo .\n", |
135 | }, |
136 | { |
137 | + "scp from machine 0 to current dir with extra args before", |
138 | + []string{"-- -rv", "0:foo", "."}, |
139 | + commonArgs + "-rv ubuntu@dummyenv-0.dns:foo .\n", |
140 | + }, |
141 | + { |
142 | + "scp from machine 0 to current dir with extra args after", |
143 | + []string{"0:foo", ".", "-- -rv"}, |
144 | + commonArgs + "-rv ubuntu@dummyenv-0.dns:foo .\n", |
145 | + }, |
146 | + { |
147 | + "scp from current dir to machine 0", |
148 | []string{"foo", "0:"}, |
149 | commonArgs + "foo ubuntu@dummyenv-0.dns:\n", |
150 | }, |
151 | { |
152 | + "scp from current dir to machine 0 with extra args in the middle", |
153 | + []string{"foo", "-- -r -v", "0:"}, |
154 | + commonArgs + "-r -v foo ubuntu@dummyenv-0.dns:\n", |
155 | + }, |
156 | + { |
157 | + "scp from machine 0 to unit mysql/0", |
158 | []string{"0:foo", "mysql/0:/foo"}, |
159 | commonArgs + "ubuntu@dummyenv-0.dns:foo ubuntu@dummyenv-0.dns:/foo\n", |
160 | }, |
161 | { |
162 | - []string{"a", "b", "mysql/0"}, |
163 | - commonArgs + "a b\n", |
164 | - }, |
165 | - { |
166 | - []string{"mongodb/1:foo", "mongodb/0:"}, |
167 | - commonArgs + "ubuntu@dummyenv-2.dns:foo ubuntu@dummyenv-1.dns:\n", |
168 | + "scp from machine 0 to unit mysql/0 and extra args", |
169 | + []string{"-- -q", "0:foo", "mysql/0:/foo"}, |
170 | + commonArgs + "-q ubuntu@dummyenv-0.dns:foo ubuntu@dummyenv-0.dns:/foo\n", |
171 | + }, |
172 | + { |
173 | + "scp two local files to unit mysql/0", |
174 | + []string{"file1", "file2", "mysql/0:/foo/"}, |
175 | + commonArgs + "file1 file2 ubuntu@dummyenv-0.dns:/foo/\n", |
176 | + }, |
177 | + { |
178 | + "scp from unit mongodb/1 to unit mongodb/0 and multiple extra args", |
179 | + []string{"-- -r", "mongodb/1:foo", "-- -v", "mongodb/0:", "-- -q -l5"}, |
180 | + commonArgs + "-r -v -q -l5 ubuntu@dummyenv-2.dns:foo ubuntu@dummyenv-1.dns:\n", |
181 | }, |
182 | } |
183 | |
184 | @@ -66,11 +91,15 @@ |
185 | s.addUnit(srv, m[1], c) |
186 | s.addUnit(srv, m[2], c) |
187 | |
188 | - for _, t := range scpTests { |
189 | - c.Logf("testing juju scp %s", t.args) |
190 | + for i, t := range scpTests { |
191 | + c.Logf("test %d: %s -> %s\n", i, t.about, t.args) |
192 | ctx := coretesting.Context(c) |
193 | - code := cmd.Main(&SCPCommand{}, ctx, t.args) |
194 | - c.Check(code, gc.Equals, 0) |
195 | + scpcmd := &SCPCommand{} |
196 | + |
197 | + err := scpcmd.Init(t.args) |
198 | + c.Check(err, gc.IsNil) |
199 | + err = scpcmd.Run(ctx) |
200 | + c.Check(err, gc.IsNil) |
201 | // we suppress stdout from scp |
202 | c.Check(ctx.Stderr.(*bytes.Buffer).String(), gc.Equals, "") |
203 | c.Check(ctx.Stdout.(*bytes.Buffer).String(), gc.Equals, "") |
204 | |
205 | === modified file 'cmd/juju/ssh.go' |
206 | --- cmd/juju/ssh.go 2014-01-13 06:25:28 +0000 |
207 | +++ cmd/juju/ssh.go 2014-02-22 15:58:57 +0000 |
208 | @@ -39,15 +39,23 @@ |
209 | "machines" section or a unit name as listed in the "services" section. |
210 | Any extra parameters are passsed as extra parameters to the ssh command. |
211 | |
212 | -Examples |
213 | +Examples: |
214 | |
215 | Connect to machine 0: |
216 | |
217 | juju ssh 0 |
218 | |
219 | +Connect to machine 1 and run 'uname -a': |
220 | + |
221 | + juju ssh 1 uname -a |
222 | + |
223 | Connect to the first mysql unit: |
224 | |
225 | juju ssh mysql/0 |
226 | + |
227 | +Connect to the first mysql unit and run 'ls -la /var/log/juju': |
228 | + |
229 | + juju ssh mysql/0 ls -la /var/log/juju |
230 | ` |
231 | |
232 | func (c *SSHCommand) Info() *cmd.Info { |
233 | @@ -82,15 +90,9 @@ |
234 | if err != nil { |
235 | return err |
236 | } |
237 | - args := c.Args |
238 | - if len(args) > 0 && args[0] == "--" { |
239 | - // utils/ssh adds "--"; we will continue to accept |
240 | - // it from the CLI for backwards compatibility. |
241 | - args = args[1:] |
242 | - } |
243 | var options ssh.Options |
244 | options.EnablePTY() |
245 | - cmd := ssh.Command("ubuntu@"+host, args, &options) |
246 | + cmd := ssh.Command("ubuntu@"+host, c.Args, &options) |
247 | cmd.Stdin = ctx.Stdin |
248 | cmd.Stdout = ctx.Stdout |
249 | cmd.Stderr = ctx.Stderr |
250 | |
251 | === modified file 'cmd/juju/ssh_test.go' |
252 | --- cmd/juju/ssh_test.go 2014-02-18 17:08:55 +0000 |
253 | +++ cmd/juju/ssh_test.go 2014-02-22 15:58:57 +0000 |
254 | @@ -54,39 +54,33 @@ |
255 | |
256 | const ( |
257 | commonArgs = `-o StrictHostKeyChecking no -o PasswordAuthentication no ` |
258 | - sshArgs = commonArgs + `-t ` |
259 | + sshArgs = commonArgs + `-t -t ` |
260 | ) |
261 | |
262 | var sshTests = []struct { |
263 | + about string |
264 | args []string |
265 | result string |
266 | }{ |
267 | { |
268 | + "connect to machine 0", |
269 | []string{"ssh", "0"}, |
270 | sshArgs + "ubuntu@dummyenv-0.dns\n", |
271 | }, |
272 | - // juju ssh 0 'uname -a' |
273 | - { |
274 | - []string{"ssh", "0", "uname -a"}, |
275 | - sshArgs + "ubuntu@dummyenv-0.dns -- uname -a\n", |
276 | - }, |
277 | - // juju ssh 0 -- uname -a |
278 | - { |
279 | - []string{"ssh", "0", "--", "uname", "-a"}, |
280 | - sshArgs + "ubuntu@dummyenv-0.dns -- uname -a\n", |
281 | - }, |
282 | - // juju ssh 0 uname -a |
283 | - { |
284 | + { |
285 | + "connect to machine 0 and pass extra arguments", |
286 | []string{"ssh", "0", "uname", "-a"}, |
287 | - sshArgs + "ubuntu@dummyenv-0.dns -- uname -a\n", |
288 | + sshArgs + "ubuntu@dummyenv-0.dns uname -a\n", |
289 | }, |
290 | { |
291 | + "connect to unit mysql/0", |
292 | []string{"ssh", "mysql/0"}, |
293 | sshArgs + "ubuntu@dummyenv-0.dns\n", |
294 | }, |
295 | { |
296 | - []string{"ssh", "mongodb/1"}, |
297 | - sshArgs + "ubuntu@dummyenv-2.dns\n", |
298 | + "connect to unit mongodb/1 and pass extra arguments", |
299 | + []string{"ssh", "mongodb/1", "ls", "/"}, |
300 | + sshArgs + "ubuntu@dummyenv-2.dns ls /\n", |
301 | }, |
302 | } |
303 | |
304 | @@ -107,8 +101,8 @@ |
305 | s.addUnit(srv, m[1], c) |
306 | s.addUnit(srv, m[2], c) |
307 | |
308 | - for _, t := range sshTests { |
309 | - c.Logf("testing juju ssh %s", t.args) |
310 | + for i, t := range sshTests { |
311 | + c.Logf("test %d: %s -> %s\n", i, t.about, t.args) |
312 | ctx := coretesting.Context(c) |
313 | jujucmd := cmd.NewSuperCommand(cmd.SuperCommandParams{}) |
314 | jujucmd.Register(&SSHCommand{}) |
315 | |
316 | === modified file 'utils/ssh/run_test.go' |
317 | --- utils/ssh/run_test.go 2014-02-18 17:08:55 +0000 |
318 | +++ utils/ssh/run_test.go 2014-02-22 15:58:57 +0000 |
319 | @@ -48,7 +48,7 @@ |
320 | c.Assert(response.Code, gc.Equals, 0) |
321 | c.Assert(string(response.Stdout), gc.Equals, "sudo apt-get update\nsudo apt-get upgrade\n") |
322 | c.Assert(string(response.Stderr), gc.Equals, |
323 | - "-o StrictHostKeyChecking no -o PasswordAuthentication no hostname -- /bin/bash -s\n") |
324 | + "-o StrictHostKeyChecking no -o PasswordAuthentication no hostname /bin/bash -s\n") |
325 | } |
326 | |
327 | func (s *ExecuteSSHCommandSuite) TestIdentityFile(c *gc.C) { |
328 | |
329 | === modified file 'utils/ssh/ssh.go' |
330 | --- utils/ssh/ssh.go 2014-02-07 11:06:43 +0000 |
331 | +++ utils/ssh/ssh.go 2014-02-22 15:58:57 +0000 |
332 | @@ -5,7 +5,6 @@ |
333 | // key management, and so on. All SSH-based command executions in |
334 | // Juju should use the Command/ScpCommand functions in this package. |
335 | // |
336 | -// TODO(axw) fallback to go.crypto/ssh if no native client is available. |
337 | package ssh |
338 | |
339 | import ( |
340 | @@ -70,10 +69,11 @@ |
341 | // Host is specified in the format [user@]host. |
342 | Command(host string, command []string, options *Options) *Cmd |
343 | |
344 | - // Copy copies a file between the local host and |
345 | - // target host. Paths are specified in the scp format, |
346 | - // [[user@]host:]path. |
347 | - Copy(source, dest string, options *Options) error |
348 | + // Copy copies file(s) between the local host a remote host(s). |
349 | + // Paths are specified in the scp format, [[user@]host:]path. If |
350 | + // any extra arguments are specified in extraArgs, they are passed |
351 | + // to scp verbatim. |
352 | + Copy(targets, extraArgs []string, options *Options) error |
353 | } |
354 | |
355 | // Cmd represents a command to be (or being) executed |
356 | @@ -203,6 +203,10 @@ |
357 | // an embedded client based on go.crypto/ssh. |
358 | var DefaultClient Client |
359 | |
360 | +// chosenClient holds the type of SSH client created for |
361 | +// DefaultClient, so that we can log it in Command or Copy. |
362 | +var chosenClient string |
363 | + |
364 | func init() { |
365 | initDefaultClient() |
366 | } |
367 | @@ -210,17 +214,21 @@ |
368 | func initDefaultClient() { |
369 | if client, err := NewOpenSSHClient(); err == nil { |
370 | DefaultClient = client |
371 | + chosenClient = "OpenSSH" |
372 | } else if client, err := NewGoCryptoClient(); err == nil { |
373 | DefaultClient = client |
374 | + chosenClient = "go.crypto (embedded)" |
375 | } |
376 | } |
377 | |
378 | // Command is a short-cut for DefaultClient.Command. |
379 | func Command(host string, command []string, options *Options) *Cmd { |
380 | + logger.Debugf("using %s ssh client", chosenClient) |
381 | return DefaultClient.Command(host, command, options) |
382 | } |
383 | |
384 | // Copy is a short-cut for DefaultClient.Copy. |
385 | -func Copy(source, dest string, options *Options) error { |
386 | - return DefaultClient.Copy(source, dest, options) |
387 | +func Copy(targets, extraArgs []string, options *Options) error { |
388 | + logger.Debugf("using %s ssh client", chosenClient) |
389 | + return DefaultClient.Copy(targets, extraArgs, options) |
390 | } |
391 | |
392 | === modified file 'utils/ssh/ssh_gocrypto.go' |
393 | --- utils/ssh/ssh_gocrypto.go 2014-02-14 04:58:10 +0000 |
394 | +++ utils/ssh/ssh_gocrypto.go 2014-02-22 15:58:57 +0000 |
395 | @@ -50,6 +50,7 @@ |
396 | port = options.port |
397 | } |
398 | } |
399 | + logger.Debugf(`running (equivalent of): ssh "%s@%s" -p %d '%s'`, user, host, port, shellCommand) |
400 | return &Cmd{impl: &goCryptoCommand{ |
401 | signers: signers, |
402 | user: user, |
403 | @@ -61,7 +62,7 @@ |
404 | // Copy implements Client.Copy. |
405 | // |
406 | // Copy is currently unimplemented, and will always return an error. |
407 | -func (c *GoCryptoClient) Copy(source, dest string, options *Options) error { |
408 | +func (c *GoCryptoClient) Copy(targets, extraArgs []string, options *Options) error { |
409 | return fmt.Errorf("Copy is not implemented") |
410 | } |
411 | |
412 | |
413 | === modified file 'utils/ssh/ssh_gocrypto_test.go' |
414 | --- utils/ssh/ssh_gocrypto_test.go 2014-02-14 04:58:10 +0000 |
415 | +++ utils/ssh/ssh_gocrypto_test.go 2014-02-22 15:58:57 +0000 |
416 | @@ -145,6 +145,6 @@ |
417 | func (s *SSHGoCryptoCommandSuite) TestCopy(c *gc.C) { |
418 | client, err := ssh.NewGoCryptoClient() |
419 | c.Assert(err, gc.IsNil) |
420 | - err = client.Copy("0.1.2.3:b", c.MkDir(), nil) |
421 | + err = client.Copy([]string{"0.1.2.3:b", c.MkDir()}, nil, nil) |
422 | c.Assert(err, gc.ErrorMatches, "Copy is not implemented") |
423 | } |
424 | |
425 | === modified file 'utils/ssh/ssh_openssh.go' |
426 | --- utils/ssh/ssh_openssh.go 2014-02-05 08:48:40 +0000 |
427 | +++ utils/ssh/ssh_openssh.go 2014-02-22 15:58:57 +0000 |
428 | @@ -14,7 +14,7 @@ |
429 | "launchpad.net/juju-core/utils" |
430 | ) |
431 | |
432 | -var opensshCommonOptions = []string{"-o", "StrictHostKeyChecking no"} |
433 | +var opensshCommonOptions = map[string][]string{"-o": []string{"StrictHostKeyChecking no"}} |
434 | |
435 | // default identities will not be attempted if |
436 | // -i is specified and they are not explcitly |
437 | @@ -63,16 +63,19 @@ |
438 | return &c, nil |
439 | } |
440 | |
441 | -func opensshOptions(options *Options, commandKind opensshCommandKind) []string { |
442 | - args := append([]string{}, opensshCommonOptions...) |
443 | +func opensshOptions(options *Options, commandKind opensshCommandKind) map[string][]string { |
444 | + args := make(map[string][]string) |
445 | + for k, v := range opensshCommonOptions { |
446 | + args[k] = v |
447 | + } |
448 | if options == nil { |
449 | options = &Options{} |
450 | } |
451 | if !options.passwordAuthAllowed { |
452 | - args = append(args, "-o", "PasswordAuthentication no") |
453 | + args["-o"] = append(args["-o"], "PasswordAuthentication no") |
454 | } |
455 | if options.allocatePTY { |
456 | - args = append(args, "-t") |
457 | + args["-t"] = []string{} |
458 | } |
459 | identities := append([]string{}, options.identities...) |
460 | if pk := PrivateKeyFiles(); len(pk) > 0 { |
461 | @@ -94,45 +97,76 @@ |
462 | } |
463 | } |
464 | for _, identity := range identities { |
465 | - args = append(args, "-i", identity) |
466 | + args["-i"] = append(args["-i"], identity) |
467 | } |
468 | if options.port != 0 { |
469 | + port := fmt.Sprint(options.port) |
470 | if commandKind == scpKind { |
471 | // scp uses -P instead of -p (-p means preserve). |
472 | - args = append(args, "-P") |
473 | + args["-P"] = []string{port} |
474 | } else { |
475 | - args = append(args, "-p") |
476 | + args["-p"] = []string{port} |
477 | } |
478 | - args = append(args, fmt.Sprint(options.port)) |
479 | } |
480 | return args |
481 | } |
482 | |
483 | +func expandArgs(args map[string][]string, quote bool) []string { |
484 | + var list []string |
485 | + for opt, vals := range args { |
486 | + if len(vals) == 0 { |
487 | + list = append(list, opt) |
488 | + if opt == "-t" { |
489 | + // In order to force a PTY to be allocated, we need to |
490 | + // pass -t twice. |
491 | + list = append(list, opt) |
492 | + } |
493 | + } |
494 | + for _, val := range vals { |
495 | + list = append(list, opt) |
496 | + if quote { |
497 | + val = fmt.Sprintf("%q", val) |
498 | + } |
499 | + list = append(list, val) |
500 | + } |
501 | + } |
502 | + return list |
503 | +} |
504 | + |
505 | // Command implements Client.Command. |
506 | func (c *OpenSSHClient) Command(host string, command []string, options *Options) *Cmd { |
507 | - args := opensshOptions(options, sshKind) |
508 | + opts := opensshOptions(options, sshKind) |
509 | + args := expandArgs(opts, false) |
510 | args = append(args, host) |
511 | if len(command) > 0 { |
512 | - args = append(args, "--") |
513 | args = append(args, command...) |
514 | } |
515 | bin, args := sshpassWrap("ssh", args) |
516 | + optsList := strings.Join(expandArgs(opts, true), " ") |
517 | + fullCommand := strings.Join(command, " ") |
518 | + logger.Debugf("running: %s %s %q '%s'", bin, optsList, host, fullCommand) |
519 | return &Cmd{impl: &opensshCmd{exec.Command(bin, args...)}} |
520 | } |
521 | |
522 | // Copy implements Client.Copy. |
523 | -func (c *OpenSSHClient) Copy(source, dest string, userOptions *Options) error { |
524 | +func (c *OpenSSHClient) Copy(targets, extraArgs []string, userOptions *Options) error { |
525 | var options Options |
526 | if userOptions != nil { |
527 | options = *userOptions |
528 | options.allocatePTY = false // doesn't make sense for scp |
529 | } |
530 | - args := opensshOptions(&options, scpKind) |
531 | - args = append(args, source, dest) |
532 | + opts := opensshOptions(&options, scpKind) |
533 | + args := expandArgs(opts, false) |
534 | + args = append(args, extraArgs...) |
535 | + args = append(args, targets...) |
536 | bin, args := sshpassWrap("scp", args) |
537 | cmd := exec.Command(bin, args...) |
538 | var stderr bytes.Buffer |
539 | cmd.Stderr = &stderr |
540 | + allOpts := append(expandArgs(opts, true), extraArgs...) |
541 | + optsList := strings.Join(allOpts, " ") |
542 | + targetList := `"` + strings.Join(targets, `" "`) + `"` |
543 | + logger.Debugf("running: %s %s %s", bin, optsList, targetList) |
544 | if err := cmd.Run(); err != nil { |
545 | stderr := strings.TrimSpace(stderr.String()) |
546 | if len(stderr) > 0 { |
547 | |
548 | === modified file 'utils/ssh/ssh_test.go' |
549 | --- utils/ssh/ssh_test.go 2014-02-18 17:08:55 +0000 |
550 | +++ utils/ssh/ssh_test.go 2014-02-22 15:58:57 +0000 |
551 | @@ -70,24 +70,24 @@ |
552 | fakesshpass := filepath.Join(s.testbin, "sshpass") |
553 | err := ioutil.WriteFile(fakesshpass, []byte(echoCommandScript), 0755) |
554 | s.assertCommandArgs(c, s.command("echo", "123"), |
555 | - s.fakessh+" -o StrictHostKeyChecking no -o PasswordAuthentication no localhost -- echo 123", |
556 | + s.fakessh+" -o StrictHostKeyChecking no -o PasswordAuthentication no localhost echo 123", |
557 | ) |
558 | // Now set $SSHPASS. |
559 | s.PatchEnvironment("SSHPASS", "anyoldthing") |
560 | s.assertCommandArgs(c, s.command("echo", "123"), |
561 | - fakesshpass+" -e ssh -o StrictHostKeyChecking no -o PasswordAuthentication no localhost -- echo 123", |
562 | + fakesshpass+" -e ssh -o StrictHostKeyChecking no -o PasswordAuthentication no localhost echo 123", |
563 | ) |
564 | // Finally, remove sshpass from $PATH. |
565 | err = os.Remove(fakesshpass) |
566 | c.Assert(err, gc.IsNil) |
567 | s.assertCommandArgs(c, s.command("echo", "123"), |
568 | - s.fakessh+" -o StrictHostKeyChecking no -o PasswordAuthentication no localhost -- echo 123", |
569 | + s.fakessh+" -o StrictHostKeyChecking no -o PasswordAuthentication no localhost echo 123", |
570 | ) |
571 | } |
572 | |
573 | func (s *SSHCommandSuite) TestCommand(c *gc.C) { |
574 | s.assertCommandArgs(c, s.command("echo", "123"), |
575 | - s.fakessh+" -o StrictHostKeyChecking no -o PasswordAuthentication no localhost -- echo 123", |
576 | + s.fakessh+" -o StrictHostKeyChecking no -o PasswordAuthentication no localhost echo 123", |
577 | ) |
578 | } |
579 | |
580 | @@ -95,7 +95,7 @@ |
581 | var opts ssh.Options |
582 | opts.EnablePTY() |
583 | s.assertCommandArgs(c, s.commandOptions([]string{"echo", "123"}, &opts), |
584 | - s.fakessh+" -o StrictHostKeyChecking no -o PasswordAuthentication no -t localhost -- echo 123", |
585 | + s.fakessh+" -o StrictHostKeyChecking no -o PasswordAuthentication no -t -t localhost echo 123", |
586 | ) |
587 | } |
588 | |
589 | @@ -103,7 +103,7 @@ |
590 | var opts ssh.Options |
591 | opts.AllowPasswordAuthentication() |
592 | s.assertCommandArgs(c, s.commandOptions([]string{"echo", "123"}, &opts), |
593 | - s.fakessh+" -o StrictHostKeyChecking no localhost -- echo 123", |
594 | + s.fakessh+" -o StrictHostKeyChecking no localhost echo 123", |
595 | ) |
596 | } |
597 | |
598 | @@ -111,7 +111,7 @@ |
599 | var opts ssh.Options |
600 | opts.SetIdentities("x", "y") |
601 | s.assertCommandArgs(c, s.commandOptions([]string{"echo", "123"}, &opts), |
602 | - s.fakessh+" -o StrictHostKeyChecking no -o PasswordAuthentication no -i x -i y localhost -- echo 123", |
603 | + s.fakessh+" -o StrictHostKeyChecking no -o PasswordAuthentication no -i x -i y localhost echo 123", |
604 | ) |
605 | } |
606 | |
607 | @@ -119,7 +119,7 @@ |
608 | var opts ssh.Options |
609 | opts.SetPort(2022) |
610 | s.assertCommandArgs(c, s.commandOptions([]string{"echo", "123"}, &opts), |
611 | - s.fakessh+" -o StrictHostKeyChecking no -o PasswordAuthentication no -p 2022 localhost -- echo 123", |
612 | + s.fakessh+" -o StrictHostKeyChecking no -o PasswordAuthentication no -p 2022 localhost echo 123", |
613 | ) |
614 | } |
615 | |
616 | @@ -129,12 +129,19 @@ |
617 | opts.AllowPasswordAuthentication() |
618 | opts.SetIdentities("x", "y") |
619 | opts.SetPort(2022) |
620 | - err := s.client.Copy("/tmp/blah", "foo@bar.com:baz", &opts) |
621 | + err := s.client.Copy([]string{"/tmp/blah", "foo@bar.com:baz"}, nil, &opts) |
622 | c.Assert(err, gc.IsNil) |
623 | out, err := ioutil.ReadFile(s.fakescp + ".args") |
624 | c.Assert(err, gc.IsNil) |
625 | // EnablePTY has no effect for Copy |
626 | c.Assert(string(out), gc.Equals, s.fakescp+" -o StrictHostKeyChecking no -i x -i y -P 2022 /tmp/blah foo@bar.com:baz\n") |
627 | + |
628 | + // Try passing extra args |
629 | + err = s.client.Copy([]string{"/tmp/blah", "foo@bar.com:baz"}, []string{"-r", "-v"}, &opts) |
630 | + c.Assert(err, gc.IsNil) |
631 | + out, err = ioutil.ReadFile(s.fakescp + ".args") |
632 | + c.Assert(err, gc.IsNil) |
633 | + c.Assert(string(out), gc.Equals, s.fakescp+" -o StrictHostKeyChecking no -i x -i y -P 2022 -r -v /tmp/blah foo@bar.com:baz\n") |
634 | } |
635 | |
636 | func (s *SSHCommandSuite) TestCommandClientKeys(c *gc.C) { |
637 | @@ -146,7 +153,7 @@ |
638 | var opts ssh.Options |
639 | opts.SetIdentities("x", "y") |
640 | s.assertCommandArgs(c, s.commandOptions([]string{"echo", "123"}, &opts), |
641 | - s.fakessh+" -o StrictHostKeyChecking no -o PasswordAuthentication no -i x -i y -i "+ck+" localhost -- echo 123", |
642 | + s.fakessh+" -o StrictHostKeyChecking no -o PasswordAuthentication no -i x -i y -i "+ck+" localhost echo 123", |
643 | ) |
644 | } |
645 | |
646 | @@ -167,7 +174,7 @@ |
647 | s.PatchValue(ssh.DefaultIdentities, []string{def1, def2}) |
648 | // If no identities are specified, then the defaults aren't added. |
649 | s.assertCommandArgs(c, s.commandOptions([]string{"echo", "123"}, &opts), |
650 | - s.fakessh+" -o StrictHostKeyChecking no -o PasswordAuthentication no localhost -- echo 123", |
651 | + s.fakessh+" -o StrictHostKeyChecking no -o PasswordAuthentication no localhost echo 123", |
652 | ) |
653 | // If identities are specified, then the defaults are must added. |
654 | // Only the defaults that exist on disk will be added. |
655 | @@ -175,6 +182,6 @@ |
656 | c.Assert(err, gc.IsNil) |
657 | opts.SetIdentities("x", "y") |
658 | s.assertCommandArgs(c, s.commandOptions([]string{"echo", "123"}, &opts), |
659 | - s.fakessh+" -o StrictHostKeyChecking no -o PasswordAuthentication no -i x -i y -i "+def2+" localhost -- echo 123", |
660 | + s.fakessh+" -o StrictHostKeyChecking no -o PasswordAuthentication no -i x -i y -i "+def2+" localhost echo 123", |
661 | ) |
662 | } |
Reviewers: mp+207498_ code.launchpad. net,
Message:
Please take a look.
Description:
ssh: Fixed bug #1281577 - ssh command-line opts
Removed the extraneous "--" that the ssh command
adds to the passed arguments, so any extra arguments
after the command will be forwarded to ssh properly.
It worked in 1.16, so this fixes a regression.
https:/ /code.launchpad .net/~dimitern/ juju-core/ 302-lp- 1281577- ssh-command- extra-args/ +merge/ 207498
Requires: /code.launchpad .net/~dimitern/ juju-core/ 301-lp- 1282553- use-curl- not-wget- precise/ +merge/ 207443
https:/
(do not edit description out of merge proposal)
Please review this at https:/ /codereview. appspot. com/66340045/
Affected files (+3, -8 lines): ssh_openssh. go
A [revision details]
M cmd/juju/ssh.go
M utils/ssh/
Index: [revision details]
=== added file '[revision details]'
--- [revision details] 2012-01-01 00:00:00 +0000
+++ [revision details] 2012-01-01 00:00:00 +0000
@@ -0,0 +1,2 @@
+Old revision:
<email address hidden>
+New revision:
<email address hidden>
Index: cmd/juju/ssh.go EnablePTY( ) "ubuntu@ "+host, args, &options) "ubuntu@ "+host, c.Args, &options)
=== modified file 'cmd/juju/ssh.go'
--- cmd/juju/ssh.go 2014-01-13 06:25:28 +0000
+++ cmd/juju/ssh.go 2014-02-20 16:46:11 +0000
@@ -82,15 +82,9 @@
if err != nil {
return err
}
- args := c.Args
- if len(args) > 0 && args[0] == "--" {
- // utils/ssh adds "--"; we will continue to accept
- // it from the CLI for backwards compatibility.
- args = args[1:]
- }
var options ssh.Options
options.
- cmd := ssh.Command(
+ cmd := ssh.Command(
cmd.Stdin = ctx.Stdin
cmd.Stdout = ctx.Stdout
cmd.Stderr = ctx.Stderr
Index: utils/ssh/ ssh_openssh. go ssh/ssh_ openssh. go' ssh_openssh. go 2014-02-05 08:48:40 +0000 ssh_openssh. go 2014-02-20 16:46:11 +0000 options, sshKind)
=== modified file 'utils/
--- utils/ssh/
+++ utils/ssh/
@@ -113,7 +113,6 @@
args := opensshOptions(
args = append(args, host)
if len(command) > 0 {
- args = append(args, "--")
args = append(args, command...)
}
bin, args := sshpassWrap("ssh", args)