├── .gitignore ├── .dockerignore ├── requirements.txt ├── example ├── file-valid-strict.yaml ├── schema.yaml ├── file-invalid.yaml └── file-valid-nostrict.yaml ├── tests ├── nostrict │ ├── schema.yaml │ ├── valid-matching-fields.yaml │ ├── invalid-nums-out-of-range.yaml │ ├── invalid-not-a-boolean.yaml │ └── valid-extra-argument.yaml └── strict │ ├── schema.yaml │ ├── valid-matching-fields.yaml │ └── invalid-extra-argument.yaml ├── .github ├── dependabot.yaml └── workflows │ ├── release.yaml │ ├── docker.yaml │ └── tests.yaml ├── Dockerfile ├── action.yml ├── LICENSE ├── entrypoint.sh └── README.md /.gitignore: -------------------------------------------------------------------------------- 1 | venv 2 | -------------------------------------------------------------------------------- /.dockerignore: -------------------------------------------------------------------------------- 1 | .git 2 | venv 3 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | yamale~=6.1.0 2 | -------------------------------------------------------------------------------- /example/file-valid-strict.yaml: -------------------------------------------------------------------------------- 1 | name: Bill 2 | age: 26 3 | height: 6.2 4 | awesome: True 5 | -------------------------------------------------------------------------------- /example/schema.yaml: -------------------------------------------------------------------------------- 1 | name: str() 2 | age: int(max=200) 3 | height: num() 4 | awesome: bool() 5 | -------------------------------------------------------------------------------- /tests/nostrict/schema.yaml: -------------------------------------------------------------------------------- 1 | name: str() 2 | age: int(max=200) 3 | height: num() 4 | awesome: bool() 5 | -------------------------------------------------------------------------------- /tests/nostrict/valid-matching-fields.yaml: -------------------------------------------------------------------------------- 1 | name: Bill 2 | age: 26 3 | height: 6.2 4 | awesome: True 5 | -------------------------------------------------------------------------------- /tests/strict/schema.yaml: -------------------------------------------------------------------------------- 1 | name: str() 2 | age: int(max=200) 3 | height: num() 4 | awesome: bool() 5 | -------------------------------------------------------------------------------- /tests/strict/valid-matching-fields.yaml: -------------------------------------------------------------------------------- 1 | name: Bill 2 | age: 26 3 | height: 6.2 4 | awesome: True 5 | -------------------------------------------------------------------------------- /tests/nostrict/invalid-nums-out-of-range.yaml: -------------------------------------------------------------------------------- 1 | name: Bill 2 | age: 420 3 | height: 6.2 4 | awesome: true 5 | -------------------------------------------------------------------------------- /example/file-invalid.yaml: -------------------------------------------------------------------------------- 1 | name: Bill 2 | age: 42000 3 | height: "not a number" 4 | awesome: "not a boolean" 5 | -------------------------------------------------------------------------------- /example/file-valid-nostrict.yaml: -------------------------------------------------------------------------------- 1 | name: Bill 2 | age: 26 3 | height: 6.2 4 | awesome: True 5 | extra: true 6 | -------------------------------------------------------------------------------- /tests/nostrict/invalid-not-a-boolean.yaml: -------------------------------------------------------------------------------- 1 | name: Bill 2 | age: 26 3 | height: 6.2 4 | awesome: "not a boolean" 5 | -------------------------------------------------------------------------------- /tests/nostrict/valid-extra-argument.yaml: -------------------------------------------------------------------------------- 1 | name: Bill 2 | age: 26 3 | height: 6.2 4 | awesome: True 5 | extra: true 6 | -------------------------------------------------------------------------------- /tests/strict/invalid-extra-argument.yaml: -------------------------------------------------------------------------------- 1 | name: Bill 2 | age: 26 3 | height: 6.2 4 | awesome: True 5 | extra: true 6 | -------------------------------------------------------------------------------- /.github/dependabot.yaml: -------------------------------------------------------------------------------- 1 | version: 2 2 | updates: 3 | - package-ecosystem: "github-actions" 4 | directory: "/" 5 | schedule: 6 | interval: "daily" 7 | - package-ecosystem: "pip" 8 | directory: "/" 9 | schedule: 10 | interval: "daily" 11 | -------------------------------------------------------------------------------- /.github/workflows/release.yaml: -------------------------------------------------------------------------------- 1 | name: Semantic release 2 | 3 | on: 4 | pull_request: 5 | branches: 6 | - main 7 | 8 | jobs: 9 | commitlint_and_release: 10 | name: Commit lint and release 11 | uses: nrkno/github-workflow-semantic-release/.github/workflows/workflow.yaml@v3.1.1 12 | with: 13 | runs-on: ubuntu-latest 14 | # Since we are following the Yamale major version scheme, we don't want 15 | # to accidentally release a new major version. 16 | release-enabled: false 17 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM alpine:3 2 | 3 | RUN apk add --no-cache bash python3 py3-pip \ 4 | && mkdir -p /usr/src/app \ 5 | && addgroup -g 10000 app \ 6 | && adduser -s /bin/bash -G app -u 10000 -h /usr/src/app -k /dev/null -D app \ 7 | && python3 -m venv /usr/src/app/venv \ 8 | && chown -R app:app /usr/src/app 9 | 10 | WORKDIR /usr/src/app 11 | USER 10000:10000 12 | ENV VIRTUAL_ENV=/usr/src/app/venv \ 13 | PATH=/usr/src/app/venv/bin:$PATH 14 | 15 | COPY requirements.txt ./ 16 | RUN pip install -r requirements.txt 17 | 18 | COPY entrypoint.sh /entrypoint.sh 19 | ENTRYPOINT ["/entrypoint.sh"] 20 | -------------------------------------------------------------------------------- /action.yml: -------------------------------------------------------------------------------- 1 | name: "YAML schema validation" 2 | description: "YAML schema validation using https://github.com/23andMe/Yamale" 3 | author: stigok 4 | branding: 5 | icon: "file-text" 6 | color: "blue" 7 | inputs: 8 | schema: 9 | description: "Yamale schema file" 10 | required: true 11 | target: 12 | description: "File to validate" 13 | required: true 14 | no-strict: 15 | description: "Disable strict mode" 16 | required: false 17 | error-is-success: 18 | description: "Flip the validation logic making a failing test pass and a passing test fail. This is used internally for testing the action itself." 19 | required: false 20 | runs: 21 | using: "docker" 22 | image: "docker://ghcr.io/nrkno/yaml-schema-validator-github-action:v6.1.0" 23 | env: 24 | # Need to convert dashes to underscores in environment variable names for bash 25 | # to be able to read them. 26 | # Github Actions passes them like INPUT_NO-STRICT when they contain dashes. 27 | INPUT_NO_STRICT: ${{ inputs.no-strict }} 28 | INPUT_ERROR_IS_SUCCESS: ${{ inputs.error-is-success }} 29 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 Norsk rikskringkasting (NRK) 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /entrypoint.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | set -eux 3 | 4 | # Returns a string `true` the string is considered boolean true, 5 | # otherwise `false`. An empty value is considered false. 6 | function str_bool { 7 | local str="${1:-false}" 8 | local pat='^(true|1|yes)$' 9 | if [[ "$str" =~ $pat ]] 10 | then 11 | echo 'true' 12 | else 13 | echo 'false' 14 | fi 15 | } 16 | 17 | schema="$INPUT_SCHEMA" 18 | target="$INPUT_TARGET" 19 | no_strict=$(str_bool "${INPUT_NO_STRICT:-}") 20 | error_is_success=$(str_bool "${INPUT_ERROR_IS_SUCCESS:-}") 21 | 22 | # Must end with a space here 23 | extra_args=' ' 24 | 25 | if [ ! -e "${schema}" ] 26 | then 27 | >&2 echo "Schema does not exist: $schema" 28 | exit 1 29 | fi 30 | 31 | # TODO: Allow directories 32 | if [ ! -e "${target}" ] 33 | then 34 | >&2 echo "Target does not exist: $target" 35 | exit 1 36 | fi 37 | 38 | if [ "$no_strict" = "true" ] 39 | then 40 | extra_args='--no-strict ' 41 | fi 42 | 43 | if [ "$error_is_success" = "true" ] 44 | then 45 | # Flipped validation logic 46 | echo "--- Flipped validation logic enabled (error-is-success: true)! ---" 47 | # shellcheck disable=SC2086 48 | yamale $extra_args --schema="${schema}" "$target" && exit 1 49 | exit 0 50 | fi 51 | 52 | # Normal execution 53 | # shellcheck disable=SC2086 54 | yamale $extra_args --schema="${schema}" "$target" 55 | -------------------------------------------------------------------------------- /.github/workflows/docker.yaml: -------------------------------------------------------------------------------- 1 | on: 2 | push: 3 | branches: 4 | - main 5 | paths: 6 | - requirements.txt 7 | 8 | env: 9 | REGISTRY: ghcr.io 10 | IMAGE_NAME: ${{ github.repository }} 11 | VERSION: v6.1.0 12 | 13 | jobs: 14 | release: 15 | runs-on: ubuntu-latest 16 | permissions: 17 | contents: write 18 | packages: write 19 | attestations: write 20 | id-token: write 21 | issues: write 22 | pull-requests: write 23 | steps: 24 | - name: Checkout repository 25 | uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5 26 | 27 | - name: Log in to the Container registry 28 | uses: docker/login-action@5e57cd118135c172c3672efd75eb46360885c0ef # v3 29 | with: 30 | registry: ${{ env.REGISTRY }} 31 | username: ${{ github.actor }} 32 | password: ${{ secrets.GITHUB_TOKEN }} 33 | 34 | - name: Set up Docker Buildx 35 | uses: docker/setup-buildx-action@e468171a9de216ec08956ac3ada2f0791b6bd435 # v3 36 | 37 | - name: Build and push Docker image 38 | id: push 39 | uses: docker/build-push-action@263435318d21b8e681c14492fe198d362a7d2c83 # v6 40 | with: 41 | context: . 42 | push: true 43 | tags: | 44 | ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ env.VERSION }} 45 | -------------------------------------------------------------------------------- /.github/workflows/tests.yaml: -------------------------------------------------------------------------------- 1 | name: Action tests 2 | 3 | on: 4 | pull_request: 5 | branches: 6 | - main 7 | 8 | jobs: 9 | schema: 10 | name: Run tests 11 | runs-on: ubuntu-latest 12 | 13 | strategy: 14 | fail-fast: false 15 | matrix: 16 | test: 17 | # Strict 18 | - suite: nostrict 19 | schema: ./tests/nostrict/schema.yaml 20 | target: ./tests/nostrict/valid-matching-fields.yaml 21 | no-strict: true 22 | error-is-success: false 23 | - suite: nostrict 24 | schema: ./tests/nostrict/schema.yaml 25 | target: ./tests/nostrict/valid-extra-argument.yaml 26 | no-strict: true 27 | error-is-success: false 28 | - suite: nostrict 29 | schema: ./tests/nostrict/schema.yaml 30 | target: ./tests/nostrict/invalid-not-a-boolean.yaml 31 | no-strict: true 32 | error-is-success: true 33 | - suite: nostrict 34 | schema: ./tests/nostrict/schema.yaml 35 | target: ./tests/nostrict/invalid-nums-out-of-range.yaml 36 | no-strict: true 37 | error-is-success: true 38 | 39 | # Nostrict 40 | - suite: strict 41 | schema: ./tests/strict/schema.yaml 42 | target: ./tests/strict/valid-matching-fields.yaml 43 | no-strict: false 44 | error-is-success: false 45 | - suite: strict 46 | schema: ./tests/strict/schema.yaml 47 | target: ./tests/strict/invalid-extra-argument.yaml 48 | no-strict: false 49 | error-is-success: true 50 | 51 | steps: 52 | - uses: actions/checkout@v4.2.2 53 | 54 | - name: Use Dockerfile action for local 55 | run: | 56 | sed -i 's|image: "docker://[^"]*"|image: "Dockerfile"|' action.yml 57 | 58 | - uses: ./ 59 | name: "Test: ${{ matrix.test.suite }} ${{ matrix.test.target }}" 60 | with: 61 | schema: ${{ matrix.test.schema }} 62 | target: ${{ matrix.test.target }} 63 | no-strict: ${{ matrix.test.no-strict }} 64 | error-is-success: ${{ matrix.test.error-is-success }} 65 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # yaml-schema-validator-github-action 2 | 3 | A GitHub action that uses [Yamale][] for YAML schema validation. 4 | 5 | ## Usage 6 | 7 | - Filenames are relative to the repository root. 8 | - Disable strict checking by setting `no-strict` to `true`, `1` or `yes`. 9 | - For help with the schema definitions and reference, see [Yamale][]. 10 | 11 | The following example sets up a check to validate a YAML file in your 12 | repository, *target.yaml*, using a schema defined in *schemas/schema.yaml*: 13 | 14 | ``` 15 | name: YAML schema validator 16 | on: [push] 17 | 18 | jobs: 19 | yaml-schema-validation: 20 | runs-on: ubuntu-latest 21 | steps: 22 | - uses: actions/checkout 23 | - uses: nrkno/yaml-schema-validator-github-action@v5 24 | with: 25 | schema: schemas/schema.yaml 26 | target: target.yaml 27 | # Uncomment to disable strict checks 28 | # no-strict: true 29 | ``` 30 | 31 | ## Versioning 32 | 33 | This action is meant to be a wrapper around Yamale, so as of version 4.x 34 | of Yamale, this action will follow Yamale's major version scheme. 35 | 36 | To bind the action to a specific release, suffix with `@`. 37 | E.g. `nrkno/yaml-schema-validator-github-action@v4`. 38 | 39 | https://help.github.com/en/actions/reference/workflow-syntax-for-github-actions#jobsjob_idstepsuses 40 | 41 | ## Developing 42 | 43 | Create and enable a Python virtualenv 44 | 45 | ``` 46 | $ python -m venv venv 47 | $ source venv/bin/activate 48 | ``` 49 | 50 | Install dependencies 51 | 52 | ``` 53 | $ pip install -r requirements.txt 54 | ``` 55 | 56 | Do a test-run with one of the provided examples 57 | 58 | ``` 59 | $ INPUT_SCHEMA=example/schema.yaml \ 60 | INPUT_TARGET=example/file-valid-strict.yaml \ 61 | ./entrypoint.sh 62 | ``` 63 | 64 | [Yamale]: https://github.com/23andMe/Yamale 65 | 66 | 67 | # Releasing 68 | 69 | Releases are created manually, with the release version matching your yamale version in `requirements.txt`. 70 | 1. Update the version tag in the "docker.yaml" github workflow, and "requirements.txt". 71 | 2. Make a pull request 72 | 3. When the pull request is merged, "docker.yaml" will run and publish a new docker image to ghcr. 73 | 4. Make a release with the same version. 74 | --------------------------------------------------------------------------------