Skip to content
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

Real time output for tool deploy scripts #337

Merged
merged 8 commits into from
Nov 2, 2022

Conversation

mickmcgrath13
Copy link
Contributor

Description

Uses subprocess.Popen instead of subprocess.run and uses subprocess.PIPE and process.poll() to output the subprocess execution line-by-line

Fixes #320

Type of change

Please delete options that are not relevant.

  • Bug fix (non-breaking change which fixes an issue)
  • New feature (non-breaking change which adds functionality)
  • Breaking change (fix or feature that would cause existing functionality to not work as expected)
  • Documentation update

How Has This Been Tested?

With a local ops repo with helm charts. Ran the 2.1.0 docker container locally and mounted the ops repo and the BitOps code to the proper locations within the container.

Logs
..were output in real time

Test Configuration:

  • BitOps image tag used: 2.1.0
  • Hardware: MacOS
  • Tool: helm, terraform

Checklist:

  • My code follows the style guidelines of this project
  • I have performed a self-review of my own code
  • I have commented my code, particularly in hard-to-understand areas
  • I have made corresponding changes to the documentation

Comment on lines 302 to 306
# if output_stdout:
# # TODO: parse output for secrets
# # TODO: specify plugin and output tight output (no extra newlines)
# # logger.info(output_stdout)
# print(output_stdout)
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this is probably not necessary, but I left it in because something about the implementation seems off (see previous comment)

Comment on lines 275 to 291
process = subprocess.Popen([
plugin_deploy_language,
plugin_deploy_script_path,
stack_action,
],
# stdout=subprocess.PIPE,
# TODO: adding any form of stderr to the above call causes the below polling to not work properly for real time output
# instead, it causes the script to wait until everything is complete before outputting anything
# tried: stderr=err, stderr=subprocess.PIPE, stderr=subprocess.STDOUT
# for stderr=err, err is a file: with open(error_file,"wb") as err:
# stderr=err,
# stderr=subprocess.STDOUT,

# TODO: oddly, if you set only stderr=subprocess.PIPE and NOT stdout,
# the process.poll() below being focused on stderr does seem to output both stdout and stderr in real time
stderr=subprocess.PIPE,
universal_newlines=True
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This was strange to me.. If we do both stderr and stdout like:

stdout=subprocess.PIPE,
stderr=subprocess.PIPE

..the below polling doesn't seem to work.
even if stderr is one of:

  • err (i.e. a file opened with `with open(error_file, "wb") as err:
  • subprocess.PIPE
  • subprocess.STDOUT

..then the weird thing is that if we do just stdout=subprocess.PIPE,, we do not get errors, but if we do stderr=subprocess.PIPE, we get both stdout and stderr real time output 🤷

Copy link
Member

@arm4b arm4b Oct 26, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I understand the easy hack idea here was just combining two streams into one if we don't need to process these streams separately.

I see where it comes from. Based on https://docs.python.org/3/library/subprocess.html#using-the-subprocess-module

If capture_output is true, stdout and stderr will be captured. When used, the internal Popen object is automatically created with stdout=PIPE and stderr=PIPE. The stdout and stderr arguments may not be supplied at the same time as capture_output. If you wish to capture and combine both streams into one, use stdout=PIPE and stderr=STDOUT instead of capture_output.

Can you try the following? No while True loop needed then.

process = subprocess.Popen(command, stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
for combined_output in process.stdout:
    print(combined_output)

It worked for me in quick console tests. The difference is that it reads stdout (which has combined output) instead of stderr.

That would be at least more intuitive and follow the official docs.
If works, we can reference that documentation URL in code comments then and call the mystery as solved.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

also, the quote from the link above is for subprocess.run not subprocess.Popen

@mickmcgrath13
Copy link
Contributor Author

@armab @PhillypHenning I could use assistance figuring out what this lint error means:
https://github.com/bitovi/bitops/actions/runs/3323834010/jobs/5494675369#step:6:78

@arm4b
Copy link
Member

arm4b commented Oct 25, 2022

black as a code formatter was added in #297 with some documentation here: https://bitovi.github.io/bitops/development/development/#python-style-guide
black for Python is like go fmt for Golang.

@mickmcgrath13 It hints at the code diff here.

You can reformat the code with black locally:

pip3 install black
black scripts/

Or can add it to VSCode so it reformats .py files on save automatically:
https://dev.to/adamlombard/how-to-use-the-black-python-code-formatter-in-vscode-3lo0

Comment on lines 273 to 289
# Tried this approach, and the output was not live output
# tested by adding the following in mounted plugin code
# sleep 3; echo "hello"; sleep 3; echo "hello 2";
# expected to see "hello" and then a pause, and then "hello 2"
# result: no output was shown until after the plugin deploy script finished
# process = subprocess.Popen(
# [
# plugin_deploy_language,
# plugin_deploy_script_path,
# stack_action,
# ],
# stdout=subprocess.PIPE,
# stderr=subprocess.STDOUT
# )

# for combined_output in process.stdout:
# print(combined_output)
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@armab I tried this. see comment

Copy link
Member

@arm4b arm4b Oct 27, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for trying it.

Screen.Recording.2022-10-27.at.14.54.47.mov

I will check what's missing end to end when applying this draft to our script.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this makes me think that it might be a docker or OS (base docker image) problem 🤔

Copy link
Member

@arm4b arm4b Oct 27, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@mickmcgrath13 Tried editing the original Docker container inline with that version of deploy_plugins.py and the live output worked, outputting terraform results line-by-line as we get it.

Screen.Recording.2022-10-27.at.19.44.40.mov

Make sure there's no cache involved. Worst case, we can build the temporary testing Docker images later to try it again.

@mickmcgrath13
Copy link
Contributor Author

we tried this in the same container I'm using:

import subprocess
command = ["ping", "-c", "3", "1.1"]
process = subprocess.Popen(command, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, universal_newlines=True)
for combined_output in process.stdout:
    print(combined_output)

Did this by exec'ing into the container, running python3 interactive terminal, and it worked as expected.

@mickmcgrath13
Copy link
Contributor Author

adding -e PYTHONUNBUFFERED=1 \ to the bitops container call seemed to help

@arm4b
Copy link
Member

arm4b commented Oct 27, 2022

Nice! Looks like PYTHONUNBUFFERED=1 is another ENV var to hardcode in the base Dockerfile as an addition to

ENV DEBIAN_FRONTEND=noninteractive

@arm4b arm4b added this to the v2.2.0 milestone Oct 31, 2022
@arm4b arm4b changed the title closes #320 - real time output for tool deploy scripts Real time output for tool deploy scripts Oct 31, 2022
@PhillypHenning PhillypHenning merged commit 037d8e2 into main Nov 2, 2022
@PhillypHenning PhillypHenning deleted the 320-realtime-deploy-output branch November 2, 2022 19:39
PhillypHenning added a commit that referenced this pull request Nov 2, 2022
@PhillypHenning PhillypHenning restored the 320-realtime-deploy-output branch November 2, 2022 20:32
@PhillypHenning PhillypHenning deleted the 320-realtime-deploy-output branch November 2, 2022 21:27
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Command output is buffered until finishing the whole execution
3 participants