Skip to content

Commit

Permalink
Fix copying of empty file (ansible-collections#1690)
Browse files Browse the repository at this point in the history
Fix copying of empty file

SUMMARY
Fixes ansible-collections#1686.
Basically it is a workaround for curl, which did not create empty files when using the -o flag (curl/curl#183). Although this issue was fixed in 2015, 'actively maintained operating systems' still use older of it (e.g. CentOS 7)
ISSUE TYPE
Bugfix Pull Request
COMPONENT NAME
aws_ssm
ADDITIONAL INFORMATION
I was not able to test it yet in a relevant environment (e.g. with CentOS 7), will do so shortly.

Reviewed-by: Bence Hornák <None>
Reviewed-by: Mark Chappell <None>
  • Loading branch information
bencehornak authored Feb 3, 2023
1 parent 231ead9 commit e8c7a10
Show file tree
Hide file tree
Showing 3 changed files with 85 additions and 42 deletions.
2 changes: 2 additions & 0 deletions changelogs/fragments/1690-fix-copying-empty-file.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
bugfixes:
- aws_ssm - fix copying empty file with older curl versions (https://github.com/ansible-collections/community.aws/issues/1686).
112 changes: 70 additions & 42 deletions plugins/connection/aws_ssm.py
Original file line number Diff line number Diff line change
Expand Up @@ -817,33 +817,66 @@ def _generate_commands(self, bucket_name, s3_path, in_path, out_path):

if self.is_windows:
put_command_headers = "; ".join([f"'{h}' = '{v}'" for h, v in put_headers.items()])
put_command = (
"Invoke-WebRequest -Method PUT "
f"-Headers @{{{put_command_headers}}} " # @{'key' = 'value'; 'key2' = 'value2'}
f"-InFile '{in_path}' "
f"-Uri '{put_url}' "
f"-UseBasicParsing"
)
get_command = (
"Invoke-WebRequest "
f"'{get_url}' "
f"-OutFile '{out_path}'"
)
put_commands = [
(
"Invoke-WebRequest -Method PUT "
f"-Headers @{{{put_command_headers}}} " # @{'key' = 'value'; 'key2' = 'value2'}
f"-InFile '{in_path}' "
f"-Uri '{put_url}' "
f"-UseBasicParsing"
),
]
get_commands = [
(
"Invoke-WebRequest "
f"'{get_url}' "
f"-OutFile '{out_path}'"
),
]
else:
put_command_headers = " ".join([f"-H '{h}: {v}'" for h, v in put_headers.items()])
put_command = (
"curl --request PUT "
f"{put_command_headers} "
f"--upload-file '{in_path}' "
f"'{put_url}'"
)
get_command = (
"curl "
f"-o '{out_path}' "
f"'{get_url}'"
)
put_commands = [
(
"curl --request PUT "
f"{put_command_headers} "
f"--upload-file '{in_path}' "
f"'{put_url}'"
),
]
get_commands = [
(
"curl "
f"-o '{out_path}' "
f"'{get_url}'"
),
# Due to https://github.com/curl/curl/issues/183 earlier
# versions of curl did not create the output file, when the
# response was empty. Although this issue was fixed in 2015,
# some actively maintained operating systems still use older
# versions of it (e.g. CentOS 7)
(
"touch "
f"'{out_path}'"
)
]

return get_commands, put_commands, put_args

def _exec_transport_commands(self, in_path, out_path, commands):
stdout_combined, stderr_combined = '', ''
for command in commands:
(returncode, stdout, stderr) = self.exec_command(command, in_data=None, sudoable=False)

return get_command, put_command, put_args
# Check the return code
if returncode != 0:
raise AnsibleError(
f"failed to transfer file to {in_path} {out_path}:\n"
f"{stdout}\n{stderr}")

stdout_combined += stdout
stderr_combined += stderr

return (returncode, stdout_combined, stderr_combined)

@_ssm_retry
def _file_transport_command(self, in_path, out_path, ssm_action):
Expand All @@ -852,30 +885,25 @@ def _file_transport_command(self, in_path, out_path, ssm_action):
bucket_name = self.get_option("bucket_name")
s3_path = self._escape_path(f"{self.instance_id}/{out_path}")

get_command, put_command, put_args = self._generate_commands(
get_commands, put_commands, put_args = self._generate_commands(
bucket_name, s3_path, in_path, out_path,
)

client = self._s3_client
if ssm_action == 'get':
(returncode, stdout, stderr) = self.exec_command(put_command, in_data=None, sudoable=False)
with open(to_bytes(out_path, errors='surrogate_or_strict'), 'wb') as data:
client.download_fileobj(bucket_name, s3_path, data)
else:
with open(to_bytes(in_path, errors='surrogate_or_strict'), 'rb') as data:
client.upload_fileobj(data, bucket_name, s3_path, ExtraArgs=put_args)
(returncode, stdout, stderr) = self.exec_command(get_command, in_data=None, sudoable=False)

# Remove the files from the bucket after they've been transferred
client.delete_object(Bucket=bucket_name, Key=s3_path)

# Check the return code
if returncode == 0:
try:
if ssm_action == 'get':
(returncode, stdout, stderr) = self._exec_transport_commands(in_path, out_path, put_commands)
with open(to_bytes(out_path, errors='surrogate_or_strict'), 'wb') as data:
client.download_fileobj(bucket_name, s3_path, data)
else:
with open(to_bytes(in_path, errors='surrogate_or_strict'), 'rb') as data:
client.upload_fileobj(data, bucket_name, s3_path, ExtraArgs=put_args)
(returncode, stdout, stderr) = self._exec_transport_commands(in_path, out_path, get_commands)
return (returncode, stdout, stderr)

raise AnsibleError(
f"failed to transfer file to {in_path} {out_path}:\n"
f"{stdout}\n{stderr}")
finally:
# Remove the files from the bucket after they've been transferred
client.delete_object(Bucket=bucket_name, Key=s3_path)

def put_file(self, in_path, out_path):
''' transfer a file from local to remote '''
Expand Down
13 changes: 13 additions & 0 deletions tests/integration/targets/connection/test_connection.yml
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
local_file: '{{ local_dir }}/汉语.txt'
remote_dir: '{{ remote_tmp }}-汉语'
remote_file: '{{ remote_dir }}/汉语.txt'
remote_empty_file: '{{ remote_dir }}/empty.txt'
tasks:

### test wait_for_connection plugin
Expand Down Expand Up @@ -77,3 +78,15 @@
- root
loop_control:
loop_var: user_name

### copy an empty file
- name: copy an empty file
action: "{{ action_prefix }}copy content= dest={{ remote_empty_file }}"
- name: stat empty file
action: "{{ action_prefix }}stat path={{ remote_empty_file }}"
register: stat_empty_file_cmd
- name: check that empty file exists
assert:
that:
- stat_empty_file_cmd.stat.isreg # it is a regular file
- stat_empty_file_cmd.stat.size == 0

0 comments on commit e8c7a10

Please sign in to comment.