Skip to content

Commit b60cbe5

Browse files
Better error handling for scp uploads (#161)
* Better error handling for scp uploads * Update handling upload warnings Continue to collect warnings instead of stopping at the first. Co-authored-by: Max Mulatz <[email protected]>
1 parent b1da3ef commit b60cbe5

File tree

4 files changed

+75
-4
lines changed

4 files changed

+75
-4
lines changed

lib/sshkit/scp/upload.ex

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -107,6 +107,9 @@ defmodule SSHKit.SCP.Upload do
107107
{:data, _, 0, data} ->
108108
handle_error_data(state, options, data)
109109

110+
{:data, _, 1, data} ->
111+
fatal(options, state, data)
112+
110113
{:exit_status, _, status} ->
111114
exited(options, state, status)
112115

@@ -215,9 +218,13 @@ defmodule SSHKit.SCP.Upload do
215218
{:cont, {:error, "SCP channel closed before completing the transfer"}}
216219
end
217220

218-
defp warning(_, state = {name, cwd, stack, errs}, buffer) do
221+
defp warning(options, {name, _file, _stat, cwd, stack, errs}, buffer) do
222+
warning(options, {name, cwd, stack, errs}, buffer)
223+
end
224+
225+
defp warning(options, state = {_name, cwd, stack, errs}, buffer) do
219226
if String.last(buffer) == "\n" do
220-
{:cont, {name, cwd, stack, errs ++ [String.trim(buffer)]}}
227+
next(options, cwd, stack, errs ++ [String.trim(buffer)])
221228
else
222229
{:cont, {:warning, state, buffer}}
223230
end

test/sshkit/scp/upload_test.exs

Lines changed: 27 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -155,7 +155,6 @@ defmodule SSHKit.SCP.UploadTest do
155155
channel: channel
156156
} do
157157
error_msg = "error part 1 error part 2 error part 3"
158-
{name, cwd, stack, _errs} = state
159158

160159
msg1 = {:data, channel, 0, <<1, "error part 1 ">>}
161160
state1 = {:warning, state, "error part 1 "}
@@ -166,7 +165,33 @@ defmodule SSHKit.SCP.UploadTest do
166165
assert {:cont, state2} == handler.(msg2, state1)
167166

168167
msg3 = {:data, channel, 0, <<"error part 3\n">>}
169-
assert {:cont, {name, cwd, stack, [error_msg]}} == handler.(msg3, state2)
168+
assert {:cont, _directive, {_name, _cwd, _stack, [^error_msg]}} = handler.(msg3, state2)
169+
end
170+
171+
test "proceeds with next file in stack after warning", %{
172+
upload: %Upload{handler: handler, state: state},
173+
channel: channel
174+
} do
175+
{name, cwd, _stack, _errs} = state
176+
msg = {:data, channel, 0, <<1, "error message\n">>}
177+
178+
assert {:cont, 'D0755 0 local_dir\n',
179+
{name, cwd <> "/local_dir", [["other.txt"], []], ["error message"]}} ==
180+
handler.(msg, state)
181+
end
182+
183+
test "collects warning when in write state", %{
184+
upload: %Upload{handler: handler, state: state},
185+
channel: channel
186+
} do
187+
{name, cwd, stack, _errs} = state
188+
189+
state = {:write, "local.txt", %{}, cwd, stack, []}
190+
msg = {:data, channel, 0, <<1, "error message\n">>}
191+
192+
assert {:cont, 'D0755 0 local_dir\n',
193+
{name, cwd <> "/local_dir", [["other.txt"], []], ["error message"]}} ==
194+
handler.(msg, state)
170195
end
171196

172197
test "aggregates connection errors in the state and halts", %{

test/sshkit/scp_functional_test.exs

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,17 @@ defmodule SSHKit.SCPFunctionalTest do
5252
assert verify_mtime(conn, local, remote)
5353
end)
5454
end
55+
56+
@tag boot: [@bootconf]
57+
test "uploads to nonexistent target directory (recursive: true)", %{hosts: [host]} do
58+
local = "test/fixtures/local_dir"
59+
remote = "/some/nonexistent/destination"
60+
61+
SSH.connect(host.name, host.options, fn conn ->
62+
assert {:error, msg} = SCP.upload(conn, local, remote, recursive: true)
63+
assert msg =~ "scp: /some/nonexistent/destination: No such file or directory"
64+
end)
65+
end
5566
end
5667

5768
describe "download/4" do

test/sshkit_functional_test.exs

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -159,6 +159,34 @@ defmodule SSHKitFunctionalTest do
159159
assert verify_transfer(context, local, Path.basename(local))
160160
end
161161

162+
test "uploads a file to a directory that does not exist", %{hosts: hosts} do
163+
local = "test/fixtures/local.txt"
164+
165+
context =
166+
hosts
167+
|> SSHKit.context()
168+
|> SSHKit.path("/otp/releases")
169+
170+
assert [
171+
error: "sh: cd: line 1: can't cd to /otp/releases",
172+
error: "sh: cd: line 1: can't cd to /otp/releases"
173+
] = SSHKit.upload(context, local)
174+
end
175+
176+
test "uploads a file to a directory we have no access to", %{hosts: hosts} do
177+
local = "test/fixtures/local.txt"
178+
179+
context =
180+
hosts
181+
|> SSHKit.context()
182+
|> SSHKit.path("/")
183+
184+
assert [
185+
error: "SCP exited with non-zero exit code 1: scp: local.txt: Permission denied",
186+
error: "SCP exited with non-zero exit code 1: scp: local.txt: Permission denied"
187+
] = SSHKit.upload(context, local)
188+
end
189+
162190
test "recursive: true", %{hosts: [host | _] = hosts} do
163191
local = "test/fixtures"
164192
remote = "/home/#{host.options[:user]}/fixtures"

0 commit comments

Comments
 (0)