main

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

  1. API Migrations: v1beta1 → v1 transitions
  2. Best Practices: Consistent error handling, testing patterns
  3. Security: Finding hardcoded secrets, unsafe patterns
  4. 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:

  1. Share rule repository: Create tektoncd/ast-grep-rules with community rules
  2. Contribute to catalog: Submit examples to https://ast-grep.github.io/catalog/
  3. Blog post: Document migration experience and lessons learned
  4. Conference talk: Present at KubeCon, cdCon, or Tekton community meetings

Resources

Next Steps

  1. Start small: Pick one rule (e.g., v1beta1 detection)
  2. Test on examples: Run on examples/ directory first
  3. Iterate: Refine rules based on false positives
  4. Expand: Add more rules gradually
  5. Automate: Integrate into CI/CD
  6. Share: Contribute back to community

This guide is a template for Tekton projects. Adapt rules and workflows to your specific needs.