diff --git a/.github/workflows/validate-pattern-specs.yml b/.github/workflows/validate-pattern-specs.yml new file mode 100644 index 00000000..9c66e404 --- /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 + + # Then 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 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" diff --git a/specifications/pattern-specification/pattern-specification-2.0.0.md b/specifications/pattern-specification/pattern-specification-2.0.0.md new file mode 100644 index 00000000..6bff73e5 --- /dev/null +++ b/specifications/pattern-specification/pattern-specification-2.0.0.md @@ -0,0 +1,101 @@ +--- +title: Ansible Patterns Specification +author: Ansible Cloud Content team +version: 2.0.0 +--- + +# Ansible Pattern Specification + +## Introduction + +This document describes the technical specification for an Ansible pattern, an extension of collections, which are the standard method for packaging and distributing Ansible content. Patterns are used by Ansible Automation Platform (AAP) to offer users the ability to start automating with minimal setup, enhancing productivity and efficiency. + +A pattern defines an Ansible automation, such as a playbook, and the resources needed in AAP to run that automation, such as a project, execution environment, job template, and survey. The pattern definition can be consumed by the AAP API to create and configure those resources, allowing pattern catalogs to provide users with a seamless journey from identifying relevant patterns to using them in AAP. + +Patterns are delivered as files within an Ansible collection. This approach ensures consistent integration with the Ansible ecosystem by leveraging the existing collection framework. These patterns are bundled during collection packaging, enabling them to be searchable, downloadable, and publishable alongside roles, modules, and plugins. + +## Conventions + +This document follows the IETF [RFC2119](https://datatracker.ietf.org/doc/html/rfc2119) definitions of Key words for use in RFCs to Indicate Requirement Levels. + +## Support + +This pattern specification is supported and maintained by Ansible. To submit support or other requests for the specification, please contact Red Hat support. + +Individual patterns that implement this specification are the responsibility of the collection maintainers for the collections in which they reside. For support or feature requests for individual patterns, contact their collection owners. + +## Pattern Directory Structure + +- A pattern **MUST** be contained within a single directory in the `/extensions/patterns/` directory of an Ansible collection. +- An Ansible pattern directory name **MUST** be limited to 64 characters and **MUST** only include lowercase ASCII letters, digits, and underscores. +- An Ansible collection **MAY** contain zero or more patterns. + +## Required Files + +### `meta/pattern.json` + +The pattern definition meta file is the machine-readable entry point for creating an instance of the pattern in AAP. It defines the resources required to execute the pattern, such as a controller project, execution environment, job templates, and labels. The pattern definition also includes metadata about the pattern to enable its discovery and use, such as its title, audience, and tags. A [JSON schema](https://github.com/ansible/pattern-service/blob/main/specifications/pattern-schema/pattern-schema-1.0.0.json) has been published to aid with validation of the pattern definition file. + +- A pattern **MUST** include exactly one meta file defining the pattern metadata and AAP resources it requires. +- The pattern definition meta file **MUST** be a valid instance of the [Ansible pattern schema](https://github.com/ansible/pattern-service/blob/main/specifications/pattern-schema/pattern-schema-1.0.0.json). + +### `README.md` + +The README file is the human-readable documentation for the pattern. It provides information on what the pattern does, its inputs, its dependencies, and how it can be used. + +- A pattern **MUST** include a README file. +- The pattern README file **SHOULD** include all of the following: + - A description of what the pattern does + - A list of the AAP resources created by the pattern + - Documentation on how to use the pattern + +### `playbooks/` + +Pattern playbook files are included in the `/extensions/patterns//playbooks/` directory. + +- A pattern **MUST** contain a `playbooks/` directory. +- The `playbooks/` directory **MUST** contain at least one playbook associated with a job template definition in the pattern's `meta/pattern.json` file. +- A pattern **MAY** contain multiple playbooks. +- If a pattern contains multiple playbooks, it **MUST** define a primary playbook in its `meta/pattern.json` file. + +#### Playbook Requirements + +- All required and optional input variables to a pattern playbook **MUST** be defined in a play argument spec following this example of the [play argument spec format](https://github.com/ansible/ansible-creator/blob/main/src/ansible_creator/resources/playbook_project/argspec_validation_plays.meta.yml). +- If a pattern playbook requires any user-provided information other than variables to launch as a job template, such as inventory or credentials, those **MUST** be specified as `ask__at_launch` in the relevant `controller_job_templates` section of the pattern definition meta file. + +## Optional Files + +### `templates/` + +Templates for various types of catalog software in which patterns may be published. A template provides pattern data in the format required for a given catalog, such as Red Hat Developer Hub. + +- A pattern **MAY** contain a `templates/` directory to hold templates specific to catalogs that may publish the pattern, such as Red Hat Developer Hub or ServiceNow. +- The `templates/` directory **MAY** contain one or more catalog template files. + + +## Validation + +Pattern developers can use [ansible-lint](https://github.com/ansible/ansible-lint) to verify the structure of a pattern and its JSON against the pattern schema. + +## Example Pattern Directory + +```txt + +/extensions/patterns/ +├── network.backup/ # Backup pattern directory +│ ├── meta/pattern.json # Backup pattern definition +│ ├── README.md # Documentation for Backup pattern +│ ├── playbooks/ # Directory containing pattern playbooks +│ │ ├── backup.meta.yaml # Backup playbook arg spec +│ │ ├── backup.yaml # Backup playbook +│ ├── templates/ # Directory containing catalog templates +│ │ ├── rhdh.yaml # Display template for RHDH catalog + +``` + +## Other Pattern Requirements + +- A pattern **MUST** inherit the version number of the collection that contains it. +- A pattern **MUST** be valid according to the requirements specified in this document, including validation of each file contained in the pattern against its relevant schema. +- Changes to patterns **MUST** be noted in collection-level changelogs and release notes. +- All system, python, and Ansible collection dependencies needed to run a pattern's automations **MUST** be declared in the collection's dependency files, including but not limited to: `galaxy.yml`, `requirements.txt`, and `execution_environment.yml`.