-
Notifications
You must be signed in to change notification settings - Fork 2.2k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
(PUP-6675) Use pipes instead of temporary files for Puppet exec #5872
Conversation
Re-opened after reverting the accidental merge. |
CLA signed by all contributors. |
# case where stdout is never closed. | ||
ready = IO.select([reader], [], [], 0.1) | ||
begin | ||
output << reader.read_nonblock(4096) if ready |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@joshcooper @Iristyle this can result in returning a string that is BINARY/ASCII-8BIT
encoded. The read is binary, and if the result of that read includes non 7-bit ASCII characters it will not implicitly convert to the internal encoding (by assigning to output).
Previously we would read from a file, which would use whatever default encoding File.open
uses.
My intuition is that we should force_encoding
to external_encoding
after finishing all reads, then encode
to internal_encoding
. Alternatively, we could use the Puppet::Util::CharacterEncoding::convert_to_utf_8
helper to convert to UTF-8. Have any suggestions?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Pushed up a PR doing this. Fixes the pxp-agent issue. It may need some error handling.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I would use the new helper, which is designed around coercing byte streams into the correct encoding.
lib/puppet/util/execution.rb
Outdated
|
||
# We close stdin/stdout/stderr immediately after execution as there no longer needed. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
typo: there -> they're
412dd6b
to
fe9758e
Compare
CLA signed by all contributors. |
fe9758e
to
1b4262a
Compare
lib/puppet/util/execution.rb
Outdated
end | ||
|
||
output.force_encoding(Encoding.default_external) | ||
output.encode! |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Not sure what this .encode!
is for...
If you want to re-encode to UTF-8, I would try and be explicit about that.
output.encode!(Encoding::UTF_8)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It converts to Encoding.default_internal
. I thought that was more generic than UTF-8, but if we assert that the default internal will always be UTF-8 then using Puppet::Util::CharacterEncoding::convert_to_utf_8
definitely makes sense.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah, we always want UTF-8 within our system - I don't see that changing as our encoding lingua franca.
a6fc753
to
3914cff
Compare
Build available with SHA= |
Now a Solaris failure in the pxp-agent test... Update: on Solaris, when run as a service, the default_external encoding is ASCII-8BIT. So this code still fails because what we get over the pipe is UTF-8. I'm tempted to just force encoding to UTF-8. |
361e8a2
to
a7ed054
Compare
# Force to external encoding to preserve prior behavior when reading a file. | ||
# Wait until after reading all data so we don't encounter corruption when | ||
# reading part of a multi-byte unicode character if default_external is UTF-8. | ||
output.force_encoding(Encoding.default_external) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Switched to forcing the encoding to Encoding.default_external
to preserve the prior behavior with File.open
(see wait_for_output
).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
To fully mimic the File.open
behavior, this should then encode to default_internal (which is normally unset, resulting in a no-op). That could often cause problems, so I'm not sure it's a behavior worth emulating.
Build available at SHA= Passed mcollective and pxp-agent tests. I believe puppet tests will as well, but ran out of time running them all. |
# `start /b ping 127.0.0.1`, we couldn't handle it with pipes as there's no non-blocking | ||
# read available. | ||
if options[:squelch] | ||
stdout = Puppet::FileSystem.open(null_file, nil, 'w') |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Would it make sense to reimplement https://stackoverflow.com/questions/34504970/non-blocking-read-on-os-pipe-on-windows to get non-blocking IO on pipes even on windows and unify the implementations?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Possibly. It's currently not causing any issues to use a file on Windows.
My feeling is this area might get a rewrite for Puppet 6, and changes to Windows aren't necessary. So I'd rather schedule that as separate work.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
ok
a7ed054
to
a6c69dd
Compare
a6c69dd
to
3a211ba
Compare
Under selinux, when Puppet is invoked by another process with reduced privileges, any sub-programs invoked by Puppet will not inherit Puppet's selinux priveleges. This specifically causes silent failures when invoking applications that don't normally have the ability to write files - such as iptables-save or hostname - because Puppet redirects their output to a temporary file. Use pipes instead of a temporary file to capture the output of subprocesses on POSIX systems. Poorly behaved scripts need special handling, as a plain foo & will inherit stdout and not close it. Read from the pipe without requiring EOF. On Windows, we retain the old file-based IO to avoid breaking poorly written scripts - start /b ping 127.0.0.1 captures stdout - and to retain the current behavior when Puppet is terminated - a new Puppet run can start even if a subprocess was left running by the prior process. When reading from the pipe in blocks, we allow binary format. This is primarily to avoid issues if we read only some bytes of a multi-byte UTF-8 character. At the end, we assign the default external encoding and convert to UTF-8, our standardized internal encoding.
3a211ba
to
36daa64
Compare
end | ||
|
||
teardown do | ||
# On Windows, Puppet waits until the sleep process exits before exiting |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Apparently Windows didn't behave the way I expected. This behavior isn't changed by my PR, but this test is different on Windows to take it into account.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
👍
@MikaelSmith sorry I didn't intend to close this - I deleted the |
Reopened as #5967. |
Under selinux, when Puppet is invoked by another process with
reduced privileges, any sub-programs invoked by Puppet will not
inherit Puppet's selinux priveleges. This specifically causes silent
failures when invoking applications that don't normally have the
ability to write files - such as iptables-save or hostname -
because Puppet redirects their output to a temporary file.
Use pipes instead of a temporary file to capture the output of
subprocesses on POSIX systems. Poorly behaved scripts need special
handling, as a plain foo & will inherit stdout and not close it.
Read from the pipe without requiring EOF.
On Windows, we retain the old file-based IO to avoid breaking poorly
written scripts - start /b ping 127.0.0.1 captures stdout - and to
retain the current behavior when Puppet is terminated - a new Puppet run
can start even if a subprocess was left running by the prior process.