Skip to content

Print error messages to stderr, not stdout #786

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

Merged
merged 2 commits into from
May 27, 2025

Conversation

icyflame
Copy link
Contributor

Summary

Messages that help the reader make sense of the output are printed to stdout and not to stderr right now. When using the structured output options (such as --output json), this causes the output to not be valid JSON. It requires the user to filter out the non-JSON lines from the output and then pipe the output to programs that expect valid JSON input (such as jq)

We are using the Helm diff plugin when deploying to our production environments, and we need to filter these lines out from the output. The logic that we have implemented is fragile and we would have to keep it in sync with changes in the Helm Diff plugin itself.

A better way to do this would be to print the messages for the reader to standard error, and keep the standard output purely output that can be piped into another program without any changes. I believe that this is a common pattern for tools that create structured output.

This PR updates the messages that are printed in the helm-diff upgrade command. I have included the before and after outputs related to this change. One concern with this change is if plugin users are using this line in their scripts to detect something (for example: the line Release was not present may be used to detect that a new release will be installed into the cluster) If this change is accepted, it would be good to add an entry in the changelog or release message about this change and how it might affect plugin users.

In order to output the commit that was built in the binary, I used the following command:

$ go build -v -o bin/diff -ldflags="-X github.com/databus23/helm-diff/v3/cmd.Version=$(git rev-parse HEAD)"

Before

$ helm diff version
05264b7b2ad6901d26899d38e25193a013dfa0f9

$ HELM_DIFF_THREE_WAY_MERGE="true" HELM_DIFF_NORMALIZE_MANIFESTS="true" helm diff upgrade --output json --allow-unreleased wait-for-duration ./ | jq .
jq: parse error: Invalid numeric literal at line 1, column 8
Error: plugin "diff" exited with error

After

Once the messages are moved to stderr, the output can be parsed properly by jq. It is also
possible to pipe these messages to /dev/null, if we want to see only the structured output.

$ helm diff version
447393a3f61b0c7747fa284ef73de91d0ac216bf

$ HELM_DIFF_THREE_WAY_MERGE="true" HELM_DIFF_NORMALIZE_MANIFESTS="true" helm diff upgrade --output json --allow-unreleased wait-for-duration ./ | jq .
Enabled three way merge via the envvar
Enabled normalize manifests via the envvar
********************

                Release was not present in Helm.  Diff will show entire contents as new.

********************
[
  {
    "api": "batch",
    "kind": "Job",
    "namespace": "default",
    "name": "sample-job-testing-blocking",
    "change": "ADD"
  }
]

$ HELM_DIFF_THREE_WAY_MERGE="true" HELM_DIFF_NORMALIZE_MANIFESTS="true" helm diff upgrade --output json --allow-unreleased wait-for-duration ./ 2>/dev/null | jq .
[
  {
    "api": "batch",
    "kind": "Job",
    "namespace": "default",
    "name": "sample-job-testing-blocking",
    "change": "ADD"
  }
]

icyflame added 2 commits May 23, 2025 12:02
Messages that help the reader make sense of the output are printed to stdout and not to stderr right
now. When using the structured output options (such as `--output json`), this causes the output to
not be valid JSON. It requires the user to filter out the non-JSON lines from the output and then
pipe the output to programs that expect valid JSON input (such as [`jq`])

We are using the Helm diff plugin when deploying to our production environments, and we need to
[filter] these lines out from the output. The logic that we have implemented is fragile and we would
have to keep it in sync with changes in the Helm Diff plugin itself.

A better way to do this would be to print the messages for the reader to standard error, and keep
the standard output purely output that can be piped into another program without any changes. I
believe that this is a common pattern for tools that create structured output.

This PR updates the messages that are printed in the helm-diff upgrade command. I have included the
before and after outputs related to this change. One concern with this change is if plugin users are
using this line in their scripts to detect something (for example: the line `Release was not
present` may be used to detect that a new release will be installed into the cluster) If this change
is accepted, it would be good to add an entry in the changelog or release message about this change
and how it might affect plugin users.

In order to output the commit that was built in the binary, I used the following command:

```sh
$ go build -v -o bin/diff -ldflags="-X github.com/databus23/helm-diff/v3/cmd.Version=$(git rev-parse HEAD)"
```

\### Before

