Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
217 changes: 217 additions & 0 deletions docs/examples/policies/_testutils.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,217 @@
#!/bin/bash
#
# Chainloop Policy Test Utilities
#
# This file contains shared test framework functions for policy testing.
# It's designed to be shared across all policy directories.
#
# Usage: source ../_testutils.sh
#

set -e

# Unalias grep to use standard grep instead of rg
unalias grep 2>/dev/null || true

# Colors for output
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
BLUE='\033[0;34m'
NC='\033[0m' # No Color

# Test counters
TESTS_PASSED=0
TESTS_FAILED=0

# Find chainloop binary - prefer PATH
find_chainloop_binary() {
if command -v chainloop &> /dev/null; then
echo "chainloop"
elif [ -f "../../../../app/cli/bin/chainloop" ]; then
echo "../../../../app/cli/bin/chainloop"
elif [ -f "../../../../bin/chainloop" ]; then
echo "../../../../bin/chainloop"
else
echo -e "${RED}Error: chainloop binary not found${NC}" >&2
echo "Please build the CLI first or ensure it's in your PATH" >&2
exit 1
fi
}

# Initialize test framework
init_tests() {
CHAINLOOP_BIN=$(find_chainloop_binary)
echo -e "${BLUE}Using Chainloop binary: ${CHAINLOOP_BIN}${NC}"
echo ""

TESTS_PASSED=0
TESTS_FAILED=0
}

# Check if policy was actually executed (not skipped or ignored)
is_policy_executed() {
local output="$1"

# Check if policy was skipped
if echo "$output" | command grep -q '"skipped": *true'; then
return 1 # false - policy was skipped
fi

# Check if policy was ignored
if echo "$output" | command grep -q '"ignored": *true'; then
return 1 # false - policy was ignored
fi

return 0 # true - policy was executed
}

# Check if violations exist in policy output
has_violations() {
local output="$1"

# Check if violations array is non-empty (not "violations": [])
if echo "$output" | command grep -q '"violations": *\[\]'; then
return 1 # false - empty violations array
elif echo "$output" | command grep -q '"violations": *\['; then
return 0 # true - has violations
else
return 1 # false - no violations field found
fi
}

# Test policy linting
test_policy_lint() {
local policy_file="$1"
local test_name="${2:-Policy Lint Check}"

echo -e "${YELLOW}Testing: ${test_name}${NC}"
echo "Command: $CHAINLOOP_BIN policy develop lint --policy $policy_file"

if output=$($CHAINLOOP_BIN policy develop lint --policy "$policy_file" 2>&1); then
echo -e "${GREEN}✓ PASSED${NC}"
((TESTS_PASSED++))
else
echo -e "${RED}✗ FAILED${NC}"
echo "Output: $output"
((TESTS_FAILED++))
fi
echo ""
}

# Test policy evaluation
test_policy_eval() {
local test_name="$1"
local expected_result="$2" # "pass" or "fail"
shift 2
local args="$@"

# Extract --kind parameter from args
local material_kind="EVIDENCE" # Default fallback
local remaining_args=""

while [[ $# -gt 0 ]]; do
case $1 in
--kind)
material_kind="$2"
shift 2
;;
*)
remaining_args="$remaining_args $1"
shift
;;
esac
done

echo -e "${YELLOW}Testing: ${test_name}${NC}"
echo "Command: $CHAINLOOP_BIN policy develop eval --policy policy.yaml --kind $material_kind $remaining_args"

# Execute the command
if output=$($CHAINLOOP_BIN policy develop eval --policy policy.yaml --kind "$material_kind" $remaining_args 2>&1); then
exit_code=0
else
exit_code=$?
fi

# Determine actual result
if [ $exit_code -eq 0 ]; then
# First check if policy was actually executed
if ! is_policy_executed "$output"; then
actual_result="fail" # Policy was skipped or ignored
if echo "$output" | command grep -q '"skipped": *true'; then
skip_reason="Policy was skipped"
elif echo "$output" | command grep -q '"ignored": *true'; then
skip_reason="Policy was ignored (material type mismatch)"
else
skip_reason="Policy was not executed"
fi
elif has_violations "$output"; then
actual_result="fail" # Has violations = test should fail
else
actual_result="pass" # No violations = test should pass
fi
else
actual_result="fail" # Command failed
fi

# Compare with expected result
if [ "$actual_result" = "$expected_result" ]; then
if [ "$expected_result" = "pass" ]; then
echo -e "${GREEN}✓ PASSED${NC}"
else
echo -e "${GREEN}✓ FAILED (as expected)${NC}"
fi
((TESTS_PASSED++))
else
if [ "$expected_result" = "pass" ]; then
echo -e "${RED}✗ FAILED (expected to pass but failed)${NC}"
else
echo -e "${RED}✗ PASSED (expected to fail but passed)${NC}"
fi

# Show skip reason if policy wasn't executed
if [ -n "${skip_reason:-}" ]; then
echo "Reason: $skip_reason"
fi
echo "Output: $output"
((TESTS_FAILED++))
fi
echo ""
}

