Skip to content

feat: add multi-format output support#1094

Merged
janisz merged 2 commits intostackrox:mainfrom
ugiordan:feat/multi-format-output
Mar 10, 2026
Merged

feat: add multi-format output support#1094
janisz merged 2 commits intostackrox:mainfrom
ugiordan:feat/multi-format-output

Conversation

@ugiordan
Copy link
Copy Markdown
Contributor

Summary

Adds support for generating multiple output formats (JSON, SARIF, plain text) in a single kube-linter run, eliminating the need for duplicate executions.

Before:

# Had to run twice - inefficient and slow
kube-linter lint --config .kube-linter.yaml --format json pod.yaml > output.json
kube-linter lint --config .kube-linter.yaml --format sarif pod.yaml > output.sarif

After:

# Single run - faster and more convenient
kube-linter lint \
  --format sarif --output kube-linter.sarif \
  --format json --output kube-linter.json \
  --config .kube-linter.yaml \
  pod.yaml

Motivation

In CI/CD pipelines, it's common to need multiple output formats:

  • SARIF for GitHub Code Scanning / security tools
  • JSON for custom processing / dashboards
  • Plain text for human-readable logs

Currently, this requires running kube-linter multiple times, which:

  • Doubles (or triples) execution time
  • Increases pipeline complexity
  • Wastes resources parsing the same YAML files repeatedly

Key Features

  • Multiple format/output pairs - Repeat --format and --output flags
  • Backward compatible - Existing single-format commands work unchanged
  • Partial failure handling - Successful outputs preserved even if one fails
  • Clear error messages - Shows which formats succeeded/failed with details
  • Resource safe - Proper file handle cleanup (no defer in loops)
Implementation Details

Architecture

Three new abstractions keep the code clean and testable:

  1. OutputDestination - Abstracts stdout vs file writing

    • Handles file creation and cleanup
    • Provides uniform interface for both cases
  2. FormatOutputPair - Represents format+output combinations

    • Validates format names against allowed list
    • Ensures format/output counts match
    • Detects duplicate output files
  3. Multi-format output loop - Processes each format sequentially

    • Explicit resource cleanup (fixed defer-in-loop anti-pattern)
    • Continues on individual failures (partial success)
    • Aggregates errors with clear context

Resource Management

Critical fix: The implementation explicitly closes files immediately after writing instead of using defer in a loop, preventing file descriptor exhaustion with many outputs.

// ✅ Correct - explicit close
dest, err := NewOutputDestination(pair.Output)
writeErr := formatter(dest.Writer, result)
closeErr := dest.Close()  // Immediate cleanup

vs.

// ❌ Wrong - defer in loop
for _, pair := range pairs {
    dest, err := NewOutputDestination(pair.Output)
    defer dest.Close()  // All defers accumulate!
}
Testing

Test Coverage

Unit Tests (9 test cases):

  • Format-output pairing validation
  • Format validation against allowed list
  • Count mismatch detection
  • Duplicate file detection
  • OutputDestination creation and cleanup

Integration Tests (3 comprehensive scenarios):

  1. TestCommand_MultiFormatOutput - End-to-end multi-format writing
  2. TestCommand_MultiFormatResourceCleanup - Stress test with 10 files
  3. TestCommand_PartialFailureHandling - Verifies partial success behavior

All tests passing:

PASS: TestCommand_InvalidResources
PASS: TestCommand_MultiFormatOutput
PASS: TestCommand_MultiFormatResourceCleanup
PASS: TestCommand_PartialFailureHandling
PASS: TestValidateAndPairFormatsOutputs (9 sub-tests)
PASS: TestNewOutputDestination
PASS: TestOutputDestination_Close

Manual Testing

# Test 1: Multiple formats to files
kube-linter lint \
  --format json --output out.json \
  --format sarif --output out.sarif \
  testdata/valid-pod.yaml

# Test 2: Error handling - mismatched counts
kube-linter lint \
  --format json --format sarif \
  --output out.json \
  testdata/valid-pod.yaml
# Expected: Error about format/output mismatch

# Test 3: Backward compatibility
kube-linter lint --format json testdata/valid-pod.yaml
# Expected: JSON to stdout (existing behavior)
Examples

Basic Usage

Single format to file:

kube-linter lint --format sarif --output results.sarif pod.yaml

Multiple formats to files:

kube-linter lint \
  --format sarif --output kube-linter.sarif \
  --format json --output kube-linter.json \
  --format plain --output kube-linter.txt \
  pod.yaml

CI/CD Integration

GitHub Actions example:

- name: Run KubeLinter
  run: |
    kube-linter lint \
      --format sarif --output kube-linter.sarif \
      --format json --output kube-linter.json \
      manifests/

- name: Upload SARIF to GitHub
  uses: github/codeql-action/upload-sarif@v2
  with:
    sarif_file: kube-linter.sarif

- name: Process JSON results
  run: |
    jq '.Checks | length' kube-linter.json

Error Handling

