Skip to content

Commit 811bc4d

Browse files
Revert "Tighten output file permissions (#10197)"
This reverts commit 01a21ee.
1 parent 84f0ec6 commit 811bc4d

4 files changed

Lines changed: 68 additions & 151 deletions

File tree

awscli/customizations/s3events.py

Lines changed: 22 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -11,11 +11,9 @@
1111
# ANY KIND, either express or implied. See the License for the specific
1212
# language governing permissions and limitations under the License.
1313
"""Add S3 specific event streaming output arg."""
14-
15-
import os
16-
1714
from awscli.arguments import CustomArgument
1815

16+
1917
STREAM_HELP_TEXT = 'Filename where the records will be saved'
2018

2119

@@ -26,28 +24,26 @@ class DocSectionNotFoundError(Exception):
2624
def register_event_stream_arg(event_handlers):
2725
event_handlers.register(
2826
'building-argument-table.s3api.select-object-content',
29-
add_event_stream_output_arg,
30-
)
27+
add_event_stream_output_arg)
3128
event_handlers.register_last(
32-
'doc-output.s3api.select-object-content', replace_event_stream_docs
29+
'doc-output.s3api.select-object-content',
30+
replace_event_stream_docs
3331
)
3432

3533

3634
def register_document_expires_string(event_handlers):
37-
event_handlers.register_last('doc-output.s3api', document_expires_string)
38-
35+
event_handlers.register_last(
36+
'doc-output.s3api',
37+
document_expires_string
38+
)
3939

40-
def add_event_stream_output_arg(
41-
argument_table, operation_model, session, **kwargs
42-
):
40+
def add_event_stream_output_arg(argument_table, operation_model,
41+
session, **kwargs):
4342
argument_table['outfile'] = S3SelectStreamOutputArgument(
44-
name='outfile',
45-
help_text=STREAM_HELP_TEXT,
46-
cli_type_name='string',
47-
positional_arg=True,
43+
name='outfile', help_text=STREAM_HELP_TEXT,
44+
cli_type_name='string', positional_arg=True,
4845
stream_key=operation_model.output_shape.serialization['payload'],
49-
session=session,
50-
)
46+
session=session)
5147

5248

5349
def replace_event_stream_docs(help_command, **kwargs):
@@ -60,14 +56,11 @@ def replace_event_stream_docs(help_command, **kwargs):
6056
# This should never happen, but in the rare case that it does
6157
# we should be raising something with a helpful error message.
6258
raise DocSectionNotFoundError(
63-
f'Could not find the "output" section for the command: {help_command}'
64-
)
59+
'Could not find the "output" section for the command: %s'
60+
% help_command)
6561
doc.write('======\nOutput\n======\n')
66-
doc.write(
67-
"This command generates no output. The selected "
68-
"object content is written to the specified outfile.\n"
69-
)
70-
62+
doc.write("This command generates no output. The selected "
63+
"object content is written to the specified outfile.\n")
7164

7265
def document_expires_string(help_command, **kwargs):
7366
doc = help_command.doc
@@ -85,7 +78,7 @@ def document_expires_string(help_command, **kwargs):
8578
f'\n\n{" " * doc.style.indentation * doc.style.indent_width}',
8679
'ExpiresString -> (string)\n\n',
8780
'\tThe raw, unparsed value of the ``Expires`` field.',
88-
f'\n\n{" " * doc.style.indentation * doc.style.indent_width}',
81+
f'\n\n{" " * doc.style.indentation * doc.style.indent_width}'
8982
]
9083

9184
for idx, write in enumerate(deprecation_note_and_expires_string):
@@ -98,7 +91,7 @@ class S3SelectStreamOutputArgument(CustomArgument):
9891
_DOCUMENT_AS_REQUIRED = True
9992

10093
def __init__(self, stream_key, session, **kwargs):
101-
super().__init__(**kwargs)
94+
super(S3SelectStreamOutputArgument, self).__init__(**kwargs)
10295
# This is the key in the response body where we can find the
10396
# streamed contents.
10497
self._stream_key = stream_key
@@ -107,9 +100,8 @@ def __init__(self, stream_key, session, **kwargs):
107100

108101
def add_to_params(self, parameters, value):
109102
self._output_file = value
110-
self._session.register(
111-
'after-call.s3.SelectObjectContent', self.save_file
112-
)
103+
self._session.register('after-call.s3.SelectObjectContent',
104+
self.save_file)
113105

114106
def save_file(self, parsed, **kwargs):
115107
# This method is hooked into after-call which fires
@@ -120,11 +112,7 @@ def save_file(self, parsed, **kwargs):
120112
if self._stream_key not in parsed:
121113
return
122114
event_stream = parsed[self._stream_key]
123-
fd = os.open(
124-
self._output_file, os.O_WRONLY | os.O_CREAT | os.O_TRUNC, 0o600
125-
)
126-
os.chmod(self._output_file, 0o600)
127-
with os.fdopen(fd, 'wb') as fp:
115+
with open(self._output_file, 'wb') as fp:
128116
for event in event_stream:
129117
if 'Records' in event:
130118
fp.write(event['Records']['Payload'])