```sh
$ helm diff version
05264b7

$ HELM_DIFF_THREE_WAY_MERGE="true" HELM_DIFF_NORMALIZE_MANIFESTS="true" helm diff upgrade --output json --allow-unreleased wait-for-duration ./ | jq .
jq: parse error: Invalid numeric literal at line 1, column 8
Error: plugin "diff" exited with error
```

\### After

Once the messages are moved to `stderr`, the output can be parsed properly by `jq`. It is also
possible to pipe these messages to `/dev/null`, if we want to see only the structured output.

```sh
$ helm diff version
447393a3f61b0c7747fa284ef73de91d0ac216bf

$ HELM_DIFF_THREE_WAY_MERGE="true" HELM_DIFF_NORMALIZE_MANIFESTS="true" helm diff upgrade --output json --allow-unreleased wait-for-duration ./ | jq .
Enabled three way merge via the envvar
Enabled normalize manifests via the envvar
********************

                Release was not present in Helm.  Diff will show entire contents as new.

********************
[
  {
    "api": "batch",
    "kind": "Job",
    "namespace": "default",
    "name": "sample-job-testing-blocking",
    "change": "ADD"
  }
]

$ HELM_DIFF_THREE_WAY_MERGE="true" HELM_DIFF_NORMALIZE_MANIFESTS="true" helm diff upgrade --output json --allow-unreleased wait-for-duration ./ 2>/dev/null | jq .
[
  {
    "api": "batch",
    "kind": "Job",
    "namespace": "default",
    "name": "sample-job-testing-blocking",
    "change": "ADD"
  }
]
```

[`jq`]: https://jqlang.org/
[filter]: https://gitlab.com/gitlab-com/gl-infra/k8s-workloads/gitlab-com/-/blob/18aa8f083d5e45bc97f7745abda6fb50e45eab6a/bin/k-ctl
@icyflame icyflame marked this pull request as ready for review May 23, 2025 03:05
@yxxhero yxxhero requested a review from Copilot May 24, 2025 07:14
Copy link

@Copilot Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull Request Overview

This PR updates the helm diff upgrade command to print informational banners to stderr instead of stdout, ensuring that structured JSON output remains valid, and also corrects minor typos in the contribution guide.

  • Redirects several fmt.Println/fmt.Printf calls to fmt.Fprintf(os.Stderr, …) in cmd/upgrade.go
  • Fixes spelling and punctuation in CONTRIBUTING.md

Reviewed Changes

Copilot reviewed 2 out of 2 changed files in this pull request and generated 1 comment.

File Description
cmd/upgrade.go Redirected upgrade command messages (three-way merge, normalize manifests, release banners) to stderr
CONTRIBUTING.md Corrected “pul” → “pull” and replaced trailing dash with a period in list items
Comments suppressed due to low confidence (3)

cmd/upgrade.go:184

  • Consider adding or updating unit tests to verify that these messages are printed to stderr instead of stdout, preventing future regressions.
fmt.Fprintf(os.Stderr, "Enabled three way merge via the envvar\n")

CONTRIBUTING.md:1

  • You removed the hyphen list marker, which breaks the markdown list formatting; re-add - at the start of this line to preserve it as a bullet point.
Before submitting a pull request, I'd encourage you to test it yourself.

CONTRIBUTING.md:3

  • Similarly, re-add - to maintain this as a list item in the markdown, ensuring consistent formatting in the contribution guide.
To do so, you need to run the plugin indirectly or directly.

Comment on lines +287 to +291
fmt.Fprintf(os.Stderr, "********************\n\n\tRelease was not present in Helm. Diff will show entire contents as new.\n\n********************\n")
newInstall = true
err = nil
} else {
fmt.Printf("********************\n\n\tRelease was not present in Helm. Include the `--allow-unreleased` to perform diff without exiting in error.\n\n********************\n")
fmt.Fprintf(os.Stderr, "********************\n\n\tRelease was not present in Helm. Include the `--allow-unreleased` to perform diff without exiting in error.\n\n********************\n")
Copy link
Preview

Copilot AI May 24, 2025

Choose a reason for hiding this comment

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

[nitpick] The multi-line banner string is duplicated; consider extracting it into a constant or helper function to reduce repetition and ease future updates.

Copilot uses AI. Check for mistakes.

@yxxhero yxxhero merged commit 1024c53 into databus23:master May 27, 2025
16 checks passed
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.

2 participants