If one format fails, successful outputs are preserved:

$ kube-linter lint \
  --format json --output /tmp/valid.json \
  --format sarif --output /nonexistent/invalid.sarif \
  pod.yaml

Wrote json output to /tmp/valid.json
Error: failed to write 1 of 2 output format(s) (1 succeeded):
  1. failed to create output destination for sarif: failed to create output file "/nonexistent/invalid.sarif": no such file or directory

Backward Compatibility

All existing commands continue to work exactly as before:

  • ✅ Default format is still plain
  • ✅ Single --format to stdout works unchanged
  • ✅ All existing tests pass without modification

Documentation

Updated documentation with examples:

  • README.md - Quick examples of multi-format usage
  • docs/using-kubelinter.md - Detailed guide with CI/CD examples
  • docs/plans/2026-01-27-multi-format-output-review.md - Design review

Checklist

  • Code follows project style guidelines
  • All tests pass (go test ./pkg/command/lint/...)
  • Documentation updated
  • Backward compatibility maintained
  • Resource cleanup tested (stress test with 10 files)
  • Error handling tested (partial failure scenarios)

Notes

This PR is not linked to a specific issue. The feature addresses a common workflow inefficiency (running kube-linter multiple times for different output formats) and maintains full backward compatibility. Happy to discuss or adjust the approach based on maintainer feedback!

@ugiordan ugiordan requested a review from rhybrillou as a code owner January 27, 2026 14:42
@codecov
Copy link
Copy Markdown

codecov Bot commented Jan 27, 2026

Codecov Report

❌ Patch coverage is 88.63636% with 10 lines in your changes missing coverage. Please review.
✅ Project coverage is 30.71%. Comparing base (dbd7529) to head (8d1c2ef).
⚠️ Report is 256 commits behind head on main.

Files with missing lines Patch % Lines
pkg/command/lint/command.go 76.19% 7 Missing and 3 partials ⚠️
Additional details and impacted files
@@             Coverage Diff             @@
##             main    #1094       +/-   ##
===========================================
- Coverage   62.36%   30.71%   -31.65%     
===========================================
  Files         197      239       +42     
  Lines        4854     6534     +1680     
===========================================
- Hits         3027     2007     -1020     
- Misses       1439     4351     +2912     
+ Partials      388      176      -212     
Flag Coverage Δ
unit 30.71% <88.63%> (-31.65%) ⬇️

Flags with carried forward coverage won't be shown. Click here to find out more.

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

@ugiordan ugiordan force-pushed the feat/multi-format-output branch 2 times, most recently from 6eb491f to d144e2b Compare January 27, 2026 16:38
Comment thread docs/plans/2026-01-27-multi-format-output-review.md Outdated
Comment thread docs/using-kubelinter.md Outdated
deployments/
```

**Example 2: All formats to stdout (less common)**
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

I think we should prevent that, what's the purpose of mixing multiple format in a single stream?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

You are absolutely right. I'll add validation to reject this case.

The fix will require users to either:

  • Use a single format to stdout: --format json pod.yaml
  • Or specify explicit output files: --format json --output a.json --format sarif --output b.sarif pod.yaml

WDYT?

"io"
"os"
"path/filepath"
"testing"
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

In other tests we are using testify for assertions

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Let me use the same approach.

Copy link
Copy Markdown
Collaborator

@janisz janisz left a comment

Choose a reason for hiding this comment

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

It needs some tune up to follow our current testing standards e.g. use testify, add e2e bat test

I think the idea is good and I share the sentiment that sometimes in CI it will be good to have human redable output + junit for machines.

@ugiordan ugiordan force-pushed the feat/multi-format-output branch from d144e2b to a41d70c Compare February 6, 2026 16:08
Enable generation of multiple output formats (JSON, SARIF, plain text) in a single
kube-linter run, eliminating duplicate executions.

Usage:
  kube-linter lint \
    --format sarif --output kube-linter.sarif \
    --format json --output kube-linter.json \
    pod.yaml

Key Features:
- Multiple --format and --output flag pairs supported
- Backward compatible: single format to stdout still works
- Partial failure handling: successful outputs preserved on error
- Clear error messages showing success/failure counts

Implementation:
- OutputDestination abstraction for stdout vs file writing
- FormatOutputPair for format-output pairing and validation
- Explicit resource cleanup (no defer in loops)
- Comprehensive integration tests for multi-format scenarios
- 100% test coverage on all new code

All linter checks and tests pass with full coverage.
@ugiordan ugiordan force-pushed the feat/multi-format-output branch from a41d70c to 9a289af Compare February 6, 2026 16:41
Signed-off-by: Tomasz Janiszewski <tomek@redhat.com>
@janisz janisz merged commit a057528 into stackrox:main Mar 10, 2026
6 of 7 checks passed
@janisz
Copy link
Copy Markdown
Collaborator

janisz commented Mar 10, 2026

@ugiordan I'm sorry it took so long 😅 To not make it longer I added bats myself

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