Skip to content

Commit 6ffc83d

Browse files
Updated linter (#1903)
* moved linter to new branch * reads each yaml file separately when given multiple * split monolith lint file into more reasonably sized files * github action linter fix * lint error codes follow the rest of the codebase's standard
1 parent 21dc4e9 commit 6ffc83d

23 files changed

+2222
-1
lines changed

cmd/preflight/cli/lint.go

Lines changed: 96 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,96 @@
1+
package cli
2+
3+
import (
4+
"fmt"
5+
"os"
6+
7+
"github.com/pkg/errors"
8+
"github.com/replicatedhq/troubleshoot/pkg/constants"
9+
"github.com/replicatedhq/troubleshoot/pkg/lint"
10+
"github.com/replicatedhq/troubleshoot/pkg/types"
11+
"github.com/spf13/cobra"
12+
"github.com/spf13/viper"
13+
)
14+
15+
func LintCmd() *cobra.Command {
16+
cmd := &cobra.Command{
17+
Use: "lint [spec-files...]",
18+
Args: cobra.MinimumNArgs(1),
19+
Short: "Lint v1beta2/v1beta3 preflight specs for syntax and structural errors",
20+
Long: `Lint v1beta2/v1beta3 preflight specs for syntax and structural errors.
21+
22+
This command validates v1beta2/v1beta3 preflight specs and checks for:
23+
- YAML syntax errors
24+
- Missing required fields (apiVersion, kind, metadata, spec)
25+
- Invalid template syntax ({{ .Values.* }})
26+
- Missing analyzers or collectors
27+
- Common structural issues
28+
- Missing docStrings (warning)
29+
30+
Examples:
31+
# Lint a single spec file
32+
preflight lint my-preflight.yaml
33+
34+
# Lint multiple spec files
35+
preflight lint spec1.yaml spec2.yaml spec3.yaml
36+
37+
# Lint with automatic fixes
38+
preflight lint --fix my-preflight.yaml
39+
40+
# Lint and output as JSON for CI/CD integration
41+
preflight lint --format json my-preflight.yaml
42+
43+
Notes:
44+
- v1beta2 does not support templating; template syntax in v1beta2 files will be flagged as errors.
45+
- v1beta3 supports templating and is linted with template-awareness.
46+
47+
Exit codes:
48+
0 - No errors found
49+
2 - Validation errors found`,
50+
PreRun: func(cmd *cobra.Command, args []string) {
51+
viper.BindPFlags(cmd.Flags())
52+
},
53+
RunE: func(cmd *cobra.Command, args []string) error {
54+
v := viper.GetViper()
55+
56+
opts := lint.LintOptions{
57+
FilePaths: args,
58+
Fix: v.GetBool("fix"),
59+
Format: v.GetString("format"),
60+
}
61+
62+
return runLint(opts)
63+
},
64+
}
65+
66+
cmd.Flags().Bool("fix", false, "Automatically fix issues where possible")
67+
cmd.Flags().String("format", "text", "Output format: text or json")
68+
69+
return cmd
70+
}
71+
72+
func runLint(opts lint.LintOptions) error {
73+
// Validate file paths exist
74+
for _, filePath := range opts.FilePaths {
75+
if _, err := os.Stat(filePath); err != nil {
76+
return errors.Wrapf(err, "file not found: %s", filePath)
77+
}
78+
}
79+
80+
// Run linting
81+
results, err := lint.LintFiles(opts)
82+
if err != nil {
83+
return errors.Wrap(err, "failed to lint files")
84+
}
85+
86+
// Format and print results
87+
output := lint.FormatResults(results, opts.Format)
88+
fmt.Print(output)
89+
90+
// Return appropriate exit code
91+
if lint.HasErrors(results) {
92+
return types.NewExitCodeError(constants.EXIT_CODE_SPEC_ISSUES, nil)
93+
}
94+
95+
return nil
96+
}

cmd/preflight/cli/root.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -89,6 +89,7 @@ that a cluster meets the requirements to run an application.`,
8989
cmd.AddCommand(TemplateCmd())
9090
cmd.AddCommand(DocsCmd())
9191
cmd.AddCommand(ConvertCmd())
92+
cmd.AddCommand(LintCmd())
9293

9394
preflight.AddFlags(cmd.PersistentFlags())
9495

cmd/troubleshoot/cli/lint.go

Lines changed: 96 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,96 @@
1+
package cli
2+
3+
import (
4+
"fmt"
5+
"os"
6+
7+
"github.com/pkg/errors"
8+
"github.com/replicatedhq/troubleshoot/pkg/constants"
9+
"github.com/replicatedhq/troubleshoot/pkg/lint"
10+
"github.com/replicatedhq/troubleshoot/pkg/types"
11+
"github.com/spf13/cobra"
12+
"github.com/spf13/viper"
13+
)
14+
15+
func LintCmd() *cobra.Command {
16+
cmd := &cobra.Command{
17+
Use: "lint [spec-files...]",
18+
Args: cobra.MinimumNArgs(1),
19+
Short: "Lint v1beta2/v1beta3 troubleshoot specs for syntax and structural errors",
20+
Long: `Lint v1beta2/v1beta3 troubleshoot specs (both preflight and support-bundle) for syntax and structural errors.
21+
22+
This command validates v1beta2/v1beta3 troubleshoot specs and checks for:
23+
- YAML syntax errors
24+
- Missing required fields (apiVersion, kind, metadata, spec)
25+
- Invalid template syntax ({{ .Values.* }})
26+
- Missing collectors or hostCollectors
27+
- Common structural issues
28+
- Missing docStrings (warning)
29+
30+
Examples:
31+
# Lint a single spec file
32+
support-bundle lint my-spec.yaml
33+
34+
# Lint multiple spec files
35+
support-bundle lint spec1.yaml spec2.yaml spec3.yaml
36+
37+
# Lint with automatic fixes
38+
support-bundle lint --fix my-spec.yaml
39+
40+
# Lint and output as JSON for CI/CD integration
41+
support-bundle lint --format json my-spec.yaml
42+
43+
Notes:
44+
- v1beta2 does not support templating; template syntax in v1beta2 files will be flagged as errors.
45+
- v1beta3 supports templating and is linted with template-awareness.
46+
47+
Exit codes:
48+
0 - No errors found
49+
2 - Validation errors found`,
50+
PreRun: func(cmd *cobra.Command, args []string) {
51+
viper.BindPFlags(cmd.Flags())
52+
},
53+
RunE: func(cmd *cobra.Command, args []string) error {
54+
v := viper.GetViper()
55+
56+
opts := lint.LintOptions{
57+
FilePaths: args,
58+
Fix: v.GetBool("fix"),
59+
Format: v.GetString("format"),
60+
}
61+
62+
return runLint(opts)
63+
},
64+
}
65+
66+
cmd.Flags().Bool("fix", false, "Automatically fix issues where possible")
67+
cmd.Flags().String("format", "text", "Output format: text or json")
68+
69+
return cmd
70+
}
71+
72+
func runLint(opts lint.LintOptions) error {
73+
// Validate file paths exist
74+
for _, filePath := range opts.FilePaths {
75+
if _, err := os.Stat(filePath); err != nil {
76+
return errors.Wrapf(err, "file not found: %s", filePath)
77+
}
78+
}
79+
80+
// Run linting
81+
results, err := lint.LintFiles(opts)
82+
if err != nil {
83+
return errors.Wrap(err, "failed to lint files")
84+
}
85+
86+
// Format and print results
87+
output := lint.FormatResults(results, opts.Format)
88+
fmt.Print(output)
89+
90+
// Return appropriate exit code
91+
if lint.HasErrors(results) {
92+
return types.NewExitCodeError(constants.EXIT_CODE_SPEC_ISSUES, nil)
93+
}
94+
95+
return nil
96+
}

cmd/troubleshoot/cli/root.go

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,10 +5,14 @@ import (
55
"os"
66
"strings"
77

8+
"errors"
9+
810
"github.com/replicatedhq/troubleshoot/cmd/internal/util"
911
"github.com/replicatedhq/troubleshoot/internal/traces"
12+
"github.com/replicatedhq/troubleshoot/pkg/constants"
1013
"github.com/replicatedhq/troubleshoot/pkg/k8sutil"
1114
"github.com/replicatedhq/troubleshoot/pkg/logger"
15+
"github.com/replicatedhq/troubleshoot/pkg/types"
1216
"github.com/replicatedhq/troubleshoot/pkg/updater"
1317
"github.com/spf13/cobra"
1418
"github.com/spf13/viper"
@@ -108,6 +112,7 @@ If no arguments are provided, specs are automatically loaded from the cluster by
108112
cmd.AddCommand(Diff())
109113
cmd.AddCommand(Schedule())
110114
cmd.AddCommand(UploadCmd())
115+
cmd.AddCommand(LintCmd())
111116
cmd.AddCommand(util.VersionCmd())
112117

113118
cmd.Flags().StringSlice("redactors", []string{}, "names of the additional redactors to use")
@@ -166,7 +171,16 @@ If no arguments are provided, specs are automatically loaded from the cluster by
166171
}
167172

168173
func InitAndExecute() {
169-
if err := RootCmd().Execute(); err != nil {
174+
cmd := RootCmd()
175+
if err := cmd.Execute(); err != nil {
176+
var exitErr types.ExitError
177+
if errors.As(err, &exitErr) {
178+
if exitErr.ExitStatus() != constants.EXIT_CODE_FAIL && exitErr.ExitStatus() != constants.EXIT_CODE_WARN {
179+
cmd.PrintErrln("Error:", err.Error())
180+
}
181+
os.Exit(exitErr.ExitStatus())
182+
}
183+
cmd.PrintErrln("Error:", err.Error())
170184
os.Exit(1)
171185
}
172186
}
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
apiVersion: troubleshoot.sh/v1beta3
2+
kind: Preflight
3+
metadata:
4+
name: helm-builtins-example
5+
labels:
6+
release: {{ .Release.Name }}
7+
spec:
8+
analyzers:
9+
- docString: |
10+
Title: Example using Helm builtin objects
11+
Requirement: Demonstrates .Values, .Release, .Chart, etc.
12+
13+
Supported Helm builtin objects:
14+
- .Values.* - User-provided values
15+
- .Release.Name - Release name (default: "preflight")
16+
- .Release.Namespace - Release namespace (default: "default")
17+
- .Release.IsInstall - Whether this is an install (true)
18+
- .Release.IsUpgrade - Whether this is an upgrade (false)
19+
- .Release.Revision - Release revision (1)
20+
- .Chart.Name - Chart name
21+
- .Chart.Version - Chart version
22+
- .Capabilities.KubeVersion - Kubernetes version capabilities
23+
clusterVersion:
24+
checkName: Kubernetes version check in {{ .Release.Namespace }}
25+
outcomes:
26+
- fail:
27+
when: '< {{ .Values.minVersion | default "1.19.0" }}'
28+
message: |
29+
Release {{ .Release.Name }} requires Kubernetes {{ .Values.minVersion | default "1.19.0" }} or later.
30+
Chart: {{ .Chart.Name }}
31+
- pass:
32+
when: '>= {{ .Values.minVersion | default "1.19.0" }}'
33+
message: Kubernetes version is supported for release {{ .Release.Name }}
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
apiVersion: troubleshoot.sh/v1beta3
2+
kind: SupportBundle
3+
metadata:
4+
name: invalid-collectors
5+
spec:
6+
collectors:
7+
# Unknown collector type
8+
- notACollector: {}
9+
# Known collector but missing required fields (e.g., ceph requires namespace)
10+
- ceph: {}
11+
# Field exists but wrong type (should be a list)
12+
hostCollectors: "not-a-list"
13+
analyzers:
14+
# Unknown analyzer type
15+
- notAnAnalyzer: {}
16+
# Known analyzer missing required 'outcomes'
17+
- cephStatus:
18+
namespace: default
19+
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
apiVersion: troubleshoot.sh/v1beta3
2+
kind: Preflight
3+
metadata
4+
name: invalid-yaml
5+
spec:
6+
analyzers:
7+
- clusterVersion:
8+
checkName: Kubernetes version
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
kind: Preflight
2+
metadata:
3+
name: missing-apiversion
4+
spec:
5+
analyzers:
6+
- clusterVersion:
7+
checkName: Kubernetes version
8+
outcomes:
9+
- pass:
10+
when: '>= 1.19.0'
11+
message: Kubernetes version is supported
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
apiVersion: troubleshoot.sh/v1beta3
2+
kind: Preflight
3+
spec:
4+
analyzers:
5+
- clusterVersion:
6+
checkName: Kubernetes version
7+
outcomes:
8+
- pass:
9+
when: '>= 1.19.0'
10+
message: Kubernetes version is supported
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
apiVersion: troubleshoot.sh/v1beta3
2+
kind: Preflight
3+
metadata:
4+
name: no-analyzers
5+
spec:
6+
collectors:
7+
- clusterInfo: {}

0 commit comments

Comments
 (0)