Skip to content

Conversation

lzap
Copy link
Contributor

@lzap lzap commented Sep 23, 2025

There is a common pattern in the progress bar code to shorten strings to fit into terminal width. This commit introduces a new utility function for that. It also introduces a fallback terminal width of 80 columns in case the terminal size cannot be determined.

Added a test for the function and made sure it never returns string longer than the specified length. That was not the case for the previous implementation.

Additionally, the progress bar implementation was updated to dynamically detect the terminal width and adjust message lengths accordingly. This ensures that progress messages are fully visible without being truncated, enhancing user experience during long-running operations.

There is few characters left for the spinner and padding, so we subtract 10 from the terminal width when setting message lengths.

A new dependency was introduced, but it was already an indirect one.

Also, turns out the container test does not actually run with any terminal size variables, so I added a fixed terminal size to the podman run command.


Split out of: #303

@lzap lzap requested a review from a team as a code owner September 23, 2025 15:30
@lzap lzap requested review from mvo5, thozza and supakeen and removed request for a team September 23, 2025 15:30
@supakeen
Copy link
Member

This will need some (manual) testing and verification since we run in many different places where there might, or might not be any terminal attached; or the terminal might misreport its width, etc.

Thank you for splitting this off ❤️.

bcl
bcl previously approved these changes Sep 24, 2025
Copy link
Contributor

@bcl bcl left a comment

Choose a reason for hiding this comment

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

Seems to work ok running it locally. But I also don't see anything wrong, so I'm mostly saying it didn't obviously break things :)

Copy link
Collaborator

@mvo5 mvo5 left a comment

Choose a reason for hiding this comment

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

Thank you! This looks nice, some idea inline for your consideration but I think we could merge as is (but I think its worth thinking about the inline comments)

// newlines with spaces. If the string is longer than length, it appends
// a unicode ellipsis character. If length is 0, it returns the unmodified
// string.
func ShortenString(msg string, length int) string {
Copy link
Collaborator

Choose a reason for hiding this comment

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

(nitpick) I'm not sure we should move it out, the side-effect of \n->" " for a general util is a bit weird.

If we move it out, maybe into strutil to not have a kitchen-sink util? I think you mention that you generally preferred it this way in another PR. And maybe strutil.ShortenAndNormalize() or something (this one is a bit long) to indicate that there is this other side-effect

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I am a bit thorn on this one, so I am going for a hybrid approach.

  • Modified the utility function to be generic, no \n replacement.
  • Added a tiny function in progress.go which utilizes this function plus replaces newlines.

Copy link
Collaborator

Choose a reason for hiding this comment

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

No appetite for strutil :) ? Its fine, just wondering.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

If you do not like it in util i can move it back to progress. I do not like util or XXXutil at all, package name should represent what it does. In this case I would consider strings if you ask me.

Copy link
Collaborator

Choose a reason for hiding this comment

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

Its fine as is, thanks for the tweaks about the \n

@lzap
Copy link
Contributor Author

lzap commented Sep 24, 2025

Locally everything works, so not sure what is wrong on the CI/CD. So I am trying out something crazy. I updated the progress integration test to use asciinema utility that will record both sessions into a "cast" file. I also updated the GHA so the recordings are archived as artifacts. These are in fact JSON Seq files and they can be played via asciinema utility (in Fedora, Ubuntu etc) or pasted (they are relatively small) via https://asciinema.org online tool for viewing.

@lzap lzap force-pushed the term-width branch 2 times, most recently from f79b8f5 to d8006e7 Compare September 24, 2025 12:43
@supakeen
Copy link
Member

supakeen commented Sep 24, 2025

Locally everything works, so not sure what is wrong on the CI/CD. So I am trying out something crazy. I updated the progress integration test to use asciinema utility that will record both sessions into a "cast" file. I also updated the GHA so the recordings are archived as artifacts. These are in fact JSON Seq files and they can be played via asciinema utility (in Fedora, Ubuntu etc) or pasted (they are relatively small) via https://asciinema.org online tool for viewing.

Using asciicinema runs a small risk of it setting up a pty and the error disappearing. It is possible that in CI (and other cases) that there's no controlling pty at all (in normal cases) and maybe no tty on the receiving end either.

What was the exact error you were seeing?

@lzap
Copy link
Contributor Author

lzap commented Sep 24, 2025

What was the exact error you were seeing?

It was this job: https://github.com/osbuild/image-builder-cli/actions/runs/17951169988/job/51050562633

@lzap
Copy link
Contributor Author

lzap commented Sep 24, 2025

Ha I wonder if --terminal-size 80x24 option broke podman which returned 125. Yeah that is it, so it was just this option is probably unsupported.

So you do not like asciinama then? Since terminal size cannot be set via podman, it actually turns useful now.

@supakeen
Copy link
Member

Ha I wonder if --terminal-size 80x24 option broke podman which returned 125. Yeah that is it, so it was just this option is probably unsupported.

So you do not like asciinama then? Since terminal size cannot be set via podman, it actually turns useful now.

It's not really about asciinema. More that it might influence how terminal detection and things work since it might spawn (another) pty.

@lzap
Copy link
Contributor Author

lzap commented Sep 24, 2025

