diff --git a/.github/workflows/validate-pattern-specs.yml b/.github/workflows/validate-pattern-specs.yml new file mode 100644 index 00000000..3efe6ea5 --- /dev/null +++ b/.github/workflows/validate-pattern-specs.yml @@ -0,0 +1,129 @@ +--- +name: Validate Pattern Specs + +concurrency: + group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.sha }} + cancel-in-progress: true + +on: + pull_request: + branches: + - main + push: + branches: + - main + +jobs: + validate-pattern-specs: + runs-on: ubuntu-latest + + steps: + - name: Checkout PR branch + uses: actions/checkout@v4 + with: + fetch-depth: 0 + + - name: Get changed files + id: changed-files + run: | + # New specification files + NEW_SPEC_FILES=$(git diff origin/main --name-only --diff-filter=A -- specifications/pattern-specification/*.md ":(exclude)*dev.md" || true) + echo "new_spec_files<> $GITHUB_OUTPUT + echo "$NEW_SPEC_FILES" >> $GITHUB_OUTPUT + echo "EOF" >> $GITHUB_OUTPUT + + # Modified specification files + MODIFIED_SPEC_FILES=$(git diff origin/main --name-only --diff-filter=M -- specifications/pattern-specification/*.md ":(exclude)*dev.md" || true) + echo "modified_spec_files<> $GITHUB_OUTPUT + echo "$MODIFIED_SPEC_FILES" >> $GITHUB_OUTPUT + echo "EOF" >> $GITHUB_OUTPUT + + # All changed specification files + ALL_SPEC_FILES=$(git diff origin/main --name-only --diff-filter=AM -- specifications/pattern-specification/*.md ":(exclude)*dev.md" || true) + echo "spec_files<> $GITHUB_OUTPUT + echo "$ALL_SPEC_FILES" >> $GITHUB_OUTPUT + echo "EOF" >> $GITHUB_OUTPUT + + # New schema files + NEW_SCHEMA_FILES=$(git diff origin/main --name-only --diff-filter=A -- specifications/pattern-schema/*.json ":(exclude)*dev.json" || true) + echo "new_schema_files<> $GITHUB_OUTPUT + echo "$NEW_SCHEMA_FILES" >> $GITHUB_OUTPUT + echo "EOF" >> $GITHUB_OUTPUT + + # Modified schema files + MODIFIED_SCHEMA_FILES=$(git diff origin/main --name-only --diff-filter=M -- specifications/pattern-schema/*.json ":(exclude)*dev.json" || true) + echo "modified_schema_files<> $GITHUB_OUTPUT + echo "$MODIFIED_SCHEMA_FILES" >> $GITHUB_OUTPUT + echo "EOF" >> $GITHUB_OUTPUT + + # All changed schema files + ALL_SCHEMA_FILES=$(git diff origin/main --name-only --diff-filter=AM -- specifications/pattern-schema/*.json ":(exclude)*dev.json" || true) + echo "schema_files<> $GITHUB_OUTPUT + echo "$ALL_SCHEMA_FILES" >> $GITHUB_OUTPUT + echo "EOF" >> $GITHUB_OUTPUT + + - name: Validate pattern specification files + if: steps.changed-files.outputs.spec_files != '' + run: | + # First, check if any existing specification files were modified (not allowed) + if [ -n "${{ steps.changed-files.outputs.modified_spec_files }}" ]; then + echo "::error::Modifying existing versioned specification files is not allowed. Modified files: ${{ steps.changed-files.outputs.modified_spec_files }}" + exit 1 + fi + + # Validate new specification files + NEW_SPEC_FILES="${{ steps.changed-files.outputs.new_spec_files }}" + while IFS= read -r file; do + if [ -n "$file" ]; then + # New specification file - validate version consistency + # Extract version once + FILENAME_VERSION=$(echo "$file" | sed -n 's/.*pattern-specification-\([^/]*\)\.md/\1/p') + FILE_VERSION=$(sed -n 's/^version:[[:space:]]*\([^[:space:]]*\).*/\1/p' "$file" | head -1) + + # Validate filename vs content version + if [ "$FILENAME_VERSION" != "$FILE_VERSION" ]; then + echo "::error::Specification file name does not match the specification version. Expected: $FILENAME_VERSION, Got: $FILE_VERSION" + exit 1 + fi + fi + done <<< "$NEW_SPEC_FILES" + + - name: Validate pattern schema files + if: steps.changed-files.outputs.schema_files != '' + run: | + SCHEMA_FILES="${{ steps.changed-files.outputs.schema_files }}" + while IFS= read -r file; do + if [ -n "$file" ]; then + # Skip the latest file to ensure proper validation of $id field + if [[ "$file" == *"pattern-schema-latest.json" ]]; then + continue + fi + + # Check if this is a modified existing versioned file (not allowed) + if git show origin/main:$file >/dev/null 2>&1; then + echo "::error::Modifying existing versioned schema files is not allowed. Modified file: $file" + exit 1 + fi + + # Extract version from filename + VERSION=$(echo "$file" | sed -n 's/.*pattern-schema-\([^/]*\)\.json/\1/p') + if [ -n "$VERSION" ]; then + # Construct expected ID for the new versioned file + EXPECTED_ID="https://raw.githubusercontent.com/ansible/pattern-service/main/specifications/pattern-schema/pattern-schema-${VERSION}.json" + + # Validate versioned schema file's $id field + ACTUAL_ID=$(git show HEAD:$file | jq -r '.["$id"] // empty') + if [ "$ACTUAL_ID" != "$EXPECTED_ID" ]; then + echo "::error::Schema file $file has incorrect \$id field. Expected: $EXPECTED_ID, Got: $ACTUAL_ID" + exit 1 + fi + + # Validate latest schema file $id points to new version + LATEST_ID=$(git show HEAD:specifications/pattern-schema/pattern-schema-latest.json | jq -r '.["$id"] // empty') + if [ "$LATEST_ID" != "$EXPECTED_ID" ]; then + echo "::error::Latest schema file was not updated to point to new version $VERSION. Expected \$id: $EXPECTED_ID, Got: $LATEST_ID" + exit 1 + fi + fi + fi + done <<< "$SCHEMA_FILES"