Initial commit
This commit is contained in:
524
skills/recipe-writer/references/jsonpath-patterns.md
Normal file
524
skills/recipe-writer/references/jsonpath-patterns.md
Normal 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/
|
||||
Reference in New Issue
Block a user