awscli/customizations/streamingoutputarg.py

Lines changed: 13 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -10,26 +10,21 @@
1010
# distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF
1111
# ANY KIND, either express or implied. See the License for the specific
1212
# language governing permissions and limitations under the License.
13-
import os
14-
1513
from botocore.model import Shape
1614

1715
from awscli.arguments import BaseCLIArgument
1816

1917

20-
def add_streaming_output_arg(
21-
argument_table, operation_model, session, **kwargs
22-
):
18+
def add_streaming_output_arg(argument_table, operation_model,
19+
session, **kwargs):
2320
# Implementation detail: hooked up to 'building-argument-table'
2421
# event.
2522
if _has_streaming_output(operation_model):
2623
streaming_argument_name = _get_streaming_argument_name(operation_model)
2724
argument_table['outfile'] = StreamingOutputArgument(
2825
response_key=streaming_argument_name,
2926
operation_model=operation_model,
30-
session=session,
31-
name='outfile',
32-
)
27+
session=session, name='outfile')
3328

3429

3530
def _has_streaming_output(model):
@@ -41,16 +36,15 @@ def _get_streaming_argument_name(model):
4136

4237

4338
class StreamingOutputArgument(BaseCLIArgument):
39+
4440
BUFFER_SIZE = 32768
4541
HELP = 'Filename where the content will be saved'
4642

47-
def __init__(
48-
self, response_key, operation_model, name, session, buffer_size=None
49-
):
43+
def __init__(self, response_key, operation_model, name,
44+
session, buffer_size=None):
5045
self._name = name
51-
self.argument_model = Shape(
52-
'StreamingOutputArgument', {'type': 'string'}
53-
)
46+
self.argument_model = Shape('StreamingOutputArgument',
47+
{'type': 'string'})
5448
if buffer_size is None:
5549
buffer_size = self.BUFFER_SIZE
5650
self._buffer_size = buffer_size
@@ -87,15 +81,15 @@ def documentation(self):
8781
return self.HELP
8882

8983
def add_to_parser(self, parser):
90-
parser.add_argument(self._name, metavar=self.py_name, help=self.HELP)
84+
parser.add_argument(self._name, metavar=self.py_name,
85+
help=self.HELP)
9186

9287
def add_to_params(self, parameters, value):
9388
self._output_file = value
9489
service_id = self._operation_model.service_model.service_id.hyphenize()
9590
operation_name = self._operation_model.name
96-
self._session.register(
97-
f'after-call.{service_id}.{operation_name}', self.save_file
98-
)
91+
self._session.register('after-call.%s.%s' % (
92+
service_id, operation_name), self.save_file)
9993

10094
def save_file(self, parsed, **kwargs):
10195
if self._response_key not in parsed:
@@ -106,11 +100,7 @@ def save_file(self, parsed, **kwargs):
106100
return
107101
body = parsed[self._response_key]
108102
buffer_size = self._buffer_size
109-
fd = os.open(
110-
self._output_file, os.O_WRONLY | os.O_CREAT | os.O_TRUNC, 0o600
111-
)
112-
os.chmod(self._output_file, 0o600)
113-
with os.fdopen(fd, 'wb') as fp:
103+
with open(self._output_file, 'wb') as fp:
114104
data = body.read(buffer_size)
115105
while data:
116106
fp.write(data)

tests/functional/s3api/test_select_object_content.py

Lines changed: 28 additions & 70 deletions
Original file line numberDiff line numberDiff line change
@@ -12,17 +12,15 @@
1212
# ANY KIND, either express or implied. See the License for the specific
1313
# language governing permissions and limitations under the License.
1414
import os
15-
import shutil
1615
import tempfile
16+
import shutil
1717

18-
from awscli.testutils import (
19-
BaseAWSCommandParamsTest,
20-
BaseAWSHelpOutputTest,
21-
skip_if_windows,
22-
)
18+
from awscli.testutils import BaseAWSCommandParamsTest
19+
from awscli.testutils import BaseAWSHelpOutputTest
2320

2421

2522
class TestGetObject(BaseAWSCommandParamsTest):
23+
2624
prefix = ['s3api', 'select-object-content']
2725

2826
def setUp(self):
@@ -38,23 +36,11 @@ def create_fake_payload(self):
3836
yield {'Records': {'Payload': b'a,b,c,d\n'}}
3937
# These next two events are ignored because they aren't
4038
# "Records".
41-
yield {
42-
'Progress': {
43-
'Details': {
44-
'BytesScanned': 1048576,
45-
'BytesProcessed': 37748736,
46-
}
47-
}
48-
}
39+
yield {'Progress': {'Details': {'BytesScanned': 1048576,
40+
'BytesProcessed': 37748736}}}
4941
yield {'Records': {'Payload': b'e,f,g,h\n'}}
50-
yield {
51-
'Stats': {
52-
'Details': {
53-
'BytesProcessed': 62605400,
54-
'BytesScanned': 1662276,
55-
}
56-
}
57-
}
42+
yield {'Stats': {'Details': {'BytesProcessed': 62605400,
43+
'BytesScanned': 1662276}}}
5844
yield {'End': {}}
5945

