Initial commit

This commit is contained in:
Zhongwei Li
2025-11-30 08:57:41 +08:00
commit c2d0b101b0
22 changed files with 6446 additions and 0 deletions

View File

@@ -0,0 +1,524 @@
# JsonPath Patterns for YAML Recipes
Comprehensive collection of JsonPath patterns for common YAML structures including GitHub Actions, Kubernetes, CI/CD configs, and generic YAML.
## Overview
JsonPath provides a query language for navigating YAML/JSON structures. In OpenRewrite, use `JsonPathMatcher` to match specific locations in YAML files.
```java
JsonPathMatcher matcher = new JsonPathMatcher("$.jobs.*.steps[*].uses");
if (matcher.matches(getCursor())) {
// This element matches the path
}
```
## JsonPath Syntax Quick Reference
| Syntax | Meaning | Example |
|--------|---------|---------|
| `$` | Root element | `$` |
| `.` | Child element | `$.jobs` |
| `..` | Recursive descent | `$..uses` |
| `*` | Wildcard (any element) | `$.jobs.*` |
| `[*]` | Array wildcard | `$.steps[*]` |
| `[n]` | Array index | `$.steps[0]` |
| `[start:end]` | Array slice | `$.steps[0:3]` |
| `[?(@.key)]` | Filter expression | `$[?(@.name)]` |
---
## GitHub Actions Patterns
### Workflow-Level Patterns
```java
// Root workflow properties
"$.name" // Workflow name
"$.on" // Trigger configuration
"$.env" // Workflow-level environment variables
"$.permissions" // Workflow-level permissions
"$.concurrency" // Concurrency configuration
"$.defaults" // Default settings
// Specific triggers
"$.on.push" // Push trigger
"$.on.pull_request" // Pull request trigger
"$.on.workflow_dispatch" // Manual trigger
"$.on.schedule[*]" // Scheduled triggers (cron)
// Trigger details
"$.on.push.branches[*]" // Push branch filters
"$.on.push.paths[*]" // Push path filters
"$.on.pull_request.types[*]" // PR event types
```
### Job-Level Patterns
```java
// All jobs
"$.jobs" // Jobs object
"$.jobs.*" // Any job (wildcard)
"$.jobs.build" // Specific job named 'build'
// Job properties
"$.jobs.*.name" // Job display name
"$.jobs.*.runs-on" // Runner specification
"$.jobs.*.needs" // Job dependencies
"$.jobs.*.if" // Job conditions
"$.jobs.*.timeout-minutes" // Job timeout
"$.jobs.*.strategy" // Matrix/parallel strategy
"$.jobs.*.env" // Job-level environment variables
"$.jobs.*.permissions" // Job-level permissions
"$.jobs.*.container" // Container configuration
"$.jobs.*.services" // Service containers
// Matrix strategy
"$.jobs.*.strategy.matrix" // Matrix configuration
"$.jobs.*.strategy.matrix.os[*]" // OS variations
"$.jobs.*.strategy.matrix.node[*]" // Node.js versions
"$.jobs.*.strategy.fail-fast" // Fail-fast setting
"$.jobs.*.strategy.max-parallel" // Parallel limit
```
### Step-Level Patterns
```java
// All steps
"$.jobs.*.steps" // Steps array
"$.jobs.*.steps[*]" // Any step in any job
"$.jobs.build.steps[*]" // Steps in 'build' job
"$.jobs.*.steps[0]" // First step of each job
// Step properties
"$.jobs.*.steps[*].name" // Step name
"$.jobs.*.steps[*].uses" // Action reference
"$.jobs.*.steps[*].run" // Shell command
"$.jobs.*.steps[*].with" // Action inputs
"$.jobs.*.steps[*].env" // Step environment variables
"$.jobs.*.steps[*].if" // Step condition
"$.jobs.*.steps[*].continue-on-error" // Error handling
// Action inputs (with block)
"$.jobs.*.steps[*].with.node-version" // Specific input
"$.jobs.*.steps[*].with.*" // Any input
```
### Complete GitHub Actions Examples
```java
// Find all uses of actions/checkout
JsonPathMatcher matcher = new JsonPathMatcher("$.jobs.*.steps[*].uses");
if (matcher.matches(getCursor()) && "uses".equals(entry.getKey().getValue())) {
if (entry.getValue() instanceof Yaml.Scalar) {
Yaml.Scalar scalar = (Yaml.Scalar) entry.getValue();
if (scalar.getValue().startsWith("actions/checkout@")) {
// Found checkout action
}
}
}
// Find all jobs running on ubuntu-latest
JsonPathMatcher matcher = new JsonPathMatcher("$.jobs.*.runs-on");
if (matcher.matches(getCursor()) && "runs-on".equals(entry.getKey().getValue())) {
// Process runner specification
}
// Find all node-version specifications in matrix
JsonPathMatcher matcher = new JsonPathMatcher("$.jobs.*.strategy.matrix.node[*]");
if (matcher.matches(getCursor())) {
// Process node version entry
}
// Find environment variables at any level
JsonPathMatcher matcher = new JsonPathMatcher("$..env.*");
if (matcher.matches(getCursor())) {
// Found an environment variable
}
```
---
## Kubernetes Patterns
### Pod/Deployment Patterns
```java
// Metadata
"$.metadata.name" // Resource name
"$.metadata.namespace" // Namespace
"$.metadata.labels.*" // Any label
"$.metadata.annotations.*" // Any annotation
// Spec
"$.spec.replicas" // Replica count
"$.spec.selector" // Pod selector
"$.spec.template" // Pod template
// Pod template
"$.spec.template.metadata.labels.*" // Pod labels
"$.spec.template.spec.containers[*]" // All containers
"$.spec.template.spec.initContainers[*]" // Init containers
"$.spec.template.spec.volumes[*]" // Volumes
// Container details
"$.spec.template.spec.containers[*].name" // Container name
"$.spec.template.spec.containers[*].image" // Container image
"$.spec.template.spec.containers[*].ports[*]" // Container ports
"$.spec.template.spec.containers[*].env[*]" // Environment variables
"$.spec.template.spec.containers[*].resources" // Resource limits
"$.spec.template.spec.containers[*].volumeMounts[*]" // Volume mounts
```
### Service Patterns
```java
"$.spec.type" // Service type (ClusterIP, NodePort, LoadBalancer)
"$.spec.selector.*" // Service selector
"$.spec.ports[*]" // Service ports
"$.spec.ports[*].port" // Port number
"$.spec.ports[*].targetPort" // Target port
"$.spec.ports[*].protocol" // Protocol (TCP/UDP)
```
### ConfigMap/Secret Patterns
```java
"$.data.*" // Any data entry
"$.data.config\\.yaml" // Specific data key
"$.stringData.*" // String data entries
"$.binaryData.*" // Binary data entries
```
### Ingress Patterns
```java
"$.spec.rules[*]" // Ingress rules
"$.spec.rules[*].host" // Host pattern
"$.spec.rules[*].http.paths[*]" // Path rules
"$.spec.rules[*].http.paths[*].path" // Path pattern
"$.spec.rules[*].http.paths[*].backend" // Backend service
"$.spec.tls[*]" // TLS configuration
```
### Complete Kubernetes Examples
```java
// Update container images for nginx
JsonPathMatcher matcher = new JsonPathMatcher("$.spec.template.spec.containers[*].image");
if (matcher.matches(getCursor()) && "image".equals(entry.getKey().getValue())) {
if (entry.getValue() instanceof Yaml.Scalar) {
Yaml.Scalar scalar = (Yaml.Scalar) entry.getValue();
if (scalar.getValue().startsWith("nginx:")) {
// Update nginx version
}
}
}
// Find all resource limits
JsonPathMatcher matcher = new JsonPathMatcher("$.spec.template.spec.containers[*].resources.limits.memory");
if (matcher.matches(getCursor())) {
// Process memory limit
}
// Find all environment variables across all containers
JsonPathMatcher matcher = new JsonPathMatcher("$..containers[*].env[*].name");
if (matcher.matches(getCursor())) {
// Process environment variable
}
```
---
## CI/CD Configuration Patterns
### GitLab CI
```java
// Job-level
"$.*.script[*]" // Script commands in any job
"$.*.stage" // Job stage
"$.*.image" // Docker image
"$.*.services[*]" // Service containers
"$.*.before_script[*]" // Before script commands
"$.*.after_script[*]" // After script commands
"$.*.variables.*" // Job variables
"$.*.cache" // Cache configuration
"$.*.artifacts" // Artifacts configuration
// Pipeline-level
"$.stages[*]" // Pipeline stages
"$.variables.*" // Global variables
"$.default" // Default settings
```
### CircleCI
```java
// Jobs
"$.jobs.*" // Any job
"$.jobs.*.docker[*]" // Docker images
"$.jobs.*.docker[*].image" // Docker image
"$.jobs.*.steps[*]" // Job steps
"$.jobs.*.environment.*" // Job environment
// Workflows
"$.workflows.*" // Any workflow
"$.workflows.*.jobs[*]" // Workflow jobs
```
### Travis CI
```java
"$.language" // Language
"$.os" // Operating system
"$.dist" // Distribution
"$.script[*]" // Build script
"$.before_install[*]" // Before install
"$.install[*]" // Install commands
"$.before_script[*]" // Before script
"$.after_success[*]" // After success
"$.matrix.include[*]" // Matrix builds
```
### Jenkins (Declarative Pipeline)
```java
"$.pipeline.agent" // Agent specification
"$.pipeline.stages[*]" // Pipeline stages
"$.pipeline.stages[*].stage" // Stage name
"$.pipeline.stages[*].steps[*]" // Stage steps
"$.pipeline.environment.*" // Environment variables
"$.pipeline.post" // Post actions
```
---
## Generic YAML Patterns
### Common Structures
```java
// Root level
"$.*" // Any root-level property
"$.version" // Version field
"$.name" // Name field
"$.description" // Description field
// Nested structures
"$.config.*" // Any config property
"$.settings.*" // Any settings property
"$.metadata.*" // Any metadata property
// Arrays
"$.items[*]" // Any item in array
"$.items[0]" // First item
"$.items[-1]" // Last item (not supported in all implementations)
// Deep search
"$..name" // Any 'name' at any level
"$..version" // Any 'version' at any level
```
### Package Manager Configs
```java
// package.json (npm)
"$.scripts.*" // npm scripts
"$.dependencies.*" // Dependencies
"$.devDependencies.*" // Dev dependencies
"$.peerDependencies.*" // Peer dependencies
// composer.json (PHP)
"$.require.*" // PHP dependencies
"$.require-dev.*" // Dev dependencies
"$.autoload.psr-4.*" // PSR-4 autoload
// Gemfile (Ruby) - typically not YAML but similar patterns
"$.dependencies[*]" // Gem dependencies
```
### Docker Compose
```java
"$.version" // Compose file version
"$.services.*" // Any service
"$.services.*.image" // Service image
"$.services.*.build" // Build configuration
"$.services.*.ports[*]" // Port mappings
"$.services.*.environment.*" // Environment variables
"$.services.*.volumes[*]" // Volume mounts
"$.services.*.depends_on[*]" // Dependencies
"$.networks.*" // Network definitions
"$.volumes.*" // Volume definitions
```
### Ansible Playbooks
```java
"$[*].hosts" // Target hosts
"$[*].tasks[*]" // All tasks
"$[*].tasks[*].name" // Task names
"$[*].tasks[*].when" // Task conditions
"$[*].vars.*" // Variables
"$[*].roles[*]" // Roles
```
---
## Advanced Patterns
### Recursive Descent
Find elements at any depth in the structure:
```java
// Find all 'version' keys anywhere in document
JsonPathMatcher matcher = new JsonPathMatcher("$..version");
// Find all 'env' objects anywhere
JsonPathMatcher matcher = new JsonPathMatcher("$..env");
// Find all arrays named 'items' anywhere
JsonPathMatcher matcher = new JsonPathMatcher("$..items[*]");
```
### Multiple Matchers
Use multiple matchers for precise targeting:
```java
// Match steps that use actions AND are in build job
JsonPathMatcher stepMatcher = new JsonPathMatcher("$.jobs.build.steps[*].uses");
JsonPathMatcher anyStepMatcher = new JsonPathMatcher("$.jobs.*.steps[*].uses");
if (stepMatcher.matches(getCursor()) || anyStepMatcher.matches(getCursor())) {
// Process action reference
}
```
### Combining with Key Checks
```java
// Match 'uses' key within steps
JsonPathMatcher pathMatcher = new JsonPathMatcher("$.jobs.*.steps[*].uses");
@Override
public Yaml.Mapping.Entry visitMappingEntry(Yaml.Mapping.Entry entry, ExecutionContext ctx) {
// Check both path AND key name
if (pathMatcher.matches(getCursor()) && "uses".equals(entry.getKey().getValue())) {
// This is definitely a 'uses' field in a step
}
return super.visitMappingEntry(entry, ctx);
}
```
---
## Pattern Selection Guide
| Use Case | Pattern Type | Example |
|----------|--------------|---------|
| Exact path | Explicit path | `$.jobs.build.steps[0]` |
| Any child | Wildcard | `$.jobs.*` |
| Any array item | Array wildcard | `$.steps[*]` |
| Any depth | Recursive descent | `$..version` |
| Conditional | With key check | `pathMatcher.matches() && "key".equals(key)` |
---
## Common Mistakes
### 1. Forgetting Key Name Check
```java
// ❌ WRONG - matches parent path, not the specific key
JsonPathMatcher matcher = new JsonPathMatcher("$.jobs.*.steps[*].uses");
if (matcher.matches(getCursor())) {
// This matches the STEP, not the 'uses' key
}
// ✅ CORRECT - check both path and key
JsonPathMatcher matcher = new JsonPathMatcher("$.jobs.*.steps[*].uses");
if (matcher.matches(getCursor()) && "uses".equals(entry.getKey().getValue())) {
// Now we're definitely at the 'uses' key
}
```
### 2. Wrong Visitor Method
```java
// ❌ WRONG - visitMapping() doesn't work for sequences
JsonPathMatcher matcher = new JsonPathMatcher("$.steps[*]");
public Yaml.Mapping visitMapping(Yaml.Mapping mapping, ExecutionContext ctx) {
// Won't match array items
}
// ✅ CORRECT - use visitSequenceEntry()
JsonPathMatcher matcher = new JsonPathMatcher("$.steps[*]");
public Yaml.Sequence.Entry visitSequenceEntry(Yaml.Sequence.Entry entry, ExecutionContext ctx) {
if (matcher.matches(getCursor())) {
// Process sequence entry
}
}
```
### 3. Overly Broad Patterns
```java
// ❌ TOO BROAD - matches 'uses' anywhere
JsonPathMatcher matcher = new JsonPathMatcher("$..uses");
// ✅ BETTER - specific to GitHub Actions steps
JsonPathMatcher matcher = new JsonPathMatcher("$.jobs.*.steps[*].uses");
```
---
## Testing JsonPath Patterns
```java
// In your test, verify the path matches
@Test
void testJsonPathMatching() {
rewriteRun(
spec -> spec.recipe(new YourRecipe()),
yaml(
"""
jobs:
build:
steps:
- uses: actions/checkout@v2
""",
"""
jobs:
build:
steps:
- uses: actions/checkout@v3
"""
)
);
}
```
---
## Quick Reference Table
| YAML Structure | JsonPath Pattern | Visitor Method |
|----------------|------------------|----------------|
| Root property | `$.property` | `visitMappingEntry` |
| Nested property | `$.parent.child` | `visitMappingEntry` |
| Any property | `$.*` or `$.parent.*` | `visitMappingEntry` |
| Array item | `$.array[*]` | `visitSequenceEntry` |
| Nested array | `$.parent.array[*]` | `visitSequenceEntry` |
| Any depth | `$..property` | `visitMappingEntry` |
| Multiple levels | `$.a.*.b.*.c` | `visitMappingEntry` |
---
## Additional Resources
- JsonPath Specification: https://goessner.net/articles/JsonPath/
- JsonPath Evaluator (online tool): https://jsonpath.com/
- OpenRewrite JsonPathMatcher JavaDoc: https://docs.openrewrite.org/