# Print test summary and exit with appropriate code
test_summary() {
echo -e "${BLUE}=== Test Results Summary ===${NC}"
echo ""
TOTAL_TESTS=$((TESTS_PASSED + TESTS_FAILED))
echo -e "Total Tests: ${TOTAL_TESTS}"
echo -e "${GREEN}Passed: ${TESTS_PASSED}${NC}"
echo -e "${RED}Failed: ${TESTS_FAILED}${NC}"
echo ""

if [ $TESTS_FAILED -eq 0 ]; then
echo -e "${GREEN}🎉 All tests passed!${NC}"
exit 0
else
echo -e "${RED}❌ Some tests failed. Please check the output above.${NC}"
exit 1
fi
}

# Verify required files exist
verify_files() {
local files=("$@")
for file in "${files[@]}"; do
if [ ! -f "$file" ]; then
echo -e "${RED}Error: $file not found${NC}" >&2
exit 1
fi
done
}

# Print a test section header
test_section() {
local section_name="$1"
echo -e "${BLUE}=== ${section_name} ===${NC}"
echo ""
}
136 changes: 136 additions & 0 deletions docs/examples/policies/json-field-validator/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,136 @@
# JSON Field Validator

Validates specific fields in JSON files used as evidence in Chainloop workflows.

## What This Policy Does

This policy allows you to validate individual fields in JSON evidence files. It can:

- **Check field values** - Verify a field matches an expected value
- **Pattern matching** - Validate field values against regex patterns
- **Structure validation** - Ensure required fields exist
- **Flexible comparison** - Supports various boolean formats (`true`, `True`, `1`)

## Policy Parameters

| Parameter | Description | Required | Example Values |
|-----------|-------------|----------|----------------|
| `required_field` | Dot-notation path to the field | ✅ Yes | `application.name`, `security.enabled` |
| `expected_value` | Expected exact value | ❌ No | `web-service`, `production`, `true` |
| `field_pattern` | Regex pattern to match | ❌ No | `^[0-9]+\.[0-9]+\.[0-9]+$` |

**Note**: Use either `expected_value` OR `field_pattern`, not both.

## Using in Workflow Contracts

Add this policy to your workflow contract:

```yaml
apiVersion: workflowcontract.chainloop.dev/v1
kind: WorkflowContract
metadata:
name: my-workflow
spec:
materials:
- type: EVIDENCE
name: app-config

policies:
- ref: ./json-field-validator/policy.yaml
with:
required_field: application.environment
expected_value: production
```

### Multiple Field Validations

```yaml
policies:
# Validate environment
- ref: ./json-field-validator/policy.yaml
with:
required_field: application.environment
expected_value: production

# Validate version format
- ref: ./json-field-validator/policy.yaml
with:
required_field: application.version
field_pattern: "^[0-9]+\\.[0-9]+\\.[0-9]+$"

# Validate security is enabled
- ref: ./json-field-validator/policy.yaml
with:
required_field: security.enabled
expected_value: "true"
```

## Development & Testing

### Lint the Policy
```bash
chainloop policy develop lint --policy policy.yaml --format
```

### Manual Testing
```bash
# Test field value validation
chainloop policy develop eval \
--policy policy.yaml \
--material testdata/config.json \
--kind EVIDENCE \
--input required_field=application.name \
--input expected_value=web-service

# Test pattern validation
chainloop policy develop eval \
--policy policy.yaml \
--material testdata/config.json \
--kind EVIDENCE \
--input required_field=application.version \
--input field_pattern="^[0-9]+\\.[0-9]+\\.[0-9]+$"
```

### Run All Tests
```bash
./test.sh
```

## Supported Field Paths

The policy supports dot notation for nested fields:

```json
{
"application": {
"name": "web-service", // → application.name
"version": "2.1.0", // → application.version
"environment": "production" // → application.environment
},
"security": {
"enabled": true, // → security.enabled
"tls_version": "1.3" // → security.tls_version
}
}
```

## Boolean Value Support

The policy accepts multiple boolean formats for `expected_value`:
- `true` (JSON boolean as string)
- `True` (capitalized)
- `1` (numeric string)

All match against JSON `true` values.

## Test Framework

- `_testutils.sh` - Contains shared test logic and utilities
- `test.sh` - Policy-specific test cases

## Sample Test Data

The `testdata/` directory contains:
- `config.json` - Application configuration with nested structure
- `compliance-checklist.json` - Different JSON structure for negative testing
- `invalid.json` - Invalid JSON for error testing
Loading
Loading