6046
def test_can_stream_to_file(self):
@@ -65,15 +51,14 @@ def test_can_stream_to_file(self):
6551
cmdline.extend(['--expression', 'SELECT * FROM S3Object'])
6652
cmdline.extend(['--expression-type', 'SQL'])
6753
cmdline.extend(['--request-progress', 'Enabled=True'])
68-
cmdline.extend(
69-
['--input-serialization', '{"CSV": {}, "CompressionType": "GZIP"}']
70-
)
54+
cmdline.extend(['--input-serialization',
55+
'{"CSV": {}, "CompressionType": "GZIP"}'])
7156
cmdline.extend(['--output-serialization', '{"CSV": {}}'])
7257
cmdline.extend([filename])
7358

7459
expected_params = {
7560
'Bucket': 'mybucket',
76-
'Key': 'mykey',
61+
'Key': u'mykey',
7762
'Expression': 'SELECT * FROM S3Object',
7863
'ExpressionType': 'SQL',
7964
'InputSerialization': {'CSV': {}, 'CompressionType': 'GZIP'},
@@ -82,31 +67,12 @@ def test_can_stream_to_file(self):
8267
}
8368
stdout = self.assert_params_for_cmd(cmdline, expected_params)[0]
8469
self.assertEqual(stdout, '')
85-
with open(filename) as f:
70+
with open(filename, 'r') as f:
8671
contents = f.read()
87-
self.assertEqual(contents, ('a,b,c,d\n' 'e,f,g,h\n'))
88-
89-
@skip_if_windows('chmod is not supported on Windows')
90-
def test_output_file_permissions(self):
91-
filename = os.path.join(self._tempdir, 'outfile_perms')
92-
cmdline = self.prefix + [
93-
'--bucket',
94-
'mybucket',
95-
'--key',
96-
'mykey',
97-
'--expression',
98-
'SELECT * FROM S3Object',
99-
'--expression-type',
100-
'SQL',
101-
'--input-serialization',
102-
'{"CSV": {}}',
103-
'--output-serialization',
104-
'{"CSV": {}}',
105-
filename,
106-
]
107-
self.assert_params_for_cmd(cmdline, ignore_params=True)
108-
# Mask file type bits to isolate permission bits (rwxrwxrwx)
109-
self.assertEqual(os.stat(filename).st_mode & 0o777, 0o600)
72+
self.assertEqual(contents, (
73+
'a,b,c,d\n'
74+
'e,f,g,h\n'
75+
))
11076

11177
def test_errors_are_propagated(self):
11278
self.http_response.status_code = 400
@@ -117,39 +83,30 @@ def test_errors_are_propagated(self):
11783
}
11884
}
11985
cmdline = self.prefix + [
120-
'--bucket',
121-
'mybucket',
122-
'--key',
123-
'mykey',
124-
'--expression',
125-
'SELECT * FROM S3Object',
126-
'--expression-type',
127-
'SQL',
128-
'--request-progress',
129-
'Enabled=True',
130-
'--input-serialization',
131-
'{"CSV": {}, "CompressionType": "GZIP"}',
132-
'--output-serialization',
133-
'{"CSV": {}}',
86+
'--bucket', 'mybucket',
87+
'--key', 'mykey',
88+
'--expression', 'SELECT * FROM S3Object',
89+
'--expression-type', 'SQL',
90+
'--request-progress', 'Enabled=True',
91+
'--input-serialization', '{"CSV": {}, "CompressionType": "GZIP"}',
92+
'--output-serialization', '{"CSV": {}}',
13493
os.path.join(self._tempdir, 'outfile'),
13594
]
13695
expected_params = {
13796
'Bucket': 'mybucket',
138-
'Key': 'mykey',
97+
'Key': u'mykey',
13998
'Expression': 'SELECT * FROM S3Object',
14099
'ExpressionType': 'SQL',
141100
'InputSerialization': {'CSV': {}, 'CompressionType': 'GZIP'},
142101
'OutputSerialization': {'CSV': {}},
143102
'RequestProgress': {'Enabled': True},
144103
}
145104
self.assert_params_for_cmd(
146-
cmd=cmdline,
147-
params=expected_params,
105+
cmd=cmdline, params=expected_params,
148106
expected_rc=255,
149107
stderr_contains=(
150108
'An error occurred (CastFailed) when '
151-
'calling the SelectObjectContent operation'
152-
),
109+
'calling the SelectObjectContent operation'),
153110
)
154111

155112

@@ -159,7 +116,8 @@ def test_output(self):
159116
# We don't want to be super picky because the wording may change
160117
# We just want to verify the Output section was customized.
161118
self.assert_contains(
162-
'Output\n======\n' 'This command generates no output'
119+
'Output\n======\n'
120+
'This command generates no output'
163121
)
164122
self.assert_not_contains('[outfile')
165123
self.assert_contains('outfile')

0 commit comments

Comments
 (0)