Build a CLI that reads K8s and Terraform YAML, validates against rules, and reports violations. Applies Modules 1-4.
Build a CLI tool that reads Kubernetes YAML files, validates them against a set of rules, and reports violations with file, line, and severity.
You've practiced all the pieces — now compose them into a real tool.
Your program should:
.yaml and .yml filesEvery infra team needs config validation. This project exercises everything from Track 1 (slices, maps, structs, error handling, testing) plus CLI and file I/O from Module 4. It's also the kind of tool that impresses in interviews — "I built a linter" shows you think about code quality and automation.
# Lint specific files
configlint deployment.yaml service.yaml
# Lint a directory recursively
configlint ./k8s/
# JSON output
configlint --format json ./k8s/
# Only show errors (not warnings)
configlint --severity error ./k8s/
deployment.yaml:15 [ERROR] missing-resource-limits: container "web" has no resource limits
deployment.yaml:22 [WARN] latest-tag: container "web" uses image tag "latest"
service.yaml:8 [WARN] missing-labels: Service "api" missing recommended label "app.kubernetes.io/version"
configmap.yaml [OK] no violations
Summary: 2 files with violations, 1 clean
1 error, 2 warnings
gopkg.in/yaml.v3 (or encoding/json after converting — your choice). Unmarshal into map[string]any for flexible access.filepath.WalkDir to find .yaml/.yml files recursively.type Rule struct {
ID string
Severity string // "error" or "warning"
Check func(doc map[string]any) []Violation
}
--format — output format: text (default) or json--severity — minimum severity to report: warning (default) or error--rules — comma-separated list of rule IDs to run (default: all)Use the flag package or Cobra (your choice).
Start with these 6 rules. Each should be a separate function:
| Rule ID | Severity | What it Checks |
|---|---|---|
missing-resource-limits |
error | K8s containers without resources.limits |
missing-resource-requests |
warning | K8s containers without resources.requests |
latest-tag |
warning | Container images using :latest or no tag |
missing-labels |
warning | K8s resources missing app.kubernetes.io/name label |
deprecated-api |
error | K8s resources using deprecated apiVersions (e.g., extensions/v1beta1) |
duplicate-env |
error | Containers with duplicate environment variable names |
K8s YAML parsed as map[string]any:
// Get containers from a Deployment
spec := doc["spec"].(map[string]any)
template := spec["template"].(map[string]any)
podSpec := template["spec"].(map[string]any)
containers := podSpec["containers"].([]any)
for _, c := range containers {
container := c.(map[string]any)
name := container["name"].(string)
image := container["image"].(string)
// ...
}
Tip: Write a helper that does safe type assertions with fallbacks — these chains panic if any key is missing.
Create a testdata/ directory with sample files:
apiVersion: apps/v1
kind: Deployment
metadata:
name: web
labels:
app: web
spec:
replicas: 3
selector:
matchLabels:
app: web
template:
metadata:
labels:
app: web
spec:
containers:
- name: web
image: nginx:latest
env:
- name: PORT
value: "8080"
- name: PORT
value: "9090"
apiVersion: v1
kind: Service
metadata:
name: api
spec:
selector:
app: api
ports:
- port: 80
configlint/
├── main.go ← CLI entry point, flag parsing
├── lint.go ← Core: walk files, parse, run rules, collect violations
├── rules.go ← All rule implementations
├── rules_test.go ← Table-driven tests for each rule
├── output.go ← Formatting (text, JSON)
└── testdata/ ← Sample YAML files for testing
Suggested approach:
- Start with
main.go: parse flags, collect file paths- Write
walkYAMLFiles(root string) ([]string, error)usingfilepath.WalkDir- Write
parseYAML(path string) ([]map[string]any, error)— note YAML files can contain multiple documents- Write one rule (e.g.,
latest-tag— it's the simplest)- Wire it together: walk → parse → check → report
- Add more rules one at a time, with tests for each
Write table-driven tests for each rule:
func TestLatestTag(t *testing.T) {
tests := []struct {
name string
doc map[string]any
wantViol int
}{
{"explicit tag", makeDeployment("nginx:1.25"), 0},
{"latest tag", makeDeployment("nginx:latest"), 1},
{"no tag", makeDeployment("nginx"), 1},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got := latestTagRule.Check(tt.doc)
if len(got) != tt.wantViol {
t.Errorf("got %d violations, want %d", len(got), tt.wantViol)
}
})
}
}
.configlint.yaml config file.tf files (parsed as HCL or JSON)git diff --name-only)Skills Used: CLI flags, file I/O, YAML parsing, directory walking, struct methods, interfaces (Rule), error handling, table-driven tests, map traversal, string parsing.