It's not really about asciinema. More that it might influence how terminal detection and things work since it might spawn (another) pty.

But that is the goal, podman does not allow too much configuration of pty. Asciinema allows setting the size, other than that it does not change anything. I do not see any problem here, explain please.

Here is the recording which I downloaded from the latest (successful) run:

https://asciinema.org/a/DImVCmlxcIsyWSY2iTdwRPQyD

@supakeen
Copy link
Member

It's not really about asciinema. More that it might influence how terminal detection and things work since it might spawn (another) pty.

But that is the goal, podman does not allow too much configuration of tty. Asciinema allows setting the size, other than that it does not change anything.

Here is the recording which I downloaded from the latest (successful) run:

This code should work in environments where there isn't a size (or that don't report a size) right? It can fall back to non-fancy (verbose) output in those cases.

@lzap
Copy link
Contributor Author

lzap commented Sep 24, 2025

This code should work in environments where there isn't a size (or that don't report a size) right? It can fall back to non-fancy (verbose) output in those cases.

Absolutely, I can extend the test and add with and without cases so both are tested in non-tty non-interactive environments. In fact, not only verbose should work, I also think progress should not break the terminal. Ideally it should detect that and do nothing. I think this test is relatively fast compared to others, locally it takes few seconds the only slow part is building the fake container.

return msg[:60] + "..."
func getTerminalWidth() int {
width, _, err := term.GetSize(int(os.Stdout.Fd()))
if err != nil {
Copy link
Member

@supakeen supakeen Sep 24, 2025

Choose a reason for hiding this comment

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

It might be safer to assume that the terminal is in fact broken if it's not responding correctly to TIOCGWINSZ and instead downgrade to a non-interactive terminal. That's not really something we can do when we get to this point of the code so:

If progress is set to auto we currently only check if isatty returns true on the fd; we should probably call GetSize there as well to see if that terminal is behaving correctly.

}

func shortenString(msg string) string {
width := getTerminalWidth()
Copy link
Member

@supakeen supakeen Sep 24, 2025

Choose a reason for hiding this comment

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

Note that doing this repeatedly can cause bogus reads; some terminals can return weird values during resizing (very large, very small). It might be better to keep some state and listen to SIGWINCH instead though that can also be racy I guess.

Feel free to ignore this one since we always erase the entire line and don't do any moves relative to the line length or such so it's unlikely to cause us any issues.

@supakeen
Copy link
Member

supakeen commented Sep 24, 2025

I put some small comments from my experiences of doing things with terminals, I need to do a deeper look and rack my brain for other issues (I distinctly remember cases where nested pty's are used from another project).

Note that these things are all very 'maybe' and very environment dependent. I'm just slightly worried that suddenly image-builder breaks in some environment. I don't think the odds of that happening are high since we always pass --progress=verbose in Koji and don't really do anything relative to the window sizes we read but only clear a line and write a new line.

It might be good to be a bit defensive and then I'm happy enough to merge since I'm probably being a bit too difficult on this.

@lzap
Copy link
Contributor Author

lzap commented Sep 24, 2025

It might be good to be a bit defensive and then I'm happy enough to merge since I'm probably being a bit too difficult on this.

You are presenting great points, don't worry.

I drafted everything you mentioned, the process now listens to the UNIX signal and updates w/h accordingly. I should probably move this into the Progress instance and introduce some proper Close() method that can be deferred from main tho, opinions?

Moreover, the initial check in progress.New also checks for size and if that is not set, it falls back to simple mode. Also updated the test, it tests with and without pty. I had to introduce a dataclass because of the pesky linter tho.

Also I found a bug in tests - those mocked functions had defer in a loop, therefore they were actually overwriting the global value and never returning the original value. Fixed with t.Run.

@supakeen
Copy link
Member

@lzap Could you fix the conflict here, I'm about ready to ack it but otherwise I have to do it a few times.

There is a common pattern in the progress bar code to shorten
strings to fit into terminal width. This commit introduces a new
utility function for that. It also introduces a fallback terminal
width of 80 columns in case the terminal size cannot be determined.

Added a test for the function and made sure it never returns string
longer than the specified length. That was not the case for the previous
implementation.

Additionally, the progress bar implementation was updated to dynamically
detect the terminal width and adjust message lengths accordingly. This
ensures that progress messages are fully visible without being
truncated, enhancing user experience during long-running operations.

There is few characters left for the spinner and padding, so we subtract
10 from the terminal width when setting message lengths.

A new dependency was introduced, but it was already an indirect one.

Testing is difficult, so this change also adds asciinema recordings
of the progress bar in action. The recordings are uploaded as artifacts
of the CI run. The test verifies that the expected progress messages
are present in the output.
@lzap
Copy link
Contributor Author

lzap commented Sep 30, 2025

Could you fix the conflict here

Of course, thanks for the heads-up. Rebased.

output = subprocess.check_output(podman_run + [
"-t",

podman_command = [ podman_run +
Copy link
Member

Choose a reason for hiding this comment

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

Needs to be podman_run + [ instead of [ podman_run +.

@supakeen
Copy link
Member

One comment (introduced by the rebase) and linters need appeasement.

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.

4 participants