ast-grep for Tekton Projects
Overview
This guide documents how ast-grep can be used for Tekton and Kubernetes projects, particularly for the tektoncd/* repositories (Pipelines, Triggers, Chains, Results, etc.).
Why ast-grep for Tekton?
Tekton projects have unique characteristics that make ast-grep valuable:
Polyglot Codebase
- Go: Core controllers, reconcilers, business logic
- YAML: CRD definitions, examples, test fixtures
- Bash: Scripts in CI/CD, testing, utilities
ast-grep handles all these languages with one tool and consistent syntax.
Common Patterns
- API Migrations: v1beta1 → v1 transitions
- Best Practices: Consistent error handling, testing patterns
- Security: Finding hardcoded secrets, unsafe patterns
- Code Quality: Ensuring workspaces, proper RBAC markers
Scale
- tektoncd/pipeline: ~1500+ Go files, ~500+ YAML files
- ast-grep can scan entire repository in < 2 seconds
- Interactive review prevents bulk mistakes
Use Cases
1. API Version Migration
Problem: Tekton v1beta1 APIs are deprecated, need migration to v1.
Find all v1beta1 usage in YAML:
ast-grep -p 'apiVersion: tekton.dev/v1beta1' -l yaml
Find in Go code:
ast-grep -p 'tekton.dev/v1beta1' -l go pkg/
Create migration rule (.ast-grep/rules/tekton-api-v1.yml):
id: use-v1-api
message: Use tekton.dev/v1 instead of v1beta1
severity: warning
language: yaml
note: |
v1beta1 APIs are deprecated. Migrate to v1.
See: https://tekton.dev/docs/pipelines/migrating-v1beta1-to-v1/
rule:
pattern: apiVersion: tekton.dev/v1beta1
fix: apiVersion: tekton.dev/v1
Interactive migration:
ast-grep -p 'apiVersion: tekton.dev/v1beta1' \
--rewrite 'apiVersion: tekton.dev/v1' \
--interactive \
-l yaml examples/
2. Security Scanning
Find potential hardcoded secrets in Go:
Rule (.ast-grep/rules/go-hardcoded-secrets.yml):
id: go-no-hardcoded-secrets
message: Potential hardcoded secret detected
severity: error
language: go
note: Use environment variables or secret managers instead
rule:
any:
- pattern: password := "$SECRET"
- pattern: token := "$SECRET"
- pattern: apiKey := "$SECRET"
where:
SECRET:
regex: '^[A-Za-z0-9+/=]{20,}$'
Find secrets in YAML:
id: yaml-no-hardcoded-secrets
message: Hardcoded secret in YAML
severity: error
language: yaml
rule:
any:
- pattern: |
password: $SECRET
- pattern: |
token: $SECRET
- pattern: |
apiKey: $SECRET
where:
SECRET:
regex: '^[A-Za-z0-9+/=_-]{20,}$'
Scan for unsafe patterns:
ast-grep scan --json | \
jq -r '.[] | select(.ruleId | startswith("go-no-hardcoded")) |
{file, line: .range.start.line, message}'
3. Best Practices Enforcement
Ensure Workspaces in Tasks:
Rule (.ast-grep/rules/tekton-require-workspace.yml):
id: tekton-require-workspace
message: Tasks should declare workspaces for data sharing
severity: info
language: yaml
note: |
Workspaces provide a better abstraction than direct volume mounts.
They allow runtime binding and make Tasks more reusable.
rule:
pattern: |
kind: Task
metadata:
$$$
spec:
$$$SPEC
not:
has:
pattern: |
workspaces:
$$$
Find :latest tags in container images:
Rule (.ast-grep/rules/no-latest-tag.yml):
id: no-latest-tag
message: Avoid 'latest' tag in container images
severity: warning
language: yaml
note: |
Using 'latest' tag is not reproducible.
Use specific versions or SHA digests:
- image: gcr.io/tekton-releases/github.com/tektoncd/pipeline/cmd/controller:v0.50.0
- image: alpine@sha256:abc123...
rule:
pattern: image: $IMAGE:latest
RBAC markers for controllers:
Rule (.ast-grep/rules/go-require-rbac-markers.yml):
id: go-require-rbac-markers
message: Controller Reconcile methods need RBAC markers
severity: warning
language: go
note: |
Kubebuilder RBAC markers are required for generating RBAC manifests.
Add comments like:
// +kubebuilder:rbac:groups=tekton.dev,resources=taskruns,verbs=get;list;watch
rule:
pattern: |
func (r *$RECONCILER) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) {
$$$
}
not:
precedes:
pattern: // +kubebuilder:rbac
4. Error Handling Patterns
Ensure errors are wrapped with %w:
Rule (.ast-grep/rules/go-wrap-errors.yml):
id: go-wrap-errors
message: Wrap errors with %w for error chains
severity: warning
language: go
note: |
Use fmt.Errorf("context: %w", err) to preserve error chains.
This allows errors.Is() and errors.As() to work properly.
Bad: return fmt.Errorf("failed: %v", err)
Good: return fmt.Errorf("failed: %w", err)
rule:
pattern: |
if err != nil {
return fmt.Errorf($MSG, err)
}
where:
MSG:
not:
regex: '%w'
Find ignored errors:
Rule (.ast-grep/rules/go-no-error-discard.yml):
id: go-no-error-discard
message: Don't discard errors without checking
severity: error
language: go
note: Always handle errors explicitly
rule:
any:
- pattern: $_, _ = $FUNC($$$)
- pattern: $_, _ := $FUNC($$$)
5. Testing Quality
Ensure t.Helper() in test helpers:
Rule (.ast-grep/rules/go-use-t-helper.yml):
id: go-use-t-helper
message: Call t.Helper() in test helper functions
severity: warning
language: go
note: |
t.Helper() marks a function as a test helper.
This makes failure reports show the correct line number.
rule:
pattern: |
func $NAME(t *testing.T$$$) {
$$$BODY
}
where:
NAME:
regex: '^[a-z].*' # Helper functions (not Test*)
not:
has:
pattern: t.Helper()
Find table tests without subtests:
Rule (.ast-grep/rules/go-use-subtests.yml):
id: go-use-subtests
message: Use t.Run() for table-driven tests
severity: info
language: go
note: |
Wrap test cases in t.Run() for better output:
for _, tc := range tests {
t.Run(tc.name, func(t *testing.T) { ... })
}
rule:
pattern: |
for _, tc := range tests {
$$$TEST_BODY
}
inside:
kind: function_declaration
not:
has:
pattern: t.Run($$$)
6. Deprecated Pattern Detection
Find deprecated PipelineResource:
Rule (.ast-grep/rules/no-pipeline-resource.yml):
id: no-pipeline-resource
message: PipelineResource is deprecated
severity: error
language: yaml
note: |
PipelineResource is deprecated in Tekton v1.
Use Tasks with workspaces instead.
Migration guide:
https://tekton.dev/docs/pipelines/migrating-v1beta1-to-v1/#pipelineresources
rule:
pattern: |
kind: PipelineResource
$$$
Find deprecated API fields:
Rule (.ast-grep/rules/tekton-deprecated-fields.yml):
id: tekton-no-resources-field
message: resources field is deprecated, use workspaces
severity: warning
language: yaml
note: |
The 'resources' field in Tasks is deprecated.
Use 'workspaces' for input/output data instead.
rule:
pattern: |
spec:
$$$
resources:
$$$
inside:
pattern: |
kind: Task
$$$
Example Workflow: API Migration
Step 1: Assess Scope
# Count v1beta1 usage in YAML files
ast-grep -p 'tekton.dev/v1beta1' -l yaml | wc -l
# Count v1beta1 imports in Go
ast-grep -p 'import "$$$tekton.dev/v1beta1$$$"' -l go | wc -l
# Get breakdown by file
ast-grep -p 'v1beta1' --json | \
jq -r 'group_by(.file) | map({file: .[0].file, count: length}) |
sort_by(.count) | reverse | .[]'
Step 2: Create Migration Rules
Create .ast-grep/rules/tekton-migration/ directory with rules for each deprecated pattern.
Step 3: Test on Examples
# Migrate example files first
ast-grep scan examples/ --json | \
jq -r '.[] | select(.ruleId | startswith("tekton-")) | .file' | \
sort -u
Step 4: Interactive Migration
# Migrate one directory at a time with review
ast-grep -p 'apiVersion: tekton.dev/v1beta1' \
--rewrite 'apiVersion: tekton.dev/v1' \
--interactive \
-l yaml examples/v1beta1/
Step 5: Verify
# Run tests after migration
go test ./...
# Verify YAML is valid
kubectl apply --dry-run=client -f examples/
Step 6: Track Progress
# Get migration report
cat > /tmp/migration-report.sh <<'EOF'
#!/bin/bash
echo "=== Tekton v1 Migration Report ==="
echo ""
echo "v1beta1 remaining in YAML:"
ast-grep -p 'tekton.dev/v1beta1' -l yaml 2>/dev/null | wc -l
echo ""
echo "v1beta1 imports in Go:"
ast-grep -p 'tekton.dev/v1beta1' -l go 2>/dev/null | wc -l
echo ""
echo "Files still using v1beta1:"
ast-grep -p 'v1beta1' --json 2>/dev/null | \
jq -r '.[] | .file' | sort -u | head -20
EOF
chmod +x /tmp/migration-report.sh
/tmp/migration-report.sh
Performance on Large Codebases
Benchmarks on tektoncd/pipeline (hypothetical, based on typical repo size):
| Operation | Files | Time | Notes |
|---|---|---|---|
| Full scan (Go + YAML) | ~2000 | ~1.5s | All rules |
| Go files only | ~1500 | ~1.0s | Error handling rules |
| YAML files only | ~500 | ~0.3s | API version rules |
| Single rule (v1beta1) | ~2000 | ~0.2s | Fast targeted search |
Compare with:
- ripgrep (text): ~0.05s (faster but less accurate)
- semgrep (semantic): ~30s (slower but more powerful)
- golangci-lint (Go): ~15s (deeper Go analysis)
ast-grep sweet spot: Fast enough for interactive use, accurate enough to avoid false positives.
CI/CD Integration
GitHub Actions
.github/workflows/ast-grep.yml:
name: ast-grep Linting
on:
pull_request:
branches: [main]
push:
branches: [main]
jobs:
ast-grep:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Install ast-grep
run: |
curl -fsSL https://github.com/ast-grep/ast-grep/releases/latest/download/sg-x86_64-unknown-linux-gnu.zip -o ast-grep.zip
unzip ast-grep.zip
sudo mv sg /usr/local/bin/ast-grep
chmod +x /usr/local/bin/ast-grep
- name: Run ast-grep scan
run: ast-grep scan --error
- name: Upload results (if needed)
if: failure()
run: |
ast-grep scan --json > ast-grep-results.json
# Upload to artifact or reporting service
Makefile Integration
.PHONY: lint-ast-grep
lint-ast-grep:
@echo "Running ast-grep linting..."
@ast-grep scan --error
.PHONY: lint-ast-grep-report
lint-ast-grep-report:
@ast-grep scan --json | \
jq -r 'group_by(.severity) | map({severity: .[0].severity, count: length})'
.PHONY: lint
lint: lint-go lint-yaml lint-ast-grep
Rule Library for Tekton
Recommended rule structure:
.ast-grep/
├── sgconfig.yml
└── rules/
├── tekton/
│ ├── api-version.yml # API migration rules
│ ├── workspaces.yml # Workspace best practices
│ ├── deprecated-fields.yml # Deprecated API fields
│ └── best-practices.yml # General best practices
├── kubernetes/
│ ├── rbac.yml # RBAC marker requirements
│ └── deprecated-apis.yml # Deprecated K8s APIs
├── go/
│ ├── error-handling.yml # Error wrapping, checking
│ ├── testing.yml # Test helper patterns
│ └── security.yml # Hardcoded secrets, etc.
└── yaml/
├── security.yml # YAML security patterns
└── style.yml # YAML formatting
Comparison with Tekton-Specific Tools
| Tool | Purpose | Speed | Coverage |
|---|---|---|---|
| ast-grep | Pattern matching | Fast | Structural patterns |
| yamllint | YAML linting | Fast | YAML syntax/style |
| golangci-lint | Go linting | Medium | Deep Go analysis |
| conftest | Policy testing | Medium | OPA-based validation |
| kubeconform | Schema validation | Fast | CRD validation |
ast-grep advantages:
- Cross-language (Go + YAML)
- Custom repo-specific rules
- Interactive refactoring
- Fast feedback loop
Use together:
make lint-yaml # yamllint for YAML syntax
make lint-go # golangci-lint for Go
make lint-ast-grep # ast-grep for structural patterns
Community Sharing
After building Tekton-specific rules, consider:
- Share rule repository: Create
tektoncd/ast-grep-ruleswith community rules - Contribute to catalog: Submit examples to https://ast-grep.github.io/catalog/
- Blog post: Document migration experience and lessons learned
- Conference talk: Present at KubeCon, cdCon, or Tekton community meetings
Resources
- Tekton Migration Guide: https://tekton.dev/docs/pipelines/migrating-v1beta1-to-v1/
- ast-grep Playground: https://ast-grep.github.io/playground.html (test patterns)
- Go AST Viewer: https://yohe.github.io/golang-ast-viewer/ (understand Go patterns)
- YAML Tree-sitter: https://github.com/tree-sitter/tree-sitter-yaml
Next Steps
- Start small: Pick one rule (e.g., v1beta1 detection)
- Test on examples: Run on
examples/directory first - Iterate: Refine rules based on false positives
- Expand: Add more rules gradually
- Automate: Integrate into CI/CD
- Share: Contribute back to community
This guide is a template for Tekton projects. Adapt rules and workflows to your specific needs.