From 7d732a5702d621dc12afea3daa3a951c1f2810d4 Mon Sep 17 00:00:00 2001 From: shivay Date: Thu, 4 Jun 2026 15:40:35 +0530 Subject: [PATCH 1/3] fix: Added check for lint cmd to handle empty or only comment files. --- lint/lint.go | 5 +++ tests/integration/lint_test.go | 32 +++++++++++++++++++ .../lint/001-simple-lint/comments-only.yaml | 4 +++ .../testdata/lint/001-simple-lint/empty.yaml | 1 + 4 files changed, 42 insertions(+) create mode 100644 tests/integration/testdata/lint/001-simple-lint/comments-only.yaml create mode 100644 tests/integration/testdata/lint/001-simple-lint/empty.yaml diff --git a/lint/lint.go b/lint/lint.go index 65d846948..5cdc858a5 100644 --- a/lint/lint.go +++ b/lint/lint.go @@ -7,6 +7,7 @@ import ( "github.com/daveshanley/vacuum/motor" "github.com/daveshanley/vacuum/rulesets" "github.com/kong/go-apiops/filebasics" + "sigs.k8s.io/yaml" ) const plainTextFormat = "plain" @@ -81,6 +82,10 @@ func WithContent( if err != nil { return nil, err } + var parsedContent interface{} + if err := yaml.Unmarshal(stateFileBytes, &parsedContent); err == nil && parsedContent == nil { + return nil, fmt.Errorf("state file is empty or contains only comments") + } ruleSetResults := motor.ApplyRulesToRuleSet(&motor.RuleSetExecution{ RuleSet: customRuleSet, Spec: stateFileBytes, diff --git a/tests/integration/lint_test.go b/tests/integration/lint_test.go index 0951b15bc..52f46f9a6 100644 --- a/tests/integration/lint_test.go +++ b/tests/integration/lint_test.go @@ -163,3 +163,35 @@ func Test_LintStructured(t *testing.T) { }) } } + +func Test_LintEmptyOrCommentsOnly(t *testing.T) { + tests := []struct { + name string + stateFile string + rulesetFile string + expectError string + }{ + { + name: "comments only file", + stateFile: "testdata/lint/001-simple-lint/comments-only.yaml", + rulesetFile: "testdata/lint/001-simple-lint/ruleset.yaml", + expectError: "state file is empty or contains only comments", + }, + { + name: "empty file", + stateFile: "testdata/lint/001-simple-lint/empty.yaml", + rulesetFile: "testdata/lint/001-simple-lint/ruleset.yaml", + expectError: "state file is empty or contains only comments", + }, + } + for _, tc := range tests { + t.Run(tc.name, func(t *testing.T) { + _, err := fileLint( + "-s", tc.stateFile, + tc.rulesetFile, + ) + require.Error(t, err) + assert.Contains(t, err.Error(), tc.expectError) + }) + } +} diff --git a/tests/integration/testdata/lint/001-simple-lint/comments-only.yaml b/tests/integration/testdata/lint/001-simple-lint/comments-only.yaml new file mode 100644 index 000000000..b80329eac --- /dev/null +++ b/tests/integration/testdata/lint/001-simple-lint/comments-only.yaml @@ -0,0 +1,4 @@ +# This is a commented YAML file +# _format_version: "3.0" +# services: +# - name: my-service \ No newline at end of file diff --git a/tests/integration/testdata/lint/001-simple-lint/empty.yaml b/tests/integration/testdata/lint/001-simple-lint/empty.yaml new file mode 100644 index 000000000..8b1378917 --- /dev/null +++ b/tests/integration/testdata/lint/001-simple-lint/empty.yaml @@ -0,0 +1 @@ + From b6475747504b26e8226f57ddbabe12ed82db26ca Mon Sep 17 00:00:00 2001 From: shivay Date: Fri, 5 Jun 2026 12:53:13 +0530 Subject: [PATCH 2/3] fix: Handling file linting with warning message instead of error. --- lint/lint.go | 10 +++++++++- tests/integration/lint_test.go | 32 ++++++++++++++++++++++++++------ tests/integration/test_utils.go | 30 ++++++++++++++++++++++++++++++ 3 files changed, 65 insertions(+), 7 deletions(-) diff --git a/lint/lint.go b/lint/lint.go index 5cdc858a5..84955bdf8 100644 --- a/lint/lint.go +++ b/lint/lint.go @@ -2,6 +2,7 @@ package lint import ( "fmt" + "os" "strings" "github.com/daveshanley/vacuum/motor" @@ -84,7 +85,14 @@ func WithContent( } var parsedContent interface{} if err := yaml.Unmarshal(stateFileBytes, &parsedContent); err == nil && parsedContent == nil { - return nil, fmt.Errorf("state file is empty or contains only comments") + // Return empty results with a warning instead of an error + // to maintain backward compatibility with older deck versions + _, _ = fmt.Fprintln(os.Stderr, "Warning: state file is empty or contains only comments, skipping linting") + return map[string]interface{}{ + "total_count": 0, + "fail_count": 0, + "results": []Result{}, + }, nil } ruleSetResults := motor.ApplyRulesToRuleSet(&motor.RuleSetExecution{ RuleSet: customRuleSet, diff --git a/tests/integration/lint_test.go b/tests/integration/lint_test.go index 52f46f9a6..b60fbad96 100644 --- a/tests/integration/lint_test.go +++ b/tests/integration/lint_test.go @@ -169,29 +169,49 @@ func Test_LintEmptyOrCommentsOnly(t *testing.T) { name string stateFile string rulesetFile string - expectError string }{ { name: "comments only file", stateFile: "testdata/lint/001-simple-lint/comments-only.yaml", rulesetFile: "testdata/lint/001-simple-lint/ruleset.yaml", - expectError: "state file is empty or contains only comments", }, { name: "empty file", stateFile: "testdata/lint/001-simple-lint/empty.yaml", rulesetFile: "testdata/lint/001-simple-lint/ruleset.yaml", - expectError: "state file is empty or contains only comments", }, } for _, tc := range tests { t.Run(tc.name, func(t *testing.T) { - _, err := fileLint( + stdout, stderr, err := fileLintWithStderr( "-s", tc.stateFile, tc.rulesetFile, ) - require.Error(t, err) - assert.Contains(t, err.Error(), tc.expectError) + require.NoError(t, err) + assert.Contains(t, stderr, "Warning: state file is empty or contains only comments, skipping linting") + assert.Empty(t, stdout) + }) + + t.Run(tc.name+" json output", func(t *testing.T) { + stdout, stderr, err := fileLintWithStderr( + "-s", tc.stateFile, + "--format", "json", + tc.rulesetFile, + ) + require.NoError(t, err) + + // Verify warning message appears in stderr (not stdout) + assert.Contains(t, stderr, "Warning: state file is empty or contains only comments, skipping linting") + + // Verify JSON output is valid and contains expected structure + var output lintErrors + err = json.Unmarshal([]byte(stdout), &output) + require.NoError(t, err, "JSON output should be valid") + + // Verify empty results + assert.Equal(t, 0, output.TotalCount) + assert.Equal(t, 0, output.FailCount) + assert.Empty(t, output.Results) }) } } diff --git a/tests/integration/test_utils.go b/tests/integration/test_utils.go index 2649f66ef..0d071c3f6 100644 --- a/tests/integration/test_utils.go +++ b/tests/integration/test_utils.go @@ -468,6 +468,36 @@ func fileLint(opts ...string) (string, error) { return stripansi.Strip(string(out)), cmdErr } +func fileLintWithStderr(opts ...string) (stdout string, stderr string, err error) { + deckCmd := cmd.NewRootCmd() + args := []string{"file", "lint"} + if len(opts) > 0 { + args = append(args, opts...) + } + deckCmd.SetArgs(args) + + // capture stdout + rescueStdout := os.Stdout + rOut, wOut, _ := os.Pipe() + os.Stdout = wOut + + // capture stderr + rescueStderr := os.Stderr + rErr, wErr, _ := os.Pipe() + os.Stderr = wErr + + cmdErr := deckCmd.ExecuteContext(context.Background()) + + wOut.Close() + wErr.Close() + outBytes, _ := io.ReadAll(rOut) + errBytes, _ := io.ReadAll(rErr) + os.Stdout = rescueStdout + os.Stderr = rescueStderr + + return stripansi.Strip(string(outBytes)), stripansi.Strip(string(errBytes)), cmdErr +} + func fileConvert(opts ...string) (string, error) { deckCmd := cmd.NewRootCmd() args := []string{"file", "convert"} From 0f6701380ec2365e0b87a89917abf057dd19b2b9 Mon Sep 17 00:00:00 2001 From: shivay Date: Fri, 5 Jun 2026 14:05:02 +0530 Subject: [PATCH 3/3] chore: handled printing as per review comment. --- lint/lint.go | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/lint/lint.go b/lint/lint.go index 84955bdf8..fc1d75f74 100644 --- a/lint/lint.go +++ b/lint/lint.go @@ -8,6 +8,7 @@ import ( "github.com/daveshanley/vacuum/motor" "github.com/daveshanley/vacuum/rulesets" "github.com/kong/go-apiops/filebasics" + "github.com/kong/go-database-reconciler/pkg/cprint" "sigs.k8s.io/yaml" ) @@ -87,7 +88,7 @@ func WithContent( if err := yaml.Unmarshal(stateFileBytes, &parsedContent); err == nil && parsedContent == nil { // Return empty results with a warning instead of an error // to maintain backward compatibility with older deck versions - _, _ = fmt.Fprintln(os.Stderr, "Warning: state file is empty or contains only comments, skipping linting") + cprint.UpdatePrintlnStdErr(os.Stderr, "Warning: state file is empty or contains only comments, skipping linting") return map[string]interface{}{ "total_count": 0, "fail_count": 0,