├── CODEOWNERS ├── docs ├── workflow-restarter │ ├── image.png │ ├── image-1.png │ ├── image-2.png │ ├── image-3.png │ ├── image-4.png │ ├── workflow-restarter.yml │ └── workflow-restarter-test.yml ├── reviewdog-shellcheck.md ├── how-to │ └── how_to_inject_puppetcore_authentication_into_the_shared_workflows.md └── workflow-restarter.md ├── .yamllint ├── .github ├── dependabot.yml ├── workflows │ ├── lint.yml │ ├── workflow-restarter.yml │ ├── gem_acceptance.yml │ ├── workflow-restarter-test.yml │ ├── mend_ruby.yml │ ├── module_release.yml │ ├── gem_release.yml │ ├── gem_release_prep.yml │ ├── tooling_mend_ruby.yml │ ├── gem_ci.yml │ ├── module_release_prep.yml │ ├── module_ci.yml │ └── module_acceptance.yml ├── pull_request_template.md └── actions │ └── workflow-restarter-proxy │ └── action.yml ├── example └── module │ ├── mend.yml │ ├── nightly.yml │ ├── labeller.yml │ ├── release_prep.yml │ ├── ci.yml │ └── release.yml ├── README.md └── LICENSE /CODEOWNERS: -------------------------------------------------------------------------------- 1 | # Setting ownership to the devx and modules team 2 | * @puppetlabs/devx @puppetlabs/modules 3 | -------------------------------------------------------------------------------- /docs/workflow-restarter/image.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/puppetlabs/cat-github-actions/main/docs/workflow-restarter/image.png -------------------------------------------------------------------------------- /.yamllint: -------------------------------------------------------------------------------- 1 | --- 2 | extends: default 3 | 4 | rules: 5 | document-start: disable 6 | truthy: disable 7 | line-length: disable 8 | 9 | -------------------------------------------------------------------------------- /docs/workflow-restarter/image-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/puppetlabs/cat-github-actions/main/docs/workflow-restarter/image-1.png -------------------------------------------------------------------------------- /docs/workflow-restarter/image-2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/puppetlabs/cat-github-actions/main/docs/workflow-restarter/image-2.png -------------------------------------------------------------------------------- /docs/workflow-restarter/image-3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/puppetlabs/cat-github-actions/main/docs/workflow-restarter/image-3.png -------------------------------------------------------------------------------- /docs/workflow-restarter/image-4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/puppetlabs/cat-github-actions/main/docs/workflow-restarter/image-4.png -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | 3 | updates: 4 | - package-ecosystem: "github-actions" 5 | directory: "/" 6 | schedule: 7 | interval: "weekly" 8 | -------------------------------------------------------------------------------- /example/module/mend.yml: -------------------------------------------------------------------------------- 1 | name: "mend" 2 | 3 | on: 4 | pull_request: 5 | branches: 6 | - "main" 7 | schedule: 8 | - cron: "0 0 * * *" 9 | workflow_dispatch: 10 | 11 | jobs: 12 | 13 | mend: 14 | uses: "puppetlabs/cat-github-actions/.github/workflows/mend_ruby.yml@main" 15 | secrets: "inherit" 16 | -------------------------------------------------------------------------------- /example/module/nightly.yml: -------------------------------------------------------------------------------- 1 | name: "nightly" 2 | 3 | on: 4 | schedule: 5 | - cron: "0 0 * * *" 6 | workflow_dispatch: 7 | 8 | jobs: 9 | Spec: 10 | uses: "puppetlabs/cat-github-actions/.github/workflows/module_ci.yml@main" 11 | secrets: "inherit" 12 | 13 | Acceptance: 14 | needs: Spec 15 | uses: "puppetlabs/cat-github-actions/.github/workflows/module_acceptance.yml@main" 16 | secrets: "inherit" 17 | -------------------------------------------------------------------------------- /example/module/labeller.yml: -------------------------------------------------------------------------------- 1 | name: Labeller 2 | 3 | on: 4 | issues: 5 | types: [ opened, reopened, labeled, unlabeled ] 6 | pull_request: 7 | types: 8 | - opened 9 | - labeled 10 | - unlabeled 11 | 12 | jobs: 13 | label: 14 | if: contains(fromJson('["puppetlabs", "puppet-toy-chest"]'), github.repository_owner) 15 | uses: "puppetlabs/cat-github-actions/.github/workflows/labeller.yml@main" 16 | secrets: inherit 17 | -------------------------------------------------------------------------------- /.github/workflows/lint.yml: -------------------------------------------------------------------------------- 1 | name: "lint" 2 | 3 | on: 4 | push: 5 | branches: 6 | - "main" 7 | pull_request: 8 | branches: 9 | - "main" 10 | 11 | jobs: 12 | lint: 13 | runs-on: "ubuntu-latest" 14 | steps: 15 | 16 | - name: "Checkout" 17 | uses: "actions/checkout@v4" 18 | 19 | - name: "Run yaml-lint" 20 | uses: "ibiqlik/action-yamllint@v3" 21 | with: 22 | file_or_dir: ".github/" 23 | config_file: ".yamllint" 24 | -------------------------------------------------------------------------------- /example/module/release_prep.yml: -------------------------------------------------------------------------------- 1 | name: "Release Prep" 2 | run-name: > 3 | version=${{ inputs.version }} 4 | 5 | on: 6 | workflow_dispatch: 7 | inputs: 8 | version: 9 | description: "Module version to be released. Must be a valid semver string. (1.2.3)" 10 | required: true 11 | 12 | jobs: 13 | release_prep: 14 | uses: "puppetlabs/cat-github-actions/.github/workflows/module_release_prep.yml@main" 15 | with: 16 | version: "${{ inputs.version }}" 17 | secrets: "inherit" 18 | -------------------------------------------------------------------------------- /.github/pull_request_template.md: -------------------------------------------------------------------------------- 1 | ## Summary 2 | Provide a detailed description of all the changes present in this pull request. 3 | 4 | ## Additional Context 5 | Add any additional context about the problem here. 6 | - [ ] Root cause and the steps to reproduce. (If applicable) 7 | - [ ] Thought process behind the implementation. 8 | 9 | ## Related Issues (if any) 10 | Mention any related issues or pull requests. 11 | 12 | ## Checklist 13 | - [ ] 🟢 Spec tests. 14 | - [ ] 🟢 Acceptance tests. 15 | - [ ] Manually verified. 16 | -------------------------------------------------------------------------------- /example/module/ci.yml: -------------------------------------------------------------------------------- 1 | name: "CI" 2 | 3 | on: 4 | pull_request: 5 | branches: 6 | - "main" 7 | workflow_dispatch: 8 | 9 | concurrency: 10 | group: ${{ github.workflow }}-${{ github.ref }} 11 | cancel-in-progress: true 12 | 13 | jobs: 14 | Spec: 15 | uses: "puppetlabs/cat-github-actions/.github/workflows/module_ci.yml@main" 16 | secrets: "inherit" 17 | 18 | Acceptance: 19 | needs: Spec 20 | uses: "puppetlabs/cat-github-actions/.github/workflows/module_acceptance.yml@main" 21 | secrets: "inherit" 22 | -------------------------------------------------------------------------------- /docs/reviewdog-shellcheck.md: -------------------------------------------------------------------------------- 1 | ## Attention 2 | 3 | Reviewdog/shellcheck is a third-party action that we have implemented in our testing workflows. As such, 4 | if your repository has strict permission control, you might need to add it as a trusted action. Otherwise, 5 | you might run into the following or similar errors: 6 | 7 | `reviewdog/action-shellcheck@v1 is not allowed to be used in . Actions in this workflow must be: within a repository that belongs to your Enterprise account, created by GitHub, verified in the GitHub Marketplace, or matching the following: ruby/*, puppetlabs/*, docker://puppet/*, luchihoratiu/*, peter-evans/*.` 8 | -------------------------------------------------------------------------------- /docs/workflow-restarter/workflow-restarter.yml: -------------------------------------------------------------------------------- 1 | # target-repo/.github/workflows/call-reusable-workflow.yml 2 | name: Workflow Restarter 3 | on: 4 | workflow_dispatch: 5 | inputs: 6 | repo: 7 | description: "GitHub repository name." 8 | required: true 9 | type: string 10 | run_id: 11 | description: "The ID of the workflow run to rerun." 12 | required: true 13 | type: string 14 | retries: 15 | description: "The number of times to retry the workflow run." 16 | required: false 17 | type: string 18 | default: "3" 19 | 20 | jobs: 21 | call-reusable-workflow: 22 | uses: "puppetlabs/cat-github-actions/.github/workflows/workflow-restarter.yml@main" 23 | with: 24 | repo: ${{ inputs.repo }} 25 | run_id: ${{ inputs.run_id }} 26 | retries: ${{ inputs.retries }} 27 | -------------------------------------------------------------------------------- /example/module/release.yml: -------------------------------------------------------------------------------- 1 | name: "Publish module" 2 | run-name: > 3 | ${{ format('tag={0}', inputs.tag) }} 4 | ${{ format('release={0}', inputs.release) }} 5 | ${{ format('publish={0}', inputs.publish) }} 6 | ${{ format('edit={0}', inputs.edit) }} 7 | 8 | on: 9 | workflow_dispatch: 10 | inputs: 11 | tag: 12 | description: "Leave blank to tag HEAD of branch, or existing tag to edit" 13 | default: '' 14 | type: string 15 | release: 16 | description: "Create a Github release" 17 | type: boolean 18 | default: true 19 | publish: 20 | description: "Publish to the Forge" 21 | type: boolean 22 | default: true 23 | edit: 24 | description: "Regenerate release notes and existing tag" 25 | default: false 26 | type: boolean 27 | 28 | jobs: 29 | release: 30 | uses: "puppetlabs/cat-github-actions/.github/workflows/module_release.yml@forked-modules" 31 | secrets: "inherit" 32 | with: 33 | tag: ${{ inputs.tag }} 34 | release: ${{ inputs.release }} 35 | publish: ${{ inputs.publish }} 36 | edit: ${{ inputs.edit }} 37 | -------------------------------------------------------------------------------- /.github/workflows/workflow-restarter.yml: -------------------------------------------------------------------------------- 1 | --- 2 | name: Workflow Restarter 3 | on: 4 | workflow_call: 5 | inputs: 6 | repo: 7 | description: "GitHub repository name." 8 | required: true 9 | type: string 10 | run_id: 11 | description: "The ID of the workflow run to rerun." 12 | required: true 13 | type: string 14 | retries: 15 | description: "The number of times to retry the workflow run." 16 | required: false 17 | type: string 18 | default: "3" 19 | 20 | jobs: 21 | rerun: 22 | runs-on: ubuntu-latest 23 | steps: 24 | - name: Checkout code 25 | uses: actions/checkout@v4 26 | 27 | - name: Check retry count 28 | id: check-retry 29 | run: | 30 | # IF `--attempts` returns a non-zero exit code, then keep retrying 31 | status_code=$(gh run view ${{ inputs.run_id }} --repo ${{ inputs.repo }} --attempt ${{ inputs.retries }} --json status) || { 32 | echo "Retry count is within limit" 33 | echo "should_retry=true" >> $GITHUB_OUTPUT 34 | exit 0 35 | } 36 | 37 | # ELSE `--attempts` returns a zero exit code, so stop retrying 38 | echo "Retry count has reached the limit" 39 | echo "should_retry=false" >> $GITHUB_OUTPUT 40 | env: 41 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 42 | 43 | - name: Re-run failed jobs 44 | if: ${{ steps.check-retry.outputs.should_retry == 'true' }} 45 | run: gh run rerun --failed ${{ inputs.run_id }} --repo ${{ inputs.repo }} 46 | continue-on-error: true 47 | env: 48 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 49 | -------------------------------------------------------------------------------- /.github/workflows/gem_acceptance.yml: -------------------------------------------------------------------------------- 1 | # This is a generic workflow for gem Acceptance operations. 2 | name: "Gem Acceptance" 3 | 4 | on: 5 | workflow_call: 6 | inputs: 7 | ruby_version: 8 | description: "The target Ruby version." 9 | required: false 10 | default: "3.1" 11 | type: "string" 12 | puppet_version: 13 | description: "The target Puppet version." 14 | required: false 15 | default: "puppet8-nightly" 16 | type: "string" 17 | rake_task: 18 | description: "The name of the rake task that executes acceptance tests" 19 | required: false 20 | default: "acceptance" 21 | type: "string" 22 | runs_on: 23 | description: "The operating system used for the runner." 24 | required: false 25 | default: "ubuntu-latest" 26 | type: "string" 27 | 28 | jobs: 29 | acceptance: 30 | name: "acceptance" 31 | runs-on: ${{ inputs.runs_on }} 32 | 33 | env: 34 | PUPPET_GEM_VERSION: ${{ inputs.puppet_version }} 35 | 36 | steps: 37 | 38 | - name: "checkout" 39 | uses: "actions/checkout@v4" 40 | 41 | - name: "export environment" 42 | run: | 43 | echo "PUPPET_VERSION=${{ inputs.puppet_version }}" >> $GITHUB_ENV 44 | echo "PUPPET_GEM_VERSION=${{ inputs.puppet_version }}" >> $GITHUB_ENV 45 | 46 | - name: "setup ruby" 47 | uses: "ruby/setup-ruby@v1" 48 | with: 49 | ruby-version: ${{ inputs.ruby_version }} 50 | bundler-cache: true 51 | 52 | - name: "bundle environment" 53 | run: | 54 | echo ::group::bundler environment 55 | bundle env 56 | echo ::endgroup:: 57 | 58 | - name: "execute acceptance tests" 59 | run: | 60 | # This generic task to run acceptance tests. 61 | # It should be overridden in the Rakefile. 62 | bundle exec rake ${{ inputs.rake_task }} 63 | -------------------------------------------------------------------------------- /.github/workflows/workflow-restarter-test.yml: -------------------------------------------------------------------------------- 1 | name: Workflow Restarter TEST 2 | 3 | on: 4 | workflow_dispatch: 5 | inputs: 6 | fail: 7 | description: > 8 | For (acceptance, unit) jobs: 9 | 'true' = (fail, succeed) and 10 | 'false' = (succeed, fail) 11 | required: true 12 | default: 'true' 13 | env: 14 | SOURCE_GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 15 | 16 | jobs: 17 | unit: 18 | runs-on: ubuntu-latest 19 | steps: 20 | - name: Check outcome 21 | run: | 22 | if [ "${{ github.event.inputs.fail }}" = "true" ]; then 23 | echo "'unit' job succeeded" 24 | exit 0 25 | else 26 | echo "'unit' job failed" 27 | exit 1 28 | fi 29 | acceptance: 30 | runs-on: ubuntu-latest 31 | steps: 32 | - name: Check outcome 33 | run: | 34 | if [ "${{ github.event.inputs.fail }}" = "true" ]; then 35 | echo "'acceptance' job failed" 36 | exit 1 37 | else 38 | echo "'acceptance' job succeeded" 39 | exit 0 40 | fi 41 | 42 | on-failure-workflow-restarter-proxy: 43 | # (1) run this job after the "acceptance" job and... 44 | needs: [acceptance, unit] 45 | # (2) continue ONLY IF "acceptance" fails 46 | if: always() && needs.acceptance.result == 'failure' || needs.unit.result == 'failure' 47 | runs-on: ubuntu-latest 48 | steps: 49 | # (3) checkout this repository in order to "see" the following custom action 50 | - name: Checkout repository 51 | uses: actions/checkout@v4 52 | 53 | - name: Trigger reusable workflow 54 | uses: "puppetlabs/cat-github-actions/.github/actions/workflow-restarter-proxy@main" 55 | env: 56 | SOURCE_GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 57 | with: 58 | repository: ${{ github.repository }} 59 | run_id: ${{ github.run_id }} 60 | -------------------------------------------------------------------------------- /docs/workflow-restarter/workflow-restarter-test.yml: -------------------------------------------------------------------------------- 1 | name: Workflow Restarter TEST 2 | 3 | on: 4 | workflow_dispatch: 5 | inputs: 6 | fail: 7 | description: > 8 | For (acceptance, unit) jobs: 9 | 'true' = (fail, succeed) and 10 | 'false' = (succeed, fail) 11 | required: true 12 | default: 'true' 13 | env: 14 | SOURCE_GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 15 | 16 | jobs: 17 | unit: 18 | runs-on: ubuntu-latest 19 | steps: 20 | - name: Check outcome 21 | run: | 22 | if [ "${{ github.event.inputs.fail }}" = "true" ]; then 23 | echo "'unit' job succeeded" 24 | exit 0 25 | else 26 | echo "'unit' job failed" 27 | exit 1 28 | fi 29 | acceptance: 30 | runs-on: ubuntu-latest 31 | steps: 32 | - name: Check outcome 33 | run: | 34 | if [ "${{ github.event.inputs.fail }}" = "true" ]; then 35 | echo "'acceptance' job failed" 36 | exit 1 37 | else 38 | echo "'acceptance' job succeeded" 39 | exit 0 40 | fi 41 | 42 | on-failure-workflow-restarter-proxy: 43 | # (1) run this job after the "acceptance" job and... 44 | needs: [acceptance, unit] 45 | # (2) continue ONLY IF "acceptance" fails 46 | if: always() && needs.acceptance.result == 'failure' || needs.unit.result == 'failure' 47 | runs-on: ubuntu-latest 48 | steps: 49 | # (3) checkout this repository in order to "see" the following custom action 50 | - name: Checkout repository 51 | uses: actions/checkout@v4 52 | 53 | # (4) "use" the custom action to retrigger the failed "acceptance job" above 54 | # NOTE: pass the SOURCE_GITHUB_TOKEN to the custom action because (a) it must have 55 | # this to trigger the reusable workflow that restarts the failed job; and 56 | # (b) custom actions do not have access to the calling workflow's secrets 57 | - name: Trigger reusable workflow 58 | uses: "puppetlabs/cat-github-actions/.github/actions/workflow-restarter-proxy@main" 59 | env: 60 | SOURCE_GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 61 | with: 62 | repository: ${{ github.repository }} 63 | run_id: ${{ github.run_id }} 64 | -------------------------------------------------------------------------------- /.github/actions/workflow-restarter-proxy/action.yml: -------------------------------------------------------------------------------- 1 | --- 2 | name: 'Workflow Restarter Proxy' 3 | description: | 4 | This custom action acts as a proxy to trigger the reusable workflow that restarts a failed job. 5 | NOTE: This action cannot itself do the re-start because in effect it's composite steps get folded 6 | into the source workflow, the one that "uses" this custom action. Since github does not allow a workflow 7 | to retrigger itself, then the source workflow must be triggered not by this but by another workflow. 8 | Therefore, this custom action triggers that other workflow. 9 | inputs: 10 | repository: 11 | description: 'Should be set to github.repository via the calling workflow' 12 | required: true 13 | run_id: 14 | description: 'Should be set to github.run_id via the calling workflow' 15 | required: true 16 | runs: 17 | using: 'composite' 18 | steps: 19 | # ABORT if not SOURCE_GITHUB_TOKEN environment variable set 20 | - name: Check for presence of SOURCE_GITHUB_TOKEN environment variable 21 | shell: bash 22 | run: | 23 | if [[ -z "${{ env.SOURCE_GITHUB_TOKEN }}" ]]; then 24 | echo "ERROR: \$SOURCE_GITHUB_TOKEN must be set by the calling workflow" 1>&2 && exit 1 25 | fi 26 | 27 | # checkout the repository because I want bundler to have access to my Gemfile 28 | - name: Checkout repository 29 | uses: actions/checkout@v4 30 | 31 | # setup ruby including a bundle install of my Gemfile 32 | - name: Set up Ruby and install Octokit 33 | uses: ruby/setup-ruby@v1 34 | with: 35 | ruby-version: '3' 36 | bundler-cache: true # 'bundle install' will be run and gems cached for faster workflow runs 37 | 38 | # Trigger the reusable workflow 39 | - name: Trigger reusable workflow 40 | shell: bash 41 | run: | 42 | gem install octokit 43 | ruby -e " 44 | require 'octokit' 45 | client = Octokit::Client.new(:access_token => '${{ env.SOURCE_GITHUB_TOKEN }}') 46 | client.post( 47 | '/repos/${{ inputs.repository }}/actions/workflows/workflow-restarter.yml/dispatches', 48 | { 49 | ref: 'main', 50 | inputs: { 51 | repo: '${{ inputs.repository }}', 52 | run_id: '${{ inputs.run_id }}' 53 | } 54 | } 55 | ) 56 | " 57 | -------------------------------------------------------------------------------- /.github/workflows/mend_ruby.yml: -------------------------------------------------------------------------------- 1 | # This is a generic workfloww that can be used to scan 2 | # content-and-tooling projects for vulnerabilities. 3 | name: mend 4 | 5 | on: 6 | workflow_call: 7 | inputs: 8 | api_key: 9 | default: '' 10 | type: string 11 | token: 12 | default: '' 13 | type: string 14 | product_name: 15 | default: 'content-and-tooling' 16 | type: string 17 | 18 | env: 19 | MEND_API_KEY: ${{ secrets.MEND_API_KEY != '' && secrets.MEND_API_KEY || inputs.api_key }} 20 | MEND_TOKEN: ${{ secrets.MEND_TOKEN != '' && secrets.MEND_TOKEN || inputs.token }} 21 | PRODUCT_NAME: ${{ inputs.PRODUCT_NAME != '' && inputs.PRODUCT_NAME || inputs.product_name }} 22 | REQUIRE_SECRETS: MEND_API_KEY MEND_TOKEN 23 | 24 | jobs: 25 | mend: 26 | if: github.event.pull_request.head.repo.fork == false 27 | runs-on: "ubuntu-latest" 28 | continue-on-error: ${{ contains(fromJson('["puppetlabs","puppet-toy-chest"]'), github.repository_owner) != true }} 29 | steps: 30 | - name: "check requirements" 31 | run: | 32 | declare -a MISSING 33 | for V in ${REQUIRE_SECRETS} ; do 34 | [[ -z "${!V}" ]] && MISSING+=($V) 35 | done 36 | if [ ${#MISSING[@]} -gt 0 ] ; then 37 | echo "::warning::missing required secrets: ${MISSING[@]}" 38 | exit 1 39 | fi 40 | 41 | - name: "checkout" 42 | if: success() 43 | uses: "actions/checkout@v4" 44 | with: 45 | fetch-depth: 1 46 | 47 | - name: "setup ruby" 48 | if: success() 49 | uses: "ruby/setup-ruby@v1" 50 | with: 51 | ruby-version: 3.1 52 | 53 | - name: "bundle lock" 54 | if: success() 55 | run: bundle lock 56 | 57 | - uses: "actions/setup-java@v4" 58 | if: success() 59 | with: 60 | distribution: "temurin" 61 | java-version: "17" 62 | 63 | - name: "download" 64 | if: success() 65 | run: curl -o wss-unified-agent.jar https://unified-agent.s3.amazonaws.com/wss-unified-agent.jar 66 | 67 | - name: "scan" 68 | if: success() 69 | run: java -jar wss-unified-agent.jar 70 | env: 71 | WS_APIKEY: ${{ env.MEND_API_KEY }} 72 | WS_WSS_URL: https://saas-eu.whitesourcesoftware.com/agent 73 | WS_USERKEY: ${{ env.MEND_TOKEN }} 74 | WS_PRODUCTNAME: ${{ env.PRODUCT_NAME }} 75 | WS_PROJECTNAME: ${{ github.event.repository.name }} 76 | -------------------------------------------------------------------------------- /.github/workflows/module_release.yml: -------------------------------------------------------------------------------- 1 | # This is a generic workflow for releasing a Puppet module. 2 | # It requires that the caller sets `secrets: inherit` to ensure 3 | # that secrets are visible from steps in this workflow. 4 | name: "Module Release" 5 | 6 | on: 7 | workflow_call: 8 | 9 | jobs: 10 | release: 11 | name: "Release" 12 | runs-on: "ubuntu-latest" 13 | if: github.repository_owner == 'puppetlabs' 14 | 15 | steps: 16 | 17 | - name: "Set up Ruby" 18 | uses: "ruby/setup-ruby@v1" 19 | with: 20 | ruby-version: "3.1" 21 | 22 | - name: "Checkout" 23 | uses: "actions/checkout@v4" 24 | with: 25 | ref: "${{ github.ref }}" 26 | clean: true 27 | fetch-depth: 0 28 | 29 | - name: "Get version" 30 | id: "get_version" 31 | run: | 32 | ruby <<-EOF >> $GITHUB_OUTPUT 33 | require "json" 34 | version = JSON.parse(File.read("metadata.json"))["version"] 35 | 36 | # from https://github.com/voxpupuli/metadata-json-lint/blob/b5e68049c6be58aa63263357bb0dcad8635a6829/lib/metadata-json-lint/schema.rb#L141-L150 37 | numeric = '(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)' # Major . Minor . Patch 38 | pre = '(?:-([0-9A-Za-z-]+(?:\.[0-9A-Za-z-]+)*))?' # Prerelease 39 | build = '(?:\+([0-9A-Za-z-]+(?:\.[0-9A-Za-z-]+)*))?' # Build 40 | full = numeric + pre + build 41 | semver_regex = /\A#{full}\Z/ 42 | if version&.match?(semver_regex) 43 | puts "version=#{version}" 44 | else 45 | raise "Version #{version} is invalid. Exiting workflow." 46 | end 47 | EOF 48 | 49 | - name: "PDK build" 50 | uses: "docker://puppet/pdk:nightly" 51 | with: 52 | args: "build" 53 | 54 | - name: "Generate release notes" 55 | run: | 56 | export GH_HOST=github.com 57 | gh extension install chelnak/gh-changelog 58 | gh changelog get --latest > OUTPUT.md 59 | env: 60 | GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} 61 | 62 | - name: "Create release" 63 | run: | 64 | gh release create v${{ steps.get_version.outputs.version }} --title v${{ steps.get_version.outputs.version }} -F OUTPUT.md 65 | env: 66 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 67 | 68 | - name: "Publish module" 69 | uses: "docker://puppet/pdk:nightly" 70 | with: 71 | args: 'release publish --forge-token ${{ secrets.FORGE_API_KEY }} --force' 72 | -------------------------------------------------------------------------------- /.github/workflows/gem_release.yml: -------------------------------------------------------------------------------- 1 | # This is a generic workflow for releasing gems. 2 | # It requires that the caller sets `secrets: inherit` to ensure 3 | # that secrets are visible from steps in this workflow. 4 | name: "Gem Release" 5 | 6 | on: 7 | workflow_call: 8 | inputs: 9 | target: 10 | description: "The target for the release. This can be a commit sha or a branch." 11 | required: false 12 | default: "main" 13 | type: "string" 14 | 15 | jobs: 16 | release: 17 | name: "Release" 18 | runs-on: "ubuntu-latest" 19 | if: github.repository_owner == 'puppetlabs' 20 | 21 | steps: 22 | 23 | - name: "Checkout" 24 | uses: "actions/checkout@v4" 25 | with: 26 | ref: ${{ github.event.inputs.target }} 27 | clean: true 28 | fetch-depth: 0 29 | 30 | - name: "Setup ruby" 31 | uses: "ruby/setup-ruby@v1" 32 | with: 33 | ruby-version: "3.1" 34 | bundler-cache: "true" 35 | 36 | - name: "Bundle environment" 37 | run: | 38 | echo ::group::bundler environment 39 | bundle env 40 | echo ::endgroup:: 41 | 42 | - name: "Get version" 43 | id: "get_version" 44 | run: | 45 | echo "version=$(ruby -e "require 'rubygems'; puts Gem::Specification::load(Dir.glob('*.gemspec').first).version.to_s")" >> $GITHUB_OUTPUT 46 | 47 | - name: "Build" 48 | run: | 49 | bundle exec rake build 50 | 51 | - name: "Generate release notes" 52 | run: | 53 | export GH_HOST=github.com 54 | gh extension install chelnak/gh-changelog 55 | gh changelog get --latest > OUTPUT.md 56 | env: 57 | GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} 58 | 59 | - name: "Create GitHub release" 60 | run: | 61 | gh release create v${{ steps.get_version.outputs.version }} ./pkg/*.gem --title v${{ steps.get_version.outputs.version }} -F OUTPUT.md 62 | env: 63 | GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} 64 | 65 | - name: "Publish gem to rubygems" 66 | run: | 67 | gem push ./pkg/*.gem 68 | env: 69 | GEM_HOST_API_KEY: '${{ secrets.GEM_HOST_API_KEY }}' 70 | 71 | - name: "Publish to GitHub Package" 72 | run: | 73 | echo "Setting up access to RubyGems" 74 | mkdir -p ~/.gem 75 | touch ~/.gem/credentials 76 | chmod 600 ~/.gem/credentials 77 | 78 | echo "Logging in to GitHub Package Registry" 79 | echo "---" > ~/.gem/credentials 80 | echo ":github: Bearer ${{ secrets.GITHUB_TOKEN }}" >> ~/.gem/credentials 81 | 82 | echo "Pushing gem to GitHub Package Registry" 83 | gem push --key "github" --host "https://rubygems.pkg.github.com/${{github.repository_owner}}" ./pkg/*.gem 84 | -------------------------------------------------------------------------------- /.github/workflows/gem_release_prep.yml: -------------------------------------------------------------------------------- 1 | # This is a generic workflow for creating a release prep 2 | # pr for a gem project. 3 | # It requires that the caller sets `secrets: inherit` to ensure 4 | # that secrets are visible from steps in this workflow. 5 | name: "Gem Release Prep" 6 | 7 | on: 8 | workflow_call: 9 | inputs: 10 | target: 11 | description: "The target for the release. This can be a commit sha or a branch." 12 | required: false 13 | default: "main" 14 | type: "string" 15 | version: 16 | description: "Version of gem to be released." 17 | required: true 18 | type: "string" 19 | 20 | jobs: 21 | release_prep: 22 | name: "Release Prep" 23 | runs-on: "ubuntu-latest" 24 | 25 | steps: 26 | 27 | - name: "Checkout" 28 | uses: "actions/checkout@v4" 29 | with: 30 | ref: ${{ github.event.inputs.target }} 31 | clean: true 32 | fetch-depth: 0 33 | 34 | - name: "setup ruby" 35 | uses: "ruby/setup-ruby@v1" 36 | with: 37 | ruby-version: "3.1" 38 | bundler-cache: "true" 39 | 40 | - name: "bundle environment" 41 | run: | 42 | echo ::group::bundler environment 43 | bundle env 44 | echo ::endgroup:: 45 | 46 | - name: "Update Version" 47 | run: | 48 | current_version=$(ruby -e "require 'rubygems'; puts Gem::Specification::load(Dir.glob('*.gemspec').first).version.to_s") 49 | normalized_version=$(echo ${{ github.event.inputs.version }} | sed "s/-/./g") 50 | sed -i "s/$current_version/$normalized_version/g" $(find . -path './lib/**' -name 'version.rb' -not -path "vendor/*") 51 | 52 | - name: "Generate changelog" 53 | run: | 54 | export GH_HOST=github.com 55 | gh extension install chelnak/gh-changelog 56 | gh changelog new --next-version v${{ github.event.inputs.version }} 57 | env: 58 | GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} 59 | 60 | - name: "Commit changes" 61 | run: | 62 | git config --local user.email "${{ github.repository_owner }}@users.noreply.github.com" 63 | git config --local user.name "GitHub Actions" 64 | git add . 65 | git commit -m "Release prep v${{ github.event.inputs.version }}" 66 | 67 | - name: "Create pull request" 68 | uses: "peter-evans/create-pull-request@v7" 69 | with: 70 | token: ${{ secrets.GITHUB_TOKEN }} 71 | commit-message: "Release prep v${{ github.event.inputs.version }}" 72 | branch: "release-prep" 73 | delete-branch: true 74 | title: "Release prep v${{ github.event.inputs.version }}" 75 | base: "main" 76 | body: | 77 | Automated release-prep from commit ${{ github.sha }}. 78 | labels: "maintenance" 79 | -------------------------------------------------------------------------------- /.github/workflows/tooling_mend_ruby.yml: -------------------------------------------------------------------------------- 1 | # This is a generic workfloww that can be used to scan 2 | # content-and-tooling projects for vulnerabilities. 3 | name: mend 4 | 5 | on: 6 | workflow_call: 7 | inputs: 8 | api_key: 9 | default: '' 10 | type: string 11 | token: 12 | default: '' 13 | type: string 14 | product_name: 15 | default: 'DevX' 16 | type: string 17 | ruby_version: 18 | description: "The target Ruby version." 19 | required: false 20 | default: "3.1" 21 | type: "string" 22 | 23 | env: 24 | MEND_API_KEY: ${{ secrets.MEND_API_KEY != '' && secrets.MEND_API_KEY || inputs.api_key }} 25 | MEND_TOKEN: ${{ secrets.MEND_TOKEN != '' && secrets.MEND_TOKEN || inputs.token }} 26 | PRODUCT_NAME: ${{ inputs.PRODUCT_NAME != '' && inputs.PRODUCT_NAME || inputs.product_name }} 27 | REQUIRE_SECRETS: MEND_API_KEY MEND_TOKEN 28 | # ENABLE PUPPETCORE. The calling workflow must: 29 | # - Set a valid PUPPET_FORGE_TOKEN secret on its repository. 30 | # - Set ruby_version >= 3.1 to override this workflow's default 2.7; otherwise bundle install will fail. 31 | PUPPET_FORGE_TOKEN: ${{ secrets.PUPPET_FORGE_TOKEN || secrets.PUPPET_FORGE_TOKEN_PUBLIC }} 32 | BUNDLE_RUBYGEMS___PUPPETCORE__PUPPET__COM: "forge-key:${{ secrets.PUPPET_FORGE_TOKEN || secrets.PUPPET_FORGE_TOKEN_PUBLIC }}" 33 | 34 | jobs: 35 | mend: 36 | if: github.event.pull_request.head.repo.fork == false 37 | runs-on: "ubuntu-latest" 38 | continue-on-error: ${{ contains(fromJson('["puppetlabs","puppet-toy-chest"]'), github.repository_owner) != true }} 39 | steps: 40 | - name: "check requirements" 41 | run: | 42 | declare -a MISSING 43 | for V in ${REQUIRE_SECRETS} ; do 44 | [[ -z "${!V}" ]] && MISSING+=($V) 45 | done 46 | if [ ${#MISSING[@]} -gt 0 ] ; then 47 | echo "::warning::missing required secrets: ${MISSING[@]}" 48 | exit 1 49 | fi 50 | 51 | - name: "checkout" 52 | if: success() 53 | uses: "actions/checkout@v4" 54 | with: 55 | fetch-depth: 1 56 | 57 | - name: "setup ruby" 58 | if: success() 59 | uses: "ruby/setup-ruby@v1" 60 | with: 61 | ruby-version: ${{ inputs.ruby_version }} 62 | 63 | - name: "bundle lock" 64 | if: success() 65 | run: bundle lock 66 | 67 | - uses: "actions/setup-java@v4" 68 | if: success() 69 | with: 70 | distribution: "temurin" 71 | java-version: "17" 72 | 73 | - name: "download" 74 | if: success() 75 | run: curl -o wss-unified-agent.jar https://unified-agent.s3.amazonaws.com/wss-unified-agent.jar 76 | 77 | - name: "scan" 78 | if: success() 79 | run: java -jar wss-unified-agent.jar 80 | env: 81 | WS_APIKEY: ${{ env.MEND_API_KEY }} 82 | WS_WSS_URL: https://saas-eu.whitesourcesoftware.com/agent 83 | WS_USERKEY: ${{ env.MEND_TOKEN }} 84 | WS_PRODUCTNAME: ${{ env.PRODUCT_NAME }} 85 | WS_PROJECTNAME: ${{ github.event.repository.name }} 86 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # cat-github-actions 2 | 3 | This repository contains GitHub Actions workflows and configurations for automating various tasks related to the `Content and Tooling (CAT)` project. These workflows help ensure code quality, automate testing, and streamline the release process. 4 | 5 | ## Workflows 6 | 7 | The following are the workflows we currently maintain in this repository: 8 | * gem_acceptance: runs automated acceptance CI on tooling PRs 9 | * gem_ci: runs automated unit testing CI on tooling PRs 10 | * gem_release_prep: prepares the gem for release by running necessary pre-release checks and tasks 11 | * gem_release: handles the release process of the gem, including versioning and publishing 12 | * lint: runs linting checks on the codebase to ensure code quality and consistency 13 | * mend_ruby: automates the usage of mend for vulnerability scanning on modules 14 | * module_acceptance: runs automated acceptance CI for modules on PRs 15 | * module_ci: runs automated unit testing CI for modules on PRs 16 | * module_release_prep: prepares the module for release by running necessary pre-release checks and tasks 17 | * module_release: handles the release process of the module, including versioning and publishing 18 | * tooling_mend_ruby: automates the usage of mend for vulnerability scanning on tools 19 | * workflow-restarter-test: tests the workflow restarter functionality 20 | * workflow-restarter: restarts workflows that have failed or need to be re-run 21 | 22 | Note: For more information about workflows like workflow-restarter, check out our [docs](./docs/) 23 | 24 | ```mermaid 25 | flowchart TD 26 | A[Content and Tooling] --> B & C 27 | B(Modules) --> D & F 28 | B@{ shape: div-rect } 29 | C(Tools) --> G & I 30 | C@{ shape: div-rect } 31 | D@{ shape: procs, label: "Release"} --> N & O 32 | F@{ shape: procs, label: "Testing"} --> J & K & L & M & Z 33 | G@{ shape: procs, label: "Testing"} --> P & Q & R & M & Z 34 | I@{ shape: procs, label: "Release"} --> S & T 35 | J@{ shape: lin-rect, label: "mend_ruby.yml"} 36 | K@{ shape: lin-rect, label: "module_acceptance.yml"} 37 | L@{ shape: lin-rect, label: "module_ci.yml"} 38 | M@{ shape: lin-rect, label: "lint.yml"} 39 | N@{ shape: lin-rect, label: "module_release_prep.yml"} 40 | O@{ shape: lin-rect, label: "module_release.yml"} 41 | P@{ shape: lin-rect, label: "gem_ci.yml"} 42 | Q@{ shape: lin-rect, label: "gem_acceptance.yml"} 43 | R@{ shape: lin-rect, label: "tooling_mend_ruby.yml"} 44 | S@{ shape: lin-rect, label: "gem_release_prep.yml"} 45 | T@{ shape: lin-rect, label: "gem_release.yml"} 46 | Z@{ shape: lin-rect, label: "workflow_restarter.yml"} 47 | ``` 48 | ## License 49 | 50 | Copyright 2025 Perforce 51 | 52 | Licensed under the Apache License, Version 2.0 (the "License"); 53 | you may not use this file except in compliance with the License. 54 | You may obtain a copy of the License at 55 | 56 | http://www.apache.org/licenses/LICENSE-2.0 57 | 58 | Unless required by applicable law or agreed to in writing, software 59 | distributed under the License is distributed on an "AS IS" BASIS, 60 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 61 | See the License for the specific language governing permissions and 62 | limitations under the License. 63 | -------------------------------------------------------------------------------- /docs/how-to/how_to_inject_puppetcore_authentication_into_the_shared_workflows.md: -------------------------------------------------------------------------------- 1 | # How to inject puppetcore authentication into the shared workflows 2 | 3 | ## Description 4 | 5 | This guide explains how to configure your repository to consume the following shared workflows and ensure that puppetcore dependencies are successfully bundle installed: 6 | 7 | - `module_ci.yml` 8 | - `gem_ci.yml` 9 | - `tooling_mend_ruby.yml` 10 | 11 | The above workflows are backward compatible and designed to work with both puppetcore and non-puppetcore repositories. 12 | 13 | ## Prerequisites 14 | 15 | - A puppetcore repository that needs to use a shared workflow. 16 | - Access to repository settings to configure secrets 17 | - A valid `PUPPET_FORGE_TOKEN` with access to the private puppetcore gem source 18 | 19 | ## Configuration Requirements 20 | 21 | ### Required Settings 22 | 23 | To use PuppetCore Gems with the above shared workflows, your repository must meet these requirements: 24 | 25 | 1. **Set up the PUPPET_FORGE_TOKEN secret**: 26 | - Navigate to your repository on GitHub 27 | - Go to Settings > Secrets and variables > Actions 28 | - Add a new repository secret named `PUPPET_FORGE_TOKEN` with your valid token value 29 | 30 | 2. **Configure Ruby Version to be >= 3.1**: 31 | - Ruby version >= 3.1 is required for PuppetCore Gems. 32 | - Some shared worklows, like `module_ci.yml`, have an old default Ruby version that must be overridden 33 | 34 | ## Usage 35 | 36 | Create or update your workflow file (typically `.github/workflows/ci.yml`) to look something like: 37 | 38 | ```yaml 39 | name: "ci" 40 | 41 | on: 42 | pull_request: 43 | branches: 44 | - "main" 45 | workflow_dispatch: 46 | 47 | jobs: 48 | Spec: 49 | uses: "puppetlabs/cat-github-actions/.github/workflows/module_ci.yml@main" 50 | with: 51 | run_shellcheck: true 52 | ruby_version: '3.1' # Required for PuppetCore Gems 53 | secrets: "inherit" # Required to pass PUPPET_FORGE_TOKEN 54 | ``` 55 | 56 | For 2 example consumers, see: 57 | 58 | * [puppet-upgrade ci.yml](https://github.com/puppetlabs/puppet-upgrade/blob/main/.github/workflows/ci.yml) 59 | * [provision ci.yml](https://github.com/puppetlabs/provision/blob/main/.github/workflows/ci.yml) 60 | 61 | ## How It Works 62 | 63 | The above shared workflows are designed to install gems from . They 64 | 65 | - **Inherit** the `PUPPET_FORGE_TOKEN` secret from the consumer repository and then **set** an environment variable of the same name. This environment variable is required by some repositories to "switch" between either the public or puppetcore gems. 66 | - **Set** the `BUNDLE_RUBYGEMS___PUPPETCORE__PUPPET__COM` environment variable ensuring authentication against the gemsource. For example, 67 | 68 | ```bash 69 | BUNDLE_RUBYGEMS___PUPPETCORE__PUPPET__COM: "forge-key:${{ secrets.PUPPET_FORGE_TOKEN }}" 70 | ``` 71 | 72 | ## Troubleshooting 73 | 74 | Common issues and their solutions: 75 | 76 | - **Bundle install fails**: Ensure Ruby version is set to at least 3.1 77 | - **Authentication errors**: Verify the PUPPET_FORGE_TOKEN is correctly set and has appropriate permissions 78 | 79 | ## Appendix 80 | 81 | ### Security Considerations 82 | 83 | - Use the `secrets: "inherit"` pattern to securely pass tokens from your consumer to shared workflow. 84 | - Push secrets into environment variables for use by code. This is another github pattern that maintains redaction of secrets in logs 85 | -------------------------------------------------------------------------------- /.github/workflows/gem_ci.yml: -------------------------------------------------------------------------------- 1 | # This is a generic workflow for gem CI operations. 2 | name: "Gem CI" 3 | 4 | on: 5 | workflow_call: 6 | inputs: 7 | ruby_version: 8 | description: "The target Ruby version." 9 | required: false 10 | default: "3.1" 11 | type: "string" 12 | puppet_gem_version: 13 | description: "Specifies the version of the Puppet gem to be installed" 14 | required: false 15 | default: "~> 8.0" 16 | type: "string" 17 | rake_task: 18 | description: "The name of the rake task that executes tests" 19 | required: false 20 | default: "spec" 21 | type: "string" 22 | runs_on: 23 | description: "The operating system used for the runner." 24 | required: false 25 | default: "ubuntu-latest" 26 | type: "string" 27 | run_shellcheck: 28 | description: "Run shellcheck on all bash files" 29 | required: false 30 | default: false 31 | type: "boolean" 32 | 33 | # ENABLE PUPPETCORE. The calling workflow must: 34 | # - Set a valid PUPPET_FORGE_TOKEN secret on its repository. 35 | # - Set ruby_version >= 3.1 to override this workflow's default 2.7; otherwise bundle install will fail. 36 | env: 37 | PUPPET_GEM_VERSION: ${{ inputs.puppet_gem_version }} 38 | CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }} 39 | PUPPET_FORGE_TOKEN: ${{ secrets.PUPPET_FORGE_TOKEN || secrets.PUPPET_FORGE_TOKEN_PUBLIC }} 40 | BUNDLE_RUBYGEMS___PUPPETCORE__PUPPET__COM: "forge-key:${{ secrets.PUPPET_FORGE_TOKEN || secrets.PUPPET_FORGE_TOKEN_PUBLIC }}" 41 | 42 | jobs: 43 | spec: 44 | name: "spec" 45 | runs-on: ${{ inputs.runs_on }} 46 | 47 | steps: 48 | - name: "checkout" 49 | uses: "actions/checkout@v4" 50 | with: 51 | fetch-depth: 1 52 | - name: "shellcheck" 53 | uses: reviewdog/action-shellcheck@v1 54 | if: | 55 | inputs.run_shellcheck && 56 | inputs.ruby_version == ${{ inputs.ruby_version.default }} && 57 | inputs.runs_on == 'ubuntu-latest' 58 | with: 59 | check_all_files_with_shebangs: "true" 60 | 61 | - name: "export environment" 62 | run: | 63 | echo "PUPPET_GEM_VERSION=${{ inputs.puppet_gem_version }}" >> $GITHUB_ENV 64 | 65 | - name: "setup ruby" 66 | uses: "ruby/setup-ruby@v1" 67 | with: 68 | ruby-version: ${{ inputs.ruby_version }} 69 | bundler-cache: true 70 | 71 | - name: "bundle environment" 72 | run: | 73 | echo ::group::bundler environment 74 | bundle env 75 | echo ::endgroup:: 76 | 77 | - name: "rubocop" 78 | run: | 79 | bundle exec rubocop --format github 80 | 81 | - name: "spec" 82 | run: | 83 | bundle exec rake ${{ inputs.rake_task }} 84 | 85 | - name: Upload coverage reports to Codecov 86 | # Only upload coverage reports once per CI.yml trigger, as multiple concurrent uploads can cause issues 87 | # so limit this step to only run once, with a conditional check for the latest Ruby version, on Ubuntu-latest 88 | # the check on inputs.rake_task helps to ensure this is only run when coverage rake_task has been executed 89 | if: | 90 | contains(inputs.rake_task, 'coverage') && 91 | inputs.runs_on == 'ubuntu-latest' && 92 | inputs.ruby_version == '3.1' && 93 | env.CODECOV_TOKEN != '' 94 | uses: codecov/codecov-action@v4 95 | with: 96 | token: ${{ secrets.CODECOV_TOKEN }} 97 | files: ./coverage/.resultset.json 98 | fail_ci_if_error: true 99 | verbose: true 100 | -------------------------------------------------------------------------------- /.github/workflows/module_release_prep.yml: -------------------------------------------------------------------------------- 1 | # This is a generic workflow for creating a release prep 2 | # pr for a puppet module. 3 | # It requires that the caller sets `secrets: inherit` to ensure 4 | # that secrets are visible from steps in this workflow. 5 | name: "Module Release Prep" 6 | 7 | on: 8 | workflow_call: 9 | inputs: 10 | version: 11 | description: "Module version to be released." 12 | required: true 13 | type: "string" 14 | 15 | 16 | jobs: 17 | release_prep: 18 | name: "Release prep" 19 | runs-on: "ubuntu-latest" 20 | 21 | env: 22 | BUNDLE_WITHOUT: development:system_tests 23 | 24 | steps: 25 | 26 | - name: "Checkout" 27 | uses: "actions/checkout@v4" 28 | with: 29 | fetch-depth: 0 30 | 31 | - name: "Generate changelog" 32 | run: | 33 | export GH_HOST=github.com 34 | gh extension install chelnak/gh-changelog 35 | gh changelog new --next-version v${{ github.event.inputs.version }} 36 | env: 37 | GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} 38 | 39 | - name: "Update metadata.json" 40 | run: | 41 | current_version=$(jq --raw-output .version metadata.json) 42 | # Update version in metadata.json, only matching first occurrence 43 | sed -i "0,/$current_version/s//${{ github.event.inputs.version }}/" $(find . -name 'metadata.json') 44 | 45 | - name: "setup ruby" 46 | uses: "ruby/setup-ruby@v1" 47 | with: 48 | ruby-version: "3.1" 49 | bundler-cache: "true" 50 | bundler: 2.4.22 51 | 52 | - name: "bundle environment" 53 | run: | 54 | echo ::group::bundler environment 55 | bundle env 56 | echo ::endgroup:: 57 | 58 | - name: "Update REFERENCE.md" 59 | run: | 60 | bundle exec rake strings:generate:reference 61 | 62 | - name: "Get version" 63 | id: "get_version" 64 | run: | 65 | echo "version=$(jq --raw-output .version metadata.json)" >> $GITHUB_OUTPUT 66 | 67 | - name: "Check if a release is necessary" 68 | id: "check" 69 | run: | 70 | git diff --quiet CHANGELOG.md && echo "release=false" >> $GITHUB_OUTPUT || echo "release=true" >> $GITHUB_OUTPUT 71 | 72 | - name: "Commit changes" 73 | if: ${{ steps.check.outputs.release == 'true' }} 74 | run: | 75 | git config --local user.email "${{ github.repository_owner }}@users.noreply.github.com" 76 | git config --local user.name "GitHub Actions" 77 | git add . 78 | git commit -m "Release prep v${{ steps.get_version.outputs.version }}" 79 | 80 | - name: "Create pull Request" 81 | uses: "peter-evans/create-pull-request@v7" 82 | if: ${{ steps.check.outputs.release == 'true' }} 83 | with: 84 | token: ${{ secrets.GITHUB_TOKEN }} 85 | commit-message: "Release prep v${{ steps.get_version.outputs.version }}" 86 | branch: "release-prep" 87 | delete-branch: true 88 | title: "Release prep v${{ steps.get_version.outputs.version }}" 89 | base: "main" 90 | body: | 91 | Automated release-prep through [pdk-templates](https://github.com/puppetlabs/pdk-templates/blob/main/moduleroot/.github/workflows/release_prep.yml.erb) from commit ${{ github.sha }}. 92 | Please verify before merging: 93 | - [ ] last [nightly](https://github.com/${{ github.repository }}/actions/workflows/nightly.yml) run is green 94 | - [ ] [Changelog](https://github.com/${{ github.repository }}/blob/release-prep/CHANGELOG.md) is readable and has no unlabeled pull requests 95 | - [ ] Ensure the [changelog](https://github.com/${{ github.repository }}/blob/release-prep/CHANGELOG.md) version and [metadata](https://github.com/${{ github.repository }}/blob/release-prep/metadata.json) version match 96 | labels: "maintenance" 97 | -------------------------------------------------------------------------------- /.github/workflows/module_ci.yml: -------------------------------------------------------------------------------- 1 | # This is a generic workflow for Puppet module CI operations. 2 | name: "Module CI" 3 | 4 | on: 5 | workflow_call: 6 | inputs: 7 | runs_on: 8 | description: "The operating system used for the runner." 9 | required: false 10 | default: "ubuntu-latest" 11 | type: "string" 12 | flags: 13 | description: "Additional flags to pass to matrix_from_metadata_v3." 14 | required: false 15 | default: '' 16 | type: "string" 17 | run_shellcheck: 18 | description: "Run shellcheck on all bash files" 19 | required: false 20 | default: false 21 | type: "boolean" 22 | ruby_version: 23 | description: "Ruby version to use" 24 | required: false 25 | default: '3.1' 26 | type: "string" 27 | additional_packages: 28 | description: "Space-separated list of OS packages to install on the runner (apt-get)" 29 | required: false 30 | default: '' 31 | type: "string" 32 | 33 | # ENABLE PUPPETCORE. The calling workflow must: 34 | # - Set a valid PUPPET_FORGE_TOKEN secret on its repository. 35 | # - Set ruby_version >= 3.1 to override this workflow's default 2.7; otherwise bundle install will fail. 36 | env: 37 | PUPPET_FORGE_TOKEN: ${{ secrets.PUPPET_FORGE_TOKEN || secrets.PUPPET_FORGE_TOKEN_PUBLIC }} 38 | BUNDLE_RUBYGEMS___PUPPETCORE__PUPPET__COM: "forge-key:${{ secrets.PUPPET_FORGE_TOKEN || secrets.PUPPET_FORGE_TOKEN_PUBLIC }}" 39 | 40 | jobs: 41 | setup_matrix: 42 | name: "Setup Test Matrix" 43 | runs-on: ${{ inputs.runs_on }} 44 | outputs: 45 | spec_matrix: ${{ steps.get-matrix.outputs.spec_matrix }} 46 | 47 | env: 48 | BUNDLE_WITHOUT: release_prep 49 | 50 | steps: 51 | 52 | - name: "Checkout" 53 | uses: "actions/checkout@v4" 54 | with: 55 | ref: ${{ github.event.pull_request.head.sha }} 56 | 57 | - name: "Install additional packages" 58 | if: ${{ inputs.additional_packages != '' }} 59 | run: sudo apt-get install -y ${{ inputs.additional_packages }} 60 | 61 | - name: "Setup ruby" 62 | uses: "ruby/setup-ruby@v1" 63 | with: 64 | ruby-version: ${{ inputs.ruby_version }} 65 | bundler-cache: true 66 | 67 | - name: "Bundle environment" 68 | run: | 69 | echo ::group::bundler environment 70 | bundle env 71 | echo ::endgroup:: 72 | 73 | - name: Setup Spec Test Matrix 74 | id: get-matrix 75 | run: | 76 | bundle exec matrix_from_metadata_v3 ${{ inputs.flags }} 77 | 78 | spec: 79 | name: "Spec tests (Puppet: ${{matrix.puppet_version}}, Ruby Ver: ${{matrix.ruby_version}})" 80 | needs: "setup_matrix" 81 | runs-on: ${{ inputs.runs_on }} 82 | strategy: 83 | fail-fast: false 84 | matrix: ${{ fromJson( needs.setup_matrix.outputs.spec_matrix ) }} 85 | 86 | env: 87 | BUNDLE_WITHOUT: release_prep 88 | PUPPET_GEM_VERSION: ${{ matrix.puppet_version }} 89 | FACTER_GEM_VERSION: 'https://github.com/puppetlabs/facter#main' # why is this set? 90 | 91 | steps: 92 | - name: "Checkout" 93 | uses: "actions/checkout@v4" 94 | with: 95 | ref: ${{ github.event.pull_request.head.sha }} 96 | 97 | - name: "Install additional packages" 98 | if: ${{ inputs.additional_packages != '' }} 99 | run: sudo apt-get install -y ${{ inputs.additional_packages }} 100 | 101 | - name: "Setup ruby" 102 | uses: "ruby/setup-ruby@v1" 103 | with: 104 | ruby-version: ${{matrix.ruby_version}} 105 | bundler-cache: true 106 | 107 | - name: "Bundle environment" 108 | run: | 109 | echo ::group::bundler environment 110 | bundle env 111 | echo ::endgroup:: 112 | 113 | - name: "shellcheck" 114 | uses: reviewdog/action-shellcheck@v1 115 | if: | 116 | inputs.run_shellcheck && 117 | inputs.runs_on == 'ubuntu-latest' && 118 | matrix.ruby_version == '3.1' 119 | with: 120 | check_all_files_with_shebangs: "true" 121 | 122 | - name: "Run Static & Syntax Tests" 123 | run: | 124 | bundle exec rake syntax lint metadata_lint check:symlinks check:git_ignore check:dot_underscore check:test_file rubocop 125 | bundle exec dependency-checker metadata.json 126 | 127 | - name: "Run tests" 128 | run: | 129 | bundle exec rake parallel_spec 130 | -------------------------------------------------------------------------------- /docs/workflow-restarter.md: -------------------------------------------------------------------------------- 1 | # workflow-restarter 2 | 3 | ## Description 4 | 5 | Although GitHub provides built-in programatic mechanisms for retrying individual steps within a workflow, it doesn't provide one for retrying entire workflows. One possible reason for this limitation may be to prevent accidental infinite retry loops around failing workflows. Any workflow that fails, however, can be manually re-started from the failed workflow on the `Actions` tab of the repository. For more information on restarting github worklows see [Re-running workflows and jobs](https://docs.github.com/en/actions/managing-workflow-runs/re-running-workflows-and-jobs). 6 | 7 | Nevertheless, it is possible to programmatically restart a workflow after it fails and the section below shows how to restart a failing workflow 3 times using the `workflow-restarter` re-usable workflow. 8 | 9 | ## Usage 10 | 11 | If setting up the the `workflow-restarter` for the first time, then make sure to initialize it first and then configure another workflow to programmatically restart on failure. In other words, you need to create 2 PRs 12 | 13 | * Create one PR that only adds the `.github/workflows/{workflow-restarter.yml,workflow-restarter-test.yml}` files. See [Create the first PR...](#create-the-first-pr-to-initialize-the-workflow-restarter) section below. 14 | * Create a second PR to add the `on-failure-workflow-restarter-proxy` to existing workflows. See [Create the second PR...](#create-the-second-pr-to-re-use-it-in-other-workflows) section below. 15 | 16 | ### Create the first PR to initialize the `Workflow Restarter` 17 | 18 | In order to begin using the workflow-restarter, you need to first raise a PR and add the workflow restarter to your target repository. In other words, for the `workflow-restarter` to work, it must be merged into the `main` branch before it can be referenced by other workflows (this seems to be a github action feature). 19 | 20 | * Copy [workflow-restarter.yml](./workflow-restarter/workflow-restarter.yml) and [workflow-restarter-test.yml](./workflow-restarter/workflow-restarter-test.yml) to the `.github/workflows` directory of your target directory. 21 | * Raise and merge a PR adding the above to the main branch of your repository. 22 | * Verify that the `Workflow Restarter TEST` workflow works as expected. See the [Appendix](#verify-workflow-restarter-with-workflow-restarter-test) for more information on what you should expect to see. 23 | 24 | Once the above `Workflow Restarter TEST` is working then you should be able to add the workflow restarter to any of your existing github workflows, which is described in the next section. 25 | 26 | ### Create the second PR to re-use it in other workflows 27 | 28 | The key here is to re-use the `on-failure-workflow-restarter-proxy` located in the `Workflow Restarter TEST`. For example, the following will trigger a restart if either the `acceptance` or the `unit` jobs preceeding it fail. A restart of the failing jobs will be attempted 3 times at which point if the failing jobs continue to fail, then the workflow will be marked as failed. If, however, at any point the `acceptance` and `unit` both pass fine then the restarted workflow will be marked as successful 29 | 30 | ```yaml 31 | on-failure-workflow-restarter-proxy: 32 | # (1) run this job after the "acceptance" job and... 33 | needs: [acceptance, unit] 34 | # (2) continue ONLY IF "acceptance" fails 35 | if: always() && needs.acceptance.result == 'failure' || needs.unit.result == 'failure' 36 | runs-on: ubuntu-latest 37 | steps: 38 | # (3) checkout this repository in order to "see" the following custom action 39 | - name: Checkout repository 40 | uses: actions/checkout@v4 41 | 42 | # (4) "use" the custom action to retrigger the failed "acceptance job" above 43 | - name: Trigger reusable workflow 44 | uses: "puppetlabs/cat-github-actions/.github/actions/workflow-restarter-proxy@main" 45 | env: 46 | SOURCE_GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 47 | with: 48 | repository: ${{ github.repository }} 49 | run_id: ${{ github.run_id }} 50 | ``` 51 | 52 | As well as adding updates to your existing workflows, you may also want to remove the `.github/workflows/workflow-restarter-test.yml` at this stage to keep your repository workflows tidy. 53 | 54 | ## Appendix 55 | 56 | ### Verify `Workflow Restarter` with `Workflow Restarter TEST` 57 | 58 | The following shows 3 `Workflow Restarter` occuring after the `Workflow Restarter TEST`, which is set to fail continuously. 59 | 60 | ![alt text](./workflow-restarter/image.png) 61 | 62 | Looking closer at the `Workflow Restarter TEST` reveals 63 | 64 | * that the workflow includes 2 jobs `unit` and `acceptance`; and 65 | * that the workflow has been re-run 3 times, e.g., 66 | 67 | ![alt text](./workflow-restarter/image-1.png) 68 | 69 | Further, the following sequence of screenshots shows that only failed jobs are re-run. 70 | 71 | * The `on-failure-workflow-restarter` job **(1)** is triggered by the failure of the `unit` job and **(2)** successfully calls the `workflow-restarter` workflow 72 | * The `workflow-restarter` in turn triggers a re-run of the `unit` job **(3)** and the `Workflow Restarter TEST` shows this as an incremented attempt count at **(4)**. 73 | 74 | ![alt text](./workflow-restarter/image-2.png) 75 | 76 | ![alt text](./workflow-restarter/image-3.png) 77 | 78 | ![alt text](./workflow-restarter/image-4.png) 79 | -------------------------------------------------------------------------------- /.github/workflows/module_acceptance.yml: -------------------------------------------------------------------------------- 1 | # This is a generic workflow for Puppet module acceptance operations. 2 | name: "Module Acceptance" 3 | 4 | on: 5 | workflow_call: 6 | inputs: 7 | runs_on: 8 | description: "The operating system used for the runner." 9 | required: false 10 | type: "string" 11 | flags: 12 | description: "Additional flags to pass to matrix_from_metadata_v3." 13 | required: false 14 | default: '' 15 | type: "string" 16 | service_url: 17 | description: "The service URL to target when provisioning from GCP." 18 | required: false 19 | default: 'https://facade-release-6f3kfepqcq-ew.a.run.app/v1/provision' 20 | type: "string" 21 | kernel_modules: 22 | description: "Volume map host kernel /lib/modules into docker container" 23 | default: true 24 | type: boolean 25 | disable_apparmor: 26 | description: "Disable and stop apparmor" 27 | default: false 28 | type: boolean 29 | 30 | 31 | # ENABLE PUPPETCORE. The calling workflow must: 32 | # - Set a valid PUPPET_FORGE_TOKEN secret on its repository. 33 | env: 34 | PUPPET_FORGE_TOKEN: ${{ secrets.PUPPET_FORGE_TOKEN || secrets.PUPPET_FORGE_TOKEN_PUBLIC }} 35 | BUNDLE_RUBYGEMS___PUPPETCORE__PUPPET__COM: "forge-key:${{ secrets.PUPPET_FORGE_TOKEN || secrets.PUPPET_FORGE_TOKEN_PUBLIC }}" 36 | SERVICE_URL: ${{ inputs.service_url }} 37 | 38 | jobs: 39 | 40 | setup_matrix: 41 | name: "Setup Test Matrix" 42 | runs-on: ubuntu-latest 43 | outputs: 44 | acceptance_matrix: ${{ steps.get-matrix.outputs.matrix }} 45 | 46 | env: 47 | BUNDLE_WITHOUT: release_prep 48 | 49 | steps: 50 | 51 | - name: "Checkout" 52 | uses: "actions/checkout@v4" 53 | 54 | - name: "Setup ruby" 55 | uses: "ruby/setup-ruby@v1" 56 | with: 57 | ruby-version: "3.1" 58 | bundler-cache: true 59 | 60 | - name: "Bundle environment" 61 | run: | 62 | echo ::group::bundler environment 63 | bundle env 64 | echo ::endgroup:: 65 | 66 | - name: Setup Test Matrix 67 | id: get-matrix 68 | run: | 69 | bundle exec matrix_from_metadata_v3 ${{ inputs.flags }} 70 | 71 | acceptance: 72 | name: "Acceptance tests (${{matrix.platforms.label}}, ${{matrix.collection.collection || matrix.collection}})" 73 | needs: "setup_matrix" 74 | runs-on: ${{ inputs.runs_on || matrix.platforms.runner }} 75 | timeout-minutes: 180 76 | strategy: 77 | fail-fast: false 78 | matrix: ${{ fromJson( needs.setup_matrix.outputs.acceptance_matrix ) }} 79 | 80 | env: 81 | BUNDLE_WITHOUT: release_prep 82 | PUPPET_GEM_VERSION: '~> 8.9' 83 | FACTER_GEM_VERSION: 'https://github.com/puppetlabs/facter#main' # why is this set? 84 | TWINGATE_PUBLIC_REPO_KEY: ${{ secrets.TWINGATE_PUBLIC_REPO_KEY }} 85 | 86 | steps: 87 | - name: "Install Twingate" 88 | uses: "twingate/github-action@v1" 89 | with: 90 | service-key: ${{ env.TWINGATE_PUBLIC_REPO_KEY }} 91 | 92 | - name: Fix DNS 93 | run: | 94 | echo "=== Remove Azure DNS from eth0 interface ===" 95 | sudo resolvectl dns eth0 "" 96 | 97 | echo "=== Configure Twingate DNS properly ===" 98 | sudo resolvectl dns sdwan0 100.95.0.251 100.95.0.252 99 | sudo resolvectl domain sdwan0 delivery.puppetlabs.net 100 | 101 | echo "=== Flush DNS cache ===" 102 | sudo resolvectl flush-caches 103 | 104 | echo "=== Check new configuration ===" 105 | resolvectl status 106 | 107 | echo "=== Test DNS resolution ===" 108 | nslookup artifactory.delivery.puppetlabs.net 109 | 110 | - name: "Checkout" 111 | uses: "actions/checkout@v4" 112 | 113 | - name: "Disable Apparmor" 114 | if: ${{ inputs.disable_apparmor }} 115 | run: | 116 | if command -v apparmor_parser >/dev/null ; then 117 | sudo find /etc/apparmor.d/ -maxdepth 1 -type f -exec ln -sf {} /etc/apparmor.d/disable/ \; 118 | sudo apparmor_parser -R /etc/apparmor.d/disable/* || true 119 | sudo systemctl disable apparmor 120 | sudo systemctl stop apparmor 121 | fi 122 | 123 | - name: "Setup ruby" 124 | uses: "ruby/setup-ruby@v1" 125 | with: 126 | ruby-version: "3.1" 127 | bundler-cache: true 128 | 129 | - name: "Bundle environment" 130 | run: | 131 | echo ::group::bundler environment 132 | bundle env 133 | echo ::endgroup:: 134 | 135 | - name: "Provision environment" 136 | run: | 137 | if [[ "${{ inputs.kernel_modules }}" == "true" ]] && [[ "${{matrix.platforms.provider}}" =~ docker* ]] ; then 138 | DOCKER_RUN_OPTS="docker_run_opts: {'--volume': '/lib/modules/$(uname -r):/lib/modules/$(uname -r)'}" 139 | else 140 | DOCKER_RUN_OPTS='' 141 | fi 142 | bundle exec rake "litmus:provision[${{matrix.platforms.provider}},${{ matrix.platforms.image }},$DOCKER_RUN_OPTS]" 143 | # Redact password 144 | FILE='spec/fixtures/litmus_inventory.yaml' 145 | sed -e 's/password: .*/password: "[redacted]"/' < $FILE || true 146 | 147 | - name: "Install Puppet agent" 148 | run: | 149 | if [[ "${{ matrix.collection.version }}" ]] ; then 150 | export PUPPET_VERSION=${{ matrix.collection.version }} 151 | bundle exec rake 'litmus:install_agent[${{ matrix.collection.collection }}]' 152 | else 153 | bundle exec rake 'litmus:install_agent[${{ matrix.collection }}]' 154 | fi 155 | 156 | - name: "Install module" 157 | run: | 158 | bundle exec rake 'litmus:install_module' 159 | 160 | - name: "Run acceptance tests" 161 | run: | 162 | bundle exec rake 'litmus:acceptance:parallel' 163 | 164 | - name: "Remove test environment" 165 | if: ${{ always() }} 166 | continue-on-error: true 167 | run: | 168 | if [[ -f spec/fixtures/litmus_inventory.yaml ]]; then 169 | bundle exec rake 'litmus:tear_down' 170 | fi 171 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright [yyyy] [name of copyright owner] 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. --------------------------------------------------------------------------------